Skip to content

Commit

Permalink
Big update
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
chiteroman committed Aug 17, 2024
1 parent a19c651 commit 56eed97
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 108 deletions.
131 changes: 114 additions & 17 deletions app/src/main/cpp/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
#include <sys/system_properties.h>
#include <unistd.h>
#include <vector>
#include <map>
#include <filesystem>
#include "zygisk.hpp"
#include "dobby.h"
Expand All @@ -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;
Expand Down Expand Up @@ -51,12 +52,11 @@ static bool DEBUG = false;

typedef void (*T_Callback)(void *, const char *, const char *, uint32_t);

static std::map<void *, T_Callback> 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);
Expand All @@ -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!");
Expand Down Expand Up @@ -174,23 +173,36 @@ class PlayIntegrityFix : public zygisk::ModuleBase {
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();
UpdateBuildFields();

if (enableHook) doHook();
else api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
cJSON_Delete(json);

injectDex();
if (spoofProps) doHook();
else api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);

cJSON_Delete(json);
if (spoofProvider || spoofSignature) injectDex();
else
LOGD("Don't inject dex, spoofProvider and spoofSignature are false");
}

void preServerSpecialize(zygisk::ServerSpecializeArgs *args) override {
Expand All @@ -202,7 +214,9 @@ class PlayIntegrityFix : public zygisk::ModuleBase {
JNIEnv *env = nullptr;
std::vector<uint8_t> dexVector;
cJSON *json = nullptr;
bool enableHook = false;
bool spoofProps = true;
bool spoofProvider = true;
bool spoofSignature = false;

void parseJSON() {
if (!json) return;
Expand All @@ -211,9 +225,11 @@ class PlayIntegrityFix : public zygisk::ModuleBase {
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)) {
Expand All @@ -234,6 +250,21 @@ class PlayIntegrityFix : public zygisk::ModuleBase {
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() {
Expand All @@ -260,9 +291,71 @@ class PlayIntegrityFix : public zygisk::ModuleBase {
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);
}
}
}
}
};

Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,4 @@ public CustomProvider(Provider 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);
}
}
92 changes: 11 additions & 81 deletions app/src/main/java/es/chiteroman/playintegrityfix/EntryPoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,9 @@
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;
Expand All @@ -21,7 +18,6 @@
import java.security.Provider;
import java.security.Security;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;

Expand Down Expand Up @@ -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");
Expand All @@ -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<PackageInfo> originalCreator = PackageInfo.CREATOR;
Parcelable.Creator<PackageInfo> customCreator = new CustomPackageInfoCreator(originalCreator, spoofedSignature);
Expand Down Expand Up @@ -138,83 +134,17 @@ private static Field findField(Class<?> currentClass, String fieldName) throws N
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);
}

if (jsonObject == null || jsonObject.length() == 0) return;

Iterator<String> 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);
public static void init(boolean spoofProvider, boolean spoofSignature) {
if (spoofProvider) {
spoofProvider();
} else {
Log.i(TAG, "Don't spoof Provider");
}

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;
}
if (spoofSignature) {
spoofSignature();
} else {
Log.i(TAG, "Don't spoof signature");
}
field.setAccessible(true);
return field;
}
}
5 changes: 3 additions & 2 deletions module/pif.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

0 comments on commit 56eed97

Please sign in to comment.