You've already forked PlayIntegrityFork
mirror of
https://github.com/osm0sis/PlayIntegrityFork.git
synced 2025-09-06 06:37:06 +00:00
Future proof to allow spoofing all fields
- very verbose logging (for now) - combine setProp and setVersionProp into a single universal function - iterate through all entries to allow modifying any matching android.os.Build or android.os.Build.VERSION field - add BUILD_ID and VNDK_VERSION support to keep cross-fork API compatibility - add exceptions for FIRST_API_VERSION (actually DEVICE_INITIAL_SDK_INT) and BUILD_ID (actually ID) for backwards API compatibility
This commit is contained in:
@@ -11,10 +11,9 @@
|
||||
#define DEX_FILE_PATH "/data/adb/modules/playintegrityfix/classes.dex"
|
||||
|
||||
#define JSON_FILE_PATH "/data/adb/modules/playintegrityfix/pif.json"
|
||||
|
||||
#define CUSTOM_JSON_FILE_PATH "/data/adb/modules/playintegrityfix/custom.pif.json"
|
||||
|
||||
static std::string FIRST_API_LEVEL, SECURITY_PATCH;
|
||||
static std::string FIRST_API_LEVEL, SECURITY_PATCH, BUILD_ID, VNDK_VERSION;
|
||||
|
||||
typedef void (*T_Callback)(void *, const char *, const char *, uint32_t);
|
||||
|
||||
@@ -31,8 +30,7 @@ static void modify_callback(void *cookie, const char *name, const char *value, u
|
||||
if (prop.ends_with("api_level")) {
|
||||
if (FIRST_API_LEVEL.empty()) {
|
||||
LOGD("FIRST_API_LEVEL is empty, ignoring it...");
|
||||
} else if (FIRST_API_LEVEL == "nullptr") {
|
||||
value = nullptr;
|
||||
return;
|
||||
} else {
|
||||
value = FIRST_API_LEVEL.c_str();
|
||||
}
|
||||
@@ -40,10 +38,27 @@ static void modify_callback(void *cookie, const char *name, const char *value, u
|
||||
} else if (prop.ends_with("security_patch")) {
|
||||
if (SECURITY_PATCH.empty()) {
|
||||
LOGD("SECURITY_PATCH is empty, ignoring it...");
|
||||
return;
|
||||
} else {
|
||||
value = SECURITY_PATCH.c_str();
|
||||
}
|
||||
LOGD("[%s] -> %s", name, value);
|
||||
} else if (prop == "ro.build.id") {
|
||||
if (BUILD_ID.empty()) {
|
||||
LOGD("BUILD_ID is empty, ignoring it...");
|
||||
return;
|
||||
} else {
|
||||
value = BUILD_ID.c_str();
|
||||
}
|
||||
LOGD("[%s] -> %s", name, value);
|
||||
} else if (prop.ends_with("vndk.version")) {
|
||||
if (VNDK_VERSION.empty()) {
|
||||
LOGD("VNDK_VERSION is empty, ignoring it...");
|
||||
return;
|
||||
} else {
|
||||
value = VNDK_VERSION.c_str();
|
||||
}
|
||||
LOGD("[%s] -> %s", name, value);
|
||||
}
|
||||
|
||||
return callbacks[cookie](cookie, name, value, serial);
|
||||
@@ -51,8 +66,7 @@ static void modify_callback(void *cookie, const char *name, const char *value, u
|
||||
|
||||
static void (*o_system_property_read_callback)(const prop_info *, T_Callback, void *);
|
||||
|
||||
static void
|
||||
my_system_property_read_callback(const prop_info *pi, T_Callback callback, void *cookie) {
|
||||
static void my_system_property_read_callback(const prop_info *pi, T_Callback callback, void *cookie) {
|
||||
if (pi == nullptr || callback == nullptr || cookie == nullptr) {
|
||||
return o_system_property_read_callback(pi, callback, cookie);
|
||||
}
|
||||
@@ -170,6 +184,20 @@ private:
|
||||
nlohmann::json json;
|
||||
|
||||
void readJson() {
|
||||
LOGD("JSON contains %d keys!", static_cast<int>(json.size()));
|
||||
|
||||
// Direct property spoofing workarounds
|
||||
if (json.contains("ID")) {
|
||||
if (json["ID"].is_null()) {
|
||||
LOGD("Key ID is null!");
|
||||
} else if (json["ID"].is_string()) {
|
||||
BUILD_ID = json["ID"].get<std::string>();
|
||||
} else {
|
||||
LOGD("Error parsing ID!");
|
||||
}
|
||||
} else {
|
||||
LOGD("Key ID doesn't exist in JSON file!");
|
||||
}
|
||||
if (json.contains("SECURITY_PATCH")) {
|
||||
if (json["SECURITY_PATCH"].is_null()) {
|
||||
LOGD("Key SECURITY_PATCH is null!");
|
||||
@@ -181,20 +209,53 @@ private:
|
||||
} else {
|
||||
LOGD("Key SECURITY_PATCH doesn't exist in JSON file!");
|
||||
}
|
||||
if (json.contains("DEVICE_INITIAL_SDK_INT")) {
|
||||
if (json["DEVICE_INITIAL_SDK_INT"].is_null()) {
|
||||
LOGD("Key DEVICE_INITIAL_SDK_INT is null!");
|
||||
} else if (json["DEVICE_INITIAL_SDK_INT"].is_string()) {
|
||||
FIRST_API_LEVEL = json["DEVICE_INITIAL_SDK_INT"].get<std::string>();
|
||||
} else {
|
||||
LOGD("Error parsing DEVICE_INITIAL_SDK_INT!");
|
||||
}
|
||||
} else {
|
||||
LOGD("Key DEVICE_INITIAL_SDK_INT doesn't exist in JSON file!");
|
||||
}
|
||||
|
||||
// Backwards compatibility for chiteroman's alternate API naming
|
||||
if (json.contains("BUILD_ID")) {
|
||||
if (json["BUILD_ID"].is_null()) {
|
||||
LOGD("Key BUILD_ID is null!");
|
||||
} else if (json["BUILD_ID"].is_string()) {
|
||||
BUILD_ID = json["BUILD_ID"].get<std::string>();
|
||||
} else {
|
||||
LOGD("Error parsing BUILD_ID!");
|
||||
}
|
||||
} else {
|
||||
LOGD("Key BUILD_ID doesn't exist in JSON file!");
|
||||
}
|
||||
if (json.contains("FIRST_API_LEVEL")) {
|
||||
if (json["FIRST_API_LEVEL"].is_null()) {
|
||||
LOGD("Key FIRST_API_LEVEL is null!");
|
||||
FIRST_API_LEVEL = "nullptr";
|
||||
} else if (json["FIRST_API_LEVEL"].is_string()) {
|
||||
FIRST_API_LEVEL = json["FIRST_API_LEVEL"].get<std::string>();
|
||||
} else {
|
||||
LOGD("Error parsing FIRST_API_LEVEL!");
|
||||
}
|
||||
json.erase("FIRST_API_LEVEL");
|
||||
} else {
|
||||
LOGD("Key FIRST_API_LEVEL doesn't exist in JSON file!");
|
||||
}
|
||||
if (json.contains("VNDK_VERSION")) {
|
||||
if (json["VNDK_VERSION"].is_null()) {
|
||||
LOGD("Key VNDK_VERSION is null!");
|
||||
} else if (json["VNDK_VERSION"].is_string()) {
|
||||
VNDK_VERSION = json["VNDK_VERSION"].get<std::string>();
|
||||
} else {
|
||||
LOGD("Error parsing VNDK_VERSION!");
|
||||
}
|
||||
json.erase("VNDK_VERSION"); // Doesn't exist in Build or Build.VERSION
|
||||
} else {
|
||||
LOGD("Key VNDK_VERSION doesn't exist in JSON file!");
|
||||
}
|
||||
}
|
||||
|
||||
void inject() {
|
||||
|
||||
@@ -32,6 +32,7 @@ public final class EntryPoint {
|
||||
reader.endObject();
|
||||
} catch (IOException e) {
|
||||
LOG("Couldn't read JSON from Zygisk: " + e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,49 +64,70 @@ public final class EntryPoint {
|
||||
}
|
||||
|
||||
static void spoofDevice() {
|
||||
if (map.isEmpty()) return;
|
||||
for (String key : map.keySet()) {
|
||||
// Backwards compatibility for chiteroman's alternate API naming
|
||||
if (key.equals("BUILD_ID")) {
|
||||
setProp("ID", map.get("BUILD_ID"));
|
||||
} else if (key.equals("FIRST_API_LEVEL")) {
|
||||
setProp("DEVICE_INITIAL_SDK_INT", map.get("FIRST_API_LEVEL"));
|
||||
} else {
|
||||
setProp(key, map.get(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setProp("PRODUCT", map.get("PRODUCT"));
|
||||
setProp("DEVICE", map.get("DEVICE"));
|
||||
setProp("MANUFACTURER", map.get("MANUFACTURER"));
|
||||
setProp("BRAND", map.get("BRAND"));
|
||||
setProp("MODEL", map.get("MODEL"));
|
||||
setProp("FINGERPRINT", map.get("FINGERPRINT"));
|
||||
setVersionProp("SECURITY_PATCH", map.get("SECURITY_PATCH"));
|
||||
private static boolean classContainsField(Class className, String fieldName) {
|
||||
for (Field field : className.getDeclaredFields()) {
|
||||
if (field.getName().equals(fieldName)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void setProp(String name, String value) {
|
||||
if (name == null || value == null || name.isEmpty() || value.isEmpty()) return;
|
||||
try {
|
||||
Field field = Build.class.getDeclaredField(name);
|
||||
field.setAccessible(true);
|
||||
String oldValue = (String) field.get(null);
|
||||
field.set(null, value);
|
||||
field.setAccessible(false);
|
||||
if (value.equals(oldValue)) return;
|
||||
LOG(String.format("[%s]: %s -> %s", name, oldValue, value));
|
||||
} catch (NoSuchFieldException e) {
|
||||
LOG(String.format("Couldn't find '%s' field name.", name));
|
||||
} catch (IllegalAccessException e) {
|
||||
LOG(String.format("Couldn't modify '%s' field value.", name));
|
||||
if (value == null || value.isEmpty()) {
|
||||
LOG(String.format("%s is null, skipping...", name));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static void setVersionProp(String name, String value) {
|
||||
if (name == null || value == null || name.isEmpty() || value.isEmpty()) return;
|
||||
Field field = null;
|
||||
String oldValue = null;
|
||||
|
||||
try {
|
||||
Field field = Build.VERSION.class.getDeclaredField(name);
|
||||
field.setAccessible(true);
|
||||
String oldValue = (String) field.get(null);
|
||||
field.set(null, value);
|
||||
field.setAccessible(false);
|
||||
if (value.equals(oldValue)) return;
|
||||
LOG(String.format("[%s]: %s -> %s", name, oldValue, value));
|
||||
if (classContainsField(Build.class, name)) {
|
||||
field = Build.class.getDeclaredField(name);
|
||||
} else if (classContainsField(Build.VERSION.class, name)) {
|
||||
field = Build.VERSION.class.getDeclaredField(name);
|
||||
} else {
|
||||
LOG(String.format("Couldn't determine '%s' class name", name));
|
||||
return;
|
||||
}
|
||||
} catch (NoSuchFieldException e) {
|
||||
LOG(String.format("Couldn't find '%s' field name.", name));
|
||||
} catch (IllegalAccessException e) {
|
||||
LOG(String.format("Couldn't modify '%s' field value.", name));
|
||||
LOG(String.format("Couldn't find '%s' field name: " + e, name));
|
||||
return;
|
||||
}
|
||||
field.setAccessible(true);
|
||||
try {
|
||||
oldValue = String.valueOf(field.get(null));
|
||||
} catch (IllegalAccessException e) {
|
||||
LOG(String.format("Couldn't access '%s' field value: " + e, name));
|
||||
return;
|
||||
}
|
||||
if (value.equals(oldValue)) {
|
||||
LOG(String.format("[%s]: already '%s', skipping...", name, value));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (field.getType().equals(Integer.TYPE)) {
|
||||
field.set(null, Integer.parseInt(value));
|
||||
} else {
|
||||
field.set(null, value);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
LOG(String.format("Couldn't modify '%s' field value: " + e, name));
|
||||
return;
|
||||
}
|
||||
field.setAccessible(false);
|
||||
LOG(String.format("[%s]: %s -> %s", name, oldValue, value));
|
||||
}
|
||||
|
||||
static void LOG(String msg) {
|
||||
|
||||
Reference in New Issue
Block a user