安卓app的动态调试
调试环境
IDA版本:7.0
进入IDA软件的数据路径 ida.app/Contents/MacOS/dbgsrv
注意: 根据so文件目录判断目标文件位数,v7a - 32位,v8是64位。使用不同的位数的ida进行调试将文件夹内对应位数的android server文件传入手机
adb push android_server /data/local/tmp/
- 进入手机,运行android server
# 进入手机 - 多台设备使用时使用-s参数设备名
adb shell
# 切换管理员 - 才能进入tmp目录
su
# 进入android server所在目录
cd /data/local/tmp/
# 给予android server最高权限
chmod 777 android_server
# 运行android server 指定端口需要携带参数-p且无空格,例如`-p23456`
./android_server
- 将android server端口转发(需要使用新端口)
# 将PC上的23946与手机23946建立转发
# 参数中前者为local(PC端口) 后者为remote(手机端口)
adb forward tcp:23946 tcp:23946
# 查看已转发端口
adb forward --list
# 取消转发(填写PC端口)
adb forward --remove tcp:11111
adb forward —-remove-all
以上准备工作完成后便可通过IDA进行调试
一般情况下会将android server进行重命名,并自定义端口号运行。防止app进行进程名与默认端口号检测
动态调试
动态调试分为 Debug调试 与 普通调试。其中IDA调试栏选项的attach与run分别对应 附加进程 与 新建进程
Debug调试
脱壳或反调试时常用此方法
挂起程序
- 手机打开目标程序
- 依次打开 IDA -> Go -> Debugger -> Attach -> Remote ARMLinux/Android debugger
- 填写监听地址。由于端口转发到本机,Host填写
127.0.0.1
,Port填写转发到主机的端口后点击OK,弹出手机进程列表 - 选择目标程序的运行进程,点击OK,完成进程附加
加载目标SO文件
- 依次打开 Debugger -> Debugger options...
- 勾选三项
- Suspend on process entry point (进程入口点挂起)
- Suspend on thread start/exit (创建与退出线程时挂起)
- Suspend on library load/unload (加载与卸载库时挂起)
- 启动程序,图形按钮 或 快捷键F9
- 等待目标so库加载请求的弹窗,关闭后在Module窗口中搜索目标so库,在目标so中搜索目标函数或JNI_Onload,双击后下断点。再F9运行
普通调试
挂起程序
- 通过jadx-gui打开app,在 Resources -> AndroidManifest.xml(配置清单)中查找make launch(搜索LAUNCHER),找到activity中的
android:name=
,得到app的包名+类名 - 使用命令挂起程序
adb shell am start -D -n <packageName>/.<className>
- 执行完成后手机弹窗 Waiting For Debugger
- 通过jadx-gui打开app,在 Resources -> AndroidManifest.xml(配置清单)中查找make launch(搜索LAUNCHER),找到activity中的
得到程序端口:打开DDMS窗口,debug图标(红色的bug)会出现在目标进程前,记录目标程序运行的端口号
<port>
ddms
依次打开 IDA -> Go -> Debugger -> Attach -> Remote ARMLinux/Android debugger。填写监听地址 127.0.0.1+转发端口。
选择目标程序的进行进程。勾选三项后F9让程序进入运行状态,此步骤与Debug调试一致
目标程序跳过ddms断点继续运行,此处端口为ddms端口
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1 port=<port>
此时ddms窗口中红色debug图标变为绿色,且ida可正常弹窗即将加载的so库,跳过弹窗使其正常加载。如需调试JNI_OnLoad
或静态注册函数Java_*
,先在函数中F2下断点后再F9运行
问题:
- 打不开DDMS的办法
- adb shell ps 得到进程PID
<pid>
adb forward tcp:xxx jdwp:<pid>
- 执行jdb
- adb shell ps 得到进程PID
- 调试时触发反调试或其他原因导致崩溃时重新调试
- 从挂起程序开始
adb shell am start -D -n ...
- 重新启动IDA并附加
- 勾选三项后F9
- 查看ddms中app端口是否变化
- 进行放手操作
jdb -connect ...
调试JNI_OnLoad
- 在右侧Modules窗口中搜索目标so文件
- 双击查看so中的函数,找到JNI_OnLoad。在汇编指令区F2下断点
- 取消三项勾选,使so正常运行不被非断点打断
- F9运行
动态调试时修改寄存器
在条件判断时,常需要手动修改寄存器的值。例如 反调试检测、会员检测 等,不符合条件会触发分支逻辑
CMP RO 0
BNE sub_xxx
通过在 寄存器窗口双击 或 快捷键E
进行修改
动态调试时修改指令
由于# 三级流水线的存在, 需在程序执行到前三条处指令前就完成对目标指令的修改
- hex窗口中找到目标指令: 在执行到前三条时通过PC定位目标指令(选中目标指令 -> hex窗口右键 -> Synchronize with -> IDA View-PC)
- 修改指令: 在hex窗口中快捷键F2进行编辑。编辑完成后使用快捷键F2进行保存。常改为全0使其变成
NOP
- 指令机器码计算: 查询《ARM指令》进行计算 或 使用https://armconverter.com/ ,输入目标指令和目标指令地址
ARM指令共32位(4字节)从高位到低位依次查询ARM指令。以跳转指令B
为例说明计算方式
- 4位(32-28): 条件。指令是否含有条件,如
BNE
中的NE
- 3位(27-25): 固定为
101
- 1位(24): 是否带链接,如
BL
中的L
- 余下24位(23-0): 目标地址与该指令的相对偏移量(相差多少条指令),即
( <目标地址> - PC ) / 4(单条指令4字节)
。由于# 三级流水线,PC值比当前执行的指令多两条指令(即指令地址+8)
00001BD0 0B 00 00 0A BEQ loc_1c04
; 偏移量为(0x1c04 - (0x1BD0+8)) / 4 = 11条 = 1011
; 0000 101 0 000000000000000000001011 -> 0A00000B -> 0B 00 00 0A (小端存放)
关于IDA调试时的几点
断点的实现:在断点处设置一个异常,调试器对其进行捕获,从而使程序暂停到断点处
勾选三项后的弹窗:找不到so的map文件(找不到so的调试符号信息,详情搜索调试符号),点击取消即可,不影响后期调试
寄存器或CPSR中的值若为黑色,则说明指令执行后对应空间内存储的值未发生改变。否则为蓝色
调试快捷键
- F2: 下断点
- F4: 执行至光标处
- F7: 单步执行
- F9: 继续运行
- 寄存器快捷键E - 修改寄存器值
- Ctrl + N: 设置PC
关于汇编代码区颜色:
- 黑色:可识别(可Tab得到C语言伪代码)
- 红色:不可识别
代码区常用快捷键
- Tab:得到伪代码
- D:选中代码识别成数据
- U:选中代码识别成未识别数据
- P:选中代码识别成函数(代码块)
- C:选中代码识别成指令(行代码)因此经常先转化成代码(C)再将多条代码转化成函数(P)
动态调试时的CPSR窗口
对IDA动态调试时常用CPSR标识位的说明
- N : 计算结果是否为负数
- Z : 计算结果是否为0
- C : 数值运算时是否出现进位或错位
- T : 当前是否为Thumb指令集
调试可执行程序
初始流程与调试apk一致,运行android_server,启动端口转发
点击菜单栏 Debugger -> Run -> Remote ARMLinux/Android debugger
Application 填写可执行文件的路径绝对路径
Directory 填写可执行文件所在文件夹的绝对路径
Parameters 填写可执行文件的运行参数。没有参数留空
Hostname 127.0.0.1
Post android_server转发端口
依次打开 Debugger -> Debugger options... ,勾选三项
更多内容查看后续IDA相关的文章