多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

CTF-Wiki Android 逆向实战:IDA 动态调试原生层程序与 so 库的完整流程

CTF-Wiki Android 逆向实战:IDA 动态调试原生层程序与 so 库的完整流程 文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载本篇技术指南系统讲解如何使用 IDA 对 Android 原生层程序进行动态调试从android_server调试服务搭建、端口转发通信链路到独立原生二进制文件的 Run 模式调试与 APK 内嵌 so 库的 Attach 模式调试并给出基址偏移计算目标函数地址的完整方法。读完之后读者可以在一台已 root 的 Android 设备上独立完成“搭建环境 → 调试 C/C 原生程序 → attach 到 App 并命中 so 库中的 JNI 函数”的全流程并理解每一步背后的 ptrace、dlopen 与 JNI 加载原理。一、适用对象与必备工具本文覆盖两类调试对象基本原生层程序即简单的、独立编译为 ARM 汇编的 C/C 可执行程序非 PIE 或 PIE 均可视 Android 版本而定原生 so 程序APK 中依赖的.so共享库例如通过 JNI 暴露给 Java 层的函数。so 文件本身不能单独运行它一般依赖 APK 宿主程序来加载因此两类对象的调试方式不同前者用 Run 模式后者用 Attach 模式这也是本篇内容的分界线。必备工具已经 root 的手机root 是硬性前提因为android_server需要 root 权限才能 ptrace 任意目标进程IDA Pro需按被调试程序的位数32/64 位选择对应版本adb用于文件推送、端口转发与安装 APK。二、基本原理android_server 与 ptrace在使用 IDA 动态调试原生层程序时实际承担调试工作的是 IDA 自带的android_server程序位于 IDA 安装目录的dbgsrv文件夹下。该程序被推送到手机上并以 root 身份运行监听23946端口当 IDA 通过 adb 端口转发把调试协议送达该端口后android_server会利用ptrace技术注入到要调试的程序中完成断点设置、单步、内存读写等调试操作。与 ptrace 直接相关的系统机制是/proc/pid/status中的TracerPid字段每个进程都有一个 status 文件其中TracerPid字段用于标识该进程被哪个进程 ptrace 跟踪如果进程没有被其他进程 ptrace该字段返回0否则返回跟踪者的 pid。从源码结构看这也是调试环境排错的重要依据如果 attach 后列表里只能看到/bin/sh这样的低权限进程说明android_server并未以 root 权限运行它无权 ptrace 目标进程。三、基本调试环境搭建无论调试什么原生层程序环境搭建步骤都相同核心是确保IDA 与手机之间的通信链路建立成功。3.1 上传 android_server 并修改权限将 IDAdbgsrv目录下的android_server推送到设备并赋予执行权限adb push android_server /data/local/tmp/ adb shell chmod 755 /data/local/tmp/android_server原文档正文中此处提及过“23496”字样但实际生效的端口以命令与 IDA 默认配置中的23946为准下文统一使用 23946。3.2 以 root 权限启动 android_serveradb shell # 进入adb su # 切换root ./data/local/tmp/android_server # 启动android_server监听 23946 端口3.3 建立本地与手机的端口转发建立本地计算机端口与手机端口 23946 的转发当 PC 上有客户端连接本地 23946 端口时数据会被转发到手机上的对应端口android_server因此收到 IDA 发来的调试数据。adb forward前后两个端口分别是本地 PC 端口与手机端口的映射adb forward tcp:23946 tcp:23946这条命令实际建立的是adb server与手机上adbd之间的通信完整链路如下IDA -- adb server -- adbd -- android_server 电脑 | 手机3.4 执行频率与脚本化建议三步中第 1 步只需执行一次而第 2、3 步每次会话都必须执行adb forward规则会随 adb server 重启而失效。因此可以把这两步封装成一个脚本并加入系统 PATH之后一条命令即可拉起整个调试环境。四、基本原生程序调试Run 模式基本原生程序指的是普通编译为 ARM 汇编的 C/C 程序。下面以一个经典的 JNI 教学示例debugnativeapp可执行文件为例CTF-Wiki 原文以吾爱破解论坛的同名教程示例为参照。4.1 推送原生程序到设备首先把原生程序放到 Android 设备中并修改权限使其可执行adb push debugnativeapp /data/local/tmp/ adb shell chmod 755 /data/local/tmp/debugnativeapp4.2 配置 Run 调试根据原生层程序的位数选择相应的 IDA 版本然后在 IDA 中选择菜单Debugger → Run → Remote ARM Linux/Android debugger其中重要参数的含义参数含义Application被调试程序在手机上的绝对路径如/data/local/tmp/debugnativeappDirectory被调试程序所在目录Hostname与哪个主机通信这里与本地 PC 通信填localhost即可Debug options 中主要勾选三个 Events使程序在入口点、线程启动时、library 加载时自动暂停也可以全部勾选启动程序后IDA 即接管执行流可正常单步、下断点。4.3 常见启动错误与处理现象可能原因与处理can not load plugin手机没有 rootandroid_server无法工作确认已 root 且android_server以 root 运行弹出与 linker 相关的提示直接点击Cancel即可继续原文档注明具体原因尚不明确属可忽略的提示error: only position independent executables (PIE) are supported一般出现在 Android 5 以上版本系统强制 PIE。两种解法使用android_server的PIE 版本或利用 010 Editor 将可执行 ELF 文件头中 ELF header 的e_type字段修改为ET_DYN (3)五、so 原生程序调试Attach 模式5.1 为什么 so 不能用 Run 模式so 文件不能单独运行——通常由其他程序APK 宿主调用 so 文件中的某些函数Android 中的 so 文件一般都依赖于 APK 文件。因此不能像第四节那样直接 Run而必须先让宿主 App 跑起来再让 IDA attach 到其进程并等待 so 库被动态加载。以下以经典示例debugjniso.apk内含libdebugjniso.so为例。前提同样是第三节的基本调试环境已经搭建完成。5.2 安装 APKadb install debugjniso.apk5.3 以调试模式启动 App必须以调试模式启动程序这样 App 才会进入 “Waiting for debugger” 挂起状态方便 IDA 捕捉并 attachadb shell am start -D -n packagename/.MainActivity这里-D表示 debug mode。从 ctf-wiki 的 Android 动态调试 一文可知Android 是否允许调试的判定顺序为先看 boot.img 中ro.debuggable是否为 1可用adb shell getprop ro.debuggable查询为 1 则所有应用可调试否则要求应用AndroidManifest.xml的application元素含android:debuggabletrue。对于拿不到源码的第三方 App常见做法是解包后补上android:debuggabletrue再重打包签名AVD 模拟器默认ro.debuggable即为 1。5.4 Attach 目标进程启动 IDA Pro点击Debugger → Attach → Remote ARM Linux/Android debuggerHostname 填localhostport 默认为 23946即本地监听端口点击 OK 后列出设备上可 attach 的进程使用CtrlF快速过滤定位目标 App 进程并选中确认。注意如果列表中只有/bin/sh一个进程可被 attach说明android_server没有以 root 权限运行。Attach 成功后IDA 会看到进程的内存映射与已加载库此时再点击Debugger → Debugger options按第四节相同思路勾选相关 Events入口点/线程/库加载暂停。为什么是 attach 成功之后才设置这些选项如果在开始 attach 时就设置attach 完成时这些状态其实已经失效断点事件不会生效——可以自行实验验证。5.5 恢复 App 执行ddms 与 jdbattach 时 App 界面停留在 “Waiting for debugger”。此时需要通过 Java 层的调试协议把 App “放行”并恢复执行。第一步打开 ddmsDalvik Debug Monitor Service选中要调试的 App 进程ddms 的操作其实等价于手动执行以下命令# 显示手机所有可供调试的用户进程 adb jdwp # 在PC的xxx端口与手机的相应端口之间建立连接方便通信 adb forward tcp:xxx jdwp:pid之所以倾向用 ddms 而不手敲命令是因为手动方式还需要自己确定 APK 对应的进程号比较麻烦。第二步使用 jdb 在 Java 层把 App attach 到电脑xxx 即 ddms 中看到的端口一般是默认的 8700jdb -connect com.sun.jdi.SocketAttach:hostnamelocalhost,portxxx执行后进入 jdb 交互提示符原文档实录输出jdb -connect com.sun.jdi.SocketAttach:hostname127.0.0.1,port8700 设置未捕获的java.lang.Throwable 设置延迟的未捕获的java.lang.Throwable 正在初始化jdb... 此时再观察手机应用已经继续运行不再是 “Waiting for debugger”。至此电脑与手机 App 虚拟机之间建立了 JDB 通信Java 层的调试通道就绪而 IDA 一侧则已经 attach 在同一个进程上负责原生层。5.6 确定原生函数的绝对地址静态侧查偏移再开一个 IDA导入 APK 中的 so 库在 Export 中查找目标 JNI 函数可读出其相对偏移示例中jniString的偏移为0xC38。动态侧等库加载回到调试 IDA 按F9连续运行直到怎么按都不再运行——此时程序断在 linker 处说明 so 尚未加载。触发加载在 App 界面点击“设置标题”按钮该按钮对应的 Java 方法内部调用了 JNI 原生 API从而触发dlopen加载 so。再次观察 IDA可以看到库已加载B3B05000: loaded /data/app/com.droider.debugjniso-1/oat/arm/base.odex B39B9000: loaded /system/lib/hw/gralloc.msm8974.so B39B2000: loaded /system/lib/libmemalloc.so B39AA000: loaded /system/lib/libqdutils.so B3B00000: loaded /data/app/com.droider.debugjniso-1/lib/arm/libdebugjniso.so计算绝对地址按CtrlS跳转到该库的基地址0xB3B00000然后套用公式绝对地址 基地址 相对偏移即0xB3B00000 0xC38 0xB3B00C38得到jniString的运行地址。5.7 下断点验证结果按G跳转到0xC38即可看到目标函数libdebugjniso.so:B3B00C38 Java_com_droider_debugjniso_TestJniMethods_jniString libdebugjniso.so:B3B00C38 LDR R1, (unk_B3B02148 - 0xB3B00C4C) libdebugjniso.so:B3B00C3C STMFD SP!, {R4,LR} libdebugjniso.so:B3B00C40 LDR R3, [R0] libdebugjniso.so:B3B00C44 ADD R1, PC, R1 ; unk_B3B02148 libdebugjniso.so:B3B00C48 MOV LR, PC在函数入口处按F2下断点再次F9运行同时观察手机可以看到 App 的标题变成了你好Nativemethod—— 这正是该 JNI 函数执行的结果so 原生程序的基本调试步骤到此完成。若中途弹出 “add map” 对话框直接点击 Cancel然后 Apply 即可。六、源码视角为什么 so 会在“点击按钮”时才加载ctf-wiki 的 so 介紹 文档基于 AOSP 源码梳理了 so 的加载链路可以帮助解释第五节的调试现象Java 层入口System.loadLibrary(name)等价于Runtime.getRuntime().loadLibrary(name)。Runtime.loadLibrary会根据 ClassLoader 的findLibrary找到libxxx.so的文件名最终统一走doLoadnative 层doLoad通过synchronized调用nativeLoadjava_lang_Runtime.cpp中注册为Dalvik_java_lang_Runtime_nativeLoad后者先更新LD_LIBRARY_PATHApp 由 zygote fork 而来自身 lib 目录不在默认路径上再调用dvmLoadNativeCode动态链接器dvmLoadNativeCode先经findSharedLibEntry判重未加载则以dlopen(pathName, RTLD_LAZY)打开共享库do_dlopen找到soinfo后执行CallConstructors()按序调用.init函数与.init_array数组JNI_OnLoad加库成功后用dlsym(handle, JNI_OnLoad)查找初始化函数存在则调用并校验其返回的 JNI 版本。综合来看so 的初始化执行顺序为.init函数 →.init_array中的函数 →JNI_OnLoad函数不存在则跳过。由此可以解释第五节两个关键现象为什么断在 linker 且看不到 soApp 启动早期尚未调用loadLibraryso 尚未被dlopen只有当 Java 侧某方法首次触发原生 API 调用示例中为“设置标题”按钮时linker 才会真正加载libdebugjniso.so。这也意味着库加载的时机决定了你观察到的基地址因此必须“先触发、再取基址”为什么基址要动态获取so 是位置相关的运行时映像每次加载的映射地址由系统决定示例中为0xB3B00000静态偏移必须叠加运行时基址才有效。另外注意dvmLoadNativeCode的判重逻辑同一个库一旦加载后续相同路径的加载请求会直接返回且不允许同一库挂到多个 ClassLoader 上因此在调试中重复触发同一按钮不会导致库被二次加载断点行为也不会改变。七、注意事项与排错清单模拟器异常使用模拟器调试 APK 出现异常时很可能是应用检测了自身是否运行在模拟器环境下建议改用真机真机卡在单条指令真机调试时也可能异常停在某条指令上此时可手动执行该条指令然后把 PC 设置为下一条指令的地址继续PIE 限制Android 5 以上遇到 “only position independent executables (PIE) are supported” 时换用android_server的 PIE 版本或用 010 Editor 把 ELF 头e_type改为ET_DYN(3)权限问题attach 列表里只有/bin/sh即android_server未以 root 运行can not load plugin则大概率是设备未 rootadd map 弹窗Cancel 后 Apply 即可不影响调试会话失效adb连接重置或 adb server 重启后23946 的 forward 规则会丢失需要重新执行第三节第 2、3 步。八、延伸与相关文档原文档中“基本原生程序调试-法 2”与“多线程调试”两节在 ctf-wiki 中仍为占位“待补充”/空节本文以法 1Run 模式为基准展开后续可关注仓库更新Android 動態調試調試判定原理、DDMS 工具ro.debuggable、android:debuggable判定顺序与调试工具总览so 介紹loadLibrary/dlopen 加載流程本文第六节的源码级依据IDA 動態調試 smali 代碼Java 层 smali 的 IDA 动态调试Android 逆向基本介紹 与 Android 關鍵代碼定位静态/动态方法选择与代码定位思路so 靜態分析示例与本篇动态调试互为补充的 so 静态分析实例。赞分享文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载相关推荐ctf-wiki Windows 逆向实战CheckRemoteDebuggerPresent 反调试检测原理与绕过ctf wiki Windows 逆向实战CheckRemoteDebuggerPresent 反调试检测原理与绕过 本文基于 ctf wiki 中 Wind文档网络安全教程ctf-wiki Windows 逆向:花指令的编写原理、IDA 修复方法与 2017 看雪 CTF 例题动态破解实战ctf wiki Windows 逆向:花指令的编写原理、IDA 修复方法与 2017 看雪 CTF 例题动态破解实战 花指令 junk code 是 Wind文档网络安全教程ctf-wiki Linux 逆向实战ptrace 反调试检测原理与两种绕过方法ctf wiki Linux 逆向实战ptrace 反调试检测原理与两种绕过方法 本篇基于 ctf wiki 仓库的 detect dbg.md https:文档网络安全教程上一篇如何打造完美的GoGoGo视觉体验终极设计规范指南下一篇攻克Linux音频无缝集成Shairport Sync MPRIS接口开发实战指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表