本帖最后由 fxb960704 于 2018-3-14 16:48 编辑
以下反调试技术均为本人分析安卓各大加固厂商以及游戏厂商程序中遇到的,并班门弄斧做出了一些对应的对抗方案。总结有疏漏错误之处在所难免,望诸位大佬指正。
既然我们要反反调试,那么何谓反调试呢?众所周知,当我们完全静态分析某个程序进入“山重水复疑无路”的境地时,动态调试往往能让我们觉得“柳暗花明又一村”。那么对应的保护程序必不可少的方案就是反调试检测,将攻击者扼杀在调试前。以下总结了常见的反调试方案以及对抗措施,没有相应的反反调试技能你甚至都无法附加到目标程序,寸步难行。
1. 安卓调试检测函数 本人最早调试百度加固中遇到,最近在某ctf题目中再次遇到,位于android.os.Debug.isDebuggerConnected()
原理: 如图所示常用在java层,比如检测到调试状态不加载对应so导致应用运行出错。通过分析该函数在native的实现,是可以移植到so层使用该函数的(暂时没有遇到过),如:dalvik模式下在libdvm.so可以找到,art模式下结果存放在libart.so中的全局变量gDebuggerActive中。 对抗: 在Java层属于非常容易绕过的反调试检测,只需要修改对应smali即可。不可小觑,下文会讲到修改安卓内核的方式绕过常见反调试,但修改该函数貌似会影响IDA正常调试,如果被放在so层调用并加以字符串混淆将很难发现并绕过。 2. IDA调试端口检测 最早在调试360加固中遇到,做ctf也遇到了,当我们使用IDA提供的android_server默认端口启动,并没有正式开始调试就发现软件已经崩溃,再次打开软件依然崩溃。那么很可能就是端口检测和下面即将讲到的调试器进程名检测。 原理: 读取/proc/net/tcp,查找IDA远程调试的默认23946端口(或者执行命令netstat -apn)
对抗: 很简单,只需要自定义调试器启动端口即可,使用 -p 可自定义端口。 3.调试器进程名检测 通常和2中提到的一起出现 原理: 遍历进程,查找类似android_server,gdbserver,gdb等调试器进程 对抗: 很简单,重命名android_server为其他字符即可 4.ptrace自身检测 属于占着茅坑不XX的反调试,当你用IDA附加目标程序出现如下情况,首先要怀疑遇到了这种反调试。
原理: 每个进程同一时刻只能被一个调试进程ptrace,不能再被其他进程附加 对抗: 抢占先机,以调试模式启动目标程序(adb shell am start –D –n 包名/入口类),这时用IDA附加就抢在应用启动前,使用jdb恢复执行(调试加固软件的基本操作)。或者可以尝试hook ptrace,如果该检测是用在下文讲到的多进程反调试中,可以参考对应的对抗思路。 5.检测系统关键文件 是现在市面上各类安卓软件反调试中最最常用的,当你附加到某个应用,单步了几下IDA就莫名其妙的挂掉了首先应该怀疑遇到了这种反调试。 原理: 在调试状态下,Linux内核会向一些文件写入进程状态信息,比如/proc/[pid]/status ,/proc/[pid]/task/[pid]/status文件的TracerPid字段写入调试器进程pid,state字段写入t(tracing stop) /proc/[pid]/stat ,/proc/[pid]/task/[pid]/stat文件中第二个字段填入T来表示调试状态
/proc/[pid]/wchan , /proc/[pid]/task/[pid]/wchan文件写入ptrace_stop这些文件的特征能有效的反映有没有调试器附加进程,实现起来简单,正打调试器痛处,是市面上最为热门的反调试方案之一。
对抗: 最容易想到的当然是在IDA中在open, fopen, strcmp, strstr等函数下断点来试图拦截这些文件的内容读取,然后伪造成没有调试器的样子。但往往一个程序会在不同时机做多次这种检测,甚至会创建线程或者进程死循环检测,想要每次都利用IDA修改只是我们一厢情愿。但道高一尺魔高一丈,修改安卓内核从根源上处理这些文件,比如将/proc/[pid]/status的TracerPid字段写死为0等,能过掉市面上绝大部分软件的反调试,让我们的调试顺畅很多(篇幅有限,如何修改,以及如何编译安卓源码不再赘述)。
6.检测安卓内核修改 上文说到了修改安卓内核过反调试这么嗨皮,是不是万事大吉了?原理: 创建一个子进程,让子进程主动ptrace附加自身,正常情况下,上文提到的/proc/[pid]/status的TracerPid字段应该为子进程pid,但我们修改内核源码后在任何情况下该字段都是0,以此判断出用户的安卓内核是不是改造的。 对抗: 其实本人并没有遇到过这种反调试,但不得不承认理论上确实存在。如果真的出现的话,本菜鸟觉得可以再次修改内核,控制fork函数,让程序子进程无法创建,诸位有更好的方案欢迎探讨。 7.多进程反调试 最早在调试全民枪战和穿越火线枪战王者中发现,目标程序创建了多个进程,互相ptrace,不同进程分工明确,守护或者反调试。只要有一个进程出现异常,集体挂掉,颇有一番兄弟有难同当的感觉。 原理: 通常是融合了多种反调试方案在子进程中,进程间可以通过管道、信号、共享内存、套接字等通信,保证反调试功能运行正常。子进程ptrace自身同时也达到了防止IDA附加的目的,这种也算是目前最流行,保护效果最好的方案之一。对抗: 一种思路是,首先通过ps找出子孙进程的pid,查看/proc/<pid3>/task找出子孙进程所有的thread,并记录下他们的tid。最后使用kill -19<tid> 将这些子孙线程挂起,调试器就能正常附加主进程了。还有一种思路是修改安卓源码/bionic/libc/bionic/fork.c里面的fork函数来使得目标进程fork失败。8.代码执行时间检测 最早分析360加固时遇到,当你附加并可以单步调试目标程序后,发现单步很容易在不同的地方莫名其妙的跑飞了,那么很可能是这种检测。 原理: 通过五个能获得时间的api,如:time()函数与time_t结构体,clock()函数与clock_t结构体,gettimeofday()函数与timeval timezone结构体,clock_gettime()函数与timespec结构体,getrusage()函数与rusage结构体获取到某处代码的执行时间,在另一处代码出再次获取时间比较时间差,正常情况下程序运行很快时间差非常小。 对抗: Hook以上api,返回同一个值 9. rtld_db_dlactivity函数 也是在360加固中遇到的,非调试状态该函数是空函数,调试状态该函数的内容改写为相应指令集的breakpoint指令
原理: 如图是Thumb breakpoint机器码 0x01, 0xde 对抗: IDA中可尝试对该函数下断点来到相应检测点或者修改内存中的机器码进行绕过 位于如图link模块下。
由于本人能力有限,暂时总结出以上反调试及对抗方案,其他的诸如检测软件断点、函数指令、Inotify事件监控等极少遇到,篇幅有限也只能侧重于理论层次,不足之处敬请谅解!
——BinCrack 2018/3/14 |