This commit is contained in:
jiqiu2021
2024-10-05 15:26:16 +08:00
commit 5408de21e5
37 changed files with 3256 additions and 0 deletions

3
module/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/.externalNativeBuild
/build
/release

107
module/build.gradle Normal file
View File

@@ -0,0 +1,107 @@
import org.apache.tools.ant.filters.FixCrLfFilter
import java.nio.file.Paths
import java.nio.file.Files
apply plugin: 'com.android.library'
apply from: file(rootProject.file('module.gradle'))
android {
compileSdkVersion rootProject.ext.targetSdkVersion
ndkVersion '25.2.9519653'
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
externalNativeBuild {
cmake {
arguments "-DMODULE_NAME:STRING=$moduleLibraryName"
}
}
}
buildFeatures {
prefab true
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.22.1"
}
}
}
repositories {
mavenLocal()
}
afterEvaluate {
android.libraryVariants.forEach { variant ->
def variantCapped = variant.name.capitalize()
def variantLowered = variant.name.toLowerCase()
def zipName = "${magiskModuleId.replace('_', '-')}-${moduleVersion}-${variantLowered}.zip"
def magiskDir = file("$outDir/magisk_module_$variantLowered")
task("prepareMagiskFiles${variantCapped}", type: Sync) {
dependsOn("assemble$variantCapped")
def templatePath = "$rootDir/template/magisk_module"
into magiskDir
from(templatePath) {
exclude 'module.prop'
}
from(templatePath) {
include 'module.prop'
expand([
id : magiskModuleId,
name : moduleName,
version : moduleVersion,
versionCode: moduleVersionCode.toString(),
author : moduleAuthor,
description: moduleDescription,
])
filter(FixCrLfFilter.class,
eol: FixCrLfFilter.CrLf.newInstance("lf"))
}
from("$buildDir/intermediates/stripped_native_libs/$variantLowered/out/lib") {
into 'lib'
}
doLast {
file("$magiskDir/zygisk").mkdir()
fileTree("$magiskDir/lib").visit { f ->
if (!f.directory) return
def srcPath = Paths.get("${f.file.absolutePath}/lib${moduleLibraryName}.so")
def dstPath = Paths.get("$magiskDir/zygisk/${f.path}.so")
Files.move(srcPath, dstPath)
}
new File("$magiskDir/lib").deleteDir()
}
}
task("zip${variantCapped}", type: Zip) {
dependsOn("prepareMagiskFiles${variantCapped}")
from magiskDir
archiveFileName.set(zipName)
destinationDirectory.set(outDir)
}
task("push${variantCapped}", type: Exec) {
dependsOn("zip${variantCapped}")
workingDir outDir
commandLine android.adbExecutable, "push", zipName, "/data/local/tmp/"
}
task("flash${variantCapped}", type: Exec) {
dependsOn("push${variantCapped}")
commandLine android.adbExecutable, "shell", "su", "-c",
"magisk --install-module /data/local/tmp/${zipName}"
}
task("flashAndReboot${variantCapped}", type: Exec) {
dependsOn("flash${variantCapped}")
commandLine android.adbExecutable, "shell", "reboot"
}
variant.assembleProvider.get().finalizedBy("zip${variantCapped}")
}
}

View File

@@ -0,0 +1 @@
<manifest package="zygisk.il2cppdumper" />

View File

@@ -0,0 +1,45 @@
cmake_minimum_required(VERSION 3.18.1)
if (NOT DEFINED MODULE_NAME)
message(FATAL_ERROR "MODULE_NAME is not set")
else ()
project(${MODULE_NAME})
endif ()
message("Build type: ${CMAKE_BUILD_TYPE}")
set(CMAKE_CXX_STANDARD 20)
set(LINKER_FLAGS "-ffixed-x18 -Wl,--hash-style=both")
set(C_FLAGS "-Werror=format -fdata-sections -ffunction-sections")
set(CXX_FLAGS "${CXX_FLAGS} -fno-exceptions -fno-rtti")
if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
set(C_FLAGS "${C_FLAGS} -O2 -fvisibility=hidden -fvisibility-inlines-hidden")
set(LINKER_FLAGS "${LINKER_FLAGS} -Wl,-exclude-libs,ALL -Wl,--gc-sections -Wl,--strip-all")
else ()
set(C_FLAGS "${C_FLAGS} -O0")
endif ()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${C_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${C_FLAGS} ${CXX_FLAGS}")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${LINKER_FLAGS}")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${LINKER_FLAGS}")
include_directories(
xdl/include
)
aux_source_directory(xdl xdl-src)
add_library(${MODULE_NAME} SHARED
main.cpp
hack.cpp
${xdl-src})
target_link_libraries(${MODULE_NAME} log)
if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
add_custom_command(TARGET ${MODULE_NAME} POST_BUILD
COMMAND ${CMAKE_STRIP} --strip-all --remove-section=.comment "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/lib${MODULE_NAME}.so")
endif ()

View File

@@ -0,0 +1,10 @@
//
// Created by Perfare on 2020/7/4.
//
#ifndef ZYGISK_IL2CPPDUMPER_GAME_H
#define ZYGISK_IL2CPPDUMPER_GAME_H
#define AimPackageName "com.example.testdlopen"
#endif //ZYGISK_IL2CPPDUMPER_GAME_H

View File

@@ -0,0 +1,264 @@
//
// Created by Perfare on 2020/7/4.
//
#include "hack.h"
#include "log.h"
#include "xdl.h"
#include <cstring>
#include <cstdio>
#include <unistd.h>
#include <sys/system_properties.h>
#include <dlfcn.h>
#include <jni.h>
#include <thread>
#include <sys/mman.h>
#include <linux/unistd.h>
#include <array>
#include <sys/stat.h>
//#include <asm-generic/fcntl.h>
#include <fcntl.h>
void hack_start(const char *game_data_dir,JavaVM *vm) {
bool load = false;
LOGI("hack_start %s", game_data_dir);
// 构建新文件路径
char new_so_path[256];
snprintf(new_so_path, sizeof(new_so_path), "%s/files/%s.so", game_data_dir, "test");
// 复制 /sdcard/test.so 到 game_data_dir 并重命名
const char *src_path = "/data/local/tmp/test.so";
int src_fd = open(src_path, O_RDONLY);
if (src_fd < 0) {
LOGE("Failed to open %s: %s (errno: %d)", src_path, strerror(errno), errno);
return;
}
int dest_fd = open(new_so_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (dest_fd < 0) {
LOGE("Failed to open %s", new_so_path);
close(src_fd);
return;
}
// 复制文件内容
char buffer[4096];
ssize_t bytes;
while ((bytes = read(src_fd, buffer, sizeof(buffer))) > 0) {
if (write(dest_fd, buffer, bytes) != bytes) {
LOGE("Failed to write to %s", new_so_path);
close(src_fd);
close(dest_fd);
return;
}
}
close(src_fd);
close(dest_fd);
if (chmod(new_so_path, 0755) != 0) {
LOGE("Failed to change permissions on %s: %s (errno: %d)", new_so_path, strerror(errno), errno);
return;
} else {
LOGI("Successfully changed permissions to 755 on %s", new_so_path);
}
void * handle;
// 使用 xdl_open 打开新复制的 so 文件
for (int i = 0; i < 10; i++) {
// void *handle = xdl_open(new_so_path, 0);
handle = dlopen(new_so_path, RTLD_NOW | RTLD_LOCAL);
if (handle) {
LOGI("Successfully loaded %s", new_so_path);
load = true;
break;
} else {
LOGE("Failed to load %s: %s", new_so_path, dlerror());
sleep(1);
}
}
if (!load) {
LOGI("test.so not found in thread %d", gettid());
}
void (*JNI_OnLoad)(JavaVM *, void *);
*(void **) (&JNI_OnLoad) = dlsym(handle, "JNI_OnLoad");
if (JNI_OnLoad) {
LOGI("JNI_OnLoad symbol found, calling JNI_OnLoad.");
JNI_OnLoad(vm, NULL);
} else {
LOGE("JNI_OnLoad symbol not found in %s", new_so_path);
}
}
std::string GetLibDir(JavaVM *vms) {
JNIEnv *env = nullptr;
vms->AttachCurrentThread(&env, nullptr);
jclass activity_thread_clz = env->FindClass("android/app/ActivityThread");
if (activity_thread_clz != nullptr) {
jmethodID currentApplicationId = env->GetStaticMethodID(activity_thread_clz,
"currentApplication",
"()Landroid/app/Application;");
if (currentApplicationId) {
jobject application = env->CallStaticObjectMethod(activity_thread_clz,
currentApplicationId);
jclass application_clazz = env->GetObjectClass(application);
if (application_clazz) {
jmethodID get_application_info = env->GetMethodID(application_clazz,
"getApplicationInfo",
"()Landroid/content/pm/ApplicationInfo;");
if (get_application_info) {
jobject application_info = env->CallObjectMethod(application,
get_application_info);
jfieldID native_library_dir_id = env->GetFieldID(
env->GetObjectClass(application_info), "nativeLibraryDir",
"Ljava/lang/String;");
if (native_library_dir_id) {
auto native_library_dir_jstring = (jstring) env->GetObjectField(
application_info, native_library_dir_id);
auto path = env->GetStringUTFChars(native_library_dir_jstring, nullptr);
LOGI("lib dir %s", path);
std::string lib_dir(path);
env->ReleaseStringUTFChars(native_library_dir_jstring, path);
return lib_dir;
} else {
LOGE("nativeLibraryDir not found");
}
} else {
LOGE("getApplicationInfo not found");
}
} else {
LOGE("application class not found");
}
} else {
LOGE("currentApplication not found");
}
} else {
LOGE("ActivityThread not found");
}
return {};
}
static std::string GetNativeBridgeLibrary() {
auto value = std::array<char, PROP_VALUE_MAX>();
__system_property_get("ro.dalvik.vm.native.bridge", value.data());
return {value.data()};
}
struct NativeBridgeCallbacks {
uint32_t version;
void *initialize;
void *(*loadLibrary)(const char *libpath, int flag);
void *(*getTrampoline)(void *handle, const char *name, const char *shorty, uint32_t len);
void *isSupported;
void *getAppEnv;
void *isCompatibleWith;
void *getSignalHandler;
void *unloadLibrary;
void *getError;
void *isPathSupported;
void *initAnonymousNamespace;
void *createNamespace;
void *linkNamespaces;
void *(*loadLibraryExt)(const char *libpath, int flag, void *ns);
};
bool NativeBridgeLoad(const char *game_data_dir, int api_level, void *data, size_t length) {
//TODO 等待houdini初始化
sleep(5);
auto libart = dlopen("libart.so", RTLD_NOW);
auto JNI_GetCreatedJavaVMs = (jint (*)(JavaVM **, jsize, jsize *)) dlsym(libart,
"JNI_GetCreatedJavaVMs");
LOGI("JNI_GetCreatedJavaVMs %p", JNI_GetCreatedJavaVMs);
JavaVM *vms_buf[1];
JavaVM *vms;
jsize num_vms;
jint status = JNI_GetCreatedJavaVMs(vms_buf, 1, &num_vms);
if (status == JNI_OK && num_vms > 0) {
vms = vms_buf[0];
} else {
LOGE("GetCreatedJavaVMs error");
return false;
}
auto lib_dir = GetLibDir(vms);
if (lib_dir.empty()) {
LOGE("GetLibDir error");
return false;
}
if (lib_dir.find("/lib/x86") != std::string::npos) {
LOGI("no need NativeBridge");
munmap(data, length);
return false;
}
auto nb = dlopen("libhoudini.so", RTLD_NOW);
if (!nb) {
auto native_bridge = GetNativeBridgeLibrary();
LOGI("native bridge: %s", native_bridge.data());
nb = dlopen(native_bridge.data(), RTLD_NOW);
}
if (nb) {
LOGI("nb %p", nb);
auto callbacks = (NativeBridgeCallbacks *) dlsym(nb, "NativeBridgeItf");
if (callbacks) {
LOGI("NativeBridgeLoadLibrary %p", callbacks->loadLibrary);
LOGI("NativeBridgeLoadLibraryExt %p", callbacks->loadLibraryExt);
LOGI("NativeBridgeGetTrampoline %p", callbacks->getTrampoline);
int fd = syscall(__NR_memfd_create, "anon", MFD_CLOEXEC);
ftruncate(fd, (off_t) length);
void *mem = mmap(nullptr, length, PROT_WRITE, MAP_SHARED, fd, 0);
memcpy(mem, data, length);
munmap(mem, length);
munmap(data, length);
char path[PATH_MAX];
snprintf(path, PATH_MAX, "/proc/self/fd/%d", fd);
LOGI("arm path %s", path);
void *arm_handle;
if (api_level >= 26) {
arm_handle = callbacks->loadLibraryExt(path, RTLD_NOW, (void *) 3);
} else {
arm_handle = callbacks->loadLibrary(path, RTLD_NOW);
}
if (arm_handle) {
LOGI("arm handle %p", arm_handle);
auto init = (void (*)(JavaVM *, void *)) callbacks->getTrampoline(arm_handle,
"JNI_OnLoad",
nullptr, 0);
LOGI("JNI_OnLoad %p", init);
init(vms, (void *) game_data_dir);
return true;
}
close(fd);
}
}
return false;
}
void hack_prepare(const char *_data_dir, void *data, size_t length) {
LOGI("hack thread: %d", gettid());
int api_level = android_get_device_api_level();
LOGI("api level: %d", api_level);
#if defined(__i386__) || defined(__x86_64__)
if (!NativeBridgeLoad(_data_dir, api_level, data, length)) {
#endif
hack_start(_data_dir, nullptr);
#if defined(__i386__) || defined(__x86_64__)
}
#endif
}
#if defined(__arm__) || defined(__aarch64__)
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
auto game_data_dir = (const char *) reserved;
std::thread hack_thread(hack_start, game_data_dir,vm);
hack_thread.detach();
return JNI_VERSION_1_6;
}
#endif

View File

@@ -0,0 +1,12 @@
//
// Created by Perfare on 2020/7/4.
//
#ifndef ZYGISK_IL2CPPDUMPER_HACK_H
#define ZYGISK_IL2CPPDUMPER_HACK_H
#include <stddef.h>
void hack_prepare(const char *game_data_dir, void *data, size_t length);
#endif //ZYGISK_IL2CPPDUMPER_HACK_H

16
module/src/main/cpp/log.h Normal file
View File

@@ -0,0 +1,16 @@
//
// Created by Perfare on 2020/7/4.
//
#ifndef ZYGISK_IL2CPPDUMPER_LOG_H
#define ZYGISK_IL2CPPDUMPER_LOG_H
#include <android/log.h>
#define LOG_TAG "Perfare"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#endif //ZYGISK_IL2CPPDUMPER_LOG_H

View File

@@ -0,0 +1,81 @@
#include <cstring>
#include <thread>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <cinttypes>
#include "hack.h"
#include "zygisk.hpp"
#include "game.h"
#include "log.h"
#include "dlfcn.h"
using zygisk::Api;
using zygisk::AppSpecializeArgs;
using zygisk::ServerSpecializeArgs;
class MyModule : public zygisk::ModuleBase {
public:
void onLoad(Api *api, JNIEnv *env) override {
this->api = api;
this->env = env;
}
void preAppSpecialize(AppSpecializeArgs *args) override {
auto package_name = env->GetStringUTFChars(args->nice_name, nullptr);
auto app_data_dir = env->GetStringUTFChars(args->app_data_dir, nullptr);
LOGI("preAppSpecialize %s %s", package_name, app_data_dir);
preSpecialize(package_name, app_data_dir);
env->ReleaseStringUTFChars(args->nice_name, package_name);
env->ReleaseStringUTFChars(args->app_data_dir, app_data_dir);
}
void postAppSpecialize(const AppSpecializeArgs *) override {
if (enable_hack) {
std::thread hack_thread(hack_prepare, _data_dir, data, length);
hack_thread.detach();
}
}
private:
Api *api;
JNIEnv *env;
bool enable_hack;
char *_data_dir;
void *data;
size_t length;
void preSpecialize(const char *package_name, const char *app_data_dir) {
if (strcmp(package_name, AimPackageName) == 0) {
LOGI("成功注入目标进程: %s", package_name);
enable_hack = true;
_data_dir = new char[strlen(app_data_dir) + 1];
strcpy(_data_dir, app_data_dir);
#if defined(__i386__)
auto path = "zygisk/armeabi-v7a.so";
#endif
#if defined(__x86_64__)
auto path = "zygisk/arm64-v8a.so";
#endif
#if defined(__i386__) || defined(__x86_64__)
int dirfd = api->getModuleDir();
int fd = openat(dirfd, path, O_RDONLY);
if (fd != -1) {
struct stat sb{};
fstat(fd, &sb);
length = sb.st_size;
data = mmap(nullptr, length, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
} else {
LOGW("Unable to open arm file");
}
#endif
} else {
api->setOption(zygisk::Option::DLCLOSE_MODULE_LIBRARY);
}
}
};
REGISTER_ZYGISK_MODULE(MyModule)

View File

@@ -0,0 +1,92 @@
// Copyright (c) 2020-2021 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: 1.2.1
//
// 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_HEXHACKING_XDL
#define IO_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 both xdl_open() 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_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().
//
int xdl_addr(void *addr, xdl_info_t *info, void **cache);
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

View File

@@ -0,0 +1,869 @@
// Copyright (c) 2020-2021 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.h"
#include <android/api-level.h>
#include <elf.h>
#include <fcntl.h>
#include <inttypes.h>
#include <link.h>
#include <pthread.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/auxv.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "xdl_iterate.h"
#include "xdl_linker.h"
#include "xdl_lzma.h"
#include "xdl_util.h"
#ifndef __LP64__
#define XDL_LIB_PATH "/system/lib"
#else
#define XDL_LIB_PATH "/system/lib64"
#endif
#define XDL_DYNSYM_IS_EXPORT_SYM(shndx) (SHN_UNDEF != (shndx))
#define XDL_SYMTAB_IS_EXPORT_SYM(shndx) \
(SHN_UNDEF != (shndx) && !((shndx) >= SHN_LORESERVE && (shndx) <= SHN_HIRESERVE))
extern __attribute((weak)) unsigned long int getauxval(unsigned long int);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpadded"
typedef struct xdl {
char *pathname;
uintptr_t load_bias;
const ElfW(Phdr) *dlpi_phdr;
ElfW(Half) dlpi_phnum;
struct xdl *next; // to next xdl obj for cache in xdl_addr()
void *linker_handle; // hold handle returned by xdl_linker_load()
//
// (1) for searching symbols from .dynsym
//
bool dynsym_try_load;
ElfW(Sym) *dynsym; // .dynsym
const char *dynstr; // .dynstr
// .hash (SYSV hash for .dynstr)
struct {
const uint32_t *buckets;
uint32_t buckets_cnt;
const uint32_t *chains;
uint32_t chains_cnt;
} sysv_hash;
// .gnu.hash (GNU hash for .dynstr)
struct {
const uint32_t *buckets;
uint32_t buckets_cnt;
const uint32_t *chains;
uint32_t symoffset;
const ElfW(Addr) *bloom;
uint32_t bloom_cnt;
uint32_t bloom_shift;
} gnu_hash;
//
// (2) for searching symbols from .symtab
//
bool symtab_try_load;
uintptr_t base;
ElfW(Sym) *symtab; // .symtab
size_t symtab_cnt;
char *strtab; // .strtab
size_t strtab_sz;
} xdl_t;
#pragma clang diagnostic pop
// load from memory
static int xdl_dynsym_load(xdl_t *self) {
// find the dynamic segment
ElfW(Dyn) *dynamic = NULL;
for (size_t i = 0; i < self->dlpi_phnum; i++) {
const ElfW(Phdr) *phdr = &(self->dlpi_phdr[i]);
if (PT_DYNAMIC == phdr->p_type) {
dynamic = (ElfW(Dyn) *)(self->load_bias + phdr->p_vaddr);
break;
}
}
if (NULL == dynamic) return -1;
// iterate the dynamic segment
for (ElfW(Dyn) *entry = dynamic; entry && entry->d_tag != DT_NULL; entry++) {
switch (entry->d_tag) {
case DT_SYMTAB: //.dynsym
self->dynsym = (ElfW(Sym) *)(self->load_bias + entry->d_un.d_ptr);
break;
case DT_STRTAB: //.dynstr
self->dynstr = (const char *)(self->load_bias + entry->d_un.d_ptr);
break;
case DT_HASH: //.hash
self->sysv_hash.buckets_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[0];
self->sysv_hash.chains_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[1];
self->sysv_hash.buckets = &(((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[2]);
self->sysv_hash.chains = &(self->sysv_hash.buckets[self->sysv_hash.buckets_cnt]);
break;
case DT_GNU_HASH: //.gnu.hash
self->gnu_hash.buckets_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[0];
self->gnu_hash.symoffset = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[1];
self->gnu_hash.bloom_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[2];
self->gnu_hash.bloom_shift = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[3];
self->gnu_hash.bloom = (const ElfW(Addr) *)(self->load_bias + entry->d_un.d_ptr + 16);
self->gnu_hash.buckets = (const uint32_t *)(&(self->gnu_hash.bloom[self->gnu_hash.bloom_cnt]));
self->gnu_hash.chains = (const uint32_t *)(&(self->gnu_hash.buckets[self->gnu_hash.buckets_cnt]));
break;
default:
break;
}
}
if (NULL == self->dynsym || NULL == self->dynstr ||
(0 == self->sysv_hash.buckets_cnt && 0 == self->gnu_hash.buckets_cnt)) {
self->dynsym = NULL;
self->dynstr = NULL;
self->sysv_hash.buckets_cnt = 0;
self->gnu_hash.buckets_cnt = 0;
return -1;
}
return 0;
}
static void *xdl_read_file_to_heap(int file_fd, size_t file_sz, size_t data_offset, size_t data_len) {
if (0 == data_len) return NULL;
if (data_offset >= file_sz) return NULL;
if (data_offset + data_len > file_sz) return NULL;
if (data_offset != (size_t)lseek(file_fd, (off_t)data_offset, SEEK_SET)) return NULL;
void *data = malloc(data_len);
if (NULL == data) return NULL;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu-statement-expression"
if ((ssize_t)data_len != XDL_UTIL_TEMP_FAILURE_RETRY(read(file_fd, data, data_len)))
#pragma clang diagnostic pop
{
free(data);
return NULL;
}
return data;
}
static void *xdl_read_file_to_heap_by_section(int file_fd, size_t file_sz, ElfW(Shdr) *shdr) {
return xdl_read_file_to_heap(file_fd, file_sz, (size_t)shdr->sh_offset, shdr->sh_size);
}
static void *xdl_read_memory_to_heap(void *mem, size_t mem_sz, size_t data_offset, size_t data_len) {
if (0 == data_len) return NULL;
if (data_offset >= mem_sz) return NULL;
if (data_offset + data_len > mem_sz) return NULL;
void *data = malloc(data_len);
if (NULL == data) return NULL;
memcpy(data, (void *)((uintptr_t)mem + data_offset), data_len);
return data;
}
static void *xdl_read_memory_to_heap_by_section(void *mem, size_t mem_sz, ElfW(Shdr) *shdr) {
return xdl_read_memory_to_heap(mem, mem_sz, (size_t)shdr->sh_offset, shdr->sh_size);
}
static void *xdl_get_memory(void *mem, size_t mem_sz, size_t data_offset, size_t data_len) {
if (0 == data_len) return NULL;
if (data_offset >= mem_sz) return NULL;
if (data_offset + data_len > mem_sz) return NULL;
return (void *)((uintptr_t)mem + data_offset);
}
static void *xdl_get_memory_by_section(void *mem, size_t mem_sz, ElfW(Shdr) *shdr) {
return xdl_get_memory(mem, mem_sz, (size_t)shdr->sh_offset, shdr->sh_size);
}
// load from disk and memory
static int xdl_symtab_load_from_debugdata(xdl_t *self, int file_fd, size_t file_sz,
ElfW(Shdr) *shdr_debugdata) {
void *debugdata = NULL;
ElfW(Shdr) *shdrs = NULL;
int r = -1;
// get zipped .gnu_debugdata
uint8_t *debugdata_zip = (uint8_t *)xdl_read_file_to_heap_by_section(file_fd, file_sz, shdr_debugdata);
if (NULL == debugdata_zip) return -1;
// get unzipped .gnu_debugdata
size_t debugdata_sz;
if (0 != xdl_lzma_decompress(debugdata_zip, shdr_debugdata->sh_size, (uint8_t **)&debugdata, &debugdata_sz))
goto end;
// get ELF header
ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)debugdata;
if (0 == ehdr->e_shnum || ehdr->e_shentsize != sizeof(ElfW(Shdr))) goto end;
// get section headers
shdrs = (ElfW(Shdr) *)xdl_read_memory_to_heap(debugdata, debugdata_sz, (size_t)ehdr->e_shoff,
ehdr->e_shentsize * ehdr->e_shnum);
if (NULL == shdrs) goto end;
// get .shstrtab
if (SHN_UNDEF == ehdr->e_shstrndx || ehdr->e_shstrndx >= ehdr->e_shnum) goto end;
char *shstrtab = (char *)xdl_get_memory_by_section(debugdata, debugdata_sz, shdrs + ehdr->e_shstrndx);
if (NULL == shstrtab) goto end;
// find .symtab & .strtab
for (ElfW(Shdr) *shdr = shdrs; shdr < shdrs + ehdr->e_shnum; shdr++) {
char *shdr_name = shstrtab + shdr->sh_name;
if (SHT_SYMTAB == shdr->sh_type && 0 == strcmp(".symtab", shdr_name)) {
// get & check associated .strtab section
if (shdr->sh_link >= ehdr->e_shnum) continue;
ElfW(Shdr) *shdr_strtab = shdrs + shdr->sh_link;
if (SHT_STRTAB != shdr_strtab->sh_type) continue;
// get .symtab & .strtab
ElfW(Sym) *symtab = (ElfW(Sym) *)xdl_read_memory_to_heap_by_section(debugdata, debugdata_sz, shdr);
if (NULL == symtab) continue;
char *strtab = (char *)xdl_read_memory_to_heap_by_section(debugdata, debugdata_sz, shdr_strtab);
if (NULL == strtab) {
free(symtab);
continue;
}
// OK
self->symtab = symtab;
self->symtab_cnt = shdr->sh_size / shdr->sh_entsize;
self->strtab = strtab;
self->strtab_sz = shdr_strtab->sh_size;
r = 0;
break;
}
}
end:
free(debugdata_zip);
if (NULL != debugdata) free(debugdata);
if (NULL != shdrs) free(shdrs);
return r;
}
// load from disk and memory
static int xdl_symtab_load(xdl_t *self) {
if ('[' == self->pathname[0]) return -1;
int r = -1;
ElfW(Shdr) *shdrs = NULL;
char *shstrtab = NULL;
// get base address
uintptr_t vaddr_min = UINTPTR_MAX;
for (size_t i = 0; i < self->dlpi_phnum; i++) {
const ElfW(Phdr) *phdr = &(self->dlpi_phdr[i]);
if (PT_LOAD == phdr->p_type) {
if (vaddr_min > phdr->p_vaddr) vaddr_min = phdr->p_vaddr;
}
}
if (UINTPTR_MAX == vaddr_min) return -1;
self->base = self->load_bias + vaddr_min;
// open file
int flags = O_RDONLY | O_CLOEXEC;
int file_fd;
if ('/' == self->pathname[0]) {
file_fd = open(self->pathname, flags);
} else {
char full_pathname[1024];
// try the fast method
snprintf(full_pathname, sizeof(full_pathname), "%s/%s", XDL_LIB_PATH, self->pathname);
file_fd = open(full_pathname, flags);
if (file_fd < 0) {
// try the slow method
if (0 != xdl_iterate_get_full_pathname(self->base, full_pathname, sizeof(full_pathname))) return -1;
file_fd = open(full_pathname, flags);
}
}
if (file_fd < 0) return -1;
struct stat st;
if (0 != fstat(file_fd, &st)) goto end;
size_t file_sz = (size_t)st.st_size;
// get ELF header
ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)self->base;
if (0 == ehdr->e_shnum || ehdr->e_shentsize != sizeof(ElfW(Shdr))) goto end;
// get section headers
shdrs = (ElfW(Shdr) *)xdl_read_file_to_heap(file_fd, file_sz, (size_t)ehdr->e_shoff,
ehdr->e_shentsize * ehdr->e_shnum);
if (NULL == shdrs) goto end;
// get .shstrtab
if (SHN_UNDEF == ehdr->e_shstrndx || ehdr->e_shstrndx >= ehdr->e_shnum) goto end;
shstrtab = (char *)xdl_read_file_to_heap_by_section(file_fd, file_sz, shdrs + ehdr->e_shstrndx);
if (NULL == shstrtab) goto end;
// find .symtab & .strtab
for (ElfW(Shdr) *shdr = shdrs; shdr < shdrs + ehdr->e_shnum; shdr++) {
char *shdr_name = shstrtab + shdr->sh_name;
if (SHT_SYMTAB == shdr->sh_type && 0 == strcmp(".symtab", shdr_name)) {
// get & check associated .strtab section
if (shdr->sh_link >= ehdr->e_shnum) continue;
ElfW(Shdr) *shdr_strtab = shdrs + shdr->sh_link;
if (SHT_STRTAB != shdr_strtab->sh_type) continue;
// get .symtab & .strtab
ElfW(Sym) *symtab = (ElfW(Sym) *)xdl_read_file_to_heap_by_section(file_fd, file_sz, shdr);
if (NULL == symtab) continue;
char *strtab = (char *)xdl_read_file_to_heap_by_section(file_fd, file_sz, shdr_strtab);
if (NULL == strtab) {
free(symtab);
continue;
}
// OK
self->symtab = symtab;
self->symtab_cnt = shdr->sh_size / shdr->sh_entsize;
self->strtab = strtab;
self->strtab_sz = shdr_strtab->sh_size;
r = 0;
break;
} else if (SHT_PROGBITS == shdr->sh_type && 0 == strcmp(".gnu_debugdata", shdr_name)) {
if (0 == xdl_symtab_load_from_debugdata(self, file_fd, file_sz, shdr)) {
// OK
r = 0;
break;
}
}
}
end:
close(file_fd);
if (NULL != shdrs) free(shdrs);
if (NULL != shstrtab) free(shstrtab);
return r;
}
static xdl_t *xdl_find_from_auxv(unsigned long type, const char *pathname) {
if (NULL == getauxval) return NULL;
uintptr_t val = (uintptr_t)getauxval(type);
if (0 == val) return NULL;
// get base
uintptr_t base = (AT_PHDR == type ? (val & (~0xffful)) : val);
if (0 != memcmp((void *)base, ELFMAG, SELFMAG)) return NULL;
// ELF info
ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)base;
const ElfW(Phdr) *dlpi_phdr = (const ElfW(Phdr) *)(base + ehdr->e_phoff);
ElfW(Half) dlpi_phnum = ehdr->e_phnum;
// get bias
uintptr_t min_vaddr = UINTPTR_MAX;
for (size_t i = 0; i < dlpi_phnum; i++) {
const ElfW(Phdr) *phdr = &(dlpi_phdr[i]);
if (PT_LOAD == phdr->p_type) {
if (min_vaddr > phdr->p_vaddr) min_vaddr = phdr->p_vaddr;
}
}
if (UINTPTR_MAX == min_vaddr || base < min_vaddr) return NULL;
uintptr_t load_bias = base - min_vaddr;
// create xDL object
xdl_t *self;
if (NULL == (self = calloc(1, sizeof(xdl_t)))) return NULL;
if (NULL == (self->pathname = strdup(pathname))) {
free(self);
return NULL;
}
self->load_bias = load_bias;
self->dlpi_phdr = dlpi_phdr;
self->dlpi_phnum = dlpi_phnum;
self->dynsym_try_load = false;
self->symtab_try_load = false;
return self;
}
static int xdl_find_iterate_cb(struct dl_phdr_info *info, size_t size, void *arg) {
(void)size;
uintptr_t *pkg = (uintptr_t *)arg;
xdl_t **self = (xdl_t **)*pkg++;
const char *filename = (const char *)*pkg;
// check load_bias
if (0 == info->dlpi_addr || NULL == info->dlpi_name) return 0;
// check pathname
if ('[' == filename[0]) {
if (0 != strcmp(info->dlpi_name, filename)) return 0;
} else if ('/' == filename[0]) {
if ('/' == info->dlpi_name[0]) {
if (0 != strcmp(info->dlpi_name, filename)) return 0;
} else {
if (!xdl_util_ends_with(filename, info->dlpi_name)) return 0;
}
} else {
if ('/' == info->dlpi_name[0]) {
if (!xdl_util_ends_with(info->dlpi_name, filename)) return 0;
} else {
if (0 != strcmp(info->dlpi_name, filename)) return 0;
}
}
// found the target ELF
if (NULL == ((*self) = calloc(1, sizeof(xdl_t)))) return 1; // return failed
if (NULL == ((*self)->pathname = strdup(info->dlpi_name))) {
free(*self);
*self = NULL;
return 1; // return failed
}
(*self)->load_bias = info->dlpi_addr;
(*self)->dlpi_phdr = info->dlpi_phdr;
(*self)->dlpi_phnum = info->dlpi_phnum;
(*self)->dynsym_try_load = false;
(*self)->symtab_try_load = false;
return 1; // return OK
}
static xdl_t *xdl_find(const char *filename) {
// from auxv (linker, vDSO)
xdl_t *self = NULL;
if (xdl_util_ends_with(filename, XDL_UTIL_LINKER_BASENAME))
self = xdl_find_from_auxv(AT_BASE, XDL_UTIL_LINKER_PATHNAME);
else if (xdl_util_ends_with(filename, XDL_UTIL_VDSO_BASENAME))
self = xdl_find_from_auxv(AT_SYSINFO_EHDR, XDL_UTIL_VDSO_BASENAME);
// from auxv (app_process)
const char *basename, *pathname;
#if (defined(__arm__) || defined(__i386__)) && __ANDROID_API__ < __ANDROID_API_L__
if (xdl_util_get_api_level() < __ANDROID_API_L__) {
basename = XDL_UTIL_APP_PROCESS_BASENAME_K;
pathname = XDL_UTIL_APP_PROCESS_PATHNAME_K;
} else
#endif
{
basename = XDL_UTIL_APP_PROCESS_BASENAME;
pathname = XDL_UTIL_APP_PROCESS_PATHNAME;
}
if (xdl_util_ends_with(filename, basename)) self = xdl_find_from_auxv(AT_PHDR, pathname);
if (NULL != self) return self;
// from dl_iterate_phdr
uintptr_t pkg[2] = {(uintptr_t)&self, (uintptr_t)filename};
xdl_iterate_phdr(xdl_find_iterate_cb, pkg, XDL_DEFAULT);
return self;
}
static void *xdl_open_always_force(const char *filename) {
// always force dlopen()
void *linker_handle = xdl_linker_load(filename);
if (NULL == linker_handle) return NULL;
// find
xdl_t *self = xdl_find(filename);
if (NULL == self)
dlclose(linker_handle);
else
self->linker_handle = linker_handle;
return (void *)self;
}
static void *xdl_open_try_force(const char *filename) {
// find
xdl_t *self = xdl_find(filename);
if (NULL != self) return (void *)self;
// try force dlopen()
void *linker_handle = xdl_linker_load(filename);
if (NULL == linker_handle) return NULL;
// find again
self = xdl_find(filename);
if (NULL == self)
dlclose(linker_handle);
else
self->linker_handle = linker_handle;
return (void *)self;
}
void *xdl_open(const char *filename, int flags) {
if (NULL == filename) return NULL;
if (flags & XDL_ALWAYS_FORCE_LOAD)
return xdl_open_always_force(filename);
else if (flags & XDL_TRY_FORCE_LOAD)
return xdl_open_try_force(filename);
else
return xdl_find(filename);
}
void *xdl_close(void *handle) {
if (NULL == handle) return NULL;
xdl_t *self = (xdl_t *)handle;
if (NULL != self->pathname) free(self->pathname);
if (NULL != self->symtab) free(self->symtab);
if (NULL != self->strtab) free(self->strtab);
void *linker_handle = self->linker_handle;
free(self);
return linker_handle;
}
static uint32_t xdl_sysv_hash(const uint8_t *name) {
uint32_t h = 0, g;
while (*name) {
h = (h << 4) + *name++;
g = h & 0xf0000000;
h ^= g;
h ^= g >> 24;
}
return h;
}
static uint32_t xdl_gnu_hash(const uint8_t *name) {
uint32_t h = 5381;
while (*name) {
h += (h << 5) + *name++;
}
return h;
}
static ElfW(Sym) *xdl_dynsym_find_symbol_use_sysv_hash(xdl_t *self, const char *sym_name) {
uint32_t hash = xdl_sysv_hash((const uint8_t *)sym_name);
for (uint32_t i = self->sysv_hash.buckets[hash % self->sysv_hash.buckets_cnt]; 0 != i;
i = self->sysv_hash.chains[i]) {
ElfW(Sym) *sym = self->dynsym + i;
if (0 != strcmp(self->dynstr + sym->st_name, sym_name)) continue;
return sym;
}
return NULL;
}
static ElfW(Sym) *xdl_dynsym_find_symbol_use_gnu_hash(xdl_t *self, const char *sym_name) {
uint32_t hash = xdl_gnu_hash((const uint8_t *)sym_name);
static uint32_t elfclass_bits = sizeof(ElfW(Addr)) * 8;
size_t word = self->gnu_hash.bloom[(hash / elfclass_bits) % self->gnu_hash.bloom_cnt];
size_t mask = 0 | (size_t)1 << (hash % elfclass_bits) |
(size_t)1 << ((hash >> self->gnu_hash.bloom_shift) % elfclass_bits);
// if at least one bit is not set, this symbol is surely missing
if ((word & mask) != mask) return NULL;
// ignore STN_UNDEF
uint32_t i = self->gnu_hash.buckets[hash % self->gnu_hash.buckets_cnt];
if (i < self->gnu_hash.symoffset) return NULL;
// loop through the chain
while (1) {
ElfW(Sym) *sym = self->dynsym + i;
uint32_t sym_hash = self->gnu_hash.chains[i - self->gnu_hash.symoffset];
if ((hash | (uint32_t)1) == (sym_hash | (uint32_t)1)) {
if (0 == strcmp(self->dynstr + sym->st_name, sym_name)) {
return sym;
}
}
// chain ends with an element with the lowest bit set to 1
if (sym_hash & (uint32_t)1) break;
i++;
}
return NULL;
}
void *xdl_sym(void *handle, const char *symbol, size_t *symbol_size) {
if (NULL == handle || NULL == symbol) return NULL;
if (NULL != symbol_size) *symbol_size = 0;
xdl_t *self = (xdl_t *)handle;
// load .dynsym only once
if (!self->dynsym_try_load) {
self->dynsym_try_load = true;
if (0 != xdl_dynsym_load(self)) return NULL;
}
// find symbol
if (NULL == self->dynsym) return NULL;
ElfW(Sym) *sym = NULL;
if (self->gnu_hash.buckets_cnt > 0) {
// use GNU hash (.gnu.hash -> .dynsym -> .dynstr), O(x) + O(1) + O(1)
sym = xdl_dynsym_find_symbol_use_gnu_hash(self, symbol);
}
if (NULL == sym && self->sysv_hash.buckets_cnt > 0) {
// use SYSV hash (.hash -> .dynsym -> .dynstr), O(x) + O(1) + O(1)
sym = xdl_dynsym_find_symbol_use_sysv_hash(self, symbol);
}
if (NULL == sym || !XDL_DYNSYM_IS_EXPORT_SYM(sym->st_shndx)) return NULL;
if (NULL != symbol_size) *symbol_size = sym->st_size;
return (void *)(self->load_bias + sym->st_value);
}
void *xdl_dsym(void *handle, const char *symbol, size_t *symbol_size) {
if (NULL == handle || NULL == symbol) return NULL;
if (NULL != symbol_size) *symbol_size = 0;
xdl_t *self = (xdl_t *)handle;
// load .symtab only once
if (!self->symtab_try_load) {
self->symtab_try_load = true;
if (0 != xdl_symtab_load(self)) return NULL;
}
// find symbol
if (NULL == self->symtab) return NULL;
for (size_t i = 0; i < self->symtab_cnt; i++) {
ElfW(Sym) *sym = self->symtab + i;
if (!XDL_SYMTAB_IS_EXPORT_SYM(sym->st_shndx)) continue;
if (0 != strncmp(self->strtab + sym->st_name, symbol, self->strtab_sz - sym->st_name)) continue;
if (NULL != symbol_size) *symbol_size = sym->st_size;
return (void *)(self->load_bias + sym->st_value);
}
return NULL;
}
static bool xdl_elf_is_match(uintptr_t load_bias, const ElfW(Phdr) *dlpi_phdr, ElfW(Half) dlpi_phnum,
uintptr_t addr) {
if (addr < load_bias) return false;
uintptr_t vaddr = addr - load_bias;
for (size_t i = 0; i < dlpi_phnum; i++) {
const ElfW(Phdr) *phdr = &(dlpi_phdr[i]);
if (PT_LOAD != phdr->p_type) continue;
if (phdr->p_vaddr <= vaddr && vaddr < phdr->p_vaddr + phdr->p_memsz) return true;
}
return false;
}
static int xdl_open_by_addr_iterate_cb(struct dl_phdr_info *info, size_t size, void *arg) {
(void)size;
uintptr_t *pkg = (uintptr_t *)arg;
xdl_t **self = (xdl_t **)*pkg++;
uintptr_t addr = *pkg;
if (xdl_elf_is_match(info->dlpi_addr, info->dlpi_phdr, info->dlpi_phnum, addr)) {
// found the target ELF
if (NULL == ((*self) = calloc(1, sizeof(xdl_t)))) return 1; // failed
if (NULL == ((*self)->pathname = strdup(info->dlpi_name))) {
free(*self);
*self = NULL;
return 1; // failed
}
(*self)->load_bias = info->dlpi_addr;
(*self)->dlpi_phdr = info->dlpi_phdr;
(*self)->dlpi_phnum = info->dlpi_phnum;
(*self)->dynsym_try_load = false;
(*self)->symtab_try_load = false;
return 1; // OK
}
return 0; // mismatch
}
static void *xdl_open_by_addr(void *addr) {
if (NULL == addr) return NULL;
xdl_t *self = NULL;
uintptr_t pkg[2] = {(uintptr_t)&self, (uintptr_t)addr};
xdl_iterate_phdr(xdl_open_by_addr_iterate_cb, pkg, XDL_DEFAULT);
return (void *)self;
}
static bool xdl_sym_is_match(ElfW(Sym) *sym, uintptr_t offset, bool is_symtab) {
if (is_symtab) {
if (!XDL_SYMTAB_IS_EXPORT_SYM(sym->st_shndx)) false;
} else {
if (!XDL_DYNSYM_IS_EXPORT_SYM(sym->st_shndx)) false;
}
return ELF_ST_TYPE(sym->st_info) != STT_TLS && offset >= sym->st_value &&
offset < sym->st_value + sym->st_size;
}
static ElfW(Sym) *xdl_sym_by_addr(void *handle, void *addr) {
xdl_t *self = (xdl_t *)handle;
// load .dynsym only once
if (!self->dynsym_try_load) {
self->dynsym_try_load = true;
if (0 != xdl_dynsym_load(self)) return NULL;
}
// find symbol
if (NULL == self->dynsym) return NULL;
uintptr_t offset = (uintptr_t)addr - self->load_bias;
if (self->gnu_hash.buckets_cnt > 0) {
const uint32_t *chains_all = self->gnu_hash.chains - self->gnu_hash.symoffset;
for (size_t i = 0; i < self->gnu_hash.buckets_cnt; i++) {
uint32_t n = self->gnu_hash.buckets[i];
if (n < self->gnu_hash.symoffset) continue;
do {
ElfW(Sym) *sym = self->dynsym + n;
if (xdl_sym_is_match(sym, offset, false)) return sym;
} while ((chains_all[n++] & 1) == 0);
}
} else if (self->sysv_hash.chains_cnt > 0) {
for (size_t i = 0; i < self->sysv_hash.chains_cnt; i++) {
ElfW(Sym) *sym = self->dynsym + i;
if (xdl_sym_is_match(sym, offset, false)) return sym;
}
}
return NULL;
}
static ElfW(Sym) *xdl_dsym_by_addr(void *handle, void *addr) {
xdl_t *self = (xdl_t *)handle;
// load .symtab only once
if (!self->symtab_try_load) {
self->symtab_try_load = true;
if (0 != xdl_symtab_load(self)) return NULL;
}
// find symbol
if (NULL == self->symtab) return NULL;
uintptr_t offset = (uintptr_t)addr - self->load_bias;
for (size_t i = 0; i < self->symtab_cnt; i++) {
ElfW(Sym) *sym = self->symtab + i;
if (xdl_sym_is_match(sym, offset, true)) return sym;
}
return NULL;
}
int xdl_addr(void *addr, xdl_info_t *info, void **cache) {
if (NULL == addr || NULL == info || NULL == cache) return 0;
memset(info, 0, sizeof(Dl_info));
// find handle from cache
xdl_t *handle = NULL;
for (handle = *((xdl_t **)cache); NULL != handle; handle = handle->next)
if (xdl_elf_is_match(handle->load_bias, handle->dlpi_phdr, handle->dlpi_phnum, (uintptr_t)addr)) break;
// create new handle, save handle to cache
if (NULL == handle) {
handle = (xdl_t *)xdl_open_by_addr(addr);
if (NULL == handle) return 0;
handle->next = *(xdl_t **)cache;
*(xdl_t **)cache = handle;
}
// we have at least: load_bias, pathname, dlpi_phdr, dlpi_phnum
info->dli_fbase = (void *)handle->load_bias;
info->dli_fname = handle->pathname;
info->dli_sname = NULL;
info->dli_saddr = 0;
info->dli_ssize = 0;
info->dlpi_phdr = handle->dlpi_phdr;
info->dlpi_phnum = (size_t)handle->dlpi_phnum;
// keep looking for: symbol name, symbol offset, symbol size
ElfW(Sym) *sym;
if (NULL != (sym = xdl_sym_by_addr((void *)handle, addr))) {
info->dli_sname = handle->dynstr + sym->st_name;
info->dli_saddr = (void *)(handle->load_bias + sym->st_value);
info->dli_ssize = sym->st_size;
} else if (NULL != (sym = xdl_dsym_by_addr((void *)handle, addr))) {
info->dli_sname = handle->strtab + sym->st_name;
info->dli_saddr = (void *)(handle->load_bias + sym->st_value);
info->dli_ssize = sym->st_size;
}
return 1;
}
void xdl_addr_clean(void **cache) {
if (NULL == cache) return;
xdl_t *handle = *((xdl_t **)cache);
while (NULL != handle) {
xdl_t *tmp = handle;
handle = handle->next;
xdl_close(tmp);
}
*cache = NULL;
}
int xdl_iterate_phdr(int (*callback)(struct dl_phdr_info *, size_t, void *), void *data, int flags) {
if (NULL == callback) return 0;
return xdl_iterate_phdr_impl(callback, data, flags);
}
int xdl_info(void *handle, int request, void *info) {
if (NULL == handle || XDL_DI_DLINFO != request || NULL == info) return -1;
xdl_t *self = (xdl_t *)handle;
xdl_info_t *dlinfo = (xdl_info_t *)info;
dlinfo->dli_fbase = (void *)self->load_bias;
dlinfo->dli_fname = self->pathname;
dlinfo->dli_sname = NULL;
dlinfo->dli_saddr = 0;
dlinfo->dli_ssize = 0;
dlinfo->dlpi_phdr = self->dlpi_phdr;
dlinfo->dlpi_phnum = (size_t)self->dlpi_phnum;
return 0;
}

View File

@@ -0,0 +1,297 @@
// Copyright (c) 2020-2021 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;
}

View File

@@ -0,0 +1,43 @@
// Copyright (c) 2020-2021 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_HEXHACKING_XDL_ITERATE
#define IO_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

View File

@@ -0,0 +1,187 @@
// Copyright (c) 2020-2021 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"
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;
static void *xdl_linker_caller_addr[] = {
NULL, // default
NULL, // art
NULL // vendor
};
#ifndef __LP64__
#define XDL_LINKER_LIB "lib"
#else
#define XDL_LINKER_LIB "lib64"
#endif
static const char *xdl_linker_vendor_path[] = {
// order is important
"/vendor/" XDL_LINKER_LIB "/egl/", "/vendor/" XDL_LINKER_LIB "/hw/",
"/vendor/" XDL_LINKER_LIB "/", "/odm/" XDL_LINKER_LIB "/",
"/vendor/" XDL_LINKER_LIB "/vndk-sp/", "/odm/" XDL_LINKER_LIB "/vndk-sp/"};
static void xdl_linker_init(void) {
static bool inited = false;
if (inited) return;
inited = true;
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);
}
void xdl_linker_lock(void) {
xdl_linker_init();
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 int xdl_linker_get_caller_addr_cb(struct dl_phdr_info *info, size_t size, void *arg) {
(void)size;
size_t *vendor_match = (size_t *)arg;
if (0 == info->dlpi_addr || NULL == info->dlpi_name) return 0; // continue
if (NULL == xdl_linker_caller_addr[0] && xdl_util_ends_with(info->dlpi_name, "/libc.so"))
xdl_linker_caller_addr[0] = xdl_linker_get_caller_addr(info);
if (NULL == xdl_linker_caller_addr[1] && xdl_util_ends_with(info->dlpi_name, "/libart.so"))
xdl_linker_caller_addr[1] = xdl_linker_get_caller_addr(info);
if (0 != *vendor_match) {
for (size_t i = 0; i < *vendor_match; i++) {
if (xdl_util_starts_with(info->dlpi_name, xdl_linker_vendor_path[i])) {
void *caller_addr = xdl_linker_get_caller_addr(info);
if (NULL != caller_addr) {
xdl_linker_caller_addr[2] = caller_addr;
*vendor_match = i;
}
}
}
}
if (NULL != xdl_linker_caller_addr[0] && NULL != xdl_linker_caller_addr[1] && 0 == *vendor_match) {
return 1; // finish
} else {
return 0; // continue
}
}
static void xdl_linker_load_caller_addr(void) {
if (NULL == xdl_linker_caller_addr[0]) {
size_t vendor_match = sizeof(xdl_linker_vendor_path) / sizeof(xdl_linker_vendor_path[0]);
xdl_iterate_phdr_impl(xdl_linker_get_caller_addr_cb, &vendor_match, XDL_DEFAULT);
}
}
void *xdl_linker_load(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();
if (NULL == xdl_linker_dlopen) return NULL;
xdl_linker_load_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_caller_addr) / sizeof(xdl_linker_caller_addr[0]); i++) {
if (NULL != xdl_linker_caller_addr[i]) {
handle =
((xdl_linker_dlopen_n_t)xdl_linker_dlopen)(filename, RTLD_NOW, NULL, xdl_linker_caller_addr[i]);
if (NULL != handle) break;
}
}
xdl_linker_unlock();
} else {
// >= Android 8.0
for (size_t i = 0; i < sizeof(xdl_linker_caller_addr) / sizeof(xdl_linker_caller_addr[0]); i++) {
if (NULL != xdl_linker_caller_addr[i]) {
handle = ((xdl_linker_dlopen_o_t)xdl_linker_dlopen)(filename, RTLD_NOW, xdl_linker_caller_addr[i]);
if (NULL != handle) break;
}
}
}
return handle;
}
}

View File

@@ -0,0 +1,40 @@
// Copyright (c) 2020-2021 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_HEXHACKING_XDL_LINKER
#define IO_HEXHACKING_XDL_LINKER
#ifdef __cplusplus
extern "C" {
#endif
void xdl_linker_lock(void);
void xdl_linker_unlock(void);
void *xdl_linker_load(const char *filename);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,181 @@
// Copyright (c) 2020-2021 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 <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 *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 bool inited = false;
if (!inited) {
xdl_lzma_init();
inited = true;
}
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;
}

View File

@@ -0,0 +1,40 @@
// Copyright (c) 2020-2021 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_HEXHACKING_XDL_LZMA
#define IO_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

View File

@@ -0,0 +1,95 @@
// Copyright (c) 2020-2021 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;
}

View File

@@ -0,0 +1,71 @@
// Copyright (c) 2020-2021 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_HEXHACKING_XDL_UTIL
#define IO_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

View File

@@ -0,0 +1,326 @@
// 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
/*
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.
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!
Example code:
static jint (*orig_logger_entry_max)(JNIEnv *env);
static jint my_logger_entry_max(JNIEnv *env) { return orig_logger_entry_max(env); }
static void example_handler(int socket) { ... }
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)
REGISTER_ZYGISK_COMPANION(example_handler)
*/
namespace zygisk {
struct Api;
struct AppSpecializeArgs;
struct ServerSpecializeArgs;
class ModuleBase {
public:
// This function is called when the module is loaded into the target process.
// A Zygisk API handle will be sent as an argument; call utility functions or interface
// with Zygisk through this handle.
virtual void onLoad([[maybe_unused]] Api *api, [[maybe_unused]] JNIEnv *env) {}
// This function 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 function 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 function runs as the same privilege of the app's own code.
virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {}
// This function is called before the system server process is specialized.
// See preAppSpecialize(args) for more info.
virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {}
// This function 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 functions 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 functions due to SELinux restrictions.
//
// The pre[XXX]Specialize functions 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 function 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 functions.
// Accessing the directory returned is only possible in the pre[XXX]Specialize functions
// 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 function 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 functions.
// 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);
// For ELFs loaded in memory 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 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 *impl;
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 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 a globally shared resource.
#define REGISTER_ZYGISK_COMPANION(func) \
void zygisk_companion_entry(int client) { func(client); }
/************************************************************************************
* All the code after this point is internal code used to interface with Zygisk
* and guarantee ABI stability. You do not have to understand what it is doing.
************************************************************************************/
namespace internal {
struct module_abi {
long api_version;
ModuleBase *_this;
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), _this(module) {
preAppSpecialize = [](auto self, auto args) { self->preAppSpecialize(args); };
postAppSpecialize = [](auto self, auto args) { self->postAppSpecialize(args); };
preServerSpecialize = [](auto self, auto args) { self->preServerSpecialize(args); };
postServerSpecialize = [](auto self, auto args) { self->postServerSpecialize(args); };
}
};
struct api_table {
// These first 2 entries are permanent, shall never change
void *_this;
bool (*registerModule)(api_table *, module_abi *);
// Utility functions
void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);
void (*pltHookRegister)(const char *, const char *, void *, void **);
void (*pltHookExclude)(const char *, const char *);
bool (*pltHookCommit)();
// Zygisk functions
int (*connectCompanion)(void * /* _this */);
void (*setOption)(void * /* _this */, Option);
int (*getModuleDir)(void * /* _this */);
uint32_t (*getFlags)(void * /* _this */);
};
template <class T>
void entry_impl(api_table *table, JNIEnv *env) {
ModuleBase *module = new T();
if (!table->registerModule(table, new module_abi(module)))
return;
auto api = new Api();
api->impl = table;
module->onLoad(api, env);
}
} // namespace internal
inline int Api::connectCompanion() {
return impl->connectCompanion ? impl->connectCompanion(impl->_this) : -1;
}
inline int Api::getModuleDir() {
return impl->getModuleDir ? impl->getModuleDir(impl->_this) : -1;
}
inline void Api::setOption(Option opt) {
if (impl->setOption) impl->setOption(impl->_this, opt);
}
inline uint32_t Api::getFlags() {
return impl->getFlags ? impl->getFlags(impl->_this) : 0;
}
inline void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) {
if (impl->hookJniNativeMethods) impl->hookJniNativeMethods(env, className, methods, numMethods);
}
inline void Api::pltHookRegister(const char *regex, const char *symbol, void *newFunc, void **oldFunc) {
if (impl->pltHookRegister) impl->pltHookRegister(regex, symbol, newFunc, oldFunc);
}
inline void Api::pltHookExclude(const char *regex, const char *symbol) {
if (impl->pltHookExclude) impl->pltHookExclude(regex, symbol);
}
inline bool Api::pltHookCommit() {
return impl->pltHookCommit != nullptr && impl->pltHookCommit();
}
} // namespace zygisk
[[gnu::visibility("default")]] [[gnu::used]]
extern "C" void zygisk_module_entry(zygisk::internal::api_table *, JNIEnv *);
[[gnu::visibility("default")]] [[gnu::used]]
extern "C" void zygisk_companion_entry(int);