通过分析这道题目的反调试原理,发现我的方法能搞定纯属运气啊————反调试线程在调试模式下被阻塞了,没有抢在我F8执行之前运行完毕。下面来具体分析一下这个反调试:
首先关注一个重要的内存区域6290
。(在我的模拟器上,apk的加载基址是47ff2000
,截图都是绝对地址,我说明时候都使用相对地址)。这个区域存放反调试、以及解密jolin
函数所要用到的关键数据。
当然在Onload
入口点,这块内存区域是都是00
。经过几个函数调用后,发现(如上图),这块区域被数据填满了。除了后面的字符串,前面部分实际上是一个和后面字符串函数对应的libc函数的内存地址。等同于一个函数跳转表。
其次反调试函数的入口点:
图中BLX R7
实际上调用pthread_create
函数创建线程(这个R7的地址也是动态生成的,和静态分析的函数调用是不同的),紧跟在后面的BL 17F4
就是jolin
的解密函数。这个线程就是专门用来反调试的。看其线程函数16A4
:
就是每隔3秒调用一次130C
进行调试器检测。
接下来就是最最关键的反调试函数130C
:
代码我都进行了名称标注和注释。这段代码为了增加逆向的难度,实际上使用了6290
处的跳转表,静态分析根本就是不知所以。
具体说说其判断apk是否处于被调试状态的原理吧:
实际上就是打开进程对应的/proc/pid/status
文件,查看里面的TracerPid
条目,对于>=1
的值(对应跟踪者不是root==0),则认为apk被调试器跟踪,kill(pid, -9)
强制关闭整个进程。
最后需要说明的是,为了增加逆向难度,反调试里面所有的函数调用地址都==真实地址+1。 我们知道ARM模式指令长度都是4字节,函数地址不可能出现在奇数地址上。这种调用方式ARM会自动处理到4字节边界上,不影响程序执行,但可以模糊IDA的函数地址分析机制,使得IDA不能正确识别出调用的是哪个系统或库函数。
我是一个一个地址手动去查的。这说明需要有个脚本能自动分析和比对相应so/dll的导出函数地址,并进行比对。
另外说说多线程的线程函数的调试:
调试器的原理是将断点处的指令改成CC
,然后执行,cpu产生异常(int 3
)后,调试器接管程序。
一旦程序暂停后所有线程都被暂停,一旦开始运行,理论上这个时候cpu是自主的可以运行任意线程,直到遇到CC
为止。
F8、F7由于
CC`靠的非常近,cpu被频繁中断,看上去似乎是在一个线程函数里调试一样。实际上后台线程的状态是无法确定的。
要调试到另一个线程的线程函数,比较好的方法是只在这个线程函数中设置断点,F9后会自然的中断在那里。因为cpu什么时候调度线程执行是我们无法精确控制的。