使用lldb + voltron + tmuxinator 优雅的debug C/C++

639 阅读2分钟

使用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

mem_view

并且窗口支持快捷键,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启动该会话,搭建完成效果如下:

lldb_gif.gif

更多技巧自行搜索tmuxinator layout配置

2、修复voltron对aarch64的支持

voltron直接支持target(调试目标程序架构)arm(32位)

voltron对target是aarch64没有直接支持,不过可以参考其对ARM64的支持修改

至于为什么会这样,我猜测作者主要在IOS上工作,IOS比较习惯用ARM64,而Android和Linux比较习惯用aarch64,从pr合入情况来看,voltron作者可能已经停止对其维护。

arm64 和 aarch64 的区别

我们需要修改文件以支持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状态寄存器显示简单修了下:

reg_bug_fix.png

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