diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ab8071b..7c7650b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -25,17 +25,12 @@ android { applicationId = "es.chiteroman.playintegrityfix" minSdk = 26 targetSdk = 35 - versionCode = 18201 - versionName = "v18.2.1-EXPERIMENTAL" + versionCode = 18200 + versionName = "v18.2" multiDexEnabled = false externalNativeBuild { cmake { - abiFilters( - "arm64-v8a", - "armeabi-v7a" - ) - arguments( "-DCMAKE_BUILD_TYPE=MinSizeRel", "-DANDROID_STL=none" @@ -114,20 +109,11 @@ tasks.register("copyFiles") { dexFile.copyTo(moduleFolder.resolve("classes.dex"), 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) - } - } + 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) + } } } diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 9314e1d..76f48a4 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -6,12 +6,8 @@ find_package(cxx REQUIRED CONFIG) link_libraries(cxx::cxx) -add_library(zygisk SHARED main.cpp) - -target_link_libraries(zygisk PRIVATE log) - -add_library(inject SHARED inject.cpp) +add_library(${CMAKE_PROJECT_NAME} SHARED main.cpp) add_subdirectory(Dobby) -target_link_libraries(inject PRIVATE log dobby_static) \ No newline at end of file +target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE log dobby_static) diff --git a/app/src/main/cpp/inject.cpp b/app/src/main/cpp/inject.cpp deleted file mode 100644 index fd29a63..0000000 --- a/app/src/main/cpp/inject.cpp +++ /dev/null @@ -1,332 +0,0 @@ -#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 deleted file mode 100644 index 95e3208..0000000 --- a/app/src/main/cpp/log.h +++ /dev/null @@ -1,4 +0,0 @@ -#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 681d03f..897432e 100644 --- a/app/src/main/cpp/main.cpp +++ b/app/src/main/cpp/main.cpp @@ -1,85 +1,432 @@ -#include "log.h" +#include +#include +#include #include "zygisk.hpp" -#include -#include -#include +#include "dobby.h" +#include "json.hpp" -#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 LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "PIF", __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "PIF", __VA_ARGS__) -#define DEX_FILE "/data/adb/modules/playintegrityfix/classes.dex" +#define DEX_PATH "/data/adb/modules/playintegrityfix/classes.dex" -#define PIF_FILE "/data/adb/modules/playintegrityfix/pif.json" +#define PIF_JSON "/data/adb/pif.json" -#define CUSTOM_PIF_FILE "/data/adb/pif.json" +#define PIF_JSON_DEFAULT "/data/adb/modules/playintegrityfix/pif.json" -#define CUSTOM_PIF_FILE_1 "/data/adb/modules/playintegrityfix/custom.pif.json" +#define TS_PATH "/data/adb/modules/tricky_store" -static inline ssize_t xread(int fd, void *buffer, size_t count) { - auto *buf = static_cast(buffer); +static ssize_t xread(int fd, void *buffer, size_t count) { ssize_t total = 0; - + char *buf = (char *) buffer; while (count > 0) { - 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; - } - + ssize_t ret = TEMP_FAILURE_RETRY(read(fd, buf, count)); + if (ret < 0) return -1; buf += ret; total += ret; count -= ret; } - return total; } -static inline ssize_t xwrite(int fd, const void *buffer, size_t count) { - auto *buf = static_cast(buffer); +static ssize_t xwrite(int fd, const void *buffer, size_t count) { ssize_t total = 0; - + char *buf = (char *) buffer; while (count > 0) { - 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; - } - + ssize_t ret = TEMP_FAILURE_RETRY(write(fd, buf, count)); + if (ret < 0) return -1; 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; @@ -98,202 +445,44 @@ 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; - size_t size = 0; - xread(fd, &size, sizeof(size)); + std::vector dex, json; - 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(DEX_PATH)) { + dex = readFile(DEX_PATH); } - std::string ts("/data/adb/modules/tricky_store"); + 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); bool trickyStore = std::filesystem::exists(ts) && !std::filesystem::exists(ts + "/disable") && !std::filesystem::exists(ts + "/remove"); - 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"); - } - } + xwrite(fd, &trickyStore, sizeof(trickyStore)); bool testSignedRom = checkOtaZip(); - 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)); + xwrite(fd, &testSignedRom, sizeof(testSignedRom)); } -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) \ No newline at end of file +REGISTER_ZYGISK_COMPANION(companion) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 06783b3..2e7b646 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" } \ No newline at end of file +android-application = { id = "com.android.application", version.ref = "agp" } diff --git a/module/module.prop b/module/module.prop index 5bef5c5..a0d5954 100644 --- a/module/module.prop +++ b/module/module.prop @@ -1,7 +1,7 @@ id=playintegrityfix name=Play Integrity Fix -version=v18.2.1-EXPERIMENTAL -versionCode=18201 +version=v18.2 +versionCode=18200 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 77c3635..8b9b56d 100644 --- a/module/pif.json +++ b/module/pif.json @@ -1,5 +1,5 @@ { - "FINGERPRINT": "google/oriole_beta/oriole:15/BP11.241121.013/12873528:user/release-keys", + "FINGERPRINT": "google/oriole_beta/oriole:Baklava/BP21.241121.009/12787338:user/release-keys", "MANUFACTURER": "Google", "MODEL": "Pixel 6", "SECURITY_PATCH": "2024-12-05",