Android逆向安全-无侵入找关键call之trace日志分析大法

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

标题

找关键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()
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

asmcvc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值