14. 使用IDA进行动态调试与过反调试(下)(三)

《使用IDA进行动态调试与过反调试(上)(三)》上篇讲了使用IDA对so文件的动态调试,与几种反调试的检测

本篇接上篇主要是使用IDA通过动态patch来绕过调试检测

仍然使用上篇中的反调试检测的apk文件

IDA动态调试 .init_array

1. 找到 .ini_array 段里面的函数 thread_function ,并在其第一条ARM指令上面按F2下断点

2.在 JNI_OnLoad 函数调用的 SearchObjProcess 处下断点(c代码窗口和汇编指令窗口快速转换按Tab键)

3.在动态注册的函数 checkport() 第一条指令下断点

4. 下面开始动态调试,但一般当我们使用IDA进行attach的时候,.init_arrayJNI_Onload()早已经执行完毕了,根本来不急调试。这时候我们可以使用jdb这个工具来解决,这个工具是安装完jdk以后自带的,可以在jdk的bin目录下找到。

依旧是先运行 android_server

端口转发

因为要调试 .init_array,所以需要在程序刚启动的时候进行调试,启动程序

adb shell am start -D -n demo2.jni.com.myapplication/.MainActivity

此时要调试的app已经挂起 "Waiting For Debugger"

打开DDMS,查看运行的app端口信息

附加进程

Debugger --> Process Options

Debugger --> Attach to Process

F9运行程序,在IDA等待的时候用jdb将app恢复执行,8618是刚才DDMS上面的那个端口号

jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8618

点击完same后程序会断在一个点,不要理会,直接F9(如果弹出框都点Yes) 直到程序断在了我们的断点处 并将函数第二条ARM指令改成POP让函数不执行直接出栈,或者把下面调用exit对应的HEX改成00 00 00 00都是可以的。

IDA继续F9让程序运行起来  直到程序断在我们下的第二个断点处,ARM指令BL是调用函数的意思,所以将其指令对应的HEX改成00 00 00 00,无意义的指令 直接patch掉达到让其不调用的目的 

IDA继续F9,这时会来到我们下的第三处断点,注意这里是动态注册的函数,对于动态注册的函数我们就不能通过把第二条ARM指令改成POP直接出栈,否则程序会崩溃,那这里怎么改呢?这里可以直接把调用exit退出对应的HEX改成00 00 00 00来过掉。

到此这三处反调试就都过掉了,现在F9运行遇见弹框都点确定,然后程序弹框 恭喜你挑战成功!

案例AliCrackme分析

这个Crackme案例是之前阿里安全挑战赛的第二题,先将apk拖入Jadx反编译分析

解压apk包,提取里面的libcrackme.so文件,使用IDA静态分析

直接在 Functions window找到 securityCheck() 方法,F5直接反编译成伪C代码,进行代码优化

双击进入v6内嵌的字符串

可以看到字符串 "wojiushidaan",输出发现校验错误,说明程序在运行时肯定对其进行了处理。那么接下来就对so进行动态调试。

这里还是以前的方法,启动android_server,转发端口,attach进程

这里要说一下为什么动态调试,attach完进程后,IDA总会停在 libc.so 这个地址

因为android系统中libc是c层中最基本的函数库,libc中封装了io、文件、socket等基本系统调用。

所有上层的调用都需要经过libc封装层。所以libc.so是最基本的,所以会断在这里,而且我们还需要知道一些常用的系统so,比如linker:

这个linker是用于加载so文件的模块,所以后面我们在分析如何在.init_array处下断点

还有一个就是libdvm.so文件,他包含了DVM中所有的底层加载dex的一些方法,在后面动态调试需要dump出加密之后的dex文件,就需要调试这个so文件了。

在要调试的方法的第一行下断点

这个下断点的地方动态调试可以直接通过so文件对应的相关方法找到,在代码第一行下断点

静态调试可以通过 相对地址(native函数) + 基地址(so文件)得到绝对地址,像这个地方

在打开一个IDA进行关联调试

Ctrl + s 找到so文件对应的基地址

这样绝对地址 = B4402000 + 11A8 = B44031A8

使用G键直接跳到这个地址,下好断点,再按F9运行,但是程序并没有断下来,反而直接退出了

因此这里肯定是做了反调试检测。

基本原理是IDA是使用android_server在root环境下注入到被调试的进程中,那么这里用到一个技术就是Linux中的ptrace,trace一个正在运行的进程称为进程附加(attach),使用的是ptrace函数的PTRACE_ATTACH参数,当一个进程成功附加到一个正在运行的进程时,此进程会成为被附加进程的父进程,同时向被附加的进程发送一个SIGSTOP信号,让其停止,这时我们就可以对其进行操纵。当我们完成对tracee的操作后就可以使用ptrace的PTRACE_DETACH参数停止附加。那么Android中如果一个进程被另外一个进程ptrace了之后,在他的status文件中有一个字段:TracerPid 可以标识是被哪个进程trace了,我们可以使用命令查看我们的被调试的进行信息:

status文件在:/proc/[pid]/status

这里的进程被 2828 进程 trace了,用ps命令看看2828是哪个进程:

果然是android_server进程,程序在底层做了一个循环检测这个字段如果不为0,那么代表自己进程在被人trace,那么就直接停止退出程序。

如何找到这个反调试检测的位置并尝试绕过?

因为 .init_array 是一个so最先加载的一个段信息,时机最早,现在一般so解密操作都是在这里做的;

JNI_OnLoad是so被 System.loadLibrary 调用的时候执行的,它的时机早于native方法的执行。

那么知道了这两个时机,下面我们先来看看是不是在JNI_OnLoad函数中做的策略,所以我们需要先动态调试JNI_OnLoad函数。

接下来就尝试断在 JNI_OnLoad 函数指令处,首先在IDA调试选项中做如下设置:

要让程序断在加载库文件时就挂起

但是由于被调试程序一运行就会执行 static 中的语句,因此需要让程序停在加载so文件之前,这里可以添加 wati For Debugger,或者使用更加简单的方法,使用debug方式来启动:

adb shell am start -D -n com.yaotong.crackme/.MainActivity

运行完,设备是处于一个等待调试的状态:

 

接下来在IDA中进行进程的 attach ,但是此时发现没有RX权限的so文件

说明so并没有被加载到内存中去,因为现在的程序是 Wait For Debugger,也就是还没有走System.loadLibrary方法,so文件当然没有加载到内存中,那么就需要让程序跑起来。

一定要在IDA调试选项中(Debugger -> Debugger options)做如下设置:

然后点击IDA的运行按钮,或者F9

这一步需要打开DDMS来默认端口转发(当前正在转发端口8600的信息到8700的静态调试端口)

或者使用

ps | grep com.yaotong.crackme

然后端口转发

adb forward tcp:8700 jdwp:14522

然后使用如下命令让程序跑起来:

jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700

但是出现如下错误

出现这种问题大多数是被调试的程序不可调试,可以查看apk的android:debuggable属性,如何没有这个属性默认为 false,所以这里需要添加这个属性为 true 再进行回编译:

打包重新安装

按照上面的步骤继续

当jdb执行成功后,程序界面便会结束waiting,同时IDA上成功加载程序后,便会进入到linker模块:

这时候,说明so已经加载进来了,因为这次要调试的是JnI_OnLoad函数,所以需要获取JNI_OnLoad函数的绝对地址

双开IDA,拖入libcrackme.so,静态分析得到JNI_Onload函数的相对地址

Ctrl + s 找到libcrackme.so的基地址

这样就可以得到绝对地址 =11A8 + B4401000 = B4402B9C

使用G键jump到函数开始运行的位置

在这个地方下断点,再次点击运行(F9),可以看到程序断在JNI_OnLoad处的断点

下面使用F8开始单步调试了,发现每次到达BLX R7这条指令执行完之后,JNI_OnLoad函数就退出了,这个地方存在问题,可能就是反调试的地方了。。

我们再次进入调试,看见BLX跳转的地方R7寄存器中是 pthread_create 函数,这个函数我们在《使用IDA进行动态调试与过反调试(上)(三)》就已经介绍过。

程序的反调试就在这里开启一个线程进行轮训操作,去读取 /proc/[pid]/status 文件中的 TrackerPid 字段值,如果发现不为0,就表示有人在调试本应用,在JNI_OnLoad中直接退出。

问题找到了,现在问题是怎么绕过反调试检测

可以把 BLX R7 这条指令给nop掉,也就是把这条指令变成空指令(相当于删除这条指令)这样apk就不会新建线程去执行检测代码了。

双卡IDA静态分析libcrackme.so,在JNI_Onload()函数定位到 BLX R7 

然后在hex窗口直接把 BLX R7 这条指令对应的hex改成 00 00 00 00.

可以看到修改过后的指令变成 :

保存修改过后的so文件

替换原来的so文件,再次重新编译签名安装,再次按照之前的逻辑给主要的加密函数下断点,这里不需要在给JNI_OnLoad函数下断点了,因为我们已经修改了反调试功能了,所以这里我们只需要在 securityCheck()  函数处下断点即可

可以看到顺利断到了 securityCheck()  函数开始的地方,说明我们修改反调试指令成功了。接下来使用 F8 一步步调试

发现存储的字符串为 "aiyou,bucuoo",输入这个密码:

破解成功

参考链接:

  《adb jdb调试相关》

  《Android IDA So的动态调试大法》

  《Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)》

  《IDA调试Android native(Crackme)》

  《IDA动态调试so源码》

posted @ 2020-07-23 19:13  bmjoker  阅读(202)  评论(0编辑  收藏