mirror of
https://github.com/chiteroman/PlayIntegrityFix.git
synced 2025-04-29 01:22:07 +08:00
This commit is contained in:
parent
83b7fcf7f6
commit
44a0ccc850
4
.github/workflows/android.yml
vendored
4
.github/workflows/android.yml
vendored
@ -2,10 +2,10 @@ name: Android CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: main
|
||||
branches: inject
|
||||
paths-ignore: '**.md'
|
||||
pull_request:
|
||||
branches: main
|
||||
branches: inject
|
||||
paths-ignore: '**.md'
|
||||
|
||||
jobs:
|
||||
|
4
.gitmodules
vendored
4
.gitmodules
vendored
@ -1,3 +1,3 @@
|
||||
[submodule "app/src/main/cpp/Dobby"]
|
||||
path = app/src/main/cpp/Dobby
|
||||
[submodule "inject/src/main/cpp/Dobby"]
|
||||
path = inject/src/main/cpp/Dobby
|
||||
url = https://github.com/JingMatrix/Dobby.git
|
||||
|
@ -1,134 +0,0 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "es.chiteroman.playintegrityfix"
|
||||
compileSdk = 35
|
||||
|
||||
buildFeatures {
|
||||
prefab = true
|
||||
}
|
||||
|
||||
packaging {
|
||||
jniLibs {
|
||||
excludes += "**/libdobby.so"
|
||||
}
|
||||
resources {
|
||||
excludes += "**"
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "es.chiteroman.playintegrityfix"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 18900
|
||||
versionName = "v18.9"
|
||||
multiDexEnabled = false
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
abiFilters(
|
||||
"arm64-v8a",
|
||||
"armeabi-v7a"
|
||||
)
|
||||
|
||||
arguments(
|
||||
"-DCMAKE_BUILD_TYPE=MinSizeRel",
|
||||
"-DANDROID_STL=none"
|
||||
)
|
||||
|
||||
cFlags(
|
||||
"-std=c23",
|
||||
"-fvisibility=hidden",
|
||||
"-fvisibility-inlines-hidden"
|
||||
)
|
||||
|
||||
cppFlags(
|
||||
"-std=c++26",
|
||||
"-fno-exceptions",
|
||||
"-fno-rtti",
|
||||
"-fvisibility=hidden",
|
||||
"-fvisibility-inlines-hidden"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
multiDexEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path = file("src/main/cpp/CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.cxx)
|
||||
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")
|
||||
}
|
@ -1,483 +0,0 @@
|
||||
#include <android/log.h>
|
||||
#include <sys/system_properties.h>
|
||||
#include <unistd.h>
|
||||
#include "zygisk.hpp"
|
||||
#include "dobby.h"
|
||||
#include "json.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"
|
||||
|
||||
#define TS_PATH "/data/adb/modules/tricky_store"
|
||||
|
||||
#define DEFAULT_JSON "/data/adb/modules/playintegrityfix/pif.json"
|
||||
#define CUSTOM_JSON_FORK "/data/adb/modules/playintegrityfix/custom.pif.json"
|
||||
#define CUSTOM_JSON "/data/adb/pif.json"
|
||||
|
||||
static ssize_t xread(int fd, void *buffer, size_t count) {
|
||||
ssize_t total = 0;
|
||||
char *buf = static_cast<char *>(buffer);
|
||||
while (count > 0) {
|
||||
ssize_t ret = TEMP_FAILURE_RETRY(read(fd, buf, count));
|
||||
if (ret < 0) return -1;
|
||||
buf += ret;
|
||||
total += ret;
|
||||
count -= ret;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
static ssize_t xwrite(int fd, const void *buffer, size_t count) {
|
||||
ssize_t total = 0;
|
||||
char *buf = (char *) buffer;
|
||||
while (count > 0) {
|
||||
ssize_t ret = TEMP_FAILURE_RETRY(write(fd, buf, count));
|
||||
if (ret < 0) return -1;
|
||||
buf += ret;
|
||||
total += ret;
|
||||
count -= ret;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
static bool DEBUG = false;
|
||||
static std::string DEVICE_INITIAL_SDK_INT = "21", SECURITY_PATCH, BUILD_ID;
|
||||
|
||||
typedef void (*T_Callback)(void *, const char *, const char *, uint32_t);
|
||||
|
||||
static T_Callback o_callback = nullptr;
|
||||
|
||||
static void modify_callback(void *cookie, const char *name, const char *value, uint32_t serial) {
|
||||
|
||||
if (!cookie || !name || !value || !o_callback) return;
|
||||
|
||||
const char *oldValue = value;
|
||||
|
||||
std::string_view prop(name);
|
||||
|
||||
if (prop == "init.svc.adbd") {
|
||||
value = "stopped";
|
||||
} else if (prop == "sys.usb.state") {
|
||||
value = "mtp";
|
||||
} else if (prop.ends_with("api_level")) {
|
||||
if (!DEVICE_INITIAL_SDK_INT.empty()) {
|
||||
value = DEVICE_INITIAL_SDK_INT.c_str();
|
||||
}
|
||||
} else if (prop.ends_with(".security_patch")) {
|
||||
if (!SECURITY_PATCH.empty()) {
|
||||
value = SECURITY_PATCH.c_str();
|
||||
}
|
||||
} else if (prop.ends_with(".build.id")) {
|
||||
if (!BUILD_ID.empty()) {
|
||||
value = BUILD_ID.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(oldValue, value) == 0) {
|
||||
if (DEBUG) LOGD("[%s]: %s (unchanged)", name, oldValue);
|
||||
} else {
|
||||
LOGD("[%s]: %s -> %s", name, oldValue, value);
|
||||
}
|
||||
|
||||
return o_callback(cookie, name, value, serial);
|
||||
}
|
||||
|
||||
static void (*o_system_property_read_callback)(prop_info *, T_Callback, void *) = nullptr;
|
||||
|
||||
static void my_system_property_read_callback(prop_info *pi, T_Callback callback, void *cookie) {
|
||||
if (pi && callback && cookie) o_callback = callback;
|
||||
return o_system_property_read_callback(pi, modify_callback, cookie);
|
||||
}
|
||||
|
||||
static bool doHook() {
|
||||
void *ptr = DobbySymbolResolver(nullptr, "__system_property_read_callback");
|
||||
|
||||
if (ptr && DobbyHook(ptr, (void *) my_system_property_read_callback,
|
||||
(void **) &o_system_property_read_callback) == 0) {
|
||||
LOGD("hook __system_property_read_callback successful at %p", ptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
LOGE("hook __system_property_read_callback failed!");
|
||||
return false;
|
||||
}
|
||||
|
||||
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 {
|
||||
const char *rawDir = env->GetStringUTFChars(args->app_data_dir, nullptr);
|
||||
const char *rawName = env->GetStringUTFChars(args->nice_name, nullptr);
|
||||
|
||||
std::string dir, name;
|
||||
|
||||
if (rawDir) {
|
||||
dir = rawDir;
|
||||
env->ReleaseStringUTFChars(args->app_data_dir, rawDir);
|
||||
}
|
||||
|
||||
if (rawName) {
|
||||
name = rawName;
|
||||
env->ReleaseStringUTFChars(args->nice_name, rawName);
|
||||
}
|
||||
|
||||
bool isGms = dir.ends_with("/com.google.android.gms");
|
||||
bool isGmsUnstable = name == "com.google.android.gms.unstable";
|
||||
|
||||
if (!isGms) {
|
||||
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
|
||||
return;
|
||||
}
|
||||
|
||||
api->setOption(zygisk::FORCE_DENYLIST_UNMOUNT);
|
||||
|
||||
if (!isGmsUnstable) {
|
||||
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
|
||||
return;
|
||||
}
|
||||
|
||||
int fd = api->connectCompanion();
|
||||
|
||||
size_t dexSize = 0, jsonSize = 0;
|
||||
std::string jsonStr;
|
||||
|
||||
xread(fd, &dexSize, sizeof(size_t));
|
||||
xread(fd, &jsonSize, sizeof(size_t));
|
||||
|
||||
if (dexSize > 0) {
|
||||
dexVector.resize(dexSize);
|
||||
xread(fd, dexVector.data(), dexSize);
|
||||
}
|
||||
|
||||
if (jsonSize > 0) {
|
||||
jsonStr.resize(jsonSize);
|
||||
jsonSize = xread(fd, jsonStr.data(), jsonSize);
|
||||
jsonStr[jsonSize] = '\0';
|
||||
json = nlohmann::json::parse(jsonStr, nullptr, false, true);
|
||||
}
|
||||
|
||||
bool trickyStore = false;
|
||||
xread(fd, &trickyStore, sizeof(bool));
|
||||
|
||||
bool testSignedRom = false;
|
||||
xread(fd, &testSignedRom, sizeof(bool));
|
||||
|
||||
close(fd);
|
||||
|
||||
LOGD("Dex file size: %zu", dexSize);
|
||||
LOGD("Json file size: %zu", jsonSize);
|
||||
|
||||
parseJSON();
|
||||
|
||||
if (trickyStore) {
|
||||
LOGD("TrickyStore module detected!");
|
||||
spoofProvider = false;
|
||||
spoofProps = false;
|
||||
}
|
||||
|
||||
if (testSignedRom) {
|
||||
LOGD("--- ROM IS SIGNED WITH TEST KEYS ---");
|
||||
spoofSignature = true;
|
||||
}
|
||||
}
|
||||
|
||||
void postAppSpecialize(const zygisk::AppSpecializeArgs *args) override {
|
||||
if (dexVector.empty() || json.empty()) return;
|
||||
|
||||
UpdateBuildFields();
|
||||
|
||||
if (spoofProvider || spoofSignature) {
|
||||
injectDex();
|
||||
} else {
|
||||
LOGD("Dex file won't be injected due spoofProvider and spoofSignature are false");
|
||||
}
|
||||
|
||||
if (spoofProps) {
|
||||
if (!doHook()) {
|
||||
dlclose();
|
||||
}
|
||||
} else {
|
||||
dlclose();
|
||||
}
|
||||
|
||||
json.clear();
|
||||
|
||||
dexVector.clear();
|
||||
dexVector.shrink_to_fit();
|
||||
}
|
||||
|
||||
void preServerSpecialize(zygisk::ServerSpecializeArgs *args) override {
|
||||
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
|
||||
}
|
||||
|
||||
private:
|
||||
zygisk::Api *api = nullptr;
|
||||
JNIEnv *env = nullptr;
|
||||
std::vector<char> dexVector;
|
||||
nlohmann::json json;
|
||||
bool spoofProps = true;
|
||||
bool spoofProvider = true;
|
||||
bool spoofSignature = false;
|
||||
|
||||
void dlclose() {
|
||||
LOGD("dlclose zygisk lib");
|
||||
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
|
||||
}
|
||||
|
||||
void parseJSON() {
|
||||
if (json.empty()) return;
|
||||
|
||||
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 value!");
|
||||
}
|
||||
json.erase("DEVICE_INITIAL_SDK_INT");
|
||||
}
|
||||
|
||||
if (json.contains("spoofProvider") && json["spoofProvider"].is_boolean()) {
|
||||
spoofProvider = json["spoofProvider"].get<bool>();
|
||||
json.erase("spoofProvider");
|
||||
}
|
||||
|
||||
if (json.contains("spoofProps") && json["spoofProps"].is_boolean()) {
|
||||
spoofProps = json["spoofProps"].get<bool>();
|
||||
json.erase("spoofProps");
|
||||
}
|
||||
|
||||
if (json.contains("spoofSignature") && json["spoofSignature"].is_boolean()) {
|
||||
spoofSignature = json["spoofSignature"].get<bool>();
|
||||
json.erase("spoofSignature");
|
||||
}
|
||||
|
||||
if (json.contains("DEBUG") && json["DEBUG"].is_boolean()) {
|
||||
DEBUG = json["DEBUG"].get<bool>();
|
||||
json.erase("DEBUG");
|
||||
}
|
||||
|
||||
if (json.contains("FINGERPRINT") && json["FINGERPRINT"].is_string()) {
|
||||
std::string fingerprint = json["FINGERPRINT"].get<std::string>();
|
||||
|
||||
std::vector<std::string> vector;
|
||||
auto parts = fingerprint | std::views::split('/');
|
||||
|
||||
for (const auto &part: parts) {
|
||||
auto subParts = std::string(part.begin(), part.end()) | std::views::split(':');
|
||||
for (const auto &subPart: subParts) {
|
||||
vector.emplace_back(subPart.begin(), subPart.end());
|
||||
}
|
||||
}
|
||||
|
||||
if (vector.size() == 8) {
|
||||
json["BRAND"] = vector[0];
|
||||
json["PRODUCT"] = vector[1];
|
||||
json["DEVICE"] = vector[2];
|
||||
json["RELEASE"] = vector[3];
|
||||
json["ID"] = vector[4];
|
||||
json["INCREMENTAL"] = vector[5];
|
||||
json["TYPE"] = vector[6];
|
||||
json["TAGS"] = vector[7];
|
||||
} else {
|
||||
LOGE("Error parsing fingerprint values!");
|
||||
}
|
||||
}
|
||||
|
||||
if (json.contains("SECURITY_PATCH") && json["SECURITY_PATCH"].is_string()) {
|
||||
SECURITY_PATCH = json["SECURITY_PATCH"].get<std::string>();
|
||||
}
|
||||
|
||||
if (json.contains("ID") && json["ID"].is_string()) {
|
||||
BUILD_ID = json["ID"].get<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
return;
|
||||
}
|
||||
|
||||
LOGD("call init");
|
||||
auto entryInit = env->GetStaticMethodID(entryPointClass, "init", "(Ljava/lang/String;ZZ)V");
|
||||
auto jsonStr = env->NewStringUTF(json.dump().c_str());
|
||||
env->CallStaticVoidMethod(entryPointClass, entryInit, jsonStr, spoofProvider,
|
||||
spoofSignature);
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
}
|
||||
|
||||
env->DeleteLocalRef(entryClassName);
|
||||
env->DeleteLocalRef(entryClassObj);
|
||||
env->DeleteLocalRef(jsonStr);
|
||||
env->DeleteLocalRef(dexCl);
|
||||
env->DeleteLocalRef(buffer);
|
||||
env->DeleteLocalRef(dexClClass);
|
||||
env->DeleteLocalRef(clClass);
|
||||
|
||||
LOGD("jni memory free");
|
||||
}
|
||||
|
||||
void UpdateBuildFields() {
|
||||
jclass buildClass = env->FindClass("android/os/Build");
|
||||
jclass versionClass = env->FindClass("android/os/Build$VERSION");
|
||||
|
||||
for (auto &[key, val]: json.items()) {
|
||||
if (!val.is_string()) continue;
|
||||
|
||||
const char *fieldName = key.c_str();
|
||||
|
||||
jfieldID fieldID = env->GetStaticFieldID(buildClass, fieldName, "Ljava/lang/String;");
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionClear();
|
||||
|
||||
fieldID = env->GetStaticFieldID(versionClass, fieldName, "Ljava/lang/String;");
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionClear();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldID != nullptr) {
|
||||
std::string str = val.get<std::string>();
|
||||
const char *value = str.c_str();
|
||||
jstring jValue = env->NewStringUTF(value);
|
||||
|
||||
env->SetStaticObjectField(buildClass, fieldID, jValue);
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionClear();
|
||||
continue;
|
||||
}
|
||||
|
||||
LOGD("Set '%s' to '%s'", fieldName, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static std::vector<char> readFile(const char *path) {
|
||||
FILE *file = fopen(path, "rb");
|
||||
|
||||
if (!file) return {};
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
long size = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
std::vector<char> vector(size);
|
||||
fread(vector.data(), 1, size, file);
|
||||
|
||||
fclose(file);
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
static bool checkOtaZip() {
|
||||
std::array<char, 256> buffer{};
|
||||
std::string result;
|
||||
bool found = false;
|
||||
|
||||
std::unique_ptr<FILE, decltype(&pclose)> pipe(
|
||||
popen("unzip -l /system/etc/security/otacerts.zip", "r"), pclose);
|
||||
if (!pipe) return false;
|
||||
|
||||
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
|
||||
result += buffer.data();
|
||||
if (result.find("test") != std::string::npos) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
static void companion(int fd) {
|
||||
|
||||
std::vector<char> dex, json;
|
||||
|
||||
if (std::filesystem::exists(DEX_PATH)) {
|
||||
dex = readFile(DEX_PATH);
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(CUSTOM_JSON)) {
|
||||
json = readFile(CUSTOM_JSON);
|
||||
} else if (std::filesystem::exists(CUSTOM_JSON_FORK)) {
|
||||
json = readFile(CUSTOM_JSON_FORK);
|
||||
} else if (std::filesystem::exists(DEFAULT_JSON)) {
|
||||
json = readFile(DEFAULT_JSON);
|
||||
}
|
||||
|
||||
size_t dexSize = dex.size();
|
||||
size_t jsonSize = json.size();
|
||||
|
||||
xwrite(fd, &dexSize, sizeof(size_t));
|
||||
xwrite(fd, &jsonSize, sizeof(size_t));
|
||||
|
||||
if (dexSize > 0) {
|
||||
xwrite(fd, dex.data(), dexSize);
|
||||
}
|
||||
|
||||
if (jsonSize > 0) {
|
||||
xwrite(fd, json.data(), jsonSize);
|
||||
}
|
||||
|
||||
std::string ts(TS_PATH);
|
||||
bool trickyStore = std::filesystem::exists(ts) &&
|
||||
!std::filesystem::exists(ts + "/disable") &&
|
||||
!std::filesystem::exists(ts + "/remove");
|
||||
xwrite(fd, &trickyStore, sizeof(bool));
|
||||
|
||||
bool testSignedRom = checkOtaZip();
|
||||
xwrite(fd, &testSignedRom, sizeof(bool));
|
||||
}
|
||||
|
||||
REGISTER_ZYGISK_MODULE(PlayIntegrityFix)
|
||||
|
||||
REGISTER_ZYGISK_COMPANION(companion)
|
@ -1,384 +0,0 @@
|
||||
/* 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"
|
@ -1,4 +1,57 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.android.library) apply false
|
||||
}
|
||||
|
||||
tasks.register("copyZygiskFiles") {
|
||||
doLast {
|
||||
val moduleFolder = project.rootDir.resolve("module")
|
||||
|
||||
val zygiskModule = project.project(":zygisk")
|
||||
val zygiskBuildDir = zygiskModule.layout.buildDirectory.get().asFile
|
||||
|
||||
val classesJar = zygiskBuildDir
|
||||
.resolve("intermediates/aar_main_jar/release/syncReleaseLibJars/classes.jar")
|
||||
classesJar.copyTo(moduleFolder.resolve("classes.jar"), overwrite = true)
|
||||
|
||||
val zygiskSoDir = zygiskBuildDir
|
||||
.resolve("intermediates/stripped_native_libs/release/stripReleaseDebugSymbols/out/lib")
|
||||
|
||||
zygiskSoDir.walk()
|
||||
.filter { it.isFile && it.name == "libzygisk.so" }
|
||||
.forEach { soFile ->
|
||||
val abiFolder = soFile.parentFile.name
|
||||
val destination = moduleFolder.resolve("zygisk/$abiFolder.so")
|
||||
soFile.copyTo(destination, overwrite = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("copyInjectFiles") {
|
||||
doLast {
|
||||
val moduleFolder = project.rootDir.resolve("module")
|
||||
|
||||
val injectModule = project.project(":inject")
|
||||
val injectBuildDir = injectModule.layout.buildDirectory.get().asFile
|
||||
|
||||
val injectSoDir = injectBuildDir
|
||||
.resolve("intermediates/stripped_native_libs/release/stripReleaseDebugSymbols/out/lib")
|
||||
|
||||
injectSoDir.walk()
|
||||
.filter { it.isFile && it.name == "libinject.so" }
|
||||
.forEach { soFile ->
|
||||
val abiFolder = soFile.parentFile.name
|
||||
val destination = moduleFolder.resolve("inject/$abiFolder.so")
|
||||
soFile.copyTo(destination, overwrite = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register<Zip>("zip") {
|
||||
dependsOn("copyZygiskFiles", "copyInjectFiles")
|
||||
|
||||
archiveFileName.set("PlayIntegrityFix.zip")
|
||||
destinationDirectory.set(project.rootDir.resolve("out"))
|
||||
|
||||
from(project.rootDir.resolve("module"))
|
||||
}
|
@ -8,4 +8,4 @@ cxx = { group = "org.lsposed.libcxx", name = "libcxx", version.ref = "cxx" }
|
||||
hiddenapibypass = { group = "org.lsposed.hiddenapibypass", name = "hiddenapibypass", version.ref = "hiddenapibypass" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||
|
0
app/.gitignore → inject/.gitignore
vendored
0
app/.gitignore → inject/.gitignore
vendored
81
inject/build.gradle.kts
Normal file
81
inject/build.gradle.kts
Normal file
@ -0,0 +1,81 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "es.chiteroman.inject"
|
||||
compileSdk = 35
|
||||
|
||||
buildFeatures {
|
||||
prefab = true
|
||||
}
|
||||
|
||||
packaging {
|
||||
jniLibs {
|
||||
excludes += "**/libdobby.so"
|
||||
}
|
||||
resources {
|
||||
excludes += "**"
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
abiFilters(
|
||||
"arm64-v8a",
|
||||
"armeabi-v7a"
|
||||
)
|
||||
|
||||
arguments(
|
||||
"-DCMAKE_BUILD_TYPE=MinSizeRel",
|
||||
"-DANDROID_STL=none"
|
||||
)
|
||||
|
||||
cFlags(
|
||||
"-std=c23",
|
||||
"-fvisibility=hidden",
|
||||
"-fvisibility-inlines-hidden"
|
||||
)
|
||||
|
||||
cppFlags(
|
||||
"-std=c++26",
|
||||
"-fno-exceptions",
|
||||
"-fno-rtti",
|
||||
"-fvisibility=hidden",
|
||||
"-fvisibility-inlines-hidden"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path("src/main/cpp/CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.cxx)
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
tasks.named("assembleRelease") {
|
||||
finalizedBy(
|
||||
rootProject.tasks["copyInjectFiles"],
|
||||
rootProject.tasks["zip"]
|
||||
)
|
||||
}
|
||||
}
|
0
inject/consumer-rules.pro
Normal file
0
inject/consumer-rules.pro
Normal file
21
inject/proguard-rules.pro
vendored
Normal file
21
inject/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
1
inject/src/main/AndroidManifest.xml
Normal file
1
inject/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1 @@
|
||||
<manifest />
|
@ -1,6 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
project("playintegrityfix")
|
||||
project("inject")
|
||||
|
||||
link_libraries(log)
|
||||
|
||||
@ -8,8 +8,8 @@ find_package(cxx REQUIRED CONFIG)
|
||||
|
||||
link_libraries(cxx::cxx)
|
||||
|
||||
add_library(${CMAKE_PROJECT_NAME} SHARED main.cpp)
|
||||
add_library(inject SHARED inject.cpp)
|
||||
|
||||
add_subdirectory(Dobby)
|
||||
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE dobby_static)
|
||||
target_link_libraries(inject PRIVATE dobby_static)
|
302
inject/src/main/cpp/inject.cpp
Normal file
302
inject/src/main/cpp/inject.cpp
Normal file
@ -0,0 +1,302 @@
|
||||
#include "dobby.h"
|
||||
#include "json.hpp"
|
||||
#include <android/log.h>
|
||||
#include <jni.h>
|
||||
#include <sys/system_properties.h>
|
||||
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "PIF", __VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "PIF", __VA_ARGS__)
|
||||
|
||||
static std::string dir;
|
||||
static JNIEnv *env;
|
||||
|
||||
static nlohmann::json json;
|
||||
|
||||
static bool spoofProps = true, spoofProvider = true, spoofSignature = false;
|
||||
|
||||
static bool DEBUG = false;
|
||||
static std::string DEVICE_INITIAL_SDK_INT, SECURITY_PATCH, BUILD_ID;
|
||||
|
||||
typedef void (*T_Callback)(void *, const char *, const char *, uint32_t);
|
||||
|
||||
static T_Callback o_callback = nullptr;
|
||||
|
||||
static void modify_callback(void *cookie, const char *name, const char *value,
|
||||
uint32_t serial) {
|
||||
|
||||
if (!cookie || !name || !value || !o_callback)
|
||||
return;
|
||||
|
||||
const char *oldValue = value;
|
||||
|
||||
std::string_view prop(name);
|
||||
|
||||
if (prop == "init.svc.adbd") {
|
||||
value = "stopped";
|
||||
} else if (prop == "sys.usb.state") {
|
||||
value = "mtp";
|
||||
} else if (prop.ends_with("api_level")) {
|
||||
if (!DEVICE_INITIAL_SDK_INT.empty()) {
|
||||
value = DEVICE_INITIAL_SDK_INT.c_str();
|
||||
}
|
||||
} else if (prop.ends_with(".security_patch")) {
|
||||
if (!SECURITY_PATCH.empty()) {
|
||||
value = SECURITY_PATCH.c_str();
|
||||
}
|
||||
} else if (prop.ends_with(".build.id")) {
|
||||
if (!BUILD_ID.empty()) {
|
||||
value = BUILD_ID.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(oldValue, value) == 0) {
|
||||
if (DEBUG)
|
||||
LOGD("[%s]: %s (unchanged)", name, oldValue);
|
||||
} else {
|
||||
LOGD("[%s]: %s -> %s", name, oldValue, value);
|
||||
}
|
||||
|
||||
return o_callback(cookie, name, value, serial);
|
||||
}
|
||||
|
||||
static void (*o_system_property_read_callback)(prop_info *, T_Callback,
|
||||
void *) = nullptr;
|
||||
|
||||
static void my_system_property_read_callback(prop_info *pi, T_Callback callback,
|
||||
void *cookie) {
|
||||
if (pi && callback && cookie)
|
||||
o_callback = callback;
|
||||
return o_system_property_read_callback(pi, modify_callback, cookie);
|
||||
}
|
||||
|
||||
static bool doHook() {
|
||||
void *ptr = DobbySymbolResolver(nullptr, "__system_property_read_callback");
|
||||
|
||||
if (ptr && DobbyHook(ptr, (void *) my_system_property_read_callback,
|
||||
(void **) &o_system_property_read_callback) == 0) {
|
||||
LOGD("hook __system_property_read_callback successful at %p", ptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
LOGE("hook __system_property_read_callback failed!");
|
||||
return false;
|
||||
}
|
||||
|
||||
static void parseJSON() {
|
||||
if (json.empty())
|
||||
return;
|
||||
|
||||
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 value!");
|
||||
}
|
||||
json.erase("DEVICE_INITIAL_SDK_INT");
|
||||
}
|
||||
|
||||
if (json.contains("spoofProvider") && json["spoofProvider"].is_boolean()) {
|
||||
spoofProvider = json["spoofProvider"].get<bool>();
|
||||
json.erase("spoofProvider");
|
||||
}
|
||||
|
||||
if (json.contains("spoofProps") && json["spoofProps"].is_boolean()) {
|
||||
spoofProps = json["spoofProps"].get<bool>();
|
||||
json.erase("spoofProps");
|
||||
}
|
||||
|
||||
if (json.contains("spoofSignature") && json["spoofSignature"].is_boolean()) {
|
||||
spoofSignature = json["spoofSignature"].get<bool>();
|
||||
json.erase("spoofSignature");
|
||||
}
|
||||
|
||||
if (json.contains("DEBUG") && json["DEBUG"].is_boolean()) {
|
||||
DEBUG = json["DEBUG"].get<bool>();
|
||||
json.erase("DEBUG");
|
||||
}
|
||||
|
||||
if (json.contains("FINGERPRINT") && json["FINGERPRINT"].is_string()) {
|
||||
std::string fingerprint = json["FINGERPRINT"].get<std::string>();
|
||||
|
||||
std::vector<std::string> vector;
|
||||
auto parts = fingerprint | std::views::split('/');
|
||||
|
||||
for (const auto &part: parts) {
|
||||
auto subParts =
|
||||
std::string(part.begin(), part.end()) | std::views::split(':');
|
||||
for (const auto &subPart: subParts) {
|
||||
vector.emplace_back(subPart.begin(), subPart.end());
|
||||
}
|
||||
}
|
||||
|
||||
if (vector.size() == 8) {
|
||||
json["BRAND"] = vector[0];
|
||||
json["PRODUCT"] = vector[1];
|
||||
json["DEVICE"] = vector[2];
|
||||
json["RELEASE"] = vector[3];
|
||||
json["ID"] = vector[4];
|
||||
json["INCREMENTAL"] = vector[5];
|
||||
json["TYPE"] = vector[6];
|
||||
json["TAGS"] = vector[7];
|
||||
} else {
|
||||
LOGE("Error parsing fingerprint values!");
|
||||
}
|
||||
}
|
||||
|
||||
if (json.contains("SECURITY_PATCH") && json["SECURITY_PATCH"].is_string()) {
|
||||
SECURITY_PATCH = json["SECURITY_PATCH"].get<std::string>();
|
||||
}
|
||||
|
||||
if (json.contains("ID") && json["ID"].is_string()) {
|
||||
BUILD_ID = json["ID"].get<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
static void UpdateBuildFields() {
|
||||
jclass buildClass = env->FindClass("android/os/Build");
|
||||
jclass versionClass = env->FindClass("android/os/Build$VERSION");
|
||||
|
||||
for (auto &[key, val]: json.items()) {
|
||||
if (!val.is_string())
|
||||
continue;
|
||||
|
||||
const char *fieldName = key.c_str();
|
||||
|
||||
jfieldID fieldID =
|
||||
env->GetStaticFieldID(buildClass, fieldName, "Ljava/lang/String;");
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionClear();
|
||||
|
||||
fieldID =
|
||||
env->GetStaticFieldID(versionClass, fieldName, "Ljava/lang/String;");
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionClear();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldID != nullptr) {
|
||||
std::string str = val.get<std::string>();
|
||||
const char *value = str.c_str();
|
||||
jstring jValue = env->NewStringUTF(value);
|
||||
|
||||
env->SetStaticObjectField(buildClass, fieldID, jValue);
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionClear();
|
||||
continue;
|
||||
}
|
||||
|
||||
LOGD("Set '%s' to '%s'", fieldName, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static 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);
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
return;
|
||||
}
|
||||
|
||||
LOGD("create class loader");
|
||||
auto dexClClass = env->FindClass("dalvik/system/PathClassLoader");
|
||||
auto dexClInit = env->GetMethodID(
|
||||
dexClClass, "<init>",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V");
|
||||
auto str1 = env->NewStringUTF((dir + "/classes.dex").c_str());
|
||||
auto str2 = env->NewStringUTF(dir.c_str());
|
||||
auto dexCl =
|
||||
env->NewObject(dexClClass, dexClInit, str1, str2, systemClassLoader);
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
return;
|
||||
}
|
||||
|
||||
LOGD("call init");
|
||||
auto entryInit = env->GetStaticMethodID(entryPointClass, "init",
|
||||
"(Ljava/lang/String;ZZ)V");
|
||||
auto jsonStr = env->NewStringUTF(json.dump().c_str());
|
||||
env->CallStaticVoidMethod(entryPointClass, entryInit, jsonStr, spoofProvider,
|
||||
spoofSignature);
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
}
|
||||
|
||||
env->DeleteLocalRef(entryClassName);
|
||||
env->DeleteLocalRef(entryClassObj);
|
||||
env->DeleteLocalRef(jsonStr);
|
||||
env->DeleteLocalRef(dexCl);
|
||||
env->DeleteLocalRef(str1);
|
||||
env->DeleteLocalRef(str2);
|
||||
env->DeleteLocalRef(dexClClass);
|
||||
env->DeleteLocalRef(clClass);
|
||||
|
||||
LOGD("jni memory free");
|
||||
}
|
||||
|
||||
extern "C" [[gnu::visibility("default"), maybe_unused]] bool
|
||||
init(JavaVM *vm, const std::string &gmsDir) {
|
||||
bool close = true;
|
||||
|
||||
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
|
||||
LOGE("[INJECT] JNI_ERR!");
|
||||
return true;
|
||||
}
|
||||
|
||||
dir = gmsDir;
|
||||
LOGD("[INJECT] GMS dir: %s", dir.c_str());
|
||||
|
||||
FILE *f = fopen((dir + "/pif.json").c_str(), "r");
|
||||
json = nlohmann::json::parse(f, nullptr, false, true);
|
||||
fclose(f);
|
||||
|
||||
parseJSON();
|
||||
|
||||
UpdateBuildFields();
|
||||
|
||||
if (spoofProvider || spoofSignature) {
|
||||
injectDex();
|
||||
} else {
|
||||
LOGD("[INJECT] Dex file won't be injected due spoofProvider and "
|
||||
"spoofSignature are false");
|
||||
}
|
||||
|
||||
if (spoofProps) {
|
||||
close = !doHook();
|
||||
}
|
||||
|
||||
LOGD("[INJECT] Done!");
|
||||
|
||||
return close;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
BIN
module/classes.jar
Normal file
BIN
module/classes.jar
Normal file
Binary file not shown.
@ -20,4 +20,5 @@ dependencyResolutionManagement {
|
||||
}
|
||||
|
||||
rootProject.name = "PlayIntegrityFix"
|
||||
include(":app")
|
||||
include(":zygisk")
|
||||
include(":inject")
|
||||
|
1
zygisk/.gitignore
vendored
Normal file
1
zygisk/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
86
zygisk/build.gradle.kts
Normal file
86
zygisk/build.gradle.kts
Normal file
@ -0,0 +1,86 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "es.chiteroman.playintegrityfix"
|
||||
compileSdk = 35
|
||||
|
||||
buildFeatures {
|
||||
prefab = true
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources {
|
||||
excludes += "**"
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
multiDexEnabled = false
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
abiFilters(
|
||||
"arm64-v8a",
|
||||
"armeabi-v7a"
|
||||
)
|
||||
|
||||
arguments(
|
||||
"-DCMAKE_BUILD_TYPE=MinSizeRel",
|
||||
"-DANDROID_STL=none"
|
||||
)
|
||||
|
||||
cFlags(
|
||||
"-std=c23",
|
||||
"-fvisibility=hidden",
|
||||
"-fvisibility-inlines-hidden"
|
||||
)
|
||||
|
||||
cppFlags(
|
||||
"-std=c++26",
|
||||
"-fno-exceptions",
|
||||
"-fno-rtti",
|
||||
"-fvisibility=hidden",
|
||||
"-fvisibility-inlines-hidden"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
multiDexEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path("src/main/cpp/CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.cxx)
|
||||
implementation(libs.hiddenapibypass)
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
tasks.named("assembleRelease") {
|
||||
finalizedBy(
|
||||
rootProject.tasks["copyZygiskFiles"],
|
||||
rootProject.tasks["zip"]
|
||||
)
|
||||
}
|
||||
}
|
15
zygisk/src/main/cpp/CMakeLists.txt
Normal file
15
zygisk/src/main/cpp/CMakeLists.txt
Normal file
@ -0,0 +1,15 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
project("zygisk")
|
||||
|
||||
link_libraries(log)
|
||||
|
||||
find_package(cxx REQUIRED CONFIG)
|
||||
|
||||
link_libraries(cxx::cxx)
|
||||
|
||||
add_library(zygisk SHARED zygisk.cpp)
|
||||
|
||||
add_subdirectory(xdl)
|
||||
|
||||
target_link_libraries(zygisk PRIVATE xdl)
|
21
zygisk/src/main/cpp/xdl/CMakeLists.txt
Normal file
21
zygisk/src/main/cpp/xdl/CMakeLists.txt
Normal file
@ -0,0 +1,21 @@
|
||||
cmake_minimum_required(VERSION 3.22.1)
|
||||
project(xdl)
|
||||
|
||||
file(GLOB XDL_SRC *.c)
|
||||
add_library(xdl STATIC ${XDL_SRC})
|
||||
target_compile_features(xdl PRIVATE c_std_17)
|
||||
target_compile_options(xdl PRIVATE -std=c17)
|
||||
target_include_directories(xdl PUBLIC include .)
|
||||
#target_link_libraries(xdl log)
|
||||
|
||||
if (USEASAN)
|
||||
target_compile_options(xdl PRIVATE -fsanitize=address -fno-omit-frame-pointer)
|
||||
target_link_options(xdl PRIVATE -fsanitize=address)
|
||||
else ()
|
||||
target_compile_options(xdl PRIVATE -Oz -ffunction-sections -fdata-sections)
|
||||
target_link_options(xdl PRIVATE -Oz -Wl,--exclude-libs,ALL -Wl,--gc-sections -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/xdl.map.txt)
|
||||
endif ()
|
||||
|
||||
if ((${ANDROID_ABI} STREQUAL "arm64-v8a") OR (${ANDROID_ABI} STREQUAL "x86_64"))
|
||||
target_link_options(xdl PRIVATE "-Wl,-z,max-page-size=16384")
|
||||
endif ()
|
95
zygisk/src/main/cpp/xdl/include/xdl.h
Normal file
95
zygisk/src/main/cpp/xdl/include/xdl.h
Normal file
@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2020-2024 HexHacking Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
// Created by caikelun on 2020-10-04.
|
||||
|
||||
//
|
||||
// xDL version: 2.2.0
|
||||
//
|
||||
// xDL is an enhanced implementation of the Android DL series functions.
|
||||
// For more information, documentation, and the latest version please check:
|
||||
// https://github.com/hexhacking/xDL
|
||||
//
|
||||
|
||||
#ifndef IO_GITHUB_HEXHACKING_XDL
|
||||
#define IO_GITHUB_HEXHACKING_XDL
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <link.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
// same as Dl_info:
|
||||
const char *dli_fname; // Pathname of shared object that contains address.
|
||||
void *dli_fbase; // Address at which shared object is loaded.
|
||||
const char *dli_sname; // Name of nearest symbol with address lower than addr.
|
||||
void *dli_saddr; // Exact address of symbol named in dli_sname.
|
||||
// added by xDL:
|
||||
size_t dli_ssize; // Symbol size of nearest symbol with address lower than addr.
|
||||
const ElfW(Phdr) *dlpi_phdr; // Pointer to array of ELF program headers for this object.
|
||||
size_t dlpi_phnum; // Number of items in dlpi_phdr.
|
||||
} xdl_info_t;
|
||||
|
||||
//
|
||||
// Default value for flags in xdl_open(), xdl_addr4(), and xdl_iterate_phdr().
|
||||
//
|
||||
#define XDL_DEFAULT 0x00
|
||||
|
||||
//
|
||||
// Enhanced dlopen() / dlclose() / dlsym().
|
||||
//
|
||||
#define XDL_TRY_FORCE_LOAD 0x01
|
||||
#define XDL_ALWAYS_FORCE_LOAD 0x02
|
||||
void *xdl_open(const char *filename, int flags);
|
||||
void *xdl_open2(struct dl_phdr_info *info);
|
||||
void *xdl_close(void *handle);
|
||||
void *xdl_sym(void *handle, const char *symbol, size_t *symbol_size);
|
||||
void *xdl_dsym(void *handle, const char *symbol, size_t *symbol_size);
|
||||
|
||||
//
|
||||
// Enhanced dladdr().
|
||||
//
|
||||
#define XDL_NON_SYM 0x01
|
||||
int xdl_addr(void *addr, xdl_info_t *info, void **cache);
|
||||
int xdl_addr4(void *addr, xdl_info_t *info, void **cache, int flags);
|
||||
void xdl_addr_clean(void **cache);
|
||||
|
||||
//
|
||||
// Enhanced dl_iterate_phdr().
|
||||
//
|
||||
#define XDL_FULL_PATHNAME 0x01
|
||||
int xdl_iterate_phdr(int (*callback)(struct dl_phdr_info *, size_t, void *), void *data, int flags);
|
||||
|
||||
//
|
||||
// Custom dlinfo().
|
||||
//
|
||||
#define XDL_DI_DLINFO 1 // type of info: xdl_info_t
|
||||
int xdl_info(void *handle, int request, void *info);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
1014
zygisk/src/main/cpp/xdl/xdl.c
Normal file
1014
zygisk/src/main/cpp/xdl/xdl.c
Normal file
File diff suppressed because it is too large
Load Diff
16
zygisk/src/main/cpp/xdl/xdl.map.txt
Normal file
16
zygisk/src/main/cpp/xdl/xdl.map.txt
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
global:
|
||||
xdl_open;
|
||||
xdl_open2;
|
||||
xdl_close;
|
||||
xdl_sym;
|
||||
xdl_dsym;
|
||||
xdl_addr;
|
||||
xdl_addr4;
|
||||
xdl_addr_clean;
|
||||
xdl_iterate_phdr;
|
||||
xdl_info;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
297
zygisk/src/main/cpp/xdl/xdl_iterate.c
Normal file
297
zygisk/src/main/cpp/xdl/xdl_iterate.c
Normal file
@ -0,0 +1,297 @@
|
||||
// Copyright (c) 2020-2024 HexHacking Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
// Created by caikelun on 2020-10-04.
|
||||
|
||||
#include "xdl_iterate.h"
|
||||
|
||||
#include <android/api-level.h>
|
||||
#include <ctype.h>
|
||||
#include <dlfcn.h>
|
||||
#include <elf.h>
|
||||
#include <inttypes.h>
|
||||
#include <link.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/auxv.h>
|
||||
|
||||
#include "xdl.h"
|
||||
#include "xdl_linker.h"
|
||||
#include "xdl_util.h"
|
||||
|
||||
/*
|
||||
* =========================================================================================================
|
||||
* API-LEVEL ANDROID-VERSION SOLUTION
|
||||
* =========================================================================================================
|
||||
* 16 4.1 /proc/self/maps
|
||||
* 17 4.2 /proc/self/maps
|
||||
* 18 4.3 /proc/self/maps
|
||||
* 19 4.4 /proc/self/maps
|
||||
* 20 4.4W /proc/self/maps
|
||||
* ---------------------------------------------------------------------------------------------------------
|
||||
* 21 5.0 dl_iterate_phdr() + __dl__ZL10g_dl_mutex + linker/linker64 from getauxval(3)
|
||||
* 22 5.1 dl_iterate_phdr() + __dl__ZL10g_dl_mutex + linker/linker64 from getauxval(3)
|
||||
* ---------------------------------------------------------------------------------------------------------
|
||||
* 23 >= 6.0 dl_iterate_phdr() + linker/linker64 from getauxval(3)
|
||||
* =========================================================================================================
|
||||
*/
|
||||
|
||||
extern __attribute((weak)) int dl_iterate_phdr(int (*)(struct dl_phdr_info *, size_t, void *), void *);
|
||||
extern __attribute((weak)) unsigned long int getauxval(unsigned long int);
|
||||
|
||||
static uintptr_t xdl_iterate_get_min_vaddr(struct dl_phdr_info *info) {
|
||||
uintptr_t min_vaddr = UINTPTR_MAX;
|
||||
for (size_t i = 0; i < info->dlpi_phnum; i++) {
|
||||
const ElfW(Phdr) *phdr = &(info->dlpi_phdr[i]);
|
||||
if (PT_LOAD == phdr->p_type) {
|
||||
if (min_vaddr > phdr->p_vaddr) min_vaddr = phdr->p_vaddr;
|
||||
}
|
||||
}
|
||||
return min_vaddr;
|
||||
}
|
||||
|
||||
static int xdl_iterate_open_or_rewind_maps(FILE **maps) {
|
||||
if (NULL == *maps) {
|
||||
*maps = fopen("/proc/self/maps", "r");
|
||||
if (NULL == *maps) return -1;
|
||||
} else
|
||||
rewind(*maps);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int xdl_iterate_get_pathname_from_maps(uintptr_t base, char *buf, size_t buf_len, FILE **maps) {
|
||||
// open or rewind maps-file
|
||||
if (0 != xdl_iterate_open_or_rewind_maps(maps)) return -1; // failed
|
||||
|
||||
char line[1024];
|
||||
while (fgets(line, sizeof(line), *maps)) {
|
||||
// check base address
|
||||
uintptr_t start, end;
|
||||
if (2 != sscanf(line, "%" SCNxPTR "-%" SCNxPTR " r", &start, &end)) continue;
|
||||
if (base < start) break; // failed
|
||||
if (base >= end) continue;
|
||||
|
||||
// get pathname
|
||||
char *pathname = strchr(line, '/');
|
||||
if (NULL == pathname) break; // failed
|
||||
xdl_util_trim_ending(pathname);
|
||||
|
||||
// found it
|
||||
strlcpy(buf, pathname, buf_len);
|
||||
return 0; // OK
|
||||
}
|
||||
|
||||
return -1; // failed
|
||||
}
|
||||
|
||||
static int xdl_iterate_by_linker_cb(struct dl_phdr_info *info, size_t size, void *arg) {
|
||||
uintptr_t *pkg = (uintptr_t *)arg;
|
||||
xdl_iterate_phdr_cb_t cb = (xdl_iterate_phdr_cb_t)*pkg++;
|
||||
void *cb_arg = (void *)*pkg++;
|
||||
FILE **maps = (FILE **)*pkg++;
|
||||
uintptr_t linker_load_bias = *pkg++;
|
||||
int flags = (int)*pkg;
|
||||
|
||||
// ignore invalid ELF
|
||||
if (0 == info->dlpi_addr || NULL == info->dlpi_name || '\0' == info->dlpi_name[0]) return 0;
|
||||
|
||||
// ignore linker if we have returned it already
|
||||
if (linker_load_bias == info->dlpi_addr) return 0;
|
||||
|
||||
struct dl_phdr_info info_fixed;
|
||||
info_fixed.dlpi_addr = info->dlpi_addr;
|
||||
info_fixed.dlpi_name = info->dlpi_name;
|
||||
info_fixed.dlpi_phdr = info->dlpi_phdr;
|
||||
info_fixed.dlpi_phnum = info->dlpi_phnum;
|
||||
info = &info_fixed;
|
||||
|
||||
// fix dlpi_phdr & dlpi_phnum (from memory)
|
||||
if (NULL == info->dlpi_phdr || 0 == info->dlpi_phnum) {
|
||||
ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)info->dlpi_addr;
|
||||
info->dlpi_phdr = (ElfW(Phdr) *)(info->dlpi_addr + ehdr->e_phoff);
|
||||
info->dlpi_phnum = ehdr->e_phnum;
|
||||
}
|
||||
|
||||
// fix dlpi_name (from /proc/self/maps)
|
||||
if ('/' != info->dlpi_name[0] && '[' != info->dlpi_name[0] && (0 != (flags & XDL_FULL_PATHNAME))) {
|
||||
// get base address
|
||||
uintptr_t min_vaddr = xdl_iterate_get_min_vaddr(info);
|
||||
if (UINTPTR_MAX == min_vaddr) return 0; // ignore this ELF
|
||||
uintptr_t base = (uintptr_t)(info->dlpi_addr + min_vaddr);
|
||||
|
||||
char buf[1024];
|
||||
if (0 != xdl_iterate_get_pathname_from_maps(base, buf, sizeof(buf), maps)) return 0; // ignore this ELF
|
||||
|
||||
info->dlpi_name = (const char *)buf;
|
||||
}
|
||||
|
||||
// callback
|
||||
return cb(info, size, cb_arg);
|
||||
}
|
||||
|
||||
static uintptr_t xdl_iterate_get_linker_base(void) {
|
||||
if (NULL == getauxval) return 0;
|
||||
|
||||
uintptr_t base = (uintptr_t)getauxval(AT_BASE);
|
||||
if (0 == base) return 0;
|
||||
if (0 != memcmp((void *)base, ELFMAG, SELFMAG)) return 0;
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
static int xdl_iterate_do_callback(xdl_iterate_phdr_cb_t cb, void *cb_arg, uintptr_t base,
|
||||
const char *pathname, uintptr_t *load_bias) {
|
||||
ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)base;
|
||||
|
||||
struct dl_phdr_info info;
|
||||
info.dlpi_name = pathname;
|
||||
info.dlpi_phdr = (const ElfW(Phdr) *)(base + ehdr->e_phoff);
|
||||
info.dlpi_phnum = ehdr->e_phnum;
|
||||
|
||||
// get load bias
|
||||
uintptr_t min_vaddr = xdl_iterate_get_min_vaddr(&info);
|
||||
if (UINTPTR_MAX == min_vaddr) return 0; // ignore invalid ELF
|
||||
info.dlpi_addr = (ElfW(Addr))(base - min_vaddr);
|
||||
if (NULL != load_bias) *load_bias = info.dlpi_addr;
|
||||
|
||||
return cb(&info, sizeof(struct dl_phdr_info), cb_arg);
|
||||
}
|
||||
|
||||
static int xdl_iterate_by_linker(xdl_iterate_phdr_cb_t cb, void *cb_arg, int flags) {
|
||||
if (NULL == dl_iterate_phdr) return 0;
|
||||
|
||||
int api_level = xdl_util_get_api_level();
|
||||
FILE *maps = NULL;
|
||||
int r;
|
||||
|
||||
// dl_iterate_phdr(3) does NOT contain linker/linker64 when Android version < 8.1 (API level 27).
|
||||
// Here we always try to get linker base address from auxv.
|
||||
uintptr_t linker_load_bias = 0;
|
||||
uintptr_t linker_base = xdl_iterate_get_linker_base();
|
||||
if (0 != linker_base) {
|
||||
if (0 !=
|
||||
(r = xdl_iterate_do_callback(cb, cb_arg, linker_base, XDL_UTIL_LINKER_PATHNAME, &linker_load_bias)))
|
||||
return r;
|
||||
}
|
||||
|
||||
// for other ELF
|
||||
uintptr_t pkg[5] = {(uintptr_t)cb, (uintptr_t)cb_arg, (uintptr_t)&maps, linker_load_bias, (uintptr_t)flags};
|
||||
if (__ANDROID_API_L__ == api_level || __ANDROID_API_L_MR1__ == api_level) xdl_linker_lock();
|
||||
r = dl_iterate_phdr(xdl_iterate_by_linker_cb, pkg);
|
||||
if (__ANDROID_API_L__ == api_level || __ANDROID_API_L_MR1__ == api_level) xdl_linker_unlock();
|
||||
|
||||
if (NULL != maps) fclose(maps);
|
||||
return r;
|
||||
}
|
||||
|
||||
#if (defined(__arm__) || defined(__i386__)) && __ANDROID_API__ < __ANDROID_API_L__
|
||||
static int xdl_iterate_by_maps(xdl_iterate_phdr_cb_t cb, void *cb_arg) {
|
||||
FILE *maps = fopen("/proc/self/maps", "r");
|
||||
if (NULL == maps) return 0;
|
||||
|
||||
int r = 0;
|
||||
char buf1[1024], buf2[1024];
|
||||
char *line = buf1;
|
||||
uintptr_t prev_base = 0;
|
||||
bool try_next_line = false;
|
||||
|
||||
while (fgets(line, sizeof(buf1), maps)) {
|
||||
// Try to find an ELF which loaded by linker.
|
||||
uintptr_t base, offset;
|
||||
char exec;
|
||||
if (3 != sscanf(line, "%" SCNxPTR "-%*" SCNxPTR " r%*c%cp %" SCNxPTR " ", &base, &exec, &offset))
|
||||
goto clean;
|
||||
|
||||
if ('-' == exec && 0 == offset) {
|
||||
// r--p
|
||||
prev_base = base;
|
||||
line = (line == buf1 ? buf2 : buf1);
|
||||
try_next_line = true;
|
||||
continue;
|
||||
} else if (exec == 'x') {
|
||||
// r-xp
|
||||
char *pathname = NULL;
|
||||
if (try_next_line && 0 != offset) {
|
||||
char *prev = (line == buf1 ? buf2 : buf1);
|
||||
char *prev_pathname = strchr(prev, '/');
|
||||
if (NULL == prev_pathname) goto clean;
|
||||
|
||||
pathname = strchr(line, '/');
|
||||
if (NULL == pathname) goto clean;
|
||||
|
||||
xdl_util_trim_ending(prev_pathname);
|
||||
xdl_util_trim_ending(pathname);
|
||||
if (0 != strcmp(prev_pathname, pathname)) goto clean;
|
||||
|
||||
// we found the line with r-xp in the next line
|
||||
base = prev_base;
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
if (0 != offset) goto clean;
|
||||
|
||||
// get pathname
|
||||
if (NULL == pathname) {
|
||||
pathname = strchr(line, '/');
|
||||
if (NULL == pathname) goto clean;
|
||||
xdl_util_trim_ending(pathname);
|
||||
}
|
||||
|
||||
if (0 != memcmp((void *)base, ELFMAG, SELFMAG)) goto clean;
|
||||
ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)base;
|
||||
struct dl_phdr_info info;
|
||||
info.dlpi_name = pathname;
|
||||
info.dlpi_phdr = (const ElfW(Phdr) *)(base + ehdr->e_phoff);
|
||||
info.dlpi_phnum = ehdr->e_phnum;
|
||||
|
||||
// callback
|
||||
if (0 != (r = xdl_iterate_do_callback(cb, cb_arg, base, pathname, NULL))) break;
|
||||
}
|
||||
|
||||
clean:
|
||||
try_next_line = false;
|
||||
}
|
||||
|
||||
fclose(maps);
|
||||
return r;
|
||||
}
|
||||
#endif
|
||||
|
||||
int xdl_iterate_phdr_impl(xdl_iterate_phdr_cb_t cb, void *cb_arg, int flags) {
|
||||
// iterate by /proc/self/maps in Android 4.x (Android 4.x only supports arm32 and x86)
|
||||
#if (defined(__arm__) || defined(__i386__)) && __ANDROID_API__ < __ANDROID_API_L__
|
||||
if (xdl_util_get_api_level() < __ANDROID_API_L__) return xdl_iterate_by_maps(cb, cb_arg);
|
||||
#endif
|
||||
|
||||
// iterate by dl_iterate_phdr()
|
||||
return xdl_iterate_by_linker(cb, cb_arg, flags);
|
||||
}
|
||||
|
||||
int xdl_iterate_get_full_pathname(uintptr_t base, char *buf, size_t buf_len) {
|
||||
FILE *maps = NULL;
|
||||
int r = xdl_iterate_get_pathname_from_maps(base, buf, buf_len, &maps);
|
||||
if (NULL != maps) fclose(maps);
|
||||
return r;
|
||||
}
|
43
zygisk/src/main/cpp/xdl/xdl_iterate.h
Normal file
43
zygisk/src/main/cpp/xdl/xdl_iterate.h
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2020-2024 HexHacking Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
// Created by caikelun on 2020-10-04.
|
||||
|
||||
#ifndef IO_GITHUB_HEXHACKING_XDL_ITERATE
|
||||
#define IO_GITHUB_HEXHACKING_XDL_ITERATE
|
||||
|
||||
#include <link.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef int (*xdl_iterate_phdr_cb_t)(struct dl_phdr_info *info, size_t size, void *arg);
|
||||
int xdl_iterate_phdr_impl(xdl_iterate_phdr_cb_t cb, void *cb_arg, int flags);
|
||||
|
||||
int xdl_iterate_get_full_pathname(uintptr_t base, char *buf, size_t buf_len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
231
zygisk/src/main/cpp/xdl/xdl_linker.c
Normal file
231
zygisk/src/main/cpp/xdl/xdl_linker.c
Normal file
@ -0,0 +1,231 @@
|
||||
// Copyright (c) 2020-2024 HexHacking Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
// Created by caikelun on 2021-02-21.
|
||||
|
||||
#include "xdl_linker.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "xdl.h"
|
||||
#include "xdl_iterate.h"
|
||||
#include "xdl_util.h"
|
||||
|
||||
#define XDL_LINKER_SYM_MUTEX "__dl__ZL10g_dl_mutex"
|
||||
#define XDL_LINKER_SYM_DLOPEN_EXT_N "__dl__ZL10dlopen_extPKciPK17android_dlextinfoPv"
|
||||
#define XDL_LINKER_SYM_DO_DLOPEN_N "__dl__Z9do_dlopenPKciPK17android_dlextinfoPv"
|
||||
#define XDL_LINKER_SYM_DLOPEN_O "__dl__Z8__dlopenPKciPKv"
|
||||
#define XDL_LINKER_SYM_LOADER_DLOPEN_P "__loader_dlopen"
|
||||
|
||||
#ifndef __LP64__
|
||||
#define LIB "lib"
|
||||
#else
|
||||
#define LIB "lib64"
|
||||
#endif
|
||||
|
||||
typedef void *(*xdl_linker_dlopen_n_t)(const char *, int, const void *, void *);
|
||||
typedef void *(*xdl_linker_dlopen_o_t)(const char *, int, const void *);
|
||||
|
||||
static pthread_mutex_t *xdl_linker_mutex = NULL;
|
||||
static void *xdl_linker_dlopen = NULL;
|
||||
|
||||
typedef enum { MATCH_PREFIX, MATCH_SUFFIX } xdl_linker_match_type_t;
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wpadded"
|
||||
typedef struct {
|
||||
xdl_linker_match_type_t type;
|
||||
const char *value;
|
||||
} xdl_linker_match_t;
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
typedef struct {
|
||||
void *addr;
|
||||
xdl_linker_match_t *matches;
|
||||
size_t matches_cursor;
|
||||
} xdl_linker_caller_t;
|
||||
|
||||
// https://source.android.com/docs/core/architecture/vndk/linker-namespace
|
||||
// The following rules are loose and incomplete, you can add more according to your needs.
|
||||
static xdl_linker_match_t xdl_linker_match_default[] = {{MATCH_SUFFIX, "/libc.so"}};
|
||||
static xdl_linker_match_t xdl_linker_match_art[] = {{MATCH_SUFFIX, "/libart.so"}};
|
||||
static xdl_linker_match_t xdl_linker_match_sphal[] = {{MATCH_PREFIX, "/vendor/" LIB "/egl/"},
|
||||
{MATCH_PREFIX, "/vendor/" LIB "/hw/"},
|
||||
{MATCH_PREFIX, "/vendor/" LIB "/"},
|
||||
{MATCH_PREFIX, "/odm/" LIB "/"}};
|
||||
static xdl_linker_match_t xdl_linker_match_vndk[] = {{MATCH_PREFIX, "/apex/com.android.vndk.v"},
|
||||
{MATCH_PREFIX, "/vendor/" LIB "/vndk-sp/"},
|
||||
{MATCH_PREFIX, "/odm/" LIB "/vndk-sp/"}};
|
||||
static xdl_linker_caller_t xdl_linker_callers[] = {
|
||||
{NULL, xdl_linker_match_default, sizeof(xdl_linker_match_default) / sizeof(xdl_linker_match_t)},
|
||||
{NULL, xdl_linker_match_art, sizeof(xdl_linker_match_art) / sizeof(xdl_linker_match_t)},
|
||||
{NULL, xdl_linker_match_sphal, sizeof(xdl_linker_match_sphal) / sizeof(xdl_linker_match_t)},
|
||||
{NULL, xdl_linker_match_vndk, sizeof(xdl_linker_match_vndk) / sizeof(xdl_linker_match_t)}};
|
||||
|
||||
static void xdl_linker_init_symbols_impl(void) {
|
||||
// find linker from: /proc/self/maps (API level < 18) or getauxval (API level >= 18)
|
||||
void *handle = xdl_open(XDL_UTIL_LINKER_BASENAME, XDL_DEFAULT);
|
||||
if (NULL == handle) return;
|
||||
|
||||
int api_level = xdl_util_get_api_level();
|
||||
if (__ANDROID_API_L__ == api_level || __ANDROID_API_L_MR1__ == api_level) {
|
||||
// == Android 5.x
|
||||
xdl_linker_mutex = (pthread_mutex_t *)xdl_dsym(handle, XDL_LINKER_SYM_MUTEX, NULL);
|
||||
} else if (__ANDROID_API_N__ == api_level || __ANDROID_API_N_MR1__ == api_level) {
|
||||
// == Android 7.x
|
||||
xdl_linker_dlopen = xdl_dsym(handle, XDL_LINKER_SYM_DLOPEN_EXT_N, NULL);
|
||||
if (NULL == xdl_linker_dlopen) {
|
||||
xdl_linker_dlopen = xdl_dsym(handle, XDL_LINKER_SYM_DO_DLOPEN_N, NULL);
|
||||
xdl_linker_mutex = (pthread_mutex_t *)xdl_dsym(handle, XDL_LINKER_SYM_MUTEX, NULL);
|
||||
}
|
||||
} else if (__ANDROID_API_O__ == api_level || __ANDROID_API_O_MR1__ == api_level) {
|
||||
// == Android 8.x
|
||||
xdl_linker_dlopen = xdl_dsym(handle, XDL_LINKER_SYM_DLOPEN_O, NULL);
|
||||
} else if (api_level >= __ANDROID_API_P__) {
|
||||
// >= Android 9.0
|
||||
xdl_linker_dlopen = xdl_sym(handle, XDL_LINKER_SYM_LOADER_DLOPEN_P, NULL);
|
||||
}
|
||||
|
||||
xdl_close(handle);
|
||||
}
|
||||
|
||||
static void xdl_linker_init_symbols(void) {
|
||||
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static bool inited = false;
|
||||
if (!inited) {
|
||||
pthread_mutex_lock(&lock);
|
||||
if (!inited) {
|
||||
xdl_linker_init_symbols_impl();
|
||||
inited = true;
|
||||
}
|
||||
pthread_mutex_unlock(&lock);
|
||||
}
|
||||
}
|
||||
|
||||
void xdl_linker_lock(void) {
|
||||
xdl_linker_init_symbols();
|
||||
|
||||
if (NULL != xdl_linker_mutex) pthread_mutex_lock(xdl_linker_mutex);
|
||||
}
|
||||
|
||||
void xdl_linker_unlock(void) {
|
||||
if (NULL != xdl_linker_mutex) pthread_mutex_unlock(xdl_linker_mutex);
|
||||
}
|
||||
|
||||
static void *xdl_linker_get_caller_addr(struct dl_phdr_info *info) {
|
||||
for (size_t i = 0; i < info->dlpi_phnum; i++) {
|
||||
const ElfW(Phdr) *phdr = &(info->dlpi_phdr[i]);
|
||||
if (PT_LOAD == phdr->p_type) {
|
||||
return (void *)(info->dlpi_addr + phdr->p_vaddr);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void xdl_linker_save_caller_addr(struct dl_phdr_info *info, xdl_linker_caller_t *caller,
|
||||
size_t cursor) {
|
||||
void *addr = xdl_linker_get_caller_addr(info);
|
||||
if (NULL != addr) {
|
||||
caller->addr = addr;
|
||||
caller->matches_cursor = cursor;
|
||||
}
|
||||
}
|
||||
|
||||
static int xdl_linker_get_caller_addr_cb(struct dl_phdr_info *info, size_t size, void *arg) {
|
||||
(void)size, (void)arg;
|
||||
if (0 == info->dlpi_addr || NULL == info->dlpi_name) return 0; // continue
|
||||
|
||||
int ret = 1; // OK
|
||||
for (size_t i = 0; i < sizeof(xdl_linker_callers) / sizeof(xdl_linker_callers[0]); i++) {
|
||||
xdl_linker_caller_t *caller = &xdl_linker_callers[i];
|
||||
for (size_t j = 0; j < caller->matches_cursor; j++) {
|
||||
xdl_linker_match_t *match = &caller->matches[j];
|
||||
if (MATCH_PREFIX == match->type) {
|
||||
if (xdl_util_starts_with(info->dlpi_name, match->value)) {
|
||||
xdl_linker_save_caller_addr(info, caller, j);
|
||||
}
|
||||
} else if (MATCH_SUFFIX == match->type) {
|
||||
if (xdl_util_ends_with(info->dlpi_name, match->value)) {
|
||||
xdl_linker_save_caller_addr(info, caller, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (NULL == caller->addr || 0 != caller->matches_cursor) ret = 0; // continue
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void xdl_linker_init_caller_addr_impl(void) {
|
||||
xdl_iterate_phdr_impl(xdl_linker_get_caller_addr_cb, NULL, XDL_DEFAULT);
|
||||
}
|
||||
|
||||
static void xdl_linker_init_caller_addr(void) {
|
||||
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static bool inited = false;
|
||||
if (!inited) {
|
||||
pthread_mutex_lock(&lock);
|
||||
if (!inited) {
|
||||
xdl_linker_init_caller_addr_impl();
|
||||
inited = true;
|
||||
}
|
||||
pthread_mutex_unlock(&lock);
|
||||
}
|
||||
}
|
||||
|
||||
void *xdl_linker_force_dlopen(const char *filename) {
|
||||
int api_level = xdl_util_get_api_level();
|
||||
|
||||
if (api_level <= __ANDROID_API_M__) {
|
||||
// <= Android 6.0
|
||||
return dlopen(filename, RTLD_NOW);
|
||||
} else {
|
||||
xdl_linker_init_symbols();
|
||||
if (NULL == xdl_linker_dlopen) return NULL;
|
||||
xdl_linker_init_caller_addr();
|
||||
|
||||
void *handle = NULL;
|
||||
if (__ANDROID_API_N__ == api_level || __ANDROID_API_N_MR1__ == api_level) {
|
||||
// == Android 7.x
|
||||
xdl_linker_lock();
|
||||
for (size_t i = 0; i < sizeof(xdl_linker_callers) / sizeof(xdl_linker_callers[0]); i++) {
|
||||
xdl_linker_caller_t *caller = &xdl_linker_callers[i];
|
||||
if (NULL != caller->addr) {
|
||||
handle = ((xdl_linker_dlopen_n_t)xdl_linker_dlopen)(filename, RTLD_NOW, NULL, caller->addr);
|
||||
if (NULL != handle) break;
|
||||
}
|
||||
}
|
||||
xdl_linker_unlock();
|
||||
} else {
|
||||
// >= Android 8.0
|
||||
for (size_t i = 0; i < sizeof(xdl_linker_callers) / sizeof(xdl_linker_callers[0]); i++) {
|
||||
xdl_linker_caller_t *caller = &xdl_linker_callers[i];
|
||||
if (NULL != caller->addr) {
|
||||
handle = ((xdl_linker_dlopen_o_t)xdl_linker_dlopen)(filename, RTLD_NOW, caller->addr);
|
||||
if (NULL != handle) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
}
|
40
zygisk/src/main/cpp/xdl/xdl_linker.h
Normal file
40
zygisk/src/main/cpp/xdl/xdl_linker.h
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright (c) 2020-2024 HexHacking Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
// Created by caikelun on 2021-02-21.
|
||||
|
||||
#ifndef IO_GITHUB_HEXHACKING_XDL_LINKER
|
||||
#define IO_GITHUB_HEXHACKING_XDL_LINKER
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void xdl_linker_lock(void);
|
||||
void xdl_linker_unlock(void);
|
||||
|
||||
void *xdl_linker_force_dlopen(const char *filename);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
187
zygisk/src/main/cpp/xdl/xdl_lzma.c
Normal file
187
zygisk/src/main/cpp/xdl/xdl_lzma.c
Normal file
@ -0,0 +1,187 @@
|
||||
// Copyright (c) 2020-2024 HexHacking Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
// Created by caikelun on 2020-11-08.
|
||||
|
||||
#include "xdl_lzma.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "xdl.h"
|
||||
#include "xdl_util.h"
|
||||
|
||||
// LZMA library pathname & symbol names
|
||||
#ifndef __LP64__
|
||||
#define XDL_LZMA_PATHNAME "/system/lib/liblzma.so"
|
||||
#else
|
||||
#define XDL_LZMA_PATHNAME "/system/lib64/liblzma.so"
|
||||
#endif
|
||||
#define XDL_LZMA_SYM_CRCGEN "CrcGenerateTable"
|
||||
#define XDL_LZMA_SYM_CRC64GEN "Crc64GenerateTable"
|
||||
#define XDL_LZMA_SYM_CONSTRUCT "XzUnpacker_Construct"
|
||||
#define XDL_LZMA_SYM_ISFINISHED "XzUnpacker_IsStreamWasFinished"
|
||||
#define XDL_LZMA_SYM_FREE "XzUnpacker_Free"
|
||||
#define XDL_LZMA_SYM_CODE "XzUnpacker_Code"
|
||||
|
||||
// LZMA data type definition
|
||||
#define SZ_OK 0
|
||||
typedef struct ISzAlloc ISzAlloc;
|
||||
typedef const ISzAlloc *ISzAllocPtr;
|
||||
struct ISzAlloc {
|
||||
void *(*Alloc)(ISzAllocPtr p, size_t size);
|
||||
void (*Free)(ISzAllocPtr p, void *address); /* address can be 0 */
|
||||
};
|
||||
typedef enum {
|
||||
CODER_STATUS_NOT_SPECIFIED, /* use main error code instead */
|
||||
CODER_STATUS_FINISHED_WITH_MARK, /* stream was finished with end mark. */
|
||||
CODER_STATUS_NOT_FINISHED, /* stream was not finished */
|
||||
CODER_STATUS_NEEDS_MORE_INPUT /* you must provide more input bytes */
|
||||
} ECoderStatus;
|
||||
typedef enum {
|
||||
CODER_FINISH_ANY, /* finish at any point */
|
||||
CODER_FINISH_END /* block must be finished at the end */
|
||||
} ECoderFinishMode;
|
||||
|
||||
// LZMA function type definition
|
||||
typedef void (*xdl_lzma_crcgen_t)(void);
|
||||
typedef void (*xdl_lzma_crc64gen_t)(void);
|
||||
typedef void (*xdl_lzma_construct_t)(void *, ISzAllocPtr);
|
||||
typedef int (*xdl_lzma_isfinished_t)(const void *);
|
||||
typedef void (*xdl_lzma_free_t)(void *);
|
||||
typedef int (*xdl_lzma_code_t)(void *, uint8_t *, size_t *, const uint8_t *, size_t *, ECoderFinishMode,
|
||||
ECoderStatus *);
|
||||
typedef int (*xdl_lzma_code_q_t)(void *, uint8_t *, size_t *, const uint8_t *, size_t *, int,
|
||||
ECoderFinishMode, ECoderStatus *);
|
||||
|
||||
// LZMA function pointor
|
||||
static xdl_lzma_construct_t xdl_lzma_construct = NULL;
|
||||
static xdl_lzma_isfinished_t xdl_lzma_isfinished = NULL;
|
||||
static xdl_lzma_free_t xdl_lzma_free = NULL;
|
||||
static void *xdl_lzma_code = NULL;
|
||||
|
||||
// LZMA init
|
||||
static void xdl_lzma_init(void) {
|
||||
void *lzma = xdl_open(XDL_LZMA_PATHNAME, XDL_TRY_FORCE_LOAD);
|
||||
if (NULL == lzma) return;
|
||||
|
||||
xdl_lzma_crcgen_t crcgen = NULL;
|
||||
xdl_lzma_crc64gen_t crc64gen = NULL;
|
||||
if (NULL == (crcgen = (xdl_lzma_crcgen_t)xdl_sym(lzma, XDL_LZMA_SYM_CRCGEN, NULL))) goto end;
|
||||
if (NULL == (crc64gen = (xdl_lzma_crc64gen_t)xdl_sym(lzma, XDL_LZMA_SYM_CRC64GEN, NULL))) goto end;
|
||||
if (NULL == (xdl_lzma_construct = (xdl_lzma_construct_t)xdl_sym(lzma, XDL_LZMA_SYM_CONSTRUCT, NULL)))
|
||||
goto end;
|
||||
if (NULL == (xdl_lzma_isfinished = (xdl_lzma_isfinished_t)xdl_sym(lzma, XDL_LZMA_SYM_ISFINISHED, NULL)))
|
||||
goto end;
|
||||
if (NULL == (xdl_lzma_free = (xdl_lzma_free_t)xdl_sym(lzma, XDL_LZMA_SYM_FREE, NULL))) goto end;
|
||||
if (NULL == (xdl_lzma_code = xdl_sym(lzma, XDL_LZMA_SYM_CODE, NULL))) goto end;
|
||||
crcgen();
|
||||
crc64gen();
|
||||
|
||||
end:
|
||||
xdl_close(lzma);
|
||||
}
|
||||
|
||||
// LZMA internal alloc / free
|
||||
static void *xdl_lzma_internal_alloc(ISzAllocPtr p, size_t size) {
|
||||
(void)p;
|
||||
return malloc(size);
|
||||
}
|
||||
static void xdl_lzma_internal_free(ISzAllocPtr p, void *address) {
|
||||
(void)p;
|
||||
free(address);
|
||||
}
|
||||
|
||||
int xdl_lzma_decompress(uint8_t *src, size_t src_size, uint8_t **dst, size_t *dst_size) {
|
||||
size_t src_offset = 0;
|
||||
size_t dst_offset = 0;
|
||||
size_t src_remaining;
|
||||
size_t dst_remaining;
|
||||
ISzAlloc alloc = {.Alloc = xdl_lzma_internal_alloc, .Free = xdl_lzma_internal_free};
|
||||
long long state[4096 / sizeof(long long)]; // must be enough, 8-bit aligned
|
||||
ECoderStatus status;
|
||||
int api_level = xdl_util_get_api_level();
|
||||
|
||||
// init and check
|
||||
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static bool inited = false;
|
||||
if (!inited) {
|
||||
pthread_mutex_lock(&lock);
|
||||
if (!inited) {
|
||||
xdl_lzma_init();
|
||||
inited = true;
|
||||
}
|
||||
pthread_mutex_unlock(&lock);
|
||||
}
|
||||
if (NULL == xdl_lzma_code) return -1;
|
||||
|
||||
xdl_lzma_construct(&state, &alloc);
|
||||
|
||||
*dst_size = 2 * src_size;
|
||||
*dst = NULL;
|
||||
do {
|
||||
*dst_size *= 2;
|
||||
if (NULL == (*dst = realloc(*dst, *dst_size))) {
|
||||
xdl_lzma_free(&state);
|
||||
return -1;
|
||||
}
|
||||
|
||||
src_remaining = src_size - src_offset;
|
||||
dst_remaining = *dst_size - dst_offset;
|
||||
|
||||
int result;
|
||||
if (api_level >= __ANDROID_API_Q__) {
|
||||
xdl_lzma_code_q_t lzma_code_q = (xdl_lzma_code_q_t)xdl_lzma_code;
|
||||
result = lzma_code_q(&state, *dst + dst_offset, &dst_remaining, src + src_offset, &src_remaining, 1,
|
||||
CODER_FINISH_ANY, &status);
|
||||
} else {
|
||||
xdl_lzma_code_t lzma_code = (xdl_lzma_code_t)xdl_lzma_code;
|
||||
result = lzma_code(&state, *dst + dst_offset, &dst_remaining, src + src_offset, &src_remaining,
|
||||
CODER_FINISH_ANY, &status);
|
||||
}
|
||||
if (SZ_OK != result) {
|
||||
free(*dst);
|
||||
xdl_lzma_free(&state);
|
||||
return -1;
|
||||
}
|
||||
|
||||
src_offset += src_remaining;
|
||||
dst_offset += dst_remaining;
|
||||
} while (status == CODER_STATUS_NOT_FINISHED);
|
||||
|
||||
xdl_lzma_free(&state);
|
||||
|
||||
if (!xdl_lzma_isfinished(&state)) {
|
||||
free(*dst);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*dst_size = dst_offset;
|
||||
*dst = realloc(*dst, *dst_size);
|
||||
return 0;
|
||||
}
|
40
zygisk/src/main/cpp/xdl/xdl_lzma.h
Normal file
40
zygisk/src/main/cpp/xdl/xdl_lzma.h
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright (c) 2020-2024 HexHacking Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
// Created by caikelun on 2020-11-08.
|
||||
|
||||
#ifndef IO_GITHUB_HEXHACKING_XDL_LZMA
|
||||
#define IO_GITHUB_HEXHACKING_XDL_LZMA
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
int xdl_lzma_decompress(uint8_t *src, size_t src_size, uint8_t **dst, size_t *dst_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
95
zygisk/src/main/cpp/xdl/xdl_util.c
Normal file
95
zygisk/src/main/cpp/xdl/xdl_util.c
Normal file
@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2020-2024 HexHacking Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
// Created by caikelun on 2020-10-04.
|
||||
|
||||
#include "xdl_util.h"
|
||||
|
||||
#include <android/api-level.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
bool xdl_util_starts_with(const char *str, const char *start) {
|
||||
while (*str && *str == *start) {
|
||||
str++;
|
||||
start++;
|
||||
}
|
||||
|
||||
return '\0' == *start;
|
||||
}
|
||||
|
||||
bool xdl_util_ends_with(const char *str, const char *ending) {
|
||||
size_t str_len = strlen(str);
|
||||
size_t ending_len = strlen(ending);
|
||||
|
||||
if (ending_len > str_len) return false;
|
||||
|
||||
return 0 == strcmp(str + (str_len - ending_len), ending);
|
||||
}
|
||||
|
||||
size_t xdl_util_trim_ending(char *start) {
|
||||
char *end = start + strlen(start);
|
||||
while (start < end && isspace((int)(*(end - 1)))) {
|
||||
end--;
|
||||
*end = '\0';
|
||||
}
|
||||
return (size_t)(end - start);
|
||||
}
|
||||
|
||||
static int xdl_util_get_api_level_from_build_prop(void) {
|
||||
char buf[128];
|
||||
int api_level = -1;
|
||||
|
||||
FILE *fp = fopen("/system/build.prop", "r");
|
||||
if (NULL == fp) goto end;
|
||||
|
||||
while (fgets(buf, sizeof(buf), fp)) {
|
||||
if (xdl_util_starts_with(buf, "ro.build.version.sdk=")) {
|
||||
api_level = atoi(buf + 21);
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
end:
|
||||
return (api_level > 0) ? api_level : -1;
|
||||
}
|
||||
|
||||
int xdl_util_get_api_level(void) {
|
||||
static int xdl_util_api_level = -1;
|
||||
|
||||
if (xdl_util_api_level < 0) {
|
||||
int api_level = android_get_device_api_level();
|
||||
if (api_level < 0)
|
||||
api_level = xdl_util_get_api_level_from_build_prop(); // compatible with unusual models
|
||||
if (api_level < __ANDROID_API_J__) api_level = __ANDROID_API_J__;
|
||||
|
||||
__atomic_store_n(&xdl_util_api_level, api_level, __ATOMIC_SEQ_CST);
|
||||
}
|
||||
|
||||
return xdl_util_api_level;
|
||||
}
|
71
zygisk/src/main/cpp/xdl/xdl_util.h
Normal file
71
zygisk/src/main/cpp/xdl/xdl_util.h
Normal file
@ -0,0 +1,71 @@
|
||||
// Copyright (c) 2020-2024 HexHacking Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
// Created by caikelun on 2020-10-04.
|
||||
|
||||
#ifndef IO_GITHUB_HEXHACKING_XDL_UTIL
|
||||
#define IO_GITHUB_HEXHACKING_XDL_UTIL
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifndef __LP64__
|
||||
#define XDL_UTIL_LINKER_BASENAME "linker"
|
||||
#define XDL_UTIL_LINKER_PATHNAME "/system/bin/linker"
|
||||
#define XDL_UTIL_APP_PROCESS_BASENAME "app_process32"
|
||||
#define XDL_UTIL_APP_PROCESS_PATHNAME "/system/bin/app_process32"
|
||||
#define XDL_UTIL_APP_PROCESS_BASENAME_K "app_process"
|
||||
#define XDL_UTIL_APP_PROCESS_PATHNAME_K "/system/bin/app_process"
|
||||
#else
|
||||
#define XDL_UTIL_LINKER_BASENAME "linker64"
|
||||
#define XDL_UTIL_LINKER_PATHNAME "/system/bin/linker64"
|
||||
#define XDL_UTIL_APP_PROCESS_BASENAME "app_process64"
|
||||
#define XDL_UTIL_APP_PROCESS_PATHNAME "/system/bin/app_process64"
|
||||
#endif
|
||||
#define XDL_UTIL_VDSO_BASENAME "[vdso]"
|
||||
|
||||
#define XDL_UTIL_TEMP_FAILURE_RETRY(exp) \
|
||||
({ \
|
||||
__typeof__(exp) _rc; \
|
||||
do { \
|
||||
errno = 0; \
|
||||
_rc = (exp); \
|
||||
} while (_rc == -1 && errno == EINTR); \
|
||||
_rc; \
|
||||
})
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
bool xdl_util_starts_with(const char *str, const char *start);
|
||||
bool xdl_util_ends_with(const char *str, const char *ending);
|
||||
|
||||
size_t xdl_util_trim_ending(char *start);
|
||||
|
||||
int xdl_util_get_api_level(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
192
zygisk/src/main/cpp/zygisk.cpp
Normal file
192
zygisk/src/main/cpp/zygisk.cpp
Normal file
@ -0,0 +1,192 @@
|
||||
#include "zygisk.hpp"
|
||||
#include <android/log.h>
|
||||
#include <string>
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
#include <cerrno>
|
||||
#include <filesystem>
|
||||
#include <sys/stat.h>
|
||||
#include "xdl.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.jar"
|
||||
|
||||
#define LIB_64 "/data/adb/modules/playintegrityfix/inject/arm64-v8a.so"
|
||||
#define LIB_32 "/data/adb/modules/playintegrityfix/inject/armeabi-v7a.so"
|
||||
|
||||
#define DEFAULT_JSON "/data/adb/modules/playintegrityfix/pif.json"
|
||||
#define CUSTOM_JSON_FORK "/data/adb/modules/playintegrityfix/custom.pif.json"
|
||||
#define CUSTOM_JSON "/data/adb/pif.json"
|
||||
|
||||
static ssize_t xread(int fd, void *buffer, size_t count) {
|
||||
ssize_t total = 0;
|
||||
auto buf = static_cast<char *>(buffer);
|
||||
while (count > 0) {
|
||||
ssize_t ret = TEMP_FAILURE_RETRY(read(fd, buf, count));
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
buf += ret;
|
||||
total += ret;
|
||||
count -= ret;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
static ssize_t xwrite(int fd, const void *buffer, size_t count) {
|
||||
ssize_t total = 0;
|
||||
auto buf = (char *) buffer;
|
||||
while (count > 0) {
|
||||
ssize_t ret = TEMP_FAILURE_RETRY(write(fd, buf, count));
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
buf += ret;
|
||||
total += ret;
|
||||
count -= ret;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
static bool copyFile(const std::string &from, const std::string &to, mode_t perms = 0777) {
|
||||
return std::filesystem::exists(from) &&
|
||||
std::filesystem::copy_file(
|
||||
from,
|
||||
to,
|
||||
std::filesystem::copy_options::overwrite_existing
|
||||
) &&
|
||||
!chmod(
|
||||
to.c_str(),
|
||||
perms
|
||||
);
|
||||
}
|
||||
|
||||
static void companion(int fd) {
|
||||
bool ok = true;
|
||||
|
||||
int length = 0;
|
||||
xread(fd, &length, sizeof(int));
|
||||
|
||||
std::string dir;
|
||||
dir.resize(length + 1);
|
||||
auto bytes = xread(fd, dir.data(), length);
|
||||
dir.resize(bytes);
|
||||
dir[bytes - 1] = '\0';
|
||||
|
||||
LOGD("[COMPANION] GMS dir: %s", dir.c_str());
|
||||
|
||||
auto libFile = dir + "/libinject.so";
|
||||
#if defined(__aarch64__)
|
||||
ok &= copyFile(LIB_64, libFile);
|
||||
#elif defined(__arm__)
|
||||
ok &= copyFile(LIB_32, libFile);
|
||||
#endif
|
||||
|
||||
LOGD("[COMPANION] copied inject lib");
|
||||
|
||||
auto dexFile = dir + "/classes.jar";
|
||||
ok &= copyFile(DEX_PATH, dexFile, 0644);
|
||||
|
||||
LOGD("[COMPANION] copied dex");
|
||||
|
||||
auto jsonFile = dir + "/pif.json";
|
||||
if (!copyFile(CUSTOM_JSON, jsonFile)) {
|
||||
if (!copyFile(CUSTOM_JSON_FORK, jsonFile)) {
|
||||
if (!copyFile(DEFAULT_JSON, jsonFile)) {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOGD("[COMPANION] copied json");
|
||||
|
||||
xwrite(fd, &ok, sizeof(bool));
|
||||
}
|
||||
|
||||
using namespace zygisk;
|
||||
|
||||
class PlayIntegrityFix : public ModuleBase {
|
||||
public:
|
||||
void onLoad(Api *api_, JNIEnv *env_) override {
|
||||
this->api = api_;
|
||||
this->env = env_;
|
||||
}
|
||||
|
||||
void preAppSpecialize(AppSpecializeArgs *args) override {
|
||||
api->setOption(DLCLOSE_MODULE_LIBRARY);
|
||||
|
||||
if (!args)
|
||||
return;
|
||||
|
||||
auto rawDir = env->GetStringUTFChars(args->app_data_dir, nullptr);
|
||||
auto rawName = env->GetStringUTFChars(args->nice_name, nullptr);
|
||||
|
||||
std::string dir, name;
|
||||
|
||||
if (rawDir) {
|
||||
dir = rawDir;
|
||||
env->ReleaseStringUTFChars(args->app_data_dir, rawDir);
|
||||
}
|
||||
|
||||
if (rawName) {
|
||||
name = rawName;
|
||||
env->ReleaseStringUTFChars(args->nice_name, rawName);
|
||||
}
|
||||
|
||||
bool isGms = dir.ends_with("/com.google.android.gms");
|
||||
bool isGmsUnstable = isGms && name == "com.google.android.gms.unstable";
|
||||
|
||||
if (!isGms)
|
||||
return;
|
||||
|
||||
api->setOption(FORCE_DENYLIST_UNMOUNT);
|
||||
|
||||
if (!isGmsUnstable)
|
||||
return;
|
||||
|
||||
auto fd = api->connectCompanion();
|
||||
|
||||
int size = static_cast<int>(dir.length());
|
||||
xwrite(fd, &size, sizeof(int));
|
||||
|
||||
xwrite(fd, dir.data(), size);
|
||||
|
||||
bool ok = false;
|
||||
xread(fd, &ok, sizeof(bool));
|
||||
|
||||
close(fd);
|
||||
|
||||
if (ok)
|
||||
gmsDir = dir;
|
||||
}
|
||||
|
||||
void postAppSpecialize(const AppSpecializeArgs *args) override {
|
||||
if (gmsDir.empty())
|
||||
return;
|
||||
|
||||
typedef bool (*InitFuncPtr)(JavaVM *, const std::string &);
|
||||
|
||||
auto handle = xdl_open((gmsDir + "/libinject.so").c_str(), XDL_DEFAULT);
|
||||
auto init_func = reinterpret_cast<InitFuncPtr>(xdl_sym(handle, "init", nullptr));
|
||||
|
||||
JavaVM *vm = nullptr;
|
||||
env->GetJavaVM(&vm);
|
||||
|
||||
init_func(vm, gmsDir);
|
||||
|
||||
xdl_close(handle);
|
||||
}
|
||||
|
||||
void preServerSpecialize(ServerSpecializeArgs *args) override {
|
||||
api->setOption(DLCLOSE_MODULE_LIBRARY);
|
||||
}
|
||||
|
||||
private:
|
||||
Api *api = nullptr;
|
||||
JNIEnv *env = nullptr;
|
||||
std::string gmsDir;
|
||||
};
|
||||
|
||||
REGISTER_ZYGISK_MODULE(PlayIntegrityFix)
|
||||
|
||||
REGISTER_ZYGISK_COMPANION(companion)
|
411
zygisk/src/main/cpp/zygisk.hpp
Normal file
411
zygisk/src/main/cpp/zygisk.hpp
Normal file
@ -0,0 +1,411 @@
|
||||
/* 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"
|
Loading…
Reference in New Issue
Block a user