1 背景
Android动态调试程序通常用来执行以下操作及其他任务:
- 选择要在设备上调试应用;
- 在Java、Kotlin和C/C++代码中设置断点;
- 在运行时检查变量和对表达式求值。
2 JEB动态调试smali代码
2.1 JEB常用功能
JEB是一款强大的跨平台Android分析工具,功能丰富。此外,JEB的脚本化功能在自动化分析与对抗代码混淆中非常使用,其主要功能如下。
- 反编译apk、dex
- 包名、树状图
- 查看指定类的smali代码
- 转换成Java语言
- Java代码中双击函数,进入函数方法的定义,查看方法的调用
- 查看AndroidManifest.xml
JEB常用快捷键及其功能如。
- JEB2按Q键将指定区域的smali代码转换成Java代码,JEB3是Tab键
- X键查看方法的引用
- Ctrl+B在指定的smali代码行中加断点
- Ctrl+F搜索函数、字符串等,注意程序搜索是从上到下搜索,如果在中途的smali代码段开始搜索是搜索不到上面代码段中的信息,而环绕搜索是在全部的smali代码中搜索
Debug模式和普通模式的区别在于启动过程。
- 普通模式:无法调试界面的创建,即onCreate函数
- Debug模式:可以调试界面的onCreate函数,即刚开始创建界面是就开始调试
2.2 JEB普通调试
首先,在模拟器中打开注册机程序,然后,使用JEB打开zhuceji.apk文件,依次点击Debugger->Start,并找到相关进程双击进行附加调试,如图1所示。
接着,在MainAtivity的checkSN函数中的equalsIgnoreCase之前下断点,然后在模拟器中输入任意16位注册码进行调试,程序走到这里时查看相关寄存器即可找到用户名ihui对应的注册码9458797e3b684bde,如图2所示。
2.3 JEB Debug调试
首先,查看AndroidManifest.xml中的application节点是否为可调试。如果是False,则不能调试,需要改成True然后重新打包APK,安装可调试的APK,然后adb shell am start -D -n 应用程序包名/应用程序入口界面,这里可以看到APK已经可调试。
然后,在cmd中输入命令adb shell am start -D -n com.qianyu.zhuceji/.MainActivity以Debug调试模式启动APP,如图4所示。
这时候,一定不要点击弹窗,否则还得重新执行上述步骤。
使用JEB开始调试,和2.2中普通调试模式一样,最终也能到用户名ihui对应的注册码9458797e3b684bde。
3 Android Studio动态调试smali代码
首先,导入反编译出来的完整的smali代码,设置好调试所需要的条件,设置合理的断点。然后,连接模拟器,运行待调试APK的应用程序,执行程序流程,运行到断点处的smali代码会自动暂停,从而进一步分析和调试程序运行过程中的参数。
3.1 前期准备
3.1.1 前期工具准备
我们需要准备以下工具,以便进行后续操作。
- Android Killer:反编译APK后获得smali代码
- Android Studio:动态调试smali代码
- smalidea-0.06.zip:Android Studio插件,用来给smali代码下断点,单步调试
【注】smali下载地址:https://github.com/JesusFreke/smalidea 。一定要下载smalidea-0.06.zip,0.05版本的不能在较新版本的AndroidStudio中下断点。
3.1.2 实现Android手机全局可调试
这一步很关键,就是让运行在设备中的程序支持Debug。方法有几种:
- 把设备root掉
- 修改测试机的/default.prop文件的ro.debuggable=1,目测这一步也可能需要root
- 使用模拟器
- 修改APK的Manifest application属性android:debuggable=”true”,可以用apktool解出Manifest然后修改,接着重新打包回去
- 打开系统调试总开关,使用am命令,以调试模式启动应用
- 终极办法,自己编译一个debug版的rom,此法稍微麻烦一点
3.2 将反编译后的smali代码导入到Android Studio
首先,使用Android Killer打开zhuceji.apk,然后点击工程管理器->注册机->右键打开方式->打开文件路径,就得到了反编译后的工程文件Project。
3.2.1 导入smali代码
在AndroidStudio中打开反编译后的工程文件Project,然后,在项目目录依次选中smali->设置Mark Directory as->设置为Sources root,如图5所示。
3.2.2 设置Project的SDK
接着,依次点击File->Project Structure,设置Project SDK,如图6所示。
3.2.3 配置远程调试
首先,依次点击Run->Edit Configurations->Remote JVM Debug,添加一个remote调试。设置Name为任意值,我这里设置为APP的包名com.qianyu.zhuceji,端口为8700,未占用端口均可,Use module classpath设置为Project,如图7所示。
3.2.4 Android Studio普通调试
在smali文件中的适当位置下断点,点击图8中Attach Debugger to Android Process
下一步,通过命令adb shell ps | findstr “com.qianyu.zhuceji”获得进程PID为4049,如图9所示。
另外,通过命令adb forward tcp:8700 jdwp:4049进行端口转发。接着,在模拟器中输入用户名ihui,注册码1234567887654321,点击注册按钮后触发checkSN函数调用,单步步过后即可获得用户名ihui对应的注册码9458797e3b684bde,如图10所示。
3.2.5 Android Studio Debug调试
Debug模式动态调试与普通模式动态调试唯一的区别就是,Debug模式动态调试使用命令行启动调试模式adb shell am start -D -n com.qianyu.zhuceji/.MainActivity启动APP。后续步骤与3.2.4一致,这里不予赘述。
4 IDA Pro动态调试so库
4.1 基本步骤
IDA Pro动态调试so库的基本步骤如下所示:
- 要把android_server发送到手机目录的data/local/tmp
- (给手机最高权限su)来到手机的data/local/tmp目录下,运行android_server(mv android_server as、mv 原文件名 新文件名)
- 重新打开一个cmd窗口,开启端口转发:adb forward tcp:23946 tcp:23946(这里转发的端口号和你运行android_server的端口号一定一致)该端口的命令:./android_server -p 65535
- 挂起程序(需要程序的包名+类名),命令是adb shell am start -D -n com.example.javandk1/.MainActivity,执行完毕这条命令后,手机现象:出现个弹窗,Waiting For Debugger
- 打开ddms窗口,观察调试进程有红色虫子(debug调试)(这一步可以放在第4步之前)
- 打开IDA,填写主机号,端口号(与转发端口一致),选择进程,双击进来,勾选三项,F9运行
- 回到cmd窗口,记录调试程序的端口号,在命令的port处修改,执行jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8614,等待so库加载。在模块中找到该so库,在so库中找方法:找jni_onload(动态注册)或者java_开头的方法(静态注册)
4.2 具体操作步骤
首先,由于我这里没有root过的手机,就只能使用模拟器,所以是启动android_x86_server(root过的手机应该启动android_server),这里指定端口10001,如图11所示。
然后,开启端口转发,如图12所示。
接着,在打开Android Device Monitor之后,使用命令adb shell am start -D -n com.ihui.cmakedemo/.MainActivity挂起程序,如图13所示。
下一步,打开IDA Pro,依次选择Debugger -> Attach -> Remote Linux debugger,填写主机号为127.0.0.1,端口号为10001,其中Debug options勾选三项,选择进程com.ihui.cmakedemo。
此外,执行jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8609,其中port的值在Android Device Monitor的第三列查看得到,而且程序前面的小红虫变成了小绿虫,如图14所示。
最后,在Modules中搜索到jni_onload查看动态注册方式,没有发现java_开头的静态注册方式,如图15所示。
4.3 IDA动态调试so的常见面试题
这里先提出五个常见的问题,如下所示:
- 动态调试的作用以及与我们常说的脱壳区别之处?
- IDA的下断点调试的原理?
- 有无反调试的步骤区别?以及原理?
- 反调试与反附加的区别?
- IDA动态调试so时有哪三个层次?以及如何下断点?
【注意】so的动态调试与脱壳在步骤上有很多的相似之处,关于脱壳在后面会详细介绍加壳以及脱壳的发展历程。
4.3.1 动态调试的作用以及与我们常说的脱壳区别之处?
动态调试作用有二:
其一:dump内存,即:找准时机dump出解密后的正确文件;
其二:查看每一步状态,进一步分析出正确的逻辑;
脱壳只是我们在调试系统级别的.so文件后 ,找准时机dump出正确而真实的.so文件,而动态调试只不过是手动脱壳的一种表现方式。
4.3.2 IDA的下断点调试的原理?
下断点原理:
由于下断点有硬件断点和软件断点,我们在这里只说IDA中的软件断点原理:
X86系列处理器提供了一条专门用来支持调试的指令,即INT 3,这条指令的目的就是使CPU中断(break)到调试器,以供调试者对执行现场进行各种分析。
当我们在IDA中对代码的某一行设置断点时,即:F2,调试器会先把这里的本来指令的第一个字节保存起来,然后写入一条INT 3指令,因为INT 3指令的机器码为11001100b(0xCC)当运行到这的时候CPU会捕获一条异常,转去处理异常,CPU会保留上下文环境,然后中断到调试器,大多数调试器的做法是在被调试程序中断到调试器时,会先将所有断点位置替换为INT 3的指令恢复成原来的指令,然后再把控制权交给用户。这样我们就可以愉快的开始调试了,图16是调试器的原理图:
4.3.3 有无反调试的步骤区别?以及原理?
先说无反调试:
1.adb push d:\android_server(IDA的dbgsrv目录下) /data/local/tmp/android_server(这个目录其实可以随便放,有的反调试会检测这)
2.adb shell
3.su(一定要有root权限)
4.cd /data/local/tmp
5.chmod 777 android_server(执行权限要给)
6.再开一个cmd
adb forward tcp:23946 tcp:23946(端口转发,调试手机上的某个进程要有协议支持通信)
7.打开待调试的应用程序,就可以愉快的调试了
再来说有反调试:
在很多情况下我们遇到的是有反调试并且用上面的步骤,附加进去以后直接就退出了,这样的例子数不胜数,那就是反调试惹的祸。
这时候我们就要改变调试战略了
在上文的基础上:
1.启动android_server;
2.端口转发adb forward tcp:23946 tcp:23946;
3.adb shell am start -D -n 包名/类名;
(说明:以启动模式启动,是停在加载so文件之前,包名在AndroidMainfest文件中可以找到)
4.打开IDA,附加上对应的进程之后,设置IDA中的load so的时机,在debug options中设置一下,后面会有实战部分;
5.adb forward tcp:8700 jdwp:进程号;(jdwp是后面jdb调试器的协议,转换到待调试的指定的应用程序);
6.jdb -connect com.sun.jdi.SocketAttach:hostname=localhost,port=8700(jdb进行附加);
7.可以愉快的下断点,开始调试了;
4.3.4 反调试与反附加的区别?
反调试就是阻止你进行动态调试所采用的一种手段。
反附加,在这块重要的是说jdb的反附加,很多情况下jdb会附加不上,就是会出现“无法附加到目标的VM”这样的问题那是因为在每个应用程序下,有这个android:debuggable=”true”才能调试。
4.3.5 IDA动态调试so时有哪三个层次?以及如何下断点?
我们知道在so的加载时候有个这个过程:
.init -> .init array -> JNI_Onload -> java_com_XXX
还有我们在脱壳的过程中会在一些系统级的.so中下断点比如:fopen,fget,dvmdexfileopen,等等
而.init以及.init_array一般会作为壳的入口地方,那我们索性叫它外壳级的.so文件
这里归纳为三类:
- 应用级别的:java_com_XXX;
- 外壳级别的:JNI_Onload,.init,.init_array;
- 系统级别的:fopen,fget,dvmdexfileopen;
对于在应用级别的和系统级别的就不说了比较简单容易理解,这里也是在实现篇中会重点说的,看到上面的.so的加载执行过程我们知道如果说反调试放在外壳级别的.so文件的话,我们就会遇到程序在应用级核心函数一下断点就退出的尴尬,事实上多数的反调试会放在这,那么过反调试就必须要在这些地方下断点,重点应说如何在.init_array和JNI_Onload处理下断点。
5 参考文献
[1]丰生强. Android软件安全权威指南[M]. 电子工业出版社, 2019.
[2]https://blog.csdn.net/freeking101/article/details/105910877
[3]https://blog.csdn.net/freeking101/article/details/105937026
[4]https://cloud.tencent.com/developer/article/1764588
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达,可以邮件至 xingshuaikun@163.com。