目录
  1. 1. 一、为什么需要调试 Native 层
  2. 2. 二、环境配置
  3. 3. 三、附加进程并设置断点
  4. 4. 四、分析 JNI 函数表
  5. 5. 五、实战:Crack 一个 Native License 校验
  6. 6. 六、IDA 调试常用快捷键
  7. 7. 面试常考问题
【逆向安全技术-实战篇】IDA工具调试so源码

一、为什么需要调试 Native 层

许多应用将核心逻辑放在 Native 层(.so 文件),包括加密算法、签名校验、反调试检测等。Java 层只做简单调用,真正的安全防护逻辑都在 C/C++ 代码中。要深入分析这些逻辑,必须掌握 IDA Pro 调试 so 文件的技术。

IDA Pro 是逆向工程领域最强大的反汇编和调试工具,支持 x86、ARM、ARM64 等多种架构。

二、环境配置

# 1. 将 IDA 目录下的 android_server 推送到设备
adb push dbgsrv/android_server /data/local/tmp/

# 2. 赋予执行权限并启动服务
adb shell chmod 755 /data/local/tmp/android_server
adb shell /data/local/tmp/android_server

# 3. 端口转发
adb forward tcp:23946 tcp:23946

若使用 Android 12+(API 31+),android_server 需较高版本(IDA 7.7+)才能兼容。

三、附加进程并设置断点

步骤:

  1. IDA Pro → Debugger → Attach → Remote ARM Linux/Android debugger
  2. Hostname: 127.0.0.1, Port: 23946
  3. 在弹出的进程列表中找到目标进程,点击 OK
  4. 在 Modules 窗口找到目标 .so 文件,双击加载
  5. 在反汇编窗口中找到函数,按 F2 设置断点
# 以调试模式启动应用(确保 IDA 能 Attach 到)
adb shell am start -D -n com.example/.MainActivity
# 等待 IDA attach 后,jdb 恢复进程
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700

四、分析 JNI 函数表

每个 Native 方法在动态注册时都会通过 RegisterNatives 或静态注册的 JNI_OnLoad 绑定。在 IDA 中搜索关键结构:

// 静态注册的函数名格式(在 IDA 中搜索 .rodata 段)
Java_com_example_ClassName_methodName

// 动态注册的 JNINativeMethod 数组结构
typedef struct {
const char* name; // Java 方法名
const char* signature; // 方法签名,如 "(Ljava/lang/String;)Z"
void* fnPtr; // 函数指针 → 这就是要分析的目标地址
} JNINativeMethod;

在 IDA 中搜索 JNINativeMethod 结构体的交叉引用,可追踪到 JNI_OnLoadRegisterNatives 的调用链,还原 Java 方法和 Native 函数的映射关系。

五、实战:Crack 一个 Native License 校验

假设目标 so 中有一个校验函数:

; 典型的 license 校验逻辑
BL check_signature ; 调用签名校验
CMP R0, #0 ; 比较返回值
BEQ license_failed ; 若 0 则跳转到失败
; ... 正常流程 ...

license_failed:
MOV R0, #0 ; 返回 0 表示失败
POP {PC}

绕过方法:

  • Patch NOP:将 BEQ license_failed 改为 NOP(Edit → Patch program → Change byte,将跳转指令改为 NOP,ARM32 下 NOP0xE1A00000
  • 修改寄存器:在 CMP R0, #0 之后暂停,将 R0 改为 1
  • 修改判断条件:将 BEQ(Branch if EQual)改为 BNE(Branch if Not Equal)

六、IDA 调试常用快捷键

快捷键 功能
F2 设置/取消断点
F7 单步进入(Step Into)
F8 单步跳过(Step Over)
F9 继续运行
Ctrl+F7 运行到光标处
Ctrl+S 查看段信息
Space 切换图形/列表视图

面试常考问题

Q1:JNI 静态注册和动态注册的区别?如何分别定位?
A:静态注册函数名遵循 Java_包名_类名_方法名 格式,直接在 IDA 的 Exports 表中就能找到。动态注册通过 RegisterNativesJNI_OnLoad 中绑定,函数名不固定,需要通过分析 JNINativeMethod 数组或 Hook RegisterNatives 来还原映射关系。

Q2:ARM32 和 ARM64 在逆向时的注意事项?
A:ARM32 使用 32 位寄存器(R0-R12, SP, LR, PC),ARM64 使用 64 位寄存器(X0-X30, SP, PC)。两种架构的函数调用约定不同:ARM32 前 4 个参数用 R0-R3 传递,ARM64 前 8 个参数用 X0-X7 传递。IDA 分析时需要加载正确的架构版本。

Q3:如何在 IDA 中处理被混淆的 Native 代码?
A:常见手段包括:使用 IDA Python 脚本进行去混淆(如常量传播、死代码消除);利用 Frida 等工具 Hook 关键函数获取运行时真实参数和返回值;结合 Unicorn 或 QEMU 进行模拟执行;在 IDA 中使用 Keypatch 插件(https://github.com/keystone-engine/keypatch)进行指令补丁分析。

打赏
  • 微信
  • 支付宝

评论