一个native层反调试例子
动态调试
apktools反编译,浏览smali代码,找原生函数。用ida打开so,找一下相关函数。
###调试准备
- 将ida下gdbsrv目录下的android_server上传到手机上
adb push ./android_server /data/local/tmp
- 提升一下文件的权限后运行
- 转发到本地端口
adb forward tcp:23946 tcp:23946
打开ida的debugger里的attach,选择remote ARMLinux/android debugger,填写本地地址及端口即可附加,选择对应进程包名。
函数查找
ida成功附加后,会自动断下来,打开Program Stegmentation窗口,找到目标so
带有X可执行权限且最低地址的段,为基地址,之前so静态分析时获得的函数地址为偏移,基地址+偏移=目标地址,在目标地址处下断,如果是未定义数据,按c将其转为code。
下完断点后,运行发现ida报错,调试停止,手机上程序也退出,重复也是如此。这里是因为程序检查到了被调试后自退出。
了解到,程序利用了ptrace。linux下一般调试器都会使用ptrace使进程被调试。
可看到,tracerPid值为3559,被pid为3559的进程调试,那么我们看看我们调试服务的pid:
果然,刚刚打开的ida的调试服务pid就是3559。
那么程序就是检查到了tracepid不为0而认为程序被调试。
在函数中无法下断,我们需要寻找另一个位置下断,如何寻找,我们先来看一下so文件加载过程。
so文件加载
http://www.blogfshare.com/linker-load-so.html
系统加载so,在完成装载、映射和重定向以后,就首先执行.init和.init_array段的代码,之后如果存在JNI_OnLoad就调用该函数.我们要对一个so进行分析,需要先看看有没有.init_array section和.init section,so加壳一般会在初始化函数进行脱壳操作。
调试
令其可调试
打开ddms,发现列表中没有进程,程序不可调试,首先要将其可调试,之前讲过方法。
- 将AndroidManifest.xml提取出来
- 用AXMLEditor添加
debuggable=“true”
属性
java -jar AXMLEditor.jar -attr -i application package debuggable true old.xml out.xml
- 删除apk包中的AndroidManifest.xml
aapt r file.apk AndroidManifest.xml
- 将新的xml添加进去
‘aapt a file.apk AndroidManifest.xml’
注意添加时文件必须是当前目录,否则apk中会添加进去同样的目录
- 去签名,删除META-INF下除MANIFEST.MF之外的所有其他文件(签名时MANIFEST.MF会重写)
aapt r file.apk META-INF/CERT.SF
aapt r file.apk META-INF/CERT.RSA
- 自行签名即可
过程中发现一些问题,xml确实存在debuggable=“true”
属性,但是应用还是不能被系统识别为可调试。
比较回编译和直接插入方式得到的XML文件,原因是直接插入属性,没有在XmlResourcesMapType中插入对应属性id值。
用AmBinaryEditor插入属性id后还是有问题,只能通过反编译再回编译解决了。得去研究一下aapt怎么打包xml的。
这里解决不生效的问题,以后还是用hook插件吧。。。
寻找反调试点
为了能让程序断在JNI_OnLoad上,我们不能直接运行app,再attach,时机太晚。
我们需要以debug的方式启动app来等待被调试。
adb shell am start -D -n com.yaotong.crackme/.MainActivity
以debug方式启动后,ida再attach上,之后还需要更改一下debug option:
这时候需要java调试器附加上去,程序才能运行起来,程序要求可调试,之前已经修改了,端口要打开devices monitor才会打开:
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
ida中F9运行后发现等待调试对话框消失了,而且断下来了,根据JNI_OnLoad的偏移寻找地址,下断,再运行后就断在JNI_OnLoad上了。
运行到这里,要跳转到R7的地址处
发现R7里是pthread_create的地址
果然运行之后就退出了,程序在这里开启一个线程来检测是否被调试。
简单方法就是把现线程创建函数nop掉。
发现是可以断在Java_com_yaotong_crackme_MainActivity_securityCheck函数里的:
总结
关于利用ptrace实现反调试,这里是利用了创建线程来检测进程的tracepid,另一种思路是父子进程相互监控来实现,可以看一下这篇文章。