一个native层反调试例子

一个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:
a-w400

这时候需要java调试器附加上去,程序才能运行起来,程序要求可调试,之前已经修改了,端口要打开devices monitor才会打开:

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

ida中F9运行后发现等待调试对话框消失了,而且断下来了,根据JNI_OnLoad的偏移寻找地址,下断,再运行后就断在JNI_OnLoad上了。
a-w600

运行到这里,要跳转到R7的地址处
a-w600

发现R7里是pthread_create的地址
a-w400

果然运行之后就退出了,程序在这里开启一个线程来检测是否被调试。

简单方法就是把现线程创建函数nop掉。

发现是可以断在Java_com_yaotong_crackme_MainActivity_securityCheck函数里的:
a-w600

总结

关于利用ptrace实现反调试,这里是利用了创建线程来检测进程的tracepid,另一种思路是父子进程相互监控来实现,可以看一下这篇文章