Android Native 函数 Hook 技术介绍
版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/
前言
Android Native 函数 Hook 技术是一种在应用运行时拦截或替换系统或自身函数行为的手段,常见实现包括 PLT Hook、Inline Hook。
PLT Hook 和 Inline Hook 是两种不同层次和机制的函数 Hook 技术,常用于逆向工程、安全分析、壳保护或热修复等场景。
PLT hook 和 Inline hook 有什么区别?
特性 | PLT Hook(符号级) | Inline Hook(指令级) |
---|---|---|
Hook 层级 | 链接层(.plt 或 GOT) | 指令层(函数入口指令) |
Hook 对象 | 动态库导出的函数 | 任意函数(不一定导出) |
修改内容 | 修改函数地址指针 | 修改指令流(如跳转指令) |
Hook 粒度 | 通常是库间调用 | 可以精确到任意函数、库内调用 |
稳定性 | 较高 | 稍差(依赖指令结构、架构) |
跨平台兼容性 | 更好 | 架构相关(ARM32/ARM64) |
PLT Hook 原理(也叫 GOT Hook)
ELF 中动态链接时使用 PLT(Procedure Linkage Table) 和 GOT(Global Offset Table);
程序调用外部函数(如 malloc、fopen)时,实际上是跳到 GOT 表中的地址;
可以通过修改 GOT 表,将目标函数地址替换为你自己的函数。
✅优点:
-
修改一次,全局生效(所有调用者都被 hook)
-
不需要写复杂汇编指令
❌ 缺点:
-
只能 Hook 动态链接函数(如 libc.so 中的函数)
-
无法 Hook 静态链接或内部函数调用
Inline Hook 原理
直接修改目标函数的前几条机器指令(通常是函数入口),替换成跳转指令(如 b <new_func> 或 ldr pc, [addr]);
原函数内容被破坏,因此会备份原始指令并在新函数中支持“回调原函数”(trampoline)。
✅ 优点:
-
可以 Hook 几乎任意函数(导出或非导出、静态或动态)
-
精细控制,适合保护/加壳/代码注入等底层用途
❌ 缺点:
-
对 CPU 架构高度依赖(ARM64、ARMv7)
-
对汇编、内存保护、缓存等有要求(必须关闭写保护)
-
稳定性较低,不当使用可能 crash
举个例子
假设目标函数是 malloc(),我们希望 Hook 它:
PLT Hook 方式:
-
找到 .got 中 malloc 的地址
-
改成我们自己的函数地址
void* my_malloc(size_t size) {printf("Intercepted malloc(%zu)\n", size);return real_malloc(size); // 调用原函数
}
Inline Hook 方式:
-
找到 malloc 函数地址,直接改入口 4~8 字节为跳转指令
-
替换后的入口可能变成:
b my_malloc ; ARM
App 中函数 Hook 是否会影响其他应用?
是否互相影响,取决于 Hook 的层级 和 Hook 方式,以下是详细分析:
关键点:Android 的进程隔离机制
在 Android 中,每个 App 通常运行在自己独立的进程(除非使用 android:sharedUserId 和特定签名),所以:
-
Java 层 Hook(如 Xposed):是 per-process(每个进程生效),不同 App 之间互不影响
-
Native 层 Hook(如 inline/PLT):也通常在当前进程生效
-
系统全局 Hook(如内核级 syscall hook):才可能影响所有进程,包括其他 App
示例分析
情况 1:两个 App 分别使用 inline hook hook 自己的 libc.so 的 open()
✅ 各自 Hook 的是自己进程中的 open
❌ 不会互相影响
情况 2:你 Hook 了 system_server 的某个系统服务(如 ActivityManager)
✅ 如果你有权限(如 Riru、Zygisk 模块),就能影响整个系统服务,其他 App 可能间接受影响
情况 3:你在内核或 system-wide 层 hook 了 syscall(比如使用 LKM hook read())
❗ 会影响整个系统,包括所有 App
🧨 风险大,容易被检测或引发稳定性问题
ShadowHook
ShadowHook 是一个 Android inline hook 库,它支持 thumb、arm32 和 arm64。
ShadowHook 现在被用于 TikTok,抖音,今日头条,西瓜视频,飞书中。
开源地址:https://github.com/bytedance/android-inline-hook
文档:https://github.com/bytedance/android-inline-hook/blob/main/README.zh-CN.md
ShadowHook 手册:https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.zh-CN.md
运行 ShadowHook
首先把 ShadowHook 源码 clone 到本地并导入到 Android Studio。
点击运行,提示找不到 CMake ‘3.30.5’
[CXX1300] CMake '3.30.5' was not found in SDK, PATH, or by cmake.dir property.
Affected Modules: app, shadowhook, systest
打开 Android SDK 配置
-
点击右下角 Show Package Details。
-
找到并勾选 CMake 3.30.5。
-
点击 Apply → 自动下载并安装。
也可以用命令行工具安装:
cd D:\App\android\sdk\cmdline-tools\latest\bin./sdkmanager "cmake;3.30.5"Warning: Observed package id 'system-images;android-34;lineage;arm64-v8a' in inconsistent location 'D:\App\android\sdk\system-images\android-34\google_apis_playstore\arm64-v8a' (Expected 'D:\App\android\sdk\system-images\android-34\lineage\arm64-v8a')
Warning: Observed package id 'system-images;android-34;lineage;x86_64' in inconsistent location 'D:\App\android\sdk\system-images\android-34\google_apis_playstore\x86_64' (Expected 'D:\App\android\sdk\system-images\android-34\lineage\x86_64')
Warning: Observed package id 'system-images;android-34;lineage;arm64-v8a' in inconsistent location 'D:\App\android\sdk\system-images\android-34\google_apis_playstore\arm64-v8a' (Expected 'D:\App\android\sdk\system-images\android-34\lineage\arm64-v8a')
Warning: Observed package id 'system-images;android-34;lineage;x86_64' in inconsistent location 'D:\App\android\sdk\system-images\android-34\google_apis_playstore\x86_64' (Expected 'D:\App\android\sdk\system-images\android-34\lineage\x86_64')
[=======================================] 100% Unzipping... share/vim/vimfiles/s
需要下载对应版本的 ndk
再次运行,提示如下错误
Execution failed for task ':shadowhook:compileDebugJavaWithJavac'.
> Java compiler version 21 has removed support for compiling with source/target version 7.Try one of the following options:1. [Recommended] Use Java toolchain with a lower language version2. Set a higher source/target version3. Use a lower version of the JDK running the build (if you're not using Java toolchain)For more details on how to configure these settings, see https://developer.android.com/build/jdks.Set Java Toolchain to 11
Change Java language level and jvmTarget to 11 in all modules if using a lower level.
Pick a different compatibility level...
Pick a different JDK to run Gradle...
More information...
这个错误的意思是你使用的是 JDK 21,而你的项目设置了 sourceCompatibility = 1.7(也就是 Java 7),但 JDK 21 已经移除了对 Java 7 的编译支持。
设置 Java Toolchain 为 Java 11
编译运行成功!
集成 ShadowHook
启用 Android 的 Prefab 功能,这是用于支持 C/C++ 依赖包管理 的功能。
android {buildFeatures {prefab = true}
}
在 gradle/libs.versions.toml 增加 shadowhook
[versions]
shadowhook = "x.y.z"[libraries]
shadowhook = { group = "com.bytedance.android", name = "shadowhook", version.ref = "shadowhook" }
x.y.z 请替换成版本号,建议使用最新的 release 版本。
在 build.gradle.kts 中增加依赖
dependencies {implementation(libs.shadowhook)
}
参考:ShadowHook 手册
初始化 ShadowHook
// 初始化 ShadowHook
ShadowHook.init(ConfigBuilder()// 设置 hook 模式:// UNIQUE 模式:同一个 hook 点只能被 hook 一次(unhook 后可以再次 hook)。// SHARED 模式:可对同一个 hook 点并发执行多个 hook 和 unhook,彼此互不干扰。.setMode(ShadowHook.Mode.UNIQUE)// 启用调试日志,方便开发阶段查看 hook 的行为.setDebuggable(true)// 启用 hook 记录功能,可以记录每一次 hook 的详细信息,开发调试有用(建议发布时设为 false).setRecordable(true)// 构建配置对象.build()
)
CMakeLists.txt 配置
add_library( # 设置库的名称cyrus_studio_hook# 设置库的类型SHARED# 设置源文件路径cyrus_studio_hook.cpp
)target_link_libraries(cyrus_studio_hook# 链接 log 库${log-lib}# 链接 shadowhookshadowhook::shadowhook
)
hook execve 函数,禁用 dex2oat
通过 hook dex2oat 调用来禁用 dex2oat,防止 OAT 文件生成(避免反编译、加壳保护)
目标函数:execve(当执行 /system/bin/dex2oat)
wayne:/ # cd /system/bin/
wayne:/system/bin # ls -hl | grep dex2oat
lrwxr-xr-x 1 root shell 37 2009-01-01 08:00 dex2oat -> /apex/com.android.runtime/bin/dex2oat
execve 函数源码:
#include <unistd.h>
#include "syscall.h"int execve(const char *path, char *const argv[], char *const envp[])
{
/* do we need to use environ if envp is null? */
return syscall(SYS_execve, path, argv, envp);
}
https://cs.android.com/android/platform/superproject/+/android10-release:bionic/libc/include/unistd.h;l=95
https://cs.android.com/android/platform/superproject/main/+/main:external/musl/src/process/execve.c
dex2oat 是 Android ART 在安装 APK 或运行时优化 dex 时调用的工具,通常通过 execve 执行。我们可以 Hook 掉这个 execve,当它的第一个参数包含 dex2oat 时直接返回一个错误码(如 -1),从而实现 跳过 dex2oat 执行。
cyrus_studio_hook.cpp
#include <unistd.h>
#include <android/log.h>
#include <jni.h>
#include <string>
#include <stddef.h>
#include "shadowhook.h"#define LOG_TAG "cyrus_studio_hook"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, 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__)// 原始 execve 函数指针
int (*orig_execve)(const char *__file, char *const *__argv, char *const *__envp);// 替代 execve 实现
int my_execve(const char *__file, char *const *__argv, char *const *__envp) {LOGI("execve called: %s", __file);if (__file && strstr(__file, "dex2oat")) {LOGW("Blocked dex2oat execution: %s", __file);// 返回失败,模拟 dex2oat 调用失败return -1;}// 调用原始 execvereturn orig_execve(__file, __argv, __envp);
}extern "C"
JNIEXPORT void JNICALL
Java_com_cyrus_example_hook_CyrusStudioHook_hookExecve(JNIEnv *, jclass) {void *handle = shadowhook_hook_sym_name("libc.so", // 函数所在模块"execve", // 要 hook 的符号名reinterpret_cast<void *>(my_execve),reinterpret_cast<void **>(&orig_execve));if (handle != nullptr) {LOGI("Successfully hooked execve");} else {LOGW("Failed to hook execve");}
}
kotlin 代码如下:
package com.cyrus.example.hookimport com.bytedance.shadowhook.ShadowHook
import com.bytedance.shadowhook.ShadowHook.ConfigBuilderobject CyrusStudioHook {init {System.loadLibrary("cyrus_studio_hook") // 加载 native 实现}fun init(){// 初始化 ShadowHookShadowHook.init(ConfigBuilder()// 设置 hook 模式:// UNIQUE 模式:同一个 hook 点只能被 hook 一次(unhook 后可以再次 hook)。// SHARED 模式:可对同一个 hook 点并发执行多个 hook 和 unhook,彼此互不干扰。.setMode(ShadowHook.Mode.UNIQUE)// 启用调试日志,方便开发阶段查看 hook 的行为.setDebuggable(true)// 启用 hook 记录功能,可以记录每一次 hook 的详细信息,开发调试有用(建议发布时设为 false).setRecordable(true)// 构建配置对象.build())}@JvmStaticexternal fun hookExecve()
}
执行 hookExecve() 方法,日志输出如下:
2025-05-07 00:14:51.790 21783-21783 shadowhook_tag com.cyrus.example I shadowhook: hook_sym_name(libc.so, execve, 0x7a178a9874) ...
2025-05-07 00:14:51.790 21783-21783 shadowhook_tag com.cyrus.example I exit: alloc out library, exit 7aaa27b020, pc 7aabaa5f70, distance 182af50, range [-8000000, 7fffffc]
2025-05-07 00:14:51.790 21783-21783 shadowhook_tag com.cyrus.example I a64 rewrite: type 0, inst d2801ba8
2025-05-07 00:14:51.790 21783-21783 shadowhook_tag com.cyrus.example I a64: hook (WITH EXIT) OK. target 7aabaa5f70 -> exit 7aaa27b020 -> new 7a178a9874 -> enter 7aaa2e7200 -> remaining 7aabaa5f74
2025-05-07 00:14:51.790 21783-21783 shadowhook_tag com.cyrus.example I switch: hook in UNIQUE mode OK: target_addr 7aabaa5f70, new_addr 7a178a9874
2025-05-07 00:14:51.791 21783-21783 shadowhook_tag com.cyrus.example I shadowhook: hook_sym_name(libc.so, execve, 0x7a178a9874) OK. return: 0x7aacaa2a80. 0 - OK
2025-05-07 00:14:51.791 21783-21783 cyrus_studio_hook com.cyrus.example I Successfully hooked execve
Successfully hooked execve
ClassLinker::LoadMethod
ClassLinker::LoadMethod 是 ART 中负责将 dex 文件中的方法信息解析并填充到 ArtMethod 结构体中的关键函数,用于方法加载过程中的元数据初始化。
找到导出符号
ART 使用 Clang/LLVM 编译,符号导出受 C++ 名字修饰(name mangling)影响:
-
不同版本可能使用不同的 Clang 版本;
-
编译参数变化(如 -fvisibility=hidden)会改变导出符号行为。
C 函数没有 Name Mangler 的概念,所以 execve 函数的符号就是函数名字;但 ClassLinker::LoadMethod 是 C++ 函数,如果要实现更好的 Android 版本兼容性,需要自己处理函数符号名的差异。
1、找到 libart.so
wayne:/ # find /system -iname 'libart.so'
/system/apex/com.android.runtime.release/lib64/libart.so
2、把 libart.so pull 下来
adb pull /system/apex/com.android.runtime.release/lib64/libart.so
3、使用 nm 确认符号名
cyrus@cyrus:/mnt/d$ nm -D libart.so | grep LoadMethod
000000000016b168 T _ZN3art11ClassLinker10LoadMethodERKNS_7DexFileERKNS_13ClassAccessor6MethodENS_6HandleINS_6mirror5ClassEEEPNS_9ArtMethodE
得到 LoadMethod 方法的符号如下:
_ZN3art11ClassLinker10LoadMethodERKNS_7DexFileERKNS_13ClassAccessor6MethodENS_6HandleINS_6mirror5ClassEEEPNS_9ArtMethodE
hook ClassLinker::LoadMethod
ClassLinker::LoadMethod 函数原型如下:
void ClassLinker::LoadMethod(const DexFile& dex_file,const ClassAccessor::Method& method,Handle<mirror::Class> klass,ArtMethod* dst)
https://cs.android.com/android/platform/superproject/+/android10-release:art/runtime/class_linker.cc;l=3732
声明一个函数指针变量,存放原 ClassLinker::LoadMethod 函数,由于访问不了系统中的类,用 void * (通用指针)代替。
void *(*orig_LoadMethod)(void *, void *, void *, void *, void *);
注意:实际上有 5 个参数,第一个是 ClassLinker 对象。
自定义替换的函数
void *my_LoadMethod(void *linker, void *dex_file, void *method, void *klass_handle, void *dst) {...// 调用原始函数void *result = orig_LoadMethod(linker, dex_file, method, klass_handle, dst);...return result;
}
根据导出符号 hook ClassLinker::LoadMethod 函数
void *handle = shadowhook_hook_sym_name("libart.so","_ZN3art11ClassLinker10LoadMethodERKNS_7DexFileERKNS_13ClassAccessor6MethodENS_6HandleINS_6mirror5ClassEEEPNS_9ArtMethodE", // 要 hook 的符号名reinterpret_cast<void *>(my_LoadMethod),reinterpret_cast<void **>(&orig_LoadMethod)
);if (handle != nullptr) {LOGI("Successfully hooked LoadMethod");
} else {LOGW("Failed to hook LoadMethod");
}
访问 DexFile
如果你想访问 DexFile 和 ArtMethod 的内容(比如打印 base、size 或 method_index),你需要将这些 void* 转换回真实的类型指针。
我们可以根据 android 源码 在工程中增加 DexFile 的结构定义,通过 namespace 区分不同版本的 DexFile
https://cs.android.com/android/platform/superproject/+/android-9.0.0_r60:art/libdexfile/dex/dex_file.h;l=1042
https://cs.android.com/android/platform/superproject/+/android-15.0.0_r9:art/libdexfile/dex/dex_file.h;l=920
#ifndef CYURS_DEX_FILE_H
#define CYURS_DEX_FILE_H#include <stdint.h>
#include <string>namespace cyurs {namespace dex {struct Header {public:uint8_t magic_[8];uint32_t checksum_; // See also location_checksum_uint8_t signature_[20];uint32_t file_size_; // size of entire fileuint32_t header_size_; // offset to start of next sectionuint32_t endian_tag_;uint32_t link_size_; // unuseduint32_t link_off_; // unuseduint32_t map_off_; // unuseduint32_t string_ids_size_; // number of StringIdsuint32_t string_ids_off_; // file offset of StringIds arrayuint32_t type_ids_size_; // number of TypeIds, we don't support more than 65535uint32_t type_ids_off_; // file offset of TypeIds arrayuint32_t proto_ids_size_; // number of ProtoIds, we don't support more than 65535uint32_t proto_ids_off_; // file offset of ProtoIds arrayuint32_t field_ids_size_; // number of FieldIdsuint32_t field_ids_off_; // file offset of FieldIds arrayuint32_t method_ids_size_; // number of MethodIdsuint32_t method_ids_off_; // file offset of MethodIds arrayuint32_t class_defs_size_; // number of ClassDefsuint32_t class_defs_off_; // file offset of ClassDef arrayuint32_t data_size_; // unuseduint32_t data_off_; // unused};struct MapItem {uint16_t type_;uint16_t unused_;uint32_t size_;uint32_t offset_;};struct MapList {uint32_t size_;MapItem list_[1];};// Raw string_id_item.struct StringId {uint32_t string_data_off_; // offset in bytes from the base address};// Raw type_id_item.struct TypeId {uint32_t descriptor_idx_; // index into string_ids};// Raw field_id_item.struct FieldId {uint16_t class_idx_; // index into type_ids_ array for defining classuint16_t type_idx_; // index into type_ids_ array for field typeuint32_t name_idx_; // index into string_ids_ array for field name};// Raw method_id_item.struct MethodId {uint16_t class_idx_; // index into type_ids_ array for defining classuint16_t proto_idx_; // index into proto_ids_ array for method prototypeuint32_t name_idx_; // index into string_ids_ array for method name};// Raw proto_id_item.struct ProtoId {uint32_t shorty_idx_; // index into string_ids array for shorty descriptoruint16_t return_type_idx_; // index into type_ids array for return typeuint16_t pad_; // padding = 0uint32_t parameters_off_; // file offset to type_list for parameter types};// Raw class_def_item.struct ClassDef {public:uint32_t class_idx_; // index into type_ids_ array for this classuint32_t access_flags_;uint32_t superclass_idx_; // index into type_ids_ array for superclassuint32_t interfaces_off_; // file offset to TypeListuint32_t source_file_idx_; // index into string_ids_ for source file nameuint32_t annotations_off_; // file offset to annotations_directory_itemuint32_t class_data_off_; // file offset to class_data_itemuint32_t static_values_off_; // file offset to EncodedArray};// Raw code_item.struct CodeItem {public:uint16_t registers_size_; // the number of registers used by this code// (locals + parameters)uint16_t ins_size_; // the number of words of incoming arguments to the method// that this code is foruint16_t outs_size_; // the number of words of outgoing argument space required// by this code for method invocationuint16_t tries_size_; // the number of try_items for this instance. If non-zero,// then these appear as the tries array just after the// insns in this instance.uint32_t debug_info_off_; // file offset to debug info streamuint32_t insns_size_in_code_units_; // size of the insns array, in 2 byte code unitsuint16_t insns_[1]; // actual array of bytecode.};// Raw try_item.struct TryItem {uint32_t start_addr_;uint16_t insn_count_;uint16_t handler_off_;};struct ClassDataHeader {uint32_t static_fields_size_; // the number of static fieldsuint32_t instance_fields_size_; // the number of instance fieldsuint32_t direct_methods_size_; // the number of direct methodsuint32_t virtual_methods_size_; // the number of virtual methods};struct ClassDataField {public:uint32_t field_idx_delta_; // delta of index into the field_ids array for FieldIduint32_t access_flags_; // access flags for the fieldClassDataField(uint32_t field_idx_delta_, uint32_t access_flags_) :field_idx_delta_(field_idx_delta_), access_flags_(access_flags_) {}ClassDataField() : field_idx_delta_(0), access_flags_(0) {}};// A decoded version of the method of a class_data_itemstruct ClassDataMethod {public:uint32_t method_idx_delta_; // delta of index into the method_ids array for MethodIduint32_t access_flags_;uint32_t code_off_;ClassDataMethod(uint32_t method_idx_delta_, uint32_t access_flags_, uint32_t code_off_):method_idx_delta_(method_idx_delta_), access_flags_(access_flags_),code_off_(code_off_) {}ClassDataMethod() :method_idx_delta_(0), access_flags_(0), code_off_(0) {}};};namespace V21 {class DexFile {public://vtable pointervoid *_;// The base address of the memory mapping.const uint8_t *const begin_;// The size of the underlying memory allocation in bytes.const size_t size_;// Typically the dex file name when available, alternatively some identifying string.//// The ClassLinker will use this to match DexFiles the boot class// path to DexCache::GetLocation when loading from an image.const std::string location_;const uint32_t location_checksum_;// Manages the underlying memory allocation.std::unique_ptr<void *> mem_map_;// Points to the header section.const dex::Header *const header_;// Points to the base of the string identifier list.const dex::StringId *const string_ids_;// Points to the base of the type identifier list.const dex::TypeId *const type_ids_;// Points to the base of the field identifier list.const dex::FieldId *const field_ids_;// Points to the base of the method identifier list.const dex::MethodId *const method_ids_;// Points to the base of the prototype identifier list.const dex::ProtoId *const proto_ids_;// Points to the base of the class definition list.const dex::ClassDef *const class_defs_;};} //namespace V21namespace V28 {class DexFile {public://vtable pointervoid *_;// The base address of the memory mapping.const uint8_t *const begin_;// The size of the underlying memory allocation in bytes.const size_t size_;// The base address of the data section (same as Begin() for standard dex).const uint8_t *const data_begin_;// The size of the data section.const size_t data_size_;// Typically the dex file name when available, alternatively some identifying string.//// The ClassLinker will use this to match DexFiles the boot class// path to DexCache::GetLocation when loading from an image.const std::string location_;const uint32_t location_checksum_;// Points to the header section.const dex::Header *const header_;// Points to the base of the string identifier list.const dex::StringId *const string_ids_;// Points to the base of the type identifier list.const dex::TypeId *const type_ids_;// Points to the base of the field identifier list.const dex::FieldId *const field_ids_;// Points to the base of the method identifier list.const dex::MethodId *const method_ids_;// Points to the base of the prototype identifier list.const dex::ProtoId *const proto_ids_;// Points to the base of the class definition list.const dex::ClassDef *const class_defs_;};} //namespace V28namespace V35 {template <typename T>class ArrayRef {private:T* array_;size_t size_;};class DexFile {public:void *_;// The base address of the memory mapping.const uint8_t* begin_;size_t unused_size_ = 0; // Preserve layout for DRM (b/305203031).// Data memory range: Most dex offsets are relative to this memory range.// Standard dex: same as (begin_, size_).// Dex container: all dex files (starting from the first header).// Compact: shared data which is located after all non-shared data.//// This is different to the "data section" in the standard dex header.ArrayRef<const uint8_t> const data_;// The full absolute path to the dex file, if it was loaded from disk.//// Can also be a path to a multidex container (typically apk), followed by// DexFileLoader.kMultiDexSeparator (i.e. '!') and the file inside the// container.//// On host this may not be an absolute path.//// On device libnativeloader uses this to determine the location of the java// package or shared library, which decides where to load native libraries// from.//// The ClassLinker will use this to match DexFiles the boot class// path to DexCache::GetLocation when loading from an image.const std::string location_;uint32_t location_checksum_;// Points to the header section.const dex::Header* header_;// Points to the base of the string identifier list.const dex::StringId* string_ids_;// Points to the base of the type identifier list.const dex::TypeId* type_ids_;// Points to the base of the field identifier list.const dex::FieldId* field_ids_;// Points to the base of the method identifier list.const dex::MethodId* method_ids_;// Points to the base of the prototype identifier list.const dex::ProtoId* proto_ids_;// Points to the base of the class definition list.const dex::ClassDef* class_defs_;};} //namespace V35
};//namespace cyrus#endif //CYURS_DEX_FILE_H
参考:https://github.com/luoyesiqiu/dpt-shell/tree/main/shell/src/main/cpp/dex
在 so 初始化的时候获取当前系统的 api level
int g_sdkLevel = 0;__attribute__ ((constructor)) void init() {// api levelg_sdkLevel = android_get_device_api_level();
}
根据 api level 把 void* 类型的 dex_file 参数强转成对应版本的 DexFile,这样就可以访问 DexFile 的成员变量了
// DexFile
std::string location;
uint8_t *begin = nullptr;
uint64_t dexSize = 0;
if (g_sdkLevel >= 35) {auto *dexFileV35 = (V35::DexFile *) dex_file;location = dexFileV35->location_;begin = (uint8_t *) dexFileV35->begin_;dexSize = dexFileV35->header_->file_size_;
} else if (g_sdkLevel >= __ANDROID_API_P__) {auto *dexFileV28 = (V28::DexFile *) dex_file;location = dexFileV28->location_;begin = (uint8_t *) dexFileV28->begin_;dexSize = dexFileV28->size_ == 0 ? dexFileV28->header_->file_size_ : dexFileV28->size_;
} else {auto *dexFileV21 = (V21::DexFile *) dex_file;location = dexFileV21->location_;begin = (uint8_t *) dexFileV21->begin_;dexSize = dexFileV21->size_ == 0 ? dexFileV21->header_->file_size_ : dexFileV21->size_;
}// 打印 DexFile 信息
LOGI("[pid=%d][API=%d] enter my_LoadMethod:\n DexFile Base = %p\n DexFile Size = %zu bytes\n DexFile Location= %s",getpid(), g_sdkLevel, begin, dexSize, location.c_str());
访问 ArtMethod
ArtMethod 源码:
https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/runtime/art_method.h;l=744
其中,GcRoot<mirror::Class> declaring_class_; 是 ART(Android Runtime)内部定义的一个模板类,用于在 native 层持有对 Java 对象的 GC 安全引用。它是 ART 中替代裸指针的“托管指针”。
本质上它只是封装了一个指针,所以 arm64 中实际占用 8 字节,这里用 uint8_t 代替。
class ArtMethod {
public:// Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses".// The class we are a part of.uint8_t declaring_class_;// Access flags; low 16 bits are defined by spec.// Getting and setting this flag needs to be atomic when concurrency is// possible, e.g. after this method's class is linked. Such as when setting// verifier flags and single-implementation flag.uint32_t access_flags_;/* Dex file fields. The defining dex file is available via declaring_class_->dex_cache_ */// Offset to the CodeItem.uint32_t dex_code_item_offset_;// Index into method_ids of the dex file associated with this method.uint32_t dex_method_index_;/* End of dex file fields. */// Entry within a dispatch table for this method. For static/direct methods the index is into// the declaringClass.directMethods, for virtual methods the vtable and for interface methods the// ifTable.uint16_t method_index_;union {// Non-abstract methods: The hotness we measure for this method. Not atomic,// as we allow missing increments: if the method is hot, we will see it eventually.uint16_t hotness_count_;// Abstract methods: IMT index (bitwise negated) or zero if it was not cached.// The negation is needed to distinguish zero index and missing cached entry.uint16_t imt_index_;};
};
my_LoadMethod 函数实现
在 my_LoadMethod 函数中:
-
根据 g_sdkLevel 解析 DexFile 并打印其 begin、size 和 location;
-
调用原始 LoadMethod 以保证 ArtMethod 已初始化;
-
在不同 Android 版本下提取 ArtMethod 的 dex_method_index_ 和 dex_code_item_offset_;
void *my_LoadMethod(void *linker, void *dex_file, void *method, void *klass_handle, void *dst) {// DexFilestd::string location;uint8_t *begin = nullptr;uint64_t dexSize = 0;if (g_sdkLevel >= 35) {auto *dexFileV35 = (V35::DexFile *) dex_file;location = dexFileV35->location_;begin = (uint8_t *) dexFileV35->begin_;dexSize = dexFileV35->header_->file_size_;} else if (g_sdkLevel >= __ANDROID_API_P__) {auto *dexFileV28 = (V28::DexFile *) dex_file;location = dexFileV28->location_;begin = (uint8_t *) dexFileV28->begin_;dexSize = dexFileV28->size_ == 0 ? dexFileV28->header_->file_size_ : dexFileV28->size_;} else {auto *dexFileV21 = (V21::DexFile *) dex_file;location = dexFileV21->location_;begin = (uint8_t *) dexFileV21->begin_;dexSize = dexFileV21->size_ == 0 ? dexFileV21->header_->file_size_ : dexFileV21->size_;}// 打印 DexFile 信息LOGI("[pid=%d][API=%d] enter my_LoadMethod:\n DexFile Base = %p\n DexFile Size = %zu bytes\n DexFile Location= %s",getpid(), g_sdkLevel, begin, dexSize, location.c_str());// 调用原始函数,使 ArtMethod 数据填充完成void *result = orig_LoadMethod(linker, dex_file, method, klass_handle, dst);// ArtMethoduint32_t dex_code_item_offset_ = -1;uint32_t dex_method_index_;if (g_sdkLevel >= 31) {auto *dstV31 = (V31::ArtMethod *) dst;auto classAccessor_method = reinterpret_cast<Method &>(method);dex_code_item_offset_ = classAccessor_method.code_off_;dex_method_index_ = dstV31->dex_method_index_;} else {auto *dstV28 = (V28::ArtMethod *) dst;dex_code_item_offset_ = dstV28->dex_code_item_offset_;dex_method_index_ = dstV28->dex_method_index_;}// 打印 Method 信息LOGI("[pid=%d][API=%d] enter my_LoadMethod:\n"" ArtMethod.dex_code_item_offset_ = 0x%x\n"" ArtMethod.dex_method_index_ = %d",getpid(), g_sdkLevel, dex_code_item_offset_, dex_method_index_);return result;
}
测试
运行后输出如下,可以看到正常打印了 DexFile 和 ArtMethod 中的信息。
完整源码
开源地址:https://github.com/CYRUS-STUDIO/AndroidExample