安卓逆向学习之路——动态调试
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状态开启需要先进行端口转发