标题
找关键call是逆向的基本技能和分析目标,找到关键call后便可以进一步利用。在安卓App的逆向分析中,人肉逆向分析虽说不难,但是繁琐,特别是现在App体积动辄几十MB甚至几百MB,反编译出的jar或者smali文件相当多,找关键call无疑是大海捞针。
那么有什么方法可以快速找关键call呢?
之前介绍过两个方法:
一个是插桩日志分析法,一个是借助加固的方法。
插桩日志分析法,理解起来比较简单,就是反编译App文件,对smali代码里的每个函数入口都插入一条日志输出语句,最后回编译出新的App包,安装运行后通过捕获并分析日志找出关键call。
该方法理解起来比较简单,早期实现门槛较低,可以通过编写工具来自动插桩。但是随着App体积变得越来越大,保护强度的提高,这种操作方法的工作量也变得很大,特别是分析体积较大的App时并不是那么方便,而且一旦App自身有签名校验、文件完整性校验等保护逻辑,这种方法就会失效。而现实是,现在App的保护强度的确在增加,有的甚至会加固保护,因此这种方法的限制性也越来越多。该方法是一个“笨”方法,有时在没有更好的方法的时候,也是一个选择。
借助加固的方法,这个门槛较高,主要是利用App加固技术来实现找关键call的目的。可以在加固的时候对每个函数进行native化处理,接管函数调用,接管函数负责打印出函数签名信息,加固后的App在运行时便会输出日志,通过分析日志找出关键call。该方法实现起来不是那么容易,笔者早期在dalvik模式下通过函数签名过滤实现过一个找新版微信骰子的关键call,只是证明该方法是可行的。在具体实践中,也没有人会对所有的函数进行加固处理,这会拖慢App的运行效率,而且该方法也有弊端:实现门槛过高;对App有侵入性,App自身有签名校验、文件完整性校验等保护逻辑,这种方法就会失效。
我们现在先对比下两种方法:
| 插桩日志分析法 | 借助加固 | |
|---|---|---|
| 实现难度 | 低 | 高 |
| 本质 | 日志分析 | 日志分析或签名过滤 |
| 限制条件 | App无校验无保护,可反编译且可回编译 | App无校验无保护 |
| 侵入性 | 高 | 略高 |
总体可以看出,这两种方法对App都有侵入性,且有一定限制条件,但其本质都是通过函数调用日志的分析方法。那么我们就会想,有没有其他方法可以减小对App的侵入性来做分析呢?例如安卓系统本身有没有这么一套机制,使得App在运行时的每个函数调用都可以有一次拦截或记录?
这个问题很早的时候就考虑过,且在2016年的时候有过类似的笔记思考:如何快速定位Android APP中的关键函数?_android,app_大星星的专栏-优快云博客
这个问题一直拖了几年,后来2019年的时候在GitHub上翻到这个项目:AppMethodOrder,号称是:“一个能让你了解所有函数调用顺序以及函数耗时的Android库(无需侵入式代码)”,看其介绍感觉就是我想要的效果。
但是该项目没有明确说明可以用来做逆向分析,但通过与作者沟通知道,这个思路确实是可以用在逆向领域的,但是怎么操作作者也不愿透露。但是有了这个确实可行的信息之后,便开始自行摸索了。
新方法探索
第一步要完成的工作是:对三方App(非自己开发的App)生成trace文件。如果项目是自己开发的,是可以通过AndroidStudio的调试功能来监控App性能分析,进而导出trace文件,这个不是难事。难就难在我们逆向App的时候,用的都是他人的App,不是自己的项目,这个要怎么生成trace文件呢?
笔者试了很多手机和模拟器,很多都不支持对其他进程的监控,只有雷电模拟器是可以的。真机可能需要root吧,但是没有找到合适的root机,这个就没验证了。但是雷电模拟器的安卓系统版本略低,在分析的时候可能也会有一些问题。这个profiler监控在安卓9.0、10.0系统上会有更好的支持。

能跑起来就不错,试试UI上点击 5 次之后的trace:

摸索着跑了两次trace,一次UI上点击了5次,一次UI上点击了7次,最后根据函数调用次数过滤,最后求交集,得出以下函数:
com.tencent.mm.view.SmileySubGrid$b.run()V
android.widget.AbsListView.performItemClick(Landroid/view/View;IJ)Z
android.widget.AdapterView.performItemClick(Landroid/view/View;IJ)Z
com.tencent.mm.view.SmileyGrid$1.onItemClick(Landroid/widget/AdapterView;Landroid/view/View;IJ)V
com.tencent.mm.view.SmileyGrid.a(Lcom/tencent/mm/view/SmileyGrid;Lcom/tencent/mm/storage/emotion/EmojiInfo;)V
com.tencent.mm.cc.a.n(Lcom/tencent/mm/storage/emotion/EmojiInfo;)Lcom/tencent/mm/storage/emotion/EmojiInfo;
com.tencent.mm.plugin.emoji.e.h.n(Lcom/tencent/mm/storage/emotion/EmojiInfo;)Lcom/tencent/mm/storage/emotion/EmojiInfo;
com.tencent.mm.ui.chatting.v.B(Lcom/tencent/mm/storage/emotion/EmojiInfo;)V
com.tencent.mm.plugin.emoji.e.h.a(Ljava/lang/String;Lcom/tencent/mm/storage/emotion/EmojiInfo;Lcom/tencent/mm/storage/bi;)V
com.tencent.mm.plugin.emoji.model.c.a(Ljava/lang/String;Lcom/tencent/mm/storage/emotion/EmojiInfo;Lcom/tencent/mm/storage/bi;)V
com.tencent.mm.model.bf.qp(Ljava/lang/String;)J
com.tencent.mm.plugin.emoji.f.r.doScene(Lcom/tencent/mm/network/e;Lcom/tencent/mm/ak/f;)I
com.tencent.mm.plugin.emoji.f.k.doScene(Lcom/tencent/mm/network/e;Lcom/tencent/mm/ak/f;)I
com.tencent.mm.ak.s$2.run()V
com.tencent.mm.plugin.emoji.f.r.onGYNetEnd(IIILjava/lang/String;Lcom/tencent/mm/network/q;[B)V
com.tencent.mm.storage.bj.ab(Lcom/tencent/mm/storage/bi;)I
com.tencent.mm.plugin.emoji.model.c.onSceneEnd(IILjava/lang/String;Lcom/tencent/mm/ak/m;)V
com.tencent.mm.model.u.a(Ljava/lang/String;Landroid/database/Cursor;)I
com.tencent.mm.plugin.fts.b.a$1.a(ILcom/tencent/mm/sdk/e/n;Ljava/lang/Object;)V
事实上里面并没有我们想要的关键call,问题出在哪里?
方法不会错,出错的只可能是工具。中间经过不断试错判断,才知道原来是trace文件捕获的有问题。
正确trace
默认的“Sample Java Methods”和“Trace Java Methods”不能满足需求:“Sample Java Methods”是随机采样,统计的不全。“Trace Java Methods”虽说是全部采集,但是默认是8MB大小,也是不够的。
需要创建自定义的捕获。勾选“Trace Java Methods”,File size limit这里选择大一点,默认是8MB是远远不够的,也不要太大,建议小于200MB吧,我这里设置了198MB。

一共统计了两次:
- 第一次投 1 次骰子。
- 第二次投 3 次骰子。

日志处理过程:

相关命令
- trace文件文本化:dmtracedump -ho xxx.trace > xxx.txt
- trace文件文本化且按条件过滤:dmtracedump -ho xxx.trace | grep “.* ent .*”
- 只保留目标包名的函数:grep -o ‘com.tencent.*’ xxx.txt > yyy.txt
- 只保留函数签名:sawk ‘{print ($1) ($2)}’ xxx.txt > yyy.txt
- 去重:sort xxx.txt | uniq -c | sort > yyy.txt
- 取两者交集:cat a.txt b.txt | sort | uniq -d
因为去重的那个环节处理后,函数的调用顺序被打乱了,函数的调用顺序在日志分析中非常重要,往往能看出核心函数的大概范围。写了个脚本重新把顺序理了一下,简单的处理方法是:从log日志中查询某函数字符串出现的位置,最后按字符串位置排下序列:
def find_pos_sort(file_name, file_name2):
ret = ''
d = {}
s = read(file_name2, False)
lines = None
with open(file_name, 'r') as file:
lines = file.readlines()
for line in lines:
pos = s.find(line)
d[line] = pos
l = sorted(d.items()
逆向分析之trace法

本文介绍了一种基于trace文件的逆向分析方法,通过分析Android App运行时的函数调用顺序和频率,定位关键call。该方法无需修改App源码,侵入性低,适用于各种加固或未加固的App。
最低0.47元/天 解锁文章
1440





