静态分析根本不够!IDA Pro 动态调试 Android 应用的完整实战

版权归作者所有,如有转发,请注明文章出处:cyrus-studio.github.io/blog/

一、前言

在进行 Android 应用逆向分析时,很多时候仅靠静态分析(例如反编译 Java 代码或反汇编 so 库)是远远不够的。开发者常常会使用混淆、加壳、动态加载等技术,使得代码逻辑在静态视角下难以完整还原。此时,动态调试就显得尤为重要。

通过动态调试,我们可以在应用运行过程中实时观察其行为,例如:

  • 精确跟踪函数调用过程

  • 监控寄存器与内存的变化

  • 分析加密/解密算法的输入与输出

  • 定位并绕过反调试逻辑

IDA Pro 作为最常用的逆向分析工具之一,不仅提供强大的静态反编译能力,还支持配合 android_server 在 Android 设备上实现远程动态调试。这样,我们既能利用 IDA 的反编译界面理解代码结构,又能在实际执行过程中验证推测的逻辑,从而大大提升分析效率。

二、调试环境准备

必备工具:IDA Pro、adb、android_server

测试设备要求(已 root 的 Android 手机 / 模拟器)

手机 root 和 开启全局调试 参考:

三、android_server 简介

android_server 是 IDA Pro 在 Android 设备上运行的远程调试服务端程序。

通过在设备上启动 android_server,IDA Pro 可以与 Android 应用建立调试会话,实现如下功能:

  • 设置与管理断点

  • 查看与修改内存

  • 检查与操作寄存器

IDA Pro 与 android_server 之间的通信通常依赖 ADB(Android Debug Bridge) 。IDA Pro 将调试命令通过 ADB 转发至 android_server,后者在设备本地执行相应操作,并将结果回传给 IDA Pro,从而实现完整的远程调试流程。

四、部署 android_server

根据设备的 CPU 架构从 IDA 安装目录/dbgsrv 下获取 android_server 二进制文件

把 android_server push 到设备 /data/local/tmp 路径下

kotlin 复制代码
adb push "D:\App\IDA_Pro\IDA_Pro_7.7\dbgsrv\android_server64" /data/local/tmp/as

进入 adb shell 启动 androd server

bash 复制代码
# 获取 root 权限
su
# 给 android server 增加执行权限
chmod +x /data/local/tmp/as

# 通过指定端口启动 android_server,假设你要使用端口 12345
/data/local/tmp/as -p 12345

将 adb 12345 端口转发到本地 12345 端口

复制代码
adb forward tcp:12345 tcp:12345

五、IDA Pro 连接调试

附加到正在运行的进程

在调试器类型中选择 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

ini 复制代码
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

arduino 复制代码
adb shell am start -D -n com.shizhuang.duapp/com.shizhuang.duapp.modules.home.ui.SplashActivity

启动DDMS(sdk\tools\monitor.bat)

解决jdk版本过高导致的DDMS启动失败问题:

vbnet 复制代码
@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 命令恢复程序执行

vbnet 复制代码
@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 变化对比:hex-rays.com/products/id...

1. 调用函数来列出加载的 .so 文件

File -> Script command,然后运行下面的 Python 脚本

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 函数并打印出加载的库

python 复制代码
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 时提示端口占用

perl 复制代码
/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

列出占用端口的进程

rust 复制代码
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)

强制停止占用端口的进程

bash 复制代码
kill -9 12679

现在,重新启动 android server 就可以了

九、自动化脚本

创建 android-server-start.bat,实现一键启动 android server 并转发端口

bash 复制代码
@echo off

REM 启用超级管理员权限
adb root

setlocal

REM 获取 frida server 的 PID,如果已经启动则强制停止进程
for /f "delims=" %%i in ('adb shell pidof as') do set PID=%%i

REM 判断 PID 是否为空
if defined PID (
    echo Found PID: %PID%
    adb shell kill -9 %PID%
) else (
    echo No as process found.
)

endlocal

REM 启动 android server
adb shell "/data/local/tmp/as -p 12345 > /dev/null 2>&1 &"

REM 等待 2 秒
timeout /t 2

REM 查看 android server 进程是否启动成功
adb shell "lsof | grep 12345"

REM 转发到本地 12345 端口
adb forward tcp:12345 tcp:12345

pause

运行效果如下:

相关推荐
阿巴斯甜12 小时前
必看6
android
angerdream12 小时前
Android手把手编写儿童手机远程监控App之SQLite详解
android
阿巴斯甜12 小时前
必看5
android
雪铃儿13 小时前
Shorebird 之外,Flutter Android 热更新还有什么选择
android·前端
张筱竼14 小时前
Android开发中的MVC、MVP与MVVM详解
android
阿巴斯甜16 小时前
必看4
android
Carson带你学Android17 小时前
Android 17 最后一个 Beta 发布,7 件事必须现在做
android·ai编程
ooseabiscuit17 小时前
Laravel 9.x重磅升级:PHP8新特性全解析
android
帅次17 小时前
深入 MaterialTheme:掌握 ColorScheme 与 Typography 的设计核心
android·kotlin·gradle·android jetpack·compose