/frida-smali-trace

smali trace

Primary LanguageTypeScript

frida-smali-trace

通过frida hook追踪所有smali指令执行情况

在Pixel4 Android 11下运行【64位】APP进行测试,版本号RQ3A.210805.001.A1

效果示意

实现过程

使用

命令示意

frida -U -n LibChecker -l _agent.js -o trace.log

如果使用frida 15之前的版本,-n后面是包名

frida -U -n com.absinthe.libchecker -l _agent.js -o trace.log

准备工作

安装库,并进行编译测试

cd frida_scripts
npm install
npm run watch

如果只是简单使用,那么后面都不用管


在正式使用此脚本之前,需要先找到关键位置,以及几个关键寄存器

从手机中提取libart.so

adb pull /apex/com.android.art/lib64/libart.so

用IDA打开libart.so,让IDA反汇编

index.ts中的hook_mterp改为false

trace_interpreter_enrtyExecuteSwitchImplCpp日志打印中添加${offset}

开启frida-server,运行命令注入脚本,具体APP请自行选择

随便滑动、点击下APP,脚本会给出一个偏移位置,比如我这里是0x169d48

IDA中按G粘贴地址,回车跳转,就会进入到其中一个ExecuteSwitchImplCpp实现

F5查看伪代码

往下翻,找到第一个while处,按TAB键跳转到汇编窗口

然后检查特征,关键特征是和0xFF相与,以及BR指令

特征确定后,那么记录下此处的偏移,比如我这里是0x169EB4

0xFF相与的是opcode,而opcode是从inst(Instruction)取的

根据这个规则,可以推测图中X28opcodeX26inst

现在回到函数开头,将a1命名为ctx,其偏移16也就是两个指针大小(64位下就是2 * 8 = 16)的取值就是shadow_frame,那么对应寄存器在后续也是shadow_frame,我这里是x19

现在将index.ts中的trace_interpreter_switch注释取消掉,把上面分析得到的0x169EB4x19x26对应修改

// 参数二是 while 循环中 inst 赋值给 next 的偏移
// 参数三是存 shadow_frame 的寄存器
// 参数四是存 inst(Instruction) 的寄存器
trace_interpreter_switch(libart, 0x169EB4, 'x19', 'x26');

然后将hook_switchhook_mterp改为false,编译新的js,进行测试

如果没有问题,现在IDA搜索ExecuteMterpImpl,跳转到对应函数,按F5查看伪代码,应该长这样

第一个参数是thread

TAB查看汇编代码,看看x0给哪个寄存器了,我这里是x22,记录下来,那么x22就是thread

然后直接在汇编窗口往下翻,找一个符号是mterp_op_开头的代码(除了mterp_op_nop

然后找一个和0xFF相与的寄存器,再往几行前看下是哪个寄存器读取来的,比如我这和0xFF相与的是x23x23是由x20读取来的,那么x20就是inst

现在将index.ts中的trace_interpreter_mterp_op注释取消掉,把上面分析得到的x22x20对应修改

// 参数二是存 thread 的寄存器
// 参数三是存 inst(Instruction) 的寄存器
trace_interpreter_mterp_op(libart, "x22", "x20");

编译新的js,进行测试

如果顺利,那么现在能够trace 64位APP的smali执行详情了

如果检查找后面的参数太麻烦,也可以注释掉trace_interpreter_switchtrace_interpreter_mterp_op

hook_switchhook_mterp改为true,这样只会做简单的trace


如果通过静态分析的方法无法确定寄存器,可以自行修改脚本,打印全部寄存器情况

比如要检查switchwhile处的shadow_frame是哪个寄存器,修改代码如下

// main
let hook_switch = true;
let hook_mterp = false;
trace_interpreter_enrty(libart, hook_switch, hook_mterp);
// trace_interpreter_enrty ExecuteSwitchImplCpp 日志添加一个 ${shadow_frame}
log(`[switch] ${Process.getCurrentThreadId()} ${shadow_frame} ${offset} ${method_name} ${inst_str}`);
// trace_interpreter_switch
log(`[${id}] [switch] ${JSON.stringify(ctx)}`);

trace_interpreter_switch只打印寄存器信息日志

这样也能定位出shadow_frame存在哪个寄存器,确定后再修改trace_interpreter_switch具体参数,还原代码

类似的,将关键代码修改如下,测试查看thread是在哪个寄存器

// main
let hook_switch = false;
let hook_mterp = true;
trace_interpreter_enrty(libart, hook_switch, hook_mterp);
// trace_interpreter_enrty ExecuteMterpImpl 日志添加一个 ${args[0]}
log(`[mterp] ${Process.getCurrentThreadId()} ${args[0]} ${method_name} ${inst_str}`);
// trace_interpreter_mterp_op 只打印一个指令的 避免过多输出
if (symbol.name != "mterp_op_move") continue;
// hook_mterp_op
log(`[${id}] [mterp] ${JSON.stringify(ctx)}`);


还有一个问题,需要确定threadmanaged_stack的偏移

但是managed_stackThread中的偏移就比较麻烦了,主要是因为Thread比较复杂

经过一番查阅后,发现在art::StackVisitor::WalkStack里面有调用GetManagedStack()

  • void art::StackVisitor::WalkStack<(art::StackVisitor::CountTransitions)0>(bool)

并且这个函数的符号还在,于是结合源代码,和IDA对比便能知道GetManagedStack()实际的偏移

注意这个偏移每个版本、手机的可能都不同,比如我这里是184也就是0xB8

确定偏移之后记得修改get_shadow_frame_ptr_by_thread_ptr里面计算managed_stack的偏移


注意,由于hook指令详细情况的位置里入口可能太近,除了上面的测试过程,其他时候

  • 使用了 trace_interpreter_switch 则 hook_switch 应当为 false
  • 使用了 trace_interpreter_mterp_op 则 hook_mterp 应当为 false

如果感兴趣详细实现过程,请查看纯frida实现smali追踪