This commit is contained in:
chiteroman 2024-06-27 02:07:05 +02:00
parent fdfa3ee793
commit 8850f447a1
15 changed files with 375 additions and 13408 deletions

263
.idea/other.xml Normal file
View File

@ -0,0 +1,263 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="direct_access_persist.xml">
<option name="deviceSelectionList">
<list>
<PersistentDeviceSelectionData>
<option name="api" value="27" />
<option name="brand" value="DOCOMO" />
<option name="codename" value="F01L" />
<option name="id" value="F01L" />
<option name="manufacturer" value="FUJITSU" />
<option name="name" value="F-01L" />
<option name="screenDensity" value="360" />
<option name="screenX" value="720" />
<option name="screenY" value="1280" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="28" />
<option name="brand" value="DOCOMO" />
<option name="codename" value="SH-01L" />
<option name="id" value="SH-01L" />
<option name="manufacturer" value="SHARP" />
<option name="name" value="AQUOS sense2 SH-01L" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2160" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="31" />
<option name="brand" value="samsung" />
<option name="codename" value="a51" />
<option name="id" value="a51" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy A51" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="akita" />
<option name="id" value="akita" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 8a" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="samsung" />
<option name="codename" value="b0q" />
<option name="id" value="b0q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S22 Ultra" />
<option name="screenDensity" value="600" />
<option name="screenX" value="1440" />
<option name="screenY" value="3088" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="32" />
<option name="brand" value="google" />
<option name="codename" value="bluejay" />
<option name="id" value="bluejay" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 6a" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="29" />
<option name="brand" value="samsung" />
<option name="codename" value="crownqlteue" />
<option name="id" value="crownqlteue" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Note9" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2220" />
<option name="screenY" value="1080" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="dm3q" />
<option name="id" value="dm3q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S23 Ultra" />
<option name="screenDensity" value="600" />
<option name="screenX" value="1440" />
<option name="screenY" value="3088" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="felix" />
<option name="id" value="felix" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Fold" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2208" />
<option name="screenY" value="1840" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="felix_camera" />
<option name="id" value="felix_camera" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Fold (Camera-enabled)" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2208" />
<option name="screenY" value="1840" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="samsung" />
<option name="codename" value="gts8uwifi" />
<option name="id" value="gts8uwifi" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Tab S8 Ultra" />
<option name="screenDensity" value="320" />
<option name="screenX" value="1848" />
<option name="screenY" value="2960" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="husky" />
<option name="id" value="husky" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 8 Pro" />
<option name="screenDensity" value="390" />
<option name="screenX" value="1008" />
<option name="screenY" value="2244" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="30" />
<option name="brand" value="motorola" />
<option name="codename" value="java" />
<option name="id" value="java" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="G20" />
<option name="screenDensity" value="280" />
<option name="screenX" value="720" />
<option name="screenY" value="1600" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="lynx" />
<option name="id" value="lynx" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 7a" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="31" />
<option name="brand" value="google" />
<option name="codename" value="oriole" />
<option name="id" value="oriole" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 6" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="panther" />
<option name="id" value="panther" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 7" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="31" />
<option name="brand" value="samsung" />
<option name="codename" value="q2q" />
<option name="id" value="q2q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Z Fold3" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1768" />
<option name="screenY" value="2208" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="q5q" />
<option name="id" value="q5q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Z Fold5" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1812" />
<option name="screenY" value="2176" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="30" />
<option name="brand" value="google" />
<option name="codename" value="r11" />
<option name="id" value="r11" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Watch" />
<option name="screenDensity" value="320" />
<option name="screenX" value="384" />
<option name="screenY" value="384" />
<option name="type" value="WEAR_OS" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="30" />
<option name="brand" value="google" />
<option name="codename" value="redfin" />
<option name="id" value="redfin" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 5" />
<option name="screenDensity" value="440" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="shiba" />
<option name="id" value="shiba" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 8" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="tangorpro" />
<option name="id" value="tangorpro" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Tablet" />
<option name="screenDensity" value="320" />
<option name="screenX" value="1600" />
<option name="screenY" value="2560" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="29" />
<option name="brand" value="samsung" />
<option name="codename" value="x1q" />
<option name="id" value="x1q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S20" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1440" />
<option name="screenY" value="3200" />
</PersistentDeviceSelectionData>
</list>
</option>
</component>
</project>

View File

@ -16,11 +16,14 @@ android {
applicationId = "es.chiteroman.playintegrityfix"
minSdk = 26
targetSdk = 34
versionCode = 16300
versionName = "v16.3"
versionCode = 16400
versionName = "v16.4"
multiDexEnabled = false
packaging {
resources {
excludes += "META-INF/**"
}
jniLibs {
excludes += "**/liblog.so"
excludes += "**/libdobby.so"
@ -33,7 +36,7 @@ android {
arguments += "-DCMAKE_BUILD_TYPE=MinSizeRel"
arguments += "-DPlugin.Android.BionicLinkerUtil=ON"
cppFlags += "-std=c++20"
cppFlags += "-std=c++2b"
cppFlags += "-fno-exceptions"
cppFlags += "-fno-rtti"
cppFlags += "-fvisibility=hidden"
@ -47,7 +50,9 @@ android {
isMinifyEnabled = true
isShrinkResources = true
multiDexEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
)
}
}
@ -91,8 +96,10 @@ tasks.register("copyFiles") {
doLast {
val moduleFolder = project.rootDir.resolve("module")
val dexFile = project.layout.buildDirectory.get().asFile.resolve("intermediates/dex/release/minifyReleaseWithR8/classes.dex")
val soDir = project.layout.buildDirectory.get().asFile.resolve("intermediates/stripped_native_libs/release/stripReleaseDebugSymbols/out/lib")
val dexFile =
project.layout.buildDirectory.get().asFile.resolve("intermediates/dex/release/minifyReleaseWithR8/classes.dex")
val soDir =
project.layout.buildDirectory.get().asFile.resolve("intermediates/stripped_native_libs/release/stripReleaseDebugSymbols/out/lib")
dexFile.copyTo(moduleFolder.resolve("classes.dex"), overwrite = true)

View File

@ -1,4 +1,3 @@
-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.KeyboxUtils
-keep class es.chiteroman.playintegrityfix.EntryPoint {*;}
-keep class es.chiteroman.playintegrityfix.CustomKeyStoreSpi {*;}
-keep class es.chiteroman.playintegrityfix.CustomProvider {*;}

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@
#include "zygisk.hpp"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "PIF", __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "PIF", __VA_ARGS__)
#define DEX_PATH "/data/adb/modules/playintegrityfix/classes.dex"
@ -14,10 +15,6 @@
#define PIF_JSON_DEFAULT "/data/adb/modules/playintegrityfix/pif.json"
#define KEYBOX_JSON "/data/adb/keybox.xml"
#define KEYBOX_JSON_DEFAULT "/data/adb/modules/playintegrityfix/keybox.xml"
static ssize_t xread(int fd, void *buffer, size_t count) {
ssize_t total = 0;
char *buf = (char *) buffer;
@ -56,7 +53,7 @@ static void modify_callback(void *cookie, const char *name, const char *value, u
std::string_view prop(name);
if (prop.ends_with("first_api_level") && !DEVICE_INITIAL_SDK_INT.empty()) {
if (prop.ends_with("first_api_level")) {
value = DEVICE_INITIAL_SDK_INT.c_str();
LOGD("[%s]: %s", name, value);
}
@ -76,6 +73,7 @@ my_system_property_read_callback(const prop_info *pi, T_Callback callback, void
}
static void doHook() {
void *handle = DobbySymbolResolver(nullptr, "__system_property_read_callback");
if (handle == nullptr) {
LOGD("Couldn't hook __system_property_read_callback");
@ -136,38 +134,32 @@ public:
int fd = api->connectCompanion();
int dexSize = 0, jsonSize = 0, keyboxSize = 0;
std::vector<char> jsonVector, keyboxVector;
int dexSize = 0, jsonSize = 0;
std::vector<char> jsonVector;
xread(fd, &dexSize, sizeof(int));
xread(fd, &jsonSize, sizeof(int));
xread(fd, &keyboxSize, sizeof(int));
dexVector.resize(dexSize);
xread(fd, dexVector.data(), dexSize);
if (jsonSize > 0) {
jsonVector.resize(jsonSize);
xread(fd, jsonVector.data(), jsonSize);
keyboxVector.resize(keyboxSize);
xread(fd, keyboxVector.data(), keyboxSize);
json = nlohmann::json::parse(jsonVector, nullptr, false, true);
}
close(fd);
json = nlohmann::json::parse(jsonVector, nullptr, false, true);
keyboxString = std::string(keyboxVector.cbegin(), keyboxVector.cend());
LOGD("Dex file size: %d", dexSize);
LOGD("Json file size: %d", jsonSize);
}
void postAppSpecialize(const zygisk::AppSpecializeArgs *args) override {
if (dexVector.empty() || json.empty()) return;
if (dexVector.empty()) return;
if (json.contains("DEVICE_INITIAL_SDK_INT")) {
DEVICE_INITIAL_SDK_INT = json["DEVICE_INITIAL_SDK_INT"].get<std::string>();
json.erase("DEVICE_INITIAL_SDK_INT"); // You can't modify field value
}
doHook();
if (needHook()) doHook();
else api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
injectDex();
}
@ -181,7 +173,25 @@ private:
JNIEnv *env = nullptr;
std::vector<char> dexVector;
nlohmann::json json;
std::string keyboxString;
bool needHook() {
if (json.contains("DEVICE_INITIAL_SDK_INT")) {
if (json["DEVICE_INITIAL_SDK_INT"].is_string()) {
DEVICE_INITIAL_SDK_INT = json["DEVICE_INITIAL_SDK_INT"].get<std::string>();
} else if (json["DEVICE_INITIAL_SDK_INT"].is_number_integer()) {
DEVICE_INITIAL_SDK_INT = std::to_string(json["DEVICE_INITIAL_SDK_INT"].get<int>());
} else {
LOGE("Couldn't parse DEVICE_INITIAL_SDK_INT from JSON file!");
return false;
}
// Value can't be modified, it's marked as SystemApi field
json.erase("DEVICE_INITIAL_SDK_INT");
return !DEVICE_INITIAL_SDK_INT.empty();
}
return false;
}
void injectDex() {
LOGD("get system classloader");
@ -194,7 +204,8 @@ private:
auto dexClClass = env->FindClass("dalvik/system/InMemoryDexClassLoader");
auto dexClInit = env->GetMethodID(dexClClass, "<init>",
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
auto buffer = env->NewDirectByteBuffer(dexVector.data(), dexVector.size());
auto buffer = env->NewDirectByteBuffer(dexVector.data(),
static_cast<jlong>(dexVector.size()));
auto dexCl = env->NewObject(dexClClass, dexClInit, buffer, systemClassLoader);
LOGD("load class");
@ -206,11 +217,9 @@ private:
auto entryPointClass = (jclass) entryClassObj;
LOGD("call init");
auto entryInit = env->GetStaticMethodID(entryPointClass, "init",
"(Ljava/lang/String;Ljava/lang/String;)V");
auto entryInit = env->GetStaticMethodID(entryPointClass, "init", "(Ljava/lang/String;)V");
auto jsonStr = env->NewStringUTF(json.dump().c_str());
auto keyboxStr = env->NewStringUTF(keyboxString.c_str());
env->CallStaticVoidMethod(entryPointClass, entryInit, jsonStr, keyboxStr);
env->CallStaticVoidMethod(entryPointClass, entryInit, jsonStr);
}
};
@ -218,12 +227,9 @@ static std::vector<char> readFile(const std::string &path) {
std::ifstream ifs(path);
if (!ifs || ifs.bad()) {
return std::vector<char>();
}
if (!ifs || ifs.bad()) return {};
return std::vector<char>((std::istreambuf_iterator<char>(ifs)),
std::istreambuf_iterator<char>());
return {(std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>()};
}
static void companion(int fd) {
@ -233,20 +239,17 @@ static void companion(int fd) {
auto json = readFile(PIF_JSON);
if (json.empty()) json = readFile(PIF_JSON_DEFAULT);
auto keybox = readFile(KEYBOX_JSON);
if (keybox.empty()) keybox = readFile(KEYBOX_JSON_DEFAULT);
int dexSize = dex.size();
int jsonSize = json.size();
int keyboxSize = keybox.size();
int dexSize = static_cast<int>(dex.size());
int jsonSize = static_cast<int>(json.size());
xwrite(fd, &dexSize, sizeof(int));
xwrite(fd, &jsonSize, sizeof(int));
xwrite(fd, &keyboxSize, sizeof(int));
xwrite(fd, dex.data(), dexSize * sizeof(char));
if (jsonSize > 0) {
xwrite(fd, json.data(), jsonSize * sizeof(char));
xwrite(fd, keybox.data(), keyboxSize * sizeof(char));
}
}
REGISTER_ZYGISK_MODULE(PlayIntegrityFix)

View File

@ -26,13 +26,16 @@ public final class CustomKeyStoreSpi extends KeyStoreSpi {
@Override
public Certificate[] engineGetCertificateChain(String alias) {
if (Arrays.stream(Thread.currentThread().getStackTrace()).anyMatch(e -> e.getClassName().toLowerCase(Locale.US).contains("droidguard"))) {
return KeyboxUtils.engineGetCertificateChain(keyStoreSpi.engineGetCertificateChain(alias));
throw new UnsupportedOperationException();
}
return keyStoreSpi.engineGetCertificateChain(alias);
}
@Override
public Certificate engineGetCertificate(String alias) {
if (Arrays.stream(Thread.currentThread().getStackTrace()).anyMatch(e -> e.getClassName().toLowerCase(Locale.US).contains("droidguard"))) {
throw new UnsupportedOperationException();
}
return keyStoreSpi.engineGetCertificate(alias);
}

View File

@ -12,12 +12,11 @@ public final class CustomProvider extends Provider {
@Override
public synchronized Service getService(String type, String algorithm) {
EntryPoint.LOG(String.format("Service: '%s' | Algorithm: '%s'", type, algorithm));
if ("KeyStore".equals(type)) {
Thread t = new Thread(EntryPoint::spoofFields);
t.setDaemon(true);
t.start();
}
return super.getService(type, algorithm);
}
}

View File

@ -1,6 +1,7 @@
package es.chiteroman.playintegrityfix;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import org.json.JSONObject;
@ -14,6 +15,7 @@ import java.util.HashMap;
import java.util.Map;
public final class EntryPoint {
public static final String TAG = "PIF";
private static final Map<Field, String> map = new HashMap<>();
static {
@ -26,7 +28,7 @@ public final class EntryPoint {
CustomKeyStoreSpi.keyStoreSpi = (KeyStoreSpi) keyStoreSpi.get(keyStore);
} catch (Throwable t) {
LOG("Couldn't get keyStoreSpi: " + t);
Log.e(TAG, "Couldn't get keyStoreSpi field!", t);
}
Provider provider = Security.getProvider("AndroidKeyStore");
@ -37,7 +39,7 @@ public final class EntryPoint {
Security.insertProviderAt(customProvider, 1);
}
public static void init(String json, String kbox) {
public static void init(String json) {
try {
JSONObject jsonObject = new JSONObject(json);
@ -45,32 +47,26 @@ public final class EntryPoint {
jsonObject.keys().forEachRemaining(s -> {
try {
String value = jsonObject.getString(s);
if (TextUtils.isEmpty(value)) return;
Field field = getFieldByName(s);
if (field == null) {
LOG("Field " + s + " not found!");
return;
}
if (field == null) return;
map.put(field, value);
LOG("Save " + field.getName() + " with value: " + value);
} catch (Throwable t) {
LOG("Couldn't parse " + s + " key!");
Log.e(TAG, "Error parsing JSON", t);
}
});
} catch (Throwable t) {
Log.e(TAG, "Error parsing JSON", t);
}
Log.i(TAG, "Fields ready to spoof: " + map.size());
spoofFields();
} catch (Throwable t) {
LOG("Error loading json file: " + t);
}
try {
KeyboxUtils.parseXml(kbox);
} catch (Throwable t) {
LOG("Error parsing keybox file: " + t);
}
}
static void spoofFields() {
@ -78,16 +74,22 @@ public final class EntryPoint {
try {
if (s.equals(field.get(null))) return;
field.setAccessible(true);
String oldValue = String.valueOf(field.get(null));
field.set(null, s);
LOG("Set " + field.getName() + " field value: " + s);
Log.d(TAG, String.format("""
---------------------------------------
[%s]
OLD: '%s'
NEW: '%s'
---------------------------------------
""", field.getName(), oldValue, field.get(null)));
} catch (Throwable t) {
LOG(t.toString());
Log.e(TAG, "Error modifying field", t);
}
});
}
private static Field getFieldByName(String name) {
Field field;
try {
field = Build.class.getDeclaredField(name);
@ -98,13 +100,7 @@ public final class EntryPoint {
return null;
}
}
field.setAccessible(true);
return field;
}
static void LOG(String msg) {
Log.d("PIF", msg);
}
}

View File

@ -1,216 +0,0 @@
package es.chiteroman.playintegrityfix;
import android.security.keystore.KeyProperties;
import android.text.TextUtils;
import org.spongycastle.asn1.ASN1Boolean;
import org.spongycastle.asn1.ASN1Encodable;
import org.spongycastle.asn1.ASN1EncodableVector;
import org.spongycastle.asn1.ASN1Enumerated;
import org.spongycastle.asn1.ASN1ObjectIdentifier;
import org.spongycastle.asn1.ASN1OctetString;
import org.spongycastle.asn1.ASN1Sequence;
import org.spongycastle.asn1.ASN1TaggedObject;
import org.spongycastle.asn1.DEROctetString;
import org.spongycastle.asn1.DERSequence;
import org.spongycastle.asn1.DERTaggedObject;
import org.spongycastle.asn1.x509.Extension;
import org.spongycastle.cert.X509CertificateHolder;
import org.spongycastle.cert.X509v3CertificateBuilder;
import org.spongycastle.cert.jcajce.JcaX509CertificateConverter;
import org.spongycastle.openssl.PEMKeyPair;
import org.spongycastle.openssl.PEMParser;
import org.spongycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.spongycastle.operator.ContentSigner;
import org.spongycastle.operator.jcajce.JcaContentSignerBuilder;
import org.spongycastle.util.io.pem.PemReader;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.io.ByteArrayInputStream;
import java.io.StringReader;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.LinkedList;
import java.util.concurrent.ThreadLocalRandom;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
public final class KeyboxUtils {
private static final ASN1ObjectIdentifier OID = new ASN1ObjectIdentifier("1.3.6.1.4.1.11129.2.1.17");
private static final LinkedList<Certificate> EC_CERTS = new LinkedList<>();
private static final LinkedList<Certificate> RSA_CERTS = new LinkedList<>();
private static final CertificateFactory certificateFactory;
private static PEMKeyPair EC, RSA;
static {
try {
certificateFactory = CertificateFactory.getInstance("X.509");
} catch (CertificateException e) {
throw new RuntimeException(e);
}
}
public static void parseXml(String kbox) throws Throwable {
if (TextUtils.isEmpty(kbox)) return;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new ByteArrayInputStream(kbox.getBytes()));
doc.getDocumentElement().normalize();
NodeList keyboxList = doc.getElementsByTagName("Keybox");
Node keyboxNode = keyboxList.item(0);
if (keyboxNode.getNodeType() == Node.ELEMENT_NODE) {
Element keyboxElement = (Element) keyboxNode;
NodeList keyList = keyboxElement.getElementsByTagName("Key");
for (int j = 0; j < keyList.getLength(); j++) {
Element keyElement = (Element) keyList.item(j);
String algorithm = keyElement.getAttribute("algorithm");
NodeList privateKeyList = keyElement.getElementsByTagName("PrivateKey");
if (privateKeyList.getLength() > 0) {
Element privateKeyElement = (Element) privateKeyList.item(0);
String privateKeyContent = privateKeyElement.getTextContent().trim();
if ("ecdsa".equals(algorithm)) {
EC = parseKeyPair(privateKeyContent);
} else if ("rsa".equals(algorithm)) {
RSA = parseKeyPair(privateKeyContent);
}
}
NodeList certificateChainList = keyElement.getElementsByTagName("CertificateChain");
if (certificateChainList.getLength() > 0) {
Element certificateChainElement = (Element) certificateChainList.item(0);
NodeList certificateList = certificateChainElement.getElementsByTagName("Certificate");
for (int k = 0; k < certificateList.getLength(); k++) {
Element certificateElement = (Element) certificateList.item(k);
String certificateContent = certificateElement.getTextContent().trim();
if ("ecdsa".equals(algorithm)) {
EC_CERTS.add(parseCert(certificateContent));
} else if ("rsa".equals(algorithm)) {
RSA_CERTS.add(parseCert(certificateContent));
}
}
}
}
}
}
private static PEMKeyPair parseKeyPair(String key) throws Throwable {
try (PEMParser parser = new PEMParser(new StringReader(key))) {
return (PEMKeyPair) parser.readObject();
}
}
private static Certificate parseCert(String cert) throws Throwable {
try (PemReader reader = new PemReader(new StringReader(cert))) {
return certificateFactory.generateCertificate(new ByteArrayInputStream(reader.readPemObject().getContent()));
}
}
public static Certificate[] engineGetCertificateChain(Certificate[] caList) {
if (caList == null) {
EntryPoint.LOG("Certificate chain is null!");
throw new UnsupportedOperationException();
}
if (EC == null && RSA == null) {
EntryPoint.LOG("EC and RSA private keys are null!");
throw new UnsupportedOperationException();
}
if (EC_CERTS.isEmpty() && RSA_CERTS.isEmpty()) {
EntryPoint.LOG("EC and RSA certs are empty!");
throw new UnsupportedOperationException();
}
try {
X509Certificate leaf = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(caList[0].getEncoded()));
byte[] bytes = leaf.getExtensionValue(OID.getId());
if (bytes == null) return caList;
X509CertificateHolder holder = new X509CertificateHolder(leaf.getEncoded());
Extension ext = holder.getExtension(OID);
ASN1Sequence sequence = ASN1Sequence.getInstance(ext.getExtnValue().getOctets());
ASN1Encodable[] encodables = sequence.toArray();
ASN1Sequence teeEnforced = (ASN1Sequence) encodables[7];
ASN1EncodableVector vector = new ASN1EncodableVector();
for (ASN1Encodable asn1Encodable : teeEnforced) {
ASN1TaggedObject taggedObject = (ASN1TaggedObject) asn1Encodable;
if (taggedObject.getTagNo() == 704) continue;
vector.add(taggedObject);
}
LinkedList<Certificate> certificates;
X509v3CertificateBuilder builder;
ContentSigner signer;
// Not all keyboxes have EC keys :)
if (EC != null && !EC_CERTS.isEmpty() && KeyProperties.KEY_ALGORITHM_EC.equals(leaf.getPublicKey().getAlgorithm())) {
EntryPoint.LOG("Using EC");
certificates = new LinkedList<>(EC_CERTS);
builder = new X509v3CertificateBuilder(new X509CertificateHolder(EC_CERTS.get(0).getEncoded()).getSubject(), holder.getSerialNumber(), holder.getNotBefore(), holder.getNotAfter(), holder.getSubject(), EC.getPublicKeyInfo());
signer = new JcaContentSignerBuilder(leaf.getSigAlgName()).build(new JcaPEMKeyConverter().getPrivateKey(EC.getPrivateKeyInfo()));
} else {
EntryPoint.LOG("Using RSA");
certificates = new LinkedList<>(RSA_CERTS);
builder = new X509v3CertificateBuilder(new X509CertificateHolder(RSA_CERTS.get(0).getEncoded()).getSubject(), holder.getSerialNumber(), holder.getNotBefore(), holder.getNotAfter(), holder.getSubject(), RSA.getPublicKeyInfo());
signer = new JcaContentSignerBuilder("SHA256withRSA").build(new JcaPEMKeyConverter().getPrivateKey(RSA.getPrivateKeyInfo()));
}
byte[] verifiedBootKey = new byte[32];
byte[] verifiedBootHash = new byte[32];
ThreadLocalRandom.current().nextBytes(verifiedBootKey);
ThreadLocalRandom.current().nextBytes(verifiedBootHash);
ASN1Encodable[] rootOfTrustEnc = {new DEROctetString(verifiedBootKey), ASN1Boolean.TRUE, new ASN1Enumerated(0), new DEROctetString(verifiedBootHash)};
ASN1Sequence rootOfTrustSeq = new DERSequence(rootOfTrustEnc);
ASN1TaggedObject rootOfTrustTagObj = new DERTaggedObject(704, rootOfTrustSeq);
vector.add(rootOfTrustTagObj);
ASN1Sequence hackEnforced = new DERSequence(vector);
encodables[7] = hackEnforced;
ASN1Sequence hackedSeq = new DERSequence(encodables);
ASN1OctetString hackedSeqOctets = new DEROctetString(hackedSeq);
Extension hackedExt = new Extension(OID, false, hackedSeqOctets);
builder.addExtension(hackedExt);
for (ASN1ObjectIdentifier extensionOID : holder.getExtensions().getExtensionOIDs()) {
if (OID.getId().equals(extensionOID.getId())) continue;
builder.addExtension(holder.getExtension(extensionOID));
}
certificates.addFirst(new JcaX509CertificateConverter().getCertificate(builder.build(signer)));
return certificates.toArray(new Certificate[0]);
} catch (Throwable t) {
EntryPoint.LOG(t.toString());
}
throw new UnsupportedOperationException();
}
}

View File

@ -1,6 +1,3 @@
buildscript {
val agp_version by extra("8.3.2")
}
plugins {
id("com.android.application") version "8.3.2" apply false
id("com.android.application") version "8.5.0" apply false
}

View File

@ -7,16 +7,8 @@ If not, try removing /data/adb/pif.json file.
Donations:
https://www.paypal.com/paypalme/chiteroman
# v16.3
# v16.4
Google fixed the bug, no more Strong pass with SW keybox 😢
- Improve C++ and Java code
- Downgrade first_api_level to 24, so all devices (should) be able to pass Device
- Included keybox.xml parsing! You can create /data/adb/keybox.xml to define your own keybox (Strong passing with my private one :D)
By default, inside module folder, it exists pif.json and keybox.xml, do NOT delete these files
keybox.xml included in the module is SW one
Keybox "hack" does NOT work on broken TEE devices, like OnePlus
- Misc improvements in code
- Remove keybox.xml parsing due DroidGuard detections (better use [FrameworkPatch](https://github.com/chiteroman/FrameworkPatch))
- Add BOARD and HARDWARE fields in json

View File

@ -1,114 +0,0 @@
<?xml version="1.0"?>
<AndroidAttestation>
<NumberOfKeyboxes>1</NumberOfKeyboxes>
<Keybox DeviceID="sw">
<Key algorithm="ecdsa">
<PrivateKey format="pem">
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEICHghkMqFRmEWc82OlD8FMnarfk19SfC39ceTW28QuVEoAoGCCqGSM49
AwEHoUQDQgAE6555+EJjWazLKpFMiYbMcK2QZpOCqXMmE/6sy/ghJ0whdJdKKv6l
uU1/ZtTgZRBmNbxTt6CjpnFYPts+Ea4QFA==
-----END EC PRIVATE KEY-----
</PrivateKey>
<CertificateChain>
<NumberOfCertificates>2</NumberOfCertificates>
<Certificate format="pem">
-----BEGIN CERTIFICATE-----
MIICeDCCAh6gAwIBAgICEAEwCgYIKoZIzj0EAwIwgZgxCzAJBgNVBAYTAlVTMRMw
EQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRUwEwYD
VQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQxMzAxBgNVBAMMKkFu
ZHJvaWQgS2V5c3RvcmUgU29mdHdhcmUgQXR0ZXN0YXRpb24gUm9vdDAeFw0xNjAx
MTEwMDQ2MDlaFw0yNjAxMDgwMDQ2MDlaMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE
CAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdB
bmRyb2lkMTswOQYDVQQDDDJBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVz
dGF0aW9uIEludGVybWVkaWF0ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOue
efhCY1msyyqRTImGzHCtkGaTgqlzJhP+rMv4ISdMIXSXSir+pblNf2bU4GUQZjW8
U7ego6ZxWD7bPhGuEBSjZjBkMB0GA1UdDgQWBBQ//KzWGrE6noEguNUlHMVlux6R
qTAfBgNVHSMEGDAWgBTIrel3TEXDo88NFhDkeUM6IVowzzASBgNVHRMBAf8ECDAG
AQH/AgEAMA4GA1UdDwEB/wQEAwIChDAKBggqhkjOPQQDAgNIADBFAiBLipt77oK8
wDOHri/AiZi03cONqycqRZ9pDMfDktQPjgIhAO7aAV229DLp1IQ7YkyUBO86fMy9
Xvsiu+f+uXc/WT/7
-----END CERTIFICATE-----
</Certificate>
<Certificate format="pem">
-----BEGIN CERTIFICATE-----
MIICizCCAjKgAwIBAgIJAKIFntEOQ1tXMAoGCCqGSM49BAMCMIGYMQswCQYDVQQG
EwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmll
dzEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMTMwMQYD
VQQDDCpBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVzdGF0aW9uIFJvb3Qw
HhcNMTYwMTExMDA0MzUwWhcNMzYwMTA2MDA0MzUwWjCBmDELMAkGA1UEBhMCVVMx
EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTAT
BgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDEzMDEGA1UEAwwq
QW5kcm9pZCBLZXlzdG9yZSBTb2Z0d2FyZSBBdHRlc3RhdGlvbiBSb290MFkwEwYH
KoZIzj0CAQYIKoZIzj0DAQcDQgAE7l1ex+HA220Dpn7mthvsTWpdamguD/9/SQ59
dx9EIm29sa/6FsvHrcV30lacqrewLVQBXT5DKyqO107sSHVBpKNjMGEwHQYDVR0O
BBYEFMit6XdMRcOjzw0WEOR5QzohWjDPMB8GA1UdIwQYMBaAFMit6XdMRcOjzw0W
EOR5QzohWjDPMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgKEMAoGCCqG
SM49BAMCA0cAMEQCIDUho++LNEYenNVg8x1YiSBq3KNlQfYNns6KGYxmSGB7AiBN
C/NR2TB8fVvaNTQdqEcbY6WFZTytTySn502vQX3xvw==
-----END CERTIFICATE-----
</Certificate>
</CertificateChain>
</Key>
<Key algorithm="rsa">
<PrivateKey format="pem">
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDAgyPcVogbuDAgafWwhWHG7r5/BeL1qEIEir6LR752/q7yXPKb
KvoyABQWAUKZiaFfz8aBXrNjWDwv0vIL5Jgyg92BSxbX4YVBeuVKvClqOm21wAQI
O2jFVsHwIzmRZBmGTVC3TUCuykhMdzVsiVoMJ1q/rEmdXX0jYvKcXgLocQIDAQAB
AoGBAL6GCwuZqAKm+xpZQ4p7txUGWwmjbcbpysxr88AsNNfXnpTGYGQo2Ix7f2V3
wc3qZAdKvo5yht8fCBHclygmCGjeldMu/Ja20IT/JxpfYN78xwPno45uKbqaPF/C
woB2tqiWrx0014gozpvdsfNPnJQEQweBKY4gExZyW728mTpBAkEA4cbZJ2RsCRbs
NoJtWUmDdAwh8bB0xKGlmGfGaXlchdPcRkxbkp6Uv7NODcxQFLEPEzQat/3V9gQU
0qMmytQcxQJBANpIWZd4XNVjD7D9jFJU+Y5TjhiYOq6ea35qWntdNDdVuSGOvUAy
DSg4fXifdvohi8wti2il9kGPu+ylF5qzr70CQFD+/DJklVlhbtZTThVFCTKdk6PY
ENvlvbmCKSz3i9i624Agro1X9LcdBThv/p6dsnHKNHejSZnbdvjl7OnA1J0CQBW3
TPJ8zv+Ls2vwTZ2DRrCaL3DS9EObDyasfgP36dH3fUuRX9KbKCPwOstdUgDghX/y
qAPpPu6W1iNc6VRCvCECQQCQp0XaiXCyzWSWYDJCKMX4KFb/1mW6moXI1g8bi+5x
fs0scurgHa2GunZU1M9FrbXx8rMdn4Eiz6XxpVcPmy0l
-----END RSA PRIVATE KEY-----
</PrivateKey>
<CertificateChain>
<NumberOfCertificates>2</NumberOfCertificates>
<Certificate format="pem">
-----BEGIN CERTIFICATE-----
MIICtjCCAh+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwYzELMAkGA1UEBhMCVVMx
EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTAT
BgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDAeFw0xNjAxMDQx
MjQwNTNaFw0zNTEyMzAxMjQwNTNaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApD
YWxpZm9ybmlhMRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJv
aWQxKTAnBgNVBAMMIEFuZHJvaWQgU29mdHdhcmUgQXR0ZXN0YXRpb24gS2V5MIGf
MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAgyPcVogbuDAgafWwhWHG7r5/BeL1
qEIEir6LR752/q7yXPKbKvoyABQWAUKZiaFfz8aBXrNjWDwv0vIL5Jgyg92BSxbX
4YVBeuVKvClqOm21wAQIO2jFVsHwIzmRZBmGTVC3TUCuykhMdzVsiVoMJ1q/rEmd
XX0jYvKcXgLocQIDAQABo2YwZDAdBgNVHQ4EFgQU1AwQG/jNY7n3OVK1DhNcpteZ
k4YwHwYDVR0jBBgwFoAUKfrxrMxN0kyWQCd1trDpMuUH/i4wEgYDVR0TAQH/BAgw
BgEB/wIBADAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADgYEAni1IX4xn
M9waha2Z11Aj6hTsQ7DhnerCI0YecrUZ3GAi5KVoMWwLVcTmnKItnzpPk2sxixZ4
Fg2Iy9mLzICdhPDCJ+NrOPH90ecXcjFZNX2W88V/q52PlmEmT7K+gbsNSQQiis6f
9/VCLiVE+iEHElqDtVWtGIL4QBSbnCBjBH8=
-----END CERTIFICATE-----
</Certificate>
<Certificate format="pem">
-----BEGIN CERTIFICATE-----
MIICpzCCAhCgAwIBAgIJAP+U2d2fB8gMMA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNV
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBW
aWV3MRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQwHhcN
MTYwMTA0MTIzMTA4WhcNMzUxMjMwMTIzMTA4WjBjMQswCQYDVQQGEwJVUzETMBEG
A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEVMBMGA1UE
CgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMIGfMA0GCSqGSIb3DQEB
AQUAA4GNADCBiQKBgQCia63rbi5EYe/VDoLmt5TRdSMfd5tjkWP/96r/C3JHTsAs
Q+wzfNes7UA+jCigZtX3hwszl94OuE4TQKuvpSe/lWmgMdsGUmX4RFlXYfC78hdL
t0GAZMAoDo9Sd47b0ke2RekZyOmLw9vCkT/X11DEHTVm+Vfkl5YLCazOkjWFmwID
AQABo2MwYTAdBgNVHQ4EFgQUKfrxrMxN0kyWQCd1trDpMuUH/i4wHwYDVR0jBBgw
FoAUKfrxrMxN0kyWQCd1trDpMuUH/i4wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B
Af8EBAMCAoQwDQYJKoZIhvcNAQELBQADgYEAT3LzNlmNDsG5dFsxWfbwjSVJMJ6j
HBwp0kUtILlNX2S06IDHeHqcOd6os/W/L3BfRxBcxebrTQaZYdKumgf/93y4q+uc
DyQHXrF/unlx/U1bnt8Uqf7f7XzAiF343ZtkMlbVNZriE/mPzsF83O+kqrJVw4Op
Lvtc9mL1J1IXvmM=
-----END CERTIFICATE-----
</Certificate>
</CertificateChain>
</Key>
</Keybox>
</AndroidAttestation>

View File

@ -1,7 +1,7 @@
id=playintegrityfix
name=Play Integrity Fix
version=v16.3
versionCode=16300
version=v16.4
versionCode=16400
author=chiteroman
description=Universal modular fix for Play Integrity (and SafetyNet) on devices running Android 8-15
updateJson=https://raw.githubusercontent.com/chiteroman/PlayIntegrityFix/main/update.json

View File

@ -1,15 +1,15 @@
{
"MANUFACTURER": "Google",
"MODEL": "Pixel",
"FINGERPRINT": "google/sailfish/sailfish:8.1.0/OPM1.171019.011/4448085:user/release-keys",
"BRAND": "google",
"ID": "OPM1.171019.011",
"PRODUCT": "sailfish",
"DEVICE": "sailfish",
"RELEASE": "8.1.0",
"ID": "OPM1.171019.011",
"BOARD": "",
"MANUFACTURER": "Google",
"BRAND": "google",
"MODEL": "Pixel",
"HARDWARE": "",
"INCREMENTAL": "4448085",
"TYPE": "user",
"TAGS": "release-keys",
"RELEASE": "8.1.0",
"SECURITY_PATCH": "2017-12-05",
"DEVICE_INITIAL_SDK_INT": "24"
"DEVICE_INITIAL_SDK_INT": "24",
"FINGERPRINT": "google/sailfish/sailfish:8.1.0/OPM1.171019.011/4448085:user/release-keys"
}

View File

@ -1,6 +1,6 @@
{
"version": "v16.3",
"versionCode": 16300,
"zipUrl": "https://github.com/chiteroman/PlayIntegrityFix/releases/download/v16.3/PlayIntegrityFix_v16.3.zip",
"version": "v16.4",
"versionCode": 16400,
"zipUrl": "https://github.com/chiteroman/PlayIntegrityFix/releases/download/v16.4/PlayIntegrityFix_v16.4.zip",
"changelog": "https://raw.githubusercontent.com/chiteroman/PlayIntegrityFix/main/changelog.md"
}