版权归作者所有,如有转发,请注明文章出处: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 version
2. Set a higher source/target version
3. 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.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}
# 链接 shadowhook
shadowhook::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/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;
}
// 调用原始 execve
return 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.hook
import com.bytedance.shadowhook.ShadowHook
import com.bytedance.shadowhook.ShadowHook.ConfigBuilder
object CyrusStudioHook {
init {
System.loadLibrary("cyrus_studio_hook") // 加载 native 实现
}
fun init(){
// 初始化 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()
)
}
@JvmStatic
external 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)
声明一个函数指针变量,存放原 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
#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 file
uint32_t header_size_; // offset to start of next section
uint32_t endian_tag_;
uint32_t link_size_; // unused
uint32_t link_off_; // unused
uint32_t map_off_; // unused
uint32_t string_ids_size_; // number of StringIds
uint32_t string_ids_off_; // file offset of StringIds array
uint32_t type_ids_size_; // number of TypeIds, we don't support more than 65535
uint32_t type_ids_off_; // file offset of TypeIds array
uint32_t proto_ids_size_; // number of ProtoIds, we don't support more than 65535
uint32_t proto_ids_off_; // file offset of ProtoIds array
uint32_t field_ids_size_; // number of FieldIds
uint32_t field_ids_off_; // file offset of FieldIds array
uint32_t method_ids_size_; // number of MethodIds
uint32_t method_ids_off_; // file offset of MethodIds array
uint32_t class_defs_size_; // number of ClassDefs
uint32_t class_defs_off_; // file offset of ClassDef array
uint32_t data_size_; // unused
uint32_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 class
uint16_t type_idx_; // index into type_ids_ array for field type
uint32_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 class
uint16_t proto_idx_; // index into proto_ids_ array for method prototype
uint32_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 descriptor
uint16_t return_type_idx_; // index into type_ids array for return type
uint16_t pad_; // padding = 0
uint32_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 class
uint32_t access_flags_;
uint32_t superclass_idx_; // index into type_ids_ array for superclass
uint32_t interfaces_off_; // file offset to TypeList
uint32_t source_file_idx_; // index into string_ids_ for source file name
uint32_t annotations_off_; // file offset to annotations_directory_item
uint32_t class_data_off_; // file offset to class_data_item
uint32_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 for
uint16_t outs_size_; // the number of words of outgoing argument space required
// by this code for method invocation
uint16_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 stream
uint32_t insns_size_in_code_units_; // size of the insns array, in 2 byte code units
uint16_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 fields
uint32_t instance_fields_size_; // the number of instance fields
uint32_t direct_methods_size_; // the number of direct methods
uint32_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 FieldId
uint32_t access_flags_; // access flags for the field
ClassDataField(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_item
struct ClassDataMethod {
public:
uint32_t method_idx_delta_; // delta of index into the method_ids array for MethodId
uint32_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 pointer
void *_;
// 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 V21
namespace V28 {
class DexFile {
public:
//vtable pointer
void *_;
// 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 V28
namespace 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 level
g_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 源码:
其中,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) {
// 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 数据填充完成
void *result = orig_LoadMethod(linker, dex_file, method, klass_handle, dst);
// ArtMethod
uint32_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 中的信息。