GDB调试之栈指针破坏

本文通过一个实例展示了栈指针被破坏的情况,分析了源代码和使用GDB查看栈帧内容,发现栈上非法操作导致的问题。在另一台电脑上的调试结果显示了__stack_chk_fail()函数指针,暗示栈指针错误。尽管在复杂代码库中定位此类问题颇具挑战,但理解栈指针的工作原理对于有效调试至关重要。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

记录一个因为栈上非法操作导致栈指针被破坏的例子。

源代码如下:

#include <stdio.h>
#include <string.h>


char name[] = "aabbccddeeffgghhiijjkkllmmnnooppqqrrsstt";

void fun()
{
    char buf[3];
    strcpy(buf, name);
}
int main(void)
{
    fun();
    return 0;
}

运行时出现了段错误,然后来看内存转储后得到的内容。

1、首先查看栈帧的内容:

(gdb) bt
#0  0x6a696968 in ?? ()
#1  0x6c6b6b6a in ?? ()
#2  0x6e6d6d6c in ?? ()
#3  0x706f6f6e in ?? ()
#4  0x72717170 in ?? ()
#5  0x74737372 in ?? ()
#6  0xbff60074 in ?? ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)

栈中所有函数指针全部显示成“?”,从中无法获取任何信息,这种情况可以怀疑是栈缓冲溢出。

然后看最后1行,它就提示我们有可能是栈指针出现的错误,所以我们需要做的事情就是去查看这个栈指针怎么回事。

注:我还在另外一个电脑上实验了这个程序,可能是GDB版本不同还是什么,另外一个电脑上最顶上的几个栈指针是对的,但是可以注意到的一点是它内部有一个函数指针是“__stack_chk_fail()”,这很明显的就告诉我们是栈指针出现问题了。

2、查看栈上的内容:

(gdb) x/30c $esp-15
0xbff6a311:	9
<think>我们正在处理一个关于使用GDB查看汇编代码以分析栈溢出问题的请求。根据引用内容,我们知道栈溢出通常是由于向栈中写入的数据超过了为其分配的空间,从而覆盖了返回地址或重要数据,导致程序崩溃。以下是使用GDB进行汇编级调试的步骤: ### 步骤1:编译程序时包含调试信息 在编译程序时,必须使用`-g`选项来包含调试信息,这样GDB才能显示源代码和行号等信息。 ```bash gcc -g -o test test.c # 假设源文件为test.c ``` ### 步骤2:运行程序生成核心转储文件(core dump) 如果程序因为栈溢出而崩溃,我们需要生成核心转储文件以便后续分析。首先,确保系统允许生成核心转储文件: ```bash ulimit -c unlimited ``` 然后运行程序,直到它崩溃并生成core文件。 ### 步骤3:启动GDB并加载核心转储文件 使用以下命令启动GDB并加载可执行文件和核心转储文件: ```bash gdb ./test core # 假设可执行文件为test,core文件为core ``` ### 步骤4:查看崩溃时的调用栈 在GDB中,使用`backtrace`(或简写`bt`)命令查看程序崩溃时的调用栈: ``` (gdb) bt ``` 这将显示函数调用序列,帮助我们定位崩溃发生在哪个函数中。 ### 步骤5:查看汇编代码 为了分析栈溢出,我们需要查看发生崩溃的函数的汇编代码。使用`disassemble`(或简写`disas`)命令: ``` (gdb) disas function_name # 如果知道函数名 或 (gdb) disas address_range # 如果知道地址范围 ``` 如果不知道函数名,可以先通过`bt`找到发生崩溃的函数,然后使用`frame`命令切换到该帧,再使用`disas`查看当前函数的汇编代码: ``` (gdb) frame n # n是帧号,如frame 0表示当前帧 (gdb) disas ``` ### 步骤6:分析栈帧 在汇编代码中,我们需要关注函数的入口(prologue)和出口(epilogue)指令,这些指令通常用于设置和恢复栈帧。 - **函数入口**:通常包括`push %rbp`(保存旧的基指针)和`mov %rsp, %rbp`(设置新的基指针)。 - **函数出口**:通常包括`leave`(相当于`mov %rbp, %rsp`和`pop %rbp`)和`ret`(返回)。 栈溢出通常发生在函数内部,当向栈上的缓冲区写入超过其分配大小的数据时,会覆盖返回地址(位于`%rbp`上方)。在汇编代码中,我们可以计算缓冲区的起始地址(通常是`%rbp`减去一个偏移量)和返回地址的位置(`%rbp+8`)。 ### 步骤7:检查寄存器和栈内容 使用以下命令查看寄存器的值: ``` (gdb) info registers ``` 特别是栈指针(`rsp`)和基指针(`rbp`)的值,它们定义了当前栈帧的范围。 查看栈内容可以使用: ``` (gdb) x/40x $rsp # 查看从rsp开始的40个字(word)的内容,以十六进制显示 ``` ### 步骤8:定位溢出点 在栈溢出时,返回地址被覆盖会导致程序跳转到非法地址。我们可以检查崩溃时指令指针(`rip`)的值,以及返回地址是否被修改: ``` (gdb) p $rip (gdb) x $rbp+8 # 查看返回地址 ``` 如果返回地址被修改,那么它可能指向一个无效地址,导致段错误。 ### 示例分析 假设我们有一个函数`vulnerable`,其中包含一个局部缓冲区`buf`,大小为10字节。在汇编代码中,我们可能会看到: ``` 0x0000000000400566 <+0>: push %rbp 0x0000000000400567 <+1>: mov %rsp,%rbp 0x000000000040056a <+4>: sub $0x10,%rsp # 分配16字节栈空间(包括buf和可能的对齐) ``` 这里,`buf`的地址可能是`%rbp-0x10`(实际上,由于对齐和编译器优化,可能不是精确的10字节,而是16字节)。如果我们向`buf`写入超过10字节的数据(比如16字节以上),就可能覆盖保存的`%rbp`和返回地址。 ### 注意事项 - 在64位系统中,函数参数通常通过寄存器传递(前6个参数),但局部变量仍然在栈上分配。 - 栈溢出攻击可能利用这一点覆盖返回地址,控制程序流。 ### 相关问题 1. 如何防止栈溢出攻击?[^1] 2. 在GDB中如何设置断点以监控栈内容的变化? 3. 除了栈溢出,还有哪些常见的内存安全问题? 4. 如何通过编译器选项(如`-fstack-protector`)来检测栈溢出?[^2] 5. 在Linux中如何调整栈大小限制?[^3] [^1]: 栈溢出攻击可以通过使用安全的函数(如`fgets`代替`gets`)和栈保护机制来防止。 [^2]: 编译器选项`-fstack-protector`可以在函数中插入栈保护代码,检测栈溢出。 [^3]: 使用`ulimit -s`可以查看和设置栈大小限制,例如`ulimit -s 8192`设置栈大小为8MB。</think>### 使用GDB查看汇编代码分析栈溢出的完整指南 #### 步骤1:编译程序并启用调试信息 ```bash gcc -g -fno-stack-protector -z execstack -o vulnerable vulnerable.c ``` - `-g`:包含调试信息 - `-fno-stack-protector`:禁用栈保护机制(便于演示) - `-z execstack`:允许栈执行(仅用于实验环境) #### 步骤2:启动GDB并设置汇编语法 ```bash gdb ./vulnerable (gdb) set disassembly-flavor intel # 设置Intel汇编语法 ``` #### 步骤3:查看关键函数汇编代码 ```c // vulnerable.c 示例代码 void vulnerable_function() { char buffer[64]; gets(buffer); // 危险函数,导致栈溢出 } int main() { vulnerable_function(); return 0; } ``` 在GDB中查看函数汇编: ```gdb (gdb) disassemble vulnerable_function Dump of assembler code for function vulnerable_function: 0x0804845b <+0>: push ebp 0x0804845c <+1>: mov ebp,esp 0x0804845e <+3>: sub esp,0x48 ; 分配栈空间(0x48=72字节) 0x08048461 <+6>: lea eax,[ebp-0x40] ; buffer起始地址(ebp-0x40) 0x08048464 <+9>: mov DWORD PTR [esp],eax 0x08048467 <+12>: call 0x8048320 <gets@plt> ; 调用危险函数 0x0804846c <+17>: leave 0x0804846d <+18>: ret End of assembler dump. ``` #### 步骤4:关键内存区域分析 | 内存位置 | 内容说明 | 偏移量示例 | |----------------|------------------------|------------| | `ebp-0x40` | 缓冲区起始位置 | -64字节 | | `ebp` | 保存的ebp值 | 0 | | `ebp+0x4` | 返回地址(关键目标) | +4字节 | | `ebp+0x8` | 函数参数起始位置 | +8字节 | #### 步骤5:设置断点并运行 ```gdb (gdb) break *0x08048467 # 在gets调用前设置断点 (gdb) run ``` #### 步骤6:检查栈状态(触发溢出前) ```gdb (gdb) x/32xw $esp # 查看栈内容(32个4字节字) 0xffffd550: 0xffffd560 0x00000000 0xf7e2a4d3 0xffffd574 0xffffd560: 0x00000000 0x00000000 0x00000000 0x00000000 # 缓冲区 0xffffd570: 0x00000000 0x00000000 0x08048480 0xffffd588 # 保存的ebp 0xffffd580: 0x0804848d 0xf7ffd000 0x08048480 0x00000000 # 返回地址 ``` #### 步骤7:触发溢出并分析 当输入超过64字节的数据时: ```gdb (gdb) continue 输入: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCC ``` 检查溢出后的栈状态: ```gdb (gdb) x/32xw $esp 0xffffd550: 0xffffd560 0x00000000 0xf7e2a4d3 0xffffd574 0xffffd560: 0x41414141 0x41414141 0x41414141 0x41414141 # 缓冲区被'A'填满 0xffffd570: 0x41414141 0x41414141 0x41414141 0x42424242 # 覆盖保存的ebp(BBBB) 0xffffd580: 0x43434343 0xf7ffd000 0x08048480 0x00000000 # 覆盖返回地址(CCCC) ``` #### 步骤8:分析崩溃原因 ```gdb (gdb) continue Program received signal SIGSEGV, Segmentation fault. 0x43434343 in ?? () # 程序尝试跳转到0x43434343('CCCC') ``` 使用`backtrace`确认调用栈损坏: ```gdb (gdb) bt #0 0x43434343 in ?? () #1 0x42424242 in ?? () #2 0x00000000 in ?? () # 调用栈完全破坏 ``` #### 栈溢出分析关键点 1. **缓冲区大小确认**: - 通过`sub esp,X`指令确定分配的栈空间大小 - 计算`缓冲区起始地址 = ebp - 分配大小` 2. **返回地址定位**: - 返回地址始终位于`ebp + 4` - 溢出攻击目标:覆盖此位置的控制流 3. **栈帧结构验证**: ``` | 高地址 | | ... | | 参数2 | <- ebp + 12 | 参数1 | <- ebp + 8 | 返回地址 | <- ebp + 4 | 保存的ebp| <- ebp | 局部变量 | <- ebp - N | ... | | 低地址 | ``` 4. **溢出特征识别**: - 返回地址被修改为非代码区域地址 - 保存的ebp值被覆盖为异常数据 - 程序计数器($eip)指向无效地址 #### 防护技术验证 ```gdb # 重新编译启用保护 gcc -g -fstack-protector -o protected vulnerable.c (gdb) run 输入超长数据后... *** stack smashing detected ***: ./protected terminated ``` 此时GDB会显示__stack_chk_fail的调用栈,帮助定位溢出点[^1]。 ### 相关问题 1. 如何利用GDB在栈溢出时自动捕获核心转储?[^2] 2. 64位系统和32位系统在栈溢出分析中有哪些关键区别?[^3] 3. 如何通过GDB脚本自动化栈溢出分析过程? 4. 除了gets函数,还有哪些常见函数会导致栈溢出漏洞? 5. 如何通过汇编指令识别金丝雀值(canary)的保护机制?[^1]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值