Rebase PIF

This commit is contained in:
chiteroman 2024-07-21 18:34:45 +02:00
commit a1dfdf3746
30 changed files with 1630 additions and 0 deletions

13
.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
*.iml
.gradle
/local.properties
/.idea
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
*.dex
*.so
*.zip

6
.gitmodules vendored Normal file
View File

@ -0,0 +1,6 @@
[submodule "app/src/main/cpp/Dobby"]
path = app/src/main/cpp/Dobby
url = https://github.com/chiteroman/Dobby.git
[submodule "app/src/main/cpp/cJSON"]
path = app/src/main/cpp/cJSON
url = https://github.com/DaveGamble/cJSON.git

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

128
app/build.gradle.kts Normal file
View File

@ -0,0 +1,128 @@
plugins {
alias(libs.plugins.android.application)
}
android {
namespace = "es.chiteroman.playintegrityfix"
compileSdk = 35
buildToolsVersion = "35.0.0"
ndkVersion = "27.0.12077973"
buildFeatures {
prefab = true
}
packaging {
resources {
excludes += "**"
}
jniLibs {
excludes += "**/libdobby.so"
}
}
defaultConfig {
applicationId = "es.chiteroman.playintegrityfix"
minSdk = 26
targetSdk = 35
versionCode = 16700
versionName = "v16.7"
multiDexEnabled = false
externalNativeBuild {
cmake {
arguments(
"-DANDROID_STL=none",
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON",
"-DANDROID_CPP_FEATURES=no-rtti no-exceptions",
"-DCMAKE_BUILD_TYPE=MinSizeRel",
"-DCMAKE_CXX_STANDARD=23",
"-DCMAKE_C_STANDARD=23",
"-DCMAKE_CXX_STANDARD_REQUIRED=ON",
"-DCMAKE_C_STANDARD_REQUIRED=ON",
"-DCMAKE_VISIBILITY_INLINES_HIDDEN=ON",
"-DCMAKE_CXX_VISIBILITY_PRESET=hidden",
"-DCMAKE_C_VISIBILITY_PRESET=hidden"
)
}
}
}
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
multiDexEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
version = "3.22.1"
}
}
}
dependencies {
implementation(libs.cxx)
implementation(libs.bouncycastle)
implementation(libs.hiddenapibypass)
}
tasks.register("updateModuleProp") {
doLast {
val versionName = project.android.defaultConfig.versionName
val versionCode = project.android.defaultConfig.versionCode
val modulePropFile = project.rootDir.resolve("module/module.prop")
var content = modulePropFile.readText()
content = content.replace(Regex("version=.*"), "version=$versionName")
content = content.replace(Regex("versionCode=.*"), "versionCode=$versionCode")
modulePropFile.writeText(content)
}
}
tasks.register("copyFiles") {
dependsOn("updateModuleProp")
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")
dexFile.copyTo(moduleFolder.resolve("classes.dex"), overwrite = true)
soDir.walk().filter { it.isFile && it.extension == "so" }.forEach { soFile ->
val abiFolder = soFile.parentFile.name
val destination = moduleFolder.resolve("zygisk/$abiFolder.so")
soFile.copyTo(destination, overwrite = true)
}
}
}
tasks.register<Zip>("zip") {
dependsOn("copyFiles")
archiveFileName.set("PlayIntegrityFix_${project.android.defaultConfig.versionName}.zip")
destinationDirectory.set(project.rootDir.resolve("out"))
from(project.rootDir.resolve("module"))
}
afterEvaluate {
tasks["assembleRelease"].finalizedBy("updateModuleProp", "copyFiles", "zip")
}

3
app/proguard-rules.pro vendored Normal file
View File

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

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />

View File

@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.22.1)
project("playintegrityfix")
find_package(cxx REQUIRED CONFIG)
link_libraries(cxx::cxx)
add_library(${CMAKE_PROJECT_NAME} SHARED main.cpp cJSON/cJSON.c)
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE cJSON)
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE log dobby_static)
add_subdirectory(Dobby)

@ -0,0 +1 @@
Subproject commit 7447fd9209bb43eba1e307f84df17613af5203dd

@ -0,0 +1 @@
Subproject commit 424ce4ce9668f288fb4ab665775546d3ed709e96

286
app/src/main/cpp/main.cpp Normal file
View File

@ -0,0 +1,286 @@
#include <android/log.h>
#include <sys/system_properties.h>
#include <unistd.h>
#include <vector>
#include <map>
#include <filesystem>
#include "zygisk.hpp"
#include "dobby.h"
#include "cJSON.h"
#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"
#define PIF_JSON "/data/adb/pif.json"
#define PIF_JSON_DEFAULT "/data/adb/modules/playintegrityfix/pif.json"
static std::string DEVICE_INITIAL_SDK_INT = "21";
static std::string SECURITY_PATCH;
static std::string BUILD_ID;
static bool DEBUG = false;
typedef void (*T_Callback)(void *, const char *, const char *, uint32_t);
static std::map<void *, T_Callback> callbacks;
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))
return;
std::string_view prop(name);
if (prop == "init.svc.adbd") {
value = "stopped";
if (!DEBUG) LOGD("[%s]: %s", name, value);
} else if (prop == "sys.usb.state") {
value = "mtp";
if (!DEBUG) LOGD("[%s]: %s", name, value);
} else if (prop.ends_with("api_level") && !DEVICE_INITIAL_SDK_INT.empty()) {
value = DEVICE_INITIAL_SDK_INT.c_str();
if (!DEBUG) LOGD("[%s]: %s", name, value);
} else if (prop.ends_with(".security_patch") && !SECURITY_PATCH.empty()) {
value = SECURITY_PATCH.c_str();
if (!DEBUG) LOGD("[%s]: %s", name, value);
} else if (prop.ends_with(".build.id") && !BUILD_ID.empty()) {
value = BUILD_ID.c_str();
if (!DEBUG) LOGD("[%s]: %s", name, value);
}
if (DEBUG) LOGD("[%s]: %s", name, value);
return callbacks[cookie](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;
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!");
return;
}
if (DobbyHook(handle, (void *) my_system_property_read_callback,
(void **) &o_system_property_read_callback)) {
LOGE("hook __system_property_read_callback failed!");
return;
}
LOGD("hook __system_property_read_callback success at %p", handle);
}
class PlayIntegrityFix : public zygisk::ModuleBase {
public:
void onLoad(zygisk::Api *api, JNIEnv *env) override {
this->api = api;
this->env = env;
}
void preAppSpecialize(zygisk::AppSpecializeArgs *args) override {
if (!args) {
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
return;
}
auto dir = env->GetStringUTFChars(args->app_data_dir, nullptr);
if (!dir) {
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
return;
}
bool isGms = std::string_view(dir).ends_with("/com.google.android.gms");
env->ReleaseStringUTFChars(args->app_data_dir, dir);
if (!isGms) {
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
return;
}
api->setOption(zygisk::FORCE_DENYLIST_UNMOUNT);
auto name = env->GetStringUTFChars(args->nice_name, nullptr);
if (!name) {
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
return;
}
bool isGmsUnstable = std::string_view(name) == "com.google.android.gms.unstable";
env->ReleaseStringUTFChars(args->nice_name, name);
if (!isGmsUnstable) {
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
return;
}
int fd = api->connectCompanion();
int dexSize = 0, jsonSize = 0;
std::vector<uint8_t> jsonVector;
read(fd, &dexSize, sizeof(int));
read(fd, &jsonSize, sizeof(int));
if (dexSize > 0) {
dexVector.resize(dexSize);
read(fd, dexVector.data(), dexSize * sizeof(uint8_t));
}
if (jsonSize > 0) {
jsonVector.resize(jsonSize);
read(fd, jsonVector.data(), jsonSize * sizeof(uint8_t));
std::string strJson(jsonVector.cbegin(), jsonVector.cend());
json = cJSON_ParseWithLength(strJson.c_str(), strJson.size());
}
close(fd);
LOGD("Dex file size: %d", dexSize);
LOGD("Json file size: %d", jsonSize);
}
void postAppSpecialize(const zygisk::AppSpecializeArgs *args) override {
if (dexVector.empty()) return;
parseJSON();
if (enableHook) doHook();
else api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
injectDex();
cJSON_Delete(json);
}
void preServerSpecialize(zygisk::ServerSpecializeArgs *args) override {
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
}
private:
zygisk::Api *api = nullptr;
JNIEnv *env = nullptr;
std::vector<uint8_t> dexVector;
cJSON *json = nullptr;
bool enableHook = false;
void parseJSON() {
if (!json) return;
const cJSON *api_level = cJSON_GetObjectItemCaseSensitive(json, "DEVICE_INITIAL_SDK_INT");
const cJSON *isDebug = cJSON_GetObjectItemCaseSensitive(json, "DEBUG");
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)) {
DEVICE_INITIAL_SDK_INT = api_level->valuestring;
}
cJSON_DeleteItemFromObjectCaseSensitive(json, "DEVICE_INITIAL_SDK_INT");
}
if (isDebug && cJSON_IsBool(isDebug)) {
DEBUG = cJSON_IsTrue(isDebug);
cJSON_DeleteItemFromObjectCaseSensitive(json, "DEBUG");
}
}
void injectDex() {
LOGD("get system classloader");
auto clClass = env->FindClass("java/lang/ClassLoader");
auto getSystemClassLoader = env->GetStaticMethodID(clClass, "getSystemClassLoader",
"()Ljava/lang/ClassLoader;");
auto systemClassLoader = env->CallStaticObjectMethod(clClass, getSystemClassLoader);
LOGD("create class loader");
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(),
static_cast<jlong>(dexVector.size()));
auto dexCl = env->NewObject(dexClClass, dexClInit, buffer, systemClassLoader);
LOGD("load class");
auto loadClass = env->GetMethodID(clClass, "loadClass",
"(Ljava/lang/String;)Ljava/lang/Class;");
auto entryClassName = env->NewStringUTF("es.chiteroman.playintegrityfix.EntryPoint");
auto entryClassObj = env->CallObjectMethod(dexCl, loadClass, entryClassName);
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);
}
};
static std::vector<uint8_t> readFile(const char *path) {
std::vector<uint8_t> vector;
FILE *file = fopen(path, "rb");
if (file) {
fseek(file, 0, SEEK_END);
long size = ftell(file);
fseek(file, 0, SEEK_SET);
vector.resize(size);
fread(vector.data(), 1, size, file);
fclose(file);
} else {
LOGD("Couldn't read %s file!", path);
}
return vector;
}
static void companion(int fd) {
std::vector<uint8_t> dex, json;
if (std::filesystem::exists(DEX_PATH)) {
dex = readFile(DEX_PATH);
}
if (std::filesystem::exists(PIF_JSON)) {
json = readFile(PIF_JSON);
} else if (std::filesystem::exists(PIF_JSON_DEFAULT)) {
json = readFile(PIF_JSON_DEFAULT);
}
int dexSize = static_cast<int>(dex.size());
int jsonSize = static_cast<int>(json.size());
write(fd, &dexSize, sizeof(int));
write(fd, &jsonSize, sizeof(int));
if (dexSize > 0) {
write(fd, dex.data(), dexSize * sizeof(uint8_t));
}
if (jsonSize > 0) {
write(fd, json.data(), jsonSize * sizeof(uint8_t));
}
}
REGISTER_ZYGISK_MODULE(PlayIntegrityFix)
REGISTER_ZYGISK_COMPANION(companion)

384
app/src/main/cpp/zygisk.hpp Normal file
View File

@ -0,0 +1,384 @@
/* Copyright 2022-2023 John "topjohnwu" Wu
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
// This is the public API for Zygisk modules.
// DO NOT MODIFY ANY CODE IN THIS HEADER.
#pragma once
#include <jni.h>
#define ZYGISK_API_VERSION 2
/*
***************
* Introduction
***************
On Android, all app processes are forked from a special daemon called "Zygote".
For each new app process, zygote will fork a new process and perform "specialization".
This specialization operation enforces the Android security sandbox on the newly forked
process to make sure that 3rd party application code is only loaded after it is being
restricted within a sandbox.
On Android, there is also this special process called "system_server". This single
process hosts a significant portion of system services, which controls how the
Android operating system and apps interact with each other.
The Zygisk framework provides a way to allow developers to build modules and run custom
code before and after system_server and any app processes' specialization.
This enable developers to inject code and alter the behavior of system_server and app processes.
Please note that modules will only be loaded after zygote has forked the child process.
THIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM_SERVER PROCESS, NOT THE ZYGOTE DAEMON!
*********************
* Development Guide
*********************
Define a class and inherit zygisk::ModuleBase to implement the functionality of your module.
Use the macro REGISTER_ZYGISK_MODULE(className) to register that class to Zygisk.
Example code:
static jint (*orig_logger_entry_max)(JNIEnv *env);
static jint my_logger_entry_max(JNIEnv *env) { return orig_logger_entry_max(env); }
class ExampleModule : public zygisk::ModuleBase {
public:
void onLoad(zygisk::Api *api, JNIEnv *env) override {
this->api = api;
this->env = env;
}
void preAppSpecialize(zygisk::AppSpecializeArgs *args) override {
JNINativeMethod methods[] = {
{ "logger_entry_max_payload_native", "()I", (void*) my_logger_entry_max },
};
api->hookJniNativeMethods(env, "android/util/Log", methods, 1);
*(void **) &orig_logger_entry_max = methods[0].fnPtr;
}
private:
zygisk::Api *api;
JNIEnv *env;
};
REGISTER_ZYGISK_MODULE(ExampleModule)
-----------------------------------------------------------------------------------------
Since your module class's code runs with either Zygote's privilege in pre[XXX]Specialize,
or runs in the sandbox of the target process in post[XXX]Specialize, the code in your class
never runs in a true superuser environment.
If your module require access to superuser permissions, you can create and register
a root companion handler function. This function runs in a separate root companion
daemon process, and an Unix domain socket is provided to allow you to perform IPC between
your target process and the root companion process.
Example code:
static void example_handler(int socket) { ... }
REGISTER_ZYGISK_COMPANION(example_handler)
*/
namespace zygisk {
struct Api;
struct AppSpecializeArgs;
struct ServerSpecializeArgs;
class ModuleBase {
public:
// This method is called as soon as the module is loaded into the target process.
// A Zygisk API handle will be passed as an argument.
virtual void onLoad([[maybe_unused]] Api *api, [[maybe_unused]] JNIEnv *env) {}
// This method is called before the app process is specialized.
// At this point, the process just got forked from zygote, but no app specific specialization
// is applied. This means that the process does not have any sandbox restrictions and
// still runs with the same privilege of zygote.
//
// All the arguments that will be sent and used for app specialization is passed as a single
// AppSpecializeArgs object. You can read and overwrite these arguments to change how the app
// process will be specialized.
//
// If you need to run some operations as superuser, you can call Api::connectCompanion() to
// get a socket to do IPC calls with a root companion process.
// See Api::connectCompanion() for more info.
virtual void preAppSpecialize([[maybe_unused]] AppSpecializeArgs *args) {}
// This method is called after the app process is specialized.
// At this point, the process has all sandbox restrictions enabled for this application.
// This means that this method runs with the same privilege of the app's own code.
virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {}
// This method is called before the system server process is specialized.
// See preAppSpecialize(args) for more info.
virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {}
// This method is called after the system server process is specialized.
// At this point, the process runs with the privilege of system_server.
virtual void postServerSpecialize([[maybe_unused]] const ServerSpecializeArgs *args) {}
};
struct AppSpecializeArgs {
// Required arguments. These arguments are guaranteed to exist on all Android versions.
jint &uid;
jint &gid;
jintArray &gids;
jint &runtime_flags;
jint &mount_external;
jstring &se_info;
jstring &nice_name;
jstring &instruction_set;
jstring &app_data_dir;
// Optional arguments. Please check whether the pointer is null before de-referencing
jboolean *const is_child_zygote;
jboolean *const is_top_app;
jobjectArray *const pkg_data_info_list;
jobjectArray *const whitelisted_data_info_list;
jboolean *const mount_data_dirs;
jboolean *const mount_storage_dirs;
AppSpecializeArgs() = delete;
};
struct ServerSpecializeArgs {
jint &uid;
jint &gid;
jintArray &gids;
jint &runtime_flags;
jlong &permitted_capabilities;
jlong &effective_capabilities;
ServerSpecializeArgs() = delete;
};
namespace internal {
struct api_table;
template <class T> void entry_impl(api_table *, JNIEnv *);
}
// These values are used in Api::setOption(Option)
enum Option : int {
// Force Magisk's denylist unmount routines to run on this process.
//
// Setting this option only makes sense in preAppSpecialize.
// The actual unmounting happens during app process specialization.
//
// Set this option to force all Magisk and modules' files to be unmounted from the
// mount namespace of the process, regardless of the denylist enforcement status.
FORCE_DENYLIST_UNMOUNT = 0,
// When this option is set, your module's library will be dlclose-ed after post[XXX]Specialize.
// Be aware that after dlclose-ing your module, all of your code will be unmapped from memory.
// YOU MUST NOT ENABLE THIS OPTION AFTER HOOKING ANY FUNCTIONS IN THE PROCESS.
DLCLOSE_MODULE_LIBRARY = 1,
};
// Bit masks of the return value of Api::getFlags()
enum StateFlag : uint32_t {
// The user has granted root access to the current process
PROCESS_GRANTED_ROOT = (1u << 0),
// The current process was added on the denylist
PROCESS_ON_DENYLIST = (1u << 1),
};
// All API methods will stop working after post[XXX]Specialize as Zygisk will be unloaded
// from the specialized process afterwards.
struct Api {
// Connect to a root companion process and get a Unix domain socket for IPC.
//
// This API only works in the pre[XXX]Specialize methods due to SELinux restrictions.
//
// The pre[XXX]Specialize methods run with the same privilege of zygote.
// If you would like to do some operations with superuser permissions, register a handler
// function that would be called in the root process with REGISTER_ZYGISK_COMPANION(func).
// Another good use case for a companion process is that if you want to share some resources
// across multiple processes, hold the resources in the companion process and pass it over.
//
// The root companion process is ABI aware; that is, when calling this method from a 32-bit
// process, you will be connected to a 32-bit companion process, and vice versa for 64-bit.
//
// Returns a file descriptor to a socket that is connected to the socket passed to your
// module's companion request handler. Returns -1 if the connection attempt failed.
int connectCompanion();
// Get the file descriptor of the root folder of the current module.
//
// This API only works in the pre[XXX]Specialize methods.
// Accessing the directory returned is only possible in the pre[XXX]Specialize methods
// or in the root companion process (assuming that you sent the fd over the socket).
// Both restrictions are due to SELinux and UID.
//
// Returns -1 if errors occurred.
int getModuleDir();
// Set various options for your module.
// Please note that this method accepts one single option at a time.
// Check zygisk::Option for the full list of options available.
void setOption(Option opt);
// Get information about the current process.
// Returns bitwise-or'd zygisk::StateFlag values.
uint32_t getFlags();
// Hook JNI native methods for a class
//
// Lookup all registered JNI native methods and replace it with your own methods.
// The original function pointer will be saved in each JNINativeMethod's fnPtr.
// If no matching class, method name, or signature is found, that specific JNINativeMethod.fnPtr
// will be set to nullptr.
void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods);
// Hook functions in the PLT (Procedure Linkage Table) of ELFs loaded in memory.
//
// Parsing /proc/[PID]/maps will give you the memory map of a process. As an example:
//
// <address> <perms> <offset> <dev> <inode> <pathname>
// 56b4346000-56b4347000 r-xp 00002000 fe:00 235 /system/bin/app_process64
// (More details: https://man7.org/linux/man-pages/man5/proc.5.html)
//
// For ELFs loaded in memory with pathname matching `regex`, replace function `symbol` with `newFunc`.
// If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`.
void pltHookRegister(const char *regex, const char *symbol, void *newFunc, void **oldFunc);
// For ELFs loaded in memory with pathname matching `regex`, exclude hooks registered for `symbol`.
// If `symbol` is nullptr, then all symbols will be excluded.
void pltHookExclude(const char *regex, const char *symbol);
// Commit all the hooks that was previously registered.
// Returns false if an error occurred.
bool pltHookCommit();
private:
internal::api_table *tbl;
template <class T> friend void internal::entry_impl(internal::api_table *, JNIEnv *);
};
// Register a class as a Zygisk module
#define REGISTER_ZYGISK_MODULE(clazz) \
void zygisk_module_entry(zygisk::internal::api_table *table, JNIEnv *env) { \
zygisk::internal::entry_impl<clazz>(table, env); \
}
// Register a root companion request handler function for your module
//
// The function runs in a superuser daemon process and handles a root companion request from
// your module running in a target process. The function has to accept an integer value,
// which is a Unix domain socket that is connected to the target process.
// See Api::connectCompanion() for more info.
//
// NOTE: the function can run concurrently on multiple threads.
// Be aware of race conditions if you have globally shared resources.
#define REGISTER_ZYGISK_COMPANION(func) \
void zygisk_companion_entry(int client) { func(client); }
/*********************************************************
* The following is internal ABI implementation detail.
* You do not have to understand what it is doing.
*********************************************************/
namespace internal {
struct module_abi {
long api_version;
ModuleBase *impl;
void (*preAppSpecialize)(ModuleBase *, AppSpecializeArgs *);
void (*postAppSpecialize)(ModuleBase *, const AppSpecializeArgs *);
void (*preServerSpecialize)(ModuleBase *, ServerSpecializeArgs *);
void (*postServerSpecialize)(ModuleBase *, const ServerSpecializeArgs *);
module_abi(ModuleBase *module) : api_version(ZYGISK_API_VERSION), impl(module) {
preAppSpecialize = [](auto m, auto args) { m->preAppSpecialize(args); };
postAppSpecialize = [](auto m, auto args) { m->postAppSpecialize(args); };
preServerSpecialize = [](auto m, auto args) { m->preServerSpecialize(args); };
postServerSpecialize = [](auto m, auto args) { m->postServerSpecialize(args); };
}
};
struct api_table {
// Base
void *impl;
bool (*registerModule)(api_table *, module_abi *);
void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);
void (*pltHookRegister)(const char *, const char *, void *, void **);
void (*pltHookExclude)(const char *, const char *);
bool (*pltHookCommit)();
int (*connectCompanion)(void * /* impl */);
void (*setOption)(void * /* impl */, Option);
int (*getModuleDir)(void * /* impl */);
uint32_t (*getFlags)(void * /* impl */);
};
template <class T>
void entry_impl(api_table *table, JNIEnv *env) {
static Api api;
api.tbl = table;
static T module;
ModuleBase *m = &module;
static module_abi abi(m);
if (!table->registerModule(table, &abi)) return;
m->onLoad(&api, env);
}
} // namespace internal
inline int Api::connectCompanion() {
return tbl->connectCompanion ? tbl->connectCompanion(tbl->impl) : -1;
}
inline int Api::getModuleDir() {
return tbl->getModuleDir ? tbl->getModuleDir(tbl->impl) : -1;
}
inline void Api::setOption(Option opt) {
if (tbl->setOption) tbl->setOption(tbl->impl, opt);
}
inline uint32_t Api::getFlags() {
return tbl->getFlags ? tbl->getFlags(tbl->impl) : 0;
}
inline void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) {
if (tbl->hookJniNativeMethods) tbl->hookJniNativeMethods(env, className, methods, numMethods);
}
inline void Api::pltHookRegister(const char *regex, const char *symbol, void *newFunc, void **oldFunc) {
if (tbl->pltHookRegister) tbl->pltHookRegister(regex, symbol, newFunc, oldFunc);
}
inline void Api::pltHookExclude(const char *regex, const char *symbol) {
if (tbl->pltHookExclude) tbl->pltHookExclude(regex, symbol);
}
inline bool Api::pltHookCommit() {
return tbl->pltHookCommit != nullptr && tbl->pltHookCommit();
}
} // namespace zygisk
extern "C" {
[[gnu::visibility("default"), maybe_unused]]
void zygisk_module_entry(zygisk::internal::api_table *, JNIEnv *);
[[gnu::visibility("default"), maybe_unused]]
void zygisk_companion_entry(int);
} // extern "C"

View File

@ -0,0 +1,107 @@
package es.chiteroman.playintegrityfix;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.Key;
import java.security.KeyStoreException;
import java.security.KeyStoreSpi;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.Date;
import java.util.Enumeration;
import java.util.Locale;
public final class CustomKeyStoreSpi extends KeyStoreSpi {
public static volatile KeyStoreSpi keyStoreSpi = null;
@Override
public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException {
return keyStoreSpi.engineGetKey(alias, password);
}
@Override
public Certificate[] engineGetCertificateChain(String alias) {
for (StackTraceElement stackTraceElement : Thread.currentThread().getStackTrace()) {
if (stackTraceElement.getClassName().toLowerCase(Locale.US).contains("droidguard")) {
Log.w(EntryPoint.TAG, "DroidGuard invoke engineGetCertificateChain! Throwing exception...");
throw new UnsupportedOperationException();
}
}
return keyStoreSpi.engineGetCertificateChain(alias);
}
@Override
public Certificate engineGetCertificate(String alias) {
return keyStoreSpi.engineGetCertificate(alias);
}
@Override
public Date engineGetCreationDate(String alias) {
return keyStoreSpi.engineGetCreationDate(alias);
}
@Override
public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) throws KeyStoreException {
keyStoreSpi.engineSetKeyEntry(alias, key, password, chain);
}
@Override
public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) throws KeyStoreException {
keyStoreSpi.engineSetKeyEntry(alias, key, chain);
}
@Override
public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException {
keyStoreSpi.engineSetCertificateEntry(alias, cert);
}
@Override
public void engineDeleteEntry(String alias) throws KeyStoreException {
keyStoreSpi.engineDeleteEntry(alias);
}
@Override
public Enumeration<String> engineAliases() {
return keyStoreSpi.engineAliases();
}
@Override
public boolean engineContainsAlias(String alias) {
return keyStoreSpi.engineContainsAlias(alias);
}
@Override
public int engineSize() {
return keyStoreSpi.engineSize();
}
@Override
public boolean engineIsKeyEntry(String alias) {
return keyStoreSpi.engineIsKeyEntry(alias);
}
@Override
public boolean engineIsCertificateEntry(String alias) {
return keyStoreSpi.engineIsCertificateEntry(alias);
}
@Override
public String engineGetCertificateAlias(Certificate cert) {
return keyStoreSpi.engineGetCertificateAlias(cert);
}
@Override
public void engineStore(OutputStream stream, char[] password) throws CertificateException, IOException, NoSuchAlgorithmException {
keyStoreSpi.engineStore(stream, password);
}
@Override
public void engineLoad(InputStream stream, char[] password) throws CertificateException, IOException, NoSuchAlgorithmException {
keyStoreSpi.engineLoad(stream, password);
}
}

View File

@ -0,0 +1,20 @@
package es.chiteroman.playintegrityfix;
import java.security.Provider;
public final class CustomProvider extends Provider {
public CustomProvider(Provider provider) {
super(provider.getName(), provider.getVersion(), provider.getInfo());
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);
}
}

View File

@ -0,0 +1,110 @@
package es.chiteroman.playintegrityfix;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
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.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 {
try {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
Field keyStoreSpi = keyStore.getClass().getDeclaredField("keyStoreSpi");
keyStoreSpi.setAccessible(true);
CustomKeyStoreSpi.keyStoreSpi = (KeyStoreSpi) keyStoreSpi.get(keyStore);
} catch (Throwable t) {
Log.e(TAG, "Couldn't get keyStoreSpi field!", t);
}
Provider provider = Security.getProvider("AndroidKeyStore");
Provider customProvider = new CustomProvider(provider);
Security.removeProvider("AndroidKeyStore");
Security.insertProviderAt(customProvider, 1);
}
public static void init(String json) {
if (TextUtils.isEmpty(json)) {
Log.e(TAG, "JSON is empty!");
} else {
try {
JSONObject jsonObject = new JSONObject(json);
jsonObject.keys().forEachRemaining(s -> {
try {
String value = jsonObject.getString(s);
if (TextUtils.isEmpty(value)) return;
Field field = getFieldByName(s);
if (field == null) return;
map.put(field, value);
} catch (Throwable t) {
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();
}
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;
}
}
field.setAccessible(true);
return field;
}
}

4
build.gradle.kts Normal file
View File

@ -0,0 +1,4 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
}

21
gradle.properties Normal file
View File

@ -0,0 +1,21 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. For more details, visit
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true

13
gradle/libs.versions.toml Normal file
View File

@ -0,0 +1,13 @@
[versions]
agp = "8.5.1"
cxx = "27.0.12077973"
bouncycastle = "1.78.1"
hiddenapibypass = "4.3"
[libraries]
cxx = { group = "org.lsposed.libcxx", name = "libcxx", version.ref = "cxx" }
bouncycastle = { group = "org.bouncycastle", name = "bcpkix-jdk18on", version.ref = "bouncycastle" }
hiddenapibypass = { group = "org.lsposed.hiddenapibypass", name = "hiddenapibypass", version.ref = "hiddenapibypass" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Sun Jul 21 17:28:50 CEST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
gradlew vendored Normal file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -0,0 +1,33 @@
#!/sbin/sh
#################
# Initialization
#################
umask 022
# echo before loading util_functions
ui_print() { echo "$1"; }
require_new_magisk() {
ui_print "*******************************"
ui_print " Please install Magisk v20.4+! "
ui_print "*******************************"
exit 1
}
#########################
# Load util_functions.sh
#########################
OUTFD=$2
ZIPFILE=$3
mount /data 2>/dev/null
[ -f /data/adb/magisk/util_functions.sh ] || require_new_magisk
. /data/adb/magisk/util_functions.sh
[ $MAGISK_VER_CODE -lt 20400 ] && require_new_magisk
install_module
exit 0

View File

@ -0,0 +1 @@
#MAGISK

17
module/common_func.sh Normal file
View File

@ -0,0 +1,17 @@
# resetprop_if_diff <prop name> <expected value>
resetprop_if_diff() {
local NAME="$1"
local EXPECTED="$2"
local CURRENT="$(resetprop "$NAME")"
[ -z "$CURRENT" ] || [ "$CURRENT" = "$EXPECTED" ] || resetprop -n "$NAME" "$EXPECTED"
}
# resetprop_if_match <prop name> <value match string> <new value>
resetprop_if_match() {
local NAME="$1"
local CONTAINS="$2"
local VALUE="$3"
[[ "$(resetprop "$NAME")" = *"$CONTAINS"* ]] && resetprop -n "$NAME" "$VALUE"
}

61
module/customize.sh Normal file
View File

@ -0,0 +1,61 @@
# Error on < Android 8
if [ "$API" -lt 26 ]; then
abort "- !!! You can't use this module on Android < 8.0"
fi
# safetynet-fix module is obsolete and it's incompatible with PIF
if [ -d "/data/adb/modules/safetynet-fix" ]; then
touch "/data/adb/modules/safetynet-fix/remove"
ui_print "! safetynet-fix module removed. Do NOT install it again along PIF"
fi
# playcurl must be removed when flashing PIF
if [ -d "/data/adb/modules/playcurl" ]; then
touch "/data/adb/modules/playcurl/remove"
ui_print "! playcurl module removed!"
fi
# MagiskHidePropsConf module is obsolete in Android 8+ but it shouldn't give issues
if [ -d "/data/adb/modules/MagiskHidePropsConf" ]; then
ui_print "! WARNING, MagiskHidePropsConf module may cause issues with PIF."
fi
# tricky_store must be removed when flashing PIF
if [ -d "/data/adb/modules/tricky_store" ]; then
touch "/data/adb/modules/tricky_store/remove"
ui_print "! tricky_store module removed!"
fi
# Check custom fingerprint
if [ -f "/data/adb/pif.json" ]; then
mv -f "/data/adb/pif.json" "/data/adb/pif.json.old"
ui_print "- Backup custom pif.json"
fi
REMOVE="
/system/product/app/XiaomiEUInject
/system/product/app/XiaomiEUInject-Stub
/system/system/app/EliteDevelopmentModule
/system/system/app/XInjectModule
/system/system_ext/app/hentaiLewdbSVTDummy
/system/system_ext/app/PifPrebuilt
"
if [ "$KSU" = "true" -o "$APATCH" = "true" ]; then
ui_print "- KernelSU/APatch detected, conflicting apps will be automatically removed"
else
ui_print "- Magisk detected, removing conflicting apps one by one :("
echo "$REMOVE" | grep -v '^$' | while read -r line; do
if [ -d "$line" ]; then
mkdir -p "${MODPATH}${line}"
touch "${MODPATH}${line}/.replace"
ui_print "- Removed dir: $line"
elif [ -f "$line" ]; then
dir=$(dirname "$line")
filename=$(basename "$line")
mkdir -p "${MODPATH}${dir}"
touch "${MODPATH}${dir}/${filename}"
ui_print "- Removed file: $line"
fi
done
fi

7
module/module.prop Normal file
View File

@ -0,0 +1,7 @@
id=playintegrityfix
name=Play Integrity Fix
version=v16.7
versionCode=16700
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

12
module/pif.json Normal file
View File

@ -0,0 +1,12 @@
{
"ID": "AP31.240517.022",
"BRAND": "google",
"DEVICE": "husky",
"FINGERPRINT": "google/husky_beta/husky:15/AP31.240517.022/11948202:user/release-keys",
"MANUFACTURER": "Google",
"MODEL": "Pixel 8 Pro",
"PRODUCT": "husky_beta",
"SECURITY_PATCH": "2024-06-05",
"DEVICE_INITIAL_SDK_INT": 21,
"DEBUG": false
}

37
module/post-fs-data.sh Normal file
View File

@ -0,0 +1,37 @@
MODPATH="${0%/*}"
. $MODPATH/common_func.sh
# Remove Play Services from Magisk DenyList when set to Enforce in normal mode
if magisk --denylist status; then
magisk --denylist rm com.google.android.gms
fi
# Conditional early sensitive properties
# Samsung
resetprop_if_diff ro.boot.warranty_bit 0
resetprop_if_diff ro.vendor.boot.warranty_bit 0
resetprop_if_diff ro.vendor.warranty_bit 0
resetprop_if_diff ro.warranty_bit 0
# Xiaomi
resetprop_if_diff ro.secureboot.lockstate locked
# Realme
resetprop_if_diff ro.boot.realmebootstate green
# OnePlus
resetprop_if_diff ro.is_ever_orange 0
# Microsoft
for PROP in $(resetprop | grep -oE 'ro.*.build.tags'); do
resetprop_if_diff $PROP release-keys
done
# Other
for PROP in $(resetprop | grep -oE 'ro.*.build.type'); do
resetprop_if_diff $PROP user
done
resetprop_if_diff ro.debuggable 0
resetprop_if_diff ro.force.debuggable 0
resetprop_if_diff ro.secure 1

44
module/service.sh Normal file
View File

@ -0,0 +1,44 @@
MODPATH="${0%/*}"
. $MODPATH/common_func.sh
# Conditional sensitive properties
# Magisk Recovery Mode
resetprop_if_match ro.boot.mode recovery unknown
resetprop_if_match ro.bootmode recovery unknown
resetprop_if_match vendor.boot.mode recovery unknown
# SELinux
resetprop_if_diff ro.boot.selinux enforcing
# use delete since it can be 0 or 1 for enforcing depending on OEM
if [ -n "$(resetprop ro.build.selinux)" ]; then
resetprop --delete ro.build.selinux
fi
# use toybox to protect stat access time reading
if [ "$(toybox cat /sys/fs/selinux/enforce)" = "0" ]; then
chmod 640 /sys/fs/selinux/enforce
chmod 440 /sys/fs/selinux/policy
fi
# Conditional late sensitive properties
# must be set after boot_completed for various OEMs
until [[ "$(getprop sys.boot_completed)" == "1" ]]; do
sleep 1
done
# SafetyNet/Play Integrity + OEM
# avoid breaking Realme fingerprint scanners
resetprop_if_diff ro.boot.flash.locked 1
resetprop_if_diff ro.boot.realme.lockstate 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 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
# Other
resetprop_if_diff sys.oem_unlock_allowed 0

23
settings.gradle.kts Normal file
View File

@ -0,0 +1,23 @@
pluginManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "PlayIntegrityFix"
include(":app")