This commit is contained in:
chiteroman 2024-03-18 00:34:07 +01:00
parent 8e239aade9
commit 972bf318cb
13 changed files with 166 additions and 153 deletions

View File

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">

View File

@ -1,15 +1,46 @@
# Play Integrity Fix # Play Integrity Fix
This module attempts to fix Play Integrity verdicts to get a certified device on bootloader unlocked devices. This module tries to fix Play Integrity and SafetyNet verdicts to get a valid attestation.
Device verdict should pass by default. You will need root and Zygisk, so you must choose ONE of this three setups:
If not, try removing /data/adb/pif.json file.
DO NOT REMOVE pif.json in module's folder!
Wiki: https://github.com/chiteroman/PlayIntegrityFix/wiki - [Magisk](https://github.com/topjohnwu/Magisk) with Zygisk enabled.
- [KernelSU](https://github.com/tiann/KernelSU) with [ZygiskNext](https://github.com/Dr-TSNG/ZygiskNext) module installed.
- [APatch](https://github.com/bmax121/APatch) with [ZygiskNext](https://github.com/Dr-TSNG/ZygiskNext) module installed.
XDA post: https://xdaforums.com/t/module-play-integrity-fix-safetynet-fix.4607985/ After flashing and reboot your device, you can check PI and SN using these apps:
Telegram group: https://t.me/playintegrityfix - Play Integrity -> https://play.google.com/store/apps/details?id=gr.nikolasspyr.integritycheck
- SafetyNet -> https://play.google.com/store/apps/details?id=rikka.safetynetchecker
Donations: https://www.paypal.com/paypalme/chiteroman NOTE: if you get an error message about a limit, you need to use another app, this is because a lot of users are requesting an attestation.
NOTE: SafetyNet is obsolete, more info here: https://developer.android.com/privacy-and-security/safetynet/deprecation-timeline
Also, if you are using custom rom or custom kernel, be sure that your kernel name isn't blacklisted, you can check it running ```uname -r``` command. This is a list of banned strings: https://xdaforums.com/t/module-play-integrity-fix-safetynet-fix.4607985/post-89308909
After requesting an attestation in Play Integrity API you should get this result:
- MEETS_BASIC_INTEGRITY ✅
- MEETS_DEVICE_INTEGRITY ✅
- MEETS_STRONG_INTEGRITY ❌
- MEETS_VIRTUAL_INTEGRITY ❌
You can know more about verdicts in this post: https://xdaforums.com/t/info-play-integrity-api-replacement-for-safetynet.4479337/
And in SafetyNet you should get this:
- basicIntegrity: true
- ctsProfileMatch: true
- evaluationType: BASIC
NOTE: Strong verdict is impossible to pass on unlocked bootloader devices, there are few devices and "exploits" which will allow you to pass it, but, in normal conditions, this verdict will be green only if you are using stock ROM and locked bootloader. The old posts talking about Strong pass was an "exploit" in Google servers, obviously, now it's patched.
FAQ: https://xdaforums.com/t/pif-faq.4653307/
## Download
https://github.com/chiteroman/PlayIntegrityFix/releases/latest
## Donations
[PayPal](https://www.paypal.com/paypalme/chiteroman)

View File

@ -12,10 +12,14 @@ android {
applicationId = "es.chiteroman.playintegrityfix" applicationId = "es.chiteroman.playintegrityfix"
minSdk = 26 minSdk = 26
targetSdk = 34 targetSdk = 34
versionCode = 15940 versionCode = 15950
versionName = "v15.9.4" versionName = "v15.9.5"
multiDexEnabled = false multiDexEnabled = false
buildFeatures {
prefab = true
}
packaging { packaging {
jniLibs { jniLibs {
excludes += "**/liblog.so" excludes += "**/liblog.so"
@ -25,8 +29,8 @@ android {
externalNativeBuild { externalNativeBuild {
cmake { cmake {
arguments += "-DANDROID_STL=c++_static" arguments += "-DANDROID_STL=none"
arguments += "-DCMAKE_BUILD_TYPE=Release" arguments += "-DCMAKE_BUILD_TYPE=MinSizeRel"
arguments += "-DPlugin.Android.BionicLinkerUtil=ON" arguments += "-DPlugin.Android.BionicLinkerUtil=ON"
cppFlags += "-std=c++20" cppFlags += "-std=c++20"
@ -34,7 +38,6 @@ android {
cppFlags += "-fno-rtti" cppFlags += "-fno-rtti"
cppFlags += "-fvisibility=hidden" cppFlags += "-fvisibility=hidden"
cppFlags += "-fvisibility-inlines-hidden" cppFlags += "-fvisibility-inlines-hidden"
cppFlags += "-flto"
} }
} }
} }
@ -61,6 +64,10 @@ android {
} }
} }
dependencies {
implementation("dev.rikka.ndk.thirdparty:cxx:1.2.0")
}
tasks.register("updateModuleProp") { tasks.register("updateModuleProp") {
doLast { doLast {
val versionName = project.android.defaultConfig.versionName val versionName = project.android.defaultConfig.versionName
@ -84,7 +91,7 @@ tasks.register("copyFiles") {
doLast { doLast {
val moduleFolder = project.rootDir.resolve("module") val moduleFolder = project.rootDir.resolve("module")
val dexFile = project.layout.buildDirectory.get().asFile.resolve("intermediates/dex/release/minifyReleaseWithR8/classes.dex") val dexFile = project.layout.buildDirectory.get().asFile.resolve("intermediates/dex/release/minifyReleaseWithR8/classes.dex")
val soDir = project.layout.buildDirectory.get().asFile.resolve("intermediates/stripped_native_libs/release/out/lib") val soDir = project.layout.buildDirectory.get().asFile.resolve("intermediates/stripped_native_libs/release/stripReleaseDebugSymbols/out/lib")
dexFile.copyTo(moduleFolder.resolve("classes.dex"), overwrite = true) dexFile.copyTo(moduleFolder.resolve("classes.dex"), overwrite = true)

View File

@ -2,6 +2,10 @@ cmake_minimum_required(VERSION 3.22.1)
project(playintegrityfix) project(playintegrityfix)
find_package(cxx REQUIRED CONFIG)
link_libraries(cxx::cxx)
add_library(${CMAKE_PROJECT_NAME} SHARED main.cpp) add_library(${CMAKE_PROJECT_NAME} SHARED main.cpp)
add_subdirectory(Dobby) add_subdirectory(Dobby)

View File

@ -17,11 +17,13 @@ static std::string FIRST_API_LEVEL, SECURITY_PATCH, BUILD_ID;
typedef void (*T_Callback)(void *, const char *, const char *, uint32_t); typedef void (*T_Callback)(void *, const char *, const char *, uint32_t);
static T_Callback o_callback = nullptr; static std::map<void *, T_Callback> callbacks;
static void modify_callback(void *cookie, const char *name, const char *value, uint32_t serial) { static void modify_callback(void *cookie, const char *name, const char *value, uint32_t serial) {
if (cookie == nullptr || name == nullptr || value == nullptr || o_callback == nullptr) return; if (cookie == nullptr || name == nullptr || value == nullptr ||
!callbacks.contains(cookie))
return;
std::string_view prop(name); std::string_view prop(name);
@ -52,7 +54,7 @@ static void modify_callback(void *cookie, const char *name, const char *value, u
LOGD("[%s]: %s", name, value); LOGD("[%s]: %s", name, value);
} }
return o_callback(cookie, name, value, serial); return callbacks[cookie](cookie, name, value, serial);
} }
static void (*o_system_property_read_callback)(const prop_info *, T_Callback, void *); static void (*o_system_property_read_callback)(const prop_info *, T_Callback, void *);
@ -62,19 +64,19 @@ my_system_property_read_callback(const prop_info *pi, T_Callback callback, void
if (pi == nullptr || callback == nullptr || cookie == nullptr) { if (pi == nullptr || callback == nullptr || cookie == nullptr) {
return o_system_property_read_callback(pi, callback, cookie); return o_system_property_read_callback(pi, callback, cookie);
} }
o_callback = callback; callbacks[cookie] = callback;
return o_system_property_read_callback(pi, modify_callback, cookie); return o_system_property_read_callback(pi, modify_callback, cookie);
} }
static void doHook() { static void doHook() {
void *handle = DobbySymbolResolver("libc.so", "__system_property_read_callback"); void *handle = DobbySymbolResolver(nullptr, "__system_property_read_callback");
if (handle == nullptr) { if (handle == nullptr) {
LOGD("Couldn't hook '__system_property_read_callback'. Report to @chiteroman"); LOGD("Couldn't hook __system_property_read_callback");
return; return;
} }
DobbyHook(handle, (void *) my_system_property_read_callback, DobbyHook(handle, (void *) my_system_property_read_callback,
(void **) &o_system_property_read_callback); (void **) &o_system_property_read_callback);
LOGD("Found and hooked '__system_property_read_callback' at %p", handle); LOGD("Found and hooked __system_property_read_callback at %p", handle);
} }
class PlayIntegrityFix : public zygisk::ModuleBase { class PlayIntegrityFix : public zygisk::ModuleBase {
@ -86,77 +88,71 @@ public:
void preAppSpecialize(zygisk::AppSpecializeArgs *args) override { void preAppSpecialize(zygisk::AppSpecializeArgs *args) override {
auto dir = env->GetStringUTFChars(args->app_data_dir, nullptr); const char *dir, *name;
bool isGms, isGmsUnstable;
if (dir == nullptr) { if (!args) goto exit;
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
return;
}
bool isGms = std::string_view(dir).ends_with("/com.google.android.gms"); dir = env->GetStringUTFChars(args->app_data_dir, nullptr);
if (!dir) goto exit;
isGms = std::string_view(dir).ends_with("/com.google.android.gms");
env->ReleaseStringUTFChars(args->app_data_dir, dir); env->ReleaseStringUTFChars(args->app_data_dir, dir);
if (!isGms) { if (isGms) {
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY); name = env->GetStringUTFChars(args->nice_name, nullptr);
return;
if (!name) goto exit;
isGmsUnstable = strcmp(name, "com.google.android.gms.unstable") == 0;
if (isGmsUnstable) {
long dexSize = 0, jsonSize = 0;
int fd = api->connectCompanion();
read(fd, &dexSize, sizeof(long));
read(fd, &jsonSize, sizeof(long));
LOGD("Dex file size: %ld", dexSize);
LOGD("Json file size: %ld", jsonSize);
if (dexSize < 1 || jsonSize < 1) {
close(fd);
LOGD("Invalid files!");
goto exit;
}
dexVector.resize(dexSize);
read(fd, dexVector.data(), dexSize);
std::vector<uint8_t> jsonVector;
jsonVector.resize(jsonSize);
read(fd, jsonVector.data(), jsonSize);
close(fd);
json = nlohmann::json::parse(jsonVector, nullptr, false, true);
parseJson();
return;
} else {
api->setOption(zygisk::FORCE_DENYLIST_UNMOUNT);
goto exit;
}
} else {
goto exit;
} }
api->setOption(zygisk::FORCE_DENYLIST_UNMOUNT); exit:
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
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);
if (!isGmsUnstable) {
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
return;
}
long dexSize = 0, jsonSize = 0;
int fd = api->connectCompanion();
read(fd, &dexSize, sizeof(long));
read(fd, &jsonSize, sizeof(long));
LOGD("Dex file size: %ld", dexSize);
LOGD("Json file size: %ld", jsonSize);
if (dexSize < 1) {
close(fd);
LOGD("Dex file empty!");
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
return;
}
dexVector.resize(dexSize);
read(fd, dexVector.data(), dexSize);
if (jsonSize < 1) {
close(fd);
LOGD("JSON file not found!");
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
return;
}
std::vector<uint8_t> jsonVector;
jsonVector.resize(jsonSize);
read(fd, jsonVector.data(), jsonSize);
close(fd);
json = nlohmann::json::parse(jsonVector, nullptr, false, true);
parseJson();
} }
void postAppSpecialize(const zygisk::AppSpecializeArgs *args) override { void postAppSpecialize(const zygisk::AppSpecializeArgs *args) override {

View File

@ -10,15 +10,12 @@ import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException; import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date; import java.util.Date;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.Locale;
public final class CustomKeyStoreSpi extends KeyStoreSpi { public final class CustomKeyStoreSpi extends KeyStoreSpi {
private static final String EAT_OID = "1.3.6.1.4.1.11129.2.1.25"; public static volatile KeyStoreSpi keyStoreSpi;
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";
public static volatile KeyStoreSpi keyStoreSpi = null;
@Override @Override
public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException { public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException {
@ -27,24 +24,15 @@ public final class CustomKeyStoreSpi extends KeyStoreSpi {
@Override @Override
public Certificate[] engineGetCertificateChain(String alias) { public Certificate[] engineGetCertificateChain(String alias) {
Certificate[] certificates = keyStoreSpi.engineGetCertificateChain(alias);
// If certificate array is null, throw exception for (StackTraceElement stackTraceElement : Thread.currentThread().getStackTrace()) {
// This shouldn't happen... if (stackTraceElement.getClassName().toLowerCase(Locale.US).contains("droidguard")) {
if (certificates == null) { EntryPoint.LOG("engineGetCertificateChain invoked by DroidGuard!");
EntryPoint.LOG("Certificate chain is null!");
throw new UnsupportedOperationException();
}
// If leaf certificate has attestation extensions, throw exception!
if (certificates[0] instanceof X509Certificate x509Certificate) {
if (x509Certificate.getExtensionValue(EAT_OID) != null || x509Certificate.getExtensionValue(ASN1_OID) != null || x509Certificate.getExtensionValue(KNOX_OID) != null) {
EntryPoint.LOG("Leaf certificate with attestation extensions. Throw exception!");
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
} }
return certificates; return keyStoreSpi.engineGetCertificateChain(alias);
} }
@Override @Override

View File

@ -14,7 +14,7 @@ public final class CustomProvider extends Provider {
public synchronized Service getService(String type, String algorithm) { public synchronized Service getService(String type, String algorithm) {
EntryPoint.LOG(String.format("Service: '%s' | Algorithm: '%s'", type, algorithm)); EntryPoint.LOG(String.format("Service: '%s' | Algorithm: '%s'", type, algorithm));
new Thread(EntryPoint::spoofFields).start(); EntryPoint.spoofFields();
return super.getService(type, algorithm); return super.getService(type, algorithm);
} }

View File

@ -1,6 +1,3 @@
buildscript {
val agp_version by extra("8.2.2")
}
plugins { plugins {
id("com.android.application") version "8.2.2" apply false id("com.android.application") version "8.3.0" apply false
} }

View File

@ -1,10 +1,14 @@
We have a Telegram group! Telegram channel:
If you want to share your knowledge join:
https://t.me/playintegrityfix https://t.me/playintegrityfix
Device verdict should pass by default. Device verdict should pass by default.
If not, try removing /data/adb/pif.json file. If not, try removing /data/adb/pif.json file.
# v15.9.4 Donations:
https://www.paypal.com/paypalme/chiteroman
- Misc improvements. # v15.9.5
- Strip libraries and reduce their size.
- Fix attestation not passing on some devices.
- Do not auto remove conflict apps, users should remove them manually.

View File

@ -22,9 +22,7 @@ if [ -f "/data/adb/pif.json" ]; then
ui_print "- If pif.json file doesn't exist, module will use default one" ui_print "- If pif.json file doesn't exist, module will use default one"
fi fi
ui_print "- Removing conflict apps..." # Conflict apps
# Remove conflict apps
APPS=" APPS="
/system/app/EliteDevelopmentModule /system/app/EliteDevelopmentModule
/system/app/XInjectModule /system/app/XInjectModule
@ -35,16 +33,7 @@ APPS="
" "
for app in $APPS; do for app in $APPS; do
if [ -d "$app" ]; then if [ -d "$app" ]; then
directory="$MODPATH$app" ui_print "- ${app##*/} app found! You should uninstall it manually!"
[ -d "$directory" ] || mkdir -p "$directory" fi
if [ "$KSU" = "true" ] || [ "$APATCH" = "true" ]; then
mknod $directory c 0 0
else
touch $directory/.replace
fi
ui_print "- ${app##*/} app removed"
else
ui_print "- ${app##*/} app doesn't exist, skip"
fi
done done

View File

@ -1,7 +1,7 @@
id=playintegrityfix id=playintegrityfix
name=Play Integrity Fix name=Play Integrity Fix
version=v15.9.4 version=v15.9.5
versionCode=15940 versionCode=15950
author=chiteroman author=chiteroman
description=Universal modular fix for Play Integrity (and SafetyNet) on devices running Android 8-14. 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 updateJson=https://raw.githubusercontent.com/chiteroman/PlayIntegrityFix/main/update.json

View File

@ -36,25 +36,21 @@ fi
# Late props which must be set after boot_completed # Late props which must be set after boot_completed
{ {
until [ "$(getprop sys.boot_completed)" = "1" ]; do until [[ "$(getprop sys.boot_completed)" == "1" ]]; do
sleep 1 sleep 1
done done
resetprop_if_diff ro.boot.flash.locked 1 # SafetyNet/Play Integrity | Avoid breaking Realme fingerprint scanners
resetprop ro.boot.flash.locked 1
resetprop_if_diff ro.boot.vbmeta.device_state locked
# SafetyNet/Play Integrity | Avoid breaking Oppo fingerprint scanners
resetprop_if_diff ro.boot.verifiedbootstate green resetprop ro.boot.vbmeta.device_state locked
resetprop_if_diff ro.boot.veritymode enforcing # SafetyNet/Play Integrity | Avoid breaking OnePlus display modes/fingerprint scanners
resetprop vendor.boot.verifiedbootstate green
resetprop_if_diff vendor.boot.verifiedbootstate green
# SafetyNet/Play Integrity | Avoid breaking OnePlus display modes/fingerprint scanners on OOS 12
resetprop_if_diff vendor.boot.vbmeta.device_state locked resetprop ro.boot.verifiedbootstate green
resetprop ro.boot.veritymode enforcing
resetprop_if_diff ro.crypto.state encrypted resetprop vendor.boot.vbmeta.device_state locked
resetprop_if_diff ro.secureboot.lockstate locked
resetprop_if_diff ro.boot.realmebootstate green
}& }&

View File

@ -1,6 +1,6 @@
{ {
"version": "v15.9.4", "version": "v15.9.5",
"versionCode": 15940, "versionCode": 15950,
"zipUrl": "https://github.com/chiteroman/PlayIntegrityFix/releases/download/v15.9.4/PlayIntegrityFix_v15.9.4.zip", "zipUrl": "https://github.com/chiteroman/PlayIntegrityFix/releases/download/v15.9.5/PlayIntegrityFix_v15.9.5.zip",
"changelog": "https://raw.githubusercontent.com/chiteroman/PlayIntegrityFix/main/changelog.md" "changelog": "https://raw.githubusercontent.com/chiteroman/PlayIntegrityFix/main/changelog.md"
} }