写点什么

Trace 大盘点

作者:奋飞安全
  • 2022 年 3 月 16 日
  • 本文字数:4845 字

    阅读完需:约 16 分钟

Trace大盘点

一、目标

李老板: 奋飞呀,最近老听别人说 Trace 一下,啥是 Trace 呀?


奋飞:老板,先把上次的加班费结算一下。



Trace 就是在更高抽象层次上去追踪程序的运行流程。

二、JNI_Trace

jnitrace

在 Android 下混饭吃,首推的就是 jnitrace


https://github.com/chame1eon/jnitrace


老牌,经典,信息全,携带方便


jnitrace -l libnative-lib.so com.example.myapplication


TIP: -l 是监控的 so 名称, -l *就是监控所有 so 了。


缺点就是,hook 的太全,欺负欺负小厂的还行,大厂的反调试一上,分分钟教你做人。


这里还有个优化的文章https://blog.seeflower.dev/archives/82/

jnitrace-engine

jnitrace-engien 是基于 jnitrace 裁剪出来的库,你可以取其所需,只 hook 你关心的函数。


https://github.com/chame1eon/jnitrace-engine


编译的时候需要 nodejs,然后有一段时间没有更新了,导致某一个 frida 的库版本比较老,反正我是没有编译过去,有搞定的老铁留个言。

jtrace

方便定制的 Trace 才是好工具,jtrace 感觉就比较帅了,信息齐全,直接在_agent.js 或者_agent_stable.js 里面加自己的逻辑就行。


https://github.com/anjia0532/jtrace


比较推荐的玩法就是:不要一上来就 hook_all_jni();, 而是指定分析某段函数


Interceptor.attach(targetSo.add(0x56582+1),{    onEnter:function(args){
console.log(" ================ jni Onload"); hook_all_jni(); }});
复制代码


需要它的时候,就 hook,不需要的时候注释掉。

hook_art.js

有了新朋友,也不忘老朋友,yang 神 hook_art.js 依然是可以提供 jni trace 的。


https://github.com/lasting-yang/frida_hook_libart


你可以灵活的增加你需要 hook 的函数


var addrCallStaticObjectMethodV = null;...
} else if (symbol.name.indexOf("CallStaticObjectMethodV") >= 0) { addrCallStaticObjectMethodV = symbol.address; console.log("CallStaticObjectMethodV is at ", symbol.address, symbol.name);}
...
if (addrCallStaticObjectMethodV != null) { Interceptor.attach(addrCallStaticObjectMethodV, { onEnter: function (args) { if (args[3] != null) { // var argsInt = parseInt(args[4]); console.log("[CallStaticObjectMethodV] >>> args[3] = " + args[3]); }else if (args[4] != null) { console.log("[CallStaticObjectMethodV] >>> args[4] = " + args[4]); }else if (args[2] != null) { console.log("[CallStaticObjectMethodV] >>> args[2] = " + args[2]); }else{ console.log("[CallStaticObjectMethodV]"); }

}, onLeave: function (retval) { if(retval != null){ console.log("[CallStaticObjectMethodV] retval= " + JSON.stringify(retval)); } } });}
复制代码

JNI-Frida-Hook

JNI-Frida-Hook 也是个不错的选择,这个哥哥把函数名都给你定义好了,哪里想看,Hook 哪里。


https://github.com/Areizen/JNI-Frida-Hook

art-tracer

https://github.com/oleavr/art-tracer


art-tracer 说实话,我还没怎么用过,老铁们可以试试


小小的总结一下 JNI_Trace, jnitrace 能跑通的,首推。量大管饱,然后就是 jtrace ,定制方便,信息全。都崩溃的你怀疑人生的时候,老老实实用 hook_art.js ,能 hook 就行。

三、Native_Trace

trace_natives

做技术,要相信一见钟情,第一次 Native Trace 用的就是他,层次分明,信息全,结合 frida-trace 使用,很奇妙的想法。


你不会还在用 IDA 7.0 吧?不会吧? 好巧,我也是,把这几行改改就行


so_path, so_name = getSoPathAndName()# search_result = [f"-a '{so_name}!{offset}'" for offset in search_result]search_result = ["-a '{}!{}'".format(so_name,offset) for offset in search_result]search_result = " ".join(search_result)
script_name = so_name.split(".")[0] + "_" + str(int(time.time())) +".txt"
save_path = os.path.join(so_path, script_name)# with open(save_path, "w", encoding="utf-8")as F:with open(save_path, "w")as F: F.write(search_result)
print("使用方法如下:")# print(f"frida-trace -UF -O {save_path}")print("frida-trace -UF -O {} !".format(save_path))
复制代码


你肯定要问,一见钟情有啥缺点没有?


和 jnitrace 一样,hook 的太多,很容易崩掉。大厂 so 木办法玩。

findhash

findhash 也是 ida 插件,他本来是为了检测加解密函数用的,批量 hook 一大堆可疑的加解密函数,但是它提供了一个超帅的批量 hook 模板,我们只要按照格式生成 hook_suspected_function 函数中的 const funcs 数组,就可以把他当成一个 Native Trace 库来用了。


https://github.com/Pr0214/findhash


那 const funcs 数组如何生成呢? 这个 改造下之前的 trace_natives.py 是可以的,不过我还没搞。


我改了下龙哥的例子,搞了个 Unidbg 的


public static Map<Integer, Integer> subTraceMap = new HashMap<Integer, Integer>();public static Map<Integer, Integer> calcMap = new HashMap<Integer, Integer>();
public static void PrintHookSubInfo(){ //* System.out.println("subTrace len = " + subTraceMap.size()); String strOut = ""; for (Map.Entry<Integer, Integer> entry : subTraceMap.entrySet()) { int iShow = entry.getKey(); // 为了和unidbg显示一致这里处理下 if(iShow % 2 != 0){ iShow = iShow -1; } strOut = strOut + " ,['sub_" + Integer.toHexString(iShow ) + "','0x" + Integer.toHexString((int)entry.getKey() ) + "']"; } System.out.println(strOut); // */}

private void traceFn(final long baseAddr, final long starAddr, final long endAddr) { // 这个代码是没法trace 导入函数的 PrintStream traceStream = null; try { // 保存文件 String traceFile = "/Users/fenfei/Desktop/money/ffFunctions.txt"; traceStream = new PrintStream(new FileOutputStream(traceFile), true); } catch (FileNotFoundException e) { e.printStackTrace(); }
final PrintStream finalTraceStream = traceStream;
emulator.getBackend().hook_add_new(new BlockHook() { @Override public void hookBlock(Backend backend, long address, int size, Object user){ // 块太小 影响 ida的 hook , 这里也可以改成 8 10 之类的 if (size > 20) { Instruction[] insns = emulator.disassemble(address, 4, 0); // 只搞函数 就开启这个 // if (insns[0].getMnemonic().equals("push")) { int level = emulator.getUnwinder().depth(); assert finalTraceStream != null; for (int i = 0; i < level; i++) { finalTraceStream.print(" | "); }
// 为了和 frida-trace 对应,所以加 1 // Thumb 模式切换导致 hook失败 // Frida-trace显示函数地址的方式是“sub_Hook地址”,因为Thumb模式下要+1的缘故,所以Frida trace中“sub_123C”在IDA中显示是“sub_123B”,对照ida分析时要注意一下。 // finalTraceStream.println(" " + "sub_" + Integer.toHexString((int) (address - baseAddr) +1 ) + " ");
// 给 traceNative 函数用的, 显示的时候就不加 1 // finalTraceStream.println(" " + "sub_" + Integer.toHexString((int) (address - baseAddr) ) + " ");
int iSize = insns[0].getSize(); int iUseAddr = 0; if( iSize == 4){ // ARM模式 4字节 iUseAddr = (int) (address - baseAddr); }else { // THUMB模式 2 字节 ,hook的时候 + 1 , iUseAddr = (int) (address - baseAddr) + 1;
}

if(calcMap.containsKey(iUseAddr)){ int iValue = calcMap.get(iUseAddr); calcMap.put(iUseAddr,iValue + 1);
// 4次以上的调用就不显示了, 也不用Native Trace了 if(iValue > 3){ subTraceMap.remove(iUseAddr); }else{ System.out.println(" " + "sub_" + Integer.toHexString((int) (address - baseAddr) ) + " "); finalTraceStream.println(" " + "sub_" + Integer.toHexString((int) (address - baseAddr) ) + " "); }
}else{ calcMap.put(iUseAddr,1); subTraceMap.put(iUseAddr,1);
System.out.println(" " + "sub_" + Integer.toHexString((int) (address - baseAddr) ) + " "); finalTraceStream.println(" " + "sub_" + Integer.toHexString((int) (address - baseAddr) ) + " "); }
} } }
@Override public void onAttach(UnHook unHook){
}
@Override public void detach(){
}
}, starAddr, endAddr, 0);}
// unidbg/unidbg-api/src/main/java/com/github/unidbg/unwind/Unwinder.javapublic final int depth(){ int count = 0; Frame frame = null; while((frame = unw_step(emulator, frame)) != null) { if(frame.isFinish()){ return count; } count++; } return count;}
复制代码


Native Trace 有这两个就差不多了,崩溃的话就少 hook 点,把引发崩溃的 hook 点删除。

Stalker

大胡子还搞了个 Stalker,也是很有野心的,但是对于国内 App 的内卷现状,效果只能说差强人意吧。


https://frida.re/docs/stalker/

fridaMemoryAccessTrace

没有内存断点的 ida 没有灵魂。一个替代方案是从 findhash 里面扣出来的。


MemoryAccessMonitor.enable({    base: this.addr,    size: 0x8 // qword}, {    onAccess: function (details) {        console.log("\n");        console.log("B >>>监控到内存访问\n");        console.log(details.from);        console.log("B >>> 访问来自:"+details.from.sub(targetSo)+"(可能有误差)");    }});
复制代码


缺点是 只会触发一次


plus 版本就是 fridaMemoryAccessTrace 了,你值得拥有。https://github.com/asmjmp0/fridaMemoryAccessTrace

四、总结

不要去期望完美的 Trace 工具,适合你的,你自己能动手调校的才是好工具。



决定战争胜负的是人,而不是一两件新式武器。这一论断永远都不过时。

发布于: 刚刚阅读数: 2
用户头像

奋飞安全

关注

独立安全研究员。 公众号: 奋飞安全 2022.02.21 加入

少习文,经史子集略有涉猎;北上学艺,A-Z语言敢说略懂;初入江湖,混入外企,乐不思蜀;后为人父,发愤图强,略有建树;而今重新出发,万事随缘。

评论

发布
暂无评论
Trace大盘点_奋飞安全_InfoQ写作平台