diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 22deba9..1e320fd 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -12,8 +12,8 @@ android { applicationId = "es.chiteroman.playintegrityfix" minSdk = 26 targetSdk = 34 - versionCode = 15702 - versionName = "v15.7.2" + versionCode = 15800 + versionName = "v15.8" multiDexEnabled = false buildFeatures { @@ -30,14 +30,23 @@ android { externalNativeBuild { cmake { arguments += "-DANDROID_STL=none" - arguments += "-DCMAKE_BUILD_TYPE=MinSizeRel" + arguments += "-DCMAKE_BUILD_TYPE=Release" arguments += "-DPlugin.Android.BionicLinkerUtil=ON" + cFlags += "-fvisibility=hidden" + cFlags += "-fvisibility-inlines-hidden" + cFlags += "-flto" + cFlags += "-O3" + cFlags += "-mllvm -polly" + cppFlags += "-std=c++20" cppFlags += "-fno-exceptions" cppFlags += "-fno-rtti" cppFlags += "-fvisibility=hidden" cppFlags += "-fvisibility-inlines-hidden" + cppFlags += "-flto" + cppFlags += "-O3" + cppFlags += "-mllvm -polly" } } } diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 6c6492c..0107057 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -10,4 +10,4 @@ add_library(${CMAKE_PROJECT_NAME} SHARED main.cpp) add_subdirectory(Dobby) -target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC log dobby_static) \ No newline at end of file +target_link_libraries(${CMAKE_PROJECT_NAME} log dobby_static) \ No newline at end of file diff --git a/app/src/main/cpp/Dobby/CMakeLists.txt b/app/src/main/cpp/Dobby/CMakeLists.txt index 6907a14..75ef3e7 100644 --- a/app/src/main/cpp/Dobby/CMakeLists.txt +++ b/app/src/main/cpp/Dobby/CMakeLists.txt @@ -26,7 +26,7 @@ option(Plugin.SymbolResolver "Enable symbol resolver" ON) option(Plugin.ImportTableReplace "Enable import table replace " OFF) -option(Plugin.Android.BionicLinkerUtil "Enable android bionic linker util" OFF) +option(Plugin.Android.BionicLinkerUtil "Enable android bionic linker util" ON) option(DOBBY_BUILD_EXAMPLE "Build example" OFF) diff --git a/app/src/main/cpp/main.cpp b/app/src/main/cpp/main.cpp index b5e914b..df9d9de 100644 --- a/app/src/main/cpp/main.cpp +++ b/app/src/main/cpp/main.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include "dobby.h" #include "json.hpp" #include "zygisk.hpp" @@ -108,6 +110,11 @@ public: auto name = env->GetStringUTFChars(args->nice_name, nullptr); + if (name == nullptr) { + api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY); + return; + } + bool isGmsUnstable = std::string_view(name) == "com.google.android.gms.unstable"; env->ReleaseStringUTFChars(args->nice_name, name); @@ -151,16 +158,16 @@ public: close(fd); json = nlohmann::json::parse(jsonVector, nullptr, false, true); + + parseJson(); } void postAppSpecialize(const zygisk::AppSpecializeArgs *args) override { if (dexVector.empty() || json.empty()) return; - parseJson(); + injectDex(); doHook(); - - injectDex(); } void preServerSpecialize(zygisk::ServerSpecializeArgs *args) override { @@ -274,31 +281,36 @@ static void companion(int fd) { std::vector dexVector, jsonVector; - FILE *dexFile = fopen(CLASSES_DEX, "rb"); + std::ifstream dexFile(CLASSES_DEX, std::ios::binary); - if (dexFile) { - fseek(dexFile, 0, SEEK_END); - dexSize = ftell(dexFile); - fseek(dexFile, 0, SEEK_SET); + if (dexFile.is_open()) { + dexFile.seekg(0, std::ios::end); + dexSize = dexFile.tellg(); + dexFile.seekg(0, std::ios::beg); dexVector.resize(dexSize); - fread(dexVector.data(), 1, dexSize, dexFile); - - fclose(dexFile); + dexFile.read(dexVector.data(), dexSize); + dexFile.close(); } - FILE *jsonFile = fopen(PIF_JSON, "rb"); - if (jsonFile == nullptr) jsonFile = fopen(PIF_JSON_DEFAULT, "rb"); + std::ifstream jsonFile; - if (jsonFile) { - fseek(jsonFile, 0, SEEK_END); - jsonSize = ftell(jsonFile); - fseek(jsonFile, 0, SEEK_SET); + if (std::filesystem::exists(PIF_JSON)) { + jsonFile = std::ifstream(PIF_JSON); + } else if (std::filesystem::exists(PIF_JSON_DEFAULT)) { + jsonFile = std::ifstream(PIF_JSON_DEFAULT); + } else { + LOGD("Couldn't open pif.json file. Did you remove it?"); + } + + if (jsonFile.is_open()) { + jsonFile.seekg(0, std::ios::end); + jsonSize = jsonFile.tellg(); + jsonFile.seekg(0, std::ios::beg); jsonVector.resize(jsonSize); - fread(jsonVector.data(), 1, jsonSize, jsonFile); - - fclose(jsonFile); + jsonFile.read(jsonVector.data(), jsonSize); + jsonFile.close(); } write(fd, &dexSize, sizeof(long)); diff --git a/app/src/main/java/es/chiteroman/playintegrityfix/CustomKeyStoreSpi.java b/app/src/main/java/es/chiteroman/playintegrityfix/CustomKeyStoreSpi.java index 078c8ce..36ba6bd 100644 --- a/app/src/main/java/es/chiteroman/playintegrityfix/CustomKeyStoreSpi.java +++ b/app/src/main/java/es/chiteroman/playintegrityfix/CustomKeyStoreSpi.java @@ -1,105 +1,116 @@ -package es.chiteroman.playintegrityfix; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.security.Key; -import java.security.KeyStoreException; -import java.security.KeyStoreSpi; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.util.Date; -import java.util.Enumeration; -import java.util.Locale; - -public final class CustomKeyStoreSpi extends KeyStoreSpi { - public static volatile KeyStoreSpi keyStoreSpi = null; - - @Override - public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException { - return keyStoreSpi.engineGetKey(alias, password); - } - - @Override - public Certificate[] engineGetCertificateChain(String alias) { - for (StackTraceElement stackTraceElement : Thread.currentThread().getStackTrace()) { - if (stackTraceElement.getClassName().toLowerCase(Locale.US).contains("droidguard")) { - EntryPoint.LOG("DroidGuard call certificate chain! Throw exception."); - throw new UnsupportedOperationException(); - } - } - return keyStoreSpi.engineGetCertificateChain(alias); - } - - @Override - public Certificate engineGetCertificate(String alias) { - return keyStoreSpi.engineGetCertificate(alias); - } - - @Override - public Date engineGetCreationDate(String alias) { - return keyStoreSpi.engineGetCreationDate(alias); - } - - @Override - public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) throws KeyStoreException { - keyStoreSpi.engineSetKeyEntry(alias, key, password, chain); - } - - @Override - public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) throws KeyStoreException { - keyStoreSpi.engineSetKeyEntry(alias, key, chain); - } - - @Override - public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException { - keyStoreSpi.engineSetCertificateEntry(alias, cert); - } - - @Override - public void engineDeleteEntry(String alias) throws KeyStoreException { - keyStoreSpi.engineDeleteEntry(alias); - } - - @Override - public Enumeration engineAliases() { - return keyStoreSpi.engineAliases(); - } - - @Override - public boolean engineContainsAlias(String alias) { - return keyStoreSpi.engineContainsAlias(alias); - } - - @Override - public int engineSize() { - return keyStoreSpi.engineSize(); - } - - @Override - public boolean engineIsKeyEntry(String alias) { - return keyStoreSpi.engineIsKeyEntry(alias); - } - - @Override - public boolean engineIsCertificateEntry(String alias) { - return keyStoreSpi.engineIsCertificateEntry(alias); - } - - @Override - public String engineGetCertificateAlias(Certificate cert) { - return keyStoreSpi.engineGetCertificateAlias(cert); - } - - @Override - public void engineStore(OutputStream stream, char[] password) throws CertificateException, IOException, NoSuchAlgorithmException { - keyStoreSpi.engineStore(stream, password); - } - - @Override - public void engineLoad(InputStream stream, char[] password) throws CertificateException, IOException, NoSuchAlgorithmException { - keyStoreSpi.engineLoad(stream, password); - } -} +package es.chiteroman.playintegrityfix; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.Key; +import java.security.KeyStoreException; +import java.security.KeyStoreSpi; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Date; +import java.util.Enumeration; + +public final class CustomKeyStoreSpi extends KeyStoreSpi { + public static KeyStoreSpi keyStoreSpi = null; + private static final String EAT_OID = "1.3.6.1.4.1.11129.2.1.25"; + private static final String ASN1_OID = "1.3.6.1.4.1.11129.2.1.17"; + private static final String KNOX_OID = "1.3.6.1.4.1.236.11.3.23.7"; + + @Override + public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException { + return keyStoreSpi.engineGetKey(alias, password); + } + + @Override + public Certificate[] engineGetCertificateChain(String alias) { + Certificate[] certificates = keyStoreSpi.engineGetCertificateChain(alias); + // This shouldn't happen... + if (certificates == null) { + throw new UnsupportedOperationException(); + } + // Is certificate chain ? + if (certificates.length > 1) { + if (certificates[0] instanceof X509Certificate x509Certificate) { + if (x509Certificate.getExtensionValue(EAT_OID) != null || x509Certificate.getExtensionValue(ASN1_OID) != null || x509Certificate.getExtensionValue(KNOX_OID) != null) { + EntryPoint.LOG("Certificate chain with dangerous extensions. Throw exception!"); + throw new UnsupportedOperationException(); + } + } + } + return certificates; + } + + @Override + public Certificate engineGetCertificate(String alias) { + return keyStoreSpi.engineGetCertificate(alias); + } + + @Override + public Date engineGetCreationDate(String alias) { + return keyStoreSpi.engineGetCreationDate(alias); + } + + @Override + public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) throws KeyStoreException { + keyStoreSpi.engineSetKeyEntry(alias, key, password, chain); + } + + @Override + public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) throws KeyStoreException { + keyStoreSpi.engineSetKeyEntry(alias, key, chain); + } + + @Override + public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException { + keyStoreSpi.engineSetCertificateEntry(alias, cert); + } + + @Override + public void engineDeleteEntry(String alias) throws KeyStoreException { + keyStoreSpi.engineDeleteEntry(alias); + } + + @Override + public Enumeration engineAliases() { + return keyStoreSpi.engineAliases(); + } + + @Override + public boolean engineContainsAlias(String alias) { + return keyStoreSpi.engineContainsAlias(alias); + } + + @Override + public int engineSize() { + return keyStoreSpi.engineSize(); + } + + @Override + public boolean engineIsKeyEntry(String alias) { + return keyStoreSpi.engineIsKeyEntry(alias); + } + + @Override + public boolean engineIsCertificateEntry(String alias) { + return keyStoreSpi.engineIsCertificateEntry(alias); + } + + @Override + public String engineGetCertificateAlias(Certificate cert) { + return keyStoreSpi.engineGetCertificateAlias(cert); + } + + @Override + public void engineStore(OutputStream stream, char[] password) throws CertificateException, IOException, NoSuchAlgorithmException { + keyStoreSpi.engineStore(stream, password); + } + + @Override + public void engineLoad(InputStream stream, char[] password) throws CertificateException, IOException, NoSuchAlgorithmException { + keyStoreSpi.engineLoad(stream, password); + } +} diff --git a/app/src/main/java/es/chiteroman/playintegrityfix/CustomProvider.java b/app/src/main/java/es/chiteroman/playintegrityfix/CustomProvider.java index f743e14..a185272 100644 --- a/app/src/main/java/es/chiteroman/playintegrityfix/CustomProvider.java +++ b/app/src/main/java/es/chiteroman/playintegrityfix/CustomProvider.java @@ -4,19 +4,17 @@ import java.security.Provider; public final class CustomProvider extends Provider { - public CustomProvider(Provider provider, boolean spoof) { + public CustomProvider(Provider provider) { super(provider.getName(), provider.getVersion(), provider.getInfo()); - putAll(provider); - - if (spoof) put("KeyStore.AndroidKeyStore", CustomKeyStoreSpi.class.getName()); + put("KeyStore.AndroidKeyStore", CustomKeyStoreSpi.class.getName()); } @Override public synchronized Service getService(String type, String algorithm) { EntryPoint.LOG(String.format("Service: '%s' | Algorithm: '%s'", type, algorithm)); - if ("AndroidKeyStore".equals(algorithm)) EntryPoint.spoofFields(); + new Thread(EntryPoint::spoofFields).start(); return super.getService(type, algorithm); } diff --git a/app/src/main/java/es/chiteroman/playintegrityfix/EntryPoint.java b/app/src/main/java/es/chiteroman/playintegrityfix/EntryPoint.java index 8013608..3807760 100644 --- a/app/src/main/java/es/chiteroman/playintegrityfix/EntryPoint.java +++ b/app/src/main/java/es/chiteroman/playintegrityfix/EntryPoint.java @@ -17,24 +17,21 @@ public final class EntryPoint { private static final Map map = new HashMap<>(); static { - boolean spoof = false; try { KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); + Field keyStoreSpi = keyStore.getClass().getDeclaredField("keyStoreSpi"); - Field field = keyStore.getClass().getDeclaredField("keyStoreSpi"); - field.setAccessible(true); + keyStoreSpi.setAccessible(true); - CustomKeyStoreSpi.keyStoreSpi = (KeyStoreSpi) field.get(keyStore); - - if (CustomKeyStoreSpi.keyStoreSpi != null) spoof = true; + CustomKeyStoreSpi.keyStoreSpi = (KeyStoreSpi) keyStoreSpi.get(keyStore); } catch (Throwable t) { - LOG("Error spoofing AndroidKeyStore: " + t); + LOG("Couldn't get keyStoreSpi: " + t); } Provider provider = Security.getProvider("AndroidKeyStore"); - Provider customProvider = new CustomProvider(provider, spoof); + Provider customProvider = new CustomProvider(provider); Security.removeProvider("AndroidKeyStore"); Security.insertProviderAt(customProvider, 1); @@ -74,10 +71,11 @@ public final class EntryPoint { map.forEach((field, s) -> { try { if (s.equals(field.get(null))) return; + field.setAccessible(true); field.set(null, s); LOG("Set " + field.getName() + " field value: " + s); - } catch (IllegalAccessException e) { - LOG("Couldn't access " + field.getName() + " value " + s + " | Exception: " + e); + } catch (Throwable t) { + LOG(t.toString()); } }); } diff --git a/changelog.md b/changelog.md index 0104f25..de94153 100644 --- a/changelog.md +++ b/changelog.md @@ -6,13 +6,7 @@ Device verdict should pass by default. If not, try removing /data/adb/pif.json file. DO NOT REMOVE pif.json in module's folder! -# v15.7.1 +# v15.8 -- Fix crash issue when JSON file have comments. -- Fix hooking in older Android versions. -- Fix CTS profile / Device verdict failures in few devices due bad spoofing code. -- Fix spoofing Provider issue. -- Added post-fs-data.sh script. -- Using latest (compileable) version of Dobby. -- Using RikkaW libcxx prefab. -- Update Gradle. \ No newline at end of file +- Correctly detect attestation certificate chain and block it. +- Misc improvements. \ No newline at end of file diff --git a/module/customize.sh b/module/customize.sh index c3ba052..bc1de0e 100644 --- a/module/customize.sh +++ b/module/customize.sh @@ -62,3 +62,18 @@ if [ -d "/system/app/EliteDevelopmentModule" ]; then ui_print "- EliteDevelopmentModule app removed." fi + +if [ -f "/data/adb/pif.json" ]; then + mv -f "/data/adb/pif.json" "/data/adb/pif.json.old" + ui_print "- Backup old pif.json file" + ui_print "- Module will use the default one" + ui_print "- If you want to use your custom fingerprint, remove .old extension" +fi + +ui_print "! If you don't pass DEVICE verdict after install (and reboot)" +ui_print "! Check kernel release name (In Shell run: 'uname -r')" +ui_print "! Google banned few strings in kernel to avoid users using custom kernels" +ui_print "! Try to use stock kernel or not banned one" +ui_print "! Kernels with 'bad' strings like 'lineageos' are banned!" +ui_print "! Check official XDA post to know more about this" +ui_print "! If you are using stock ROM without custom kernel just ignore this" diff --git a/module/module.prop b/module/module.prop index e452d1e..9fa876e 100644 --- a/module/module.prop +++ b/module/module.prop @@ -1,7 +1,7 @@ id=playintegrityfix name=Play Integrity Fix -version=v15.7.2 -versionCode=15702 +version=v15.8 +versionCode=15800 author=chiteroman -description=Universal modular fix for Play Integrity (and SafetyNet) on devices running Android 8+. +description=Universal modular fix for Play Integrity (and SafetyNet) on devices running Android 8-14. updateJson=https://raw.githubusercontent.com/chiteroman/PlayIntegrityFix/main/update.json diff --git a/module/post-fs-data.sh b/module/post-fs-data.sh index c61e7cf..dd92da4 100644 --- a/module/post-fs-data.sh +++ b/module/post-fs-data.sh @@ -17,21 +17,20 @@ resetprop_if_diff() { [ -z "$CURRENT" ] || [ "$CURRENT" = "$EXPECTED" ] || resetprop "$NAME" "$EXPECTED" } -# Conditional early sensitive properties - -# Samsung -resetprop_if_diff ro.boot.warranty_bit 0 -resetprop_if_diff ro.vendor.boot.warranty_bit 0 -resetprop_if_diff ro.vendor.warranty_bit 0 -resetprop_if_diff ro.warranty_bit 0 - -# OnePlus -resetprop_if_diff ro.is_ever_orange 0 - -# Microsoft, RootBeer resetprop_if_diff ro.build.tags release-keys -# Other +resetprop_if_diff ro.boot.warranty_bit 0 + +resetprop_if_diff ro.vendor.boot.warranty_bit 0 + +resetprop_if_diff ro.vendor.warranty_bit 0 + +resetprop_if_diff ro.warranty_bit 0 + +resetprop_if_diff ro.is_ever_orange 0 + resetprop_if_diff ro.build.type user + resetprop_if_diff ro.debuggable 0 + resetprop_if_diff ro.secure 1 diff --git a/module/service.sh b/module/service.sh index fe381b8..80b9de4 100644 --- a/module/service.sh +++ b/module/service.sh @@ -22,43 +22,33 @@ resetprop_if_match ro.boot.mode recovery unknown resetprop_if_match vendor.boot.mode recovery unknown # SELinux +resetprop_if_diff ro.boot.selinux enforcing +# use delete since it can be 0 or 1 for enforcing depending on OEM if [ -n "$(resetprop ro.build.selinux)" ]; then resetprop --delete ro.build.selinux fi # use toybox to protect *stat* access time reading -if [ "$(toybox cat /sys/fs/selinux/enforce)" == "0" ]; then +if [ "$(toybox cat /sys/fs/selinux/enforce)" = "0" ]; then chmod 640 /sys/fs/selinux/enforce chmod 440 /sys/fs/selinux/policy fi -# Conditional late sensitive properties - -# SafetyNet/Play Integrity +# Late props which must be set after boot_completed { - # must be set after boot_completed for various OEMs until [ "$(getprop sys.boot_completed)" = "1" ]; do sleep 1 done - # avoid breaking Realme fingerprint scanners resetprop_if_diff ro.boot.flash.locked 1 - resetprop_if_diff ro.boot.realme.lockstate 1 - # avoid breaking Oppo fingerprint scanners resetprop_if_diff ro.boot.vbmeta.device_state locked - # avoid breaking OnePlus display modes/fingerprint scanners + resetprop_if_diff ro.boot.verifiedbootstate green + + resetprop_if_diff ro.boot.veritymode enforcing + resetprop_if_diff vendor.boot.verifiedbootstate green - # avoid breaking OnePlus/Oppo display fingerprint scanners on OOS/ColorOS 12+ - resetprop_if_diff ro.boot.verifiedbootstate green - resetprop_if_diff ro.boot.veritymode enforcing resetprop_if_diff vendor.boot.vbmeta.device_state locked - - # Xiaomi - resetprop_if_diff ro.secureboot.lockstate locked - - # Realme - resetprop_if_diff ro.boot.realmebootstate green }& diff --git a/update.json b/update.json index 31b0b2e..3591dc1 100644 --- a/update.json +++ b/update.json @@ -1,6 +1,6 @@ { - "version": "v15.7.2", - "versionCode": 15702, - "zipUrl": "https://github.com/chiteroman/PlayIntegrityFix/releases/download/v15.7.2/PlayIntegrityFix_v15.7.2.zip", + "version": "v15.8", + "versionCode": 15800, + "zipUrl": "https://github.com/chiteroman/PlayIntegrityFix/releases/download/v15.8/PlayIntegrityFix_v15.8.zip", "changelog": "https://raw.githubusercontent.com/chiteroman/PlayIntegrityFix/main/changelog.md" } \ No newline at end of file