使用IDA Pro动态调试Android APP
版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/
关于 android_server
android_server 是 IDA Pro 在 Android 设备上运行的一个调试服务器。
通过在 Android 设备上运行android_server,IDA Pro 可以远程调试 Android 应用程序,并实现断点设置、内存查看、寄存器检查等功能。
IDA Pro 通过 adb(Android Debug Bridge)将调试命令发送给 android_server,然后 android_server 在 Android 设备上执行这些命令,并将结果返回给 IDA Pro。
调试环境准备
把 IDA安装目录/dbgsrv 下的 android_server64 push 到设备 /data/local/tmp 路径下
adb push "D:\App\IDA_Pro\IDA_Pro_7.7\dbgsrv\android_server64" /data/local/tmp/as
进入 adb shell 启动 androd server
# 获取 root 权限
su
# 给 android server 增加执行权限
chmod +x /data/local/tmp/as
# 通过指定端口启动 android_server,假设你要使用端口 12345
/data/local/tmp/as -p 12345
关于获取手机 root 权限和开启全局调试可以参考下面两篇文章:
将 adb 12345 端口转发到本地 12345 端口
adb forward tcp:12345 tcp:12345
附加到正在运行的进程
在调试器类型中选择【Remote ARM Linux/Android debugger】
调试设置 Host=127.0.0.1,Port=12345
选择你要动态调试的 app 进程 点击 Search(Alt + T) 可以通过搜索关键字查找进程
启动前附加进程
首先,通过 Androird Killer 反编译 apk ,在 AndroidManifest.xml 中搜索 android.intent.action.MAIN 找到 app 的启动入口
或者进入 adb shell 通过下面的命令查找最近启动的 Activity
dumpsys activity activities | grep "Hist" | head -n 5
* Hist #0: ActivityRecord{1088151 u0 com.cyrus.example/.MainActivity t66}
keysPaused=false inHistory=true visible=true sleeping=false idle=true mStartingWindowState=STARTING_WINDOW_SHOWN
* Hist #0: ActivityRecord{3afa4ee u0 com.android.launcher3/.lineage.LineageLauncher t56}
keysPaused=false inHistory=true visible=false sleeping=false idle=true mStartingWindowState=STARTING_WINDOW_NOT_SHOWN
* Hist #0: ActivityRecord{f256169 u0 com.shizhuang.duapp/.modules.home.ui.HomeActivity t58}
以调试模式启动 app
adb shell am start -D -n com.shizhuang.duapp/com.shizhuang.duapp.modules.home.ui.SplashActivity
启动DDMS(sdk\tools\monitor.bat)
解决jdk版本过高导致的DDMS启动失败问题:
解压jdk到本地
在 monitor.bat 前面加上下面的代码(强制使用jdk8)
- @echo off
- REM 设置 JDK 路径
- set JAVA_HOME=D:\App\jdk-8
REM 更新 PATH 变量
set PATH=%JAVA_HOME%\bin;%PATH%
REM 验证 JDK 设置
echo JAVA_HOME is set to %JAVA_HOME%
java -version
IDA 附加到你要动态调试的 app 进程 现在你就可以做一下在 APP 启动前需要完成的一些操作了,比如在 APP 启动前 Hook 某个函数。
创建一个 jdb_connect.bat,使用 jdb 命令恢复程序执行
@echo off
REM 设置使用 JDK8
set JAVA_HOME=D:\App\jdk-8
REM 更新 PATH 变量
set PATH=%JAVA_HOME%\bin;%PATH%
REM 验证 JDK 设置
echo JAVA_HOME is set to %JAVA_HOME%
java -version
REM 使用 jdb 命令恢复程序执行
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
使用Python代码调试进程
下面脚本代码是基于IDA Pro 7.7.220118,不同版本之间可能会有差异。
IDA6到IDA7 api变化对比:https://hex-rays.com/products/ida/support/ida74_idapython_no_bc695_porting_guide.shtml
1. 调用函数来列出加载的 .so 文件
File -> Script command,然后运行下面的 Python 脚本
import idaapi
def list_loaded_so_files():
# 获取所有段(模块)信息
seg_qty = idaapi.get_segm_qty()
if seg_qty == 0:
print("No segments loaded.")
return
print("Loaded .so files:")
# 遍历所有段,获取段信息
for i in range(seg_qty):
seg = idaapi.getnseg(i)
if seg:
seg_name = idaapi.get_segm_name(seg)
# 如果段名以 .so 结尾,则打印模块信息
if seg_name.endswith(".so"):
seg_start = seg.start_ea
seg_end = seg.end_ea
seg_size = seg_end - seg_start
print(f"Name: {seg_name}, Base: {hex(seg_start)}, Size: {seg_size}")
# 调用函数来列出加载的 .so 文件
list_loaded_so_files()
2. hook dlopen函数
Hook dlopen 函数并打印出加载的库
import idaapi
import idc
class DlopenHook(idaapi.IDB_Hooks):
def __init__(self):
idaapi.IDB_Hooks.__init__(self)
def dbg_bpt(self, tid, ea):
# 当断点被触发时,打印库信息
print(f"Breakpoint hit at: {hex(ea)}")
# 获取 dlopen 的参数
esp = idc.get_reg_value('esp')
# 假设库名称在栈上参数位置 + 4
lib_name_addr = esp + 4
lib_name = idc.get_strlit_contents(lib_name_addr)
if lib_name:
print(f"dlopen called with: {lib_name.decode('utf-8')}")
else:
print("dlopen called with unknown library")
return 0
def main():
# 获取 dlopen 函数的地址
dlopen_addr = idc.get_name_ea_simple("dlopen")
if dlopen_addr == idc.BADADDR:
print("dlopen function not found.")
return
# 设置断点
idaapi.add_bpt(dlopen_addr)
print(f"Breakpoint set at dlopen: {hex(dlopen_addr)}")
# 实例化钩子并添加到 IDA Pro
hook = DlopenHook()
hook.hook()
# 运行主函数
main()
断点调试
在调试过程中,你可以使用以下命令来控制程序的执行:
Step Into (F7):进入当前行调用的函数内部。
Step Over (F8):跳过当前行,执行到下一行。
Run (F9):继续运行程序,直到下一个断点或程序结束。
解决端口占用问题
如果在启动 android server 时提示端口占用
/data/local/tmp/as -p 12345
IDA Android 64-bit remote debug server(ST) v7.7.27. Hex-Rays (c) 2004-2022
0.0.0.0:12345: bind: Address already in use
列出占用端口的进程
lsof | grep 12345
as 12679 root 3u IPv4 0t0 246861 TCP :12345->:0 (LISTEN)
as 12679 root 4u IPv4 0t0 523893 TCP :12345->:43865 (CLOSE_WAIT)
强制停止占用端口的进程
kill -9 12679
现在,重新启动 android server 就可以了
自动化脚本
创建 android-server-start.bat,实现一键启动 android server 并转发端口
@echo off
REM 启用超级管理员权限
adb root
REM 启动android server
adb shell "/data/local/tmp/as -p 12345 > /dev/null 2>&1 &"
REM 等待 3 秒
timeout /t 3
REM 查看android server进程是否启动成功
adb shell "lsof | grep 12345"
REM 转发到本地12345端口
adb forward tcp:12345 tcp:12345
pause