追踪Android方法调用2
字数 1486 2025-08-29 22:41:24
Android方法调用追踪技术详解
1. 前言
本文是《追踪Android方法调用》系列的第二部分,重点介绍如何通过Frida工具hook关键ART函数来追踪Android应用中的各种方法调用关系,包括:
- Java->Java调用
- Java->JNI调用
- JNI->Java调用
- JNI->JNI调用
2. 核心原理分析
所有方法调用最终都会进入ArtMethod::Invoke函数,但直接hook该函数存在局限性。更深入的追踪需要关注解释执行逻辑中的关键函数:
ArtInterpreterToInterpreterBridge:处理Java->Java调用ArtInterpreterToCompiledCodeBridge:处理Java->JNI调用
3. hook ArtMethod::Invoke
3.1 函数签名
void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result, const char* shorty)
3.2 参数解析
通过shorty参数可以解析传入参数的类型和值:
function parse_arg_arrays(arg_arrays: NativePointer, shorty: string) {
let result:any = {};
let count = 0
let j = 0;
let tmp_shorty = shorty.slice(1);
for (let i of tmp_shorty) {
switch (i) {
case 'Z': case 'B': case 'C': case 'S': case 'I': case 'F': case 'L':
result[`arg${j}`] = "0x" + arg_arrays.add(count).readU32().toString(16)
count += 4; j += 1; break;
case 'D': case 'J':
result[`arg${j}`] = "0x" + arg_arrays.add(count).readU64().toString(16)
count += 8; j += 1; break;
case 'V': break;
default: console.log("error shorty:", i); break
}
}
return result;
}
3.3 获取方法名
通过ArtMethod::PrettyMethod获取被调用方法名:
function find_func(so_name: string, export_name: string, ret_type: any, args_type: any) {
let addr = Module.findExportByName(so_name, export_name);
let called_name = null;
if (addr) {
called_name = new NativeFunction(addr, ret_type, args_type);
}
return called_name;
}
let pretty_method_func: any = find_func("libart.so", "_ZN3art9ArtMethod12PrettyMethodEb",
["pointer", "pointer", "pointer"], ["pointer", "bool"]);
3.4 完整hook代码
export function hook_ArtMethod_Invoke() {
let module = Process.findModuleByName("libart.so");
let symbols = module ? module.enumerateSymbols() : [];
let addr_artmethod_invoke = null;
// 查找ArtMethod::Invoke符号
for (let symbol of symbols) {
if (symbol.name.indexOf("_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc") >= 0) {
addr_artmethod_invoke = symbol.address;
break;
}
}
if (addr_artmethod_invoke) {
Interceptor.attach(addr_artmethod_invoke, {
onEnter: function (args) {
this.artmethod = args[0];
this.tid = args[1].add(0x10).readU32();
this.arg_arrays = args[2];
this.shorty = args[5].readCString();
let func_name = readStdString(pretty_method_func(ptr(this.artmethod), 1));
this.hooked = 0;
if (func_name?.includes("com.example")) {
this.hooked = 1;
let artmethod_args = parse_arg_arrays(this.arg_arrays, this.shorty);
console.log("[+] [" + this.threadId + "] ArtMethod::Invoke onEnter:",
ptr(this.artmethod), func_name);
if (Object.keys(artmethod_args).length > 0) {
console.log("----------args----------\n",
JSON.stringify(artmethod_args), "\n----------end-----------");
}
}
},
onLeave: function (retval) {
// 可在此处处理返回值
}
});
}
}
3.5 局限性
- 无法捕获Java->Java的直接调用(绕过ArtMethod::Invoke)
- 无法获取调用者(caller)信息
4. 处理Java层调用追踪
4.1 ShadowFrame结构分析
ShadowFrame内存布局:
ShadowFrame* link_; // 指向调用者frame
ArtMethod* method_; // 当前方法
JValue* result_register_; // 结果寄存器
const uint16_t* dex_pc_ptr_; // dex pc指针
const uint16_t* dex_instructions_; // dex指令
LockCountData lock_count_data_; // 锁计数数据(指针大小)
const uint32_t number_of_vregs_; // 虚拟寄存器数量
uint32_t dex_pc_; // dex pc值
int16_t cached_hotness_countdown_;
int16_t hotness_countdown_; // 热度计数
uint32_t frame_flags_; // 帧标志
uint32_t vregs_[0]; // 虚拟寄存器数组
4.2 参数解析关键点
vregs_包含参数和局部变量- 实际参数数量由CodeItem中的
ins_size_决定 - 参数位置:
vregs[number_of_vregs_ - ins_size : number_of_vregs]
4.3 hook ArtInterpreterToInterpreterBridge
用于追踪Java->Java调用:
void ArtInterpreterToInterpreterBridge(Thread* self,
const CodeItemDataAccessor& accessor,
ShadowFrame* shadow_frame,
JValue* result);
通过shadow_frame->link_可以获取调用者信息。
4.4 hook ArtInterpreterToCompiledCodeBridge
用于追踪Java->JNI调用:
void ArtInterpreterToCompiledCodeBridge(Thread* self,
ArtMethod* caller,
ShadowFrame* shadow_frame,
uint16_t arg_offset,
JValue* result);
该函数直接提供了caller参数,无需通过link_解析。
5. 处理JNI层调用追踪
5.1 挑战
ArtInterpreterToXxxBridge无法监测JNI层发起的调用- JNI调用会通过
art_quick_invoke_stub和art_quick_invoke_static_stub
5.2 解决方案
hook InvokeWithArgArray函数,其签名:
void InvokeWithArgArray(const ScopedObjectAccessAlreadyRunnable& soa,
ArtMethod* method,
ArgArray* arg_array,
JValue* result,
const char* shorty)
可以使用类似ArtMethod::Invoke的参数解析方法来处理。
6. 读取内存字符串
当参数类型为java.lang.String时,需要特殊处理:
// 在parse_arg_arrays函数中添加字符串处理
case 'L':
if (func_name.includes("java.lang.String")) {
let str_ptr = arg_arrays.add(count).readPointer();
if (!str_ptr.isNull()) {
let is_compressed = str_ptr.add(8).readU32() & 1;
let count = str_ptr.add(12).readU32();
let value_ptr = str_ptr.add(16);
if (is_compressed) {
result[`arg${j}`] = value_ptr.readUtf8String(count);
} else {
result[`arg${j}`] = value_ptr.readUtf16String(count);
}
}
}
count += 4; j += 1; break;
7. 完整实现
完整代码可参考GitHub仓库:
https://github.com/youncyb/trace_android_call
8. 参考资源
- 纯frida实现smali追踪
- 使用Frida打印Java类函数调用关系
- [libc++'s implementation of std::string](libc++'s implementation of std::string)
9. 总结
本文详细介绍了通过Frida hook ART关键函数来追踪Android方法调用的技术,包括:
- 参数解析技术
- ShadowFrame结构分析
- Java和JNI调用的不同处理方式
- 内存字符串读取方法
这套技术可以用于Android应用逆向分析、安全审计和性能优化等多个领域。