├── .gitignore ├── ADI ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── singularity │ │ └── ptraceinject │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── cpp │ │ ├── CMakeLists.txt │ │ └── adi │ │ │ ├── CMakeLists.txt │ │ │ ├── PtraceUtils.h │ │ │ ├── Utils.h │ │ │ ├── contorlProcess.cpp │ │ │ ├── contorlProcess.h │ │ │ ├── elf_symbol_resolver.cpp │ │ │ ├── elf_symbol_resolver.h │ │ │ ├── json.hpp │ │ │ ├── logging.cpp │ │ │ ├── logging.h │ │ │ ├── main.cpp │ │ │ ├── parse_args.cpp │ │ │ └── parse_args.h │ └── java │ │ └── com │ │ └── singularity │ │ └── ptraceinject │ │ └── NativeLib.java │ └── test │ └── java │ └── com │ └── singularity │ └── ptraceinject │ └── ExampleUnitTest.java ├── ADILib ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── hepta │ │ └── zygisk │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── cpp │ │ ├── CMakeLists.txt │ │ ├── DrmHook │ │ │ └── DrmHook.cpp │ │ ├── inject.json │ │ ├── inject2.json │ │ └── lsplt │ │ │ ├── .clang-format │ │ │ ├── .clang-tidy │ │ │ ├── CMakeLists.txt │ │ │ ├── elf_util.cc │ │ │ ├── elf_util.hpp │ │ │ ├── include │ │ │ └── lsplt.hpp │ │ │ ├── logging.hpp │ │ │ ├── lsplt.cc │ │ │ └── syscall.hpp │ └── java │ │ └── com │ │ └── hepta │ │ └── adilib │ │ └── NativeLib.java │ └── test │ └── java │ └── com │ └── hepta │ └── adilib │ └── ExampleUnitTest.java ├── README.md ├── Zygisk ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── hepta │ │ └── zygisk │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── cpp │ │ ├── CMakeLists.txt │ │ ├── common │ │ │ ├── daemon.cpp │ │ │ ├── daemon.h │ │ │ ├── dl.cpp │ │ │ ├── dl.h │ │ │ ├── elf_symbol_resolver.cpp │ │ │ ├── elf_symbol_resolver.h │ │ │ ├── files.cpp │ │ │ ├── files.hpp │ │ │ ├── logging.cpp │ │ │ ├── logging.h │ │ │ ├── misc.cpp │ │ │ ├── misc.hpp │ │ │ ├── socket_utils.cpp │ │ │ └── socket_utils.h │ │ ├── lsplt │ │ │ ├── .clang-format │ │ │ ├── .clang-tidy │ │ │ ├── CMakeLists.txt │ │ │ ├── elf_util.cc │ │ │ ├── elf_util.hpp │ │ │ ├── include │ │ │ │ └── lsplt.hpp │ │ │ ├── logging.hpp │ │ │ ├── lsplt.cc │ │ │ └── syscall.hpp │ │ ├── zygisk │ │ │ ├── api.hpp │ │ │ ├── art_method.hpp │ │ │ ├── clean.cpp │ │ │ ├── clean.h │ │ │ ├── elf_util.cpp │ │ │ ├── elf_util.hpp │ │ │ ├── entry.cpp │ │ │ ├── gen_jni_hooks.py │ │ │ ├── hook.cpp │ │ │ ├── jni_helper.hpp │ │ │ ├── jni_hooks.hpp │ │ │ ├── module.cpp │ │ │ ├── module.hpp │ │ │ ├── solist.hpp │ │ │ ├── unmount.cpp │ │ │ └── zygisk.hpp │ │ └── zygiskd │ │ │ ├── RootImp.cpp │ │ │ ├── RootImp.h │ │ │ ├── apatch.cpp │ │ │ ├── apatch.h │ │ │ ├── companion.cpp │ │ │ ├── companion.h │ │ │ ├── ksu.cpp │ │ │ ├── ksu.h │ │ │ ├── magisk.cpp │ │ │ ├── magisk.h │ │ │ ├── zygiskd.cpp │ │ │ └── zygiskd.h │ └── java │ │ └── com │ │ └── hepta │ │ └── zygisk │ │ └── NativeLib.java │ └── test │ └── java │ └── com │ └── hepta │ └── zygisk │ └── ExampleUnitTest.java ├── build.gradle.kts ├── doc ├── images │ ├── start.jpg │ └── wx.jpg └── zygisk Mount.md ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── module ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── META-INF │ └── com │ │ └── google │ │ └── android │ │ ├── update-binary │ │ └── updater-script │ ├── module.prop │ ├── post-fs-data.sh │ ├── sepolicy.rule │ ├── service.sh │ ├── verify.sh │ ├── zygisk.json │ ├── zygisk3.json │ └── zygisk5.json └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties 11 | -------------------------------------------------------------------------------- /ADI/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /ADI/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | } 4 | 5 | 6 | val defaultCFlags = arrayOf( 7 | "-Wall", "-Wextra", 8 | "-fno-rtti", "-fno-exceptions", 9 | "-fno-stack-protector", "-fomit-frame-pointer", 10 | "-Wno-builtin-macro-redefined", "-D__FILE__=__FILE_NAME__" 11 | ) 12 | 13 | val releaseFlags = arrayOf( 14 | "-Oz", "-flto", 15 | "-Wno-unused", "-Wno-unused-parameter", 16 | "-fvisibility=hidden", "-fvisibility-inlines-hidden", 17 | "-fno-unwind-tables", "-fno-asynchronous-unwind-tables", 18 | "-Wl,--exclude-libs,ALL", "-Wl,--gc-sections", "-Wl,--strip-all" 19 | ) 20 | 21 | android { 22 | 23 | defaultConfig { 24 | 25 | externalNativeBuild { 26 | cmake { 27 | // arguments += "-DANDROID_STL=none" //关闭原生stl 如何打卡将关闭ndk自带的stl,需要在导入stl库 28 | cFlags("-std=c18", *defaultCFlags) 29 | cppFlags("-std=c++20", *defaultCFlags) 30 | // abiFilters.add("armeabi-v7a") 31 | abiFilters.add("arm64-v8a") 32 | 33 | } 34 | } 35 | } 36 | 37 | buildTypes { 38 | release { 39 | isMinifyEnabled = false 40 | proguardFiles( 41 | getDefaultProguardFile("proguard-android-optimize.txt"), 42 | "proguard-rules.pro" 43 | ) 44 | } 45 | } 46 | externalNativeBuild.cmake { 47 | path("src/main/cpp/CMakeLists.txt") 48 | version = "3.22.1" 49 | } 50 | 51 | buildFeatures { 52 | prefab = true 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ADI/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thehepta/Android-Debug-Inject/8a5450a38e9c2a6cc97c98e717d116ad34c3b9af/ADI/consumer-rules.pro -------------------------------------------------------------------------------- /ADI/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /ADI/src/androidTest/java/com/singularity/ptraceinject/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.singularity.ptraceinject; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("com.singularity.ptraceinject.test", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /ADI/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ADI/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # For more information about using CMake with Android Studio, read the 2 | # documentation: https://d.android.com/studio/projects/add-native-code.html. 3 | # For more examples on how to use CMake, see https://github.com/android/ndk-samples. 4 | 5 | # Sets the minimum CMake version required for this project. 6 | cmake_minimum_required(VERSION 3.22.1) 7 | 8 | add_subdirectory(adi) 9 | -------------------------------------------------------------------------------- /ADI/src/main/cpp/adi/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22.1) 2 | project (adi) 3 | # The version number. 4 | set (Tutorial_VERSION_MAJOR 1) 5 | set (Tutorial_VERSION_MINOR 0) 6 | 7 | # configure a header file to pass some of the CMake settings 8 | # to the source code 9 | 10 | # add the binary tree to the search path for include files 11 | # so that we will find TutorialConfig.h 12 | 13 | # add the executable 14 | 15 | #set(CMAKE_CXX_STANDARD 17) 16 | 17 | 18 | 19 | add_executable(adi main.cpp contorlProcess.cpp logging.cpp elf_symbol_resolver.cpp parse_args.cpp) 20 | 21 | target_link_libraries( # Specifies the target library. 22 | adi 23 | log) 24 | -------------------------------------------------------------------------------- /ADI/src/main/cpp/adi/contorlProcess.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by chic on 2024/11/19. 3 | // 4 | 5 | #pragma once 6 | #include 7 | #include 8 | #include 9 | #include 10 | #define STOPPED_WITH(status,sig, event) WIFSTOPPED(status) && (status >> 8 == ((sig) | (event << 8))) 11 | void func_test(int argc, char *argv[]); 12 | 13 | bool inject_process(pid_t pid,const char *LibPath,const char *FunctionName,const char*FunctionArgs); 14 | class ContorlProcess { 15 | public: 16 | 17 | std::string exec; 18 | std::string waitSoPath; 19 | std::string waitFunSym; 20 | std::string InjectSO; 21 | std::string InjectFunSym; 22 | std::string InjectFunArg; 23 | unsigned int monitorCount; 24 | 25 | }; 26 | 27 | 28 | class InjectProc { 29 | 30 | public: 31 | 32 | 33 | void setTracePid(pid_t pid){ 34 | traced_pid = pid; 35 | } 36 | 37 | std::set& get_Tracee_Process(){ 38 | return monitor_pid; 39 | } 40 | pid_t getTracePid(){ 41 | return traced_pid; 42 | } 43 | 44 | void monitor_process(pid_t pid); 45 | 46 | bool filter_proce_exec_file(pid_t pid, ContorlProcess &cp); 47 | 48 | void add_childProces(ContorlProcess cp){ 49 | cps.emplace_back(cp); 50 | } 51 | 52 | // 获取单例实例的静态方法 53 | static InjectProc& getInstance() { 54 | static InjectProc instance; // 使用static保证只创建一次 55 | return instance; 56 | } 57 | 58 | private: 59 | InjectProc(){ 60 | 61 | } 62 | std::vector cps; 63 | std::string requestoSocket; 64 | pid_t traced_pid; 65 | std::string zygote64_Inject_So; 66 | std::string zygote32_Inject_So; 67 | std::set monitor_pid; 68 | 69 | }; -------------------------------------------------------------------------------- /ADI/src/main/cpp/adi/elf_symbol_resolver.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by chic on 2023/6/2. 3 | // 4 | 5 | #pragma once 6 | 7 | 8 | 9 | class SymbolName { 10 | public: 11 | explicit SymbolName(const char* name) 12 | : name_(name), has_elf_hash_(false), has_gnu_hash_(false), 13 | elf_hash_(0), gnu_hash_(0) { } 14 | 15 | const char* get_name() { 16 | return name_; 17 | } 18 | 19 | 20 | uint32_t elf_hash(); 21 | uint32_t gnu_hash(); 22 | 23 | 24 | private: 25 | const char* name_; 26 | bool has_elf_hash_; 27 | bool has_gnu_hash_; 28 | uint32_t elf_hash_; 29 | uint32_t gnu_hash_; 30 | 31 | }; 32 | 33 | void *get_remote_load_Sym_Addr(void *so_addr, pid_t pid, const char *symbol_name) ; 34 | 35 | void *get_self_load_Sym_Addr(const char *library_name, const char *symbol_name) ; 36 | 37 | uintptr_t get_libFile_Symbol_off(char *lib_path,char *fun_name); -------------------------------------------------------------------------------- /ADI/src/main/cpp/adi/logging.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "logging.h" 4 | 5 | namespace logging { 6 | static int logfd = -1; 7 | 8 | void setfd(int fd) { 9 | close(logfd); 10 | logfd = fd; 11 | } 12 | 13 | int getfd() { 14 | return logfd; 15 | } 16 | 17 | void log(int prio, const char* tag, const char* fmt, ...) { 18 | if (logfd == -1) { 19 | va_list ap; 20 | va_start(ap, fmt); 21 | __android_log_vprint(prio, tag, fmt, ap); 22 | va_end(ap); 23 | } else { 24 | char buf[4096]; 25 | va_list ap; 26 | va_start(ap, fmt); 27 | vsnprintf(buf, sizeof(buf), fmt, ap); 28 | va_end(ap); 29 | 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ADI/src/main/cpp/adi/logging.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #ifndef LOG_TAG 8 | #if defined(__LP64__) 9 | # define LOG_TAG "ADI" 10 | #else 11 | # define LOG_TAG "ADI32" 12 | #endif 13 | #endif 14 | 15 | 16 | #ifndef NDEBUG 17 | #define LOGD(...) logging::log(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) 18 | #define LOGV(...) logging::log(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) 19 | #else 20 | #define LOGD(...) 21 | #define LOGV(...) 22 | #endif 23 | 24 | #define LOGI(...) logging::log(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) 25 | #define LOGW(...) logging::log(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) 26 | #define LOGE(...) logging::log(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) 27 | #define LOGF(...) logging::log(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__) 28 | #define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno)) 29 | 30 | namespace logging { 31 | void setfd(int fd); 32 | 33 | int getfd(); 34 | 35 | [[gnu::format(printf, 3, 4)]] 36 | void log(int prio, const char* tag, const char* fmt, ...); 37 | } 38 | -------------------------------------------------------------------------------- /ADI/src/main/cpp/adi/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "json.hpp" 17 | #include "contorlProcess.h" 18 | #include "logging.h" 19 | #include "parse_args.h" 20 | using namespace std; 21 | using json = nlohmann::json; 22 | 23 | #define WPTEVENT(x) (x >> 16) 24 | 25 | 26 | inline const char* sigabbrev_np(int sig) { 27 | if (sig > 0 && sig < NSIG) return sys_signame[sig]; 28 | return "(unknown)"; 29 | } 30 | 31 | 32 | 33 | 34 | [[noreturn]] 35 | void PtraceTask(){ 36 | InjectProc & injectProc = InjectProc::getInstance(); 37 | pid_t tracd_pid = injectProc.getTracePid(); 38 | ptrace(PTRACE_SEIZE, tracd_pid, 0, PTRACE_O_TRACEFORK); 39 | int status; 40 | while(true){ 41 | int pid = waitpid(-1, &status, __WALL); 42 | if (tracd_pid == -1) { 43 | continue; 44 | } else if(tracd_pid == 0){ 45 | continue; 46 | } 47 | if(tracd_pid == pid){ 48 | 49 | if (WIFEXITED(status) || WIFSIGNALED(status)) { 50 | LOGE("ptrace process exited\n"); 51 | // kill(getpid(),SIGINT); 52 | continue; 53 | } 54 | if (STOPPED_WITH(status,SIGTRAP, PTRACE_EVENT_FORK)) { 55 | long child_pid; 56 | ptrace(PTRACE_GETEVENTMSG, pid, 0, &child_pid); 57 | LOGD("int fork monitor : %ld\n",child_pid); 58 | 59 | } else if (STOPPED_WITH(status,SIGTRAP, PTRACE_EVENT_STOP) ) { 60 | if (ptrace(PTRACE_DETACH, pid, 0, 0) == -1) 61 | LOGE("failed to detach init\n"); 62 | LOGE("stop tracing init\n"); 63 | continue; 64 | } 65 | 66 | if (WIFSTOPPED(status)) { 67 | 68 | if (WPTEVENT(status) == 0) { 69 | if (WSTOPSIG(status) != SIGSTOP && WSTOPSIG(status) != SIGTSTP && WSTOPSIG(status) != SIGTTIN && WSTOPSIG(status) != SIGTTOU) { 70 | LOGD("recv signal : %s %d\n",sigabbrev_np(WSTOPSIG(status)),WSTOPSIG(status)); 71 | ptrace(PTRACE_CONT, pid, 0, WSTOPSIG(status)); 72 | continue; 73 | } else { 74 | LOGD("suppress stopping signal sent to init: %s %d\n",sigabbrev_np(WSTOPSIG(status)), WSTOPSIG(status)); 75 | } 76 | } 77 | ptrace(PTRACE_CONT, pid, 0, 0); 78 | } 79 | 80 | } else{ 81 | 82 | std::set &process = injectProc.get_Tracee_Process(); 83 | auto state = process.find(pid); 84 | if (state == process.end()) { //运行到这里说明都是子进程信号,所以要么是新创建的子进程,要么是符合条件的子进程 85 | // 新创建的子进程会会加入到监控队列,如果是旧的子进程,会走else的分支 86 | LOGD("new process attached %d",pid); 87 | process.emplace(pid); 88 | //前面ptrace的时候,使用的是PTRACE_O_TRACEFORK,所以子进程会在调用fork以后停止,并被追踪到 89 | ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACEEXEC); //这段代码 进程会停止在exec加载完,但是还没没有执行的时候 90 | ptrace(PTRACE_CONT, pid, 0, 0); 91 | continue; 92 | }else{ 93 | //旧的子继承,等待他执行完exec,这个时候只是加载了可执行文件,我们可以判断是那个进程了. 94 | //所以在这里停止,如果在前面停止,我们很难知道要运行的进程是那个. 95 | 96 | //然后通过文件判断是否符合过滤的进程要求, 97 | LOGD("old process attached %d",pid); 98 | if (STOPPED_WITH(status,SIGTRAP, PTRACE_EVENT_EXEC)){ 99 | kill(pid, SIGSTOP); // 信号会在进程运行起来以后接受到 100 | ptrace(PTRACE_CONT, pid, 0, 0); //由于进程当前已经停止,所以先运行起来 101 | waitpid(pid, &status, __WALL); 102 | if (STOPPED_WITH(status,SIGSTOP, 0)) { //这个就是接受到的信号,前面 kill(pid, SIGSTOP); 发送的 103 | injectProc.monitor_process(pid); 104 | ptrace(PTRACE_DETACH, pid, 0, 0); 105 | 106 | } 107 | } else { 108 | LOGE("old process handle: STOPPED_WITH is not"); 109 | } 110 | 111 | process.erase(state); 112 | if (WIFSTOPPED(status)) { 113 | LOGE("detach process"); 114 | ptrace(PTRACE_DETACH, pid, 0, 0); 115 | } 116 | } 117 | } 118 | } 119 | } 120 | 121 | 122 | void clean_trace(int arg) { 123 | LOGE("clean_trace "); 124 | InjectProc & injectProc = InjectProc::getInstance(); 125 | std::set &process = injectProc.get_Tracee_Process(); 126 | for (auto pid:process){ 127 | LOGD("clean_trace detach pid: %d",pid); 128 | ptrace(PTRACE_DETACH, pid, nullptr, nullptr); 129 | } 130 | LOGD("clean_trace trace pid: %d",injectProc.getTracePid()); 131 | ptrace(PTRACE_DETACH, injectProc.getTracePid(), nullptr, nullptr); 132 | exit(0); 133 | } 134 | int inject_main(pid_t inject_pid,char*InjectSO,char* InjectFunSym,char*InjectFunArg){ 135 | 136 | } 137 | 138 | int tracee_main_cmd(pid_t tracee_pid,ContorlProcess &cp){ 139 | InjectProc & injectProc = InjectProc::getInstance(); 140 | injectProc.setTracePid(tracee_pid); 141 | if(tracee_pid <0){ 142 | LOGD("traced_pid is error"); 143 | return 0; 144 | } 145 | 146 | injectProc.add_childProces(cp); 147 | std::thread ptraceThread(PtraceTask); 148 | ptraceThread.join(); 149 | } 150 | int tracee_main_config(char * file){ 151 | std::ifstream f(file); 152 | json jsonData = nlohmann::json::parse(f); 153 | InjectProc & injectProc = InjectProc::getInstance(); 154 | json array = jsonData["childProcess"]; 155 | pid_t traced_pid = jsonData.value("traced_pid",-1); 156 | if(traced_pid <0){ 157 | LOGD("traced_pid is error"); 158 | return 0; 159 | } 160 | if(!array.is_array()){ 161 | LOGD("config File is error"); 162 | LOGD("childProcess is not array"); 163 | return 0; 164 | } 165 | for(const auto& e : array){ 166 | std::string exec = e.value("exec", ""); 167 | std::string waitSoPath = e.value("waitSoPath", ""); 168 | std::string waitFunSym = e.value("waitFunSym", ""); 169 | std::string InjectSO = e.value("InjectSO", ""); 170 | std::string InjectFunSym = e.value("InjectFunSym", ""); 171 | std::string InjectFunArg = e.value("InjectFunArg", ""); 172 | unsigned int monitorCount = e.value("monitorCount", 0); 173 | auto cp = ContorlProcess {exec, waitSoPath, waitFunSym, InjectSO, InjectFunSym,InjectFunArg,monitorCount}; 174 | injectProc.add_childProces(cp); 175 | } 176 | 177 | injectProc.setTracePid(traced_pid); 178 | std::thread ptraceThread(PtraceTask); 179 | ptraceThread.join(); 180 | } 181 | 182 | 183 | int main(int argc, char *argv[]) { 184 | LOGD("buile time: %s",__TIMESTAMP__); 185 | signal(SIGINT, clean_trace); 186 | 187 | ProgramArgs args; 188 | parse_args(argc, argv, &args); 189 | 190 | if(args.monitor){ 191 | if(args.config != NULL){ 192 | LOGD("args.config: %s",args.config); 193 | tracee_main_config(args.config); 194 | } else{ 195 | LOGD("ContorlProcess: %s %s %s %s %s %s %d",args.exec,args.waitSoPath,args.waitFunSym, args.injectSoPath, args.injectFunSym,args.injectFunArg,args.monitorCount); 196 | auto cp = ContorlProcess {args.exec, args.waitSoPath, args.waitFunSym, args.injectSoPath, args.injectFunSym,args.injectFunArg,args.monitorCount}; 197 | tracee_main_cmd(args.pid,cp); 198 | } 199 | } 200 | if(args.inject){ 201 | LOGD("start inject process"); 202 | int status = 0; 203 | if (ptrace(PTRACE_ATTACH, args.pid, NULL, NULL) < 0){ 204 | LOGE("[-] ptrace attach process error, pid:%d, err:%s\n", args.pid, strerror(errno)); 205 | return -1; 206 | } 207 | LOGD("[+] attach porcess success, pid:%d\n", args.pid); 208 | waitpid(args.pid, &status, WUNTRACED); 209 | inject_process(args.pid,args.injectSoPath, args.injectFunSym,args.injectFunArg); 210 | ptrace(PTRACE_CONT, args.pid, 0, 0); 211 | } 212 | 213 | return 0; 214 | } 215 | 216 | //int main(int argc, char *argv[]) { 217 | // func_test(argc,argv); 218 | // return 0; 219 | //} -------------------------------------------------------------------------------- /ADI/src/main/cpp/adi/parse_args.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by chic on 2025/5/17. 3 | // 4 | 5 | #include "parse_args.h" 6 | #include // 必须包含此头文件 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "logging.h" 12 | 13 | /** 14 | * @brief Get the pid by pkg_name 15 | * 成功返回 true 16 | * 失败返回 false 17 | * 18 | * @param pid 19 | * @param task_name 20 | * @return true 21 | * @return false 22 | */ 23 | bool get_pid_by_name(pid_t *pid, char *task_name){ 24 | DIR *dir; 25 | struct dirent *ptr; 26 | FILE *fp; 27 | char filepath[150]; 28 | char cur_task_name[1024]; 29 | char buf[1024]; 30 | 31 | dir = opendir("/proc"); 32 | if (NULL != dir){ 33 | while ((ptr = readdir(dir)) != NULL){ //循环读取/proc下的每一个文件/文件夹 34 | //如果读取到的是"."或者".."则跳过,读取到的不是文件夹名字也跳过 35 | if ((strcmp(ptr->d_name, ".") == 0) || (strcmp(ptr->d_name, "..") == 0)) 36 | continue; 37 | if (DT_DIR != ptr->d_type) 38 | continue; 39 | 40 | sprintf(filepath, "/proc/%s/cmdline", ptr->d_name); //生成要读取的文件的路径 41 | fp = fopen(filepath, "r"); 42 | if (NULL != fp){ 43 | if (fgets(buf, 1024 - 1, fp) == NULL){ 44 | fclose(fp); 45 | continue; 46 | } 47 | sscanf(buf, "%s", cur_task_name); 48 | //如果文件内容满足要求则打印路径的名字(即进程的PID) 49 | if (strstr(task_name, cur_task_name)){ 50 | *pid = atoi(ptr->d_name); 51 | return true; 52 | } 53 | fclose(fp); 54 | } 55 | } 56 | closedir(dir); 57 | } 58 | return false; 59 | } 60 | 61 | 62 | 63 | bool parse_args(int argc, char **argv, ProgramArgs *args) { 64 | // 初始化默认值 65 | int opt; 66 | int option_index = 0; 67 | char* niceName= nullptr; 68 | int is_niceName = -1; 69 | bool is_config = false; 70 | int is_inject_sopath = -1; 71 | int is_inject_funsym = -1; 72 | // 定义长选项(long options) 73 | static struct option long_options[] = { 74 | {"help", no_argument, 0, 'h'}, 75 | {"monitor", no_argument, 0, 'm'}, 76 | {"inject", no_argument, 0, 'i'}, 77 | {"config", required_argument, 0, 'c'}, 78 | {"verbose", no_argument, 0, 'v'}, 79 | {"pid", required_argument, 0, 'p'}, 80 | {"niceName", required_argument, 0, 'n'}, 81 | {"exec", required_argument, 0, OPT_TRACE_EXEC}, 82 | {"waitSoPath", required_argument, 0, OPT_TRACE_WAITSOPATH}, 83 | {"waitFunSym", required_argument, 0, OPT_TRACE_WAITFUNSYM}, 84 | {"injectSoPath", required_argument, 0, OPT_INJECT_SOPATH}, 85 | {"injectFunSym", required_argument, 0, OPT_INJECT_FUNSYM}, 86 | {"injectFunArg", required_argument, 0, OPT_INJECT_FUNARG}, 87 | {"monitorCount", required_argument, 0,OPT_MONITORCOUNT}, 88 | {"hidemaps", required_argument, 0,OPT_HIDEMAPS}, 89 | {"unload", required_argument, 0,OPT_UNLOAD}, 90 | {0, 0, 0, 0} // 结束标记 91 | }; 92 | 93 | // 定义短选项(short options) 94 | const char *short_options = "himvc:p:n:"; 95 | 96 | 97 | // 解析选项 98 | while ((opt = getopt_long(argc, argv, short_options, long_options, &option_index)) != -1) { 99 | switch (opt) { 100 | case 'c': 101 | args->config = strdup(optarg); 102 | is_config = true; 103 | break; 104 | case 'h': 105 | args->help = true; 106 | break; 107 | case 'i': 108 | args->inject = true; 109 | break; 110 | case 'n':{ 111 | niceName = strdup(optarg); 112 | int tmp_pid = -1; 113 | if (get_pid_by_name(&tmp_pid, niceName)){ 114 | args->pid = tmp_pid; 115 | } else{ 116 | LOGE("get_pid_by_name failed"); 117 | return false; 118 | } 119 | } 120 | break; 121 | case 'p': 122 | args->pid = atoi(optarg); 123 | break; 124 | case 'm': 125 | args->monitor = true; 126 | break; 127 | case 'v': 128 | args->verbose = true; 129 | break; 130 | 131 | case OPT_TRACE_EXEC: 132 | args->exec = strdup(optarg); 133 | break; 134 | case OPT_TRACE_WAITSOPATH: 135 | args->waitSoPath = strdup(optarg); 136 | break; 137 | case OPT_TRACE_WAITFUNSYM: 138 | args->waitFunSym = strdup(optarg); 139 | break; 140 | case OPT_INJECT_SOPATH: 141 | args->injectSoPath = strdup(optarg); 142 | break; 143 | case OPT_INJECT_FUNSYM: 144 | args->injectFunSym = strdup(optarg); 145 | break; 146 | case OPT_INJECT_FUNARG: 147 | args->injectFunArg = strdup(optarg); 148 | break; 149 | case OPT_MONITORCOUNT: 150 | args->monitorCount = atoi(optarg); 151 | break; 152 | case OPT_HIDEMAPS: 153 | args->hidemaps = true; 154 | break; 155 | case OPT_UNLOAD: 156 | args->unload = true; 157 | break; 158 | 159 | } 160 | } 161 | 162 | if (args->monitor == args->inject) { 163 | LOGE("--monitor or --inject arg error"); 164 | return false; 165 | 166 | } 167 | if(is_config){ 168 | return true; 169 | } 170 | if(args->pid == -1){ 171 | LOGE("error,pid is -1"); 172 | return false; 173 | } 174 | 175 | 176 | return true; 177 | } -------------------------------------------------------------------------------- /ADI/src/main/cpp/adi/parse_args.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by chic on 2025/5/17. 3 | // 4 | 5 | 6 | #pragma once 7 | 8 | 9 | enum { 10 | OPT_TRACE_EXEC = 1000, 11 | OPT_TRACE_WAITSOPATH , 12 | OPT_TRACE_WAITFUNSYM, 13 | OPT_INJECT_SOPATH, 14 | OPT_INJECT_FUNSYM , 15 | OPT_INJECT_FUNARG , 16 | OPT_MONITORCOUNT, 17 | OPT_HIDEMAPS, 18 | OPT_UNLOAD 19 | }; 20 | 21 | #include 22 | 23 | struct ProgramArgs{ 24 | bool help; // --help 或 -h 25 | bool verbose; // --verbose 或 -v 26 | bool monitor; 27 | bool inject; 28 | pid_t pid; 29 | bool hidemaps; 30 | bool unload; 31 | char* injectSoPath; 32 | char* injectFunSym; 33 | char* injectFunArg; 34 | char* waitSoPath; 35 | char* waitFunSym; 36 | char* exec; 37 | char *config; 38 | unsigned int monitorCount; 39 | ProgramArgs(){ 40 | help = false; 41 | verbose = false; 42 | config = nullptr; 43 | pid = -1; 44 | monitor = false; 45 | inject = false; 46 | injectSoPath = ""; 47 | injectFunSym = ""; 48 | injectFunArg = ""; 49 | waitSoPath = ""; 50 | waitFunSym = ""; 51 | exec = ""; 52 | config = nullptr; 53 | monitorCount = 0; 54 | hidemaps = false; 55 | unload = false; 56 | } 57 | } ; 58 | 59 | 60 | bool parse_args(int argc, char **argv, ProgramArgs *args) ; -------------------------------------------------------------------------------- /ADI/src/main/java/com/singularity/ptraceinject/NativeLib.java: -------------------------------------------------------------------------------- 1 | package com.singularity.ptraceinject; 2 | 3 | public class NativeLib { 4 | 5 | // Used to load the 'ptraceinject' library on application startup. 6 | // static { 7 | // System.loadLibrary("ptraceinject"); 8 | // } 9 | 10 | /** 11 | * A native method that is implemented by the 'ptraceinject' native library, 12 | * which is packaged with this application. 13 | */ 14 | // public native String stringFromJNI(); 15 | } -------------------------------------------------------------------------------- /ADI/src/test/java/com/singularity/ptraceinject/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.singularity.ptraceinject; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /ADILib/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /ADILib/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | } 4 | 5 | val defaultCFlags = arrayOf( 6 | "-Wall", "-Wextra", 7 | "-fno-rtti", "-fno-exceptions", 8 | "-fno-stack-protector", "-fomit-frame-pointer", 9 | "-Wno-builtin-macro-redefined", "-D__FILE__=__FILE_NAME__" 10 | ) 11 | 12 | 13 | android { 14 | 15 | defaultConfig { 16 | 17 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 18 | consumerProguardFiles("consumer-rules.pro") 19 | externalNativeBuild { 20 | cmake { 21 | abiFilters.add("arm64-v8a") 22 | // abiFilters.add("armeabi-v7a") 23 | } 24 | } 25 | } 26 | 27 | buildTypes { 28 | release { 29 | isMinifyEnabled = false 30 | proguardFiles( 31 | getDefaultProguardFile("proguard-android-optimize.txt"), 32 | "proguard-rules.pro" 33 | ) 34 | } 35 | } 36 | externalNativeBuild { 37 | cmake { 38 | path("src/main/cpp/CMakeLists.txt") 39 | version = "3.22.1" 40 | } 41 | } 42 | 43 | buildFeatures { 44 | prefab = true 45 | } 46 | } -------------------------------------------------------------------------------- /ADILib/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thehepta/Android-Debug-Inject/8a5450a38e9c2a6cc97c98e717d116ad34c3b9af/ADILib/consumer-rules.pro -------------------------------------------------------------------------------- /ADILib/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /ADILib/src/androidTest/java/com/hepta/zygisk/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.hepta.zygisk; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("com.hepta.zygisk.test", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /ADILib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ADILib/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # For more information about using CMake with Android Studio, read the 2 | # documentation: https://d.android.com/studio/projects/add-native-code.html. 3 | # For more examples on how to use CMake, see https://github.com/android/ndk-samples. 4 | 5 | # Sets the minimum CMake version required for this project. 6 | cmake_minimum_required(VERSION 3.22.1) 7 | 8 | # Declares the project name. The project name can be accessed via ${ PROJECT_NAME}, 9 | # Since this is the top level CMakeLists.txt, the project name is also accessible 10 | # with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level 11 | # build script scope). 12 | project("adilib") 13 | 14 | # The version number. 15 | set (Tutorial_VERSION_MAJOR 1) 16 | set (Tutorial_VERSION_MINOR 0) 17 | 18 | # configure a header file to pass some of the CMake settings 19 | # to the source code 20 | 21 | # add the binary tree to the search path for include files 22 | # so that we will find TutorialConfig.h 23 | 24 | # add the executable 25 | add_subdirectory(lsplt) 26 | 27 | aux_source_directory(DrmHook DrmHook_SRC_LIST) 28 | add_library(DrmHook SHARED ${DrmHook_SRC_LIST}) 29 | target_link_libraries(DrmHook log lsplt_static) 30 | 31 | 32 | -------------------------------------------------------------------------------- /ADILib/src/main/cpp/DrmHook/DrmHook.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by thehepta on 2024/2/19. 3 | // 4 | 5 | #include 6 | #include "lsplt.hpp" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "vector" 12 | #include "array" 13 | 14 | #define LOG_TAG "DrmIdHook" 15 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) 16 | 17 | 18 | struct MapInfo { 19 | /// \brief The start address of the memory region. 20 | uintptr_t start; 21 | /// \brief The end address of the memory region. 22 | uintptr_t end; 23 | /// \brief The permissions of the memory region. This is a bit mask of the following values: 24 | /// - PROT_READ 25 | /// - PROT_WRITE 26 | /// - PROT_EXEC 27 | uint8_t perms; 28 | /// \brief Whether the memory region is private. 29 | bool is_private; 30 | /// \brief The offset of the memory region. 31 | uintptr_t offset; 32 | /// \brief The device number of the memory region. 33 | /// Major can be obtained by #major() 34 | /// Minor can be obtained by #minor() 35 | dev_t dev; 36 | /// \brief The inode number of the memory region. 37 | ino_t inode; 38 | /// \brief The path of the memory region. 39 | std::string path; 40 | 41 | 42 | }; 43 | 44 | 45 | std::vector Scan(pid_t pid) { 46 | constexpr static auto kPermLength = 5; 47 | constexpr static auto kMapEntry = 7; 48 | std::vector info; 49 | auto path = "/proc/" + std::to_string(pid) + "/maps"; 50 | auto maps = std::unique_ptr{fopen(path.c_str(), "r"), &fclose}; 51 | if (maps) { 52 | char *line = nullptr; 53 | size_t len = 0; 54 | ssize_t read; 55 | while ((read = getline(&line, &len, maps.get())) > 0) { 56 | line[read - 1] = '\0'; 57 | uintptr_t start = 0; 58 | uintptr_t end = 0; 59 | uintptr_t off = 0; 60 | ino_t inode = 0; 61 | unsigned int dev_major = 0; 62 | unsigned int dev_minor = 0; 63 | std::array perm{'\0'}; 64 | int path_off; 65 | if (sscanf(line, "%" PRIxPTR "-%" PRIxPTR " %4s %" PRIxPTR " %x:%x %lu %n%*s", &start, 66 | &end, perm.data(), &off, &dev_major, &dev_minor, &inode, 67 | &path_off) != kMapEntry) { 68 | continue; 69 | } 70 | while (path_off < read && isspace(line[path_off])) path_off++; 71 | auto ref = MapInfo{start, end, 0, perm[3] == 'p', off, 72 | static_cast(makedev(dev_major, dev_minor)), 73 | inode, line + path_off}; 74 | if (perm[0] == 'r') ref.perms |= PROT_READ; 75 | if (perm[1] == 'w') ref.perms |= PROT_WRITE; 76 | if (perm[2] == 'x') ref.perms |= PROT_EXEC; 77 | info.emplace_back(ref); 78 | } 79 | free(line); 80 | } 81 | return info; 82 | } 83 | 84 | 85 | char * replace_drm; 86 | int length = -1; 87 | void * (* old_getDeviceUniqueId)(void * arg1,intptr_t * drmid); 88 | 89 | void * new_getDeviceUniqueId(void * arg1, intptr_t * drmid){ 90 | void * ret = old_getDeviceUniqueId(arg1,drmid); 91 | LOGE("new_getDeviceUniqueId %ld\n",drmid[0]); 92 | LOGE("new_getDeviceUniqueId %ld\n",drmid[1]); 93 | LOGE("new_getDeviceUniqueId %lx\n",drmid[2]); 94 | memcpy(reinterpret_cast(drmid[2]), replace_drm, length); 95 | return ret; 96 | } 97 | 98 | 99 | // 将16进制字符串转换为字节数组 100 | std::vector hex_string_to_bytes(std::string& hex) { 101 | std::vector bytes; 102 | for (size_t i = 0; i < hex.length(); i += 2) { 103 | std::string byte_string = hex.substr(i, 2); 104 | auto byte = static_cast(stoi(byte_string, nullptr, 16)); 105 | bytes.push_back(byte); 106 | } 107 | return bytes; 108 | } 109 | 110 | //./generalInjectTool -p 10897 -so /data/local/tmp/libDrmHook.so -symbols _Z9DrmIdHookPKc 11111111111193baf8cb6a22de8a5ae4bfecc174b1a9405dc71b8b3fac1c734f 111 | extern "C" [[gnu::visibility("default")]] 112 | void DrmIdHook(void* handle, const char* AUTHORITY) { 113 | LOGE("DrmIdHook start1 %s\n",AUTHORITY); 114 | std::string tmp_string = strdup(AUTHORITY); 115 | std::vector memory = hex_string_to_bytes(tmp_string); 116 | length = tmp_string.length()/2; 117 | replace_drm = static_cast(malloc(length)); 118 | memcpy(replace_drm,memory.data(),length); 119 | ino_t art_inode = 0; 120 | dev_t art_dev = 0; 121 | for (auto &map : Scan(getpid())) { 122 | if (map.path.find("libwvhidl.so") != std::string_view::npos) { 123 | LOGE("found libwvhidl\n"); 124 | 125 | art_inode = map.inode; 126 | art_dev = map.dev; 127 | break; 128 | } 129 | } 130 | char *symbol = "_ZN5wvdrm8hardware3drm4V1_48widevine11WVDrmPlugin20CdmIdentifierBuilder17getDeviceUniqueIdEPNSt3__112basic_stringIcNS6_11char_traitsIcEENS6_9allocatorIcEEEE"; 131 | if (!lsplt::RegisterHook(art_dev, art_inode, symbol,(void* )new_getDeviceUniqueId, (void **)&old_getDeviceUniqueId)) { 132 | LOGE("Failed to register plt_hook getDeviceUniqueId \n"); 133 | return; 134 | } 135 | 136 | lsplt::CommitHook(); 137 | 138 | } 139 | 140 | 141 | -------------------------------------------------------------------------------- /ADILib/src/main/cpp/inject.json: -------------------------------------------------------------------------------- 1 | { 2 | "traced_pid": 1, 3 | "persistence": true, 4 | "childProcess": [ 5 | { 6 | "exec": "/vendor/bin/hw/android.hardware.drm@1.4-service.widevine", 7 | "waitSoPath": "", 8 | "waitFunSym": "", 9 | "InjectSO": "/data/adb/modules/ZygiskADI/lib/arm64-v8a/libDrmHook.so", 10 | "InjectFunSym": "DrmIdHook", 11 | "InjectFunArg": "11111111111193baf8cb6a22de8a5ae4bfecc174b1a9405dc71b8b3fac1c734f" 12 | } 13 | 14 | ] 15 | } -------------------------------------------------------------------------------- /ADILib/src/main/cpp/inject2.json: -------------------------------------------------------------------------------- 1 | { 2 | "traced_pid": 1, 3 | "persistence": true, 4 | "childProcess": [ 5 | { 6 | "exec": "/vendor/bin/hw/android.hardware.drm@1.4-service.widevine", 7 | "waitSoPath": "/vendor/lib64/libwvhidl.so", 8 | "waitFunSym": "", 9 | "InjectSO": "/data/local/tmp/libDrmHook.so", 10 | "InjectFunSym": "DrmIdHook", 11 | "InjectFunArg": "11111111111193baf8cb6a22de8a5ae4bfecc174b1a9405dc71b8b3fac1c734f" 12 | } 13 | 14 | ] 15 | } -------------------------------------------------------------------------------- /ADILib/src/main/cpp/lsplt/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Google 3 | IndentWidth: 4 4 | UseCRLF: false 5 | UseTab: false 6 | --- 7 | Language: Cpp 8 | DerivePointerAlignment: true 9 | PointerAlignment: Right 10 | ColumnLimit: 100 11 | AlignEscapedNewlines: Right 12 | Cpp11BracedListStyle: true 13 | Standard: Latest 14 | # IndentAccessModifiers: false 15 | IndentCaseLabels: false 16 | BreakStringLiterals: false 17 | IndentExternBlock: false 18 | AccessModifierOffset: -4 19 | # EmptyLineBeforeAccessModifier: true 20 | -------------------------------------------------------------------------------- /ADILib/src/main/cpp/lsplt/.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: > 3 | -*, 4 | bugprone-*, 5 | google-*, 6 | misc-*, 7 | modernize-*, 8 | performance-*, 9 | portability-*, 10 | readability-*, 11 | clang-analyzer-*, 12 | llvm-include-order, 13 | -modernize-use-trailing-return-type, 14 | -readability-implicit-bool-conversion, 15 | -performance-no-int-to-ptr, 16 | 17 | CheckOptions: 18 | - key: readability-identifier-naming.ClassCase 19 | value: CamelCase 20 | - key: readability-identifier-naming.ClassMemberCase 21 | value: lower_case 22 | - key: readability-identifier-naming.EnumCase 23 | value: CamelCase 24 | - key: readability-identifier-naming.EnumConstantCase 25 | value: CamelCase 26 | - key: readability-identifier-naming.EnumConstantPrefix 27 | value: k 28 | - key: readability-identifier-naming.FunctionCase 29 | value: CamelCase 30 | - key: readability-identifier-naming.GlobalConstantCase 31 | value: CamelCase 32 | - key: readability-identifier-naming.GlobalConstantPrefix 33 | value: k 34 | - key: readability-identifier-naming.StaticConstantCase 35 | value: CamelCase 36 | - key: readability-identifier-naming.StaticConstantPrefix 37 | value: k 38 | - key: readability-identifier-naming.StaticVariableCase 39 | value: CamelCase 40 | - key: readability-identifier-naming.StaticVariablePrefix 41 | value: k 42 | - key: readability-identifier-naming.MacroDefinitionCase 43 | value: UPPER_CASE 44 | - key: readability-identifier-naming.MemberCase 45 | value: lower_case 46 | - key: readability-identifier-naming.PrivateMemberSuffix 47 | value: _ 48 | - key: readability-identifier-naming.ProtectedMemberSuffix 49 | value: _ 50 | - key: readability-identifier-naming.NamespaceCase 51 | value: lower_case 52 | - key: readability-identifier-naming.ParameterCase 53 | value: lower_case 54 | - key: readability-identifier-naming.TypeAliasCase 55 | value: CamelCase 56 | - key: readability-identifier-naming.TypedefCase 57 | value: CamelCase 58 | - key: readability-identifier-naming.VariableCase 59 | value: lower_case 60 | - key: readability-identifier-naming.IgnoreMainLikeFunctions 61 | value: 1 62 | - key: readability-braces-around-statements.ShortStatementLines 63 | value: 1 64 | - key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic 65 | value: '1' 66 | -------------------------------------------------------------------------------- /ADILib/src/main/cpp/lsplt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.4.1) 2 | project(lsplt) 3 | 4 | find_program(CCACHE ccache) 5 | 6 | if (CCACHE) 7 | set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE}) 8 | set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE}) 9 | endif () 10 | 11 | if (LSPLT_STANDALONE) 12 | find_package(cxx REQUIRED CONFIG) 13 | link_libraries(cxx::cxx) 14 | endif() 15 | 16 | set(SOURCES lsplt.cc elf_util.cc) 17 | 18 | option(LSPLT_BUILD_SHARED "If ON, lsplt will also build shared library" ON) 19 | 20 | 21 | add_library(${PROJECT_NAME}_static STATIC ${SOURCES}) 22 | target_include_directories(${PROJECT_NAME}_static PUBLIC include) 23 | target_include_directories(${PROJECT_NAME}_static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) 24 | 25 | if (NOT DEFINED DEBUG_SYMBOLS_PATH) 26 | set(DEBUG_SYMBOLS_PATH ${CMAKE_BINARY_DIR}/symbols) 27 | endif() 28 | 29 | target_link_libraries(${PROJECT_NAME}_static PUBLIC log) 30 | -------------------------------------------------------------------------------- /ADILib/src/main/cpp/lsplt/elf_util.cc: -------------------------------------------------------------------------------- 1 | #include "elf_util.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #if defined(__arm__) 9 | #define ELF_R_GENERIC_JUMP_SLOT R_ARM_JUMP_SLOT //.rel.plt 10 | #define ELF_R_GENERIC_GLOB_DAT R_ARM_GLOB_DAT //.rel.dyn 11 | #define ELF_R_GENERIC_ABS R_ARM_ABS32 //.rel.dyn 12 | #elif defined(__aarch64__) 13 | #define ELF_R_GENERIC_JUMP_SLOT R_AARCH64_JUMP_SLOT 14 | #define ELF_R_GENERIC_GLOB_DAT R_AARCH64_GLOB_DAT 15 | #define ELF_R_GENERIC_ABS R_AARCH64_ABS64 16 | #elif defined(__i386__) 17 | #define ELF_R_GENERIC_JUMP_SLOT R_386_JMP_SLOT 18 | #define ELF_R_GENERIC_GLOB_DAT R_386_GLOB_DAT 19 | #define ELF_R_GENERIC_ABS R_386_32 20 | #elif defined(__x86_64__) 21 | #define ELF_R_GENERIC_JUMP_SLOT R_X86_64_JUMP_SLOT 22 | #define ELF_R_GENERIC_GLOB_DAT R_X86_64_GLOB_DAT 23 | #define ELF_R_GENERIC_ABS R_X86_64_64 24 | #endif 25 | 26 | #if defined(__LP64__) 27 | #define ELF_R_SYM(info) ELF64_R_SYM(info) 28 | #define ELF_R_TYPE(info) ELF64_R_TYPE(info) 29 | #else 30 | #define ELF_R_SYM(info) ELF32_R_SYM(info) 31 | #define ELF_R_TYPE(info) ELF32_R_TYPE(info) 32 | #endif 33 | 34 | namespace { 35 | template 36 | inline constexpr auto OffsetOf(ElfW(Ehdr) * head, ElfW(Off) off) { 37 | return reinterpret_cast, T, T *>>( 38 | reinterpret_cast(head) + off); 39 | } 40 | 41 | template 42 | inline constexpr auto SetByOffset(T &ptr, ElfW(Addr) base, ElfW(Addr) bias, ElfW(Addr) off) { 43 | if (auto val = bias + off; val > base) { 44 | ptr = reinterpret_cast(val); 45 | return true; 46 | } 47 | ptr = 0; 48 | return false; 49 | } 50 | 51 | } // namespace 52 | 53 | Elf::Elf(uintptr_t base_addr) : base_addr_(base_addr) { 54 | header_ = reinterpret_cast(base_addr); 55 | 56 | // check magic 57 | if (0 != memcmp(header_->e_ident, ELFMAG, SELFMAG)) return; 58 | 59 | // check class (64/32) 60 | #if defined(__LP64__) 61 | if (ELFCLASS64 != header_->e_ident[EI_CLASS]) return; 62 | #else 63 | if (ELFCLASS32 != header_->e_ident[EI_CLASS]) return; 64 | #endif 65 | 66 | // check endian (little/big) 67 | if (ELFDATA2LSB != header_->e_ident[EI_DATA]) return; 68 | 69 | // check version 70 | if (EV_CURRENT != header_->e_ident[EI_VERSION]) return; 71 | 72 | // check type 73 | if (ET_EXEC != header_->e_type && ET_DYN != header_->e_type) return; 74 | 75 | // check machine 76 | #if defined(__arm__) 77 | if (EM_ARM != header_->e_machine) return; 78 | #elif defined(__aarch64__) 79 | if (EM_AARCH64 != header_->e_machine) return; 80 | #elif defined(__i386__) 81 | if (EM_386 != header_->e_machine) return; 82 | #elif defined(__x86_64__) 83 | if (EM_X86_64 != header_->e_machine) return; 84 | #else 85 | return; 86 | #endif 87 | 88 | // check version 89 | if (EV_CURRENT != header_->e_version) return; 90 | 91 | program_header_ = OffsetOf(header_, header_->e_phoff); 92 | 93 | auto ph_off = reinterpret_cast(program_header_); 94 | for (int i = 0; i < header_->e_phnum; i++, ph_off += header_->e_phentsize) { 95 | auto *program_header = reinterpret_cast(ph_off); 96 | if (program_header->p_type == PT_LOAD && program_header->p_offset == 0) { 97 | if (base_addr_ >= program_header->p_vaddr) { 98 | bias_addr_ = base_addr_ - program_header->p_vaddr; 99 | } 100 | } else if (program_header->p_type == PT_DYNAMIC) { 101 | dynamic_ = reinterpret_cast(program_header->p_vaddr); 102 | dynamic_size_ = program_header->p_memsz; 103 | } 104 | } 105 | if (!dynamic_ || !bias_addr_) return; 106 | dynamic_ = 107 | reinterpret_cast(bias_addr_ + reinterpret_cast(dynamic_)); 108 | 109 | for (auto *dynamic = dynamic_, *dynamic_end = dynamic_ + (dynamic_size_ / sizeof(dynamic[0])); 110 | dynamic < dynamic_end; ++dynamic) { 111 | switch (dynamic->d_tag) { 112 | case DT_NULL: 113 | // the end of the dynamic-section 114 | dynamic = dynamic_end; 115 | break; 116 | case DT_STRTAB: { 117 | if (!SetByOffset(dyn_str_, base_addr_, bias_addr_, dynamic->d_un.d_ptr)) return; 118 | break; 119 | } 120 | case DT_SYMTAB: { 121 | if (!SetByOffset(dyn_sym_, base_addr_, bias_addr_, dynamic->d_un.d_ptr)) return; 122 | break; 123 | } 124 | case DT_PLTREL: 125 | // use rel or rela? 126 | is_use_rela_ = dynamic->d_un.d_val == DT_RELA; 127 | break; 128 | case DT_JMPREL: { 129 | if (!SetByOffset(rel_plt_, base_addr_, bias_addr_, dynamic->d_un.d_ptr)) return; 130 | break; 131 | } 132 | case DT_PLTRELSZ: 133 | rel_plt_size_ = dynamic->d_un.d_val; 134 | break; 135 | case DT_REL: 136 | case DT_RELA: { 137 | if (!SetByOffset(rel_dyn_, base_addr_, bias_addr_, dynamic->d_un.d_ptr)) return; 138 | break; 139 | } 140 | case DT_RELSZ: 141 | case DT_RELASZ: 142 | rel_dyn_size_ = dynamic->d_un.d_val; 143 | break; 144 | case DT_ANDROID_REL: 145 | case DT_ANDROID_RELA: { 146 | if (!SetByOffset(rel_android_, base_addr_, bias_addr_, dynamic->d_un.d_ptr)) return; 147 | break; 148 | } 149 | case DT_ANDROID_RELSZ: 150 | case DT_ANDROID_RELASZ: 151 | rel_android_size_ = dynamic->d_un.d_val; 152 | break; 153 | case DT_HASH: { 154 | // ignore DT_HASH when ELF contains DT_GNU_HASH hash table 155 | if (bloom_) continue; 156 | auto *raw = reinterpret_cast(bias_addr_ + dynamic->d_un.d_ptr); 157 | bucket_count_ = raw[0]; 158 | bucket_ = raw + 2; 159 | chain_ = bucket_ + bucket_count_; 160 | break; 161 | } 162 | case DT_GNU_HASH: { 163 | auto *raw = reinterpret_cast(bias_addr_ + dynamic->d_un.d_ptr); 164 | bucket_count_ = raw[0]; 165 | sym_offset_ = raw[1]; 166 | bloom_size_ = raw[2]; 167 | bloom_shift_ = raw[3]; 168 | bloom_ = reinterpret_cast(raw + 4); 169 | bucket_ = reinterpret_cast(bloom_ + bloom_size_); 170 | chain_ = bucket_ + bucket_count_ - sym_offset_; 171 | // is_use_gnu_hash_ = true; 172 | break; 173 | } 174 | default: 175 | break; 176 | } 177 | } 178 | 179 | // check android rel/rela 180 | if (0 != rel_android_) { 181 | const auto *rel = reinterpret_cast(rel_android_); 182 | if (rel_android_size_ < 4 || rel[0] != 'A' || rel[1] != 'P' || rel[2] != 'S' || 183 | rel[3] != '2') { 184 | return; 185 | } 186 | 187 | rel_android_ += 4; 188 | rel_android_size_ -= 4; 189 | } 190 | 191 | valid_ = true; 192 | } 193 | 194 | uint32_t Elf::GnuLookup(std::string_view name) const { 195 | static constexpr auto kBloomMaskBits = sizeof(ElfW(Addr)) * 8; 196 | static constexpr uint32_t kInitialHash = 5381; 197 | static constexpr uint32_t kHashShift = 5; 198 | 199 | if (!bucket_ || !bloom_) return 0; 200 | 201 | uint32_t hash = kInitialHash; 202 | for (unsigned char chr : name) { 203 | hash += (hash << kHashShift) + chr; 204 | } 205 | 206 | auto bloom_word = bloom_[(hash / kBloomMaskBits) % bloom_size_]; 207 | uintptr_t mask = 0 | uintptr_t{1} << (hash % kBloomMaskBits) | 208 | uintptr_t{1} << ((hash >> bloom_shift_) % kBloomMaskBits); 209 | if ((mask & bloom_word) == mask) { 210 | auto idx = bucket_[hash % bucket_count_]; 211 | if (idx >= sym_offset_) { 212 | const char *strings = dyn_str_; 213 | do { 214 | auto *sym = dyn_sym_ + idx; 215 | if (((chain_[idx] ^ hash) >> 1) == 0 && name == strings + sym->st_name) { 216 | return idx; 217 | } 218 | } while ((chain_[idx++] & 1) == 0); 219 | } 220 | } 221 | return 0; 222 | } 223 | 224 | uint32_t Elf::ElfLookup(std::string_view name) const { 225 | static constexpr uint32_t kHashMask = 0xf0000000; 226 | static constexpr uint32_t kHashShift = 24; 227 | uint32_t hash = 0; 228 | uint32_t tmp; 229 | 230 | if (!bucket_ || bloom_) return 0; 231 | 232 | for (unsigned char chr : name) { 233 | hash = (hash << 4) + chr; 234 | tmp = hash & kHashMask; 235 | hash ^= tmp; 236 | hash ^= tmp >> kHashShift; 237 | } 238 | const char *strings = dyn_str_; 239 | 240 | for (auto idx = bucket_[hash % bucket_count_]; idx != 0; idx = chain_[idx]) { 241 | auto *sym = dyn_sym_ + idx; 242 | if (name == strings + sym->st_name) { 243 | return idx; 244 | } 245 | } 246 | return 0; 247 | } 248 | 249 | uint32_t Elf::LinearLookup(std::string_view name) const { 250 | if (!dyn_sym_ || !sym_offset_) return 0; 251 | for (uint32_t idx = 0; idx < sym_offset_; idx++) { 252 | auto *sym = dyn_sym_ + idx; 253 | if (name == dyn_str_ + sym->st_name) { 254 | return idx; 255 | } 256 | } 257 | return 0; 258 | } 259 | 260 | std::vector Elf::FindPltAddr(std::string_view name) const { 261 | std::vector res; 262 | 263 | uint32_t idx = GnuLookup(name); 264 | if (!idx) idx = ElfLookup(name); 265 | if (!idx) idx = LinearLookup(name); 266 | if (!idx) return res; 267 | 268 | auto looper = [&](auto begin, auto size, bool is_plt) -> void { 269 | const auto *rel_end = reinterpret_cast(begin + size); 270 | for (const auto *rel = reinterpret_cast(begin); rel < rel_end; ++rel) { 271 | auto r_info = rel->r_info; 272 | auto r_offset = rel->r_offset; 273 | auto r_sym = ELF_R_SYM(r_info); 274 | auto r_type = ELF_R_TYPE(r_info); 275 | if (r_sym != idx) continue; 276 | if (is_plt && r_type != ELF_R_GENERIC_JUMP_SLOT) continue; 277 | if (!is_plt && r_type != ELF_R_GENERIC_ABS && r_type != ELF_R_GENERIC_GLOB_DAT) { 278 | continue; 279 | } 280 | auto addr = bias_addr_ + r_offset; 281 | if (addr > base_addr_) res.emplace_back(addr); 282 | if (is_plt) break; 283 | } 284 | }; 285 | 286 | for (const auto &[rel, rel_size, is_plt] : 287 | {std::make_tuple(rel_plt_, rel_plt_size_, true), 288 | std::make_tuple(rel_dyn_, rel_dyn_size_, false), 289 | std::make_tuple(rel_android_, rel_android_size_, false)}) { 290 | if (!rel) continue; 291 | if (is_use_rela_) { 292 | looper.template operator()(rel, rel_size, is_plt); 293 | } else { 294 | looper.template operator()(rel, rel_size, is_plt); 295 | } 296 | } 297 | 298 | return res; 299 | } 300 | -------------------------------------------------------------------------------- /ADILib/src/main/cpp/lsplt/elf_util.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | class Elf { 8 | ElfW(Addr) base_addr_ = 0; 9 | ElfW(Addr) bias_addr_ = 0; 10 | 11 | ElfW(Ehdr) *header_ = nullptr; 12 | ElfW(Phdr) *program_header_ = nullptr; 13 | 14 | ElfW(Dyn) *dynamic_ = nullptr; //.dynamic 15 | ElfW(Word) dynamic_size_ = 0; 16 | 17 | const char *dyn_str_ = nullptr; //.dynstr (string-table) 18 | ElfW(Sym) *dyn_sym_ = nullptr; //.dynsym (symbol-index to string-table's offset) 19 | 20 | ElfW(Addr) rel_plt_ = 0; //.rel.plt or .rela.plt 21 | ElfW(Word) rel_plt_size_ = 0; 22 | 23 | ElfW(Addr) rel_dyn_ = 0; //.rel.dyn or .rela.dyn 24 | ElfW(Word) rel_dyn_size_ = 0; 25 | 26 | ElfW(Addr) rel_android_ = 0; // android compressed rel or rela 27 | ElfW(Word) rel_android_size_ = 0; 28 | 29 | // for ELF hash 30 | uint32_t *bucket_ = nullptr; 31 | uint32_t bucket_count_ = 0; 32 | uint32_t *chain_ = nullptr; 33 | 34 | // append for GNU hash 35 | uint32_t sym_offset_ = 0; 36 | ElfW(Addr) *bloom_ = nullptr; 37 | uint32_t bloom_size_ = 0; 38 | uint32_t bloom_shift_ = 0; 39 | 40 | bool is_use_rela_ = false; 41 | bool valid_ = false; 42 | 43 | uint32_t GnuLookup(std::string_view name) const; 44 | uint32_t ElfLookup(std::string_view name) const; 45 | uint32_t LinearLookup(std::string_view name) const; 46 | public: 47 | std::vector FindPltAddr(std::string_view name) const; 48 | Elf(uintptr_t base_addr); 49 | bool Valid() const { return valid_; }; 50 | }; 51 | -------------------------------------------------------------------------------- /ADILib/src/main/cpp/lsplt/include/lsplt.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | /// \namespace lsplt 9 | namespace lsplt { 10 | inline namespace v2 { 11 | 12 | /// \struct MapInfo 13 | /// \brief An entry that describes a line in /proc/self/maps. You can obtain a list of these entries 14 | /// by calling #Scan(). 15 | struct MapInfo { 16 | /// \brief The start address of the memory region. 17 | uintptr_t start; 18 | /// \brief The end address of the memory region. 19 | uintptr_t end; 20 | /// \brief The permissions of the memory region. This is a bit mask of the following values: 21 | /// - PROT_READ 22 | /// - PROT_WRITE 23 | /// - PROT_EXEC 24 | uint8_t perms; 25 | /// \brief Whether the memory region is private. 26 | bool is_private; 27 | /// \brief The offset of the memory region. 28 | uintptr_t offset; 29 | /// \brief The device number of the memory region. 30 | /// Major can be obtained by #major() 31 | /// Minor can be obtained by #minor() 32 | dev_t dev; 33 | /// \brief The inode number of the memory region. 34 | ino_t inode; 35 | /// \brief The path of the memory region. 36 | std::string path; 37 | 38 | /// \brief Scans /proc/self/maps and returns a list of \ref MapInfo entries. 39 | /// This is useful to find out the inode of the library to hook. 40 | /// \return A list of \ref MapInfo entries. 41 | [[maybe_unused, gnu::visibility("default")]] static std::vector Scan(); 42 | }; 43 | 44 | /// \brief Register a hook to a function by inode. For so within an archive, you should use 45 | /// #RegisterHook(ino_t, uintptr_t, size_t, std::string_view, void *, void **) instead. 46 | /// \param[in] dev The device number of the memory region. 47 | /// \param[in] inode The inode of the library to hook. You can obtain the inode by #stat() or by finding 48 | /// the library in the list returned by #lsplt::v1::MapInfo::Scan(). 49 | /// \param[in] symbol The function symbol to hook. 50 | /// \param[in] callback The callback function pointer to call when the function is called. 51 | /// \param[out] backup The backup function pointer which can call the original function. This is 52 | /// optional. 53 | /// \return Whether the hook is successfully registered. 54 | /// \note This function is thread-safe. 55 | /// \note \p backup will not be available until #CommitHook() is called. 56 | /// \note \p backup will be nullptr if the hook fails. 57 | /// \note You can unhook the function by calling this function with \p callback set to the backup 58 | /// set by previous call. 59 | /// \note LSPlt will backup the hook memory region and restore it when the 60 | /// hook is restored to its original function pointer so that there won't be dirty pages. LSPlt will 61 | /// do hooks on a copied memory region so that the original memory region will not be modified. You 62 | /// can invalidate this behaviour and hook the original memory region by calling 63 | /// #InvalidateBackup(). 64 | /// \see #CommitHook() 65 | /// \see #InvalidateBackup() 66 | [[maybe_unused, gnu::visibility("default")]] bool RegisterHook(dev_t dev, ino_t inode, std::string_view symbol, 67 | void *callback, void **backup); 68 | 69 | /// \brief Register a hook to a function by inode with offset range. This is useful when hooking 70 | /// a library that is directly loaded from an archive without extraction. 71 | /// \param[in] dev The device number of the memory region. 72 | /// \param[in] inode The inode of the library to hook. You can obtain the inode by #stat() or by finding 73 | /// the library in the list returned by #lsplt::v1::MapInfo::Scan(). 74 | /// \param[in] offset The to the library in the file. 75 | /// \param[in] size The upper bound size to the library in the file. 76 | /// \param[in] symbol The function symbol to hook. 77 | /// \param[in] callback The callback function pointer to call when the function is called. 78 | /// \param[out] backup The backup function pointer which can call the original function. This is 79 | /// optional. 80 | /// \return Whether the hook is successfully registered. 81 | /// \note This function is thread-safe. 82 | /// \note \p backup will not be available until #CommitHook() is called. 83 | /// \note \p backup will be nullptr if the hook fails. 84 | /// \note You can unhook the function by calling this function with \p callback set to the backup 85 | /// set by previous call. 86 | /// \note LSPlt will backup the hook memory region and restore it when the 87 | /// hook is restored to its original function pointer so that there won't be dirty pages. LSPlt will 88 | /// do hooks on a copied memory region so that the original memory region will not be modified. You 89 | /// can invalidate this behaviour and hook the original memory region by calling 90 | /// #InvalidateBackup(). 91 | /// \note You can get the offset range of the library by getting its entry offset and size in the 92 | /// zip file. 93 | /// \note According to the Android linker specification, the \p offset must be page aligned. 94 | /// \note The \p offset must be accurate, otherwise the hook may fail because the ELF header 95 | /// cannot be found. 96 | /// \note The \p size can be inaccurate but should be larger or equal to the library size, 97 | /// otherwise the hook may fail when the hook pointer is beyond the range. 98 | /// \note The behaviour of this function is undefined if \p offset + \p size is larger than the 99 | /// the maximum value of \p size_t. 100 | /// \see #CommitHook() 101 | /// \see #InvalidateBackup() 102 | [[maybe_unused, gnu::visibility("default")]] bool RegisterHook(dev_t dev, ino_t inode, uintptr_t offset, 103 | size_t size, std::string_view symbol, 104 | void *callback, void **backup); 105 | /// \brief Commit all registered hooks. 106 | /// \return Whether all hooks are successfully committed. If any of the hooks fail to commit, 107 | /// the result is false. 108 | /// \note This function is thread-safe. 109 | /// \note The return value indicates whether all hooks are successfully committed. You can 110 | /// determine which hook fails by checking the backup function pointer of #RegisterHook(). 111 | /// \see #RegisterHook() 112 | [[maybe_unused, gnu::visibility("default")]] bool CommitHook(); 113 | 114 | /// \brief Invalidate backup memory regions 115 | /// Normally LSPlt will backup the hooked memory region and do hook on a copied anonymous memory 116 | /// region, and restore the original memory region when the hook is unregistered 117 | /// (when the callback of #RegisterHook() is the original function). This function will restore 118 | /// the backup memory region and do all existing hooks on the original memory region. 119 | /// \return Whether all hooks are successfully invalidated. If any of the hooks fail to invalidate, 120 | /// the result is false. 121 | /// \note This function is thread-safe. 122 | /// \note This will be automatically called when the library is unloaded. 123 | /// \see #RegisterHook() 124 | [[maybe_unused, gnu::visibility("default")]] bool InvalidateBackup(); 125 | } // namespace v2 126 | } // namespace lsplt 127 | -------------------------------------------------------------------------------- /ADILib/src/main/cpp/lsplt/logging.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifndef LOG_TAG 6 | #define LOG_TAG "LSPlt" 7 | #endif 8 | 9 | #ifdef LOG_DISABLED 10 | #define LOGD(...) 0 11 | #define LOGV(...) 0 12 | #define LOGI(...) 0 13 | #define LOGW(...) 0 14 | #define LOGE(...) 0 15 | #else 16 | #ifndef NDEBUG 17 | #define LOGD(fmt, ...) \ 18 | __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, \ 19 | "%s:%d#%s" \ 20 | ": " fmt, \ 21 | __FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(, ) __VA_ARGS__) 22 | #define LOGV(fmt, ...) \ 23 | __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, \ 24 | "%s:%d#%s" \ 25 | ": " fmt, \ 26 | __FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(, ) __VA_ARGS__) 27 | #else 28 | #define LOGD(...) 0 29 | #define LOGV(...) 0 30 | #endif 31 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) 32 | #define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) 33 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) 34 | #define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__) 35 | #define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno)) 36 | #endif 37 | -------------------------------------------------------------------------------- /ADILib/src/main/java/com/hepta/adilib/NativeLib.java: -------------------------------------------------------------------------------- 1 | package com.hepta.adilib; 2 | 3 | public class NativeLib { 4 | 5 | // // Used to load the 'zygisk' library on application startup. 6 | // static { 7 | // System.loadLibrary("zygisk"); 8 | // } 9 | // 10 | // /** 11 | // * A native method that is implemented by the 'zygisk' native library, 12 | // * which is packaged with this application. 13 | // */ 14 | // public native String stringFromJNI(); 15 | } -------------------------------------------------------------------------------- /ADILib/src/test/java/com/hepta/adilib/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.hepta.adilib; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # 开发初衷 3 | 4 | + 这个项目的开发主是因为于zygiskNext 老版本特征太多了,我改了很多,后来发现需要有些部分需要大改才能完成对抗,索性注入工具部分完全重写了。 5 | + zygiskNext rust部分,由于我不太熟悉,同时我希望zygisk 相关部分和 注入工具完全分离,所以抄了magisk zygisk部分的代码,用c重写了zygiskd。 6 | + zygiskNext目前新版本功能很强大,但是并没有开源,有点没有安全感,而且我有很多扩展的无痕注入需要从源码进行修改。 7 | + 对抗问题,如果没有写过某一个工具,经历他的对抗,你真的永远不知道他有哪些对抗问题。(dlclose对抗) 8 | 9 | 在开发工程中我我又发现了很多可以扩展点,比如任意位置停止,以及init进程注入。 10 | 11 | 12 | ## 项目介绍 13 | 14 | 这个项目主要是以注入工具Android-Debug-Inject(adi)为主,zygisk 项目使用的是adi程序进行注入,zygisk可以说只是一个adi工具注入init进程的demo, 15 | 同时提供了另一个注入drm进程并且修改drm id的项目。 16 | 17 | 18 | # 工程介绍 19 | 20 | ## 项目结构 21 | + adi 注入工具, 一个android 进程注入工具 22 | + zygisk,提供zygisk 以及zyiskd 服务,单独将zygsk 剥离,使他能够单独使用 23 | + ADLib, 这是一个注入 drm 进程 的demo,可以修改drm 的id,用于演示注入init子进程 24 | + module 跟zygisk 一样,用来编译工程的,会生成 zygiskADI 这个magisk模块。 25 | 26 | ## zygiskADI 27 | 28 | ### 编译 29 | 我根据zygiskNext的 android studio 工程又写了一个类似的工程,但是构建方式一样 gradlew zipDebug 30 | 31 | ### 使用 32 | 直接刷入即可,copy文件可动态执行,可以动态开发zygisk 插件,只需要不断的杀死zygote,让他重启即可 33 | 34 | 35 | ## 配置文件例子说明 36 | 通过 module/src/zygisk.json 配置文件,将监控zygote启动,并注入libzygisk.so文件 37 | 通过 ADILib/src/main/cpp/inject.json 配置文件,将监控drm进程启动,并注入so文件 38 | 39 | ## 配置文件参数说明 40 | 41 | ```json 42 | { 43 | "traced_pid": 1, 要监控的父进程 44 | "persistence": true, 暂时不用,后续可能会做持久化 45 | "childProcess": [ 要监控的进程数组 46 | { 47 | "exec": "/vendor/bin/hw/android.hardware.drm@1.4-service.widevine", 监控的进程exec文件名字 48 | "waitSoPath": "/apex/com.android.art/lib64/libart.so", 等待这个so加载在继续执行 49 | "waitFunSym": "", 等待这个函数执行在继续执行 50 | "InjectSO": "/data/adb/modules/ZygiskADI/lib/arm64-v8a/libDrmHook.so", 要加载的so文件 51 | "InjectFunSym": "DrmIdHook", 要执行的函数 52 | "InjectFunArg": "11111111111193baf8cb6a22de8a5ae4bfecc174b1a9405dc71b8b3fac1c734f" , 函数参数,目前只支持一个参数,并且这个参数会传入到第二个参数的位置里,第一个为so的handle 53 | "monitorCount": 10 监控的次数,如果失败了,注入程序不懂程序,超过次数就不会再注入了, 54 | }] 55 | } 56 | 57 | 58 | 59 | ``` 60 | 61 | waitSoPath 尽量不要不写 62 | waitFunSym 可以不写,如果不写,将在so加载以后直接加载so. 63 | waitSoPath和waitFunSym,一般是是配合,表示某个so的某个函数,但这个函数执行以后执行hook代码 64 | 65 | 66 | 67 | 68 | ## 问题 69 | + zygisk commpanion 70 | 71 | 这个功能并未完全测试,lsp使用虽然测试了,但是lsp并未完全使用,目前不知道这个功能是否有问题,而且这个功能我使用的比较少,抄袭的magisk 的zygisk的代码 72 | 73 | + init 子进程的问题 74 | 75 | 1、提供init子进程注入功能,但是目前的zygisk 设计框架可能不一定是适合,比如selinux权限这些 76 | 2、kernelsu 提供的挂在镜像的 /data/adb/module 更是无法支持,init ns里没有挂载这个目录 77 | 3、如果想要使用这个功能建议自己处理挂载路径和so权限问题,这比较简单的,而且我觉得是比较正常的. 78 | 79 | + 32 zygisk 不支持 80 | android 有两个架构的zygote,但是现在32未程序已经很少了,后续看情况支持 81 | 82 | + 未进行大规模测试 83 | 本程序并未进行大规模手机测试,目前只进行了红米手机测试 84 | 85 | + magisk 上无法自动启动 86 | 我目前一般在kernelsu上开发,在magisk上测试了一下,发现这个模块pose-fd-data.sh 脚本无法运行,可能是我哪里写的有问题,目前没有做兼容,如果你有这方面的需求可以联系我或者自己修复一下,哦,手动运行测试是没有问题的 87 | 88 | + 89 | 90 | 91 | 92 | ## 最后 93 | 感谢zygiskNext的开源代码,以及magisk 这些项目,也希望大家且看且珍惜,对开源项目多一点宽容,安利一波公众号,希望大家支持 94 | 95 | ![输入图片说明](doc/images/wx.jpg) 96 | 97 | ![输入图片说明](doc/images/start.jpg) 98 | 99 | 100 | 101 | # 感谢 102 | https://github.com/Dr-TSNG/ZygiskNext 103 | 104 | https://github.com/topjohnwu/Magisk -------------------------------------------------------------------------------- /Zygisk/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /Zygisk/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | } 4 | 5 | val defaultCFlags = arrayOf( 6 | "-Wall", "-Wextra", 7 | "-fno-rtti", "-fno-exceptions", 8 | "-fno-stack-protector", "-fomit-frame-pointer", 9 | "-Wno-builtin-macro-redefined", "-D__FILE__=__FILE_NAME__" 10 | ) 11 | 12 | 13 | android { 14 | 15 | defaultConfig { 16 | 17 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 18 | consumerProguardFiles("consumer-rules.pro") 19 | externalNativeBuild { 20 | cmake { 21 | cFlags("-std=c18", *defaultCFlags) 22 | cppFlags("-std=c++20", *defaultCFlags) 23 | abiFilters.add("arm64-v8a") 24 | abiFilters.add("armeabi-v7a") 25 | 26 | } 27 | } 28 | } 29 | 30 | externalNativeBuild { 31 | cmake { 32 | path("src/main/cpp/CMakeLists.txt") 33 | version = "3.22.1" 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Zygisk/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thehepta/Android-Debug-Inject/8a5450a38e9c2a6cc97c98e717d116ad34c3b9af/Zygisk/consumer-rules.pro -------------------------------------------------------------------------------- /Zygisk/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /Zygisk/src/androidTest/java/com/hepta/zygisk/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.hepta.zygisk; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("com.hepta.zygisk.test", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /Zygisk/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # For more information about using CMake with Android Studio, read the 2 | # documentation: https://d.android.com/studio/projects/add-native-code.html. 3 | # For more examples on how to use CMake, see https://github.com/android/ndk-samples. 4 | 5 | # Sets the minimum CMake version required for this project. 6 | cmake_minimum_required(VERSION 3.22.1) 7 | 8 | # Declares the project name. The project name can be accessed via ${ PROJECT_NAME}, 9 | # Since this is the top level CMakeLists.txt, the project name is also accessible 10 | # with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level 11 | # build script scope). 12 | project("zygisk") 13 | 14 | set(CMAKE_CXX_STANDARD 23) 15 | 16 | # configure a header file to pass some of the CMake settings 17 | # to the source code 18 | 19 | # add the binary tree to the search path for include files 20 | # so that we will find TutorialConfig.h 21 | 22 | # add the executable 23 | add_subdirectory(lsplt) 24 | 25 | 26 | include_directories(common) 27 | aux_source_directory(common COMMON_SRC_LIST) 28 | add_library(common STATIC ${COMMON_SRC_LIST}) 29 | target_include_directories(common PRIVATE include) 30 | 31 | aux_source_directory(zygiskd ZYGISKD_SRC_LIST) 32 | add_executable(zygiskd ${ZYGISKD_SRC_LIST}) 33 | target_link_libraries(zygiskd log common) 34 | 35 | aux_source_directory(zygisk ZYGISK_SRC_LIST) 36 | add_library(zygisk SHARED ${ZYGISK_SRC_LIST}) 37 | target_link_libraries(zygisk log common lsplt_static) 38 | 39 | 40 | #aux_source_directory(rootImp ROOTIMP_SRC_LIST) 41 | #add_executable(rootImp ${ROOTIMP_SRC_LIST}) 42 | 43 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/common/daemon.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "daemon.h" 6 | #include "socket_utils.h" 7 | 8 | 9 | namespace zygiskComm { 10 | 11 | static std::string TMP_PATH; 12 | 13 | 14 | 15 | void InitRequestorSocket(const char *path) { 16 | TMP_PATH = path; 17 | } 18 | 19 | std::string GetRequestorSocket() { 20 | return TMP_PATH; 21 | } 22 | 23 | int Connect(uint8_t retry) { 24 | int fd = socket( AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0 ); 25 | struct sockaddr_un remote_addr; //服务器端网络地址结构体 26 | set_sockaddr(remote_addr); 27 | 28 | while (retry--) { 29 | int r = connect(fd, reinterpret_cast(&remote_addr), sizeof(remote_addr)); 30 | if (r == 0) return fd; 31 | if (retry) { 32 | PLOGE("Retrying to connect to zygiskd, sleep 1s\n"); 33 | sleep(1); 34 | } 35 | } 36 | 37 | close(fd); 38 | return -1; 39 | } 40 | 41 | void set_sockaddr(struct sockaddr_un &addr ){ 42 | memset(&addr,0,sizeof(addr)); 43 | addr.sun_family=AF_UNIX; 44 | strcpy(addr.sun_path+1, GetRequestorSocket().c_str()); 45 | addr.sun_path[0]='\0'; 46 | } 47 | 48 | bool PingHeartbeat() { 49 | UniqueFd fd = Connect(5); 50 | if (fd == -1) { 51 | PLOGE("Connect to zygiskd"); 52 | return false; 53 | } 54 | socket_utils::write_u8(fd, (uint8_t) SocketAction::PingHeartBeat); 55 | return true; 56 | } 57 | 58 | int RequestLogcatFd() { 59 | int fd = Connect(1); 60 | if (fd == -1) { 61 | PLOGE("RequestLogcatFd"); 62 | return -1; 63 | } 64 | socket_utils::write_u8(fd, (uint8_t) SocketAction::RequestLogcatFd); 65 | return fd; 66 | } 67 | 68 | uint32_t GetProcessFlags(uid_t uid) { 69 | UniqueFd fd = Connect(1); 70 | if (fd == -1) { 71 | PLOGE("GetProcessFlags"); 72 | return 0; 73 | } 74 | socket_utils::write_u8(fd, (uint8_t) SocketAction::GetProcessFlags); 75 | socket_utils::write_u32(fd, uid); 76 | return socket_utils::read_u32(fd); 77 | } 78 | //这个函数会编译进注入库,同时被32为和64位使用,请求不同架构的文件描述符 79 | std::vector ReadModules() { 80 | std::vector modules; 81 | UniqueFd fd = Connect(1); 82 | if (fd == -1) { 83 | PLOGE("ReadModules failed"); 84 | return modules; 85 | } 86 | socket_utils::write_u8(fd, (uint8_t) SocketAction::ReadModules); 87 | uint8_t arch=0; 88 | #if defined(__LP64__) 89 | arch=1; 90 | #endif 91 | socket_utils::write_u8(fd, arch); 92 | 93 | size_t len = socket_utils::read_usize(fd); 94 | for (size_t i = 0; i < len; i++) { 95 | Module md; 96 | std::string name = socket_utils::read_string(fd); 97 | int module_fd = socket_utils::recv_fd(fd); 98 | md.name = name; 99 | if(arch == 1){ 100 | md.z64=module_fd; 101 | } else{ 102 | md.z32=module_fd; 103 | } 104 | modules.emplace_back(md); 105 | } 106 | return modules; 107 | } 108 | //这个函数只会在zygiskd 进程运行,并且只会运行64位,所有需要对两个架构做处理 109 | void WriteModules(int fd,std::vector modules){ 110 | 111 | uint8_t arch = socket_utils::read_u8(fd); 112 | 113 | size_t len = modules.size(); 114 | socket_utils::write_usize(fd,len); 115 | 116 | for (size_t i = 0; i < len; i++) { 117 | socket_utils::write_string(fd,modules[i].name); 118 | if(arch == 1){ 119 | socket_utils::send_fd(fd,modules[i].z64); 120 | } else{ 121 | socket_utils::send_fd(fd,modules[i].z32); 122 | 123 | } 124 | } 125 | 126 | } 127 | 128 | void CacheMountNamespace(pid_t pid) { 129 | UniqueFd fd = Connect(1); 130 | if (fd == -1) { 131 | PLOGE("CacheMountNamespace"); 132 | } 133 | socket_utils::write_u8(fd, (uint8_t) SocketAction::CacheMountNamespace); 134 | socket_utils::write_u32(fd, (uint32_t) pid); 135 | } 136 | 137 | std::string UpdateMountNamespace(MountNamespace type) { 138 | UniqueFd fd = Connect(1); 139 | if (fd == -1) { 140 | PLOGE("UpdateMountNamespace"); 141 | return ""; 142 | } 143 | socket_utils::write_u8(fd, (uint8_t) SocketAction::UpdateMountNamespace); 144 | socket_utils::write_u8(fd, (uint8_t) type); 145 | uint32_t target_pid = socket_utils::read_u32(fd); 146 | int target_fd = (int) socket_utils::read_u32(fd); 147 | if (target_fd == 0) return ""; 148 | return "/proc/" + std::to_string(target_pid) + "/fd/" + std::to_string(target_fd); 149 | } 150 | 151 | 152 | 153 | // 这个是实现的zygisk请求,向zygiskd服务发送请求,但是因为zygiskd 也是我们自己实现的 154 | // 我们需要和zygiskd 服务中的这个请求的处理相匹配. 155 | // 如果我们想要和别的zygiskd服务兼容需要去兼容别的zygisk服务 156 | int ConnectCompanion(size_t index) { 157 | int fd = Connect(1); 158 | if (fd == -1) { 159 | PLOGE("ConnectCompanion"); 160 | return -1; 161 | } 162 | socket_utils::write_u8(fd, (uint8_t) SocketAction::RequestCompanionSocket); 163 | #ifdef __LP64__ 164 | socket_utils::write_u32(fd, 1); 165 | #else 166 | socket_utils::write_usize(fd, 0); 167 | #endif 168 | socket_utils::write_u32(fd, index); 169 | return fd; 170 | // magisk zygis 这里是没有等待的,所以这里也不等待 171 | // if (socket_utils::read_u8(fd) == 1) { 172 | // return fd; 173 | // } else { 174 | // close(fd); 175 | // return -1; 176 | // } 177 | } 178 | 179 | int GetModuleDir(size_t index) { 180 | UniqueFd fd = Connect(1); 181 | if (fd == -1) { 182 | PLOGE("GetModuleDir"); 183 | return -1; 184 | } 185 | socket_utils::write_u8(fd, (uint8_t) SocketAction::GetModuleDir); 186 | socket_utils::write_usize(fd, index); 187 | return socket_utils::recv_fd(fd); 188 | } 189 | 190 | 191 | 192 | void ZygoteRestart() { 193 | UniqueFd fd = Connect(1); 194 | if (fd == -1) { 195 | if (errno == ENOENT) { 196 | LOGD("Could not notify ZygoteRestart (maybe it hasn't been created)"); 197 | } else { 198 | LOGD("Could not notify ZygoteRestart"); 199 | } 200 | return; 201 | } 202 | if (!socket_utils::write_u8(fd, (uint8_t) SocketAction::ZygoteRestart)) { 203 | LOGD("Failed to request ZygoteRestart"); 204 | } 205 | } 206 | 207 | void SystemServerStarted() { 208 | UniqueFd fd = Connect(1); 209 | if (fd == -1) { 210 | PLOGE("Failed to report system server started"); 211 | } else { 212 | if (!socket_utils::write_u8(fd, (uint8_t) SocketAction::SystemServerStarted)) { 213 | PLOGE("Failed to report system server started"); 214 | } 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/common/daemon.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | 11 | class UniqueFd { 12 | using Fd = int; 13 | public: 14 | UniqueFd() = default; 15 | 16 | UniqueFd(Fd fd) : fd_(fd) {} 17 | 18 | ~UniqueFd() { if (fd_ >= 0) close(fd_); } 19 | 20 | // Disallow copy 21 | UniqueFd(const UniqueFd&) = delete; 22 | 23 | UniqueFd& operator=(const UniqueFd&) = delete; 24 | 25 | // Allow move 26 | UniqueFd(UniqueFd&& other) { std::swap(fd_, other.fd_); } 27 | 28 | UniqueFd& operator=(UniqueFd&& other) { 29 | std::swap(fd_, other.fd_); 30 | return *this; 31 | } 32 | 33 | // Implict cast to Fd 34 | operator const Fd&() const { return fd_; } 35 | 36 | private: 37 | Fd fd_ = -1; 38 | }; 39 | 40 | 41 | struct Module { 42 | std::string name; 43 | int companion; 44 | int z32 = -1; 45 | int z64 = -1; 46 | 47 | }; 48 | 49 | namespace zygiskComm { 50 | 51 | enum class SocketAction { 52 | PingHeartBeat , 53 | RequestLogcatFd, 54 | GetProcessFlags, 55 | CacheMountNamespace, 56 | UpdateMountNamespace, 57 | ReadModules, 58 | RequestCompanionSocket, 59 | GetModuleDir, 60 | ZygoteRestart, 61 | SystemServerStarted, 62 | }; 63 | 64 | enum class MountNamespace { Clean, Root, Module }; 65 | 66 | 67 | void InitRequestorSocket(const char *path); 68 | 69 | std::string GetTmpPath(); 70 | void WriteModules(int fd,std::vector modules); 71 | bool PingHeartbeat(); 72 | 73 | int RequestLogcatFd(); 74 | 75 | std::vector ReadModules(); 76 | 77 | uint32_t GetProcessFlags(uid_t uid); 78 | 79 | std::string UpdateMountNamespace(MountNamespace type); 80 | 81 | int ConnectCompanion(size_t index); 82 | 83 | int GetModuleDir(size_t index); 84 | 85 | void ZygoteRestart(); 86 | 87 | void SystemServerStarted(); 88 | 89 | void set_sockaddr(struct sockaddr_un &addr); 90 | 91 | 92 | } 93 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/common/dl.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "dl.h" 9 | #include "logging.h" 10 | 11 | extern "C" [[gnu::weak]] struct android_namespace_t* 12 | //NOLINTNEXTLINE 13 | __loader_android_create_namespace([[maybe_unused]] const char* name, 14 | [[maybe_unused]] const char* ld_library_path, 15 | [[maybe_unused]] const char* default_library_path, 16 | [[maybe_unused]] uint64_t type, 17 | [[maybe_unused]] const char* permitted_when_isolated_path, 18 | [[maybe_unused]] android_namespace_t* parent, 19 | [[maybe_unused]] const void* caller_addr); 20 | 21 | void* DlopenExt(const char* path, int flags) { 22 | auto info = android_dlextinfo{}; 23 | auto* dir = dirname(path); 24 | auto* ns = &__loader_android_create_namespace == nullptr ? nullptr : 25 | __loader_android_create_namespace(path, dir, nullptr, 26 | 2, /* ANDROID_NAMESPACE_TYPE_SHARED */ 27 | nullptr, nullptr, 28 | reinterpret_cast(&DlopenExt)); 29 | if (ns) { 30 | info.flags = ANDROID_DLEXT_USE_NAMESPACE; 31 | info.library_namespace = ns; 32 | 33 | LOGD("Open %s with namespace %p", path, ns); 34 | } else { 35 | LOGD("Cannot create namespace for %s", path); 36 | } 37 | 38 | auto* handle = android_dlopen_ext(path, flags, &info); 39 | if (handle) { 40 | LOGD("dlopen %s: %p", path, handle); 41 | } else { 42 | LOGE("dlopen %s: %s", path, dlerror()); 43 | } 44 | return handle; 45 | } 46 | 47 | void* DlopenMem(int fd, int flags) { 48 | auto info = android_dlextinfo{ 49 | .flags = ANDROID_DLEXT_USE_LIBRARY_FD, 50 | .library_fd = fd 51 | }; 52 | 53 | auto* handle = android_dlopen_ext("/jit-cache", flags, &info); 54 | if (handle) { 55 | LOGV("dlopen fd %d: %p", fd, handle); 56 | } else { 57 | LOGE("dlopen fd %d: %s", fd, dlerror()); 58 | } 59 | return handle; 60 | } 61 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/common/dl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void *DlopenExt(const char *path, int flags); 6 | 7 | void *DlopenMem(int memfd, int flags); 8 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/common/elf_symbol_resolver.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by chic on 2023/6/2. 3 | // 4 | 5 | #pragma once 6 | 7 | 8 | 9 | class SymbolName { 10 | public: 11 | explicit SymbolName(const char* name) 12 | : name_(name), has_elf_hash_(false), has_gnu_hash_(false), 13 | elf_hash_(0), gnu_hash_(0) { } 14 | 15 | const char* get_name() { 16 | return name_; 17 | } 18 | 19 | 20 | uint32_t elf_hash(); 21 | uint32_t gnu_hash(); 22 | 23 | 24 | private: 25 | const char* name_; 26 | bool has_elf_hash_; 27 | bool has_gnu_hash_; 28 | uint32_t elf_hash_; 29 | uint32_t gnu_hash_; 30 | 31 | }; 32 | 33 | void *get_remote_load_Sym_Addr(void *so_addr, pid_t pid, const char *symbol_name) ; 34 | 35 | void *get_self_load_Sym_Addr(const char *library_name, const char *symbol_name) ; 36 | 37 | uintptr_t get_libFile_Symbol_off(char *lib_path,char *fun_name); -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/common/files.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "files.hpp" 4 | #include "misc.hpp" 5 | 6 | using namespace std::string_view_literals; 7 | 8 | void file_readline(bool trim, FILE *fp, const std::function &fn) { 9 | size_t len = 1024; 10 | char *buf = (char *) malloc(len); 11 | char *start; 12 | ssize_t read; 13 | while ((read = getline(&buf, &len, fp)) >= 0) { 14 | start = buf; 15 | if (trim) { 16 | while (read && "\n\r "sv.find(buf[read - 1]) != std::string::npos) 17 | --read; 18 | buf[read] = '\0'; 19 | while (*start == ' ') 20 | ++start; 21 | } 22 | if (!fn(start)) 23 | break; 24 | } 25 | free(buf); 26 | } 27 | 28 | void file_readline(bool trim, const char *file, const std::function &fn) { 29 | if (auto fp = open_file(file, "re")) 30 | file_readline(trim, fp.get(), fn); 31 | } 32 | void file_readline(const char *file, const std::function &fn) { 33 | file_readline(false, file, fn); 34 | } 35 | 36 | std::vector parse_mount_info(const char *pid) { 37 | char buf[PATH_MAX] = {}; 38 | snprintf(buf, sizeof(buf), "/proc/%s/mountinfo", pid); 39 | std::vector result; 40 | 41 | file_readline(buf, [&result](std::string_view line) -> bool { 42 | int root_start = 0, root_end = 0; 43 | int target_start = 0, target_end = 0; 44 | int vfs_option_start = 0, vfs_option_end = 0; 45 | int type_start = 0, type_end = 0; 46 | int source_start = 0, source_end = 0; 47 | int fs_option_start = 0, fs_option_end = 0; 48 | int optional_start = 0, optional_end = 0; 49 | unsigned int id, parent, maj, min; 50 | sscanf(line.data(), 51 | "%u " // (1) id 52 | "%u " // (2) parent 53 | "%u:%u " // (3) maj:min 54 | "%n%*s%n " // (4) mountroot 55 | "%n%*s%n " // (5) target 56 | "%n%*s%n" // (6) vfs options (fs-independent) 57 | "%n%*[^-]%n - " // (7) optional fields 58 | "%n%*s%n " // (8) FS type 59 | "%n%*s%n " // (9) source 60 | "%n%*s%n", // (10) fs options (fs specific) 61 | &id, &parent, &maj, &min, &root_start, &root_end, &target_start, 62 | &target_end, &vfs_option_start, &vfs_option_end, 63 | &optional_start, &optional_end, &type_start, &type_end, 64 | &source_start, &source_end, &fs_option_start, &fs_option_end); 65 | 66 | auto root = line.substr(root_start, root_end - root_start); 67 | auto target = line.substr(target_start, target_end - target_start); 68 | auto vfs_option = 69 | line.substr(vfs_option_start, vfs_option_end - vfs_option_start); 70 | ++optional_start; 71 | --optional_end; 72 | auto optional = line.substr( 73 | optional_start, 74 | optional_end - optional_start > 0 ? optional_end - optional_start : 0); 75 | 76 | auto type = line.substr(type_start, type_end - type_start); 77 | auto source = line.substr(source_start, source_end - source_start); 78 | auto fs_option = 79 | line.substr(fs_option_start, fs_option_end - fs_option_start); 80 | 81 | unsigned int shared = 0; 82 | unsigned int master = 0; 83 | unsigned int propagate_from = 0; 84 | if (auto pos = optional.find("shared:"); pos != std::string_view::npos) { 85 | shared = parse_int(optional.substr(pos + 7)); 86 | } 87 | if (auto pos = optional.find("master:"); pos != std::string_view::npos) { 88 | master = parse_int(optional.substr(pos + 7)); 89 | } 90 | if (auto pos = optional.find("propagate_from:"); 91 | pos != std::string_view::npos) { 92 | propagate_from = parse_int(optional.substr(pos + 15)); 93 | } 94 | 95 | result.emplace_back(mount_info { 96 | .id = id, 97 | .parent = parent, 98 | .device = static_cast(makedev(maj, min)), 99 | .root {root}, 100 | .target {target}, 101 | .vfs_option {vfs_option}, 102 | .optional { 103 | .shared = shared, 104 | .master = master, 105 | .propagate_from = propagate_from, 106 | }, 107 | .type {type}, 108 | .source {source}, 109 | .fs_option {fs_option}, 110 | }); 111 | return true; 112 | }); 113 | return result; 114 | } 115 | 116 | sDIR make_dir(DIR *dp) { 117 | return sDIR(dp, [](DIR *dp){ return dp ? closedir(dp) : 1; }); 118 | } 119 | 120 | sFILE make_file(FILE *fp) { 121 | return sFILE(fp, [](FILE *fp){ return fp ? fclose(fp) : 1; }); 122 | } 123 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/common/files.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | struct mount_info { 7 | unsigned int id; 8 | unsigned int parent; 9 | dev_t device; 10 | std::string root; 11 | std::string target; 12 | std::string vfs_option; 13 | struct { 14 | unsigned int shared; 15 | unsigned int master; 16 | unsigned int propagate_from; 17 | } optional; 18 | std::string type; 19 | std::string source; 20 | std::string fs_option; 21 | }; 22 | 23 | void file_readline(bool trim, FILE *fp, const std::function &fn); 24 | void file_readline(bool trim, const char *file, const std::function &fn); 25 | void file_readline(const char *file, const std::function &fn); 26 | 27 | std::vector parse_mount_info(const char *pid); 28 | 29 | using sFILE = std::unique_ptr; 30 | using sDIR = std::unique_ptr; 31 | sDIR make_dir(DIR *dp); 32 | sFILE make_file(FILE *fp); 33 | 34 | static inline sDIR open_dir(const char *path) { 35 | return make_dir(opendir(path)); 36 | } 37 | 38 | static inline sDIR xopen_dir(const char *path) { 39 | return make_dir(opendir(path)); 40 | } 41 | 42 | static inline sDIR xopen_dir(int dirfd) { 43 | return make_dir(fdopendir(dirfd)); 44 | } 45 | 46 | static inline sFILE open_file(const char *path, const char *mode) { 47 | return make_file(fopen(path, mode)); 48 | } 49 | 50 | static inline sFILE xopen_file(const char *path, const char *mode) { 51 | return make_file(fopen(path, mode)); 52 | } 53 | 54 | static inline sFILE xopen_file(int fd, const char *mode) { 55 | return make_file(fdopen(fd, mode)); 56 | } 57 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/common/logging.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "logging.h" 5 | #include "socket_utils.h" 6 | 7 | namespace logging { 8 | static int logfd = -1; 9 | 10 | void setfd(int fd) { 11 | close(logfd); 12 | logfd = fd; 13 | } 14 | 15 | int getfd() { 16 | return logfd; 17 | } 18 | 19 | void log(int prio, const char* tag, const char* fmt, ...) { 20 | if (logfd == -1) { 21 | va_list ap; 22 | va_start(ap, fmt); 23 | __android_log_vprint(prio, tag, fmt, ap); 24 | va_end(ap); 25 | } else { 26 | char buf[BUFSIZ]; 27 | va_list ap; 28 | va_start(ap, fmt); 29 | vsnprintf(buf, sizeof(buf), fmt, ap); 30 | va_end(ap); 31 | socket_utils::write_u8(logfd, prio); 32 | socket_utils::write_string(logfd, tag); 33 | socket_utils::write_string(logfd, buf); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/common/logging.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #ifndef LOG_TAG 8 | #if defined(__LP64__) 9 | # define LOG_TAG "zygisk" 10 | #else 11 | # define LOG_TAG "zygisk-core32" 12 | #endif 13 | #endif 14 | 15 | 16 | 17 | 18 | 19 | #ifndef NDEBUG 20 | #define LOGD(...) logging::log(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) 21 | #define LOGV(...) logging::log(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) 22 | #else 23 | #define LOGD(...) 24 | #define LOGV(...) 25 | #endif 26 | 27 | #define LOGI(...) logging::log(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) 28 | #define LOGW(...) logging::log(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) 29 | #define LOGE(...) logging::log(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) 30 | #define LOGF(...) logging::log(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__) 31 | #define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno)) 32 | 33 | namespace logging { 34 | void setfd(int fd); 35 | 36 | int getfd(); 37 | 38 | [[gnu::format(printf, 3, 4)]] 39 | void log(int prio, const char* tag, const char* fmt, ...); 40 | } 41 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/common/misc.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "misc.hpp" 4 | 5 | int new_daemon_thread(thread_entry entry, void *arg) { 6 | pthread_t thread; 7 | pthread_attr_t attr; 8 | pthread_attr_init(&attr); 9 | pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 10 | errno = pthread_create(&thread, &attr, entry, arg); 11 | if (errno) { 12 | PLOGE("pthread_create"); 13 | } 14 | return errno; 15 | } 16 | 17 | int parse_int(std::string_view s) { 18 | int val = 0; 19 | for (char c : s) { 20 | if (!c) break; 21 | if (c > '9' || c < '0') 22 | return -1; 23 | val = val * 10 + c - '0'; 24 | } 25 | return val; 26 | } 27 | 28 | std::list split_str(std::string_view s, std::string_view delimiter) { 29 | std::list ret; 30 | size_t pos = 0; 31 | while (pos < s.size()) { 32 | auto next = s.find(delimiter, pos); 33 | if (next == std::string_view::npos) { 34 | ret.emplace_back(s.substr(pos)); 35 | break; 36 | } 37 | ret.emplace_back(s.substr(pos, next - pos)); 38 | pos = next + delimiter.size(); 39 | } 40 | return ret; 41 | } 42 | 43 | std::string join_str(const std::list& list, std::string_view delimiter) { 44 | std::string ret; 45 | for (auto& s : list) { 46 | if (!ret.empty()) 47 | ret += delimiter; 48 | ret += s; 49 | } 50 | return ret; 51 | } 52 | 53 | 54 | int fork_dont_care() { 55 | if (int pid = fork()) { 56 | waitpid(pid, nullptr, 0); 57 | return pid; 58 | } else if (fork()) { 59 | exit(0); 60 | } 61 | return 0; 62 | } 63 | 64 | int vssprintf(char *dest, size_t size, const char *fmt, va_list ap) { 65 | if (size > 0) { 66 | *dest = 0; 67 | return std::min(vsnprintf(dest, size, fmt, ap), (int) size - 1); 68 | } 69 | return -1; 70 | } 71 | 72 | int ssprintf(char *dest, size_t size, const char *fmt, ...) { 73 | va_list va; 74 | va_start(va, fmt); 75 | int r = vssprintf(dest, size, fmt, va); 76 | va_end(va); 77 | return r; 78 | } -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/common/misc.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "logging.h" 10 | 11 | #define DISALLOW_COPY_AND_MOVE(clazz) \ 12 | clazz(const clazz &) = delete; \ 13 | clazz(clazz &&) = delete; 14 | 15 | class mutex_guard { 16 | DISALLOW_COPY_AND_MOVE(mutex_guard) 17 | public: 18 | explicit mutex_guard(pthread_mutex_t &m): mutex(&m) { 19 | pthread_mutex_lock(mutex); 20 | } 21 | void unlock() { 22 | pthread_mutex_unlock(mutex); 23 | mutex = nullptr; 24 | } 25 | ~mutex_guard() { 26 | if (mutex) pthread_mutex_unlock(mutex); 27 | } 28 | private: 29 | pthread_mutex_t *mutex; 30 | }; 31 | 32 | using thread_entry = void *(*)(void *); 33 | int new_daemon_thread(thread_entry entry, void *arg); 34 | 35 | static inline bool str_contains(std::string_view s, std::string_view ss) { 36 | return s.find(ss) != std::string_view::npos; 37 | } 38 | 39 | template 40 | class stateless_allocator { 41 | public: 42 | using value_type = T; 43 | T *allocate(size_t num) { return static_cast(Impl::allocate(sizeof(T) * num)); } 44 | void deallocate(T *ptr, size_t num) { Impl::deallocate(ptr, sizeof(T) * num); } 45 | stateless_allocator() = default; 46 | stateless_allocator(const stateless_allocator&) = default; 47 | stateless_allocator(stateless_allocator&&) = default; 48 | template 49 | stateless_allocator(const stateless_allocator&) {} 50 | bool operator==(const stateless_allocator&) { return true; } 51 | bool operator!=(const stateless_allocator&) { return false; } 52 | }; 53 | 54 | template 55 | class reversed_container { 56 | public: 57 | reversed_container(T &base) : base(base) {} 58 | decltype(std::declval().rbegin()) begin() { return base.rbegin(); } 59 | decltype(std::declval().crbegin()) begin() const { return base.crbegin(); } 60 | decltype(std::declval().crbegin()) cbegin() const { return base.crbegin(); } 61 | decltype(std::declval().rend()) end() { return base.rend(); } 62 | decltype(std::declval().crend()) end() const { return base.crend(); } 63 | decltype(std::declval().crend()) cend() const { return base.crend(); } 64 | private: 65 | T &base; 66 | }; 67 | 68 | template 69 | reversed_container reversed(T &base) { 70 | return reversed_container(base); 71 | } 72 | 73 | template 74 | static inline void default_new(T *&p) { p = new T(); } 75 | 76 | template 77 | static inline void default_new(std::unique_ptr &p) { p.reset(new T()); } 78 | 79 | struct StringCmp { 80 | using is_transparent = void; 81 | bool operator()(std::string_view a, std::string_view b) const { return a < b; } 82 | }; 83 | 84 | /* 85 | * Bionic's atoi runs through strtol(). 86 | * Use our own implementation for faster conversion. 87 | */ 88 | int parse_int(std::string_view s); 89 | 90 | std::list split_str(std::string_view s, std::string_view delimiter); 91 | 92 | std::string join_str(const std::list& list, std::string_view delimiter); 93 | 94 | int ssprintf(char *dest, size_t size, const char *fmt, ...) ; 95 | int fork_dont_care() ; 96 | template 97 | static inline T align_to(T v, int a) { 98 | static_assert(std::is_integral::value); 99 | return (v + a - 1) / a * a; 100 | } 101 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/common/socket_utils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "vector" 5 | #include "socket_utils.h" 6 | 7 | namespace socket_utils { 8 | 9 | ssize_t xread(int fd, void* buf, size_t count) { 10 | size_t read_sz = 0; 11 | ssize_t ret; 12 | do { 13 | ret = read(fd, (std::byte*) buf + read_sz, count - read_sz); 14 | if (ret < 0) { 15 | if (errno == EINTR) continue; 16 | PLOGE("read"); 17 | return ret; 18 | } 19 | read_sz += ret; 20 | } while (read_sz != count && ret != 0); 21 | if (read_sz != count) { 22 | PLOGE("read (%zu != %zu)", count, read_sz); 23 | } 24 | return read_sz; 25 | } 26 | 27 | size_t xwrite(int fd, const void* buf, size_t count) { 28 | size_t write_sz = 0; 29 | ssize_t ret; 30 | do { 31 | ret = write(fd, (std::byte*) buf + write_sz, count - write_sz); 32 | if (ret < 0) { 33 | if (errno == EINTR) continue; 34 | PLOGE("write"); 35 | return write_sz; 36 | } 37 | write_sz += ret; 38 | } while (write_sz != count && ret != 0); 39 | if (write_sz != count) { 40 | PLOGE("write (%zu != %zu)", count, write_sz); 41 | } 42 | return write_sz; 43 | } 44 | 45 | int xsendmsg(int sockfd,const struct msghdr* cmsgbuf,int flags){ 46 | int rec = sendmsg(sockfd, cmsgbuf, flags); 47 | if (rec < 0) PLOGE("recvmsg"); 48 | return rec; 49 | } 50 | 51 | ssize_t xrecvmsg(int sockfd, struct msghdr* msg, int flags) { 52 | int rec = recvmsg(sockfd, msg, flags); 53 | if (rec < 0) PLOGE("recvmsg"); 54 | return rec; 55 | } 56 | 57 | 58 | 59 | 60 | void* recv_fds(int sockfd, char* cmsgbuf, size_t bufsz, int cnt) { 61 | iovec iov = { 62 | .iov_base = &cnt, 63 | .iov_len = sizeof(cnt), 64 | }; 65 | msghdr msg = { 66 | .msg_iov = &iov, 67 | .msg_iovlen = 1, 68 | .msg_control = cmsgbuf, 69 | .msg_controllen = bufsz 70 | }; 71 | 72 | xrecvmsg(sockfd, &msg, MSG_WAITALL); 73 | cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); 74 | 75 | if (msg.msg_controllen != bufsz || 76 | cmsg == nullptr || 77 | // TODO: pass from rust: 20, expected: 16 78 | // cmsg->cmsg_len != CMSG_LEN(sizeof(int) * cnt) || 79 | cmsg->cmsg_level != SOL_SOCKET || 80 | cmsg->cmsg_type != SCM_RIGHTS) { 81 | return nullptr; 82 | } 83 | 84 | return CMSG_DATA(cmsg); 85 | } 86 | 87 | std::vector recv_fds(int sockfd) { 88 | std::vector results; 89 | 90 | // Peek fd count to allocate proper buffer 91 | int cnt; 92 | recv(sockfd, &cnt, sizeof(cnt), MSG_PEEK); 93 | if (cnt == 0) { 94 | // Consume data 95 | recv(sockfd, &cnt, sizeof(cnt), MSG_WAITALL); 96 | return results; 97 | } 98 | 99 | std::vector cmsgbuf; 100 | cmsgbuf.resize(CMSG_SPACE(sizeof(int) * cnt)); 101 | 102 | void *data = recv_fds(sockfd, cmsgbuf.data(), cmsgbuf.size(), cnt); 103 | if (data == nullptr) 104 | return results; 105 | 106 | results.resize(cnt); 107 | memcpy(results.data(), data, sizeof(int) * cnt); 108 | 109 | return results; 110 | } 111 | 112 | 113 | template 114 | inline T read_exact_or(int fd, T fail) { 115 | T res; 116 | return sizeof(T) == xread(fd, &res, sizeof(T)) ? res : fail; 117 | } 118 | 119 | template 120 | inline bool write_exact(int fd, T val) { 121 | return sizeof(T) == xwrite(fd, &val, sizeof(T)); 122 | } 123 | 124 | uint8_t read_u8(int fd) { 125 | return read_exact_or(fd, 0); 126 | } 127 | 128 | uint32_t read_u32(int fd) { 129 | return read_exact_or(fd, 0); 130 | } 131 | 132 | uint32_t read_usize(int fd) { 133 | return read_exact_or(fd, 0); 134 | } 135 | 136 | bool write_usize(int fd, uint32_t val) { 137 | return write_exact(fd, val); 138 | } 139 | 140 | std::string read_string(int fd) { 141 | auto len = read_usize(fd); 142 | char buf[len + 1]; 143 | buf[len] = '\0'; 144 | xread(fd, buf, len); 145 | return buf; 146 | } 147 | 148 | bool write_u8(int fd, uint8_t val) { 149 | return write_exact(fd, val); 150 | } 151 | 152 | bool write_u32(int fd, uint32_t val) { 153 | return write_exact(fd, val); 154 | } 155 | 156 | bool write_string(int fd, std::string_view str) { 157 | return write_usize(fd, str.size()) && str.size() == xwrite(fd, str.data(), str.size()); 158 | } 159 | 160 | int recv_fd(int sockfd) { 161 | char cmsgbuf[CMSG_SPACE(sizeof(int))]; 162 | 163 | void* data = recv_fds(sockfd, cmsgbuf, sizeof(cmsgbuf), 1); 164 | if (data == nullptr) return -1; 165 | 166 | int result; 167 | memcpy(&result, data, sizeof(int)); 168 | return result; 169 | } 170 | 171 | 172 | 173 | static int send_fds(int sockfd, void *cmsgbuf, size_t bufsz, const int *fds, int cnt) { 174 | iovec iov = { 175 | .iov_base = &cnt, 176 | .iov_len = sizeof(cnt), 177 | }; 178 | msghdr msg = { 179 | .msg_iov = &iov, 180 | .msg_iovlen = 1, 181 | }; 182 | 183 | if (cnt) { 184 | msg.msg_control = cmsgbuf; 185 | msg.msg_controllen = bufsz; 186 | cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); 187 | cmsg->cmsg_len = CMSG_LEN(sizeof(int) * cnt); 188 | cmsg->cmsg_level = SOL_SOCKET; 189 | cmsg->cmsg_type = SCM_RIGHTS; 190 | 191 | memcpy(CMSG_DATA(cmsg), fds, sizeof(int) * cnt); 192 | } 193 | 194 | return xsendmsg(sockfd, &msg, 0); 195 | } 196 | 197 | 198 | int send_fd(int sockfd, int fd) { 199 | if (fd < 0) { 200 | return send_fds(sockfd, nullptr, 0, nullptr, 0); 201 | } 202 | char cmsgbuf[CMSG_SPACE(sizeof(int))]; 203 | return send_fds(sockfd, cmsgbuf, sizeof(cmsgbuf), &fd, 1); 204 | } 205 | 206 | int send_fds(int sockfd, const int *fds, int cnt) { 207 | if (cnt == 0) { 208 | return send_fds(sockfd, nullptr, 0, nullptr, 0); 209 | } 210 | std::vector cmsgbuf; 211 | cmsgbuf.resize(CMSG_SPACE(sizeof(int) * cnt)); 212 | return send_fds(sockfd, cmsgbuf.data(), cmsgbuf.size(), fds, cnt); 213 | } 214 | 215 | 216 | } 217 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/common/socket_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "logging.h" 7 | 8 | namespace socket_utils { 9 | 10 | ssize_t xread(int fd, void *buf, size_t count); 11 | 12 | size_t xwrite(int fd, const void *buf, size_t count); 13 | 14 | uint8_t read_u8(int fd); 15 | 16 | uint32_t read_u32(int fd); 17 | 18 | uint32_t read_usize(int fd); 19 | 20 | std::string read_string(int fd); 21 | 22 | bool write_u8(int fd, uint8_t val); 23 | 24 | bool write_u32(int fd, uint32_t val); 25 | 26 | int recv_fd(int fd); 27 | 28 | bool write_usize(int fd, uint32_t val); 29 | 30 | bool write_string(int fd, std::string_view str); 31 | 32 | int send_fd(int sockfd, int fd); 33 | 34 | int send_fds(int sockfd, const int *fds, int cnt) ; 35 | 36 | std::vector recv_fds(int sockfd) ; 37 | } 38 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/lsplt/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Google 3 | IndentWidth: 4 4 | UseCRLF: false 5 | UseTab: false 6 | --- 7 | Language: Cpp 8 | DerivePointerAlignment: true 9 | PointerAlignment: Right 10 | ColumnLimit: 100 11 | AlignEscapedNewlines: Right 12 | Cpp11BracedListStyle: true 13 | Standard: Latest 14 | # IndentAccessModifiers: false 15 | IndentCaseLabels: false 16 | BreakStringLiterals: false 17 | IndentExternBlock: false 18 | AccessModifierOffset: -4 19 | # EmptyLineBeforeAccessModifier: true 20 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/lsplt/.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: > 3 | -*, 4 | bugprone-*, 5 | google-*, 6 | misc-*, 7 | modernize-*, 8 | performance-*, 9 | portability-*, 10 | readability-*, 11 | clang-analyzer-*, 12 | llvm-include-order, 13 | -modernize-use-trailing-return-type, 14 | -readability-implicit-bool-conversion, 15 | -performance-no-int-to-ptr, 16 | 17 | CheckOptions: 18 | - key: readability-identifier-naming.ClassCase 19 | value: CamelCase 20 | - key: readability-identifier-naming.ClassMemberCase 21 | value: lower_case 22 | - key: readability-identifier-naming.EnumCase 23 | value: CamelCase 24 | - key: readability-identifier-naming.EnumConstantCase 25 | value: CamelCase 26 | - key: readability-identifier-naming.EnumConstantPrefix 27 | value: k 28 | - key: readability-identifier-naming.FunctionCase 29 | value: CamelCase 30 | - key: readability-identifier-naming.GlobalConstantCase 31 | value: CamelCase 32 | - key: readability-identifier-naming.GlobalConstantPrefix 33 | value: k 34 | - key: readability-identifier-naming.StaticConstantCase 35 | value: CamelCase 36 | - key: readability-identifier-naming.StaticConstantPrefix 37 | value: k 38 | - key: readability-identifier-naming.StaticVariableCase 39 | value: CamelCase 40 | - key: readability-identifier-naming.StaticVariablePrefix 41 | value: k 42 | - key: readability-identifier-naming.MacroDefinitionCase 43 | value: UPPER_CASE 44 | - key: readability-identifier-naming.MemberCase 45 | value: lower_case 46 | - key: readability-identifier-naming.PrivateMemberSuffix 47 | value: _ 48 | - key: readability-identifier-naming.ProtectedMemberSuffix 49 | value: _ 50 | - key: readability-identifier-naming.NamespaceCase 51 | value: lower_case 52 | - key: readability-identifier-naming.ParameterCase 53 | value: lower_case 54 | - key: readability-identifier-naming.TypeAliasCase 55 | value: CamelCase 56 | - key: readability-identifier-naming.TypedefCase 57 | value: CamelCase 58 | - key: readability-identifier-naming.VariableCase 59 | value: lower_case 60 | - key: readability-identifier-naming.IgnoreMainLikeFunctions 61 | value: 1 62 | - key: readability-braces-around-statements.ShortStatementLines 63 | value: 1 64 | - key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic 65 | value: '1' 66 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/lsplt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.4.1) 2 | project(lsplt) 3 | 4 | 5 | 6 | OPTION(LSPLT_BUILD_SHARED OFF) 7 | 8 | find_program(CCACHE ccache) 9 | 10 | if (CCACHE) 11 | set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE}) 12 | set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE}) 13 | endif () 14 | 15 | if (LSPLT_STANDALONE) 16 | find_package(cxx REQUIRED CONFIG) 17 | link_libraries(cxx::cxx) 18 | endif() 19 | 20 | add_definitions(-std=c++20) 21 | 22 | set(SOURCES lsplt.cc elf_util.cc) 23 | 24 | option(LSPLT_BUILD_SHARED "If ON, lsplt will also build shared library" ON) 25 | 26 | if (LSPLT_BUILD_SHARED) 27 | message(STATUS "Building lsplt as a shared library") 28 | add_library(${PROJECT_NAME} SHARED ${SOURCES}) 29 | target_include_directories(${PROJECT_NAME} PUBLIC include) 30 | target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) 31 | target_compile_options(${PROJECT_NAME} PRIVATE -flto) 32 | target_link_options(${PROJECT_NAME} PRIVATE -flto) 33 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 34 | COMMAND ${CMAKE_COMMAND} -E make_directory ${DEBUG_SYMBOLS_PATH}/${ANDROID_ABI} 35 | COMMAND ${CMAKE_OBJCOPY} --only-keep-debug $ 36 | ${DEBUG_SYMBOLS_PATH}/${ANDROID_ABI}/${PROJECT_NAME} 37 | COMMAND ${CMAKE_STRIP} --strip-all $) 38 | 39 | target_link_libraries(${PROJECT_NAME} PUBLIC log) 40 | endif() 41 | 42 | add_library(${PROJECT_NAME}_static STATIC ${SOURCES}) 43 | target_include_directories(${PROJECT_NAME}_static PUBLIC include) 44 | target_include_directories(${PROJECT_NAME}_static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) 45 | 46 | if (NOT DEFINED DEBUG_SYMBOLS_PATH) 47 | set(DEBUG_SYMBOLS_PATH ${CMAKE_BINARY_DIR}/symbols) 48 | endif() 49 | 50 | target_link_libraries(${PROJECT_NAME}_static PUBLIC log) 51 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/lsplt/elf_util.cc: -------------------------------------------------------------------------------- 1 | #include "elf_util.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #if defined(__arm__) 9 | #define ELF_R_GENERIC_JUMP_SLOT R_ARM_JUMP_SLOT //.rel.plt 10 | #define ELF_R_GENERIC_GLOB_DAT R_ARM_GLOB_DAT //.rel.dyn 11 | #define ELF_R_GENERIC_ABS R_ARM_ABS32 //.rel.dyn 12 | #elif defined(__aarch64__) 13 | #define ELF_R_GENERIC_JUMP_SLOT R_AARCH64_JUMP_SLOT 14 | #define ELF_R_GENERIC_GLOB_DAT R_AARCH64_GLOB_DAT 15 | #define ELF_R_GENERIC_ABS R_AARCH64_ABS64 16 | #elif defined(__i386__) 17 | #define ELF_R_GENERIC_JUMP_SLOT R_386_JMP_SLOT 18 | #define ELF_R_GENERIC_GLOB_DAT R_386_GLOB_DAT 19 | #define ELF_R_GENERIC_ABS R_386_32 20 | #elif defined(__x86_64__) 21 | #define ELF_R_GENERIC_JUMP_SLOT R_X86_64_JUMP_SLOT 22 | #define ELF_R_GENERIC_GLOB_DAT R_X86_64_GLOB_DAT 23 | #define ELF_R_GENERIC_ABS R_X86_64_64 24 | #elif defined(__riscv) 25 | #define ELF_R_GENERIC_JUMP_SLOT R_RISCV_JUMP_SLOT 26 | #define ELF_R_GENERIC_GLOB_DAT R_RISCV_64 27 | #define ELF_R_GENERIC_ABS R_RISCV_64 28 | #endif 29 | 30 | #if defined(__LP64__) 31 | #define ELF_R_SYM(info) ELF64_R_SYM(info) 32 | #define ELF_R_TYPE(info) ELF64_R_TYPE(info) 33 | #else 34 | #define ELF_R_SYM(info) ELF32_R_SYM(info) 35 | #define ELF_R_TYPE(info) ELF32_R_TYPE(info) 36 | #endif 37 | 38 | namespace { 39 | template 40 | inline constexpr auto OffsetOf(ElfW(Ehdr) * head, ElfW(Off) off) { 41 | return reinterpret_cast, T, T *>>( 42 | reinterpret_cast(head) + off); 43 | } 44 | 45 | template 46 | inline constexpr auto SetByOffset(T &ptr, ElfW(Addr) base, ElfW(Addr) bias, ElfW(Addr) off) { 47 | if (auto val = bias + off; val > base) { 48 | ptr = reinterpret_cast(val); 49 | return true; 50 | } 51 | ptr = 0; 52 | return false; 53 | } 54 | 55 | } // namespace 56 | 57 | Elf::Elf(uintptr_t base_addr) : base_addr_(base_addr) { 58 | header_ = reinterpret_cast(base_addr); 59 | 60 | // check magic 61 | if (0 != memcmp(header_->e_ident, ELFMAG, SELFMAG)) return; 62 | 63 | // check class (64/32) 64 | #if defined(__LP64__) 65 | if (ELFCLASS64 != header_->e_ident[EI_CLASS]) return; 66 | #else 67 | if (ELFCLASS32 != header_->e_ident[EI_CLASS]) return; 68 | #endif 69 | 70 | // check endian (little/big) 71 | if (ELFDATA2LSB != header_->e_ident[EI_DATA]) return; 72 | 73 | // check version 74 | if (EV_CURRENT != header_->e_ident[EI_VERSION]) return; 75 | 76 | // check type 77 | if (ET_EXEC != header_->e_type && ET_DYN != header_->e_type) return; 78 | 79 | // check machine 80 | #if defined(__arm__) 81 | if (EM_ARM != header_->e_machine) return; 82 | #elif defined(__aarch64__) 83 | if (EM_AARCH64 != header_->e_machine) return; 84 | #elif defined(__i386__) 85 | if (EM_386 != header_->e_machine) return; 86 | #elif defined(__x86_64__) 87 | if (EM_X86_64 != header_->e_machine) return; 88 | #elif defined(__riscv) 89 | if (EM_RISCV != header_->e_machine) return; 90 | #else 91 | return; 92 | #endif 93 | 94 | // check version 95 | if (EV_CURRENT != header_->e_version) return; 96 | 97 | program_header_ = OffsetOf(header_, header_->e_phoff); 98 | 99 | auto ph_off = reinterpret_cast(program_header_); 100 | for (int i = 0; i < header_->e_phnum; i++, ph_off += header_->e_phentsize) { 101 | auto *program_header = reinterpret_cast(ph_off); 102 | if (program_header->p_type == PT_LOAD && program_header->p_offset == 0) { 103 | if (base_addr_ >= program_header->p_vaddr) { 104 | bias_addr_ = base_addr_ - program_header->p_vaddr; 105 | } 106 | } else if (program_header->p_type == PT_DYNAMIC) { 107 | dynamic_ = reinterpret_cast(program_header->p_vaddr); 108 | dynamic_size_ = program_header->p_memsz; 109 | } 110 | } 111 | if (!dynamic_ || !bias_addr_) return; 112 | dynamic_ = 113 | reinterpret_cast(bias_addr_ + reinterpret_cast(dynamic_)); 114 | 115 | for (auto *dynamic = dynamic_, *dynamic_end = dynamic_ + (dynamic_size_ / sizeof(dynamic[0])); 116 | dynamic < dynamic_end; ++dynamic) { 117 | switch (dynamic->d_tag) { 118 | case DT_NULL: 119 | // the end of the dynamic-section 120 | dynamic = dynamic_end; 121 | break; 122 | case DT_STRTAB: { 123 | if (!SetByOffset(dyn_str_, base_addr_, bias_addr_, dynamic->d_un.d_ptr)) return; 124 | break; 125 | } 126 | case DT_SYMTAB: { 127 | if (!SetByOffset(dyn_sym_, base_addr_, bias_addr_, dynamic->d_un.d_ptr)) return; 128 | break; 129 | } 130 | case DT_PLTREL: 131 | // use rel or rela? 132 | is_use_rela_ = dynamic->d_un.d_val == DT_RELA; 133 | break; 134 | case DT_JMPREL: { 135 | if (!SetByOffset(rel_plt_, base_addr_, bias_addr_, dynamic->d_un.d_ptr)) return; 136 | break; 137 | } 138 | case DT_PLTRELSZ: 139 | rel_plt_size_ = dynamic->d_un.d_val; 140 | break; 141 | case DT_REL: 142 | case DT_RELA: { 143 | if (!SetByOffset(rel_dyn_, base_addr_, bias_addr_, dynamic->d_un.d_ptr)) return; 144 | break; 145 | } 146 | case DT_RELSZ: 147 | case DT_RELASZ: 148 | rel_dyn_size_ = dynamic->d_un.d_val; 149 | break; 150 | case DT_ANDROID_REL: 151 | case DT_ANDROID_RELA: { 152 | if (!SetByOffset(rel_android_, base_addr_, bias_addr_, dynamic->d_un.d_ptr)) return; 153 | break; 154 | } 155 | case DT_ANDROID_RELSZ: 156 | case DT_ANDROID_RELASZ: 157 | rel_android_size_ = dynamic->d_un.d_val; 158 | break; 159 | case DT_HASH: { 160 | // ignore DT_HASH when ELF contains DT_GNU_HASH hash table 161 | if (bloom_) continue; 162 | auto *raw = reinterpret_cast(bias_addr_ + dynamic->d_un.d_ptr); 163 | bucket_count_ = raw[0]; 164 | bucket_ = raw + 2; 165 | chain_ = bucket_ + bucket_count_; 166 | break; 167 | } 168 | case DT_GNU_HASH: { 169 | auto *raw = reinterpret_cast(bias_addr_ + dynamic->d_un.d_ptr); 170 | bucket_count_ = raw[0]; 171 | sym_offset_ = raw[1]; 172 | bloom_size_ = raw[2]; 173 | bloom_shift_ = raw[3]; 174 | bloom_ = reinterpret_cast(raw + 4); 175 | bucket_ = reinterpret_cast(bloom_ + bloom_size_); 176 | chain_ = bucket_ + bucket_count_ - sym_offset_; 177 | // is_use_gnu_hash_ = true; 178 | break; 179 | } 180 | default: 181 | break; 182 | } 183 | } 184 | 185 | // check android rel/rela 186 | if (0 != rel_android_) { 187 | const auto *rel = reinterpret_cast(rel_android_); 188 | if (rel_android_size_ < 4 || rel[0] != 'A' || rel[1] != 'P' || rel[2] != 'S' || 189 | rel[3] != '2') { 190 | return; 191 | } 192 | 193 | rel_android_ += 4; 194 | rel_android_size_ -= 4; 195 | } 196 | 197 | valid_ = true; 198 | } 199 | 200 | uint32_t Elf::GnuLookup(std::string_view name) const { 201 | static constexpr auto kBloomMaskBits = sizeof(ElfW(Addr)) * 8; 202 | static constexpr uint32_t kInitialHash = 5381; 203 | static constexpr uint32_t kHashShift = 5; 204 | 205 | if (!bucket_ || !bloom_) return 0; 206 | 207 | uint32_t hash = kInitialHash; 208 | for (unsigned char chr : name) { 209 | hash += (hash << kHashShift) + chr; 210 | } 211 | 212 | auto bloom_word = bloom_[(hash / kBloomMaskBits) % bloom_size_]; 213 | uintptr_t mask = 0 | uintptr_t{1} << (hash % kBloomMaskBits) | 214 | uintptr_t{1} << ((hash >> bloom_shift_) % kBloomMaskBits); 215 | if ((mask & bloom_word) == mask) { 216 | auto idx = bucket_[hash % bucket_count_]; 217 | if (idx >= sym_offset_) { 218 | const char *strings = dyn_str_; 219 | do { 220 | auto *sym = dyn_sym_ + idx; 221 | if (((chain_[idx] ^ hash) >> 1) == 0 && name == strings + sym->st_name) { 222 | return idx; 223 | } 224 | } while ((chain_[idx++] & 1) == 0); 225 | } 226 | } 227 | return 0; 228 | } 229 | 230 | uint32_t Elf::ElfLookup(std::string_view name) const { 231 | static constexpr uint32_t kHashMask = 0xf0000000; 232 | static constexpr uint32_t kHashShift = 24; 233 | uint32_t hash = 0; 234 | uint32_t tmp; 235 | 236 | if (!bucket_ || bloom_) return 0; 237 | 238 | for (unsigned char chr : name) { 239 | hash = (hash << 4) + chr; 240 | tmp = hash & kHashMask; 241 | hash ^= tmp; 242 | hash ^= tmp >> kHashShift; 243 | } 244 | const char *strings = dyn_str_; 245 | 246 | for (auto idx = bucket_[hash % bucket_count_]; idx != 0; idx = chain_[idx]) { 247 | auto *sym = dyn_sym_ + idx; 248 | if (name == strings + sym->st_name) { 249 | return idx; 250 | } 251 | } 252 | return 0; 253 | } 254 | 255 | uint32_t Elf::LinearLookup(std::string_view name) const { 256 | if (!dyn_sym_ || !sym_offset_) return 0; 257 | for (uint32_t idx = 0; idx < sym_offset_; idx++) { 258 | auto *sym = dyn_sym_ + idx; 259 | if (name == dyn_str_ + sym->st_name) { 260 | return idx; 261 | } 262 | } 263 | return 0; 264 | } 265 | 266 | std::vector Elf::FindPltAddr(std::string_view name) const { 267 | std::vector res; 268 | 269 | uint32_t idx = GnuLookup(name); 270 | if (!idx) idx = ElfLookup(name); 271 | if (!idx) idx = LinearLookup(name); 272 | if (!idx) return res; 273 | 274 | auto looper = [&](auto begin, auto size, bool is_plt) -> void { 275 | const auto *rel_end = reinterpret_cast(begin + size); 276 | for (const auto *rel = reinterpret_cast(begin); rel < rel_end; ++rel) { 277 | auto r_info = rel->r_info; 278 | auto r_offset = rel->r_offset; 279 | auto r_sym = ELF_R_SYM(r_info); 280 | auto r_type = ELF_R_TYPE(r_info); 281 | if (r_sym != idx) continue; 282 | if (is_plt && r_type != ELF_R_GENERIC_JUMP_SLOT) continue; 283 | if (!is_plt && r_type != ELF_R_GENERIC_ABS && r_type != ELF_R_GENERIC_GLOB_DAT) { 284 | continue; 285 | } 286 | auto addr = bias_addr_ + r_offset; 287 | if (addr > base_addr_) res.emplace_back(addr); 288 | if (is_plt) break; 289 | } 290 | }; 291 | 292 | for (const auto &[rel, rel_size, is_plt] : 293 | {std::make_tuple(rel_plt_, rel_plt_size_, true), 294 | std::make_tuple(rel_dyn_, rel_dyn_size_, false), 295 | std::make_tuple(rel_android_, rel_android_size_, false)}) { 296 | if (!rel) continue; 297 | if (is_use_rela_) { 298 | looper.template operator()(rel, rel_size, is_plt); 299 | } else { 300 | looper.template operator()(rel, rel_size, is_plt); 301 | } 302 | } 303 | 304 | return res; 305 | } 306 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/lsplt/elf_util.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | class Elf { 8 | ElfW(Addr) base_addr_ = 0; 9 | ElfW(Addr) bias_addr_ = 0; 10 | 11 | ElfW(Ehdr) *header_ = nullptr; 12 | ElfW(Phdr) *program_header_ = nullptr; 13 | 14 | ElfW(Dyn) *dynamic_ = nullptr; //.dynamic 15 | ElfW(Word) dynamic_size_ = 0; 16 | 17 | const char *dyn_str_ = nullptr; //.dynstr (string-table) 18 | ElfW(Sym) *dyn_sym_ = nullptr; //.dynsym (symbol-index to string-table's offset) 19 | 20 | ElfW(Addr) rel_plt_ = 0; //.rel.plt or .rela.plt 21 | ElfW(Word) rel_plt_size_ = 0; 22 | 23 | ElfW(Addr) rel_dyn_ = 0; //.rel.dyn or .rela.dyn 24 | ElfW(Word) rel_dyn_size_ = 0; 25 | 26 | ElfW(Addr) rel_android_ = 0; // android compressed rel or rela 27 | ElfW(Word) rel_android_size_ = 0; 28 | 29 | // for ELF hash 30 | uint32_t *bucket_ = nullptr; 31 | uint32_t bucket_count_ = 0; 32 | uint32_t *chain_ = nullptr; 33 | 34 | // append for GNU hash 35 | uint32_t sym_offset_ = 0; 36 | ElfW(Addr) *bloom_ = nullptr; 37 | uint32_t bloom_size_ = 0; 38 | uint32_t bloom_shift_ = 0; 39 | 40 | bool is_use_rela_ = false; 41 | bool valid_ = false; 42 | 43 | uint32_t GnuLookup(std::string_view name) const; 44 | uint32_t ElfLookup(std::string_view name) const; 45 | uint32_t LinearLookup(std::string_view name) const; 46 | public: 47 | std::vector FindPltAddr(std::string_view name) const; 48 | Elf(uintptr_t base_addr); 49 | bool Valid() const { return valid_; }; 50 | }; 51 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/lsplt/include/lsplt.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | /// \namespace lsplt 9 | namespace lsplt { 10 | inline namespace v2 { 11 | 12 | /// \struct MapInfo 13 | /// \brief An entry that describes a line in /proc/self/maps. You can obtain a list of these entries 14 | /// by calling #Scan(). 15 | struct MapInfo { 16 | /// \brief The start address of the memory region. 17 | uintptr_t start; 18 | /// \brief The end address of the memory region. 19 | uintptr_t end; 20 | /// \brief The permissions of the memory region. This is a bit mask of the following values: 21 | /// - PROT_READ 22 | /// - PROT_WRITE 23 | /// - PROT_EXEC 24 | uint8_t perms; 25 | /// \brief Whether the memory region is private. 26 | bool is_private; 27 | /// \brief The offset of the memory region. 28 | uintptr_t offset; 29 | /// \brief The device number of the memory region. 30 | /// Major can be obtained by #major() 31 | /// Minor can be obtained by #minor() 32 | dev_t dev; 33 | /// \brief The inode number of the memory region. 34 | ino_t inode; 35 | /// \brief The path of the memory region. 36 | std::string path; 37 | 38 | /// \brief Scans /proc/self/maps and returns a list of \ref MapInfo entries. 39 | /// This is useful to find out the inode of the library to hook. 40 | /// \param[in] pid The process id to scan. This is "self" by default. 41 | /// \return A list of \ref MapInfo entries. 42 | [[maybe_unused, gnu::visibility("default")]] static std::vector Scan(std::string_view pid = "self"); 43 | }; 44 | 45 | /// \brief Register a hook to a function by inode. For so within an archive, you should use 46 | /// #RegisterHook(ino_t, uintptr_t, size_t, std::string_view, void *, void **) instead. 47 | /// \param[in] dev The device number of the memory region. 48 | /// \param[in] inode The inode of the library to hook. You can obtain the inode by #stat() or by finding 49 | /// the library in the list returned by #lsplt::v1::MapInfo::Scan(). 50 | /// \param[in] symbol The function symbol to hook. 51 | /// \param[in] callback The callback function pointer to call when the function is called. 52 | /// \param[out] backup The backup function pointer which can call the original function. This is 53 | /// optional. 54 | /// \return Whether the hook is successfully registered. 55 | /// \note This function is thread-safe. 56 | /// \note \p backup will not be available until #CommitHook() is called. 57 | /// \note \p backup will be nullptr if the hook fails. 58 | /// \note You can unhook the function by calling this function with \p callback set to the backup 59 | /// set by previous call. 60 | /// \note LSPlt will backup the hook memory region and restore it when the 61 | /// hook is restored to its original function pointer so that there won't be dirty pages. LSPlt will 62 | /// do hooks on a copied memory region so that the original memory region will not be modified. You 63 | /// can invalidate this behaviour and hook the original memory region by calling 64 | /// #InvalidateBackup(). 65 | /// \see #CommitHook() 66 | /// \see #InvalidateBackup() 67 | [[maybe_unused, gnu::visibility("default")]] bool RegisterHook(dev_t dev, ino_t inode, std::string_view symbol, 68 | void *callback, void **backup); 69 | 70 | /// \brief Register a hook to a function by inode with offset range. This is useful when hooking 71 | /// a library that is directly loaded from an archive without extraction. 72 | /// \param[in] dev The device number of the memory region. 73 | /// \param[in] inode The inode of the library to hook. You can obtain the inode by #stat() or by finding 74 | /// the library in the list returned by #lsplt::v1::MapInfo::Scan(). 75 | /// \param[in] offset The to the library in the file. 76 | /// \param[in] size The upper bound size to the library in the file. 77 | /// \param[in] symbol The function symbol to hook. 78 | /// \param[in] callback The callback function pointer to call when the function is called. 79 | /// \param[out] backup The backup function pointer which can call the original function. This is 80 | /// optional. 81 | /// \return Whether the hook is successfully registered. 82 | /// \note This function is thread-safe. 83 | /// \note \p backup will not be available until #CommitHook() is called. 84 | /// \note \p backup will be nullptr if the hook fails. 85 | /// \note You can unhook the function by calling this function with \p callback set to the backup 86 | /// set by previous call. 87 | /// \note LSPlt will backup the hook memory region and restore it when the 88 | /// hook is restored to its original function pointer so that there won't be dirty pages. LSPlt will 89 | /// do hooks on a copied memory region so that the original memory region will not be modified. You 90 | /// can invalidate this behaviour and hook the original memory region by calling 91 | /// #InvalidateBackup(). 92 | /// \note You can get the offset range of the library by getting its entry offset and size in the 93 | /// zip file. 94 | /// \note According to the Android linker specification, the \p offset must be page aligned. 95 | /// \note The \p offset must be accurate, otherwise the hook may fail because the ELF header 96 | /// cannot be found. 97 | /// \note The \p size can be inaccurate but should be larger or equal to the library size, 98 | /// otherwise the hook may fail when the hook pointer is beyond the range. 99 | /// \note The behaviour of this function is undefined if \p offset + \p size is larger than the 100 | /// the maximum value of \p size_t. 101 | /// \see #CommitHook() 102 | /// \see #InvalidateBackup() 103 | [[maybe_unused, gnu::visibility("default")]] bool RegisterHook(dev_t dev, ino_t inode, uintptr_t offset, 104 | size_t size, std::string_view symbol, 105 | void *callback, void **backup); 106 | /// \brief Commit all registered hooks. 107 | /// \return Whether all hooks are successfully committed. If any of the hooks fail to commit, 108 | /// the result is false. 109 | /// \note This function is thread-safe. 110 | /// \note The return value indicates whether all hooks are successfully committed. You can 111 | /// determine which hook fails by checking the backup function pointer of #RegisterHook(). 112 | /// \see #RegisterHook() 113 | [[maybe_unused, gnu::visibility("default")]] bool CommitHook(); 114 | [[maybe_unused, gnu::visibility("default")]] bool CommitHook(std::vector &maps); 115 | 116 | /// \brief Invalidate backup memory regions 117 | /// Normally LSPlt will backup the hooked memory region and do hook on a copied anonymous memory 118 | /// region, and restore the original memory region when the hook is unregistered 119 | /// (when the callback of #RegisterHook() is the original function). This function will restore 120 | /// the backup memory region and do all existing hooks on the original memory region. 121 | /// \return Whether all hooks are successfully invalidated. If any of the hooks fail to invalidate, 122 | /// the result is false. 123 | /// \note This function is thread-safe. 124 | /// \note This will be automatically called when the library is unloaded. 125 | /// \see #RegisterHook() 126 | [[maybe_unused, gnu::visibility("default")]] bool InvalidateBackup(); 127 | } // namespace v2 128 | } // namespace lsplt 129 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/lsplt/logging.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifndef LOG_TAG 6 | #define LOG_TAG "LSPlt" 7 | #endif 8 | 9 | #ifdef LOG_DISABLED 10 | #define LOGD(...) 0 11 | #define LOGV(...) 0 12 | #define LOGI(...) 0 13 | #define LOGW(...) 0 14 | #define LOGE(...) 0 15 | #else 16 | #define NDEBUG 17 | #ifndef NDEBUG 18 | #define LOGD(fmt, ...) \ 19 | __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, \ 20 | "%s:%d#%s" \ 21 | ": " fmt, \ 22 | __FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(, ) __VA_ARGS__) 23 | #define LOGV(fmt, ...) \ 24 | __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, \ 25 | "%s:%d#%s" \ 26 | ": " fmt, \ 27 | __FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(, ) __VA_ARGS__) 28 | #else 29 | #define LOGD(...) 0 30 | #define LOGV(...) 0 31 | #endif 32 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) 33 | #define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) 34 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) 35 | #define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__) 36 | #define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno)) 37 | #endif 38 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/zygisk/art_method.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "jni_helper.hpp" 4 | #include "logging.h" 5 | template 6 | constexpr inline auto RoundUpTo(T v, size_t size) { 7 | return v + size - 1 - ((v + size - 1) & (size - 1)); 8 | } 9 | 10 | inline static constexpr auto kPointerSize = sizeof(void *); 11 | 12 | namespace lsplant::art { 13 | 14 | class ArtMethod { 15 | 16 | public: 17 | void *GetData() { 18 | return *reinterpret_cast(reinterpret_cast(this) + data_offset); 19 | } 20 | 21 | static art::ArtMethod *FromReflectedMethod(JNIEnv *env, jobject method) { 22 | if (art_method_field) [[likely]] { 23 | return reinterpret_cast( 24 | JNI_GetLongField(env, method, art_method_field)); 25 | } else { 26 | return reinterpret_cast(env->FromReflectedMethod(method)); 27 | } 28 | } 29 | 30 | static bool Init(JNIEnv *env) { 31 | ScopedLocalRef executable{env, nullptr}; 32 | executable = JNI_FindClass(env, "java/lang/reflect/Executable"); 33 | if (!executable) { 34 | LOGE("Failed to found Executable"); 35 | return false; 36 | } 37 | 38 | if (art_method_field = JNI_GetFieldID(env, executable, "artMethod", "J"); 39 | !art_method_field) { 40 | LOGE("Failed to find artMethod field"); 41 | return false; 42 | } 43 | 44 | auto throwable = JNI_FindClass(env, "java/lang/Throwable"); 45 | if (!throwable) { 46 | LOGE("Failed to found Executable"); 47 | return false; 48 | } 49 | auto clazz = JNI_FindClass(env, "java/lang/Class"); 50 | static_assert(std::is_same_v); 51 | jmethodID get_declared_constructors = JNI_GetMethodID(env, clazz, "getDeclaredConstructors", 52 | "()[Ljava/lang/reflect/Constructor;"); 53 | const auto constructors = 54 | JNI_Cast(JNI_CallObjectMethod(env, throwable, get_declared_constructors)); 55 | if (constructors.size() < 2) { 56 | LOGE("Throwable has less than 2 constructors"); 57 | return false; 58 | } 59 | auto &first_ctor = constructors[0]; 60 | auto &second_ctor = constructors[1]; 61 | auto *first = FromReflectedMethod(env, first_ctor.get()); 62 | auto *second = FromReflectedMethod(env, second_ctor.get()); 63 | art_method_size = reinterpret_cast(second) - reinterpret_cast(first); 64 | LOGD("ArtMethod size: %zu", art_method_size); 65 | if (RoundUpTo(4 * 9, kPointerSize) + kPointerSize * 3 < art_method_size) [[unlikely]] { 66 | LOGW("ArtMethod size exceeds maximum assume. There may be something wrong."); 67 | } 68 | entry_point_offset = art_method_size - kPointerSize; 69 | data_offset = entry_point_offset - kPointerSize; 70 | LOGD("ArtMethod::entrypoint offset: %zu", entry_point_offset); 71 | LOGD("ArtMethod::data offset: %zu", data_offset); 72 | return true; 73 | } 74 | 75 | private: 76 | inline static jfieldID art_method_field = nullptr; 77 | inline static size_t art_method_size = 0; 78 | inline static size_t entry_point_offset = 0; 79 | inline static size_t data_offset = 0; 80 | }; 81 | 82 | } // namespace lsplant::art 83 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/zygisk/clean.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "logging.h" 6 | #include "solist.hpp" 7 | 8 | 9 | void reSoMap(const char *path){ 10 | LOGD("spoofing virtual maps for %s", path); 11 | for (auto &map : lsplt::MapInfo::Scan()) { 12 | if (strstr(map.path.c_str(), path)) { 13 | void *addr = (void *) map.start; 14 | size_t size = map.end - map.start; 15 | void *copy = mmap(nullptr, size, PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); 16 | if (copy == MAP_FAILED) { 17 | LOGE("failed to backup block %s [%p, %p]", map.path.c_str(), addr, 18 | (void *) map.end); 19 | continue; 20 | } 21 | 22 | if ((map.perms & PROT_READ) == 0) { 23 | mprotect(addr, size, PROT_READ); 24 | } 25 | memcpy(copy, addr, size); 26 | mremap(copy, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, addr); 27 | mprotect(addr, size, map.perms); 28 | } 29 | } 30 | } 31 | 32 | 33 | size_t remove_soinfo(const char *path, size_t load, size_t unload, bool spoof_maps) { 34 | LOGD("cleaning trace for path %s", path); 35 | if (load > 0 || unload > 0) SoList::resetCounters(load, unload); 36 | size_t size_found = SoList::dropSoPath(path); 37 | // if (!(size_found !=-1) || !spoof_maps) return; 38 | if(size_found == -1){ 39 | return -1; 40 | } 41 | 42 | return size_found; 43 | } 44 | 45 | namespace SoList { 46 | 47 | bool initialize() { 48 | SandHook::ElfImg linker("/linker"); 49 | if (!ProtectedDataGuard::setup(linker)) return false; 50 | LOGD("found symbol ProtectedDataGuard"); 51 | 52 | std::string_view solist_sym_name = linker.findSymbolNameByPrefix("__dl__ZL6solist"); 53 | if (solist_sym_name.empty()) return false; 54 | LOGD("found symbol name %s", solist_sym_name.data()); 55 | 56 | std::string_view soinfo_free_name = 57 | linker.findSymbolNameByPrefix("__dl__ZL11soinfo_freeP6soinfo"); 58 | if (soinfo_free_name.empty()) return false; 59 | LOGD("found symbol name %s", soinfo_free_name.data()); 60 | 61 | char llvm_sufix[llvm_suffix_length + 1]; 62 | 63 | if (solist_sym_name.length() != strlen("__dl__ZL6solist")) { 64 | strncpy(llvm_sufix, solist_sym_name.data() + strlen("__dl__ZL6solist"), sizeof(llvm_sufix)); 65 | } else { 66 | llvm_sufix[0] = '\0'; 67 | } 68 | 69 | char somain_sym_name[sizeof("__dl__ZL6somain") + sizeof(llvm_sufix)]; 70 | snprintf(somain_sym_name, sizeof(somain_sym_name), "__dl__ZL6somain%s", llvm_sufix); 71 | 72 | char sonext_sym_name[sizeof("__dl__ZL6sonext") + sizeof(llvm_sufix)]; 73 | snprintf(sonext_sym_name, sizeof(somain_sym_name), "__dl__ZL6sonext%s", llvm_sufix); 74 | 75 | char vdso_sym_name[sizeof("__dl__ZL4vdso") + sizeof(llvm_sufix)]; 76 | snprintf(vdso_sym_name, sizeof(vdso_sym_name), "__dl__ZL4vdso%s", llvm_sufix); 77 | 78 | somain = getStaticPointer(linker, somain_sym_name); 79 | if (somain == nullptr) return false; 80 | LOGD("found symbol somain"); 81 | 82 | sonext = linker.getSymbAddress(sonext_sym_name); 83 | if (sonext == nullptr) return false; 84 | LOGD("found symbol sonext"); 85 | 86 | auto *vdso = getStaticPointer(linker, vdso_sym_name); 87 | if (vdso != nullptr) LOGD("found symbol vdso"); 88 | 89 | SoInfo::get_realpath_sym = reinterpret_cast( 90 | linker.getSymbAddress("__dl__ZNK6soinfo12get_realpathEv")); 91 | if (SoInfo::get_realpath_sym != nullptr) LOGD("found symbol get_realpath_sym"); 92 | 93 | // SoInfo::get_soname = reinterpret_cast( 94 | // linker.getSymbAddress("__dl__ZNK6soinfo10get_sonameEv")); 95 | // if (SoInfo::get_soname != nullptr) LOGD("found symbol get_soname"); 96 | 97 | SoInfo::soinfo_free = 98 | reinterpret_cast(linker.getSymbAddress(soinfo_free_name)); 99 | if (SoInfo::soinfo_free == nullptr) return false; 100 | LOGD("found symbol soinfo_free"); 101 | 102 | g_module_load_counter = reinterpret_cast( 103 | linker.getSymbAddress("__dl__ZL21g_module_load_counter")); 104 | if (g_module_load_counter != nullptr) LOGD("found symbol g_module_load_counter"); 105 | 106 | g_module_unload_counter = reinterpret_cast( 107 | linker.getSymbAddress("__dl__ZL23g_module_unload_counter")); 108 | if (g_module_unload_counter != nullptr) LOGD("found symbol g_module_unload_counter"); 109 | 110 | solist = getStaticPointer(linker, solist_sym_name.data()); 111 | if (solist == nullptr) return false; 112 | LOGD("found symbol solist"); 113 | 114 | bool size_filed_found = false; 115 | bool next_filed_found = false; 116 | const size_t linker_realpath_size = linker.name().size(); 117 | for (size_t i = 0; i < size_block_range / sizeof(void *); i++) { 118 | auto possible_field = reinterpret_cast(solist) + i * sizeof(void *); 119 | auto possible_size_of_somain = 120 | *reinterpret_cast(reinterpret_cast(somain) + i * sizeof(void *)); 121 | if (!size_filed_found && possible_size_of_somain < size_maximal && 122 | possible_size_of_somain > size_minimal) { 123 | SoInfo::field_size_offset = i * sizeof(void *); 124 | LOGD("field_size_offset is %zu * %zu = %p", i, sizeof(void *), 125 | (void *) SoInfo::field_size_offset); 126 | size_filed_found = true; 127 | } 128 | if (!next_filed_found && 129 | (*reinterpret_cast(possible_field) == somain || 130 | (vdso != nullptr && *reinterpret_cast(possible_field) == vdso))) { 131 | SoInfo::field_next_offset = i * sizeof(void *); 132 | LOGD("field_next_offset should be here %zu * %zu = %p", i, sizeof(void *), 133 | (void *) SoInfo::field_next_offset); 134 | next_filed_found = true; 135 | if (SoInfo::get_realpath_sym != nullptr) break; 136 | } 137 | if (size_filed_found && next_filed_found) { 138 | std::string *realpath = reinterpret_cast( 139 | reinterpret_cast(solist) + i * sizeof(void *)); 140 | if (realpath->size() == linker_realpath_size) { 141 | char buffer[100]; 142 | strncpy(buffer, realpath->c_str(), linker_realpath_size); 143 | buffer[linker_realpath_size] = '\0'; 144 | if (strcmp(linker.name().c_str(), buffer) == 0) { 145 | SoInfo::field_realpath_offset = i * sizeof(void *); 146 | LOGD("field_realpath_offset is %zu * %zu = %p", i, sizeof(void *), 147 | (void *) SoInfo::field_realpath_offset); 148 | break; 149 | } 150 | } 151 | } 152 | } 153 | return true; 154 | } 155 | 156 | size_t dropSoPath(const char *target_path) { 157 | size_t size_found = -1; 158 | if (solist == nullptr && !initialize()) { 159 | LOGE("failed to initialize solist"); 160 | return size_found; 161 | } 162 | for (auto *iter = solist; iter; iter = iter->getNext()) { 163 | if (iter->getPath() && strstr(iter->getPath(), target_path)) { 164 | SoList::ProtectedDataGuard guard; 165 | size_found = iter->getSize(); 166 | LOGD("dropping solist record for %s addr %lx with size %zu", iter->getPath(), iter,size_found);//.831488 167 | if (iter->getSize() > 0) { 168 | iter->setSize(0); 169 | SoInfo::soinfo_free(iter); 170 | // const char* soname = iter->getSoname(); 171 | // LOGE("soinfo soname %s soname_addr %p addr %lx",soname,soname,iter); // 7604c573b0 172 | // memset(iter,0, strlen(soname)); 173 | // LOGE("soinfo addr next 0x%x",iter->getNext()); 174 | } 175 | } 176 | } 177 | return size_found; 178 | } 179 | 180 | void resetCounters(size_t load, size_t unload) { 181 | if (solist == nullptr && !initialize()) { 182 | LOGE("failed to initialize solist"); 183 | return; 184 | } 185 | if (g_module_load_counter == nullptr || g_module_unload_counter == nullptr) { 186 | LOGD("g_module counters not defined, skip reseting them"); 187 | return; 188 | } 189 | auto loaded_modules = *g_module_load_counter; 190 | auto unloaded_modules = *g_module_unload_counter; 191 | if (loaded_modules >= load) { 192 | *g_module_load_counter = loaded_modules - load; 193 | LOGD("reset g_module_load_counter to %zu", (size_t) *g_module_load_counter); 194 | } 195 | if (unloaded_modules >= unload) { 196 | *g_module_unload_counter = unloaded_modules - unload; 197 | LOGD("reset g_module_unload_counter to %zu", (size_t) *g_module_unload_counter); 198 | } 199 | } 200 | } // namespace SoList 201 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/zygisk/clean.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by chic on 2025/5/14. 3 | // 4 | 5 | #ifndef ANDROID_DEBUG_INJECT_CLEAN_H 6 | #define ANDROID_DEBUG_INJECT_CLEAN_H 7 | 8 | 9 | 10 | size_t remove_soinfo(const char *path, size_t load, size_t unload, bool spoof_maps); 11 | void reSoMap(const char *path); 12 | #endif //ANDROID_DEBUG_INJECT_CLEAN_H 13 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/zygisk/elf_util.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of LSPosed. 3 | * 4 | * LSPosed is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * LSPosed is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with LSPosed. If not, see . 16 | * 17 | * Copyright (C) 2019 Swift Gan 18 | * Copyright (C) 2021 LSPosed Contributors 19 | */ 20 | #include "elf_util.hpp" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | 31 | using namespace SandHook; 32 | 33 | template 34 | inline constexpr auto offsetOf(ElfW(Ehdr) * head, ElfW(Off) off) { 35 | return reinterpret_cast, T, T *>>( 36 | reinterpret_cast(head) + off); 37 | } 38 | 39 | ElfImg::ElfImg(std::string_view base_name) : elf(base_name) { 40 | if (!findModuleBase()) { 41 | base = nullptr; 42 | return; 43 | } 44 | 45 | // load elf 46 | int fd = open(elf.data(), O_RDONLY); 47 | if (fd < 0) { 48 | // LOGE("failed to open %s", elf.data()); 49 | return; 50 | } 51 | 52 | size = lseek(fd, 0, SEEK_END); 53 | if (size <= 0) { 54 | // LOGE("lseek() failed for %s", elf.data()); 55 | } 56 | 57 | header = reinterpret_cast(mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0)); 58 | 59 | close(fd); 60 | 61 | section_header = offsetOf(header, header->e_shoff); 62 | 63 | auto shoff = reinterpret_cast(section_header); 64 | char *section_str = offsetOf(header, section_header[header->e_shstrndx].sh_offset); 65 | 66 | for (int i = 0; i < header->e_shnum; i++, shoff += header->e_shentsize) { 67 | auto *section_h = (ElfW(Shdr) *) shoff; 68 | char *sname = section_h->sh_name + section_str; 69 | auto entsize = section_h->sh_entsize; 70 | switch (section_h->sh_type) { 71 | case SHT_DYNSYM: { 72 | if (bias == -4396) { 73 | dynsym = section_h; 74 | dynsym_offset = section_h->sh_offset; 75 | dynsym_start = offsetOf(header, dynsym_offset); 76 | } 77 | break; 78 | } 79 | case SHT_SYMTAB: { 80 | if (strcmp(sname, ".symtab") == 0) { 81 | symtab = section_h; 82 | symtab_offset = section_h->sh_offset; 83 | symtab_size = section_h->sh_size; 84 | symtab_count = symtab_size / entsize; 85 | symtab_start = offsetOf(header, symtab_offset); 86 | } 87 | break; 88 | } 89 | case SHT_STRTAB: { 90 | if (bias == -4396) { 91 | strtab = section_h; 92 | symstr_offset = section_h->sh_offset; 93 | strtab_start = offsetOf(header, symstr_offset); 94 | } 95 | if (strcmp(sname, ".strtab") == 0) { 96 | symstr_offset_for_symtab = section_h->sh_offset; 97 | } 98 | break; 99 | } 100 | case SHT_PROGBITS: { 101 | if (strtab == nullptr || dynsym == nullptr) break; 102 | if (bias == -4396) { 103 | bias = (off_t) section_h->sh_addr - (off_t) section_h->sh_offset; 104 | } 105 | break; 106 | } 107 | case SHT_HASH: { 108 | auto *d_un = offsetOf(header, section_h->sh_offset); 109 | nbucket_ = d_un[0]; 110 | bucket_ = d_un + 2; 111 | chain_ = bucket_ + nbucket_; 112 | break; 113 | } 114 | case SHT_GNU_HASH: { 115 | auto *d_buf = reinterpret_cast(((size_t) header) + section_h->sh_offset); 116 | gnu_nbucket_ = d_buf[0]; 117 | gnu_symndx_ = d_buf[1]; 118 | gnu_bloom_size_ = d_buf[2]; 119 | gnu_shift2_ = d_buf[3]; 120 | gnu_bloom_filter_ = reinterpret_cast(d_buf + 4); 121 | gnu_bucket_ = 122 | reinterpret_cast(gnu_bloom_filter_ + gnu_bloom_size_); 123 | gnu_chain_ = gnu_bucket_ + gnu_nbucket_ - gnu_symndx_; 124 | break; 125 | } 126 | } 127 | } 128 | } 129 | 130 | ElfW(Addr) ElfImg::ElfLookup(std::string_view name, uint32_t hash) const { 131 | if (nbucket_ == 0) return 0; 132 | 133 | char *strings = (char *) strtab_start; 134 | 135 | for (auto n = bucket_[hash % nbucket_]; n != 0; n = chain_[n]) { 136 | auto *sym = dynsym_start + n; 137 | if (name == strings + sym->st_name) { 138 | return sym->st_value; 139 | } 140 | } 141 | return 0; 142 | } 143 | 144 | ElfW(Addr) ElfImg::GnuLookup(std::string_view name, uint32_t hash) const { 145 | static constexpr auto bloom_mask_bits = sizeof(ElfW(Addr)) * 8; 146 | 147 | if (gnu_nbucket_ == 0 || gnu_bloom_size_ == 0) return 0; 148 | 149 | auto bloom_word = gnu_bloom_filter_[(hash / bloom_mask_bits) % gnu_bloom_size_]; 150 | uintptr_t mask = 0 | (uintptr_t) 1 << (hash % bloom_mask_bits) | 151 | (uintptr_t) 1 << ((hash >> gnu_shift2_) % bloom_mask_bits); 152 | if ((mask & bloom_word) == mask) { 153 | auto sym_index = gnu_bucket_[hash % gnu_nbucket_]; 154 | if (sym_index >= gnu_symndx_) { 155 | char *strings = (char *) strtab_start; 156 | do { 157 | auto *sym = dynsym_start + sym_index; 158 | if (((gnu_chain_[sym_index] ^ hash) >> 1) == 0 && name == strings + sym->st_name) { 159 | return sym->st_value; 160 | } 161 | } while ((gnu_chain_[sym_index++] & 1) == 0); 162 | } 163 | } 164 | return 0; 165 | } 166 | 167 | ElfW(Addr) ElfImg::LinearLookup(std::string_view name) const { 168 | if (symtabs_.empty()) { 169 | symtabs_.reserve(symtab_count); 170 | if (symtab_start != nullptr && symstr_offset_for_symtab != 0) { 171 | for (ElfW(Off) i = 0; i < symtab_count; i++) { 172 | unsigned int st_type = ELF_ST_TYPE(symtab_start[i].st_info); 173 | const char *st_name = offsetOf( 174 | header, symstr_offset_for_symtab + symtab_start[i].st_name); 175 | if ((st_type == STT_FUNC || st_type == STT_OBJECT) && symtab_start[i].st_size) { 176 | symtabs_.emplace(st_name, &symtab_start[i]); 177 | } 178 | } 179 | } 180 | } 181 | 182 | if (auto i = symtabs_.find(name); i != symtabs_.end()) { 183 | return i->second->st_value; 184 | } else { 185 | return 0; 186 | } 187 | } 188 | 189 | std::string_view ElfImg::LinearLookupByPrefix(std::string_view name) const { 190 | if (symtabs_.empty()) { 191 | symtabs_.reserve(symtab_count); 192 | if (symtab_start != nullptr && symstr_offset_for_symtab != 0) { 193 | for (ElfW(Off) i = 0; i < symtab_count; i++) { 194 | unsigned int st_type = ELF_ST_TYPE(symtab_start[i].st_info); 195 | const char *st_name = offsetOf( 196 | header, symstr_offset_for_symtab + symtab_start[i].st_name); 197 | if ((st_type == STT_FUNC || st_type == STT_OBJECT) && symtab_start[i].st_size) { 198 | symtabs_.emplace(st_name, &symtab_start[i]); 199 | } 200 | } 201 | } 202 | } 203 | 204 | auto size = name.size(); 205 | for (auto symtab : symtabs_) { 206 | if (symtab.first.size() < size) continue; 207 | 208 | if (symtab.first.substr(0, size) == name) { 209 | return symtab.first; 210 | } 211 | } 212 | 213 | return ""; 214 | } 215 | 216 | ElfImg::~ElfImg() { 217 | // open elf file local 218 | if (buffer) { 219 | free(buffer); 220 | buffer = nullptr; 221 | } 222 | // use mmap 223 | if (header) { 224 | munmap(header, size); 225 | } 226 | } 227 | 228 | ElfW(Addr) ElfImg::getSymbOffset(std::string_view name, uint32_t gnu_hash, 229 | uint32_t elf_hash) const { 230 | if (auto offset = GnuLookup(name, gnu_hash); offset > 0) { 231 | // LOGD("found %s %p in %s in dynsym by gnuhash", name.data(), reinterpret_cast(offset), elf.data()); 233 | return offset; 234 | } else if (offset = ElfLookup(name, elf_hash); offset > 0) { 235 | // LOGD("found %s %p in %s in dynsym by elfhash", name.data(), reinterpret_cast(offset), elf.data()); 237 | return offset; 238 | } else if (offset = LinearLookup(name); offset > 0) { 239 | // LOGD("found %s %p in %s in symtab by linear lookup", name.data(), reinterpret_cast(offset), elf.data()); 241 | return offset; 242 | } else { 243 | return 0; 244 | } 245 | } 246 | 247 | bool ElfImg::findModuleBase() { 248 | dl_iterate_phdr( 249 | [](struct dl_phdr_info *info, size_t size, void *data) -> int { 250 | (void) size; 251 | 252 | if ((info)->dlpi_name == nullptr) { 253 | return 0; 254 | } 255 | 256 | auto *self = reinterpret_cast(data); 257 | if (strstr(info->dlpi_name, self->elf.data())) { 258 | self->elf = info->dlpi_name; 259 | self->base = reinterpret_cast(info->dlpi_addr); 260 | return 1; 261 | } 262 | return 0; 263 | }, 264 | this); 265 | return base != 0; 266 | } 267 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/zygisk/elf_util.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of LSPosed. 3 | * 4 | * LSPosed is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * LSPosed is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with LSPosed. If not, see . 16 | * 17 | * Copyright (C) 2019 Swift Gan 18 | * Copyright (C) 2021 LSPosed Contributors 19 | */ 20 | #ifndef SANDHOOK_ELF_UTIL_H 21 | #define SANDHOOK_ELF_UTIL_H 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #define SHT_GNU_HASH 0x6ffffff6 32 | 33 | namespace SandHook { 34 | class ElfImg { 35 | public: 36 | ElfImg(std::string_view elf); 37 | 38 | constexpr ElfW(Addr) getSymbOffset(std::string_view name) const { 39 | return getSymbOffset(name, GnuHash(name), ElfHash(name)); 40 | } 41 | 42 | constexpr ElfW(Addr) getSymbAddress(std::string_view name) const { 43 | ElfW(Addr) offset = getSymbOffset(name); 44 | if (offset > 0 && base != nullptr) { 45 | return static_cast((uintptr_t) base + offset - bias); 46 | } else { 47 | return 0; 48 | } 49 | } 50 | 51 | std::string_view findSymbolNameByPrefix(std::string_view prefix) const { 52 | return LinearLookupByPrefix(prefix); 53 | } 54 | 55 | template 56 | constexpr T getSymbAddress(std::string_view name) const { 57 | return reinterpret_cast(getSymbAddress(name)); 58 | } 59 | 60 | bool isValid() const { return base != nullptr; } 61 | 62 | const std::string name() const { return elf; } 63 | 64 | ~ElfImg(); 65 | 66 | private: 67 | ElfW(Addr) getSymbOffset(std::string_view name, uint32_t gnu_hash, uint32_t elf_hash) const; 68 | 69 | ElfW(Addr) ElfLookup(std::string_view name, uint32_t hash) const; 70 | 71 | ElfW(Addr) GnuLookup(std::string_view name, uint32_t hash) const; 72 | 73 | ElfW(Addr) LinearLookup(std::string_view name) const; 74 | 75 | std::string_view LinearLookupByPrefix(std::string_view name) const; 76 | 77 | constexpr static uint32_t ElfHash(std::string_view name); 78 | 79 | constexpr static uint32_t GnuHash(std::string_view name); 80 | 81 | bool findModuleBase(); 82 | 83 | std::string elf; 84 | void *base = nullptr; 85 | char *buffer = nullptr; 86 | off_t size = 0; 87 | off_t bias = -4396; 88 | ElfW(Ehdr) *header = nullptr; 89 | ElfW(Shdr) *section_header = nullptr; 90 | ElfW(Shdr) *symtab = nullptr; 91 | ElfW(Shdr) *strtab = nullptr; 92 | ElfW(Shdr) *dynsym = nullptr; 93 | ElfW(Sym) *symtab_start = nullptr; 94 | ElfW(Sym) *dynsym_start = nullptr; 95 | ElfW(Sym) *strtab_start = nullptr; 96 | ElfW(Off) symtab_count = 0; 97 | ElfW(Off) symstr_offset = 0; 98 | ElfW(Off) symstr_offset_for_symtab = 0; 99 | ElfW(Off) symtab_offset = 0; 100 | ElfW(Off) dynsym_offset = 0; 101 | ElfW(Off) symtab_size = 0; 102 | 103 | uint32_t nbucket_{}; 104 | uint32_t *bucket_ = nullptr; 105 | uint32_t *chain_ = nullptr; 106 | 107 | uint32_t gnu_nbucket_{}; 108 | uint32_t gnu_symndx_{}; 109 | uint32_t gnu_bloom_size_; 110 | uint32_t gnu_shift2_; 111 | uintptr_t *gnu_bloom_filter_; 112 | uint32_t *gnu_bucket_; 113 | uint32_t *gnu_chain_; 114 | 115 | mutable std::unordered_map symtabs_; 116 | }; 117 | 118 | constexpr uint32_t ElfImg::ElfHash(std::string_view name) { 119 | uint32_t h = 0, g = 0; 120 | for (unsigned char p : name) { 121 | h = (h << 4) + p; 122 | g = h & 0xf0000000; 123 | h ^= g; 124 | h ^= g >> 24; 125 | } 126 | return h; 127 | } 128 | 129 | constexpr uint32_t ElfImg::GnuHash(std::string_view name) { 130 | uint32_t h = 5381; 131 | for (unsigned char p : name) { 132 | h += (h << 5) + p; 133 | } 134 | return h; 135 | } 136 | } // namespace SandHook 137 | 138 | #endif // SANDHOOK_ELF_UTIL_H 139 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/zygisk/entry.cpp: -------------------------------------------------------------------------------- 1 | #include "daemon.h" 2 | #include "logging.h" 3 | #include "zygisk.hpp" 4 | #include "module.hpp" 5 | #include 6 | #include "clean.h" 7 | using namespace std; 8 | void *self_handle = nullptr; 9 | 10 | 11 | extern "C" [[gnu::visibility("default")]] 12 | void entry(void* handle, const char* path) { 13 | self_handle = handle; 14 | zygiskComm::InitRequestorSocket(path); 15 | if (!zygiskComm::PingHeartbeat()) { 16 | LOGE("Zygisk daemon is not running"); 17 | return; 18 | } 19 | 20 | //#ifdef NDEBUG 21 | // logging::setfd(zygiskd::RequestLogcatFd()); 22 | //#endif 23 | 24 | LOGD("Start hooking"); 25 | 26 | Dl_info dl_info; 27 | dladdr((void*)hook_entry, reinterpret_cast(&dl_info)); 28 | string file_path = dl_info.dli_fname; 29 | void* so_start_addr =dl_info.dli_fbase; 30 | LOGD("hook_entry %s addr %p",file_path.c_str(),so_start_addr); 31 | size_t so_size = remove_soinfo(file_path.c_str(), 1, 0, false); 32 | LOGD("hook_entry so_size %zu ",so_size); 33 | 34 | hook_entry(so_start_addr,so_size); 35 | } 36 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/zygisk/solist.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "elf_util.hpp" 5 | 6 | namespace SoList { 7 | class SoInfo { 8 | public: 9 | #ifdef __LP64__ 10 | inline static size_t field_size_offset = 0x18; 11 | inline static size_t field_next_offset = 0x28; 12 | inline static size_t field_realpath_offset = 0x1a0; 13 | inline static size_t field_soname_offset = 0x189; 14 | #else 15 | inline static size_t field_size_offset = 0x90; 16 | inline static size_t field_next_offset = 0xa4; 17 | inline static size_t field_realpath_offset = 0x17c; 18 | inline static size_t field_soname_offset = 0x189; 19 | #endif 20 | 21 | inline static const char *(*get_realpath_sym)(SoInfo *) = nullptr; 22 | inline static void (*soinfo_free)(SoInfo *) = nullptr; 23 | inline static const char *(*get_soname)(SoInfo *) = nullptr; 24 | 25 | inline SoInfo *getNext() { 26 | return *reinterpret_cast(reinterpret_cast(this) + field_next_offset); 27 | } 28 | 29 | inline size_t getSize() { 30 | return *reinterpret_cast(reinterpret_cast(this) + field_size_offset); 31 | } 32 | 33 | inline const char *getPath() { 34 | if (get_realpath_sym) return get_realpath_sym(this); 35 | 36 | return (reinterpret_cast(reinterpret_cast(this) + 37 | field_realpath_offset)) 38 | ->c_str(); 39 | } 40 | 41 | inline const char *getSoname() { 42 | if (get_soname) return get_soname(this); 43 | 44 | return (reinterpret_cast(reinterpret_cast(this) + 45 | field_soname_offset)) 46 | ->c_str(); 47 | } 48 | 49 | void setNext(SoInfo *info) { 50 | *reinterpret_cast(reinterpret_cast(this) + field_next_offset) = info; 51 | } 52 | 53 | void setSize(size_t size) { 54 | *reinterpret_cast(reinterpret_cast(this) + field_size_offset) = size; 55 | } 56 | }; 57 | 58 | class ProtectedDataGuard { 59 | public: 60 | ProtectedDataGuard() { 61 | if (ctor != nullptr) (this->*ctor)(); 62 | } 63 | 64 | ~ProtectedDataGuard() { 65 | if (dtor != nullptr) (this->*dtor)(); 66 | } 67 | 68 | static bool setup(const SandHook::ElfImg &linker) { 69 | ctor = MemFunc{.data = {.p = reinterpret_cast( 70 | linker.getSymbAddress("__dl__ZN18ProtectedDataGuardC2Ev")), 71 | .adj = 0}} 72 | .f; 73 | dtor = MemFunc{.data = {.p = reinterpret_cast( 74 | linker.getSymbAddress("__dl__ZN18ProtectedDataGuardD2Ev")), 75 | .adj = 0}} 76 | .f; 77 | return ctor != nullptr && dtor != nullptr; 78 | } 79 | 80 | ProtectedDataGuard(const ProtectedDataGuard &) = delete; 81 | 82 | void operator=(const ProtectedDataGuard &) = delete; 83 | 84 | private: 85 | using FuncType = void (ProtectedDataGuard::*)(); 86 | 87 | inline static FuncType ctor = nullptr; 88 | inline static FuncType dtor = nullptr; 89 | 90 | union MemFunc { 91 | FuncType f; 92 | 93 | struct { 94 | void *p; 95 | std::ptrdiff_t adj; 96 | } data; 97 | }; 98 | }; 99 | 100 | static SoInfo *solist = nullptr; 101 | static SoInfo *somain = nullptr; 102 | static SoInfo **sonext = nullptr; 103 | 104 | static uint64_t *g_module_load_counter = nullptr; 105 | static uint64_t *g_module_unload_counter = nullptr; 106 | 107 | const size_t size_block_range = 1024; 108 | const size_t size_maximal = 0x100000; 109 | const size_t size_minimal = 0x100; 110 | const size_t llvm_suffix_length = 25; 111 | template 112 | inline T *getStaticPointer(const SandHook::ElfImg &linker, const char *name) { 113 | auto *addr = reinterpret_cast(linker.getSymbAddress(name)); 114 | 115 | return addr == nullptr ? nullptr : *addr; 116 | } 117 | 118 | bool initialize(); 119 | size_t dropSoPath(const char *target_path); 120 | void resetCounters(size_t load, size_t unload); 121 | 122 | } // namespace SoList 123 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/zygisk/unmount.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "files.hpp" 5 | #include "logging.h" 6 | #include "misc.hpp" 7 | #include "zygisk.hpp" 8 | 9 | using namespace std::string_view_literals; 10 | 11 | namespace { 12 | constexpr auto MODULE_DIR = "/data/adb/modules"; 13 | constexpr auto KSU_OVERLAY_SOURCE = "KSU"; 14 | const std::vector KSU_PARTITIONS{"/system", "/vendor", "/product", "/system_ext", "/odm", "/oem"}; 15 | 16 | void lazy_unmount(const char* mountpoint) { 17 | if (umount2(mountpoint, MNT_DETACH) != -1) { 18 | LOGD("Unmounted (%s)", mountpoint); 19 | } else { 20 | #ifndef NDEBUG 21 | PLOGE("Unmount (%s)", mountpoint); 22 | #endif 23 | } 24 | } 25 | } 26 | 27 | void revert_unmount_ksu() { 28 | std::string ksu_loop; 29 | std::vector targets; 30 | 31 | // Unmount ksu module dir last 32 | targets.emplace_back(MODULE_DIR); 33 | 34 | for (auto& info: parse_mount_info("self")) { 35 | if (info.target == MODULE_DIR) { 36 | ksu_loop = info.source; 37 | continue; 38 | } 39 | // Unmount everything mounted to /data/adb 40 | // if (info.target.starts_with("/data/adb")) { 41 | // targets.emplace_back(info.target); 42 | // } 43 | // Unmount ksu overlays 44 | if (info.type == "overlay" 45 | && info.source == KSU_OVERLAY_SOURCE 46 | && std::find(KSU_PARTITIONS.begin(), KSU_PARTITIONS.end(), info.target) != KSU_PARTITIONS.end()) { 47 | targets.emplace_back(info.target); 48 | } 49 | // Unmount temp dir 50 | if (info.type == "tmpfs" && info.source == KSU_OVERLAY_SOURCE) { 51 | targets.emplace_back(info.target); 52 | } 53 | } 54 | for (auto& info: parse_mount_info("self")) { 55 | // Unmount everything from ksu loop except ksu module dir 56 | if (info.source == ksu_loop && info.target != MODULE_DIR) { 57 | targets.emplace_back(info.target); 58 | } 59 | } 60 | 61 | // Do unmount 62 | for (auto& s: reversed(targets)) { 63 | lazy_unmount(s.data()); 64 | } 65 | } 66 | 67 | void revert_unmount_magisk() { 68 | std::vector targets; 69 | 70 | // Unmount dummy skeletons and MAGISKTMP 71 | // since mirror nodes are always mounted under skeleton, we don't have to specifically unmount 72 | for (auto& info: parse_mount_info("self")) { 73 | // if (info.source == "magisk" || info.source == "worker" || // magisktmp tmpfs 74 | // info.root.starts_with("/adb/modules")) { // bind mount from data partition 75 | // targets.push_back(info.target); 76 | // } 77 | // // Unmount everything mounted to /data/adb 78 | // if (info.target.starts_with("/data/adb")) { 79 | // targets.emplace_back(info.target); 80 | // } 81 | } 82 | 83 | for (auto& s: reversed(targets)) { 84 | lazy_unmount(s.data()); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/zygisk/zygisk.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | extern void *self_handle; 7 | 8 | void hook_entry(void *start_addr, size_t block_size); 9 | 10 | void hookJniNativeMethods(JNIEnv *env, const char *clz, JNINativeMethod *methods, int numMethods); 11 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/zygiskd/RootImp.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by chic on 2024/12/4. 3 | // 4 | 5 | #ifndef ZYGISKNEXT_ROOTIMP_H 6 | #define ZYGISKNEXT_ROOTIMP_H 7 | #include "sys/types.h" 8 | #include "daemon.h" 9 | 10 | 11 | 12 | enum : uint32_t { 13 | 14 | PROCESS_GRANTED_ROOT = (1u << 0), 15 | PROCESS_ON_DENYLIST = (1u << 1), 16 | 17 | PROCESS_IS_MANAGER = (1u << 27), 18 | PROCESS_ROOT_IS_APATCH = (1u << 28), 19 | PROCESS_ROOT_IS_KSU = (1u << 29), 20 | PROCESS_ROOT_IS_MAGISK = (1u << 30), 21 | IS_FIRST_PROCESS = (1u << 31), 22 | 23 | PRIVATE_MASK = (PROCESS_IS_MANAGER | PROCESS_ROOT_IS_APATCH | PROCESS_ROOT_IS_KSU | 24 | PROCESS_ROOT_IS_MAGISK | IS_FIRST_PROCESS), 25 | UNMOUNT_MASK = PROCESS_ON_DENYLIST 26 | }; 27 | 28 | struct mountinfo { 29 | unsigned int id; 30 | unsigned int parent; 31 | dev_t device; 32 | const char *root; 33 | const char *target; 34 | const char *vfs_option; 35 | struct { 36 | unsigned int shared; 37 | unsigned int master; 38 | unsigned int propagate_from; 39 | } optional; 40 | const char *type; 41 | const char *source; 42 | const char *fs_option; 43 | }; 44 | struct mountinfos { 45 | struct mountinfo *mounts; 46 | size_t length; 47 | }; 48 | 49 | class RootImp { 50 | 51 | 52 | public: 53 | int getProcessFlags(uid_t uid); 54 | static bool is_magisk_root(); 55 | static int is_command_available(const char *command); 56 | 57 | 58 | RootImp(const RootImp&)= delete; 59 | 60 | RootImp(){}; 61 | void init(){ 62 | manager_uid = get_mamager_uid(); 63 | } 64 | RootImp& operator=(const RootImp)=delete; 65 | bool uid_is_manager(uid_t uid) const; 66 | bool cache_mount_namespace(pid_t pid); 67 | 68 | virtual bool uid_should_umount(uid_t uid) = 0; 69 | int update_mount_namespace(zygiskComm::MountNamespace type) ; 70 | virtual bool uid_granted_root(uid_t uid) = 0 ; 71 | virtual int getRootFlags (uid_t uid) = 0; 72 | 73 | virtual uid_t get_mamager_uid() = 0; 74 | 75 | private: 76 | uid_t manager_uid = -1; 77 | int root_imp; 78 | int module_mnt_ns_fd = -1; 79 | int root_mnt_ns_fd = -1; 80 | int clean_mnt_ns_fd = -1; 81 | 82 | }; 83 | 84 | 85 | 86 | 87 | #endif //ZYGISKNEXT_ROOTIMP_H 88 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/zygiskd/apatch.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by chic on 2025/5/31. 3 | // 4 | 5 | #include "apatch.h" 6 | #include 7 | #include "logging.h" 8 | #include 9 | 10 | 11 | 12 | uid_t apatch::get_mamager_uid() { 13 | const char *path = "/data/user_de/0/me.bmax.apatch"; 14 | struct stat file_stat; 15 | if (stat(path, &file_stat) == 0) { 16 | // 检查文件的用户 ID 是否与指定的 uid 匹配 17 | LOGD("get_apatch_mamager_uid:%d",file_stat.st_uid); 18 | return file_stat.st_uid; 19 | } else { 20 | LOGD("stat failed"); // 打印错误信息 21 | return -1; // 文件不存在或无法访问 22 | } 23 | return -1; 24 | } 25 | 26 | bool apatch::uid_granted_root(uid_t uid) 27 | { 28 | return false; 29 | } 30 | 31 | int apatch::getRootFlags(uid_t uid) { 32 | return 0; 33 | } 34 | bool apatch::uid_should_umount(uid_t uid){ 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/zygiskd/apatch.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by chic on 2025/5/31. 3 | // 4 | 5 | #pragma once 6 | 7 | 8 | #include "RootImp.h" 9 | 10 | class apatch : public RootImp { 11 | 12 | 13 | uid_t get_mamager_uid() ; 14 | bool uid_granted_root(uid_t uid); 15 | int getRootFlags(uid_t uid); 16 | bool uid_should_umount(uid_t uid); 17 | 18 | 19 | }; 20 | 21 | 22 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/zygiskd/companion.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by chic on 2025/4/5. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include "logging.h" 19 | #include "socket_utils.h" 20 | #include 21 | #include 22 | #include "dl.h" 23 | #include 24 | #include 25 | 26 | # define LOG_TAG "zygiskCommpanion" 27 | 28 | using comp_entry = void(*)(int); 29 | 30 | typedef void (*zygisk_companion_entry)(int); 31 | 32 | struct companion_module_thread_args { 33 | int fd; 34 | zygisk_companion_entry entry; 35 | }; 36 | 37 | zygisk_companion_entry load_module(int fd) { 38 | char path[PATH_MAX]; 39 | snprintf(path, sizeof(path), "/proc/self/fd/%d", fd); 40 | 41 | void *handle = DlopenExt(path, RTLD_NOW); 42 | 43 | if (!handle) return NULL; 44 | 45 | void *entry = dlsym(handle, "zygisk_companion_entry"); 46 | if (!entry) { 47 | LOGE("Failed to dlsym zygisk_companion_entry: %s\n", dlerror()); 48 | 49 | dlclose(handle); 50 | 51 | return NULL; 52 | } 53 | 54 | return (zygisk_companion_entry)entry; 55 | } 56 | 57 | 58 | 59 | void entry_thread(int client,comp_entry entry){ 60 | struct stat s1; 61 | fstat(client, &s1); 62 | entry(client); 63 | // Only close client if it is the same file so we don't 64 | // accidentally close a re-used file descriptor. 65 | // This check is required because the module companion 66 | // handler could've closed the file descriptor already. 67 | if (struct stat s2; fstat(client, &s2) == 0) { 68 | if (s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino) { 69 | close(client); 70 | } 71 | } 72 | } 73 | 74 | 75 | /* WARNING: Dynamic memory based */ 76 | void companion_entry(int socket) { 77 | LOGW("start companion_entry:%d",getpid()); 78 | 79 | if (getuid() != 0 || fcntl(socket, F_GETFD) < 0) 80 | exit(-1); 81 | 82 | 83 | 84 | // Load modules 85 | std::vector modules; 86 | { 87 | std::vector module_fds = socket_utils::recv_fds(socket); 88 | for (int fd : module_fds) { 89 | comp_entry entry = nullptr; 90 | struct stat s{}; 91 | 92 | if (fstat(fd, &s) == 0 && S_ISREG(s.st_mode)) { 93 | android_dlextinfo info { 94 | .flags = ANDROID_DLEXT_USE_LIBRARY_FD, 95 | .library_fd = fd, 96 | }; 97 | if (void *h = android_dlopen_ext("/jit-cache", RTLD_LAZY, &info)) { 98 | *(void **) &entry = dlsym(h, "zygisk_companion_entry"); 99 | } else { 100 | LOGW("Failed to dlopen zygisk module: %s\n", dlerror()); 101 | } 102 | } 103 | modules.push_back(entry); 104 | close(fd); 105 | } 106 | } 107 | 108 | // ack 109 | socket_utils::write_u32(socket, 0); 110 | 111 | // Start accepting requests 112 | pollfd pfd = { socket, POLLIN, 0 }; 113 | for (;;) { 114 | poll(&pfd, 1, -1); 115 | if (pfd.revents && !(pfd.revents & POLLIN)) { 116 | // Something bad happened in magiskd, terminate zygiskd 117 | exit(0); 118 | } 119 | int client = socket_utils::recv_fd(socket); 120 | if (client < 0) { 121 | // Something bad happened in magiskd, terminate zygiskd 122 | exit(0); 123 | } 124 | int module_id = socket_utils::read_u32(client); 125 | 126 | if (module_id >= 0 && module_id < modules.size() && modules[module_id]) { 127 | std::thread new_thread(entry_thread,client,modules[module_id]); 128 | new_thread.detach(); // 线程分离,主线程不等待 129 | } else { 130 | close(client); 131 | } 132 | } 133 | 134 | } -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/zygiskd/companion.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by chic on 2025/4/5. 3 | // 4 | 5 | #ifndef ALINUX_DEBUG_INJECT_COMPANION_H 6 | #define ALINUX_DEBUG_INJECT_COMPANION_H 7 | 8 | void companion_entry(int fd); 9 | #endif //ALINUX_DEBUG_INJECT_COMPANION_H 10 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/zygiskd/ksu.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by chic on 2025/5/31. 3 | // 4 | 5 | #include "ksu.h" 6 | #include 7 | #include "logging.h" 8 | #include 9 | #include 10 | 11 | uid_t ksu::get_mamager_uid() { 12 | 13 | const char *path = "/data/user_de/0/me.weishu.kernelsu"; 14 | struct stat file_stat; 15 | if (stat(path, &file_stat) == 0) { 16 | // 检查文件的用户 ID 是否与指定的 uid 匹配 17 | LOGD("get_ksu_mamager_uid:%d",file_stat.st_uid); 18 | return file_stat.st_uid; 19 | } else { 20 | LOGE("stat failed"); // 打印错误信息 21 | return -1; // 文件不存在或无法访问 22 | } 23 | 24 | return -1; 25 | } 26 | 27 | bool ksu::uid_granted_root(uid_t uid) { 28 | 29 | unsigned int result = 0; 30 | bool granted = false; 31 | 32 | // 调用 prctl 33 | int ret = prctl( 34 | KERNEL_SU_OPTION, 35 | CMD_UID_GRANTED_ROOT, 36 | uid, 37 | (long)&granted, // 强制转换为 long 类型以适配 prctl 参数 38 | (long)&result // 强制转换为 long 类型以适配 prctl 参数 39 | ); 40 | 41 | if (ret < 0) { 42 | LOGE("prctl failed"); 43 | return false; 44 | } 45 | 46 | if (result != KERNEL_SU_OPTION) { 47 | LOGE("uid_granted_root failed"); 48 | return false; 49 | } 50 | 51 | return granted != 0; 52 | 53 | } 54 | 55 | int ksu::getRootFlags(uid_t uid) { 56 | 57 | 58 | int version = 0; 59 | if(prctl(KERNEL_SU_OPTION,CMD_GET_VERSION,&version,0,0)<0){ 60 | return 0; 61 | } 62 | // 判断版本号 63 | const int MAX_OLD_VERSION = MIN_KSU_VERSION - 1; 64 | if (version == 0) { 65 | return 0; 66 | } else if (version >= MIN_KSU_VERSION && version <= MAX_KSU_VERSION) { 67 | return PROCESS_ROOT_IS_KSU; 68 | } else if (version >= 1 && version <= MAX_OLD_VERSION) { 69 | return 0; 70 | } else { 71 | return 0; 72 | } 73 | } 74 | 75 | bool ksu::uid_should_umount(uid_t uid) { 76 | uint32_t result = 0; 77 | bool umount = false; 78 | prctl(KERNEL_SU_OPTION, CMD_UID_SHOULD_UMOUNT, uid, &umount, &result); 79 | 80 | if ((int)result != KERNEL_SU_OPTION) return false; 81 | 82 | return umount; 83 | } 84 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/zygiskd/ksu.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by chic on 2025/5/31. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "RootImp.h" 8 | 9 | 10 | // 常量定义 11 | #define KERNEL_SU_OPTION 0xdeadbeefu 12 | #define CMD_GET_VERSION 2 13 | 14 | // 假定的最小和最大版本号,请根据实际需要设置 15 | #define MIN_KSU_VERSION 10 16 | #define MAX_KSU_VERSION 20 17 | #define CMD_UID_GRANTED_ROOT 12 18 | #define CMD_UID_SHOULD_UMOUNT 13 19 | 20 | class ksu : public RootImp { 21 | 22 | public: 23 | uid_t get_mamager_uid() ; 24 | bool uid_granted_root(uid_t uid); 25 | int getRootFlags(uid_t uid); 26 | bool uid_should_umount(uid_t uid); 27 | }; 28 | 29 | 30 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/zygiskd/magisk.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by chic on 2025/5/31. 3 | // 4 | 5 | #include "magisk.h" 6 | #include 7 | #include "logging.h" 8 | #include 9 | #include 10 | 11 | uid_t magisk::get_mamager_uid() { 12 | 13 | const char *path = "/data/user_de/0/com.topjohnwu.magisk"; 14 | struct stat file_stat; 15 | if (stat(path, &file_stat) == 0) { 16 | // 检查文件的用户 ID 是否与指定的 uid 匹配 17 | LOGD("get_magisk_mamager_uid:%d",file_stat.st_uid); 18 | return file_stat.st_uid; 19 | } else { 20 | LOGD("stat failed"); // 打印错误信息 21 | return -1; // 文件不存在或无法访问 22 | } 23 | 24 | return -1; 25 | } 26 | 27 | bool magisk::uid_granted_root(uid_t uid){ 28 | char sqlite_cmd[256]; 29 | snprintf(sqlite_cmd, sizeof(sqlite_cmd), "select 1 from policies where uid=%d and policy=2 limit 1", uid); 30 | 31 | char *const argv[] = { "magisk", "--sqlite", sqlite_cmd, NULL }; 32 | 33 | char result[32]; 34 | // if (!exec_command(result, sizeof(result), (const char *)"", argv)) { 35 | // LOGE("Failed to execute magisk binary: %s\n", strerror(errno)); 36 | // errno = 0; 37 | // 38 | // return false; 39 | // } 40 | 41 | return result[0] != '\0'; 42 | } 43 | 44 | int magisk::getRootFlags(uid_t uid) { 45 | int version=0; 46 | 47 | if (is_command_available("magisk")) { 48 | FILE *fp = popen("magisk -V", "r"); 49 | if (fp == NULL) { 50 | return 0; // 如果打开管道失败,返回 0 51 | } 52 | char result[256]; 53 | if (fgets(result, sizeof(result), fp) != NULL) { 54 | // 如果读取到结果,表示命令存在 55 | fclose(fp); 56 | version = atoi(result); 57 | } else { 58 | // 如果没有读取到结果,表示命令不存在 59 | fclose(fp); 60 | return 0; 61 | } 62 | } else { 63 | printf("Magisk command is not available.\n"); 64 | return 0; 65 | } 66 | if(version <= 0){ 67 | return 0; 68 | } else { 69 | return PROCESS_ROOT_IS_MAGISK; 70 | 71 | } 72 | 73 | return 0; 74 | } 75 | 76 | bool magisk::uid_should_umount(uid_t uid) { 77 | 78 | return false; 79 | } 80 | -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/zygiskd/magisk.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by chic on 2025/5/31. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "RootImp.h" 8 | 9 | class magisk: public RootImp { 10 | 11 | uid_t get_mamager_uid(); 12 | bool uid_granted_root(uid_t uid); 13 | int getRootFlags(uid_t uid); 14 | bool uid_should_umount(uid_t uid); 15 | 16 | }; -------------------------------------------------------------------------------- /Zygisk/src/main/cpp/zygiskd/zygiskd.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by chic on 2024/12/4. 3 | // 4 | 5 | #ifndef ZYGISKNEXT_ZYGISKD_H 6 | #define ZYGISKNEXT_ZYGISKD_H 7 | 8 | #include 9 | #include "daemon.h" 10 | #include "RootImp.h" 11 | #include "ksu.h" 12 | 13 | #define SECURE_DIR "/data/adb" 14 | #define MODULEROOT SECURE_DIR "/modules" 15 | 16 | 17 | 18 | 19 | 20 | void zygiskd_main(const char *); 21 | 22 | 23 | class Zygiskd { 24 | 25 | public: 26 | void collect_modules(); 27 | void foreach_module(int fd,dirent *entry,int modfd); 28 | static Zygiskd& getInstance(){ 29 | static Zygiskd instance; 30 | return instance; 31 | } 32 | std::vector getModule_list(){ 33 | return module_list; 34 | } 35 | 36 | std::string getModul_by_index(int index){ 37 | return module_list.at(index).name; 38 | } 39 | void set_exec_path(std::string exec_path){ 40 | this->exec_path = exec_path; 41 | 42 | } 43 | std::string get_exec_path(){ 44 | return this->exec_path; 45 | } 46 | Zygiskd(const Zygiskd&)= delete; 47 | Zygiskd& operator=(const Zygiskd)=delete; 48 | static RootImp* getRootImp(){ 49 | return getInstance().rootImp; 50 | } 51 | void rootImpInit(); 52 | private: 53 | Zygiskd(){ 54 | } 55 | ~Zygiskd() { 56 | } 57 | std::vector module_list; 58 | std::string moduleRoot; 59 | std::string exec_path; 60 | bool running = false; 61 | RootImp* rootImp; 62 | }; 63 | 64 | 65 | 66 | 67 | 68 | 69 | #endif //ZYGISKNEXT_ZYGISKD_H 70 | -------------------------------------------------------------------------------- /Zygisk/src/main/java/com/hepta/zygisk/NativeLib.java: -------------------------------------------------------------------------------- 1 | package com.hepta.zygisk; 2 | 3 | public class NativeLib { 4 | 5 | // // Used to load the 'zygisk' library on application startup. 6 | // static { 7 | // System.loadLibrary("zygisk"); 8 | // } 9 | // 10 | // /** 11 | // * A native method that is implemented by the 'zygisk' native library, 12 | // * which is packaged with this application. 13 | // */ 14 | // public native String stringFromJNI(); 15 | } -------------------------------------------------------------------------------- /Zygisk/src/test/java/com/hepta/zygisk/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.hepta.zygisk; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.android.build.gradle.LibraryExtension 2 | import java.io.ByteArrayOutputStream 3 | 4 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 5 | plugins { 6 | alias(libs.plugins.android.library) apply false 7 | alias(libs.plugins.android.application) apply false 8 | 9 | } 10 | 11 | val androidCompileSdkVersion by extra(34) 12 | val androidBuildToolsVersion by extra("35.0.0") 13 | val androidCompileNdkVersion by extra( "27.0.12077973") 14 | val androidMinSdkVersion by extra(27) 15 | val androidTargetSdkVersion by extra(35) 16 | 17 | fun Project.configureBaseExtension() { 18 | extensions.findByType(LibraryExtension::class)?.run { 19 | namespace = "com.hepta.zygiskADI" 20 | compileSdk = 34 21 | ndkVersion = androidCompileNdkVersion 22 | buildToolsVersion = androidBuildToolsVersion 23 | 24 | defaultConfig { 25 | minSdk = androidMinSdkVersion 26 | } 27 | 28 | lint { 29 | abortOnError = true 30 | } 31 | } 32 | } 33 | 34 | subprojects { 35 | plugins.withId("com.android.library") { 36 | configureBaseExtension() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /doc/images/start.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thehepta/Android-Debug-Inject/8a5450a38e9c2a6cc97c98e717d116ad34c3b9af/doc/images/start.jpg -------------------------------------------------------------------------------- /doc/images/wx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thehepta/Android-Debug-Inject/8a5450a38e9c2a6cc97c98e717d116ad34c3b9af/doc/images/wx.jpg -------------------------------------------------------------------------------- /doc/zygisk Mount.md: -------------------------------------------------------------------------------- 1 | zygisk Mount 需要注意的问题 2 | 3 | 4 | 5 | ### root工具挂载 6 | 目前以magisk模块整体的设计来看,magisk模块,以及模块挂载路径,包括magisk模块实现的zyigisk,他们其实是一个配套的存在。 7 | 由于目前的各个root都提供了Magisk的模块功能,并且模块安装路路径跟原始maigsk模块所在的路径是一样的,这还不包括其他一个root工具自身挂载的一些特定路径。 8 | zygisk检测工具会根据挂载路径进行检测,并且会针对各个root工具的所有挂载进行检测。在这种情况下,就需要对挂载路径进行处理。 9 | magisk 的root环境,拥有自带的zygisk来处理他的挂载路径,但是别的root工具,都是使用第三方zygisk,他们没有自己配套的zygisk. 10 | 所以各个root工具的目录卸载重任就落到第三方的zygisk上了,这就导致第三方zygisk需要兼容一些root工具的目录隐藏。 11 | 12 | 13 | ### zygisk 辅助实现root获取 14 | 15 | root工具有一个授予应用root的功能,可以让应用直接调用su,然后获取root权限,对于kernelsu来说,不存在su文件或者路径的挂载问题,但是对于别的通过su文件获取root的root工具, 16 | 这需要su文件在这个授予了root权限的应用的命名空间内,才能调用,否则命名空间不可能见,是无法调用的。为什么会这样,因为需要防止应用对su文件进行检测,所以,不授权的应用会将su文件相关的挂载删除。 17 | 所以zygisk工具还需要对给予root权限的应用,不卸妆su相关的命名空间 18 | 19 | ### zygisk 模块启用 20 | 对于root工具挂载的相关模块目录,我们前面给说了,为了防止检测也会针对性的进行卸载,所以,如果一个zygisk模块在应用进程中一直启用并且不卸载,那么也需要对命名空间放行。 21 | 22 | 23 | 24 | 25 | ### 各个模块卸载命名空间的差异 26 | 27 | 有不少第三法zygisk,我也是参考别人的代码实现的,有些zygisk是在每个进程启动的进行进程的命名空间卸载,有些zygisk是在第一次zygote启动以后直接先进行全局卸载,后续启动的应用在根据是会否需要进行命名空间挂载。 -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. For more details, visit 12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Enables namespacing of each library's R class so that its R class includes only the 19 | # resources declared in the library itself and none from the library's dependencies, 20 | # thereby reducing the size of the R class for that library 21 | android.nonTransitiveRClass=true 22 | kotlin.code.style=official 23 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | agp = "8.6.0" 3 | grgit = "5.2.0" 4 | 5 | [plugins] 6 | android-application = { id = "com.android.application", version.ref = "agp" } 7 | android-library = { id = "com.android.library", version.ref = "agp" } 8 | grgit = { id = "org.ajoberstar.grgit", version.ref = "grgit" } 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thehepta/Android-Debug-Inject/8a5450a38e9c2a6cc97c98e717d116ad34c3b9af/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu May 08 16:39:18 CST 2025 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /module/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /module/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import android.databinding.tool.ext.capitalizeUS 2 | import org.ajoberstar.grgit.Grgit 3 | 4 | plugins { 5 | alias(libs.plugins.android.library) 6 | alias(libs.plugins.grgit) 7 | } 8 | android{ 9 | 10 | buildFeatures { 11 | resValues = false 12 | buildConfig = false 13 | 14 | } 15 | } 16 | var git = Grgit.open{ dir = rootProject.rootDir } // 明确指定目录 17 | 18 | 19 | val moduleId by extra("zygiskADI") 20 | val moduleName by extra("zygiskADI") 21 | val verName by extra("v0-0.2") 22 | val verCode by extra(git.log().size) 23 | val commitHash by extra(git.head().abbreviatedId) 24 | 25 | 26 | androidComponents.onVariants { variant -> 27 | val variantLowered = variant.name.lowercase() 28 | val variantCapped = variant.name.capitalizeUS() 29 | val buildTypeLowered = variant.buildType?.lowercase() 30 | val moduleDir = layout.buildDirectory.dir("outputs/module/$variantLowered") 31 | val zipFileName = "$moduleId-$verName-$verCode-$commitHash-$buildTypeLowered.zip".replace(' ', '-') 32 | val prepareModuleFilesTask = task("prepareModuleFiles$variantCapped") { 33 | 34 | dependsOn( 35 | ":ADI:externalNativeBuild$variantCapped", 36 | ":Zygisk:externalNativeBuild$variantCapped", 37 | ":ADILib:externalNativeBuild$variantCapped", 38 | ) 39 | into(moduleDir) 40 | from("$projectDir/src") { 41 | exclude("module.prop") 42 | // filter("eol" to FixCrLfFilter.CrLf.newInstance("lf")) 43 | } 44 | from("$projectDir/src") { 45 | include("module.prop") 46 | expand( 47 | "moduleId" to moduleId, 48 | "moduleName" to moduleName, 49 | "versionName" to "$verName($variantLowered)", 50 | "versionCode" to verCode 51 | ) 52 | } 53 | 54 | into("lib/arm64-v8a"){ 55 | from(project(":Zygisk").layout.buildDirectory.file("intermediates/cmake/$variantLowered/obj/arm64-v8a/libzygisk.so")) 56 | from(project(":ADILib").layout.buildDirectory.file("intermediates/cmake/$variantLowered/obj/arm64-v8a/libDrmHook.so")) 57 | } 58 | into("bin"){ 59 | from(project(":Zygisk").layout.buildDirectory.file("intermediates/cmake/$variantLowered/obj/arm64-v8a/zygiskd")) 60 | from(project(":ADI").layout.buildDirectory.file("intermediates/cmake/$variantLowered/obj/arm64-v8a/adi")) 61 | 62 | } 63 | 64 | 65 | } 66 | 67 | val zipTask = task("zip$variantCapped") { 68 | group = "module" 69 | dependsOn(prepareModuleFilesTask) 70 | archiveFileName.set(zipFileName) 71 | destinationDirectory.set(layout.buildDirectory.file("outputs/release").get().asFile) 72 | from(moduleDir) 73 | } 74 | 75 | val pushTask = task("push$variantCapped") { 76 | group = "module" 77 | dependsOn(zipTask) 78 | commandLine("adb", "push", zipTask.outputs.files.singleFile.path, "/data/local/tmp") 79 | } 80 | 81 | 82 | val installKsuTask = task("installKsu$variantCapped") { 83 | group = "module" 84 | dependsOn(pushTask) 85 | commandLine( 86 | "adb", "shell", "su", "-c", 87 | "/data/adb/ksud module install /data/local/tmp/$zipFileName" 88 | ) 89 | } 90 | 91 | val installMagiskTask = task("installMagisk$variantCapped") { 92 | group = "module" 93 | dependsOn(pushTask) 94 | commandLine( 95 | "adb", 96 | "shell", 97 | "su", 98 | "-M", 99 | "-c", 100 | "magisk --install-module /data/local/tmp/$zipFileName" 101 | ) 102 | } 103 | 104 | task("installKsuAndReboot$variantCapped") { 105 | group = "module" 106 | dependsOn(installKsuTask) 107 | commandLine("adb", "reboot") 108 | } 109 | 110 | task("installMagiskAndReboot$variantCapped") { 111 | group = "module" 112 | dependsOn(installMagiskTask) 113 | commandLine("adb", "reboot") 114 | } 115 | 116 | 117 | } -------------------------------------------------------------------------------- /module/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /module/src/META-INF/com/google/android/update-binary: -------------------------------------------------------------------------------- 1 | #!/sbin/sh 2 | 3 | ################# 4 | # Initialization 5 | ################# 6 | 7 | umask 022 8 | 9 | # echo before loading util_functions 10 | ui_print() { echo "$1"; } 11 | 12 | require_new_magisk() { 13 | ui_print "*******************************" 14 | ui_print " Please install Magisk v19.0+! " 15 | ui_print "*******************************" 16 | exit 1 17 | } 18 | 19 | ######################### 20 | # Load util_functions.sh 21 | ######################### 22 | 23 | OUTFD=$2 24 | ZIPFILE=$3 25 | 26 | mount /data 2>/dev/null 27 | 28 | [ -f /data/adb/magisk/util_functions.sh ] || require_new_magisk 29 | . /data/adb/magisk/util_functions.sh 30 | [ $MAGISK_VER_CODE -lt 19000 ] && require_new_magisk 31 | 32 | if [ $MAGISK_VER_CODE -ge 20400 ]; then 33 | # New Magisk have complete installation logic within util_functions.sh 34 | install_module 35 | exit 0 36 | fi 37 | 38 | ################# 39 | # Legacy Support 40 | ################# 41 | 42 | TMPDIR=/dev/tmp 43 | PERSISTDIR=/sbin/.magisk/mirror/persist 44 | 45 | is_legacy_script() { 46 | unzip -l "$ZIPFILE" install.sh | grep -q install.sh 47 | return $? 48 | } 49 | 50 | print_modname() { 51 | local len 52 | len=`echo -n $MODNAME | wc -c` 53 | len=$((len + 2)) 54 | local pounds=`printf "%${len}s" | tr ' ' '*'` 55 | ui_print "$pounds" 56 | ui_print " $MODNAME " 57 | ui_print "$pounds" 58 | ui_print "*******************" 59 | ui_print " Powered by Magisk " 60 | ui_print "*******************" 61 | } 62 | 63 | # Override abort as old scripts have some issues 64 | abort() { 65 | ui_print "$1" 66 | $BOOTMODE || recovery_cleanup 67 | [ -n $MODPATH ] && rm -rf $MODPATH 68 | rm -rf $TMPDIR 69 | exit 1 70 | } 71 | 72 | rm -rf $TMPDIR 2>/dev/null 73 | mkdir -p $TMPDIR 74 | 75 | # Preperation for flashable zips 76 | setup_flashable 77 | 78 | # Mount partitions 79 | mount_partitions 80 | 81 | # Detect version and architecture 82 | api_level_arch_detect 83 | 84 | # Setup busybox and binaries 85 | $BOOTMODE && boot_actions || recovery_actions 86 | 87 | ############## 88 | # Preparation 89 | ############## 90 | 91 | # Extract prop file 92 | unzip -o "$ZIPFILE" module.prop -d $TMPDIR >&2 93 | [ ! -f $TMPDIR/module.prop ] && abort "! Unable to extract zip file!" 94 | 95 | $BOOTMODE && MODDIRNAME=modules_update || MODDIRNAME=modules 96 | MODULEROOT=$NVBASE/$MODDIRNAME 97 | MODID=`grep_prop id $TMPDIR/module.prop` 98 | MODPATH=$MODULEROOT/$MODID 99 | MODNAME=`grep_prop name $TMPDIR/module.prop` 100 | 101 | # Create mod paths 102 | rm -rf $MODPATH 2>/dev/null 103 | mkdir -p $MODPATH 104 | 105 | ########## 106 | # Install 107 | ########## 108 | 109 | if is_legacy_script; then 110 | unzip -oj "$ZIPFILE" module.prop install.sh uninstall.sh 'common/*' -d $TMPDIR >&2 111 | 112 | # Load install script 113 | . $TMPDIR/install.sh 114 | 115 | # Callbacks 116 | print_modname 117 | on_install 118 | 119 | # Custom uninstaller 120 | [ -f $TMPDIR/uninstall.sh ] && cp -af $TMPDIR/uninstall.sh $MODPATH/uninstall.sh 121 | 122 | # Skip mount 123 | $SKIPMOUNT && touch $MODPATH/skip_mount 124 | 125 | # prop file 126 | $PROPFILE && cp -af $TMPDIR/system.prop $MODPATH/system.prop 127 | 128 | # Module info 129 | cp -af $TMPDIR/module.prop $MODPATH/module.prop 130 | 131 | # post-fs-data scripts 132 | $POSTFSDATA && cp -af $TMPDIR/post-fs-data.sh $MODPATH/post-fs-data.sh 133 | 134 | # service scripts 135 | $LATESTARTSERVICE && cp -af $TMPDIR/service.sh $MODPATH/service.sh 136 | 137 | ui_print "- Setting permissions" 138 | set_permissions 139 | else 140 | print_modname 141 | 142 | unzip -o "$ZIPFILE" customize.sh -d $MODPATH >&2 143 | 144 | if ! grep -q '^SKIPUNZIP=1$' $MODPATH/customize.sh 2>/dev/null; then 145 | ui_print "- Extracting module files" 146 | unzip -o "$ZIPFILE" -x 'META-INF/*' -d $MODPATH >&2 147 | 148 | # Default permissions 149 | set_perm_recursive $MODPATH 0 0 0755 0644 150 | fi 151 | 152 | # Load customization script 153 | [ -f $MODPATH/customize.sh ] && . $MODPATH/customize.sh 154 | fi 155 | 156 | # Handle replace folders 157 | for TARGET in $REPLACE; do 158 | ui_print "- Replace target: $TARGET" 159 | mktouch $MODPATH$TARGET/.replace 160 | done 161 | 162 | if $BOOTMODE; then 163 | # Update info for Magisk Manager 164 | mktouch $NVBASE/modules/$MODID/update 165 | cp -af $MODPATH/module.prop $NVBASE/modules/$MODID/module.prop 166 | fi 167 | 168 | # Copy over custom sepolicy rules 169 | if [ -f $MODPATH/sepolicy.rule -a -e $PERSISTDIR ]; then 170 | ui_print "- Installing custom sepolicy patch" 171 | PERSISTMOD=$PERSISTDIR/magisk/$MODID 172 | mkdir -p $PERSISTMOD 173 | cp -af $MODPATH/sepolicy.rule $PERSISTMOD/sepolicy.rule 174 | fi 175 | 176 | # Remove stuffs that don't belong to modules 177 | rm -rf \ 178 | $MODPATH/system/placeholder $MODPATH/customize.sh \ 179 | $MODPATH/README.md $MODPATH/.git* 2>/dev/null 180 | 181 | ############# 182 | # Finalizing 183 | ############# 184 | 185 | cd / 186 | $BOOTMODE || recovery_cleanup 187 | rm -rf $TMPDIR 188 | 189 | ui_print "- Done" 190 | exit 0 -------------------------------------------------------------------------------- /module/src/META-INF/com/google/android/updater-script: -------------------------------------------------------------------------------- 1 | #MAGISK 2 | -------------------------------------------------------------------------------- /module/src/module.prop: -------------------------------------------------------------------------------- 1 | id=${moduleId} 2 | name=${moduleName} 3 | version=${versionName} 4 | versionCode=${versionCode} 5 | author=Thehepta 6 | description= Android Linux Debug Inject Zygisk 7 | -------------------------------------------------------------------------------- /module/src/post-fs-data.sh: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | 3 | 4 | MODDIR=${0%/*} 5 | if [ "$ZYGISK_ENABLED" ]; then 6 | exit 0 7 | fi 8 | 9 | cd "$MODDIR" 10 | 11 | if [ "$(which magisk)" ]; then 12 | for file in ../*; do 13 | if [ -d "$file" ] && [ -d "$file/zygisk" ] && ! [ -f "$file/disable" ]; then 14 | if [ -f "$file/post-fs-data.sh" ]; then 15 | cd "$file" 16 | log -p i -t "zygisk-sh" "Manually trigger post-fs-data.sh for $file" 17 | sh "$(realpath ./post-fs-data.sh)" 18 | cd "$MODDIR" 19 | fi 20 | fi 21 | done 22 | fi 23 | 24 | cd "$MODDIR" || exit 25 | 26 | chmod +x $MODDIR/bin -R 27 | 28 | 29 | 30 | create_sys_perm() { 31 | mkdir -p $1 32 | chmod 555 $1 33 | chcon u:object_r:system_file:s0 $1 34 | } 35 | 36 | export TMP_PATH=/apex/com.android.syzuel 37 | create_sys_perm $TMP_PATH 38 | mount -t tmpfs tmpfs $TMP_PATH 39 | 40 | if [ -f $MODDIR/lib/arm64-v8a/libzygisk.so ];then 41 | create_sys_perm $TMP_PATH/lib64 42 | cp $MODDIR/lib/arm64-v8a/libzygisk.so $TMP_PATH/lib64/libsyzuel.so 43 | chcon u:object_r:system_file:s0 $TMP_PATH/lib64/libsyzuel.so 44 | fi 45 | 46 | 47 | #export LIB_ZYGISK_SO_PATH=$TMP_PATH/lib64/libsyzuel.so 48 | export LIB_ZYGISK_SO_PATH=/data/adb/modules/zygiskADI/lib/arm64-v8a/libzygisk.so 49 | 50 | # must usr & 51 | ./bin/zygiskd unix_socket d63138f231 & 52 | #./bin/adi -m -c $MODDIR/zygisk3.json & 53 | ./bin/adi -m -p 1 --exec /system/bin/app_process64 --injectSoPath $LIB_ZYGISK_SO_PATH --injectFunSym entry --injectFunArg d63138f231 --monitorCount 10& 54 | 55 | -------------------------------------------------------------------------------- /module/src/sepolicy.rule: -------------------------------------------------------------------------------- 1 | type zygisk_file file_type 2 | typeattribute zygisk_file mlstrustedobject 3 | allow zygote zygisk_file sock_file {read write} 4 | 5 | allow zygote magisk lnk_file read 6 | allow zygote unlabeled file {read open} 7 | allow zygote zygote capability sys_chroot 8 | allow zygote su dir search 9 | allow zygote su {lnk_file file} read 10 | 11 | allow zygote adb_data_file dir search 12 | allow zygote zygote process execmem 13 | allow system_server system_server process execmem 14 | allow zygote tmpfs file * 15 | allow zygote appdomain_tmpfs file * 16 | allow zygote su unix_stream_socket * 17 | -------------------------------------------------------------------------------- /module/src/service.sh: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | 3 | DEBUG=@DEBUG@ 4 | 5 | MODDIR=${0%/*} 6 | if [ "$ZYGISK_ENABLED" ]; then 7 | exit 0 8 | fi 9 | 10 | cd "$MODDIR" 11 | 12 | if [ "$(which magisk)" ]; then 13 | for file in ../*; do 14 | if [ -d "$file" ] && [ -d "$file/zygisk" ] && ! [ -f "$file/disable" ]; then 15 | if [ -f "$file/service.sh" ]; then 16 | cd "$file" 17 | log -p i -t "zygisk-sh" "Manually trigger service.sh for $file" 18 | sh "$(realpath ./service.sh)" & 19 | cd "$MODDIR" 20 | fi 21 | fi 22 | done 23 | fi -------------------------------------------------------------------------------- /module/src/verify.sh: -------------------------------------------------------------------------------- 1 | TMPDIR_FOR_VERIFY="$TMPDIR/.vunzip" 2 | mkdir "$TMPDIR_FOR_VERIFY" 3 | 4 | abort_verify() { 5 | ui_print "*********************************************************" 6 | ui_print "! $1" 7 | ui_print "! This zip may be corrupted, please try downloading again" 8 | abort "*********************************************************" 9 | } 10 | 11 | # extract 12 | extract() { 13 | zip=$1 14 | file=$2 15 | dir=$3 16 | junk_paths=$4 17 | [ -z "$junk_paths" ] && junk_paths=false 18 | opts="-o" 19 | [ $junk_paths = true ] && opts="-oj" 20 | 21 | file_path="" 22 | hash_path="" 23 | if [ $junk_paths = true ]; then 24 | file_path="$dir/$(basename "$file")" 25 | hash_path="$TMPDIR_FOR_VERIFY/$(basename "$file").sha256" 26 | else 27 | file_path="$dir/$file" 28 | hash_path="$TMPDIR_FOR_VERIFY/$file.sha256" 29 | fi 30 | 31 | unzip $opts "$zip" "$file" -d "$dir" >&2 32 | [ -f "$file_path" ] || abort_verify "$file not exists" 33 | 34 | unzip $opts "$zip" "$file.sha256" -d "$TMPDIR_FOR_VERIFY" >&2 35 | [ -f "$hash_path" ] || abort_verify "$file.sha256 not exists" 36 | 37 | (echo "$(cat "$hash_path") $file_path" | sha256sum -c -s -) || abort_verify "Failed to verify $file" 38 | ui_print "- Verified $file" >&1 39 | } 40 | 41 | file="META-INF/com/google/android/update-binary" 42 | file_path="$TMPDIR_FOR_VERIFY/$file" 43 | hash_path="$file_path.sha256" 44 | unzip -o "$ZIPFILE" "META-INF/com/google/android/*" -d "$TMPDIR_FOR_VERIFY" >&2 45 | [ -f "$file_path" ] || abort_verify "$file not exists" 46 | if [ -f "$hash_path" ]; then 47 | (echo "$(cat "$hash_path") $file_path" | sha256sum -c -s -) || abort_verify "Failed to verify $file" 48 | ui_print "- Verified $file" >&1 49 | else 50 | ui_print "- Download from Magisk app" 51 | fi 52 | -------------------------------------------------------------------------------- /module/src/zygisk.json: -------------------------------------------------------------------------------- 1 | { 2 | "traced_pid": 1, 3 | "persistence": true, 4 | "childProcess": [ 5 | { 6 | "exec": "/system/bin/app_process64", 7 | "waitSoPath": "/system/lib64/libwebviewchromium_loader.so", 8 | "waitFunSym": "", 9 | "InjectSO": "/data/adb/modules/zygiskADI/lib/arm64-v8a/libzygisk.so", 10 | "InjectFunSym": "entry", 11 | "InjectFunArg": "d63138f231", 12 | "monitorCount": 10 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /module/src/zygisk3.json: -------------------------------------------------------------------------------- 1 | { 2 | "traced_pid": 1, 3 | "persistence": true, 4 | "childProcess": [ 5 | { 6 | "exec": "/system/bin/app_process64", 7 | "waitSoPath": "", 8 | "waitFunSym": "", 9 | "InjectSO": "/data/adb/modules/zygiskADI/lib/arm64-v8a/libzygisk.so", 10 | "InjectFunSym": "entry", 11 | "InjectFunArg": "d63138f231", 12 | "monitorCount": 10 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /module/src/zygisk5.json: -------------------------------------------------------------------------------- 1 | { 2 | "traced_pid": 1, 3 | "persistence": true, 4 | "childProcess": [ 5 | { 6 | "exec": "/system/bin/app_process64", 7 | "waitSoPath": "/apex/com.android.art/lib64/libart.so", 8 | "waitFunSym": "_ZN3artL25ZygoteHooks_nativePreForkEP7_JNIEnvP7_jclass", 9 | "InjectSO": "/data/adb/modules/zygiskADI/lib/arm64-v8a/libzygisk.so", 10 | "InjectFunSym": "entry", 11 | "InjectFunArg": "d63138f231", 12 | "monitorCount": 10 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google { 4 | content { 5 | includeGroupByRegex("com\\.android.*") 6 | includeGroupByRegex("com\\.google.*") 7 | includeGroupByRegex("androidx.*") 8 | } 9 | } 10 | mavenCentral() 11 | gradlePluginPortal() 12 | } 13 | } 14 | dependencyResolutionManagement { 15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 16 | repositories { 17 | google() 18 | mavenCentral() 19 | } 20 | } 21 | 22 | rootProject.name = "Android-Debug-Inject" 23 | include(":ADI") 24 | include(":module") 25 | include(":Zygisk") 26 | include(":ADILib") 27 | --------------------------------------------------------------------------------