IDA动态调试破解AliCrackme与反调试对抗
Posted Tr0e
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了IDA动态调试破解AliCrackme与反调试对抗相关的知识,希望对你有一定的参考价值。
前言
在前面的文章中 IDA动态调试破解EXE文件与分析APK流程 介绍了 IDA 对 APK 进行动态调试分析的简单流程,然而实际上很多 APP 为了防止被动态调试分析,经常会做一些反调试的防护措施。本文将通过 2014 年阿里安全挑战赛的第二题 AliCrackme_2(APK下载地址),来进一步学习 APK so 文件的动态调试和反调试技术的对抗。
APK破解(上)
显然,上面的程序希望我们输入正确的验证码/密码,故破解的目标就是获取到正确的密码(即 Flag 值)。
1.1 静态分析
1、先拖进 jadx 查看其验证码校验逻辑,发现程序调用了 libcrackme.so 的 securityCheck() 函数对输入密码进行校验,如果正确则启动一个新的活动,否则则提示验证码校验错误:
2、将 libcrackme.so 文件导出并拖进 IDA 进行静态分析:
3、按 F5 转换成伪代码:
发觉 v6 变量 off_628C 可能有东西,双击跟进看看:
4、进行 JNI 函数方法名还原,可以较清晰地分析出程序的逻辑:
C 伪代码的解释我已添加在备注中,可以看出接下来的目标就是通过 IDA 动态调试来获取真实的 v6 变量(真实的密码)的值。
1.2 动态分析
1、在 securityCheck() 函数如下位置设置断点:
2、在真机 Nexus5 中安装目标 APK,运行 IDA 调试服务并转发端口:
3、设置 IDA Debugger:
4、启动 APP 并在 IDA 进行进程附加:
5、然而发现附加进程后,一点击运行程序按钮(或者 F9 快捷键),程序立马闪退……
可以猜测该 APP 使用了反调试机制防止程序被动态调试分析,想进一步调试分析,必须绕过其反调试机制。
反调试对抗
接下来来通过 IDA 动态调试分析 APP 的反调试检测机制,并进行对抗和破解,使得目标 APP 能够被我们正常地调试。
2.1 Ptrace
【反调试原理】IDA 是使用 android_server 在 root 环境下注入到被调试的 APP 程序进程中,那么对抗调试就可以利用 Linux 系统的 ptrace 来实现,当 Android 应用被调试时应用内存里的 TracerPid 字段就不为 0(即在其 status 文件中有一个字段 TracerPid 可以标识是被哪一个进程 trace 了),只要是不为 0 的时候,就会直接的退出程序,达到反调试的目的。
来验证一下上述调试过程中,真机上目标 APP 进程的 ptrace 字段:
进入设备:adb shell
获取Root权限:su
获得APP的进程ID:ps | grep 软件的包名
查看进程的信息及TracerPid值: cat /proc/进程ID/status
结果如下,可以看到程序进程被进程 id 为 16453 的进程所附加:
Linux 系统中 ptrace 系统调用提供了一个进程 (tracer) 可以控制另一个进程 (tracee) 运行的方法,并且 tracer 可以监控和修改 tracee 的内存和寄存器,主要用作实现断点调试和系统调用追踪。具体可参见看雪的文章:[原创] Linux ptrace详细分析系列(一),此处不展开介绍。
2.2 全局调试
既然目标 APP 在 so 文件进行了反调试,那么要想破解反调试机制,就需要对 so 文件进行调试(静态分析你要是能分析出来也行……)。但是前面的调试过程已经以失败告终,无法正常运行程序,那该怎么办?
进行常规 IDA 调试过程,只要一运行目标 APP 程序就会强制退出了调试界面,说明目标 APP 循环检测被调试状态的函数执行的时机非常早。幸运的是,有两个地方是 so 动态加载完毕前执行的:
.init_array
是一个so最先加载的一个段信息,时机最早,现在一般 so 解密操作都是在这里做的;JNI_OnLoad
是 so 被 System.loadLibrary 调用的时候执行的,它的时机早于 native 方法的执行。
那么知道了这两个时机,下面我们先来看看是不是在JNI_OnLoad
函数中做的策略,所以我们需要先动态调试JNI_OnLoad
函数。我们既然知道了 JNI_OnLoad 函数的时机,如果阿里把检测函数放在这里的话,我们不能用之前的方式去调试了,因为之前的那种方式时机太晚了,只要运行就已经执行了 JNI_OnLoad 函数,所以就会退出调试页面。
1、接下来我们就尝试断在 JNI_OnLoad 函数指令处,先在 JNI_OnLoad 函数设置断点并在 debugger 设置中勾选如下选项:
2、但是由于被调试程序一运行就会执行 static 中的语句,因此需要让程序停在加载 so 文件之前,这里可以添加 watiForDebugger,或者使用更加简单的方法,使用 debug 方式来启动 APP:
adb shell am start -D -n com.yaotong.crackme/.MainActivity
运行该命令后,目标 APP 将处于一个等待 Debugger 的状态:
3、IDA 开始对进程进行附加:
4、接着执行如下命令让 APP 运行起来:
进入设备: adb shell
获取Root权限:su
获得APP的进程ID:ps | grep com.yaotong.crackme (直接关注上一步进程附加的 ID 也可以获得)
监听进程的ID:adb forward tcp:8700 jdwp:4495(4495为上面获得的APP进程ID)
运行进程:jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
执行过程如下:
5、返回 IDA 按 F9 开始运行程序:
此时 APP 由等待 Debugger 状态进入如下页面:
6、继续按一下运行程序的按钮(或者 F9 快捷键),程序即会运行到刚才我设置的 JNI_OnLoad 函数断点处:
【注意】程序没有直接闪退的原因是此处的断点位置在检测调试的代码前面,故我们接下来就可以往下逐步运行程序来调试分析反调试的代码的逻辑和位置!
7、在进一步进行调试分析反调试检测代码前,我想补充一个点,实际上上面加载 JNI_OnLoad 函数断点的调试过程我在首次执行时失败,执行jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
命令时遇到如下报错:
报错的原因是目标 APP 不可被调试!解决的办法是修改 APP AndroidMenifest.xml 文件, 在 <application 里给APP加上可调试权限,android:debuggable="true"
,重新打包 APP 后重现编译、签名、安装,如下图所示:
8、可见在 Android 真机上调试程序有一个前提,就是这个 apk 包必须有 debuggable=true
的属性才行,而除了自己开发的 apk 能够控制打包属性之外,其他的程序发行之后显然不会设这个值为 true 的(打包 APK 时 release 发布版本默认为 false 不可调试,只有 debug 版本可调试)。为了调试 APK 而每次都去修改配置文件并重打包也太麻烦了,实际上为了调试这些第三方的 apk,我们可以从整个手机系统入手 —— 因为除了每个 apk 中的 debuggable 标志以外,这个标志还可以在系统中全局指定,换句话说,只要把系统里的 debuggable 值设为 true(即将/default.prop
中默认字段是 0 的ro.debuggable
的值修改为 1,前提是手机需要 root ),那么不管 apk 的这个属性是什么值,该手机上的 APP 都可以被调试了。修改 ro.debuggable 的步骤:
下载 mprop 工具:https://github.com/wpvsyou/mprop
adb push mprop /data/local/tmp # 将下载的mprop 放入 /data/local/tmp 当中
adb shell
su
cat default.prop | grep debug # 查看default.prop里面的配置值,此处是 0
getprop ro.debuggable # 获取ro.debuggable 此处应该是 0
cd /data/local/tmp
chmod 755 mprop # 修改权限
./mprop ro.debuggable 1 # 修改 ro.debuggable 1 的值为 1
cat default.prop | grep debug # 查看default.prop里面的配置值,此处是应该还是 0
getprop ro.debuggable # 获取 ro.debuggable 此处应该是 1
ok,修改完成后,再次看下是不是可以调试了,执行命令adb shell getprop | findstr debuggable
查看手机的ro.debuggable
参数值:
【再次强调】上面修改ro.debuggable
参数值的过程需要在已 root 的手机中进行,同时手机再次重启后需要再次进行一次修改操作。
2.3 反反调试
好了,介绍完将真机设置全局可调试的模式后,我们返回到 JNI_OnLoad 函数断点处,继续分析程序检测调试状态的代码逻辑。
此处先小结一下上述在 JNI_OnLoad 函数设置断点并拦截程序的过程(即 IDA 调试具有反调试机制的程序的步骤):
1)启动 android_server
2)端口转发adb forward tcp:23946 tcp:23946;
3)打开 IDA,设置 JNI_OnLoad 函数断点并在 debug options 中设置 load so 的时机;
4)adb shell am start -D -n 包名/类名;出现 Debugger 的等待状态;
(说明:以启动模式启动,是停在加载so文件之前,包名可以在 Androidmanifest 文件中找到)
5)IDA Debugger 附加上对应的进程(留意目标 APP 的进程 ID);
6)运行命令:
adb forward tcp:8700 jdwp:4495(4495为上面 IDA 附加的目标APP进程ID)
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
7)点击 IDA 运行按钮,或者F9快捷键。
1、按 F8 单步步过,继续往下运行程序,但是当我们每次到达 BLX R7
这条指令执行完之后,JNI_OnLoad 就退出了:
2、再次进入调试,看见运行到 BLX 跳转指令时 R7 寄存器中是 pthread_create
函数,这个是 Linux 中新建一个线程的方法:
所以猜测阿里的反调试就在这里开启一个线程进行轮训操作,去读取/proc/[pid]/status
文件中的 TrackerPid 字段值,如果发现不为 0,就表示有人在调试本应用,在 JNI_OnLoad 中直接退出。其实这里可以再详细进入查看具体代码实现的,但是这里限于篇幅问题,不详细解释了,后续将单独写一篇文章学习反调试机制,本文的重点是能够动态调试即可。
3、定位到关键的反调试检测代码位置后,该怎么绕过反调试检测?其实很简单,我们只要把BLX R7
这段指令干掉即可,如果是 smali 代码的话,可以直接删除这行代码即可,但是 so 文件不一样,它是汇编指令,如果直接删除这条指令的话,文件会发生错乱,因为本身 so 文件就有固定的格式,比如很多 Segement 的内容,每个 Segement 的偏移值也是有保存的,如果这样去删除会影响这些偏移值,会破坏 so 文件格式,导致 so 加载出错的。所以这里我们不能手动的去删除这条指令,我们还有另外一种方法,就是把这条指令变成空指令,在汇编语言中,nop 指令就是一个空指令,它什么都不干,所以这里我们直接改一下指令即可,ARM 汇编中对应的 nop 指令是:00 00 00 00
。首先恢复到静态分析的状态(退出动态分析),将鼠标定位到BLX R7
指令处,切换到 “Hex View-1” 窗口可以看到该指令对应的十六进制表示形式为 37 FF 2F E1
:
4、继续选中BLX R7
指令然后选择如下“Change byte…”选项:
修改37 FF 2F E1
为00 00 00 00
:
5、最后将所作的修改保存到 so 文件中( IDA 中修改 SO 文件保存修改覆盖到 so 文件而非临时工程文件的方式:edit->patch program-> apply patches to input file...
):
至此已完成自毁程序 so 文件的修改,使得反调试的检测核心逻辑代码被破坏,新的 so 文件中将无法正常调用反调试检测代码。
APK破解(下)
完成上述工作,我们来将新的 so 文件替换掉原来自毁程序 APK 里面的 so 文件,并尝试对其正常的动态调试分析。
3.1 重新编译
1、将上面获得的新的 so 文件替换掉原来自毁程序 APK 里面的 so 文件:
2、在 AndroidKiller 中重新编译、打包、重签名自毁程序 APK:
接着将新的自毁程序 APK 一键安装到 Nexus5 测试机即可。
3.2 动态调试
新的自毁程序 APK 讲道理可以正常地被 IDA 动态调试分析而不会闪退了,下面来试试。
1、直接双击开始运行 APP 程序(无需再将 APP 处于一个等待 Debugger 的状态),然后正常运行 android_server 并转发 23946 端口,然后 debugger 勾选如下选项即可:
2、取消 JNI_OnLoad 函数的断点(该函数反调试的逻辑已被破坏,无需再拦截并调试该函数),直接在 securityCheck() 函数设置如下断点:
3、IDA 开始正常附加 APP 程序进程,点击运行按钮或 F9 继续运行程序,程序不再闪退!!!!往输入框输入 1234 字符串并提交,发现程序被成功拦截在刚才设置的断点处了:
4、切换到 IDA View-PC 窗口查看断点并 F8 单步步过运行程序,观察到 if 判断语句对应的汇编指令 R2 寄存器存放的字符串为 “aiyou,bucuoyoo”,也 就是我们想要的目标密码/flag:
5、将 flag 值输入自毁程序,成功破解密码:
总结
本文篇幅相对较长,但也算学习了不少东西。从如何设置测试机真机全局可调试、再到 APK 反调试机制的动态分析和绕过、最后再到重编译程序并动态调试破解密码,整个实践流程下来耗费了不少时间(太菜了)……最后说一下,Android 反调试机制不局限于本例所提及的 TracerPid 字段检测,后面我将继续单独对 Android 反调试机制及其对抗机制进行学习。本文至此 Over!!!
本文参考文章:
- [原创]超级详细的实战分析一个Crackme的过程(很细致,推荐);
- 安卓逆向实践5——IDA动态调试so源码(分析很到位);
- Android逆向之旅—动态方式破解apk进阶篇(IDA调试so源码)(姜维大佬的文章)。
以上是关于IDA动态调试破解AliCrackme与反调试对抗的主要内容,如果未能解决你的问题,请参考以下文章