commit 5408de21e5208a4ff62e8dac09765a64a7660e15 Author: jiqiu2021 Date: Sat Oct 5 15:26:16 2024 +0800 add diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..27fc7c2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +* text=auto eol=lf + +*.bat text eol=crlf +*.jar binary \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..ec41728 --- /dev/null +++ b/.github/workflows/build.yml @@ -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/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fc00840 --- /dev/null +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..39817e7 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d4ed2d4 --- /dev/null +++ b/README.md @@ -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 \ No newline at end of file diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 0000000..f8b2012 --- /dev/null +++ b/README.zh-CN.md @@ -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` \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..5e02054 --- /dev/null +++ b/build.gradle @@ -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 +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..01b80d7 --- /dev/null +++ b/gradle.properties @@ -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 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f6b961f Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2ab173e --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -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" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat @@ -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 diff --git a/module.gradle b/module.gradle new file mode 100644 index 0000000..b294adf --- /dev/null +++ b/module.gradle @@ -0,0 +1,9 @@ +ext { + moduleLibraryName = "myinjector" + magiskModuleId = "zygisk_myinjector" + moduleName = "myinjector" + moduleAuthor = "jiqiu2021" + moduleDescription = "注入任意SO到指定APP内" + moduleVersion = "v0.01" + moduleVersionCode = 1 +} diff --git a/module/.gitignore b/module/.gitignore new file mode 100644 index 0000000..9833d4b --- /dev/null +++ b/module/.gitignore @@ -0,0 +1,3 @@ +/.externalNativeBuild +/build +/release \ No newline at end of file diff --git a/module/build.gradle b/module/build.gradle new file mode 100644 index 0000000..915645c --- /dev/null +++ b/module/build.gradle @@ -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}") + } +} diff --git a/module/src/main/AndroidManifest.xml b/module/src/main/AndroidManifest.xml new file mode 100644 index 0000000..762b99d --- /dev/null +++ b/module/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/module/src/main/cpp/CMakeLists.txt b/module/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000..4549504 --- /dev/null +++ b/module/src/main/cpp/CMakeLists.txt @@ -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 () diff --git a/module/src/main/cpp/game.h b/module/src/main/cpp/game.h new file mode 100644 index 0000000..64378e7 --- /dev/null +++ b/module/src/main/cpp/game.h @@ -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 diff --git a/module/src/main/cpp/hack.cpp b/module/src/main/cpp/hack.cpp new file mode 100644 index 0000000..3bcf9a0 --- /dev/null +++ b/module/src/main/cpp/hack.cpp @@ -0,0 +1,264 @@ +// +// Created by Perfare on 2020/7/4. +// + +#include "hack.h" +#include "log.h" +#include "xdl.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +#include + +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(); + __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 \ No newline at end of file diff --git a/module/src/main/cpp/hack.h b/module/src/main/cpp/hack.h new file mode 100644 index 0000000..30912ed --- /dev/null +++ b/module/src/main/cpp/hack.h @@ -0,0 +1,12 @@ +// +// Created by Perfare on 2020/7/4. +// + +#ifndef ZYGISK_IL2CPPDUMPER_HACK_H +#define ZYGISK_IL2CPPDUMPER_HACK_H + +#include + +void hack_prepare(const char *game_data_dir, void *data, size_t length); + +#endif //ZYGISK_IL2CPPDUMPER_HACK_H diff --git a/module/src/main/cpp/log.h b/module/src/main/cpp/log.h new file mode 100644 index 0000000..bb4e5f1 --- /dev/null +++ b/module/src/main/cpp/log.h @@ -0,0 +1,16 @@ +// +// Created by Perfare on 2020/7/4. +// + +#ifndef ZYGISK_IL2CPPDUMPER_LOG_H +#define ZYGISK_IL2CPPDUMPER_LOG_H + +#include + +#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 diff --git a/module/src/main/cpp/main.cpp b/module/src/main/cpp/main.cpp new file mode 100644 index 0000000..5fa0b4f --- /dev/null +++ b/module/src/main/cpp/main.cpp @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#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) \ No newline at end of file diff --git a/module/src/main/cpp/xdl/include/xdl.h b/module/src/main/cpp/xdl/include/xdl.h new file mode 100644 index 0000000..c4ef845 --- /dev/null +++ b/module/src/main/cpp/xdl/include/xdl.h @@ -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 +#include +#include + +#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 diff --git a/module/src/main/cpp/xdl/xdl.c b/module/src/main/cpp/xdl/xdl.c new file mode 100644 index 0000000..9267f26 --- /dev/null +++ b/module/src/main/cpp/xdl/xdl.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/module/src/main/cpp/xdl/xdl_iterate.c b/module/src/main/cpp/xdl/xdl_iterate.c new file mode 100644 index 0000000..01f4145 --- /dev/null +++ b/module/src/main/cpp/xdl/xdl_iterate.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/module/src/main/cpp/xdl/xdl_iterate.h b/module/src/main/cpp/xdl/xdl_iterate.h new file mode 100644 index 0000000..81b6188 --- /dev/null +++ b/module/src/main/cpp/xdl/xdl_iterate.h @@ -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 +#include + +#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 diff --git a/module/src/main/cpp/xdl/xdl_linker.c b/module/src/main/cpp/xdl/xdl_linker.c new file mode 100644 index 0000000..c29b950 --- /dev/null +++ b/module/src/main/cpp/xdl/xdl_linker.c @@ -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 +#include +#include +#include + +#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; + } +} diff --git a/module/src/main/cpp/xdl/xdl_linker.h b/module/src/main/cpp/xdl/xdl_linker.h new file mode 100644 index 0000000..4067de4 --- /dev/null +++ b/module/src/main/cpp/xdl/xdl_linker.h @@ -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 diff --git a/module/src/main/cpp/xdl/xdl_lzma.c b/module/src/main/cpp/xdl/xdl_lzma.c new file mode 100644 index 0000000..14b041e --- /dev/null +++ b/module/src/main/cpp/xdl/xdl_lzma.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/module/src/main/cpp/xdl/xdl_lzma.h b/module/src/main/cpp/xdl/xdl_lzma.h new file mode 100644 index 0000000..bd12ac0 --- /dev/null +++ b/module/src/main/cpp/xdl/xdl_lzma.h @@ -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 +#include + +#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 diff --git a/module/src/main/cpp/xdl/xdl_util.c b/module/src/main/cpp/xdl/xdl_util.c new file mode 100644 index 0000000..1405a2e --- /dev/null +++ b/module/src/main/cpp/xdl/xdl_util.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +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; +} diff --git a/module/src/main/cpp/xdl/xdl_util.h b/module/src/main/cpp/xdl/xdl_util.h new file mode 100644 index 0000000..1bd8502 --- /dev/null +++ b/module/src/main/cpp/xdl/xdl_util.h @@ -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 +#include +#include + +#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 diff --git a/module/src/main/cpp/zygisk.hpp b/module/src/main/cpp/zygisk.hpp new file mode 100644 index 0000000..a7383e5 --- /dev/null +++ b/module/src/main/cpp/zygisk.hpp @@ -0,0 +1,326 @@ +// This is the public API for Zygisk modules. +// DO NOT MODIFY ANY CODE IN THIS HEADER. + +#pragma once + +#include + +#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 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 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(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 +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); diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..a843339 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,5 @@ +include ':module' + +import org.apache.tools.ant.DirectoryScanner + +DirectoryScanner.removeDefaultExclude('**/.gitattributes') diff --git a/template/magisk_module/META-INF/com/google/android/update-binary b/template/magisk_module/META-INF/com/google/android/update-binary new file mode 100644 index 0000000..28b48e5 --- /dev/null +++ b/template/magisk_module/META-INF/com/google/android/update-binary @@ -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 diff --git a/template/magisk_module/META-INF/com/google/android/updater-script b/template/magisk_module/META-INF/com/google/android/updater-script new file mode 100644 index 0000000..11d5c96 --- /dev/null +++ b/template/magisk_module/META-INF/com/google/android/updater-script @@ -0,0 +1 @@ +#MAGISK diff --git a/template/magisk_module/module.prop b/template/magisk_module/module.prop new file mode 100644 index 0000000..236bd20 --- /dev/null +++ b/template/magisk_module/module.prop @@ -0,0 +1,6 @@ +id=${id} +name=${name} +version=${version} +versionCode=${versionCode} +author=${author} +description=${description}