diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7c7650b..ab8071b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -25,12 +25,17 @@ android { applicationId = "es.chiteroman.playintegrityfix" minSdk = 26 targetSdk = 35 - versionCode = 18200 - versionName = "v18.2" + versionCode = 18201 + versionName = "v18.2.1-EXPERIMENTAL" multiDexEnabled = false externalNativeBuild { cmake { + abiFilters( + "arm64-v8a", + "armeabi-v7a" + ) + arguments( "-DCMAKE_BUILD_TYPE=MinSizeRel", "-DANDROID_STL=none" @@ -109,11 +114,20 @@ tasks.register("copyFiles") { dexFile.copyTo(moduleFolder.resolve("classes.dex"), overwrite = true) - soDir.walk().filter { it.isFile && it.extension == "so" }.forEach { soFile -> - val abiFolder = soFile.parentFile.name - val destination = moduleFolder.resolve("zygisk/$abiFolder.so") - soFile.copyTo(destination, overwrite = true) - } + soDir.walk() + .filter { it.isFile } + .forEach { file -> + val abiFolder = file.parentFile.name + var destination = File("") + if (file.name == "libzygisk.so") { + destination = moduleFolder.resolve("zygisk/$abiFolder.so") + } else if (file.name == "libinject.so") { + destination = moduleFolder.resolve("inject/$abiFolder.so") + } + if (!destination.name.isNullOrEmpty()) { + file.copyTo(destination, overwrite = true) + } + } } } diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 76f48a4..9314e1d 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -6,8 +6,12 @@ find_package(cxx REQUIRED CONFIG) link_libraries(cxx::cxx) -add_library(${CMAKE_PROJECT_NAME} SHARED main.cpp) +add_library(zygisk SHARED main.cpp) + +target_link_libraries(zygisk PRIVATE log) + +add_library(inject SHARED inject.cpp) add_subdirectory(Dobby) -target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE log dobby_static) +target_link_libraries(inject PRIVATE log dobby_static) \ No newline at end of file diff --git a/app/src/main/cpp/inject.cpp b/app/src/main/cpp/inject.cpp new file mode 100644 index 0000000..fd29a63 --- /dev/null +++ b/app/src/main/cpp/inject.cpp @@ -0,0 +1,332 @@ +#include "json.hpp" +#include "log.h" +#include "dobby.h" +#include "jni.h" +#include + +static JNIEnv *env = nullptr; + +static std::string gmsDir; + +static bool spoofProps = true; +static bool spoofProvider = true; +static bool spoofSignature = false; + +static nlohmann::json json; + +static bool DEBUG = false; + +static std::string DEVICE_INITIAL_SDK_INT, SECURITY_PATCH, BUILD_ID; + +typedef void (*T_Callback)(void *, const char *, const char *, uint32_t); + +static T_Callback o_callback = nullptr; + +static void modify_callback(void *cookie, const char *name, const char *value, + uint32_t serial) { + + if (!cookie || !name || !value || !o_callback) + return; + + const char *oldValue = value; + + std::string_view prop(name); + + if (prop == "init.svc.adbd") { + value = "stopped"; + } else if (prop == "sys.usb.state") { + value = "mtp"; + } else if (prop.ends_with("api_level")) { + if (!DEVICE_INITIAL_SDK_INT.empty()) { + value = DEVICE_INITIAL_SDK_INT.c_str(); + } + } else if (prop.ends_with(".security_patch")) { + if (!SECURITY_PATCH.empty()) { + value = SECURITY_PATCH.c_str(); + } + } else if (prop.ends_with(".build.id")) { + if (!BUILD_ID.empty()) { + value = BUILD_ID.c_str(); + } + } + + if (strcmp(oldValue, value) == 0) { + if (DEBUG) + LOGD("[%s]: %s (unchanged)", name, oldValue); + } else { + LOGD("[%s]: %s -> %s", name, oldValue, value); + } + + return o_callback(cookie, name, value, serial); +} + +static void (*o_system_property_read_callback)(prop_info *, T_Callback, + void *) = nullptr; + +static void my_system_property_read_callback(prop_info *pi, T_Callback callback, + void *cookie) { + if (pi && callback && cookie) + o_callback = callback; + return o_system_property_read_callback(pi, modify_callback, cookie); +} + +void UpdateBuildFields() { + jclass buildClass = env->FindClass("android/os/Build"); + jclass versionClass = env->FindClass("android/os/Build$VERSION"); + + for (auto &[key, val]: json.items()) { + if (!val.is_string()) continue; + + const char *fieldName = key.c_str(); + + jfieldID fieldID = env->GetStaticFieldID(buildClass, fieldName, "Ljava/lang/String;"); + + if (env->ExceptionCheck()) { + env->ExceptionClear(); + + fieldID = env->GetStaticFieldID(versionClass, fieldName, "Ljava/lang/String;"); + + if (env->ExceptionCheck()) { + env->ExceptionClear(); + continue; + } + } + + if (fieldID != nullptr) { + std::string str = val.get(); + const char *value = str.c_str(); + jstring jValue = env->NewStringUTF(value); + + env->SetStaticObjectField(buildClass, fieldID, jValue); + if (env->ExceptionCheck()) { + env->ExceptionClear(); + continue; + } + + LOGD("Set '%s' to '%s'", fieldName, value); + } + } +} + +void parseJSON() { + if (json.empty()) return; + + if (json.contains("DEVICE_INITIAL_SDK_INT")) { + if (json["DEVICE_INITIAL_SDK_INT"].is_string()) { + DEVICE_INITIAL_SDK_INT = json["DEVICE_INITIAL_SDK_INT"].get(); + } else if (json["DEVICE_INITIAL_SDK_INT"].is_number_integer()) { + DEVICE_INITIAL_SDK_INT = std::to_string(json["DEVICE_INITIAL_SDK_INT"].get()); + } else { + LOGE("Couldn't parse DEVICE_INITIAL_SDK_INT value!"); + } + json.erase("DEVICE_INITIAL_SDK_INT"); + } + + if (json.contains("spoofProvider") && json["spoofProvider"].is_boolean()) { + spoofProvider = json["spoofProvider"].get(); + json.erase("spoofProvider"); + } + + if (json.contains("spoofProps") && json["spoofProps"].is_boolean()) { + spoofProps = json["spoofProps"].get(); + json.erase("spoofProps"); + } + + if (json.contains("spoofSignature") && json["spoofSignature"].is_boolean()) { + spoofSignature = json["spoofSignature"].get(); + json.erase("spoofSignature"); + } + + if (json.contains("DEBUG") && json["DEBUG"].is_boolean()) { + DEBUG = json["DEBUG"].get(); + json.erase("DEBUG"); + } + + if (json.contains("FINGERPRINT") && json["FINGERPRINT"].is_string()) { + std::string fingerprint = json["FINGERPRINT"].get(); + + std::vector vector; + auto parts = fingerprint | std::views::split('/'); + + for (const auto &part: parts) { + auto subParts = std::string(part.begin(), part.end()) | std::views::split(':'); + for (const auto &subPart: subParts) { + vector.emplace_back(subPart.begin(), subPart.end()); + } + } + + if (vector.size() == 8) { + json["BRAND"] = vector[0]; + json["PRODUCT"] = vector[1]; + json["DEVICE"] = vector[2]; + json["RELEASE"] = vector[3]; + json["ID"] = vector[4]; + json["INCREMENTAL"] = vector[5]; + json["TYPE"] = vector[6]; + json["TAGS"] = vector[7]; + } else { + LOGE("Error parsing fingerprint values!"); + } + } + + if (json.contains("SECURITY_PATCH") && json["SECURITY_PATCH"].is_string()) { + SECURITY_PATCH = json["SECURITY_PATCH"].get(); + } + + if (json.contains("ID") && json["ID"].is_string()) { + BUILD_ID = json["ID"].get(); + } +} + +void injectDex() { + LOGD("get system classloader"); + auto clClass = env->FindClass("java/lang/ClassLoader"); + auto getSystemClassLoader = env->GetStaticMethodID(clClass, "getSystemClassLoader", + "()Ljava/lang/ClassLoader;"); + auto systemClassLoader = env->CallStaticObjectMethod(clClass, getSystemClassLoader); + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return; + } + + LOGD("create class loader"); + auto dexClClass = env->FindClass("dalvik/system/PathClassLoader"); + auto dexClInit = env->GetMethodID(dexClClass, "", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V"); + auto str1 = env->NewStringUTF((gmsDir + "/classes.dex").c_str()); + auto str2 = env->NewStringUTF(gmsDir.c_str()); + auto dexCl = env->NewObject(dexClClass, dexClInit, str1, str2, systemClassLoader); + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return; + } + + LOGD("load class"); + auto loadClass = env->GetMethodID(clClass, "loadClass", + "(Ljava/lang/String;)Ljava/lang/Class;"); + auto entryClassName = env->NewStringUTF("es.chiteroman.playintegrityfix.EntryPoint"); + auto entryClassObj = env->CallObjectMethod(dexCl, loadClass, entryClassName); + auto entryPointClass = (jclass) entryClassObj; + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return; + } + + LOGD("call init"); + auto entryInit = env->GetStaticMethodID(entryPointClass, "init", "(Ljava/lang/String;ZZ)V"); + auto jsonStr = env->NewStringUTF(json.dump().c_str()); + env->CallStaticVoidMethod(entryPointClass, entryInit, jsonStr, spoofProvider, + spoofSignature); + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + } + + env->DeleteLocalRef(entryClassName); + env->DeleteLocalRef(entryClassObj); + env->DeleteLocalRef(jsonStr); + env->DeleteLocalRef(dexCl); + env->DeleteLocalRef(dexClClass); + env->DeleteLocalRef(clClass); + + LOGD("jni memory free"); +} + +extern "C" { +[[gnu::visibility("default"), maybe_unused]] +void init(const char *dir, JavaVM *jvm) { + + if (dir) { + LOGD("[INJECT] GMS dir: %s", dir); + } else { + LOGE("[INJECT] dir is null!"); + return; + } + + if (jvm) { + auto result = jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); + LOGD("[INJECT] JVM: %d", result); + if (result == JNI_EDETACHED) { + result = jvm->AttachCurrentThread(&env, nullptr); + LOGD("[INJECT] (JNI_EDETACHED) JVM: %d", result); + } + } else { + LOGE("[INJECT] jvm is null!"); + } + + if (env) { + LOGD("[INJECT] JNIEnv: %d", env->GetVersion()); + } else { + LOGE("[INJECT] env is null!"); + return; + } + + gmsDir = dir; + + FILE *pif = fopen((gmsDir + "/pif.json").c_str(), "r"); + if (pif) { + json = nlohmann::json::parse(pif, nullptr, false, true); + fclose(pif); + } + + if (json == nullptr || json.empty()) + return; + + bool trickyStore = false; + FILE *trickyStoreFile = fopen((gmsDir + "/trickystore").c_str(), "r"); + if (trickyStoreFile) { + trickyStore = true; + fclose(trickyStoreFile); + } + + LOGD("[INJECT] TrickyStore? %s", trickyStore ? "yes" : "no"); + + bool testSignedRom = false; + FILE *testSignedFile = fopen((gmsDir + "/testsign").c_str(), "r"); + if (testSignedFile) { + testSignedRom = true; + fclose(testSignedFile); + } + + LOGD("[INJECT] ROM signed with test keys? %s", testSignedRom ? "yes" : "no"); + + if (trickyStore) { + spoofProvider = false; + spoofProps = false; + } + + if (testSignedRom) { + spoofSignature = true; + } + + parseJSON(); + + UpdateBuildFields(); + + if (spoofProvider || spoofSignature) { + injectDex(); + } else { + LOGD("Dex file won't be injected due spoofProvider and spoofSignature are false"); + } + + if (!spoofProps) + return; + + auto ptr = DobbySymbolResolver(nullptr, "__system_property_read_callback"); + + if (ptr && DobbyHook(ptr, (void *) my_system_property_read_callback, + (void **) &o_system_property_read_callback) == 0) { + LOGD("hook __system_property_read_callback successful at %p", ptr); + return; + } + + LOGE("hook __system_property_read_callback failed!"); +} +} \ No newline at end of file diff --git a/app/src/main/cpp/log.h b/app/src/main/cpp/log.h new file mode 100644 index 0000000..95e3208 --- /dev/null +++ b/app/src/main/cpp/log.h @@ -0,0 +1,4 @@ +#include + +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "PIF", __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "PIF", __VA_ARGS__) \ No newline at end of file diff --git a/app/src/main/cpp/main.cpp b/app/src/main/cpp/main.cpp index 897432e..681d03f 100644 --- a/app/src/main/cpp/main.cpp +++ b/app/src/main/cpp/main.cpp @@ -1,432 +1,85 @@ -#include -#include -#include +#include "log.h" #include "zygisk.hpp" -#include "dobby.h" -#include "json.hpp" +#include +#include +#include -#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "PIF", __VA_ARGS__) -#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "PIF", __VA_ARGS__) +#if __aarch64__ +#define INJECT_LIB \ + "/data/adb/modules/playintegrityfix/inject/arm64-v8a.so" +#elif __arm__ +#define INJECT_LIB \ + "/data/adb/modules/playintegrityfix/inject/armeabi-v7a.so" +#endif -#define DEX_PATH "/data/adb/modules/playintegrityfix/classes.dex" +#define DEX_FILE "/data/adb/modules/playintegrityfix/classes.dex" -#define PIF_JSON "/data/adb/pif.json" +#define PIF_FILE "/data/adb/modules/playintegrityfix/pif.json" -#define PIF_JSON_DEFAULT "/data/adb/modules/playintegrityfix/pif.json" +#define CUSTOM_PIF_FILE "/data/adb/pif.json" -#define TS_PATH "/data/adb/modules/tricky_store" +#define CUSTOM_PIF_FILE_1 "/data/adb/modules/playintegrityfix/custom.pif.json" -static ssize_t xread(int fd, void *buffer, size_t count) { +static inline ssize_t xread(int fd, void *buffer, size_t count) { + auto *buf = static_cast(buffer); ssize_t total = 0; - char *buf = (char *) buffer; + while (count > 0) { - ssize_t ret = TEMP_FAILURE_RETRY(read(fd, buf, count)); - if (ret < 0) return -1; + ssize_t ret = ::read(fd, buf, count); + + if (ret < 0) { + // Retry if interrupted + if (errno == EINTR) { + continue; + } + + return -1; + } + + // If 0, we've hit EOF (read no more data) + if (ret == 0) { + break; + } + buf += ret; total += ret; count -= ret; } + return total; } -static ssize_t xwrite(int fd, const void *buffer, size_t count) { +static inline ssize_t xwrite(int fd, const void *buffer, size_t count) { + auto *buf = static_cast(buffer); ssize_t total = 0; - char *buf = (char *) buffer; + while (count > 0) { - ssize_t ret = TEMP_FAILURE_RETRY(write(fd, buf, count)); - if (ret < 0) return -1; + ssize_t ret = ::write(fd, buf, count); + + if (ret < 0) { + // Retry if interrupted + if (errno == EINTR) { + continue; + } + + return -1; + } + + // Technically, write returning 0 is unusual (e.g., disk full); handle it if needed + if (ret == 0) { + break; + } + buf += ret; total += ret; count -= ret; } + return total; } -static bool DEBUG = false; -static std::string DEVICE_INITIAL_SDK_INT, SECURITY_PATCH, BUILD_ID; - -typedef void (*T_Callback)(void *, const char *, const char *, uint32_t); - -static T_Callback o_callback = nullptr; - -static void modify_callback(void *cookie, const char *name, const char *value, uint32_t serial) { - - if (!cookie || !name || !value || !o_callback) return; - - const char *oldValue = value; - - std::string_view prop(name); - - if (prop == "init.svc.adbd") { - value = "stopped"; - } else if (prop == "sys.usb.state") { - value = "mtp"; - } else if (prop.ends_with("api_level")) { - if (!DEVICE_INITIAL_SDK_INT.empty()) { - value = DEVICE_INITIAL_SDK_INT.c_str(); - } - } else if (prop.ends_with(".security_patch")) { - if (!SECURITY_PATCH.empty()) { - value = SECURITY_PATCH.c_str(); - } - } else if (prop.ends_with(".build.id")) { - if (!BUILD_ID.empty()) { - value = BUILD_ID.c_str(); - } - } - - if (strcmp(oldValue, value) == 0) { - if (DEBUG) LOGD("[%s]: %s (unchanged)", name, oldValue); - } else { - LOGD("[%s]: %s -> %s", name, oldValue, value); - } - - return o_callback(cookie, name, value, serial); -} - -static void (*o_system_property_read_callback)(prop_info *, T_Callback, void *) = nullptr; - -static void my_system_property_read_callback(prop_info *pi, T_Callback callback, void *cookie) { - if (pi && callback && cookie) o_callback = callback; - return o_system_property_read_callback(pi, modify_callback, cookie); -} - -static bool doHook() { - void *ptr = DobbySymbolResolver(nullptr, "__system_property_read_callback"); - - if (ptr && DobbyHook(ptr, (void *) my_system_property_read_callback, - (void **) &o_system_property_read_callback) == 0) { - LOGD("hook __system_property_read_callback successful at %p", ptr); - return true; - } - - LOGE("hook __system_property_read_callback failed!"); - return false; -} - -class PlayIntegrityFix : public zygisk::ModuleBase { -public: - void onLoad(zygisk::Api *api, JNIEnv *env) override { - this->api = api; - this->env = env; - } - - void preAppSpecialize(zygisk::AppSpecializeArgs *args) override { - - if (!args) { - api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY); - return; - } - - auto dir = env->GetStringUTFChars(args->app_data_dir, nullptr); - - if (!dir) { - api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY); - return; - } - - bool 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; - } - - api->setOption(zygisk::FORCE_DENYLIST_UNMOUNT); - - auto name = env->GetStringUTFChars(args->nice_name, nullptr); - - if (!name) { - 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; - } - - int fd = api->connectCompanion(); - - int dexSize = 0, jsonSize = 0; - std::string jsonStr; - - xread(fd, &dexSize, sizeof(dexSize)); - xread(fd, &jsonSize, sizeof(jsonSize)); - - if (dexSize > 0) { - dexVector.resize(dexSize); - xread(fd, dexVector.data(), dexSize * sizeof(uint8_t)); - } - - if (jsonSize > 0) { - jsonStr.resize(jsonSize); - xread(fd, jsonStr.data(), jsonSize * sizeof(uint8_t)); - json = nlohmann::json::parse(jsonStr, nullptr, false, true); - } - - bool trickyStore = false; - xread(fd, &trickyStore, sizeof(trickyStore)); - - bool testSignedRom = false; - xread(fd, &testSignedRom, sizeof(testSignedRom)); - - close(fd); - - LOGD("Dex file size: %d", dexSize); - LOGD("Json file size: %d", jsonSize); - - parseJSON(); - - if (trickyStore) { - LOGD("TrickyStore module detected!"); - spoofProvider = false; - spoofProps = false; - } - - if (testSignedRom) { - LOGD("--- ROM IS SIGNED WITH TEST KEYS ---"); - spoofSignature = true; - } - } - - void postAppSpecialize(const zygisk::AppSpecializeArgs *args) override { - if (dexVector.empty()) return; - - UpdateBuildFields(); - - if (spoofProvider || spoofSignature) { - injectDex(); - } else { - LOGD("Dex file won't be injected due spoofProvider and spoofSignature are false"); - } - - if (spoofProps) { - if (!doHook()) { - dlclose(); - } - } else { - dlclose(); - } - - json.clear(); - dexVector.clear(); - dexVector.shrink_to_fit(); - } - - void preServerSpecialize(zygisk::ServerSpecializeArgs *args) override { - api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY); - } - -private: - zygisk::Api *api = nullptr; - JNIEnv *env = nullptr; - std::vector dexVector; - nlohmann::json json; - bool spoofProps = true; - bool spoofProvider = true; - bool spoofSignature = false; - - void dlclose() { - LOGD("dlclose zygisk lib"); - api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY); - } - - void parseJSON() { - if (json.empty()) return; - - if (json.contains("DEVICE_INITIAL_SDK_INT")) { - if (json["DEVICE_INITIAL_SDK_INT"].is_string()) { - DEVICE_INITIAL_SDK_INT = json["DEVICE_INITIAL_SDK_INT"].get(); - } else if (json["DEVICE_INITIAL_SDK_INT"].is_number_integer()) { - DEVICE_INITIAL_SDK_INT = std::to_string(json["DEVICE_INITIAL_SDK_INT"].get()); - } else { - LOGE("Couldn't parse DEVICE_INITIAL_SDK_INT value!"); - } - json.erase("DEVICE_INITIAL_SDK_INT"); - } - - if (json.contains("spoofProvider") && json["spoofProvider"].is_boolean()) { - spoofProvider = json["spoofProvider"].get(); - json.erase("spoofProvider"); - } - - if (json.contains("spoofProps") && json["spoofProps"].is_boolean()) { - spoofProps = json["spoofProps"].get(); - json.erase("spoofProps"); - } - - if (json.contains("spoofSignature") && json["spoofSignature"].is_boolean()) { - spoofSignature = json["spoofSignature"].get(); - json.erase("spoofSignature"); - } - - if (json.contains("DEBUG") && json["DEBUG"].is_boolean()) { - DEBUG = json["DEBUG"].get(); - json.erase("DEBUG"); - } - - if (json.contains("FINGERPRINT") && json["FINGERPRINT"].is_string()) { - std::string fingerprint = json["FINGERPRINT"].get(); - - std::vector vector; - auto parts = fingerprint | std::views::split('/'); - - for (const auto &part: parts) { - auto subParts = std::string(part.begin(), part.end()) | std::views::split(':'); - for (const auto &subPart: subParts) { - vector.emplace_back(subPart.begin(), subPart.end()); - } - } - - if (vector.size() == 8) { - json["BRAND"] = vector[0]; - json["PRODUCT"] = vector[1]; - json["DEVICE"] = vector[2]; - json["RELEASE"] = vector[3]; - json["ID"] = vector[4]; - json["INCREMENTAL"] = vector[5]; - json["TYPE"] = vector[6]; - json["TAGS"] = vector[7]; - } else { - LOGE("Error parsing fingerprint values!"); - } - } - - if (json.contains("SECURITY_PATCH") && json["SECURITY_PATCH"].is_string()) { - SECURITY_PATCH = json["SECURITY_PATCH"].get(); - } - - if (json.contains("ID") && json["ID"].is_string()) { - BUILD_ID = json["ID"].get(); - } - } - - void injectDex() { - LOGD("get system classloader"); - auto clClass = env->FindClass("java/lang/ClassLoader"); - auto getSystemClassLoader = env->GetStaticMethodID(clClass, "getSystemClassLoader", - "()Ljava/lang/ClassLoader;"); - auto systemClassLoader = env->CallStaticObjectMethod(clClass, getSystemClassLoader); - - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); - return; - } - - LOGD("create class loader"); - auto dexClClass = env->FindClass("dalvik/system/InMemoryDexClassLoader"); - auto dexClInit = env->GetMethodID(dexClClass, "", - "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V"); - auto buffer = env->NewDirectByteBuffer(dexVector.data(), - static_cast(dexVector.size())); - auto dexCl = env->NewObject(dexClClass, dexClInit, buffer, systemClassLoader); - - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); - return; - } - - LOGD("load class"); - auto loadClass = env->GetMethodID(clClass, "loadClass", - "(Ljava/lang/String;)Ljava/lang/Class;"); - auto entryClassName = env->NewStringUTF("es.chiteroman.playintegrityfix.EntryPoint"); - auto entryClassObj = env->CallObjectMethod(dexCl, loadClass, entryClassName); - auto entryPointClass = (jclass) entryClassObj; - - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); - return; - } - - LOGD("call init"); - auto entryInit = env->GetStaticMethodID(entryPointClass, "init", "(Ljava/lang/String;ZZ)V"); - auto jsonStr = env->NewStringUTF(json.dump().c_str()); - env->CallStaticVoidMethod(entryPointClass, entryInit, jsonStr, spoofProvider, - spoofSignature); - - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); - } - - env->DeleteLocalRef(entryClassName); - env->DeleteLocalRef(entryClassObj); - env->DeleteLocalRef(jsonStr); - env->DeleteLocalRef(dexCl); - env->DeleteLocalRef(buffer); - env->DeleteLocalRef(dexClClass); - env->DeleteLocalRef(clClass); - - LOGD("jni memory free"); - } - - void UpdateBuildFields() { - jclass buildClass = env->FindClass("android/os/Build"); - jclass versionClass = env->FindClass("android/os/Build$VERSION"); - - for (auto &[key, val]: json.items()) { - if (!val.is_string()) continue; - - const char *fieldName = key.c_str(); - - jfieldID fieldID = env->GetStaticFieldID(buildClass, fieldName, "Ljava/lang/String;"); - - if (env->ExceptionCheck()) { - env->ExceptionClear(); - - fieldID = env->GetStaticFieldID(versionClass, fieldName, "Ljava/lang/String;"); - - if (env->ExceptionCheck()) { - env->ExceptionClear(); - continue; - } - } - - if (fieldID != nullptr) { - std::string str = val.get(); - const char *value = str.c_str(); - jstring jValue = env->NewStringUTF(value); - - env->SetStaticObjectField(buildClass, fieldID, jValue); - if (env->ExceptionCheck()) { - env->ExceptionClear(); - continue; - } - - LOGD("Set '%s' to '%s'", fieldName, value); - } - } - } -}; - -static std::vector readFile(const char *path) { - FILE *file = fopen(path, "rb"); - - if (!file) return {}; - - int size = static_cast(std::filesystem::file_size(path)); - - std::vector vector(size); - - fread(vector.data(), 1, size, file); - - fclose(file); - - return vector; -} - static bool checkOtaZip() { - std::array buffer{}; + std::array buffer{}; std::string result; bool found = false; @@ -445,44 +98,202 @@ static bool checkOtaZip() { return found; } +static std::string gmsDir; + +static bool +copyFile(const std::string &origin, const std::string &path, + std::filesystem::perms perms = std::filesystem::perms::all) { + std::string dest = gmsDir + "/" + path; + + bool ok = std::filesystem::copy_file( + origin, dest, std::filesystem::copy_options::overwrite_existing); + + std::error_code ec; + + std::filesystem::permissions(dest, perms, ec); + + ok &= ec.value() == 0; + + return ok; +} + static void companion(int fd) { + bool ok = false; - std::vector dex, json; + size_t size = 0; + xread(fd, &size, sizeof(size)); - if (std::filesystem::exists(DEX_PATH)) { - dex = readFile(DEX_PATH); + gmsDir.resize(size); + + size = xread(fd, gmsDir.data(), size); + + gmsDir.resize(size); + gmsDir[size] = '\0'; + gmsDir.shrink_to_fit(); + + LOGD("[ROOT] GMS dir: %s", gmsDir.c_str()); + + ok = copyFile(INJECT_LIB, "libinject.so"); + + ok &= copyFile(DEX_FILE, "classes.dex", + std::filesystem::perms::owner_read | + std::filesystem::perms::group_read | + std::filesystem::perms::others_read); + + if (std::filesystem::exists(CUSTOM_PIF_FILE)) { + ok &= copyFile(CUSTOM_PIF_FILE, "pif.json"); + } else if (std::filesystem::exists(CUSTOM_PIF_FILE_1)) { + ok &= copyFile(CUSTOM_PIF_FILE_1, "pif.json"); + } else if (std::filesystem::exists(PIF_FILE)) { + ok &= copyFile(PIF_FILE, "pif.json"); + } else { + ok = false; } - if (std::filesystem::exists(PIF_JSON)) { - json = readFile(PIF_JSON); - } else if (std::filesystem::exists(PIF_JSON_DEFAULT)) { - json = readFile(PIF_JSON_DEFAULT); - } - - int dexSize = static_cast(dex.size()); - int jsonSize = static_cast(json.size()); - - xwrite(fd, &dexSize, sizeof(dexSize)); - xwrite(fd, &jsonSize, sizeof(jsonSize)); - - if (dexSize > 0) { - xwrite(fd, dex.data(), dexSize * sizeof(uint8_t)); - } - - if (jsonSize > 0) { - xwrite(fd, json.data(), jsonSize * sizeof(uint8_t)); - } - - std::string ts(TS_PATH); + std::string ts("/data/adb/modules/tricky_store"); bool trickyStore = std::filesystem::exists(ts) && !std::filesystem::exists(ts + "/disable") && !std::filesystem::exists(ts + "/remove"); - xwrite(fd, &trickyStore, sizeof(trickyStore)); + if (trickyStore) { + FILE *file = fopen((gmsDir + "/trickystore").c_str(), "w"); + if (file) + fclose(file); + } else { + if (std::filesystem::exists(gmsDir + "/trickystore")) { + std::filesystem::remove(gmsDir + "/trickystore"); + } + } bool testSignedRom = checkOtaZip(); - xwrite(fd, &testSignedRom, sizeof(testSignedRom)); + if (testSignedRom) { + FILE *file = fopen((gmsDir + "/testsign").c_str(), "w"); + if (file) + fclose(file); + } else { + if (std::filesystem::exists(gmsDir + "/testsign")) { + std::filesystem::remove(gmsDir + "/testsign"); + } + } + + gmsDir.clear(); + gmsDir.shrink_to_fit(); + + LOGD("[ROOT] OK? %d", ok); + + write(fd, &ok, sizeof(ok)); } +using namespace zygisk; + +class PlayIntegrityFix : public ModuleBase { +public: + void onLoad(Api *_api, JNIEnv *_env) override { + this->api = _api; + this->env = _env; + } + + void preAppSpecialize(AppSpecializeArgs *args) override { + api->setOption(DLCLOSE_MODULE_LIBRARY); + + if (!args) + return; + + bool isGms = false, isGmsUnstable = false; + + auto name = env->GetStringUTFChars(args->nice_name, nullptr); + auto dir = env->GetStringUTFChars(args->app_data_dir, nullptr); + + if (name) { + isGmsUnstable = + std::string_view(name) == "com.google.android.gms.unstable"; + env->ReleaseStringUTFChars(args->nice_name, name); + } + + if (dir) { + isGms = std::string_view(dir).ends_with("/com.google.android.gms"); + isGmsUnstable &= isGms; + if (isGmsUnstable) { + gmsDir = dir; + } + env->ReleaseStringUTFChars(args->app_data_dir, dir); + } + + if (!isGms) + return; + + api->setOption(FORCE_DENYLIST_UNMOUNT); + + if (!isGmsUnstable) + return; + + LOGD("We are in GMS unstable process!"); + + if (gmsDir.empty()) { + LOGE("dir is empty, wtf?"); + return; + } + + LOGD("GMS dir: %s", gmsDir.c_str()); + + int fd = api->connectCompanion(); + + size_t size = gmsDir.size(); + xwrite(fd, &size, sizeof(size)); + + xwrite(fd, gmsDir.data(), size); + + bool done = false; + xread(fd, &done, sizeof(done)); + + close(fd); + + if (!done) + gmsDir.clear(); + } + + void postAppSpecialize(const AppSpecializeArgs *args) override { + if (!args || gmsDir.empty()) + return; + + void *handle = dlopen((gmsDir + "/libinject.so").c_str(), RTLD_NOW); + if (!handle) { + LOGE("Error loading lib: %s", dlerror()); + return; + } + + dlerror(); + + typedef void (*init_t)(const char *, JavaVM *); + + auto init_func = reinterpret_cast(dlsym(handle, "init")); + const char *error = dlerror(); + if (error) { + LOGE("Error loading lib: %s", error); + dlclose(handle); + return; + } + + JavaVM *jvm = nullptr; + auto result = env->GetJavaVM(&jvm); + if (result != JNI_OK) { + LOGE("couldn't get jvm"); + dlclose(handle); + return; + } + + init_func(gmsDir.c_str(), jvm); + } + + void preServerSpecialize(ServerSpecializeArgs *args) override { + api->setOption(DLCLOSE_MODULE_LIBRARY); + } + +private: + JNIEnv *env = nullptr; + Api *api = nullptr; + std::string gmsDir; +}; + REGISTER_ZYGISK_MODULE(PlayIntegrityFix) -REGISTER_ZYGISK_COMPANION(companion) +REGISTER_ZYGISK_COMPANION(companion) \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2e7b646..06783b3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,4 +8,4 @@ cxx = { group = "org.lsposed.libcxx", name = "libcxx", version.ref = "cxx" } hiddenapibypass = { group = "org.lsposed.hiddenapibypass", name = "hiddenapibypass", version.ref = "hiddenapibypass" } [plugins] -android-application = { id = "com.android.application", version.ref = "agp" } +android-application = { id = "com.android.application", version.ref = "agp" } \ No newline at end of file diff --git a/module/module.prop b/module/module.prop index a0d5954..5bef5c5 100644 --- a/module/module.prop +++ b/module/module.prop @@ -1,7 +1,7 @@ id=playintegrityfix name=Play Integrity Fix -version=v18.2 -versionCode=18200 +version=v18.2.1-EXPERIMENTAL +versionCode=18201 author=chiteroman description=Universal modular fix for Play Integrity (and SafetyNet) on devices running Android 8-15 updateJson=https://raw.githubusercontent.com/chiteroman/PlayIntegrityFix/main/update.json diff --git a/module/pif.json b/module/pif.json index 8b9b56d..77c3635 100644 --- a/module/pif.json +++ b/module/pif.json @@ -1,5 +1,5 @@ { - "FINGERPRINT": "google/oriole_beta/oriole:Baklava/BP21.241121.009/12787338:user/release-keys", + "FINGERPRINT": "google/oriole_beta/oriole:15/BP11.241121.013/12873528:user/release-keys", "MANUFACTURER": "Google", "MODEL": "Pixel 6", "SECURITY_PATCH": "2024-12-05",