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

4
.gitattributes vendored Normal file
View File

@@ -0,0 +1,4 @@
* text=auto eol=lf
*.bat text eol=crlf
*.jar binary

27
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: Build
on:
workflow_dispatch:
inputs:
package_name:
description: "Package name of the game:"
required: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 11
cache: gradle
- run: |
chmod +x ./gradlew
sed -i 's/moduleDescription = "/moduleDescription = "(${{ github.event.inputs.package_name }}) /g' module.gradle
sed -i "s/com.game.packagename/${{ github.event.inputs.package_name }}/g" module/src/main/cpp/game.h
./gradlew :module:assembleRelease
- uses: actions/upload-artifact@v3
with:
name: zygisk-il2cppdumper
path: out/magisk_module_release/

14
.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
*.iml
.gradle
/local.properties
.idea
/.idea/caches/build_file_checksums.ser
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
.DS_Store
/build
/captures
/out
.externalNativeBuild
.cxx

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Rikka
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

21
README.md Normal file
View File

@@ -0,0 +1,21 @@
# Zygisk-Il2CppDumper
Il2CppDumper with Zygisk, dump il2cpp data at runtime, can bypass protection, encryption and obfuscation.
中文说明请戳[这里](README.zh-CN.md)
## How to use
1. Install [Magisk](https://github.com/topjohnwu/Magisk) v24 or later and enable Zygisk
2. Build module
- GitHub Actions
1. Fork this repo
2. Go to the **Actions** tab in your forked repo
3. In the left sidebar, click the **Build** workflow.
4. Above the list of workflow runs, select **Run workflow**
5. Input the game package name and click **Run workflow**
6. Wait for the action to complete and download the artifact
- Android Studio
1. Download the source code
2. Edit `game.h`, modify `AimPackageName` to the game package name
3. Use Android Studio to run the gradle task `:module:assembleRelease` to compile, the zip package will be generated in the `out` folder
3. Install module in Magisk
4. Start the game, `dump.cs` will be generated in the `/data/data/AimPackageName/files/` directory

19
README.zh-CN.md Normal file
View File

@@ -0,0 +1,19 @@
# Zygisk-Il2CppDumper
Zygisk版Il2CppDumper在游戏运行时dump il2cpp数据可以绕过保护加密以及混淆。
## 如何食用
1. 安装[Magisk](https://github.com/topjohnwu/Magisk) v24以上版本并开启Zygisk
2. 生成模块
- GitHub Actions
1. Fork这个项目
2. 在你fork的项目中选择**Actions**选项卡
3. 在左边的侧边栏中,单击**Build**
4. 选择**Run workflow**
5. 输入游戏包名并点击**Run workflow**
6. 等待操作完成并下载
- Android Studio
1. 下载源码
2. 编辑`game.h`, 修改`AimPackageName`为游戏包名
3. 使用Android Studio运行gradle任务`:module:assembleRelease`编译zip包会生成在`out`文件夹下
3. 在Magisk里安装模块
4. 启动游戏,会在`/data/data/AimPackageName/files/`目录下生成`dump.cs`

35
build.gradle Normal file
View File

@@ -0,0 +1,35 @@
apply plugin: 'idea'
idea.module {
excludeDirs += file('out')
resourceDirs += file('template')
resourceDirs += file('scripts')
}
buildscript {
repositories {
mavenCentral()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
}
}
allprojects {
repositories {
mavenCentral()
google()
}
}
ext {
minSdkVersion = 23
targetSdkVersion = 32
outDir = file("$rootDir/out")
}
task clean(type: Delete) {
delete rootProject.buildDir, outDir
}

19
gradle.properties Normal file
View File

@@ -0,0 +1,19 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true

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

Binary file not shown.

View File

@@ -0,0 +1,6 @@
#Mon May 22 11:22:38 CST 2023
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

172
gradlew vendored Normal file
View File

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

84
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

9
module.gradle Normal file
View File

@@ -0,0 +1,9 @@
ext {
moduleLibraryName = "myinjector"
magiskModuleId = "zygisk_myinjector"
moduleName = "myinjector"
moduleAuthor = "jiqiu2021"
moduleDescription = "注入任意SO到指定APP内"
moduleVersion = "v0.01"
moduleVersionCode = 1
}

3
module/.gitignore vendored Normal file
View File

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

107
module/build.gradle Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

@@ -0,0 +1,92 @@
// Copyright (c) 2020-2021 HexHacking Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Created by caikelun on 2020-10-04.
//
// xDL version: 1.2.1
//
// xDL is an enhanced implementation of the Android DL series functions.
// For more information, documentation, and the latest version please check:
// https://github.com/hexhacking/xDL
//
#ifndef IO_HEXHACKING_XDL
#define IO_HEXHACKING_XDL
#include <dlfcn.h>
#include <link.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
// same as Dl_info:
const char *dli_fname; // Pathname of shared object that contains address.
void *dli_fbase; // Address at which shared object is loaded.
const char *dli_sname; // Name of nearest symbol with address lower than addr.
void *dli_saddr; // Exact address of symbol named in dli_sname.
// added by xDL:
size_t dli_ssize; // Symbol size of nearest symbol with address lower than addr.
const ElfW(Phdr) *dlpi_phdr; // Pointer to array of ELF program headers for this object.
size_t dlpi_phnum; // Number of items in dlpi_phdr.
} xdl_info_t;
//
// Default value for flags in both xdl_open() and xdl_iterate_phdr().
//
#define XDL_DEFAULT 0x00
//
// Enhanced dlopen() / dlclose() / dlsym().
//
#define XDL_TRY_FORCE_LOAD 0x01
#define XDL_ALWAYS_FORCE_LOAD 0x02
void *xdl_open(const char *filename, int flags);
void *xdl_close(void *handle);
void *xdl_sym(void *handle, const char *symbol, size_t *symbol_size);
void *xdl_dsym(void *handle, const char *symbol, size_t *symbol_size);
//
// Enhanced dladdr().
//
int xdl_addr(void *addr, xdl_info_t *info, void **cache);
void xdl_addr_clean(void **cache);
//
// Enhanced dl_iterate_phdr().
//
#define XDL_FULL_PATHNAME 0x01
int xdl_iterate_phdr(int (*callback)(struct dl_phdr_info *, size_t, void *), void *data, int flags);
//
// Custom dlinfo().
//
#define XDL_DI_DLINFO 1 // type of info: xdl_info_t
int xdl_info(void *handle, int request, void *info);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,869 @@
// Copyright (c) 2020-2021 HexHacking Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Created by caikelun on 2020-10-04.
#include "xdl.h"
#include <android/api-level.h>
#include <elf.h>
#include <fcntl.h>
#include <inttypes.h>
#include <link.h>
#include <pthread.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/auxv.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "xdl_iterate.h"
#include "xdl_linker.h"
#include "xdl_lzma.h"
#include "xdl_util.h"
#ifndef __LP64__
#define XDL_LIB_PATH "/system/lib"
#else
#define XDL_LIB_PATH "/system/lib64"
#endif
#define XDL_DYNSYM_IS_EXPORT_SYM(shndx) (SHN_UNDEF != (shndx))
#define XDL_SYMTAB_IS_EXPORT_SYM(shndx) \
(SHN_UNDEF != (shndx) && !((shndx) >= SHN_LORESERVE && (shndx) <= SHN_HIRESERVE))
extern __attribute((weak)) unsigned long int getauxval(unsigned long int);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpadded"
typedef struct xdl {
char *pathname;
uintptr_t load_bias;
const ElfW(Phdr) *dlpi_phdr;
ElfW(Half) dlpi_phnum;
struct xdl *next; // to next xdl obj for cache in xdl_addr()
void *linker_handle; // hold handle returned by xdl_linker_load()
//
// (1) for searching symbols from .dynsym
//
bool dynsym_try_load;
ElfW(Sym) *dynsym; // .dynsym
const char *dynstr; // .dynstr
// .hash (SYSV hash for .dynstr)
struct {
const uint32_t *buckets;
uint32_t buckets_cnt;
const uint32_t *chains;
uint32_t chains_cnt;
} sysv_hash;
// .gnu.hash (GNU hash for .dynstr)
struct {
const uint32_t *buckets;
uint32_t buckets_cnt;
const uint32_t *chains;
uint32_t symoffset;
const ElfW(Addr) *bloom;
uint32_t bloom_cnt;
uint32_t bloom_shift;
} gnu_hash;
//
// (2) for searching symbols from .symtab
//
bool symtab_try_load;
uintptr_t base;
ElfW(Sym) *symtab; // .symtab
size_t symtab_cnt;
char *strtab; // .strtab
size_t strtab_sz;
} xdl_t;
#pragma clang diagnostic pop
// load from memory
static int xdl_dynsym_load(xdl_t *self) {
// find the dynamic segment
ElfW(Dyn) *dynamic = NULL;
for (size_t i = 0; i < self->dlpi_phnum; i++) {
const ElfW(Phdr) *phdr = &(self->dlpi_phdr[i]);
if (PT_DYNAMIC == phdr->p_type) {
dynamic = (ElfW(Dyn) *)(self->load_bias + phdr->p_vaddr);
break;
}
}
if (NULL == dynamic) return -1;
// iterate the dynamic segment
for (ElfW(Dyn) *entry = dynamic; entry && entry->d_tag != DT_NULL; entry++) {
switch (entry->d_tag) {
case DT_SYMTAB: //.dynsym
self->dynsym = (ElfW(Sym) *)(self->load_bias + entry->d_un.d_ptr);
break;
case DT_STRTAB: //.dynstr
self->dynstr = (const char *)(self->load_bias + entry->d_un.d_ptr);
break;
case DT_HASH: //.hash
self->sysv_hash.buckets_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[0];
self->sysv_hash.chains_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[1];
self->sysv_hash.buckets = &(((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[2]);
self->sysv_hash.chains = &(self->sysv_hash.buckets[self->sysv_hash.buckets_cnt]);
break;
case DT_GNU_HASH: //.gnu.hash
self->gnu_hash.buckets_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[0];
self->gnu_hash.symoffset = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[1];
self->gnu_hash.bloom_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[2];
self->gnu_hash.bloom_shift = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[3];
self->gnu_hash.bloom = (const ElfW(Addr) *)(self->load_bias + entry->d_un.d_ptr + 16);
self->gnu_hash.buckets = (const uint32_t *)(&(self->gnu_hash.bloom[self->gnu_hash.bloom_cnt]));
self->gnu_hash.chains = (const uint32_t *)(&(self->gnu_hash.buckets[self->gnu_hash.buckets_cnt]));
break;
default:
break;
}
}
if (NULL == self->dynsym || NULL == self->dynstr ||
(0 == self->sysv_hash.buckets_cnt && 0 == self->gnu_hash.buckets_cnt)) {
self->dynsym = NULL;
self->dynstr = NULL;
self->sysv_hash.buckets_cnt = 0;
self->gnu_hash.buckets_cnt = 0;
return -1;
}
return 0;
}
static void *xdl_read_file_to_heap(int file_fd, size_t file_sz, size_t data_offset, size_t data_len) {
if (0 == data_len) return NULL;
if (data_offset >= file_sz) return NULL;
if (data_offset + data_len > file_sz) return NULL;
if (data_offset != (size_t)lseek(file_fd, (off_t)data_offset, SEEK_SET)) return NULL;
void *data = malloc(data_len);
if (NULL == data) return NULL;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu-statement-expression"
if ((ssize_t)data_len != XDL_UTIL_TEMP_FAILURE_RETRY(read(file_fd, data, data_len)))
#pragma clang diagnostic pop
{
free(data);
return NULL;
}
return data;
}
static void *xdl_read_file_to_heap_by_section(int file_fd, size_t file_sz, ElfW(Shdr) *shdr) {
return xdl_read_file_to_heap(file_fd, file_sz, (size_t)shdr->sh_offset, shdr->sh_size);
}
static void *xdl_read_memory_to_heap(void *mem, size_t mem_sz, size_t data_offset, size_t data_len) {
if (0 == data_len) return NULL;
if (data_offset >= mem_sz) return NULL;
if (data_offset + data_len > mem_sz) return NULL;
void *data = malloc(data_len);
if (NULL == data) return NULL;
memcpy(data, (void *)((uintptr_t)mem + data_offset), data_len);
return data;
}
static void *xdl_read_memory_to_heap_by_section(void *mem, size_t mem_sz, ElfW(Shdr) *shdr) {
return xdl_read_memory_to_heap(mem, mem_sz, (size_t)shdr->sh_offset, shdr->sh_size);
}
static void *xdl_get_memory(void *mem, size_t mem_sz, size_t data_offset, size_t data_len) {
if (0 == data_len) return NULL;
if (data_offset >= mem_sz) return NULL;
if (data_offset + data_len > mem_sz) return NULL;
return (void *)((uintptr_t)mem + data_offset);
}
static void *xdl_get_memory_by_section(void *mem, size_t mem_sz, ElfW(Shdr) *shdr) {
return xdl_get_memory(mem, mem_sz, (size_t)shdr->sh_offset, shdr->sh_size);
}
// load from disk and memory
static int xdl_symtab_load_from_debugdata(xdl_t *self, int file_fd, size_t file_sz,
ElfW(Shdr) *shdr_debugdata) {
void *debugdata = NULL;
ElfW(Shdr) *shdrs = NULL;
int r = -1;
// get zipped .gnu_debugdata
uint8_t *debugdata_zip = (uint8_t *)xdl_read_file_to_heap_by_section(file_fd, file_sz, shdr_debugdata);
if (NULL == debugdata_zip) return -1;
// get unzipped .gnu_debugdata
size_t debugdata_sz;
if (0 != xdl_lzma_decompress(debugdata_zip, shdr_debugdata->sh_size, (uint8_t **)&debugdata, &debugdata_sz))
goto end;
// get ELF header
ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)debugdata;
if (0 == ehdr->e_shnum || ehdr->e_shentsize != sizeof(ElfW(Shdr))) goto end;
// get section headers
shdrs = (ElfW(Shdr) *)xdl_read_memory_to_heap(debugdata, debugdata_sz, (size_t)ehdr->e_shoff,
ehdr->e_shentsize * ehdr->e_shnum);
if (NULL == shdrs) goto end;
// get .shstrtab
if (SHN_UNDEF == ehdr->e_shstrndx || ehdr->e_shstrndx >= ehdr->e_shnum) goto end;
char *shstrtab = (char *)xdl_get_memory_by_section(debugdata, debugdata_sz, shdrs + ehdr->e_shstrndx);
if (NULL == shstrtab) goto end;
// find .symtab & .strtab
for (ElfW(Shdr) *shdr = shdrs; shdr < shdrs + ehdr->e_shnum; shdr++) {
char *shdr_name = shstrtab + shdr->sh_name;
if (SHT_SYMTAB == shdr->sh_type && 0 == strcmp(".symtab", shdr_name)) {
// get & check associated .strtab section
if (shdr->sh_link >= ehdr->e_shnum) continue;
ElfW(Shdr) *shdr_strtab = shdrs + shdr->sh_link;
if (SHT_STRTAB != shdr_strtab->sh_type) continue;
// get .symtab & .strtab
ElfW(Sym) *symtab = (ElfW(Sym) *)xdl_read_memory_to_heap_by_section(debugdata, debugdata_sz, shdr);
if (NULL == symtab) continue;
char *strtab = (char *)xdl_read_memory_to_heap_by_section(debugdata, debugdata_sz, shdr_strtab);
if (NULL == strtab) {
free(symtab);
continue;
}
// OK
self->symtab = symtab;
self->symtab_cnt = shdr->sh_size / shdr->sh_entsize;
self->strtab = strtab;
self->strtab_sz = shdr_strtab->sh_size;
r = 0;
break;
}
}
end:
free(debugdata_zip);
if (NULL != debugdata) free(debugdata);
if (NULL != shdrs) free(shdrs);
return r;
}
// load from disk and memory
static int xdl_symtab_load(xdl_t *self) {
if ('[' == self->pathname[0]) return -1;
int r = -1;
ElfW(Shdr) *shdrs = NULL;
char *shstrtab = NULL;
// get base address
uintptr_t vaddr_min = UINTPTR_MAX;
for (size_t i = 0; i < self->dlpi_phnum; i++) {
const ElfW(Phdr) *phdr = &(self->dlpi_phdr[i]);
if (PT_LOAD == phdr->p_type) {
if (vaddr_min > phdr->p_vaddr) vaddr_min = phdr->p_vaddr;
}
}
if (UINTPTR_MAX == vaddr_min) return -1;
self->base = self->load_bias + vaddr_min;
// open file
int flags = O_RDONLY | O_CLOEXEC;
int file_fd;
if ('/' == self->pathname[0]) {
file_fd = open(self->pathname, flags);
} else {
char full_pathname[1024];
// try the fast method
snprintf(full_pathname, sizeof(full_pathname), "%s/%s", XDL_LIB_PATH, self->pathname);
file_fd = open(full_pathname, flags);
if (file_fd < 0) {
// try the slow method
if (0 != xdl_iterate_get_full_pathname(self->base, full_pathname, sizeof(full_pathname))) return -1;
file_fd = open(full_pathname, flags);
}
}
if (file_fd < 0) return -1;
struct stat st;
if (0 != fstat(file_fd, &st)) goto end;
size_t file_sz = (size_t)st.st_size;
// get ELF header
ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)self->base;
if (0 == ehdr->e_shnum || ehdr->e_shentsize != sizeof(ElfW(Shdr))) goto end;
// get section headers
shdrs = (ElfW(Shdr) *)xdl_read_file_to_heap(file_fd, file_sz, (size_t)ehdr->e_shoff,
ehdr->e_shentsize * ehdr->e_shnum);
if (NULL == shdrs) goto end;
// get .shstrtab
if (SHN_UNDEF == ehdr->e_shstrndx || ehdr->e_shstrndx >= ehdr->e_shnum) goto end;
shstrtab = (char *)xdl_read_file_to_heap_by_section(file_fd, file_sz, shdrs + ehdr->e_shstrndx);
if (NULL == shstrtab) goto end;
// find .symtab & .strtab
for (ElfW(Shdr) *shdr = shdrs; shdr < shdrs + ehdr->e_shnum; shdr++) {
char *shdr_name = shstrtab + shdr->sh_name;
if (SHT_SYMTAB == shdr->sh_type && 0 == strcmp(".symtab", shdr_name)) {
// get & check associated .strtab section
if (shdr->sh_link >= ehdr->e_shnum) continue;
ElfW(Shdr) *shdr_strtab = shdrs + shdr->sh_link;
if (SHT_STRTAB != shdr_strtab->sh_type) continue;
// get .symtab & .strtab
ElfW(Sym) *symtab = (ElfW(Sym) *)xdl_read_file_to_heap_by_section(file_fd, file_sz, shdr);
if (NULL == symtab) continue;
char *strtab = (char *)xdl_read_file_to_heap_by_section(file_fd, file_sz, shdr_strtab);
if (NULL == strtab) {
free(symtab);
continue;
}
// OK
self->symtab = symtab;
self->symtab_cnt = shdr->sh_size / shdr->sh_entsize;
self->strtab = strtab;
self->strtab_sz = shdr_strtab->sh_size;
r = 0;
break;
} else if (SHT_PROGBITS == shdr->sh_type && 0 == strcmp(".gnu_debugdata", shdr_name)) {
if (0 == xdl_symtab_load_from_debugdata(self, file_fd, file_sz, shdr)) {
// OK
r = 0;
break;
}
}
}
end:
close(file_fd);
if (NULL != shdrs) free(shdrs);
if (NULL != shstrtab) free(shstrtab);
return r;
}
static xdl_t *xdl_find_from_auxv(unsigned long type, const char *pathname) {
if (NULL == getauxval) return NULL;
uintptr_t val = (uintptr_t)getauxval(type);
if (0 == val) return NULL;
// get base
uintptr_t base = (AT_PHDR == type ? (val & (~0xffful)) : val);
if (0 != memcmp((void *)base, ELFMAG, SELFMAG)) return NULL;
// ELF info
ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)base;
const ElfW(Phdr) *dlpi_phdr = (const ElfW(Phdr) *)(base + ehdr->e_phoff);
ElfW(Half) dlpi_phnum = ehdr->e_phnum;
// get bias
uintptr_t min_vaddr = UINTPTR_MAX;
for (size_t i = 0; i < dlpi_phnum; i++) {
const ElfW(Phdr) *phdr = &(dlpi_phdr[i]);
if (PT_LOAD == phdr->p_type) {
if (min_vaddr > phdr->p_vaddr) min_vaddr = phdr->p_vaddr;
}
}
if (UINTPTR_MAX == min_vaddr || base < min_vaddr) return NULL;
uintptr_t load_bias = base - min_vaddr;
// create xDL object
xdl_t *self;
if (NULL == (self = calloc(1, sizeof(xdl_t)))) return NULL;
if (NULL == (self->pathname = strdup(pathname))) {
free(self);
return NULL;
}
self->load_bias = load_bias;
self->dlpi_phdr = dlpi_phdr;
self->dlpi_phnum = dlpi_phnum;
self->dynsym_try_load = false;
self->symtab_try_load = false;
return self;
}
static int xdl_find_iterate_cb(struct dl_phdr_info *info, size_t size, void *arg) {
(void)size;
uintptr_t *pkg = (uintptr_t *)arg;
xdl_t **self = (xdl_t **)*pkg++;
const char *filename = (const char *)*pkg;
// check load_bias
if (0 == info->dlpi_addr || NULL == info->dlpi_name) return 0;
// check pathname
if ('[' == filename[0]) {
if (0 != strcmp(info->dlpi_name, filename)) return 0;
} else if ('/' == filename[0]) {
if ('/' == info->dlpi_name[0]) {
if (0 != strcmp(info->dlpi_name, filename)) return 0;
} else {
if (!xdl_util_ends_with(filename, info->dlpi_name)) return 0;
}
} else {
if ('/' == info->dlpi_name[0]) {
if (!xdl_util_ends_with(info->dlpi_name, filename)) return 0;
} else {
if (0 != strcmp(info->dlpi_name, filename)) return 0;
}
}
// found the target ELF
if (NULL == ((*self) = calloc(1, sizeof(xdl_t)))) return 1; // return failed
if (NULL == ((*self)->pathname = strdup(info->dlpi_name))) {
free(*self);
*self = NULL;
return 1; // return failed
}
(*self)->load_bias = info->dlpi_addr;
(*self)->dlpi_phdr = info->dlpi_phdr;
(*self)->dlpi_phnum = info->dlpi_phnum;
(*self)->dynsym_try_load = false;
(*self)->symtab_try_load = false;
return 1; // return OK
}
static xdl_t *xdl_find(const char *filename) {
// from auxv (linker, vDSO)
xdl_t *self = NULL;
if (xdl_util_ends_with(filename, XDL_UTIL_LINKER_BASENAME))
self = xdl_find_from_auxv(AT_BASE, XDL_UTIL_LINKER_PATHNAME);
else if (xdl_util_ends_with(filename, XDL_UTIL_VDSO_BASENAME))
self = xdl_find_from_auxv(AT_SYSINFO_EHDR, XDL_UTIL_VDSO_BASENAME);
// from auxv (app_process)
const char *basename, *pathname;
#if (defined(__arm__) || defined(__i386__)) && __ANDROID_API__ < __ANDROID_API_L__
if (xdl_util_get_api_level() < __ANDROID_API_L__) {
basename = XDL_UTIL_APP_PROCESS_BASENAME_K;
pathname = XDL_UTIL_APP_PROCESS_PATHNAME_K;
} else
#endif
{
basename = XDL_UTIL_APP_PROCESS_BASENAME;
pathname = XDL_UTIL_APP_PROCESS_PATHNAME;
}
if (xdl_util_ends_with(filename, basename)) self = xdl_find_from_auxv(AT_PHDR, pathname);
if (NULL != self) return self;
// from dl_iterate_phdr
uintptr_t pkg[2] = {(uintptr_t)&self, (uintptr_t)filename};
xdl_iterate_phdr(xdl_find_iterate_cb, pkg, XDL_DEFAULT);
return self;
}
static void *xdl_open_always_force(const char *filename) {
// always force dlopen()
void *linker_handle = xdl_linker_load(filename);
if (NULL == linker_handle) return NULL;
// find
xdl_t *self = xdl_find(filename);
if (NULL == self)
dlclose(linker_handle);
else
self->linker_handle = linker_handle;
return (void *)self;
}
static void *xdl_open_try_force(const char *filename) {
// find
xdl_t *self = xdl_find(filename);
if (NULL != self) return (void *)self;
// try force dlopen()
void *linker_handle = xdl_linker_load(filename);
if (NULL == linker_handle) return NULL;
// find again
self = xdl_find(filename);
if (NULL == self)
dlclose(linker_handle);
else
self->linker_handle = linker_handle;
return (void *)self;
}
void *xdl_open(const char *filename, int flags) {
if (NULL == filename) return NULL;
if (flags & XDL_ALWAYS_FORCE_LOAD)
return xdl_open_always_force(filename);
else if (flags & XDL_TRY_FORCE_LOAD)
return xdl_open_try_force(filename);
else
return xdl_find(filename);
}
void *xdl_close(void *handle) {
if (NULL == handle) return NULL;
xdl_t *self = (xdl_t *)handle;
if (NULL != self->pathname) free(self->pathname);
if (NULL != self->symtab) free(self->symtab);
if (NULL != self->strtab) free(self->strtab);
void *linker_handle = self->linker_handle;
free(self);
return linker_handle;
}
static uint32_t xdl_sysv_hash(const uint8_t *name) {
uint32_t h = 0, g;
while (*name) {
h = (h << 4) + *name++;
g = h & 0xf0000000;
h ^= g;
h ^= g >> 24;
}
return h;
}
static uint32_t xdl_gnu_hash(const uint8_t *name) {
uint32_t h = 5381;
while (*name) {
h += (h << 5) + *name++;
}
return h;
}
static ElfW(Sym) *xdl_dynsym_find_symbol_use_sysv_hash(xdl_t *self, const char *sym_name) {
uint32_t hash = xdl_sysv_hash((const uint8_t *)sym_name);
for (uint32_t i = self->sysv_hash.buckets[hash % self->sysv_hash.buckets_cnt]; 0 != i;
i = self->sysv_hash.chains[i]) {
ElfW(Sym) *sym = self->dynsym + i;
if (0 != strcmp(self->dynstr + sym->st_name, sym_name)) continue;
return sym;
}
return NULL;
}
static ElfW(Sym) *xdl_dynsym_find_symbol_use_gnu_hash(xdl_t *self, const char *sym_name) {
uint32_t hash = xdl_gnu_hash((const uint8_t *)sym_name);
static uint32_t elfclass_bits = sizeof(ElfW(Addr)) * 8;
size_t word = self->gnu_hash.bloom[(hash / elfclass_bits) % self->gnu_hash.bloom_cnt];
size_t mask = 0 | (size_t)1 << (hash % elfclass_bits) |
(size_t)1 << ((hash >> self->gnu_hash.bloom_shift) % elfclass_bits);
// if at least one bit is not set, this symbol is surely missing
if ((word & mask) != mask) return NULL;
// ignore STN_UNDEF
uint32_t i = self->gnu_hash.buckets[hash % self->gnu_hash.buckets_cnt];
if (i < self->gnu_hash.symoffset) return NULL;
// loop through the chain
while (1) {
ElfW(Sym) *sym = self->dynsym + i;
uint32_t sym_hash = self->gnu_hash.chains[i - self->gnu_hash.symoffset];
if ((hash | (uint32_t)1) == (sym_hash | (uint32_t)1)) {
if (0 == strcmp(self->dynstr + sym->st_name, sym_name)) {
return sym;
}
}
// chain ends with an element with the lowest bit set to 1
if (sym_hash & (uint32_t)1) break;
i++;
}
return NULL;
}
void *xdl_sym(void *handle, const char *symbol, size_t *symbol_size) {
if (NULL == handle || NULL == symbol) return NULL;
if (NULL != symbol_size) *symbol_size = 0;
xdl_t *self = (xdl_t *)handle;
// load .dynsym only once
if (!self->dynsym_try_load) {
self->dynsym_try_load = true;
if (0 != xdl_dynsym_load(self)) return NULL;
}
// find symbol
if (NULL == self->dynsym) return NULL;
ElfW(Sym) *sym = NULL;
if (self->gnu_hash.buckets_cnt > 0) {
// use GNU hash (.gnu.hash -> .dynsym -> .dynstr), O(x) + O(1) + O(1)
sym = xdl_dynsym_find_symbol_use_gnu_hash(self, symbol);
}
if (NULL == sym && self->sysv_hash.buckets_cnt > 0) {
// use SYSV hash (.hash -> .dynsym -> .dynstr), O(x) + O(1) + O(1)
sym = xdl_dynsym_find_symbol_use_sysv_hash(self, symbol);
}
if (NULL == sym || !XDL_DYNSYM_IS_EXPORT_SYM(sym->st_shndx)) return NULL;
if (NULL != symbol_size) *symbol_size = sym->st_size;
return (void *)(self->load_bias + sym->st_value);
}
void *xdl_dsym(void *handle, const char *symbol, size_t *symbol_size) {
if (NULL == handle || NULL == symbol) return NULL;
if (NULL != symbol_size) *symbol_size = 0;
xdl_t *self = (xdl_t *)handle;
// load .symtab only once
if (!self->symtab_try_load) {
self->symtab_try_load = true;
if (0 != xdl_symtab_load(self)) return NULL;
}
// find symbol
if (NULL == self->symtab) return NULL;
for (size_t i = 0; i < self->symtab_cnt; i++) {
ElfW(Sym) *sym = self->symtab + i;
if (!XDL_SYMTAB_IS_EXPORT_SYM(sym->st_shndx)) continue;
if (0 != strncmp(self->strtab + sym->st_name, symbol, self->strtab_sz - sym->st_name)) continue;
if (NULL != symbol_size) *symbol_size = sym->st_size;
return (void *)(self->load_bias + sym->st_value);
}
return NULL;
}
static bool xdl_elf_is_match(uintptr_t load_bias, const ElfW(Phdr) *dlpi_phdr, ElfW(Half) dlpi_phnum,
uintptr_t addr) {
if (addr < load_bias) return false;
uintptr_t vaddr = addr - load_bias;
for (size_t i = 0; i < dlpi_phnum; i++) {
const ElfW(Phdr) *phdr = &(dlpi_phdr[i]);
if (PT_LOAD != phdr->p_type) continue;
if (phdr->p_vaddr <= vaddr && vaddr < phdr->p_vaddr + phdr->p_memsz) return true;
}
return false;
}
static int xdl_open_by_addr_iterate_cb(struct dl_phdr_info *info, size_t size, void *arg) {
(void)size;
uintptr_t *pkg = (uintptr_t *)arg;
xdl_t **self = (xdl_t **)*pkg++;
uintptr_t addr = *pkg;
if (xdl_elf_is_match(info->dlpi_addr, info->dlpi_phdr, info->dlpi_phnum, addr)) {
// found the target ELF
if (NULL == ((*self) = calloc(1, sizeof(xdl_t)))) return 1; // failed
if (NULL == ((*self)->pathname = strdup(info->dlpi_name))) {
free(*self);
*self = NULL;
return 1; // failed
}
(*self)->load_bias = info->dlpi_addr;
(*self)->dlpi_phdr = info->dlpi_phdr;
(*self)->dlpi_phnum = info->dlpi_phnum;
(*self)->dynsym_try_load = false;
(*self)->symtab_try_load = false;
return 1; // OK
}
return 0; // mismatch
}
static void *xdl_open_by_addr(void *addr) {
if (NULL == addr) return NULL;
xdl_t *self = NULL;
uintptr_t pkg[2] = {(uintptr_t)&self, (uintptr_t)addr};
xdl_iterate_phdr(xdl_open_by_addr_iterate_cb, pkg, XDL_DEFAULT);
return (void *)self;
}
static bool xdl_sym_is_match(ElfW(Sym) *sym, uintptr_t offset, bool is_symtab) {
if (is_symtab) {
if (!XDL_SYMTAB_IS_EXPORT_SYM(sym->st_shndx)) false;
} else {
if (!XDL_DYNSYM_IS_EXPORT_SYM(sym->st_shndx)) false;
}
return ELF_ST_TYPE(sym->st_info) != STT_TLS && offset >= sym->st_value &&
offset < sym->st_value + sym->st_size;
}
static ElfW(Sym) *xdl_sym_by_addr(void *handle, void *addr) {
xdl_t *self = (xdl_t *)handle;
// load .dynsym only once
if (!self->dynsym_try_load) {
self->dynsym_try_load = true;
if (0 != xdl_dynsym_load(self)) return NULL;
}
// find symbol
if (NULL == self->dynsym) return NULL;
uintptr_t offset = (uintptr_t)addr - self->load_bias;
if (self->gnu_hash.buckets_cnt > 0) {
const uint32_t *chains_all = self->gnu_hash.chains - self->gnu_hash.symoffset;
for (size_t i = 0; i < self->gnu_hash.buckets_cnt; i++) {
uint32_t n = self->gnu_hash.buckets[i];
if (n < self->gnu_hash.symoffset) continue;
do {
ElfW(Sym) *sym = self->dynsym + n;
if (xdl_sym_is_match(sym, offset, false)) return sym;
} while ((chains_all[n++] & 1) == 0);
}
} else if (self->sysv_hash.chains_cnt > 0) {
for (size_t i = 0; i < self->sysv_hash.chains_cnt; i++) {
ElfW(Sym) *sym = self->dynsym + i;
if (xdl_sym_is_match(sym, offset, false)) return sym;
}
}
return NULL;
}
static ElfW(Sym) *xdl_dsym_by_addr(void *handle, void *addr) {
xdl_t *self = (xdl_t *)handle;
// load .symtab only once
if (!self->symtab_try_load) {
self->symtab_try_load = true;
if (0 != xdl_symtab_load(self)) return NULL;
}
// find symbol
if (NULL == self->symtab) return NULL;
uintptr_t offset = (uintptr_t)addr - self->load_bias;
for (size_t i = 0; i < self->symtab_cnt; i++) {
ElfW(Sym) *sym = self->symtab + i;
if (xdl_sym_is_match(sym, offset, true)) return sym;
}
return NULL;
}
int xdl_addr(void *addr, xdl_info_t *info, void **cache) {
if (NULL == addr || NULL == info || NULL == cache) return 0;
memset(info, 0, sizeof(Dl_info));
// find handle from cache
xdl_t *handle = NULL;
for (handle = *((xdl_t **)cache); NULL != handle; handle = handle->next)
if (xdl_elf_is_match(handle->load_bias, handle->dlpi_phdr, handle->dlpi_phnum, (uintptr_t)addr)) break;
// create new handle, save handle to cache
if (NULL == handle) {
handle = (xdl_t *)xdl_open_by_addr(addr);
if (NULL == handle) return 0;
handle->next = *(xdl_t **)cache;
*(xdl_t **)cache = handle;
}
// we have at least: load_bias, pathname, dlpi_phdr, dlpi_phnum
info->dli_fbase = (void *)handle->load_bias;
info->dli_fname = handle->pathname;
info->dli_sname = NULL;
info->dli_saddr = 0;
info->dli_ssize = 0;
info->dlpi_phdr = handle->dlpi_phdr;
info->dlpi_phnum = (size_t)handle->dlpi_phnum;
// keep looking for: symbol name, symbol offset, symbol size
ElfW(Sym) *sym;
if (NULL != (sym = xdl_sym_by_addr((void *)handle, addr))) {
info->dli_sname = handle->dynstr + sym->st_name;
info->dli_saddr = (void *)(handle->load_bias + sym->st_value);
info->dli_ssize = sym->st_size;
} else if (NULL != (sym = xdl_dsym_by_addr((void *)handle, addr))) {
info->dli_sname = handle->strtab + sym->st_name;
info->dli_saddr = (void *)(handle->load_bias + sym->st_value);
info->dli_ssize = sym->st_size;
}
return 1;
}
void xdl_addr_clean(void **cache) {
if (NULL == cache) return;
xdl_t *handle = *((xdl_t **)cache);
while (NULL != handle) {
xdl_t *tmp = handle;
handle = handle->next;
xdl_close(tmp);
}
*cache = NULL;
}
int xdl_iterate_phdr(int (*callback)(struct dl_phdr_info *, size_t, void *), void *data, int flags) {
if (NULL == callback) return 0;
return xdl_iterate_phdr_impl(callback, data, flags);
}
int xdl_info(void *handle, int request, void *info) {
if (NULL == handle || XDL_DI_DLINFO != request || NULL == info) return -1;
xdl_t *self = (xdl_t *)handle;
xdl_info_t *dlinfo = (xdl_info_t *)info;
dlinfo->dli_fbase = (void *)self->load_bias;
dlinfo->dli_fname = self->pathname;
dlinfo->dli_sname = NULL;
dlinfo->dli_saddr = 0;
dlinfo->dli_ssize = 0;
dlinfo->dlpi_phdr = self->dlpi_phdr;
dlinfo->dlpi_phnum = (size_t)self->dlpi_phnum;
return 0;
}

View File

@@ -0,0 +1,297 @@
// Copyright (c) 2020-2021 HexHacking Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Created by caikelun on 2020-10-04.
#include "xdl_iterate.h"
#include <android/api-level.h>
#include <ctype.h>
#include <dlfcn.h>
#include <elf.h>
#include <inttypes.h>
#include <link.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/auxv.h>
#include "xdl.h"
#include "xdl_linker.h"
#include "xdl_util.h"
/*
* =========================================================================================================
* API-LEVEL ANDROID-VERSION SOLUTION
* =========================================================================================================
* 16 4.1 /proc/self/maps
* 17 4.2 /proc/self/maps
* 18 4.3 /proc/self/maps
* 19 4.4 /proc/self/maps
* 20 4.4W /proc/self/maps
* ---------------------------------------------------------------------------------------------------------
* 21 5.0 dl_iterate_phdr() + __dl__ZL10g_dl_mutex + linker/linker64 from getauxval(3)
* 22 5.1 dl_iterate_phdr() + __dl__ZL10g_dl_mutex + linker/linker64 from getauxval(3)
* ---------------------------------------------------------------------------------------------------------
* 23 >= 6.0 dl_iterate_phdr() + linker/linker64 from getauxval(3)
* =========================================================================================================
*/
extern __attribute((weak)) int dl_iterate_phdr(int (*)(struct dl_phdr_info *, size_t, void *), void *);
extern __attribute((weak)) unsigned long int getauxval(unsigned long int);
static uintptr_t xdl_iterate_get_min_vaddr(struct dl_phdr_info *info) {
uintptr_t min_vaddr = UINTPTR_MAX;
for (size_t i = 0; i < info->dlpi_phnum; i++) {
const ElfW(Phdr) *phdr = &(info->dlpi_phdr[i]);
if (PT_LOAD == phdr->p_type) {
if (min_vaddr > phdr->p_vaddr) min_vaddr = phdr->p_vaddr;
}
}
return min_vaddr;
}
static int xdl_iterate_open_or_rewind_maps(FILE **maps) {
if (NULL == *maps) {
*maps = fopen("/proc/self/maps", "r");
if (NULL == *maps) return -1;
} else
rewind(*maps);
return 0;
}
static int xdl_iterate_get_pathname_from_maps(uintptr_t base, char *buf, size_t buf_len, FILE **maps) {
// open or rewind maps-file
if (0 != xdl_iterate_open_or_rewind_maps(maps)) return -1; // failed
char line[1024];
while (fgets(line, sizeof(line), *maps)) {
// check base address
uintptr_t start, end;
if (2 != sscanf(line, "%" SCNxPTR "-%" SCNxPTR " r", &start, &end)) continue;
if (base < start) break; // failed
if (base >= end) continue;
// get pathname
char *pathname = strchr(line, '/');
if (NULL == pathname) break; // failed
xdl_util_trim_ending(pathname);
// found it
strlcpy(buf, pathname, buf_len);
return 0; // OK
}
return -1; // failed
}
static int xdl_iterate_by_linker_cb(struct dl_phdr_info *info, size_t size, void *arg) {
uintptr_t *pkg = (uintptr_t *)arg;
xdl_iterate_phdr_cb_t cb = (xdl_iterate_phdr_cb_t)*pkg++;
void *cb_arg = (void *)*pkg++;
FILE **maps = (FILE **)*pkg++;
uintptr_t linker_load_bias = *pkg++;
int flags = (int)*pkg;
// ignore invalid ELF
if (0 == info->dlpi_addr || NULL == info->dlpi_name || '\0' == info->dlpi_name[0]) return 0;
// ignore linker if we have returned it already
if (linker_load_bias == info->dlpi_addr) return 0;
struct dl_phdr_info info_fixed;
info_fixed.dlpi_addr = info->dlpi_addr;
info_fixed.dlpi_name = info->dlpi_name;
info_fixed.dlpi_phdr = info->dlpi_phdr;
info_fixed.dlpi_phnum = info->dlpi_phnum;
info = &info_fixed;
// fix dlpi_phdr & dlpi_phnum (from memory)
if (NULL == info->dlpi_phdr || 0 == info->dlpi_phnum) {
ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)info->dlpi_addr;
info->dlpi_phdr = (ElfW(Phdr) *)(info->dlpi_addr + ehdr->e_phoff);
info->dlpi_phnum = ehdr->e_phnum;
}
// fix dlpi_name (from /proc/self/maps)
if ('/' != info->dlpi_name[0] && '[' != info->dlpi_name[0] && (0 != (flags & XDL_FULL_PATHNAME))) {
// get base address
uintptr_t min_vaddr = xdl_iterate_get_min_vaddr(info);
if (UINTPTR_MAX == min_vaddr) return 0; // ignore this ELF
uintptr_t base = (uintptr_t)(info->dlpi_addr + min_vaddr);
char buf[1024];
if (0 != xdl_iterate_get_pathname_from_maps(base, buf, sizeof(buf), maps)) return 0; // ignore this ELF
info->dlpi_name = (const char *)buf;
}
// callback
return cb(info, size, cb_arg);
}
static uintptr_t xdl_iterate_get_linker_base(void) {
if (NULL == getauxval) return 0;
uintptr_t base = (uintptr_t)getauxval(AT_BASE);
if (0 == base) return 0;
if (0 != memcmp((void *)base, ELFMAG, SELFMAG)) return 0;
return base;
}
static int xdl_iterate_do_callback(xdl_iterate_phdr_cb_t cb, void *cb_arg, uintptr_t base,
const char *pathname, uintptr_t *load_bias) {
ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)base;
struct dl_phdr_info info;
info.dlpi_name = pathname;
info.dlpi_phdr = (const ElfW(Phdr) *)(base + ehdr->e_phoff);
info.dlpi_phnum = ehdr->e_phnum;
// get load bias
uintptr_t min_vaddr = xdl_iterate_get_min_vaddr(&info);
if (UINTPTR_MAX == min_vaddr) return 0; // ignore invalid ELF
info.dlpi_addr = (ElfW(Addr))(base - min_vaddr);
if (NULL != load_bias) *load_bias = info.dlpi_addr;
return cb(&info, sizeof(struct dl_phdr_info), cb_arg);
}
static int xdl_iterate_by_linker(xdl_iterate_phdr_cb_t cb, void *cb_arg, int flags) {
if (NULL == dl_iterate_phdr) return 0;
int api_level = xdl_util_get_api_level();
FILE *maps = NULL;
int r;
// dl_iterate_phdr(3) does NOT contain linker/linker64 when Android version < 8.1 (API level 27).
// Here we always try to get linker base address from auxv.
uintptr_t linker_load_bias = 0;
uintptr_t linker_base = xdl_iterate_get_linker_base();
if (0 != linker_base) {
if (0 !=
(r = xdl_iterate_do_callback(cb, cb_arg, linker_base, XDL_UTIL_LINKER_PATHNAME, &linker_load_bias)))
return r;
}
// for other ELF
uintptr_t pkg[5] = {(uintptr_t)cb, (uintptr_t)cb_arg, (uintptr_t)&maps, linker_load_bias, (uintptr_t)flags};
if (__ANDROID_API_L__ == api_level || __ANDROID_API_L_MR1__ == api_level) xdl_linker_lock();
r = dl_iterate_phdr(xdl_iterate_by_linker_cb, pkg);
if (__ANDROID_API_L__ == api_level || __ANDROID_API_L_MR1__ == api_level) xdl_linker_unlock();
if (NULL != maps) fclose(maps);
return r;
}
#if (defined(__arm__) || defined(__i386__)) && __ANDROID_API__ < __ANDROID_API_L__
static int xdl_iterate_by_maps(xdl_iterate_phdr_cb_t cb, void *cb_arg) {
FILE *maps = fopen("/proc/self/maps", "r");
if (NULL == maps) return 0;
int r = 0;
char buf1[1024], buf2[1024];
char *line = buf1;
uintptr_t prev_base = 0;
bool try_next_line = false;
while (fgets(line, sizeof(buf1), maps)) {
// Try to find an ELF which loaded by linker.
uintptr_t base, offset;
char exec;
if (3 != sscanf(line, "%" SCNxPTR "-%*" SCNxPTR " r%*c%cp %" SCNxPTR " ", &base, &exec, &offset)) goto clean;
if ('-' == exec && 0 == offset) {
// r--p
prev_base = base;
line = (line == buf1 ? buf2 : buf1);
try_next_line = true;
continue;
}
else if (exec == 'x') {
// r-xp
char *pathname = NULL;
if (try_next_line && 0 != offset) {
char *prev = (line == buf1 ? buf2 : buf1);
char *prev_pathname = strchr(prev, '/');
if (NULL == prev_pathname) goto clean;
pathname = strchr(line, '/');
if (NULL == pathname) goto clean;
xdl_util_trim_ending(prev_pathname);
xdl_util_trim_ending(pathname);
if (0 != strcmp(prev_pathname, pathname)) goto clean;
// we found the line with r-xp in the next line
base = prev_base;
offset = 0;
}
if (0 != offset) goto clean;
// get pathname
if (NULL == pathname) {
pathname = strchr(line, '/');
if (NULL == pathname) goto clean;
xdl_util_trim_ending(pathname);
}
if (0 != memcmp((void *)base, ELFMAG, SELFMAG)) goto clean;
ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)base;
struct dl_phdr_info info;
info.dlpi_name = pathname;
info.dlpi_phdr = (const ElfW(Phdr) *)(base + ehdr->e_phoff);
info.dlpi_phnum = ehdr->e_phnum;
// callback
if (0 != (r = xdl_iterate_do_callback(cb, cb_arg, base, pathname, NULL))) break;
}
clean:
try_next_line = false;
}
fclose(maps);
return r;
}
#endif
int xdl_iterate_phdr_impl(xdl_iterate_phdr_cb_t cb, void *cb_arg, int flags) {
// iterate by /proc/self/maps in Android 4.x (Android 4.x only supports arm32 and x86)
#if (defined(__arm__) || defined(__i386__)) && __ANDROID_API__ < __ANDROID_API_L__
if (xdl_util_get_api_level() < __ANDROID_API_L__) return xdl_iterate_by_maps(cb, cb_arg);
#endif
// iterate by dl_iterate_phdr()
return xdl_iterate_by_linker(cb, cb_arg, flags);
}
int xdl_iterate_get_full_pathname(uintptr_t base, char *buf, size_t buf_len) {
FILE *maps = NULL;
int r = xdl_iterate_get_pathname_from_maps(base, buf, buf_len, &maps);
if (NULL != maps) fclose(maps);
return r;
}

View File

@@ -0,0 +1,43 @@
// Copyright (c) 2020-2021 HexHacking Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Created by caikelun on 2020-10-04.
#ifndef IO_HEXHACKING_XDL_ITERATE
#define IO_HEXHACKING_XDL_ITERATE
#include <link.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef int (*xdl_iterate_phdr_cb_t)(struct dl_phdr_info *info, size_t size, void *arg);
int xdl_iterate_phdr_impl(xdl_iterate_phdr_cb_t cb, void *cb_arg, int flags);
int xdl_iterate_get_full_pathname(uintptr_t base, char *buf, size_t buf_len);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,187 @@
// Copyright (c) 2020-2021 HexHacking Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Created by caikelun on 2021-02-21.
#include "xdl_linker.h"
#include <dlfcn.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include "xdl.h"
#include "xdl_iterate.h"
#include "xdl_util.h"
#define XDL_LINKER_SYM_MUTEX "__dl__ZL10g_dl_mutex"
#define XDL_LINKER_SYM_DLOPEN_EXT_N "__dl__ZL10dlopen_extPKciPK17android_dlextinfoPv"
#define XDL_LINKER_SYM_DO_DLOPEN_N "__dl__Z9do_dlopenPKciPK17android_dlextinfoPv"
#define XDL_LINKER_SYM_DLOPEN_O "__dl__Z8__dlopenPKciPKv"
#define XDL_LINKER_SYM_LOADER_DLOPEN_P "__loader_dlopen"
typedef void *(*xdl_linker_dlopen_n_t)(const char *, int, const void *, void *);
typedef void *(*xdl_linker_dlopen_o_t)(const char *, int, const void *);
static pthread_mutex_t *xdl_linker_mutex = NULL;
static void *xdl_linker_dlopen = NULL;
static void *xdl_linker_caller_addr[] = {
NULL, // default
NULL, // art
NULL // vendor
};
#ifndef __LP64__
#define XDL_LINKER_LIB "lib"
#else
#define XDL_LINKER_LIB "lib64"
#endif
static const char *xdl_linker_vendor_path[] = {
// order is important
"/vendor/" XDL_LINKER_LIB "/egl/", "/vendor/" XDL_LINKER_LIB "/hw/",
"/vendor/" XDL_LINKER_LIB "/", "/odm/" XDL_LINKER_LIB "/",
"/vendor/" XDL_LINKER_LIB "/vndk-sp/", "/odm/" XDL_LINKER_LIB "/vndk-sp/"};
static void xdl_linker_init(void) {
static bool inited = false;
if (inited) return;
inited = true;
void *handle = xdl_open(XDL_UTIL_LINKER_BASENAME, XDL_DEFAULT);
if (NULL == handle) return;
int api_level = xdl_util_get_api_level();
if (__ANDROID_API_L__ == api_level || __ANDROID_API_L_MR1__ == api_level) {
// == Android 5.x
xdl_linker_mutex = (pthread_mutex_t *)xdl_dsym(handle, XDL_LINKER_SYM_MUTEX, NULL);
} else if (__ANDROID_API_N__ == api_level || __ANDROID_API_N_MR1__ == api_level) {
// == Android 7.x
xdl_linker_dlopen = xdl_dsym(handle, XDL_LINKER_SYM_DLOPEN_EXT_N, NULL);
if (NULL == xdl_linker_dlopen) {
xdl_linker_dlopen = xdl_dsym(handle, XDL_LINKER_SYM_DO_DLOPEN_N, NULL);
xdl_linker_mutex = (pthread_mutex_t *)xdl_dsym(handle, XDL_LINKER_SYM_MUTEX, NULL);
}
} else if (__ANDROID_API_O__ == api_level || __ANDROID_API_O_MR1__ == api_level) {
// == Android 8.x
xdl_linker_dlopen = xdl_dsym(handle, XDL_LINKER_SYM_DLOPEN_O, NULL);
} else if (api_level >= __ANDROID_API_P__) {
// >= Android 9.0
xdl_linker_dlopen = xdl_sym(handle, XDL_LINKER_SYM_LOADER_DLOPEN_P, NULL);
}
xdl_close(handle);
}
void xdl_linker_lock(void) {
xdl_linker_init();
if (NULL != xdl_linker_mutex) pthread_mutex_lock(xdl_linker_mutex);
}
void xdl_linker_unlock(void) {
if (NULL != xdl_linker_mutex) pthread_mutex_unlock(xdl_linker_mutex);
}
static void *xdl_linker_get_caller_addr(struct dl_phdr_info *info) {
for (size_t i = 0; i < info->dlpi_phnum; i++) {
const ElfW(Phdr) *phdr = &(info->dlpi_phdr[i]);
if (PT_LOAD == phdr->p_type) {
return (void *)(info->dlpi_addr + phdr->p_vaddr);
}
}
return NULL;
}
static int xdl_linker_get_caller_addr_cb(struct dl_phdr_info *info, size_t size, void *arg) {
(void)size;
size_t *vendor_match = (size_t *)arg;
if (0 == info->dlpi_addr || NULL == info->dlpi_name) return 0; // continue
if (NULL == xdl_linker_caller_addr[0] && xdl_util_ends_with(info->dlpi_name, "/libc.so"))
xdl_linker_caller_addr[0] = xdl_linker_get_caller_addr(info);
if (NULL == xdl_linker_caller_addr[1] && xdl_util_ends_with(info->dlpi_name, "/libart.so"))
xdl_linker_caller_addr[1] = xdl_linker_get_caller_addr(info);
if (0 != *vendor_match) {
for (size_t i = 0; i < *vendor_match; i++) {
if (xdl_util_starts_with(info->dlpi_name, xdl_linker_vendor_path[i])) {
void *caller_addr = xdl_linker_get_caller_addr(info);
if (NULL != caller_addr) {
xdl_linker_caller_addr[2] = caller_addr;
*vendor_match = i;
}
}
}
}
if (NULL != xdl_linker_caller_addr[0] && NULL != xdl_linker_caller_addr[1] && 0 == *vendor_match) {
return 1; // finish
} else {
return 0; // continue
}
}
static void xdl_linker_load_caller_addr(void) {
if (NULL == xdl_linker_caller_addr[0]) {
size_t vendor_match = sizeof(xdl_linker_vendor_path) / sizeof(xdl_linker_vendor_path[0]);
xdl_iterate_phdr_impl(xdl_linker_get_caller_addr_cb, &vendor_match, XDL_DEFAULT);
}
}
void *xdl_linker_load(const char *filename) {
int api_level = xdl_util_get_api_level();
if (api_level <= __ANDROID_API_M__) {
// <= Android 6.0
return dlopen(filename, RTLD_NOW);
} else {
xdl_linker_init();
if (NULL == xdl_linker_dlopen) return NULL;
xdl_linker_load_caller_addr();
void *handle = NULL;
if (__ANDROID_API_N__ == api_level || __ANDROID_API_N_MR1__ == api_level) {
// == Android 7.x
xdl_linker_lock();
for (size_t i = 0; i < sizeof(xdl_linker_caller_addr) / sizeof(xdl_linker_caller_addr[0]); i++) {
if (NULL != xdl_linker_caller_addr[i]) {
handle =
((xdl_linker_dlopen_n_t)xdl_linker_dlopen)(filename, RTLD_NOW, NULL, xdl_linker_caller_addr[i]);
if (NULL != handle) break;
}
}
xdl_linker_unlock();
} else {
// >= Android 8.0
for (size_t i = 0; i < sizeof(xdl_linker_caller_addr) / sizeof(xdl_linker_caller_addr[0]); i++) {
if (NULL != xdl_linker_caller_addr[i]) {
handle = ((xdl_linker_dlopen_o_t)xdl_linker_dlopen)(filename, RTLD_NOW, xdl_linker_caller_addr[i]);
if (NULL != handle) break;
}
}
}
return handle;
}
}

View File

@@ -0,0 +1,40 @@
// Copyright (c) 2020-2021 HexHacking Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Created by caikelun on 2021-02-21.
#ifndef IO_HEXHACKING_XDL_LINKER
#define IO_HEXHACKING_XDL_LINKER
#ifdef __cplusplus
extern "C" {
#endif
void xdl_linker_lock(void);
void xdl_linker_unlock(void);
void *xdl_linker_load(const char *filename);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,181 @@
// Copyright (c) 2020-2021 HexHacking Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Created by caikelun on 2020-11-08.
#include "xdl_lzma.h"
#include <ctype.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "xdl.h"
#include "xdl_util.h"
// LZMA library pathname & symbol names
#ifndef __LP64__
#define XDL_LZMA_PATHNAME "/system/lib/liblzma.so"
#else
#define XDL_LZMA_PATHNAME "/system/lib64/liblzma.so"
#endif
#define XDL_LZMA_SYM_CRCGEN "CrcGenerateTable"
#define XDL_LZMA_SYM_CRC64GEN "Crc64GenerateTable"
#define XDL_LZMA_SYM_CONSTRUCT "XzUnpacker_Construct"
#define XDL_LZMA_SYM_ISFINISHED "XzUnpacker_IsStreamWasFinished"
#define XDL_LZMA_SYM_FREE "XzUnpacker_Free"
#define XDL_LZMA_SYM_CODE "XzUnpacker_Code"
// LZMA data type definition
#define SZ_OK 0
typedef struct ISzAlloc ISzAlloc;
typedef const ISzAlloc *ISzAllocPtr;
struct ISzAlloc {
void *(*Alloc)(ISzAllocPtr p, size_t size);
void (*Free)(ISzAllocPtr p, void *address); /* address can be 0 */
};
typedef enum {
CODER_STATUS_NOT_SPECIFIED, /* use main error code instead */
CODER_STATUS_FINISHED_WITH_MARK, /* stream was finished with end mark. */
CODER_STATUS_NOT_FINISHED, /* stream was not finished */
CODER_STATUS_NEEDS_MORE_INPUT /* you must provide more input bytes */
} ECoderStatus;
typedef enum {
CODER_FINISH_ANY, /* finish at any point */
CODER_FINISH_END /* block must be finished at the end */
} ECoderFinishMode;
// LZMA function type definition
typedef void (*xdl_lzma_crcgen_t)(void);
typedef void (*xdl_lzma_crc64gen_t)(void);
typedef void (*xdl_lzma_construct_t)(void *, ISzAllocPtr);
typedef int (*xdl_lzma_isfinished_t)(const void *);
typedef void (*xdl_lzma_free_t)(void *);
typedef int (*xdl_lzma_code_t)(void *, uint8_t *, size_t *, const uint8_t *, size_t *, ECoderFinishMode,
ECoderStatus *);
typedef int (*xdl_lzma_code_q_t)(void *, uint8_t *, size_t *, const uint8_t *, size_t *, int,
ECoderFinishMode, ECoderStatus *);
// LZMA function pointor
static xdl_lzma_construct_t xdl_lzma_construct = NULL;
static xdl_lzma_isfinished_t xdl_lzma_isfinished = NULL;
static xdl_lzma_free_t xdl_lzma_free = NULL;
static void *xdl_lzma_code = NULL;
// LZMA init
static void xdl_lzma_init() {
void *lzma = xdl_open(XDL_LZMA_PATHNAME, XDL_TRY_FORCE_LOAD);
if (NULL == lzma) return;
xdl_lzma_crcgen_t crcgen = NULL;
xdl_lzma_crc64gen_t crc64gen = NULL;
if (NULL == (crcgen = (xdl_lzma_crcgen_t)xdl_sym(lzma, XDL_LZMA_SYM_CRCGEN, NULL))) goto end;
if (NULL == (crc64gen = (xdl_lzma_crc64gen_t)xdl_sym(lzma, XDL_LZMA_SYM_CRC64GEN, NULL))) goto end;
if (NULL == (xdl_lzma_construct = (xdl_lzma_construct_t)xdl_sym(lzma, XDL_LZMA_SYM_CONSTRUCT, NULL)))
goto end;
if (NULL == (xdl_lzma_isfinished = (xdl_lzma_isfinished_t)xdl_sym(lzma, XDL_LZMA_SYM_ISFINISHED, NULL)))
goto end;
if (NULL == (xdl_lzma_free = (xdl_lzma_free_t)xdl_sym(lzma, XDL_LZMA_SYM_FREE, NULL))) goto end;
if (NULL == (xdl_lzma_code = xdl_sym(lzma, XDL_LZMA_SYM_CODE, NULL))) goto end;
crcgen();
crc64gen();
end:
xdl_close(lzma);
}
// LZMA internal alloc / free
static void *xdl_lzma_internal_alloc(ISzAllocPtr p, size_t size) {
(void)p;
return malloc(size);
}
static void xdl_lzma_internal_free(ISzAllocPtr p, void *address) {
(void)p;
free(address);
}
int xdl_lzma_decompress(uint8_t *src, size_t src_size, uint8_t **dst, size_t *dst_size) {
size_t src_offset = 0;
size_t dst_offset = 0;
size_t src_remaining;
size_t dst_remaining;
ISzAlloc alloc = {.Alloc = xdl_lzma_internal_alloc, .Free = xdl_lzma_internal_free};
long long state[4096 / sizeof(long long)]; // must be enough, 8-bit aligned
ECoderStatus status;
int api_level = xdl_util_get_api_level();
// init and check
static bool inited = false;
if (!inited) {
xdl_lzma_init();
inited = true;
}
if (NULL == xdl_lzma_code) return -1;
xdl_lzma_construct(&state, &alloc);
*dst_size = 2 * src_size;
*dst = NULL;
do {
*dst_size *= 2;
if (NULL == (*dst = realloc(*dst, *dst_size))) {
xdl_lzma_free(&state);
return -1;
}
src_remaining = src_size - src_offset;
dst_remaining = *dst_size - dst_offset;
int result;
if (api_level >= __ANDROID_API_Q__) {
xdl_lzma_code_q_t lzma_code_q = (xdl_lzma_code_q_t)xdl_lzma_code;
result = lzma_code_q(&state, *dst + dst_offset, &dst_remaining, src + src_offset, &src_remaining, 1,
CODER_FINISH_ANY, &status);
} else {
xdl_lzma_code_t lzma_code = (xdl_lzma_code_t)xdl_lzma_code;
result = lzma_code(&state, *dst + dst_offset, &dst_remaining, src + src_offset, &src_remaining,
CODER_FINISH_ANY, &status);
}
if (SZ_OK != result) {
free(*dst);
xdl_lzma_free(&state);
return -1;
}
src_offset += src_remaining;
dst_offset += dst_remaining;
} while (status == CODER_STATUS_NOT_FINISHED);
xdl_lzma_free(&state);
if (!xdl_lzma_isfinished(&state)) {
free(*dst);
return -1;
}
*dst_size = dst_offset;
*dst = realloc(*dst, *dst_size);
return 0;
}

View File

@@ -0,0 +1,40 @@
// Copyright (c) 2020-2021 HexHacking Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Created by caikelun on 2020-11-08.
#ifndef IO_HEXHACKING_XDL_LZMA
#define IO_HEXHACKING_XDL_LZMA
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
int xdl_lzma_decompress(uint8_t *src, size_t src_size, uint8_t **dst, size_t *dst_size);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,95 @@
// Copyright (c) 2020-2021 HexHacking Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Created by caikelun on 2020-10-04.
#include "xdl_util.h"
#include <android/api-level.h>
#include <ctype.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
bool xdl_util_starts_with(const char *str, const char *start) {
while (*str && *str == *start) {
str++;
start++;
}
return '\0' == *start;
}
bool xdl_util_ends_with(const char *str, const char *ending) {
size_t str_len = strlen(str);
size_t ending_len = strlen(ending);
if (ending_len > str_len) return false;
return 0 == strcmp(str + (str_len - ending_len), ending);
}
size_t xdl_util_trim_ending(char *start) {
char *end = start + strlen(start);
while (start < end && isspace((int)(*(end - 1)))) {
end--;
*end = '\0';
}
return (size_t)(end - start);
}
static int xdl_util_get_api_level_from_build_prop(void) {
char buf[128];
int api_level = -1;
FILE *fp = fopen("/system/build.prop", "r");
if (NULL == fp) goto end;
while (fgets(buf, sizeof(buf), fp)) {
if (xdl_util_starts_with(buf, "ro.build.version.sdk=")) {
api_level = atoi(buf + 21);
break;
}
}
fclose(fp);
end:
return (api_level > 0) ? api_level : -1;
}
int xdl_util_get_api_level(void) {
static int xdl_util_api_level = -1;
if (xdl_util_api_level < 0) {
int api_level = android_get_device_api_level();
if (api_level < 0)
api_level = xdl_util_get_api_level_from_build_prop(); // compatible with unusual models
if (api_level < __ANDROID_API_J__) api_level = __ANDROID_API_J__;
__atomic_store_n(&xdl_util_api_level, api_level, __ATOMIC_SEQ_CST);
}
return xdl_util_api_level;
}

View File

@@ -0,0 +1,71 @@
// Copyright (c) 2020-2021 HexHacking Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Created by caikelun on 2020-10-04.
#ifndef IO_HEXHACKING_XDL_UTIL
#define IO_HEXHACKING_XDL_UTIL
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#ifndef __LP64__
#define XDL_UTIL_LINKER_BASENAME "linker"
#define XDL_UTIL_LINKER_PATHNAME "/system/bin/linker"
#define XDL_UTIL_APP_PROCESS_BASENAME "app_process32"
#define XDL_UTIL_APP_PROCESS_PATHNAME "/system/bin/app_process32"
#define XDL_UTIL_APP_PROCESS_BASENAME_K "app_process"
#define XDL_UTIL_APP_PROCESS_PATHNAME_K "/system/bin/app_process"
#else
#define XDL_UTIL_LINKER_BASENAME "linker64"
#define XDL_UTIL_LINKER_PATHNAME "/system/bin/linker64"
#define XDL_UTIL_APP_PROCESS_BASENAME "app_process64"
#define XDL_UTIL_APP_PROCESS_PATHNAME "/system/bin/app_process64"
#endif
#define XDL_UTIL_VDSO_BASENAME "[vdso]"
#define XDL_UTIL_TEMP_FAILURE_RETRY(exp) \
({ \
__typeof__(exp) _rc; \
do { \
errno = 0; \
_rc = (exp); \
} while (_rc == -1 && errno == EINTR); \
_rc; \
})
#ifdef __cplusplus
extern "C" {
#endif
bool xdl_util_starts_with(const char *str, const char *start);
bool xdl_util_ends_with(const char *str, const char *ending);
size_t xdl_util_trim_ending(char *start);
int xdl_util_get_api_level(void);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,326 @@
// This is the public API for Zygisk modules.
// DO NOT MODIFY ANY CODE IN THIS HEADER.
#pragma once
#include <jni.h>
#define ZYGISK_API_VERSION 2
/*
Define a class and inherit zygisk::ModuleBase to implement the functionality of your module.
Use the macro REGISTER_ZYGISK_MODULE(className) to register that class to Zygisk.
Please note that modules will only be loaded after zygote has forked the child process.
THIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM SERVER PROCESS, NOT THE ZYGOTE DAEMON!
Example code:
static jint (*orig_logger_entry_max)(JNIEnv *env);
static jint my_logger_entry_max(JNIEnv *env) { return orig_logger_entry_max(env); }
static void example_handler(int socket) { ... }
class ExampleModule : public zygisk::ModuleBase {
public:
void onLoad(zygisk::Api *api, JNIEnv *env) override {
this->api = api;
this->env = env;
}
void preAppSpecialize(zygisk::AppSpecializeArgs *args) override {
JNINativeMethod methods[] = {
{ "logger_entry_max_payload_native", "()I", (void*) my_logger_entry_max },
};
api->hookJniNativeMethods(env, "android/util/Log", methods, 1);
*(void **) &orig_logger_entry_max = methods[0].fnPtr;
}
private:
zygisk::Api *api;
JNIEnv *env;
};
REGISTER_ZYGISK_MODULE(ExampleModule)
REGISTER_ZYGISK_COMPANION(example_handler)
*/
namespace zygisk {
struct Api;
struct AppSpecializeArgs;
struct ServerSpecializeArgs;
class ModuleBase {
public:
// This function is called when the module is loaded into the target process.
// A Zygisk API handle will be sent as an argument; call utility functions or interface
// with Zygisk through this handle.
virtual void onLoad([[maybe_unused]] Api *api, [[maybe_unused]] JNIEnv *env) {}
// This function is called before the app process is specialized.
// At this point, the process just got forked from zygote, but no app specific specialization
// is applied. This means that the process does not have any sandbox restrictions and
// still runs with the same privilege of zygote.
//
// All the arguments that will be sent and used for app specialization is passed as a single
// AppSpecializeArgs object. You can read and overwrite these arguments to change how the app
// process will be specialized.
//
// If you need to run some operations as superuser, you can call Api::connectCompanion() to
// get a socket to do IPC calls with a root companion process.
// See Api::connectCompanion() for more info.
virtual void preAppSpecialize([[maybe_unused]] AppSpecializeArgs *args) {}
// This function is called after the app process is specialized.
// At this point, the process has all sandbox restrictions enabled for this application.
// This means that this function runs as the same privilege of the app's own code.
virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {}
// This function is called before the system server process is specialized.
// See preAppSpecialize(args) for more info.
virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {}
// This function is called after the system server process is specialized.
// At this point, the process runs with the privilege of system_server.
virtual void postServerSpecialize([[maybe_unused]] const ServerSpecializeArgs *args) {}
};
struct AppSpecializeArgs {
// Required arguments. These arguments are guaranteed to exist on all Android versions.
jint &uid;
jint &gid;
jintArray &gids;
jint &runtime_flags;
jint &mount_external;
jstring &se_info;
jstring &nice_name;
jstring &instruction_set;
jstring &app_data_dir;
// Optional arguments. Please check whether the pointer is null before de-referencing
jboolean *const is_child_zygote;
jboolean *const is_top_app;
jobjectArray *const pkg_data_info_list;
jobjectArray *const whitelisted_data_info_list;
jboolean *const mount_data_dirs;
jboolean *const mount_storage_dirs;
AppSpecializeArgs() = delete;
};
struct ServerSpecializeArgs {
jint &uid;
jint &gid;
jintArray &gids;
jint &runtime_flags;
jlong &permitted_capabilities;
jlong &effective_capabilities;
ServerSpecializeArgs() = delete;
};
namespace internal {
struct api_table;
template <class T> void entry_impl(api_table *, JNIEnv *);
}
// These values are used in Api::setOption(Option)
enum Option : int {
// Force Magisk's denylist unmount routines to run on this process.
//
// Setting this option only makes sense in preAppSpecialize.
// The actual unmounting happens during app process specialization.
//
// Set this option to force all Magisk and modules' files to be unmounted from the
// mount namespace of the process, regardless of the denylist enforcement status.
FORCE_DENYLIST_UNMOUNT = 0,
// When this option is set, your module's library will be dlclose-ed after post[XXX]Specialize.
// Be aware that after dlclose-ing your module, all of your code will be unmapped from memory.
// YOU MUST NOT ENABLE THIS OPTION AFTER HOOKING ANY FUNCTIONS IN THE PROCESS.
DLCLOSE_MODULE_LIBRARY = 1,
};
// Bit masks of the return value of Api::getFlags()
enum StateFlag : uint32_t {
// The user has granted root access to the current process
PROCESS_GRANTED_ROOT = (1u << 0),
// The current process was added on the denylist
PROCESS_ON_DENYLIST = (1u << 1),
};
// All API functions will stop working after post[XXX]Specialize as Zygisk will be unloaded
// from the specialized process afterwards.
struct Api {
// Connect to a root companion process and get a Unix domain socket for IPC.
//
// This API only works in the pre[XXX]Specialize functions due to SELinux restrictions.
//
// The pre[XXX]Specialize functions run with the same privilege of zygote.
// If you would like to do some operations with superuser permissions, register a handler
// function that would be called in the root process with REGISTER_ZYGISK_COMPANION(func).
// Another good use case for a companion process is that if you want to share some resources
// across multiple processes, hold the resources in the companion process and pass it over.
//
// The root companion process is ABI aware; that is, when calling this function from a 32-bit
// process, you will be connected to a 32-bit companion process, and vice versa for 64-bit.
//
// Returns a file descriptor to a socket that is connected to the socket passed to your
// module's companion request handler. Returns -1 if the connection attempt failed.
int connectCompanion();
// Get the file descriptor of the root folder of the current module.
//
// This API only works in the pre[XXX]Specialize functions.
// Accessing the directory returned is only possible in the pre[XXX]Specialize functions
// or in the root companion process (assuming that you sent the fd over the socket).
// Both restrictions are due to SELinux and UID.
//
// Returns -1 if errors occurred.
int getModuleDir();
// Set various options for your module.
// Please note that this function accepts one single option at a time.
// Check zygisk::Option for the full list of options available.
void setOption(Option opt);
// Get information about the current process.
// Returns bitwise-or'd zygisk::StateFlag values.
uint32_t getFlags();
// Hook JNI native methods for a class
//
// Lookup all registered JNI native methods and replace it with your own functions.
// The original function pointer will be saved in each JNINativeMethod's fnPtr.
// If no matching class, method name, or signature is found, that specific JNINativeMethod.fnPtr
// will be set to nullptr.
void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods);
// For ELFs loaded in memory matching `regex`, replace function `symbol` with `newFunc`.
// If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`.
void pltHookRegister(const char *regex, const char *symbol, void *newFunc, void **oldFunc);
// For ELFs loaded in memory matching `regex`, exclude hooks registered for `symbol`.
// If `symbol` is nullptr, then all symbols will be excluded.
void pltHookExclude(const char *regex, const char *symbol);
// Commit all the hooks that was previously registered.
// Returns false if an error occurred.
bool pltHookCommit();
private:
internal::api_table *impl;
template <class T> friend void internal::entry_impl(internal::api_table *, JNIEnv *);
};
// Register a class as a Zygisk module
#define REGISTER_ZYGISK_MODULE(clazz) \
void zygisk_module_entry(zygisk::internal::api_table *table, JNIEnv *env) { \
zygisk::internal::entry_impl<clazz>(table, env); \
}
// Register a root companion request handler function for your module
//
// The function runs in a superuser daemon process and handles a root companion request from
// your module running in a target process. The function has to accept an integer value,
// which is a socket that is connected to the target process.
// See Api::connectCompanion() for more info.
//
// NOTE: the function can run concurrently on multiple threads.
// Be aware of race conditions if you have a globally shared resource.
#define REGISTER_ZYGISK_COMPANION(func) \
void zygisk_companion_entry(int client) { func(client); }
/************************************************************************************
* All the code after this point is internal code used to interface with Zygisk
* and guarantee ABI stability. You do not have to understand what it is doing.
************************************************************************************/
namespace internal {
struct module_abi {
long api_version;
ModuleBase *_this;
void (*preAppSpecialize)(ModuleBase *, AppSpecializeArgs *);
void (*postAppSpecialize)(ModuleBase *, const AppSpecializeArgs *);
void (*preServerSpecialize)(ModuleBase *, ServerSpecializeArgs *);
void (*postServerSpecialize)(ModuleBase *, const ServerSpecializeArgs *);
module_abi(ModuleBase *module) : api_version(ZYGISK_API_VERSION), _this(module) {
preAppSpecialize = [](auto self, auto args) { self->preAppSpecialize(args); };
postAppSpecialize = [](auto self, auto args) { self->postAppSpecialize(args); };
preServerSpecialize = [](auto self, auto args) { self->preServerSpecialize(args); };
postServerSpecialize = [](auto self, auto args) { self->postServerSpecialize(args); };
}
};
struct api_table {
// These first 2 entries are permanent, shall never change
void *_this;
bool (*registerModule)(api_table *, module_abi *);
// Utility functions
void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);
void (*pltHookRegister)(const char *, const char *, void *, void **);
void (*pltHookExclude)(const char *, const char *);
bool (*pltHookCommit)();
// Zygisk functions
int (*connectCompanion)(void * /* _this */);
void (*setOption)(void * /* _this */, Option);
int (*getModuleDir)(void * /* _this */);
uint32_t (*getFlags)(void * /* _this */);
};
template <class T>
void entry_impl(api_table *table, JNIEnv *env) {
ModuleBase *module = new T();
if (!table->registerModule(table, new module_abi(module)))
return;
auto api = new Api();
api->impl = table;
module->onLoad(api, env);
}
} // namespace internal
inline int Api::connectCompanion() {
return impl->connectCompanion ? impl->connectCompanion(impl->_this) : -1;
}
inline int Api::getModuleDir() {
return impl->getModuleDir ? impl->getModuleDir(impl->_this) : -1;
}
inline void Api::setOption(Option opt) {
if (impl->setOption) impl->setOption(impl->_this, opt);
}
inline uint32_t Api::getFlags() {
return impl->getFlags ? impl->getFlags(impl->_this) : 0;
}
inline void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) {
if (impl->hookJniNativeMethods) impl->hookJniNativeMethods(env, className, methods, numMethods);
}
inline void Api::pltHookRegister(const char *regex, const char *symbol, void *newFunc, void **oldFunc) {
if (impl->pltHookRegister) impl->pltHookRegister(regex, symbol, newFunc, oldFunc);
}
inline void Api::pltHookExclude(const char *regex, const char *symbol) {
if (impl->pltHookExclude) impl->pltHookExclude(regex, symbol);
}
inline bool Api::pltHookCommit() {
return impl->pltHookCommit != nullptr && impl->pltHookCommit();
}
} // namespace zygisk
[[gnu::visibility("default")]] [[gnu::used]]
extern "C" void zygisk_module_entry(zygisk::internal::api_table *, JNIEnv *);
[[gnu::visibility("default")]] [[gnu::used]]
extern "C" void zygisk_companion_entry(int);

5
settings.gradle Normal file
View File

@@ -0,0 +1,5 @@
include ':module'
import org.apache.tools.ant.DirectoryScanner
DirectoryScanner.removeDefaultExclude('**/.gitattributes')

View File

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

View File

@@ -0,0 +1 @@
#MAGISK

View File

@@ -0,0 +1,6 @@
id=${id}
name=${name}
version=${version}
versionCode=${versionCode}
author=${author}
description=${description}