GDB - Debugging stripped binaries

本文演示如何在Linux上使用GDB调试stripped binary,包括编译过程、符号解析和使用技巧。
部署运行你感兴趣的模型镜像

A few days ago I had a discussion with a colleague on how to debug a stripped binary on linux with GDB.

Yesterday I also read an article from an ex-colleague at EPITA on debugging with the dmesg command.
I therefore decided to write my own article, here I will demonstrate how to use GDB with a stripped binary.

Test program

First of all, here is the small C program we will be working on:

#include <stdio.h>
 
 __attribute__ ((noinline)) void fun(int test)
{
  printf("value: %d\n", test);
}
 
int main()
{
  int v = 21;
  fun(v);
}

You can notice we have used a GCC attribute to prevent the compiler from inlining the function.

Symbols

GCC and symbols

When compiling a program, GCC (for example) adds symbols to the binary to help the developer during debugging. There are several types of symbols but the goal of this article is not to explain them.

Contrarily to popular beliefs, GCC does write symbols to an object file even in release mode (with the -O3switch). That's why even with a release binary, you can do this:

$ gcc -O3 -m32 test.c
$ gdb a.out
[...]
Reading symbols from /home/felix/test/a.out...(no debugging symbols found)...done.
(gdb) b main
Breakpoint 1 at 0x8048453

Listing symbols

We can use the nm command to list all symbols in our binary:

$ nm a.out
08049f28 d _DYNAMIC
08049ff4 d _GLOBAL_OFFSET_TABLE_
0804853c R _IO_stdin_used
w _Jv_RegisterClasses
[...]
08048470 T __libc_csu_init
U __libc_start_main@@GLIBC_2.0
U __printf_chk@@GLIBC_2.3.4
[...]
080483e0 t frame_dummy
08048410 T fun
08048440 T main

Symbols and debugging

With the previous information, GDB knows the bounds of all functions so we can ask the assembly code for any of them. For example thanks to nm we know there is a fun function in the code, we can ask its assembly code:

(gdb) disas fun
Dump of assembler code for function fun:
0x08048410 <+0>: push %ebp
0x08048411 <+1>: mov %esp,%ebp
0x08048413 <+3>: sub $0x18,%esp
0x08048416 <+6>: mov 0x8(%ebp),%eax
0x08048419 <+9>: movl $0x8048530,0x4(%esp)
0x08048421 <+17>: movl $0x1,(%esp)
0x08048428 <+24>: mov %eax,0x8(%esp)
0x0804842c <+28>: call 0x8048340 <__printf_chk@plt>
0x08048431 <+33>: leave
0x08048432 <+34>: ret

Discarding Symbols

Symbols can be removed from the binary using the strip command:

$ gcc -O3 -m32 test.c
$ strip -s a.out
$ nm a.out
nm: a.out: no symbols

Why stripping you may ask ? Well, the resulting binary is smaller which mean it uses less memory and therefore it probably executes faster. When applying this strategy system-wide, the responsiveness of the system will probably be better. You can check this by yourself: use nm on /bin/*: you won't find any symbols.

The problem

Okay, there are no more symbols now, what does it change when using GDB ?

$ gdb a.out
[...]
(gdb) b main
Function "main" not defined.
(gdb) b fun
Function "fun" not defined.

We cannot add a breakpoint now, even on the main function.

The solution

Locating the entry point

Debugging is still possible, but it is more complicated. First we need the memory address of the entry point:

(gdb) info file
Symbols from "a.out".
Local exec file:
`a.out', file type elf32-i386.
Entry point: 0x8048350

With GDB we can add a breakpoint on a memory address:

(gdb) b *0x8048350
Breakpoint 1 at 0x8048350
(gdb) run
Starting program: a.out

Breakpoint 1, 0x08048350 in ?? ()

Disassembling code

We managed to add a breakpoint on the entry point of our binary (and we reached that breakpoint), but we are still having some troubles with our favorite commands:

(gdb) disas
No function contains program counter for selected frame.
(gdb) step
Cannot find bounds of current function

As GDB does not know the bounds of the functions, it does not know which address range should be disassembled.

Once again, we will need to use a command working at a lower level.
We must use the examine (x) command on the address pointed by the Program Counter register, we ask a dump of the 14 next assembly instructions:

(gdb) x/14i $pc
=> 0x8048350: xor %ebp,%ebp
0x8048352: pop %esi
0x8048353: mov %esp,%ecx
0x8048355: and $0xfffffff0,%esp
0x8048358: push %eax
0x8048359: push %esp
0x804835a: push %edx
0x804835b: push $0x80484e0
0x8048360: push $0x8048470
0x8048365: push %ecx
0x8048366: push %esi
0x8048367: push $0x8048440
0x804836c: call 0x8048330 <__libc_start_main@plt>
0x8048371: hlt

Libc initialization

By looking at the code, you might be asking yourself: "Where the hell are we??"
The C runtime has to do some initialization before calling our own main function, this is handled by the initialization routine __libc_start_main (check its prototype here).

Before calling this routine, arguments are pushed on the stack in reverse order (following the cdecl calling convention). The first argument of __libc_start_main is a pointer to our main function, so we now have the memory address corresponding to our code: 0x8048440. This is what we found with nm earlier!
Let's add a breakpoint on this address, continue and disassemble the code:

(gdb) b *0x8048440
Breakpoint 2 at 0x8048440
(gdb) c
Continuing.

Breakpoint 2, 0x08048440 in ?? ()
(gdb) x/10i $pc
=> 0x8048440: push %ebp
0x8048441: mov %esp,%ebp
0x8048443: and $0xfffffff0,%esp
0x8048446: sub $0x10,%esp
0x8048449: movl $0x15,(%esp)
0x8048450: call 0x8048410
0x8048455: xor %eax,%eax
0x8048457: leave
0x8048458: ret
0x8048459: nop

This looks like our main function, the value 21 (0x15) is placed on the stack and a function (the address corresponds to fun) is called.
Afterwards, the eax register is cleared because our main function returns 0.

Additional commands

To step to the next assembly instruction you can use the stepi command.
You can use print and set directly on registers:

(gdb) print $eax
$1 = 1
(gdb) set $eax = 0x2a
(gdb) print $eax
$2 = 42

You can also dump the value of all registers:

(gdb) info registers
eax 0x2a 42
ecx 0xffffd6e4 -10524
edx 0xffffd674 -10636
ebx 0xf7fb8ff4 -134508556
esp 0xffffd64c 0xffffd64c
ebp 0x0 0x0
esi 0x0 0
edi 0x0 0
eip 0x8048440 0x8048440

That's all for now!

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

<think> 我们遇到的问题是:在运行aarch64-openwrt-linux-musl-gdb-stripped时,缺少libncurses.so.5库。 解决方法通常有两种:安装包含该库的包,或者创建符号链接(如果已有其他版本的库)。 步骤: 1. 确认系统是否已安装libncurses5,或者是否有其他版本的libncurses。 2. 如果没有,则安装libncurses5;如果有更高版本,可以尝试创建符号链接。 具体操作: 首先,我们可以尝试安装libncurses5: 对于Ubuntu/Debian系统:sudo apt-get install libncurses5 如果已经安装了libncurses5,但仍然出现错误,可能是因为库文件的位置不在动态链接器搜索路径中。可以检查: - 库文件是否存在:通常位于/usr/lib/x86_64-linux-gnu/或/lib/x86_64-linux-gnu/(64位系统) - 使用`ldconfig -p | grep libncurses.so.5`来查看是否有该库的缓存。 如果没有找到,可以手动创建符号链接(假设有更高版本,比如libncurses.so.6): sudo ln -s /usr/lib/x86_64-linux-gnu/libncurses.so.6 /usr/lib/x86_64-linux-gnu/libncurses.so.5 但是注意:创建符号链接可能会引起其他程序的不兼容,因为不同版本的库可能有不同的ABI。 另外,考虑到这是交叉编译工具链中的gdb,可能这个gdb是动态链接的,并且是针对目标架构的,但这里缺少的库是主机上的库(因为gdb-stripped是在主机上运行的)。因此,我们需要确保主机上安装了32位库还是64位库。 根据错误信息,是找不到libncurses.so.5,这个库通常是32位或64位的,我们需要根据gdb的架构来安装对应的版本。 检查gdb的架构: file aarch64-openwrt-linux-musl-gdb-stripped 如果显示是32位的,则需要安装32位库:sudo apt-get install libncurses5:i386 如果是64位的,则安装64位库:sudo apt-get install libncurses5 另外,如果是在64位系统上运行32位的gdb,还需要确保有32位的运行库支持。 如果上述方法都不行,可以考虑使用静态链接的gdb,这样就不依赖系统的动态库。但需要重新编译gdb。 总结步骤: 1. 检查gdb的架构:`file aarch64-openwrt-linux-musl-gdb-stripped` 2. 根据架构安装对应的libncurses5: 64位:`sudo apt install libncurses5` 32位:`sudo apt install libncurses5:i386` 3. 如果已经安装,检查库文件是否存在,并确保在链接器缓存中(使用ldconfig更新缓存:`sudo ldconfig`)。 4. 如果安装后仍然找不到,可以手动创建符号链接(如果有其他版本的libncurses,比如libncurses.so.6,可以创建一个libncurses.so.5的软链接)。 注意:创建软链接要谨慎,避免破坏其他依赖。 如果以上都不行,考虑使用静态链接的GDB版本,或者重新编译GDB。 由于错误信息是“error while loading shared libraries”,所以问题出在主机上缺少动态库,与目标平台无关。 </think>### 解决GDB缺少libncurses.so.5库的问题 #### 错误原因分析 该错误表明交叉编译的GDB工具链**运行在错误的系统环境**中: - `aarch64-openwrt-linux-musl-gdb-stripped` 是ARM架构的交叉调试器 - 当前运行在x86_64架构的Ubuntu系统上 - 缺少ARM架构的libncurses库依赖 --- ### 解决方案 #### 方法1:安装兼容库(推荐) ```bash # 安装ARM架构的ncurses兼容库 sudo apt install libncurses5:arm64 # 设置多架构支持 sudo dpkg --add-architecture arm64 sudo apt update # 完整安装依赖 sudo apt install libncurses5:arm64 libtinfo5:arm64 ``` #### 方法2:使用QEMU用户模式仿真 ```bash # 安装QEMU用户模式 sudo apt install qemu-user-static binfmt-support # 创建库加载包装脚本 echo '#!/bin/sh exec qemu-aarch64-static -L /usr/aarch64-linux-gnu/ "$@"' > arm_gdb_wrapper chmod +x arm_gdb_wrapper # 通过包装器运行 ./arm_gdb_wrapper ./aarch64-openwrt-linux-musl-gdb-stripped uclited core-uclited ``` #### 方法3:静态链接重建(需源码) ```bash # 下载GDB源码 wget https://ftp.gnu.org/gnu/gdb/gdb-13.2.tar.xz # 配置静态编译 ./configure --host=aarch64-linux-gnu \ --target=aarch64-openwrt-linux-musl \ --enable-static \ --disable-shared # 编译安装 make -j$(nproc) && sudo make install ``` #### 方法4:使用Docker容器环境 ```dockerfile # Dockerfile FROM arm64v8/ubuntu:22.04 RUN apt update && apt install -y gdb COPY uclited core-uclited / ``` ```bash # 构建并运行 docker build -t arm-gdb . docker run -it --rm arm-gdb gdb uclited core-uclited ``` --- ### 验证解决方案 ```mermaid graph LR A[问题] --> B{解决方案} B --> C[安装兼容库] B --> D[QEMU仿真] B --> E[静态编译] B --> F[Docker容器] C --> G[验证] D --> G E --> G F --> G G --> H[成功调试] ``` 成功标志: ```bash # 应显示正常GDB提示符 (gdb) bt #0 0x0000fffff7e456b0 in ?? () #1 0x0000aaaad6c0d1a0 in main () at main.c:25 ``` --- ### 预防措施 1. **环境标准化** ```bash # 使用buildroot管理工具链 make sdk ./output/host/relocate-sdk.sh ``` 2. **依赖检查脚本** ```bash #!/bin/bash readelf -d $1 | grep NEEDED ``` 3. **容器化工作流** ```bash podman run --rm -v $PWD:/work arm64v8/ubuntu:22.04 \ aarch64-openwrt-linux-musl-gdb uclited core-uclited ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值