引言
动态调试是逆向工程中“透视”应用运行逻辑的核心手段。相比静态分析,动态调试能实时观察内存状态、函数调用栈和寄存器变化,尤其适用于破解加密算法、分析复杂业务逻辑等场景。本文将以LLDB为核心工具,详解如何对iOS App进行动态调试,覆盖从环境搭建到高级技巧的全流程。
一、核心工具与环境准备
1. 必要工具
-
LLDB:Apple官方调试器,支持源码级调试与内存操作(集成在Xcode中)
-
debugserver:iOS端调试服务程序(需通过越狱设备获取)
-
iOS越狱设备:推荐使用Checkra1n或Unc0ver越狱的iPhone(iOS 14-15.5)
-
Python脚本:通过LLDB的Python API实现自动化调试
2. 环境配置步骤
步骤1:安装debugserver
-
从越狱设备的
/Developer/usr/bin/
复制debugserver
到Mac -
添加调试权限(Entitlements):
<key>com.apple.springboard.debugapplications</key> <true/> <key>get-task-allow</key> <true/> <key>task_for_pid-allow</key> <true/> <key>run-unsigned-code</key> <true/>
重签名:
codesign -s - --entitlements entitlements.plist -f debugserver
步骤2:配置SSH与端口转发
ssh -p 2222 root@iOS_IP # 默认越狱SSH端口
iproxy 1234 1234 # 将设备端口1234映射到本地
二、动态调试六步法
1. 附加目标进程
命令:
# 启动debugserver监听
debugserver *:1234 -a "AppName"
# Mac端连接
lldb
(lldb) process connect connect://iOS_IP:1234
技巧:
-
若App有反调试保护,需先使用
kill -0
绕过或Frida Hookptrace
2. 定位关键函数地址
方法1:符号断点
(lldb) breakpoint set -n "-[UIViewController viewDidLoad]"
方法2:ASLR偏移计算
(lldb) image list -o -f | grep TargetApp
-
计算真实地址:
基址 + 偏移
3. 设置断点与观察点
普通断点:
(lldb) br s -a 0x0000000100123456
内存读写观察点:
(lldb) watchpoint set expression -w write -- 0x16dff0a50
4. 寄存器与内存操作
查看寄存器:
(lldb) register read x0
修改内存值:
(lldb) memory write 0x12345678 "AAAA"
导出内存数据:
(lldb) memory read --outfile /tmp/dump.bin --count 256 0x12345678
5. 堆栈回溯与流程控制
查看调用栈:
(lldb) bt
单步执行:
(lldb) ni # 汇编级单步
(lldb) s # 源码级单步
继续执行:
(lldb) continue
6. 实时Hook与代码注入
执行任意代码:
(lldb) expr -- (void)printf("Hooked!\\n")
调用Objective-C方法:
(lldb) expr -- (void)[[NSUserDefaults standardUserDefaults] setObject:@"test" forKey:@"key"]
三、实战案例:破解登录加密算法
场景描述
目标App的登录请求参数包含加密字段encryptedToken
,需逆向其生成逻辑。
调试过程
-
定位加密函数
-
通过字符串搜索找到
encryptedToken
的赋值位置 -
设置断点:
br s -a 0x100012300+0x1234
-
-
分析寄存器与参数
(lldb) po $x1 # 查看Objective-C方法名 (lldb) x/s $x2 # 查看输入字符串地址 (lldb) register read x3 # 查看密钥指针
-
追踪加密结果
-
在函数返回前(
ret
指令)捕获X0寄存器的值 -
导出内存:
memory read --format bytes --count 32 $x0
-
-
验证算法
-
对比多次调试结果,确认是否为AES-CBC模式
-
提取密钥与IV参数,使用Python还原加密过
-
四、高级技巧:LLDB自动化
1. Python脚本扩展
# 自动化断点回调
def breakpoint_callback(frame, bp_loc, dict):
print("Hit breakpoint! RAX =", frame.registers["rax"].value)
return False
target = lldb.debugger.GetSelectedTarget()
bp = target.BreakpointCreateByAddress(0x12345678)
bp.SetScriptCallbackFunction("breakpoint_callback")
2. 自定义命令
# 实现命令"dump_encrypted"
def dump_encrypted(debugger, command, result, dict):
frame = debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
encrypted_ptr = frame.FindRegister("x0").GetValueAsUnsigned()
data = debugger.GetSelectedTarget().GetProcess().ReadMemory(encrypted_ptr, 32, lldb.SBError())
print(bytes(data).hex())
lldb.debugger.HandleCommand('command script add -f dump_encrypted dump_encrypted')
五、避坑指南
-
代码签名问题
-
调试前需重签名App:
codesign -fs "Your Cert" --entitlements entitlements.plist Target.app
-
关闭SIP(macOS)与AMFI(iOS):
amfi_get_out_of_my_way=0x1
-
-
多线程调试
-
使用
thread list
查看所有线程 -
thread continue -t 2
恢复指定线程
-
-
性能优化
-
禁用无关符号加载:
settings set target.load-script-from-symbol-file false
-
使用
target modules list
管理加载模块
-
六、扩展思路
-
逆向系统框架
-
结合Dyld Shared Cache分析UIKit内部逻辑
-
-
与Frida联动
-
通过
frida-ll-bridge
实现双向通信
-
-
非越狱调试
-
利用
debugserver
定制版本绕过签名验证
-
结语
LLDB动态调试如同为逆向工程师装上“X光透视眼”,不仅能观察应用运行时的每个细节,更能实时干预逻辑走向。掌握本文所述技巧后,读者可快速定位关键代码、破解加密逻辑,甚至挖掘隐藏功能。但切记,技术探索需在合法合规的边界内进行。
“技术是钥匙,但选择打开哪扇门取决于你。”
附录