├── .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 | 
96 |
97 | 
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