安卓逆向学习之路——动态调试
0x01 smail的动态调试
使用的apk来自正己第四课的教程demo:教程demo
参考正己的《安卓逆向这档事》第五课操作:《安卓逆向这档事》五、1000-7=?&动态调试&Log插桩
教程使用mt管理器对xml文件进行更改,但很多时候该用电脑还得用电脑,所以这边提供电脑的操作
以下两种方法更改apk中的AndroidManifest.xml使其可被调试:
1.JEB
使用JEB进行电脑上的操作:https://www.pnfsoftware.com/jeb/manual/android-debugging/#generating-debuggable-apk
首先将apk转换为可动态调试的版本
1  | jeb_wincon.bat -c --makeapkdebug -- file.apk  | 
之后对apk进行签名
1  | apksigner.bat sign -ks %USERPROFILE%\.android\debug.keystore --ks-pass pass:android file_debuggable.apk  | 
这个debug.keystore需要先用Android Studio创建个项目然后生成调试apk才能得到
2.apktool
apktool提供了解包和打包的方法,总的来说还是挺方便的
1  | apktool d file.apk  | 
解压出来之后就可以改xml文件了,在<application>标签中加上
1  | android:debuggable="true"  | 
重新打包即可
1  | apktool b filepath -o file.apk  | 
别忘了重签名
1  | apksigner.bat sign -ks %USERPROFILE%\.android\debug.keystore --ks-pass pass:android file.apk  | 
之后就可以开始愉快调试了
首先使用adb以调试模式启动对应的包
1  | adb shell am start -D -n 包名/类名  | 
adb shell am start -D -n
adb shell am start -D -n 包名/类名
am start -n 表示启动一个activity
am start -D 表示将应用设置为可调试模式
之后再JEB上下断点然后attach上去就可以了
0x02 Native的动态调试
参考:IDA调试Android native、IDA 动态调试原生层程序、IDA7.5安卓10动态调试还有IDA动态调试so
考虑到手上的IDA8.3不支持Arm架构,用7.7来搞
首先要搞清楚位数
1  | adb shell getprop ro.product.cpu.abilist  | 
我的是arm64,所以决定使用android_sever64
1  | adb push android_server64 /data/local/tmp/  | 
push上去之后要设置为可执行
1  | adb shell chmod 777 /data/local/tmp/android_server64  | 
建立本地计算机 23496 端口与手机端口 23946 的通信。当 PC 有客户端连接 23946 端口时,其发送的信息都将被转发到手机的相应的端口,这时 android_server64就收到相应的数据,从而使得 IDA 可以调试程序。前面的 tcp 的端口指的是本地 PC 的端口,后面的指的是手机的端口。
1  | adb forward tcp:23946 tcp:23946  | 
教程中的原生程序太过老旧了,实机上根本无法运行,主要是因为这个原生程序并没有使用开启PIE,导致连系统都无法执行它(参考Android5.0中的安全增强功能)
因此执行原生程序这一步从/system/bin/里面找一个测试就行了
这边用了ls,可以成功执行,有警告,但不影响程序执行
重头戏在so库的调试上
教程中的apk也是老得不成样子了,实机上无法运行,这边整一个其他地方的来
首先将其改为能调试的,在application标签中加上
1  | android:debuggable="true"  | 
android:extractNativeLibs = "true"时,gradle 打包时会对工程中的 so 库进行压缩,最终生成 apk 包的体积会减小。但用户在手机端进行 apk 安装时,系统会对压缩后的 so 库进行解压,一般解压到/data/app/某一目录下,从而造成用户安装 apk 的时间变长。如果为 false 则不会解压到该目录。
minSdkVersion >= 23并且Android Gradle plugin >= 3.6.0情况下,打包时默认android:extractNativeLibs=false, 如果该属性为false,虽然 IDA 可以正常附加,但是无法加载对应的 so 进行调试,所以如果未开启 so 压缩会直接导致程序执行的时候 ida 不能够识别到响应的 so 加载,导致无法定位到 so 中的代码从而无法停在断点。对于未开启 so 压缩的情况需要通过重打包来修改该字段以便可以定位到 so。
针对性的,也有相应的模块,用于 Hook 安卓的安装器,让其在安装时将该属性的值设为 true,从而能够正确的调试,例如项目ForceExtractNativeLibs即通过 Xposed 进行 Hook 在安装时重置该属性。
注:毕竟是个病毒样本,虽然音频被替换了但是还是很抽象,这边建议解包后顺便把asset里面的0.mp3换成无声的一段音频
之后签个名安装
1  | adb install test_dbgable.apk  | 
然后从apk里面提取so文件,用IDA打开,下个断点

之后启动手机的调试端
1  | adb shell  | 
另开一个cmd窗口,端口转发
1  | adb forward tcp:23946 tcp:23946  | 
用调试模式开启对应Activity
1  | adb shell am start -D -n com.sgzh.dt/com.androlua.Welcome  | 
包名在开头

对应的activity在action.MAIN附近可以找到

之后在IDA中设置进程选项

然后调试器选项里面打开
1  | 进程入口点暂停  | 
然后就可以选择对应包名,附加到进程了

之后执行
1  | adb shell ps | findstr 包名  | 
可以找到这个进程相关信息

各段含义如下
1  | USER PID PPID USIZE RSS WCHAN PC NAME  | 
内容 含义 USER 进程当前所属的用户 PID 进程ID PPID 父进程ID VSIZE 进程的虚拟内存大小,以KB为单位 RSS 进程实际占用的内存大小,以KB为单位 WCHAN 进程正在睡眠的内核函数名称; PC 计算机中提供要从“存储器”中取出的下一个指令地址的寄存器 NAME 进程状态(注:状态说明见下)和名称 
NAME中进程状态的不同值如下
状态 含义 D 不可中断的睡眠态 R 运行态 S 睡眠态 T 被跟踪或已停止 Z 僵尸态 W 进入内存交换(从内核2.6开始无效) X 死掉的进程 < 高优先级 N 低优先级 L 有些页被锁进内存 s 包含子进程 l 多线程,克隆线程 + 位于后台的进程组 
这里重点是获取PID(虽然好像在IDA里面都能直接看见)
之后执行如下命令,将 8700 端口转发到调试进程(8700是固定的,一般来说不会更改)
1  | adb forward tcp:8700 jdwp:<PID>  | 
借用以下ctfwiki的图片解释这个转发(虽然但是DDMS已经被废除了)

执行完上面的那一条指令,我们的电脑已经与手机的 app 虚拟机之间建立了通信。
之后需要使用 jdb 在 java 层来将 apk 应用 attach 到我们的电脑上,这里使用如下的命令。
1  | jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700  | 
此为固定的
之后在IDA中F9运行,直到这个出现

选择相同之后,就会断在下断点的位置了
至此调试完成了
(这边还是不太建议直接调so库的,因为问题很多,不稳定)
0x03 附录
附一下可以一键执行调试的脚本
1.杀死IDA的调试服务器as64进程
killIDAdbg.bat
1  | adb shell < killIDAserver.sh  | 
killIDAserver.sh
1  | su  | 
2.开启IDA的调试服务器as64
IDAdbg1.bat
1  | @echo on %关闭回显命令%  | 
3.dbg状态开启app
IDAdbgD.bat
1  | @echo on %关闭回显命令%  | 
4.非dbg状态开启app
IDAdbgnD.bat
1  | @echo on %关闭回显命令%  | 
注意非dbg状态开启需要先进行端口转发
