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:
osm0sis
2023-12-11 14:34:29 -04:00
parent 093cf04232
commit ecfa0bbbdf
2 changed files with 125 additions and 42 deletions

View File

@@ -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() {

View File

@@ -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) {