android逆向数据分析,深入理解Android逆向调试原理

IDA Android 64-bit remote debug server(ST) v1.22. Hex-Rays (c) 2004-2017Listening on 0.0.0.0:23946...

3. 执行端口转发

adb forward tcp:23946 tcp:23946

4. IDA附加到调试进程

选择远程ARMLinux/Android debugger:

7101fb2b16207a3ce6099d940586597d.png

填写 hostname为 127.0.0.1或localhost:

5885e02efed7e408645fefa5c3fc4de6.png

附加到调试进程:

76d486d87e16a07850464142f756c02e.png

成功附加到调试进程:

c876b5eaa82b5aba8a724437d5c88e5f.png

配置调试选项,勾选“Suspend on library load/unload”,以在目标SO加载时进程能够停下来:

a87b18a7d89fa8c84f294c956b5fa9fa.png

5. Jdb连接到调试进程的VM

DDMS查看调试进程端口号,假设为55555,在host机器上执行:

C:Usersreverser>jdb -connect com.sun.jdi.SocketAttach:hostname=localhost,port=55555设置未捕获的java.lang.Throwable设置延迟的未捕获的java.lang.Throwable正在初始化jdb...>

IDA中按F9进程继续运行,直到目标SO被加载。

IDA output窗口显示目标SO文件被加载:

PDBSRC: loading symbols for'/data/app/com.outdoor.debugtest-iQxBx29FyKFuLtnU6wwQag==/lib/arm64/libnative-lib.so'...

6. 在目标SO上下断点

在IDA Modules模块中搜索目标SO库并双击选中,找到目标函数下断,即可开启调试流程。

以上便是IDA远程调试Android SO的关键步骤,针对如上步骤,我现在提出如下几个疑问供大家思考:

a. DDMS展示的端口号,是怎么得来的?有没有更加便捷的方式获取这个端口号?

b. Jdb connect命令是Java VM调试相关的命令,在SO调试中为什么要用它?它在和哪个模块进行通信?IDA附加上进程之后为什么不能直接开始调试?

c. Adb、android_server、IDA之间的关系是什么?IDA与应用进程之间的调试连接到底是如何建立的?IDA调试Android SO的技术原理是什么?

以上问题由浅入深,从对操作技巧层面的思考,到对背后调试原理的思考,这也是我自己探索这个问题的一个过程。在回答这些问题之前,我们需要做一些关于调试原理的铺排工作,以帮助大家全面、深入地理解问题。

五、Android调试模型分析

本小节对Android调试模型的介绍,主要参考自源码文件/ system/core/adb/jdwp_service.cpp中的相关注释。因为这一部分注释详细阐述了Android调试模型是如何建立起来的,所以我会首先翻译这个模型的建立过程,然后进行一个抽象的总结,以帮助大家更好地理解Android的调试模型。

1. Android源码对调试模型建立的描述

(1)当adbd启动之后,它会创建一个unix类型的server socket套接字,名字叫@jdwp-control;

(2)当一个新的JDWP守护线程在一个新的VM进程中启动时,这个新启动的JDWP线程会创建一个到@jdwp-control的连接,以宣告它自己的可用性:

50 JDWP thread @jdwp-control51 | |52 |-------------------------------> |53 | hello I'm in process |54 | |55 | |

上面这个连接一直保持着,直到JDWP线程所在的进程结束;

(3)因此,adbd维护着一个JDWP列表,记录着每个活跃的进程。Adbd可以通过“device:debug-ports”服务同每一个客户进程进行通信;

(4)当有调试器想要连接时,调试器执行“adb forward tcp: jdwp:”命令,“jdwp:”用来指定设备上的目标JDWP进程。

(5)Adbd收到命令后,执行如下操作:

调用socketpair创建一对“equivalent sockets”;

将第一个socket附加到本地socket,本地socket本身又连接到远程socket;

使用sendmsg将第二个socket的文件描述符直接发送给JDWP进程。

JDWP thread @jdwp-control| ||

(6)接着,JDWP线程使用这个新的socket描述符作为它与调试器的通信通道(接收JDWP-Handshake消息,应答调试器的消息等等)。

整个过程建立起来后,可以用下面这个图表示:

____________________________________| || ADB Server (host) || |Debugger LocalSocket RemoteSocket || ^^ ||___________________________||_______|||Transport ||(TCP for emulator - USB for device) ||||___________________________||_______| || || ADBD (device) || || VV |JDWP <======> LocalSocket RemoteSocket || ||____________________________________|

2. 对Android调试模型的总结

对于如上内容,我要明确阐述的第一个点是,以上调试模型是针对Android VM的调试模型,这个模型的顶层形式如下图,其中,调试器通过JDWP协议与Android虚拟机进行通信。

80c4bee259c31d905fc0ed9bd81ee243.png

而IDA对android的支持,是在整个进程的层面,并不局限于Android虚拟机。这其中的区别可以用如下方法简单区分,IDA能够支持对原生程序进程的调试,如linker、init进程等等,而基于JDWP协议的这一套原生调试框架并不支持。

de4bcca50b5375af5d903087922abe49.png

归纳而言之,原生基于JDWP的调试框架与IDA的android调试框架,是两个层面上的调试实现,前者是虚拟机层面的实现,后者是整个进程层面的实现。理解这一点对理解Android逆向调试的原理至关重要。

六、逆向调试关键步骤解密

有了以上知识的铺垫,我们就可以开始研究第四章中提出的几个问题了。

1. DDMS展示的端口号,是怎么得来的?有没有更加便捷的方式获取这个端口号?

根据前一章的阐述,调试器通过执行“adb forward tcp: jdwp:”命令将host机器上的hostport端口转发到Android上的调试进程,以便调试器通过这个端口连接到目标进程。所以这个hostport是调试器指定的,据此推测,DDMS便是通过这种转发方式获取到这个端口并展示到界面上。

因此,我们可以通过“adb forward tcp: jdwp:”命令,替代使用DDMS的方式。

与问题1相关的进一步思考

当我们以调试模式启动应用时,应用会输出如下形式的日志信息:

--------- beginning of system03-08 10:01:05.709 6761 6761 W ActivityThread: Application com.outdoor.appcomvulbypassactivityperm is waiting for the debugger on port 8100...

日志信息表明应用在端口8100处等待调试连接,那是不是表明我们可以直接将hostport转发至Android 8100端口呢,尝试后你会发现不会成功。原因如下:

Android应用的调试模式有“dt_socket”和“dt_android_adb”两种,即通过socket或者adb连接到JDWP线程。所以理论上,通过将hostport转发到8100也是可以实现的。但进一步分析源码之后发现,Android虚拟机默认以“dt_android_adb”的模式启动,并且这个启动参数硬编码在源文件之中,不可手动配置的。

frameworks/base/core/jni/AndroidRuntime.cpp

601 int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote)602 {……772773 /*774 * Enable debugging only for apps forked from zygote.775 * Set suspend=y to pause during VM init and use android ADB transport.776 */777 if (zygote) {778 addOption("-agentlib:jdwp=transport=dt_android_adb,suspend=n,server=y");779 }

……995 if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {996 ALOGE("JNI_CreateJavaVM failedn");997 return -1;998 }9991000 return 0;1001 }

所以直接转发8100端口会失败,因为Android VM的启动模式是“dt_android_adb”,JDWP线程也没有监听8100端口,这条日志给很多人带来了困扰。

最后,如果想要实现通过socket直接连接到JDWP线程,有没有办法呢?答案是有的。有两种可行的方式,一种是安装hook框架直接修改参数,但这种做法意义不大;另外一种是通过操作动态库的方式(dlopen、dlsym),修改调试启动参数,然后重启JDWP线程,这种适合实现生产环境下的远程调试。

2. Jdb connect命令是Java VM调试相关的命令,在SO调试中为什么要用它?它在和哪个模块进行通信?IDA附加上进程之后为什么不能直接开始调试?

在前面一章中,我们明确了通过原生JDWP的方式调试与使用IDA调试是两个层面的调试手段。

使用Jdb的目的,是为了通知VM继续运行,因为应用以调试模式启动(以调试模式启动的原因是为了在程序早期下断点),启动之后一直在等待调试连接,Jdb通过“dt_android_adb”的方式,连接到了目标进程的VM,VM继续运行,这样IDA才可以在运行着的进程上进行调试。

归纳而言之,对JDWP调试手段的运用,是一种辅助手段,这种辅助手段使得我们能够在进程早期下断点。

3. Adb、android_server、IDA之间的关系是什么?IDA与应用进程之间的调试连接到底是如何建立的?IDA调试A ndroid SO的技术原理是什么?

根据前两个小节的内容,我们知道了adb套件实际上是在辅助建立调试连接:

a. 在JDWP调试连接中,提供“dt_android_adb”模式所需的通信功能;

b. 在IDA调试连接中,提供端口转发、远程启动进程的手段。

而android_server、IDA之间的关系,基本上与原生调试模型保持着一致,它是IDA对android逆向调试的实现:

____________________________________| || IDA (host) || || RemoteSocket || ^^ ||___________________________||_______|||Transport ||(TCP for emulator - USB for device) ||||___________________________||_______| || || android_server (device) || || VV |PROCESS <======> ptrace RemoteSocket || ||____________________________________|

android_server的作用类似于adbd,区别在于,adbd通过本地socket与JDWP进行通信,以转发调试信号;而android_server此处实际上是基于ptrace实现的一个调试器,它一端通过socket与IDA相连接,传输调试指令和数据,另一端通过ptrace直接操控调试进程。

七、总结

经 过以上几个章节的阐述,我希望已经讲清楚我提出的那些问题,也希望这篇文章能够达到它的目的。

- 结尾 -

d8e06a2cddf8853852996cd9a4b4f596.png

看雪ID:kxliping

*这里由看雪论坛 kxliping原创,转载请注明来自看雪社区。返回搜狐,查看更多

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值