使用lldb + voltron + tmuxinator 优雅的debug C/C++
关键词:lldb、调试、逆向分析、aarch64、voltron、tmuxinator
在看雪上翻了翻一些关于Android逆向的文章,对调试来了兴趣,花了些业余时间搭建环境。尽管是以Android为例,但大多数操作系统和处理器架构原理基本类似,把调试器玩6,所有程序的运行机制将不再是秘密。
1、环境准备
1.1、工具准备
1、Linux
2、root权限的android设备(关闭SELinux),编译好的AOSP代码(可选)
3、Android NDK,下载链接,内含交叉编译工具链、调试工具(lldb、objdump、addr2line等)
4、voltron,基于Python的lldb调试终端UI插件具体安装使用参考GitHub的wiki
5、tmuxinator(终端复用),用于布局调试窗口
1.2、调试准备
把NDK下载解压后,加入bashrc环境变量
export PATH=$PATH:你的NDK安装路径/android-ndk-r26c/toolchains/llvm/prebuilt/linux-x86_64/bin
这样就可以使用lldb命令了
在Linux主机上用lldb调试Android进程是远程调试,需要Android端运行lldb-server,Linux主机上的lldb作为client连接上进行调试
1.2.1、Android端准备:
找到ndk上的lldb-server
./toolchains/llvm/prebuilt/linux-x86_64/lib/clang/17/lib/linux/aarch64/lldb-server
adb push 到 /data/local/tmp/,并执行chmod 777 lldb-server添加执行权限,后续push可执行程序,也要添加权限这一步骤。
执行开启server
./lldb-server platform --listen unix-abstract:///data/local/tmp/debug.sock --server
1.2.2、Linux端准备:
编辑文件
~/.lldbinit
command script import /home/kryo/.local/lib/python3.10/site-packages/voltron/entry.py # voltron 插件初始化,这个是安装voltron生成的,不要改动
platform select remote-android # 调试目标机是远程Android
platform connect unix-abstract-connect:///data/local/tmp/debug.sock # 链接lldb-server
settings set target.source-map ./ /mnt/wsl/repo/rock-android11/ # 指定源码映射到AOSP代码
现在输入lldb即可直接连上Android并且开始愉快的调试了。
1.2.3、voltron使用:
安装voltron后,在运行lldb调试中,可以另起终端直接输入voltron view命令查看调试视图
voltron view disasm
voltron view reg
voltron view stack
voltron view bt
voltron view bp
voltrom view mem
以查看内存为例:
voltron view mem -a 7E1668BCD0 --words 1 -v -t
并且窗口支持快捷键,n下一页,p上一页,上下方向键单行翻页,对于查看内存堆栈来说十分方便
调试时要查看的窗口太多,使用tmuxinator终端复用功能:
配置~/.tmuxinator/lldb_a64.yml
name: voltron
root: ~/
windows:
- madhax:
# layout: f8af,243x63,0,0[243x37,0,0{121x37,0,0[121x20,0,0,0,121x16,0,21,5],121x37,122,0,1},243x16,0,38{121x16,0,38,2,121x16,122,38,3},243x8,0,55,4]
panes:
- voltron view disasm # 反汇编代码
- lldb # lldb
- voltron view reg # 寄存器组
- voltron view stack # 栈 内存
- voltron view bt # 线程栈帧
- voltron view bp # 断点列表
tmuxinator start lldb_a64
启动该会话,搭建完成效果如下:
更多技巧自行搜索tmuxinator layout配置
2、修复voltron对aarch64的支持
voltron直接支持target(调试目标程序架构)arm(32位)
voltron对target是aarch64没有直接支持,不过可以参考其对ARM64的支持修改
至于为什么会这样,我猜测作者主要在IOS上工作,IOS比较习惯用ARM64,而Android和Linux比较习惯用aarch64,从pr合入情况来看,voltron作者可能已经停止对其维护。
我们需要修改文件以支持aarch64
build/lib/voltron/dbg.py
, 需要修改build目录下的文件,然后执行voltron的install.sh脚本重新安装。
diff --git a/voltron/dbg.py b/voltron/dbg.py
index 4d92f5b..d5d52b4 100644
--- a/voltron/dbg.py
+++ b/voltron/dbg.py
@@ -98,6 +98,7 @@ class DebuggerAdaptor(object):
"armv7": {"pc": "pc", "sp": "sp"},
"armv7s": {"pc": "pc", "sp": "sp"},
"arm64": {"pc": "pc", "sp": "sp"},
+ "aarch64": {"pc": "pc", "sp": "sp"},
"powerpc": {"pc": "pc", "sp": "r1"},
}
cs_archs = {}
@@ -110,6 +111,7 @@ class DebuggerAdaptor(object):
"armv7": (capstone.CS_ARCH_ARM, capstone.CS_MODE_ARM),
"armv7s": (capstone.CS_ARCH_ARM, capstone.CS_MODE_ARM),
"arm64": (capstone.CS_ARCH_ARM64, capstone.CS_MODE_ARM),
+ "aarch64": (capstone.CS_ARCH_ARM64, capstone.CS_MODE_ARM),
"powerpc": (capstone.CS_ARCH_PPC, capstone.CS_MODE_32),
}
diff --git a/voltron/plugins/view/register.py b/voltron/plugins/view/register.py
index a4a0a4a..2b08082 100644
--- a/voltron/plugins/view/register.py
+++ b/voltron/plugins/view/register.py
@@ -115,6 +115,16 @@ class RegisterView (TerminalView):
'category': 'general',
},
],
+ 'aarch64': [
+ {
+ 'regs': ['pc', 'sp', 'x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9', 'x10',
+ 'x11', 'x12', 'x13', 'x14', 'x15', 'x16', 'x17', 'x18', 'x19', 'x20',
+ 'x21', 'x22', 'x23', 'x24', 'x25', 'x26', 'x27', 'x28', 'fp', 'lr', 'cpsr'],
+ 'label_format': '{0:3s}',
+ 'value_format': SHORT_ADDR_FORMAT_64,
+ 'category': 'general',
+ },
+ ],
'powerpc': [
{
'regs': ['pc','msr','cr','lr', 'ctr',
有些功能需要我们修改其python源码添加,最近把arm上的cpsr状态寄存器显示简单修了下:
3、lldb常用调试命令
lldb调试的命令十分强大,这里按照调试的一般步骤简来归纳下常用的调试命令,用,
隔开别名,如果命令错了,lldb也会给出详细提示。
file vtable
启动工作目录下的vtable程序
process attach --pid 123
如果要调试正在运行的程序需要使用process attach命令
settings set target.source-map
settings show target.source-map
映射源码路径,可参考上述lldbinit文件,比较重要,不然lldb不显示源码,无法正常断点
add-dsym
添加带符号的调试目标文件,也是lldb断点和显示源码的关键
# 添加可执行程序带符号文件
add-dsym ~/rock-android11/out/target/product/rk3399_ROCKPI4B_Android11/symbols/vendor/bin/vtable
# 添加so库符号文件
add-dsym ~/rock-android11/out/target/product/rk3399_ROCKPI4B_Android11/symbols/system/lib64/libbinder.so
# 如果是-g -O0编译,则不需要添加
br, breakpoint
断点命令
br set -f Binder.cpp -l 171 # 文件行数断点
br set -a 0x0000007ff6ae2864 # 代码段内存地址断点
br main # 在所有main名称的函数处断点
# 当然还有更加高级的断点方式,如变量监控、条件逻辑
br delete # 删除断点
... # 断点的命令太多了
r, run
运行程序
source list
查看源码,这一步也可以在打断点前验证lldb能找到源码
source list -a $pc -c 10 # 显示PC附近10行源码
source list -a 0x0000007ff6ae0864 -c 10
n, next
源码级单行执行,执行一次跳到下一行C/C++代码,也就是不会进入函数
ni
汇编级别单行执行,执行一次跳到下一行汇编代码,不会通过跳转指令(b,bl,blx)进入子程序
si
汇编级步入,同ni,区别是可以跳进子程序
finish
跳出子程序,不小心进入log打印函数或者库函数比如new,std::iostream等函数可以跳出
p
打印变量值,p var,打印var变量值
reg read/write
读写寄存器
mem read/write
读写内存
c
释放程序继续执行,直到下次断点
process kill
强行停止程序
shell
执行shell命令,shell clear 执行clear清屏
以上命令足够大多数场景使用,有了调试,就不用使用if和print大法了。
Reference
-
一切皆可调试 一起挂上调试器 “重新认识” 世界吧。