Skip to content

Commit

Permalink
v15.9.5
Browse files Browse the repository at this point in the history
  • Loading branch information
chiteroman committed Mar 17, 2024
1 parent 8e239aa commit 972bf31
Show file tree
Hide file tree
Showing 13 changed files with 150 additions and 137 deletions.
1 change: 1 addition & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 39 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,46 @@
# 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.
If not, try removing /data/adb/pif.json file.
DO NOT REMOVE pif.json in module's folder!
You will need root and Zygisk, so you must choose ONE of this three setups:

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)
19 changes: 13 additions & 6 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@ android {
applicationId = "es.chiteroman.playintegrityfix"
minSdk = 26
targetSdk = 34
versionCode = 15940
versionName = "v15.9.4"
versionCode = 15950
versionName = "v15.9.5"
multiDexEnabled = false

buildFeatures {
prefab = true
}

packaging {
jniLibs {
excludes += "**/liblog.so"
Expand All @@ -25,16 +29,15 @@ android {

externalNativeBuild {
cmake {
arguments += "-DANDROID_STL=c++_static"
arguments += "-DCMAKE_BUILD_TYPE=Release"
arguments += "-DANDROID_STL=none"
arguments += "-DCMAKE_BUILD_TYPE=MinSizeRel"
arguments += "-DPlugin.Android.BionicLinkerUtil=ON"

cppFlags += "-std=c++20"
cppFlags += "-fno-exceptions"
cppFlags += "-fno-rtti"
cppFlags += "-fvisibility=hidden"
cppFlags += "-fvisibility-inlines-hidden"
cppFlags += "-flto"
}
}
}
Expand All @@ -61,6 +64,10 @@ android {
}
}

dependencies {
implementation("dev.rikka.ndk.thirdparty:cxx:1.2.0")
}

tasks.register("updateModuleProp") {
doLast {
val versionName = project.android.defaultConfig.versionName
Expand All @@ -84,7 +91,7 @@ tasks.register("copyFiles") {
doLast {
val moduleFolder = project.rootDir.resolve("module")
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)

Expand Down
4 changes: 4 additions & 0 deletions app/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ cmake_minimum_required(VERSION 3.22.1)

project(playintegrityfix)

find_package(cxx REQUIRED CONFIG)

link_libraries(cxx::cxx)

add_library(${CMAKE_PROJECT_NAME} SHARED main.cpp)

add_subdirectory(Dobby)
Expand Down
106 changes: 51 additions & 55 deletions app/src/main/cpp/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

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) {

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);

Expand Down Expand Up @@ -52,7 +54,7 @@ static void modify_callback(void *cookie, const char *name, const char *value, u
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 *);
Expand All @@ -62,19 +64,19 @@ my_system_property_read_callback(const prop_info *pi, T_Callback callback, void
if (pi == nullptr || callback == nullptr || cookie == nullptr) {
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);
}

static void doHook() {
void *handle = DobbySymbolResolver("libc.so", "__system_property_read_callback");
void *handle = DobbySymbolResolver(nullptr, "__system_property_read_callback");
if (handle == nullptr) {
LOGD("Couldn't hook '__system_property_read_callback'. Report to @chiteroman");
LOGD("Couldn't hook __system_property_read_callback");
return;
}
DobbyHook(handle, (void *) my_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 {
Expand All @@ -86,77 +88,71 @@ class PlayIntegrityFix : public zygisk::ModuleBase {

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) {
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
return;
}
if (!args) goto exit;

dir = env->GetStringUTFChars(args->app_data_dir, nullptr);

if (!dir) goto exit;

bool isGms = std::string_view(dir).ends_with("/com.google.android.gms");
isGms = std::string_view(dir).ends_with("/com.google.android.gms");

env->ReleaseStringUTFChars(args->app_data_dir, dir);

if (!isGms) {
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
return;
}
if (isGms) {
name = env->GetStringUTFChars(args->nice_name, nullptr);

api->setOption(zygisk::FORCE_DENYLIST_UNMOUNT);
if (!name) goto exit;

auto name = env->GetStringUTFChars(args->nice_name, nullptr);
isGmsUnstable = strcmp(name, "com.google.android.gms.unstable") == 0;

if (name == nullptr) {
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
return;
}
if (isGmsUnstable) {

bool isGmsUnstable = std::string_view(name) == "com.google.android.gms.unstable";
long dexSize = 0, jsonSize = 0;

env->ReleaseStringUTFChars(args->nice_name, name);
int fd = api->connectCompanion();

if (!isGmsUnstable) {
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
return;
}
read(fd, &dexSize, sizeof(long));
read(fd, &jsonSize, sizeof(long));

long dexSize = 0, jsonSize = 0;
LOGD("Dex file size: %ld", dexSize);
LOGD("Json file size: %ld", jsonSize);

int fd = api->connectCompanion();
if (dexSize < 1 || jsonSize < 1) {
close(fd);
LOGD("Invalid files!");
goto exit;
}

read(fd, &dexSize, sizeof(long));
read(fd, &jsonSize, sizeof(long));
dexVector.resize(dexSize);
read(fd, dexVector.data(), dexSize);

LOGD("Dex file size: %ld", dexSize);
LOGD("Json file size: %ld", jsonSize);
std::vector<uint8_t> jsonVector;

if (dexSize < 1) {
close(fd);
LOGD("Dex file empty!");
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
return;
}
jsonVector.resize(jsonSize);
read(fd, jsonVector.data(), jsonSize);

dexVector.resize(dexSize);
read(fd, dexVector.data(), dexSize);
close(fd);

if (jsonSize < 1) {
close(fd);
LOGD("JSON file not found!");
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
return;
}
json = nlohmann::json::parse(jsonVector, nullptr, false, true);

std::vector<uint8_t> jsonVector;
parseJson();

jsonVector.resize(jsonSize);
read(fd, jsonVector.data(), jsonSize);
return;

close(fd);
} else {
api->setOption(zygisk::FORCE_DENYLIST_UNMOUNT);
goto exit;
}

json = nlohmann::json::parse(jsonVector, nullptr, false, true);
} else {
goto exit;
}

parseJson();
exit:
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
}

void postAppSpecialize(const zygisk::AppSpecializeArgs *args) override {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,12 @@
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;
import java.util.Locale;

public final class CustomKeyStoreSpi extends KeyStoreSpi {
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";
public static volatile KeyStoreSpi keyStoreSpi = null;
public static volatile KeyStoreSpi keyStoreSpi;

@Override
public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException {
Expand All @@ -27,24 +24,15 @@ public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmExc

@Override
public Certificate[] engineGetCertificateChain(String alias) {
Certificate[] certificates = keyStoreSpi.engineGetCertificateChain(alias);

// If certificate array is null, throw exception
// This shouldn't happen...
if (certificates == null) {
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!");
for (StackTraceElement stackTraceElement : Thread.currentThread().getStackTrace()) {
if (stackTraceElement.getClassName().toLowerCase(Locale.US).contains("droidguard")) {
EntryPoint.LOG("engineGetCertificateChain invoked by DroidGuard!");
throw new UnsupportedOperationException();
}
}

return certificates;
return keyStoreSpi.engineGetCertificateChain(alias);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public CustomProvider(Provider provider) {
public synchronized Service getService(String type, String algorithm) {
EntryPoint.LOG(String.format("Service: '%s' | Algorithm: '%s'", type, algorithm));

new Thread(EntryPoint::spoofFields).start();
EntryPoint.spoofFields();

return super.getService(type, algorithm);
}
Expand Down
5 changes: 1 addition & 4 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
buildscript {
val agp_version by extra("8.2.2")
}
plugins {
id("com.android.application") version "8.2.2" apply false
id("com.android.application") version "8.3.0" apply false
}
12 changes: 8 additions & 4 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
We have a Telegram group!
If you want to share your knowledge join:
Telegram channel:
https://t.me/playintegrityfix

Device verdict should pass by default.
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.
Loading

0 comments on commit 972bf31

Please sign in to comment.