From 56eed97fdd8301d97dac818f69e6f9fca15187e4 Mon Sep 17 00:00:00 2001 From: chiteroman Date: Sun, 18 Aug 2024 01:34:13 +0200 Subject: [PATCH] Big update - Spoof android.os.Build fields in Zygisk instead Java - Compatibility with TrickyStore module - If hook is disabled, unload Zygisk lib - Add granular advanced spoofing options (thanks @osm0sis) - Other minor improvements --- app/src/main/cpp/main.cpp | 133 +++++++++++++++--- .../playintegrityfix/CustomProvider.java | 8 -- .../playintegrityfix/EntryPoint.java | 92 ++---------- module/pif.json | 5 +- 4 files changed, 129 insertions(+), 109 deletions(-) diff --git a/app/src/main/cpp/main.cpp b/app/src/main/cpp/main.cpp index f18404c..c0a58e1 100644 --- a/app/src/main/cpp/main.cpp +++ b/app/src/main/cpp/main.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include #include "zygisk.hpp" #include "dobby.h" @@ -17,6 +16,8 @@ #define PIF_JSON_DEFAULT "/data/adb/modules/playintegrityfix/pif.json" +#define TS_PATH "/data/adb/modules/tricky_store" + static ssize_t xread(int fd, void *buffer, size_t count) { ssize_t total = 0; char *buf = (char *) buffer; @@ -51,12 +52,11 @@ static bool DEBUG = false; typedef void (*T_Callback)(void *, const char *, const char *, uint32_t); -static std::map callbacks; +static volatile T_Callback o_callback = nullptr; static void modify_callback(void *cookie, const char *name, const char *value, uint32_t serial) { - if (cookie == nullptr || name == nullptr || value == nullptr || - !callbacks.contains(cookie)) + if (cookie == nullptr || name == nullptr || value == nullptr || o_callback == nullptr) return; std::string_view prop(name); @@ -80,19 +80,18 @@ static void modify_callback(void *cookie, const char *name, const char *value, u if (DEBUG) LOGD("[%s]: %s", name, value); - return callbacks[cookie](cookie, name, value, serial); + return o_callback(cookie, name, value, serial); } static void (*o_system_property_read_callback)(const prop_info *, T_Callback, void *) = nullptr; static void my_system_property_read_callback(const prop_info *pi, T_Callback callback, void *cookie) { - if (pi && callback && cookie) callbacks[cookie] = callback; + if (pi && callback && cookie) o_callback = callback; return o_system_property_read_callback(pi, modify_callback, cookie); } static void doHook() { - LOGD("JSON contains DEVICE_INITIAL_SDK_INT key. Hooking native prop symbol"); void *handle = DobbySymbolResolver(nullptr, "__system_property_read_callback"); if (!handle) { LOGE("error resolving __system_property_read_callback symbol!"); @@ -174,23 +173,36 @@ public: json = cJSON_ParseWithLength(strJson.c_str(), strJson.size()); } + bool trickyStore = false; + xread(fd, &trickyStore, sizeof(trickyStore)); + close(fd); LOGD("Dex file size: %d", dexSize); LOGD("Json file size: %d", jsonSize); + + parseJSON(); + + if (trickyStore) { + LOGD("TrickyStore module installed and enabled, disabling spoofProps and spoofProvider"); + spoofProps = false; + spoofProvider = false; + } } void postAppSpecialize(const zygisk::AppSpecializeArgs *args) override { if (dexVector.empty()) return; - parseJSON(); - - if (enableHook) doHook(); - else api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY); - - injectDex(); + UpdateBuildFields(); cJSON_Delete(json); + + if (spoofProps) doHook(); + else api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY); + + if (spoofProvider || spoofSignature) injectDex(); + else + LOGD("Don't inject dex, spoofProvider and spoofSignature are false"); } void preServerSpecialize(zygisk::ServerSpecializeArgs *args) override { @@ -202,7 +214,9 @@ private: JNIEnv *env = nullptr; std::vector dexVector; cJSON *json = nullptr; - bool enableHook = false; + bool spoofProps = true; + bool spoofProvider = true; + bool spoofSignature = false; void parseJSON() { if (!json) return; @@ -211,9 +225,11 @@ private: const cJSON *security_patch = cJSON_GetObjectItemCaseSensitive(json, "SECURITY_PATCH"); const cJSON *build_id = cJSON_GetObjectItemCaseSensitive(json, "ID"); const cJSON *isDebug = cJSON_GetObjectItemCaseSensitive(json, "DEBUG"); + const cJSON *spoof_props = cJSON_GetObjectItemCaseSensitive(json, "spoofProps"); + const cJSON *spoof_provider = cJSON_GetObjectItemCaseSensitive(json, "spoofProvider"); + const cJSON *spoof_signature = cJSON_GetObjectItemCaseSensitive(json, "spoofSignature"); if (api_level) { - enableHook = true; if (cJSON_IsNumber(api_level)) { DEVICE_INITIAL_SDK_INT = std::to_string(api_level->valueint); } else if (cJSON_IsString(api_level)) { @@ -234,6 +250,21 @@ private: DEBUG = cJSON_IsTrue(isDebug); cJSON_DeleteItemFromObjectCaseSensitive(json, "DEBUG"); } + + if (spoof_props && cJSON_IsBool(spoof_props)) { + spoofProps = cJSON_IsTrue(spoof_props); + cJSON_DeleteItemFromObjectCaseSensitive(json, "spoofProps"); + } + + if (spoof_provider && cJSON_IsBool(spoof_provider)) { + spoofProvider = cJSON_IsTrue(spoof_provider); + cJSON_DeleteItemFromObjectCaseSensitive(json, "spoofProvider"); + } + + if (spoof_signature && cJSON_IsBool(spoof_signature)) { + spoofSignature = cJSON_IsTrue(spoof_signature); + cJSON_DeleteItemFromObjectCaseSensitive(json, "spoofSignature"); + } } void injectDex() { @@ -260,9 +291,71 @@ private: auto entryPointClass = (jclass) entryClassObj; LOGD("call init"); - auto entryInit = env->GetStaticMethodID(entryPointClass, "init", "(Ljava/lang/String;)V"); - auto jsonStr = env->NewStringUTF(cJSON_Print(json)); - env->CallStaticVoidMethod(entryPointClass, entryInit, jsonStr); + auto entryInit = env->GetStaticMethodID(entryPointClass, "init", "(ZZ)V"); + env->CallStaticVoidMethod(entryPointClass, entryInit, spoofProvider, spoofSignature); + } + + void UpdateBuildFields() { + jclass buildClass = env->FindClass("android/os/Build"); + jclass versionClass = env->FindClass("android/os/Build$VERSION"); + + cJSON *currentElement = nullptr; + cJSON_ArrayForEach(currentElement, json) { + const char *key = currentElement->string; + + if (cJSON_IsString(currentElement)) { + const char *value = currentElement->valuestring; + jfieldID fieldID = env->GetStaticFieldID(buildClass, key, "Ljava/lang/String;"); + + if (env->ExceptionCheck()) { + env->ExceptionClear(); + + fieldID = env->GetStaticFieldID(versionClass, key, "Ljava/lang/String;"); + + if (env->ExceptionCheck()) { + env->ExceptionClear(); + continue; + } + } + + if (fieldID != nullptr) { + jstring jValue = env->NewStringUTF(value); + + env->SetStaticObjectField(buildClass, fieldID, jValue); + if (env->ExceptionCheck()) { + env->ExceptionClear(); + continue; + } + + LOGD("Set '%s' to '%s'", key, value); + } + } else if (cJSON_IsNumber(currentElement)) { + int value = currentElement->valueint; + jfieldID fieldID = env->GetStaticFieldID(buildClass, key, "I"); + + if (env->ExceptionCheck()) { + env->ExceptionClear(); + + fieldID = env->GetStaticFieldID(versionClass, key, "I"); + + if (env->ExceptionCheck()) { + env->ExceptionClear(); + continue; + } + } + + if (fieldID != nullptr) { + env->SetStaticIntField(buildClass, fieldID, value); + + if (env->ExceptionCheck()) { + env->ExceptionClear(); + continue; + } + + LOGD("Set '%s' to '%d'", key, value); + } + } + } } }; @@ -309,6 +402,10 @@ static void companion(int fd) { if (jsonSize > 0) { xwrite(fd, json.data(), jsonSize * sizeof(uint8_t)); } + + bool trickyStore = std::filesystem::exists(TS_PATH) && + !std::filesystem::exists(std::string(TS_PATH) + "/disable"); + xwrite(fd, &trickyStore, sizeof(trickyStore)); } REGISTER_ZYGISK_MODULE(PlayIntegrityFix) diff --git a/app/src/main/java/es/chiteroman/playintegrityfix/CustomProvider.java b/app/src/main/java/es/chiteroman/playintegrityfix/CustomProvider.java index c14552d..c288b74 100644 --- a/app/src/main/java/es/chiteroman/playintegrityfix/CustomProvider.java +++ b/app/src/main/java/es/chiteroman/playintegrityfix/CustomProvider.java @@ -9,12 +9,4 @@ public final class CustomProvider extends Provider { putAll(provider); put("KeyStore.AndroidKeyStore", CustomKeyStoreSpi.class.getName()); } - - @Override - public synchronized Service getService(String type, String algorithm) { - Thread t = new Thread(EntryPoint::spoofFields); - t.setDaemon(true); - t.start(); - return super.getService(type, algorithm); - } } diff --git a/app/src/main/java/es/chiteroman/playintegrityfix/EntryPoint.java b/app/src/main/java/es/chiteroman/playintegrityfix/EntryPoint.java index fdbdb3d..59edec0 100644 --- a/app/src/main/java/es/chiteroman/playintegrityfix/EntryPoint.java +++ b/app/src/main/java/es/chiteroman/playintegrityfix/EntryPoint.java @@ -6,12 +6,9 @@ import android.content.pm.Signature; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; -import android.text.TextUtils; import android.util.Base64; import android.util.Log; -import org.json.JSONException; -import org.json.JSONObject; import org.lsposed.hiddenapibypass.HiddenApiBypass; import java.lang.reflect.Field; @@ -21,7 +18,6 @@ import java.security.KeyStoreSpi; import java.security.Provider; import java.security.Security; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; import java.util.Objects; @@ -58,7 +54,7 @@ public final class EntryPoint { F7Xt """; - static { + private static void spoofProvider() { try { KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); Field keyStoreSpi = keyStore.getClass().getDeclaredField("keyStoreSpi"); @@ -79,7 +75,7 @@ public final class EntryPoint { Security.insertProviderAt(customProvider, 1); } - private static void spoofPackageManager() { + private static void spoofSignature() { Signature spoofedSignature = new Signature(Base64.decode(signatureData, Base64.DEFAULT)); Parcelable.Creator originalCreator = PackageInfo.CREATOR; Parcelable.Creator customCreator = new CustomPackageInfoCreator(originalCreator, spoofedSignature); @@ -138,83 +134,17 @@ public final class EntryPoint { throw new NoSuchFieldException("Field '" + fieldName + "' not found in class hierarchy of " + Objects.requireNonNull(currentClass).getName()); } - public static void init(String json) { - boolean spoofPackageManager = false; - - JSONObject jsonObject = null; - - try { - jsonObject = new JSONObject(json); - } catch (JSONException e) { - Log.e(TAG, "Can't parse json", e); + public static void init(boolean spoofProvider, boolean spoofSignature) { + if (spoofProvider) { + spoofProvider(); + } else { + Log.i(TAG, "Don't spoof Provider"); } - if (jsonObject == null || jsonObject.length() == 0) return; - - Iterator it = jsonObject.keys(); - - while (it.hasNext()) { - String key = it.next(); - - String value = ""; - try { - value = jsonObject.getString(key); - } catch (JSONException e) { - Log.e(TAG, "Couldn't get value from key", e); - } - - if (TextUtils.isEmpty(value)) continue; - - if ("SPOOF_PACKAGE_MANAGER".equals(key) && Boolean.parseBoolean(value)) { - spoofPackageManager = true; - continue; - } - - Field field = getFieldByName(key); - - if (field == null) continue; - - map.put(field, value); + if (spoofSignature) { + spoofSignature(); + } else { + Log.i(TAG, "Don't spoof signature"); } - - Log.i(TAG, "Fields ready to spoof: " + map.size()); - - spoofFields(); - if (spoofPackageManager) spoofPackageManager(); - } - - static void spoofFields() { - map.forEach((field, s) -> { - try { - if (s.equals(field.get(null))) return; - field.setAccessible(true); - String oldValue = String.valueOf(field.get(null)); - field.set(null, s); - Log.d(TAG, String.format(""" - --------------------------------------- - [%s] - OLD: '%s' - NEW: '%s' - --------------------------------------- - """, field.getName(), oldValue, field.get(null))); - } catch (Throwable t) { - Log.e(TAG, "Error modifying field", t); - } - }); - } - - private static Field getFieldByName(String name) { - Field field; - try { - field = Build.class.getDeclaredField(name); - } catch (NoSuchFieldException e) { - try { - field = Build.VERSION.class.getDeclaredField(name); - } catch (NoSuchFieldException ex) { - return null; - } - } - field.setAccessible(true); - return field; } } diff --git a/module/pif.json b/module/pif.json index 503907a..efb0ca9 100644 --- a/module/pif.json +++ b/module/pif.json @@ -8,6 +8,7 @@ "PRODUCT": "akita_beta", "SECURITY_PATCH": "2024-08-05", "DEVICE_INITIAL_SDK_INT": 21, - "SPOOF_PACKAGE_MANAGER": false, - "DEBUG": false + "spoofProps": true, + "spoofProvider": true, + "spoofSignature": false }