This commit is contained in:
chiteroman 2023-12-28 01:18:58 +01:00
parent 325fbdc2f5
commit aa340e742e
19 changed files with 236 additions and 263 deletions

3
.idea/.gitignore vendored
View File

@ -1,3 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
</component>
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
</component>
</project>

View File

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<value>
<entry key="app">
<State />
</entry>
</value>
</component>
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<value>
<entry key="app">
<State />
</entry>
</value>
</component>
</project>

View File

@ -1,19 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="jbr-17" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings>
</option>
</component>
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

View File

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

View File

@ -1,9 +1,10 @@
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/app/src/main/cpp/Dobby" vcs="Git" />
</component>
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -1,6 +1,3 @@
-ignorewarnings
-dontobfuscate
-keep class es.chiteroman.playintegrityfix.EntryPoint {public <methods>;}
-keep class es.chiteroman.playintegrityfix.CustomProvider
-keep class es.chiteroman.playintegrityfix.CustomKeyStoreSpi
-keep class es.chiteroman.playintegrityfix.CustomCertificates
-keep class es.chiteroman.playintegrityfix.CustomKeyStoreSpi

View File

@ -1,7 +1,6 @@
#include <android/log.h>
#include <sys/system_properties.h>
#include <unistd.h>
#include <filesystem>
#include "zygisk.hpp"
#include "dobby.h"
@ -48,6 +47,10 @@ static void modify_callback(void *cookie, const char *name, const char *value, u
value = BUILD_ID.c_str();
LOGD("Set '%s' to '%s'", name, value);
}
} else if (prop == "sys.usb.state") {
value = "none";
}
return o_callback(cookie, name, value, serial);
@ -87,6 +90,11 @@ public:
void preAppSpecialize(zygisk::AppSpecializeArgs *args) override {
if (args->is_child_zygote && *args->is_child_zygote) {
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
return;
}
auto name = env->GetStringUTFChars(args->nice_name, nullptr);
if (name && strncmp(name, "com.google.android.gms", 22) == 0) {
@ -95,24 +103,39 @@ public:
if (strcmp(name, "com.google.android.gms.unstable") == 0) {
auto rawDir = env->GetStringUTFChars(args->app_data_dir, nullptr);
dir = rawDir;
env->ReleaseStringUTFChars(args->app_data_dir, rawDir);
long size = dir.size();
bool done = false;
int fd = api->connectCompanion();
write(fd, &size, sizeof(long));
write(fd, dir.data(), size);
read(fd, &dexSize, sizeof(long));
read(fd, &done, sizeof(bool));
if (dexSize > 0) {
dexBuffer = new unsigned char[dexSize];
read(fd, dexBuffer, dexSize);
} else {
LOGD("Couldn't load classes.dex file in memory!");
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
goto end;
}
read(fd, &jsonSize, sizeof(long));
if (jsonSize > 0) {
jsonBuffer = new char[jsonSize];
read(fd, jsonBuffer, jsonSize);
} else {
LOGD("Couldn't load pif.json file in memory!");
delete[] dexBuffer;
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
goto end;
}
end:
close(fd);
LOGD("Files copied: %d", done);
goto clear;
}
}
@ -124,31 +147,10 @@ public:
}
void postAppSpecialize(const zygisk::AppSpecializeArgs *args) override {
if (dir.empty()) return;
if (dexSize < 1 || jsonSize < 1 || dexBuffer == nullptr || jsonBuffer == nullptr) return;
std::string classesDex(dir + "/classes.dex");
FILE *dexFile = fopen(classesDex.c_str(), "rb");
if (dexFile == nullptr) {
LOGD("classes.dex doesn't exist... This is weird.");
dir.clear();
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
return;
}
fclose(dexFile);
doHook();
std::string pifJson(dir + "/pif.json");
FILE *jsonFile = fopen(pifJson.c_str(), "r");
nlohmann::json json = nlohmann::json::parse(jsonFile, nullptr, false, true);
fclose(jsonFile);
std::string_view jsonStr(jsonBuffer, jsonSize);
nlohmann::json json = nlohmann::json::parse(jsonStr, nullptr, false, true);
if (json.contains("FIRST_API_LEVEL")) {
@ -180,20 +182,20 @@ public:
LOGD("JSON file doesn't contain SECURITY_PATCH key :(");
}
if (json.contains("BUILD_ID")) {
if (json.contains("ID")) {
if (json["BUILD_ID"].is_string()) {
if (json["ID"].is_string()) {
BUILD_ID = json["BUILD_ID"].get<std::string>();
BUILD_ID = json["ID"].get<std::string>();
}
json.erase("BUILD_ID");
} else {
LOGD("JSON file doesn't contain BUILD_ID key :(");
}
doHook();
LOGD("get system classloader");
auto clClass = env->FindClass("java/lang/ClassLoader");
auto getSystemClassLoader = env->GetStaticMethodID(clClass, "getSystemClassLoader",
@ -201,11 +203,11 @@ public:
auto systemClassLoader = env->CallStaticObjectMethod(clClass, getSystemClassLoader);
LOGD("create class loader");
auto dexClClass = env->FindClass("dalvik/system/PathClassLoader");
auto dexClClass = env->FindClass("dalvik/system/InMemoryDexClassLoader");
auto dexClInit = env->GetMethodID(dexClClass, "<init>",
"(Ljava/lang/String;Ljava/lang/ClassLoader;)V");
auto dexStr = env->NewStringUTF(classesDex.c_str());
auto dexCl = env->NewObject(dexClClass, dexClInit, dexStr, systemClassLoader);
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
auto buffer = env->NewDirectByteBuffer(dexBuffer, dexSize);
auto dexCl = env->NewObject(dexClClass, dexClInit, buffer, systemClassLoader);
LOGD("load class");
auto loadClass = env->GetMethodID(clClass, "loadClass",
@ -220,7 +222,16 @@ public:
auto str = env->NewStringUTF(json.dump().c_str());
env->CallStaticVoidMethod(entryClass, entryInit, str);
dir.clear();
env->DeleteLocalRef(clClass);
env->DeleteLocalRef(dexClClass);
env->DeleteLocalRef(buffer);
env->DeleteLocalRef(dexCl);
env->DeleteLocalRef(entryClassName);
env->DeleteLocalRef(entryClassObj);
env->DeleteLocalRef(str);
delete[] dexBuffer;
delete[] jsonBuffer;
}
void preServerSpecialize(zygisk::ServerSpecializeArgs *args) override {
@ -230,42 +241,53 @@ public:
private:
zygisk::Api *api = nullptr;
JNIEnv *env = nullptr;
std::string dir;
long dexSize = 0, jsonSize = 0;
unsigned char *dexBuffer = nullptr;
char *jsonBuffer = nullptr;
};
static void companion(int fd) {
long dexSize = 0, jsonSize = 0;
unsigned char *dexBuffer = nullptr;
char *jsonBuffer = nullptr;
long size = 0;
std::string dir;
FILE *dexFile = fopen(CLASSES_DEX, "rb");
read(fd, &size, sizeof(long));
if (dexFile) {
dir.resize(size);
fseek(dexFile, 0, SEEK_END);
dexSize = ftell(dexFile);
fseek(dexFile, 0, SEEK_SET);
read(fd, dir.data(), size);
dexBuffer = new unsigned char[dexSize];
fread(dexBuffer, 1, dexSize, dexFile);
LOGD("[ROOT] GMS dir: %s", dir.c_str());
fclose(dexFile);
}
std::string classesDex(dir + "/classes.dex");
std::string pifJson(dir + "/pif.json");
write(fd, &dexSize, sizeof(long));
write(fd, dexBuffer, dexSize);
bool a = std::filesystem::copy_file(CLASSES_DEX, classesDex,
std::filesystem::copy_options::overwrite_existing);
delete[] dexBuffer;
std::filesystem::permissions(classesDex, std::filesystem::perms::owner_read |
std::filesystem::perms::group_read |
std::filesystem::perms::others_read);
FILE *jsonFile = fopen(PIF_JSON, "r");
bool b = std::filesystem::copy_file(PIF_JSON, pifJson,
std::filesystem::copy_options::overwrite_existing);
if (jsonFile) {
std::filesystem::permissions(pifJson, std::filesystem::perms::owner_read |
std::filesystem::perms::group_read |
std::filesystem::perms::others_read);
fseek(jsonFile, 0, SEEK_END);
jsonSize = ftell(jsonFile);
fseek(jsonFile, 0, SEEK_SET);
bool done = a && b;
jsonBuffer = new char[jsonSize];
fread(jsonBuffer, 1, jsonSize, jsonFile);
write(fd, &done, sizeof(bool));
fclose(jsonFile);
}
write(fd, &jsonSize, sizeof(long));
write(fd, jsonBuffer, jsonSize);
delete[] jsonBuffer;
}
REGISTER_ZYGISK_MODULE(PlayIntegrityFix)

View File

@ -14,7 +14,8 @@ import java.util.Date;
import java.util.Enumeration;
public class CustomKeyStoreSpi extends KeyStoreSpi {
public static volatile KeyStoreSpi keyStoreSpi;
public static KeyStoreSpi keyStoreSpi;
@Override
public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException {
@ -23,14 +24,8 @@ public class CustomKeyStoreSpi extends KeyStoreSpi {
@Override
public Certificate[] engineGetCertificateChain(String alias) {
if (EntryPoint.isDroidGuard()) {
EntryPoint.LOG("engineGetCertificateChain call! Throw exception");
throw new UnsupportedOperationException();
}
return keyStoreSpi.engineGetCertificateChain(alias);
EntryPoint.LOG("Tried to get certificate chain, throwing exception!");
throw new UnsupportedOperationException();
}
@Override

View File

@ -2,7 +2,7 @@ package es.chiteroman.playintegrityfix;
import java.security.Provider;
public class CustomProvider extends Provider {
public final class CustomProvider extends Provider {
public CustomProvider(Provider provider) {
super(provider.getName(), provider.getVersion(), provider.getInfo());
@ -14,7 +14,6 @@ public class CustomProvider extends Provider {
@Override
public synchronized Service getService(String type, String algorithm) {
EntryPoint.LOG("[SERVICE] Type: " + type + " | Algorithm: " + algorithm);
EntryPoint.spoofDevice();

View File

@ -1,66 +1,58 @@
package es.chiteroman.playintegrityfix;
import android.os.Build;
import android.util.JsonReader;
import android.util.Log;
import java.io.StringReader;
import org.json.JSONException;
import org.json.JSONObject;
import java.lang.reflect.Field;
import java.security.KeyStore;
import java.security.KeyStoreSpi;
import java.security.Provider;
import java.security.Security;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
public class EntryPoint {
private static final Map<String, String> map = new HashMap<>();
public final class EntryPoint {
private static JSONObject jsonObject = new JSONObject();
public static void init(String json) {
try (JsonReader reader = new JsonReader(new StringReader(json))) {
reader.beginObject();
while (reader.hasNext()) {
String key = reader.nextName();
String value = reader.nextString();
map.put(key, value);
}
reader.endObject();
} catch (Exception e) {
LOG("Error parsing JSON: " + e);
try {
jsonObject = new JSONObject(json);
} catch (JSONException e) {
LOG("Couldn't parse JSON from Zygisk");
}
LOG("Map info (keys and values):");
map.forEach((s, s2) -> LOG(String.format("[%s] -> %s", s, s2)));
boolean FORCE_BASIC_ATTESTATION = true;
if (jsonObject.has("FORCE_BASIC_ATTESTATION")) {
try {
FORCE_BASIC_ATTESTATION = jsonObject.getBoolean("FORCE_BASIC_ATTESTATION");
} catch (JSONException e) {
LOG("Couldn't parse FORCE_BASIC_ATTESTATION from JSON");
}
jsonObject.remove("FORCE_BASIC_ATTESTATION");
}
spoofDevice();
spoofProvider();
if (FORCE_BASIC_ATTESTATION) spoofProvider();
}
protected static void LOG(String msg) {
static void LOG(String msg) {
Log.d("PIF/Java", msg);
}
protected static void spoofDevice() {
map.forEach(EntryPoint::setFieldValue);
}
protected static boolean isDroidGuard() {
return Arrays.stream(Thread.currentThread().getStackTrace()).anyMatch(e -> e.getClassName().toLowerCase(Locale.US).contains("droidguard"));
static void spoofDevice() {
jsonObject.keys().forEachRemaining(s -> {
try {
Object value = jsonObject.get(s);
setFieldValue(s, value);
} catch (JSONException ignored) {
}
});
}
private static void spoofProvider() {
try {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
Field field = keyStore.getClass().getDeclaredField("keyStoreSpi");
field.setAccessible(true);
CustomKeyStoreSpi.keyStoreSpi = (KeyStoreSpi) field.get(keyStore);
field.setAccessible(false);
Provider provider = Security.getProvider("AndroidKeyStore");
Provider customProvider = new CustomProvider(provider);
@ -70,14 +62,18 @@ public class EntryPoint {
LOG("Spoof KeyStoreSpi and Provider done!");
} catch (Exception e) {
LOG("spoofProvider exception: " + e);
} catch (Throwable t) {
LOG("spoofProvider exception: " + t);
}
}
private static void setFieldValue(String name, String value) {
if (name == null || value == null || name.isEmpty() || value.isEmpty()) return;
private static void setFieldValue(String name, Object value) {
if (name == null || value == null || name.isEmpty()) return;
if (value instanceof String str) if (str.isEmpty() || str.isBlank()) return;
Field field = null;
try {
field = Build.class.getDeclaredField(name);
} catch (NoSuchFieldException e) {
@ -87,16 +83,23 @@ public class EntryPoint {
LOG("Couldn't find field: " + e);
}
}
if (field == null) return;
field.setAccessible(true);
String oldValue = null;
try {
oldValue = (String) field.get(null);
if (value.equals(oldValue)) return;
field.set(null, value);
Object oldValue = field.get(null);
if (!value.equals(oldValue)) {
field.set(null, value);
LOG("Set [" + name + "] field value to [" + value + "]");
}
} catch (IllegalAccessException e) {
LOG("Couldn't get or set field: " + e);
LOG("Couldn't modify field :(");
}
LOG(String.format("Field '%s' with value '%s' is now set to '%s'", name, oldValue, value));
field.setAccessible(false);
}
}

View File

@ -2,6 +2,6 @@ We have a Telegram channel!
If you want to share your knowledge join:
https://t.me/playintegrityfix
# v14.9
# v15.0
- Fix DEVICE verdict not passing with some fingerprints.
- Fix issues.

View File

@ -1,32 +0,0 @@
resetprop_if_diff() {
local NAME=$1
local EXPECTED=$2
local CURRENT=$(resetprop $NAME)
[ -z "$CURRENT" ] || [ "$CURRENT" == "$EXPECTED" ] || resetprop $NAME $EXPECTED
}
resetprop_if_match() {
local NAME=$1
local CONTAINS=$2
local VALUE=$3
[[ "$(resetprop $NAME)" == *"$CONTAINS"* ]] && resetprop $NAME $VALUE
}
# Avoid breaking Realme fingerprint scanners
resetprop_if_diff ro.boot.flash.locked 1
# Avoid breaking Oppo fingerprint scanners
resetprop_if_diff ro.boot.vbmeta.device_state locked
# Avoid breaking OnePlus display modes/fingerprint scanners
resetprop_if_diff vendor.boot.verifiedbootstate green
# Avoid breaking OnePlus/Oppo display fingerprint scanners on OOS/ColorOS 12+
resetprop_if_diff ro.boot.verifiedbootstate green
resetprop_if_diff ro.boot.veritymode enforcing
resetprop_if_diff vendor.boot.vbmeta.device_state locked
# Restrict permissions to socket file
chmod 440 /proc/net/unix

View File

@ -52,7 +52,7 @@ if [ -d "/system/app/EliteDevelopmentModule" ]; then
ui_print "- EliteDevelopmentModule app removed."
fi
# Move pif.json file
# Move empty pif.json file
if [ ! -f "/data/adb/pif.json" ]; then
mv -f $MODPATH/pif.json /data/adb/
fi

View File

@ -1,7 +1,7 @@
id=playintegrityfix
name=Play Integrity Fix
version=v14.9
versionCode=14900
version=v15.0
versionCode=15000
author=chiteroman
description=Universal modular fix for Play Integrity (and SafetyNet) on devices running Android 8+.
updateJson=https://raw.githubusercontent.com/chiteroman/PlayIntegrityFix/main/update.json

View File

@ -1,11 +1,12 @@
{
"PRODUCT": "",
"DEVICE": "",
"MANUFACTURER": "",
"BRAND": "",
"MODEL": "",
"FINGERPRINT": "",
"FIRST_API_LEVEL": 21,
"SECURITY_PATCH": "",
"BUILD_ID": ""
"PRODUCT": "",
"DEVICE": "",
"MANUFACTURER": "",
"BRAND": "",
"MODEL": "",
"FINGERPRINT": "",
"SECURITY_PATCH": "",
"ID": "",
"FIRST_API_LEVEL": 24,
"FORCE_BASIC_ATTESTATION": true
}

View File

@ -32,29 +32,21 @@ if [ "$(toybox cat /sys/fs/selinux/enforce)" == "0" ]; then
chmod 440 /sys/fs/selinux/policy
fi
# KernelSU handles boot completed state in different file.
if [ -z "$KSU" ] || [ "$KSU" = false ]; then
{
# late props which must be set after boot_completed for various OEMs
until [ "$(resetprop sys.boot_completed)" == "1" ]; do
sleep 1
done
# late props which must be set after boot_completed for various OEMs
until [ "$(resetprop sys.boot_completed)" == "1" ]; do
sleep 1
done
# Avoid breaking Realme fingerprint scanners
resetprop_if_diff ro.boot.flash.locked 1
# Avoid breaking Realme fingerprint scanners
resetprop_if_diff ro.boot.flash.locked 1
# Avoid breaking Oppo fingerprint scanners
resetprop_if_diff ro.boot.vbmeta.device_state locked
# Avoid breaking OnePlus display modes/fingerprint scanners
resetprop_if_diff vendor.boot.verifiedbootstate green
# Avoid breaking OnePlus/Oppo display fingerprint scanners on OOS/ColorOS 12+
resetprop_if_diff ro.boot.verifiedbootstate green
resetprop_if_diff ro.boot.veritymode enforcing
resetprop_if_diff vendor.boot.vbmeta.device_state locked
# Avoid breaking Oppo fingerprint scanners
resetprop_if_diff ro.boot.vbmeta.device_state locked
# Avoid breaking OnePlus display modes/fingerprint scanners
resetprop_if_diff vendor.boot.verifiedbootstate green
# Avoid breaking OnePlus/Oppo display fingerprint scanners on OOS/ColorOS 12+
resetprop_if_diff ro.boot.verifiedbootstate green
resetprop_if_diff ro.boot.veritymode enforcing
resetprop_if_diff vendor.boot.vbmeta.device_state locked
# Restrict permissions to socket file
chmod 440 /proc/net/unix
}&
fi
# Restrict permissions to socket file
chmod 440 /proc/net/unix

View File

@ -1,6 +1,6 @@
{
"version": "v14.9",
"versionCode": 14900,
"zipUrl": "https://github.com/chiteroman/PlayIntegrityFix/releases/download/v14.9/PlayIntegrityFix_v14.9.zip",
"version": "v15.0",
"versionCode": 15000,
"zipUrl": "https://github.com/chiteroman/PlayIntegrityFix/releases/download/v15.0/PlayIntegrityFix_v15.0.zip",
"changelog": "https://raw.githubusercontent.com/chiteroman/PlayIntegrityFix/main/changelog.md"
}