From a4086509f9211ed5cf8cdd58e809c10f9df99b8e Mon Sep 17 00:00:00 2001 From: chiteroman Date: Wed, 29 Jan 2025 22:53:14 +0100 Subject: [PATCH] fix code --- app/src/main/cpp/inject.cpp | 274 +++++++++++++++++++++++++++++++++++- app/src/main/cpp/main.cpp | 202 +++++++++++++++++++++----- module/module.prop | 2 +- 3 files changed, 438 insertions(+), 40 deletions(-) diff --git a/app/src/main/cpp/inject.cpp b/app/src/main/cpp/inject.cpp index 6825ff6..c71c516 100644 --- a/app/src/main/cpp/inject.cpp +++ b/app/src/main/cpp/inject.cpp @@ -1,7 +1,8 @@ #include #include -#include "json.hpp" +#include #include "dobby.h" +#include "json.hpp" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "PIF", __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "PIF", __VA_ARGS__) @@ -9,11 +10,280 @@ static std::string dir; static JNIEnv *env; +static nlohmann::json json; + +static bool spoofProps = true, spoofProvider = true, spoofSignature = false; + +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; +} + +static 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(); + } +} + +static 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 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((dir + "/classes.dex").c_str()); + auto str2 = env->NewStringUTF(dir.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(str1); + env->DeleteLocalRef(str2); + env->DeleteLocalRef(dexClClass); + env->DeleteLocalRef(clClass); + + LOGD("jni memory free"); +} + extern "C" [[gnu::visibility("default"), maybe_unused]] -void init(char *rawDir, JavaVM *jvm) { +bool init(char *rawDir, JavaVM *jvm) { dir = rawDir; LOGD("[INJECT] GMS dir: %s", dir.c_str()); jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); + bool close = true; + + FILE *f = fopen((dir + "/pif.json").c_str(), "r"); + json = nlohmann::json::parse(f, nullptr, false, true); + fclose(f); + + parseJSON(); + + UpdateBuildFields(); + + if (std::filesystem::exists(dir + "/trickystore")) { + LOGD("[INJECT] trickystore detected!"); + spoofProvider = false; + spoofProps = false; + } + + if (std::filesystem::exists(dir + "/unsign")) { + LOGD("[INJECT] test-keys signed rom detected!"); + spoofSignature = true; + } + + if (spoofProvider || spoofSignature) { + injectDex(); + } else { + LOGD("[INJECT] Dex file won't be injected due spoofProvider and spoofSignature are false"); + } + + if (spoofProps) { + close = !doHook(); + } LOGD("[INJECT] Done!"); + + return close; } \ No newline at end of file diff --git a/app/src/main/cpp/main.cpp b/app/src/main/cpp/main.cpp index 797d7ac..741742f 100644 --- a/app/src/main/cpp/main.cpp +++ b/app/src/main/cpp/main.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "zygisk.hpp" @@ -21,6 +22,10 @@ #define CUSTOM_JSON "/data/adb/pif.json" #define TS_PATH "/data/adb/modules/tricky_store" +#define TS_PATH_DISABLE "/data/adb/modules/tricky_store/disable" +#define TS_PATH_REMOVE "/data/adb/modules/tricky_store/remove" + +#define TS_TARGET "/data/adb/tricky_store/target.txt" static ssize_t xread(int fd, void *buffer, size_t count) { ssize_t total = 0; @@ -37,7 +42,7 @@ static ssize_t xread(int fd, void *buffer, size_t count) { static ssize_t xwrite(int fd, const void *buffer, size_t count) { ssize_t total = 0; - const char *buf = static_cast(buffer); + char *buf = (char *) buffer; while (count > 0) { ssize_t ret = write(fd, buf, count); if (ret < 0) return -1; @@ -48,45 +53,62 @@ static ssize_t xwrite(int fd, const void *buffer, size_t count) { return total; } +static bool createFile(const char *path, mode_t perms) { + FILE *file = fopen(path, "w"); + + if (file == nullptr) { + LOGE("[COMPANION] Failed to create file: %s", path); + return false; + } + + if (chmod(path, perms) == -1) { + LOGE("[COMPANION] Failed to set permissions on destination file: %s", path); + fclose(file); + return false; + } + + fclose(file); + + return true; +} + static bool copyFile(const char *origin, const char *dest, mode_t perms) { - int fd_in, fd_out; - ssize_t bytes_read, bytes_written; - char buffer[4096]; + int input, output; + struct stat stat_buf{}; + off_t offset = 0; - fd_in = open(origin, O_RDONLY); - if (fd_in < 0) { + if ((input = open(origin, O_RDONLY)) == -1) { + LOGE("[COMPANION] Failed to open source file: %s", origin); return false; } - fd_out = open(dest, O_WRONLY | O_CREAT | O_TRUNC, perms); - if (fd_out < 0) { - close(fd_in); + if (fstat(input, &stat_buf) == -1) { + LOGE("[COMPANION] Failed to stat source file: %s", origin); + close(input); return false; } - while ((bytes_read = read(fd_in, buffer, sizeof(buffer))) > 0) { - ssize_t total_written = 0; - while (total_written < bytes_read) { - bytes_written = write(fd_out, buffer + total_written, bytes_read - total_written); - if (bytes_written < 0) { - close(fd_in); - close(fd_out); - return false; - } - total_written += bytes_written; - } - } - - if (bytes_read < 0) { - close(fd_in); - close(fd_out); + if ((output = open(dest, O_WRONLY | O_CREAT | O_TRUNC, perms)) == -1) { + LOGE("[COMPANION] Failed to open destination file: %s", dest); + close(input); return false; } - fchmod(fd_out, perms); + ssize_t bytes_copied = sendfile(output, input, &offset, stat_buf.st_size); + if (bytes_copied == -1) { + LOGE("[COMPANION] Failed to copy file: %s", origin); + close(input); + close(output); + return false; + } - close(fd_in); - close(fd_out); + close(input); + close(output); + + if (chmod(dest, perms) == -1) { + LOGE("[COMPANION] Failed to set permissions on destination file: %s", dest); + return false; + } return true; } @@ -103,11 +125,79 @@ static char *concatStr(const char *str1, const char *str2) { return buffer; } +static bool trickyStoreExists() { + struct stat st{}; + + if (stat(TS_PATH, &st) == 0 && S_ISDIR(st.st_mode)) { + if (stat(TS_PATH_DISABLE, &st) != 0) { + if (stat(TS_PATH_REMOVE, &st) != 0) { + return true; + } + } + } + + return false; +} + +static bool checkOtaZip() { + char buffer[256] = {0}; + char result[1024 * 10] = {0}; + bool found = false; + + FILE *pipe = popen("unzip -l /system/etc/security/otacerts.zip", "r"); + if (!pipe) return false; + + while (fgets(buffer, sizeof(buffer), pipe) != nullptr) { + strcat(result, buffer); + if (strstr(result, "test") != nullptr) { + found = true; + break; + } + } + + pclose(pipe); + return found; +} + +static void playIntegrityApiHandleNewChecks() { + FILE *file = fopen(TS_TARGET, "r+"); + if (file == nullptr) { + return; + } + + char line[256]; + bool android_found = false, vending_found = false; + + while (fgets(line, sizeof(line), file) != nullptr) { + size_t len = strlen(line); + if (len > 0 && line[len - 1] == '\n') { + line[len - 1] = '\0'; + } + if (strcmp(line, "android") == 0) android_found = true; + else if (strcmp(line, "com.android.vending") == 0) vending_found = true; + } + + fseek(file, 0, SEEK_END); + + if (!android_found) { + fprintf(file, "android\n"); + LOGE("[COMPANION] add 'android' to target.txt"); + } + + if (!vending_found) { + fprintf(file, "com.android.vending\n"); + LOGE("[COMPANION] add 'com.android.vending' to target.txt"); + } + + fclose(file); +} + static void companion(int fd) { + bool ok = true; size_t len = 0; xread(fd, &len, sizeof(size_t)); - char *dir = static_cast(calloc(len, sizeof(char))); + char *dir = static_cast(calloc(len + 1, sizeof(char))); ssize_t size = xread(fd, dir, len); dir[size] = '\0'; @@ -115,16 +205,16 @@ static void companion(int fd) { char *libFile = concatStr(dir, "/libinject.so"); #if defined(__aarch64__) - copyFile(LIB_64, libFile, 0777); + ok &= copyFile(LIB_64, libFile, 0777); #elif defined(__arm__) - copyFile(LIB_32, libFile, 0777); + ok &= copyFile(LIB_32, libFile, 0777); #endif free(libFile); LOGD("[COMPANION] copied lib"); char *dexFile = concatStr(dir, "/classes.dex"); - copyFile(DEX_PATH, dexFile, 0644); + ok &= copyFile(DEX_PATH, dexFile, 0644); free(dexFile); LOGD("[COMPANION] copied dex"); @@ -132,16 +222,36 @@ static void companion(int fd) { char *jsonFile = concatStr(dir, "/pif.json"); if (!copyFile(CUSTOM_JSON, jsonFile, 0777)) { if (!copyFile(CUSTOM_JSON_FORK, jsonFile, 0777)) { - copyFile(DEFAULT_JSON, jsonFile, 0777); + if (!copyFile(DEFAULT_JSON, jsonFile, 0777)) { + ok = false; + } } } free(jsonFile); LOGD("[COMPANION] copied json"); + char *ts = concatStr(dir, "/trickystore"); + if (trickyStoreExists()) { + ok &= createFile(ts, 0777); + playIntegrityApiHandleNewChecks(); + LOGD("[COMPANION] trickystore detected!"); + } else { + remove(ts); + } + free(ts); + + char *unsign = concatStr(dir, "/unsign"); + if (checkOtaZip()) { + ok &= createFile(unsign, 0777); + LOGD("[COMPANION] test-keys signed rom detected!"); + } else { + remove(unsign); + } + free(unsign); + free(dir); - bool ok = true; xwrite(fd, &ok, sizeof(bool)); LOGD("[COMPANION] end"); @@ -194,6 +304,12 @@ public: bool ok = false; xread(fd, &ok, sizeof(bool)); + if (!ok) { + LOGE("ERROR"); + free(targetDir); + targetDir = nullptr; + } + close(fd); } @@ -201,7 +317,7 @@ public: if (!targetDir) return; char *lib = concatStr(targetDir, "/libinject.so"); - void *handle = dlopen(lib, RTLD_NOW); + void *handle = dlopen(lib, RTLD_LAZY); free(lib); if (!handle) { @@ -212,7 +328,7 @@ public: dlerror(); - void (*init)(char *, JavaVM *); + bool (*init)(char *, JavaVM *); *(void **) (&init) = dlsym(handle, "init"); const char *error = dlerror(); @@ -226,9 +342,21 @@ public: JavaVM *jvm = nullptr; env->GetJavaVM(&jvm); - init(targetDir, jvm); + bool close = true; + + if (jvm) + close = init(targetDir, jvm); + else + LOGE("jvm is null!"); free(targetDir); + + if (close) { + dlclose(handle); + LOGD("dlclose injected lib!"); + } + + LOGD("DONE"); } void preServerSpecialize(zygisk::ServerSpecializeArgs *args) override { diff --git a/module/module.prop b/module/module.prop index d35d00e..0ea8370 100644 --- a/module/module.prop +++ b/module/module.prop @@ -4,4 +4,4 @@ version=v1-inject versionCode=1 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 +updateJson=https://raw.githubusercontent.com/chiteroman/PlayIntegrityFix/refs/heads/inject/update.json