Linux遵循W^X原则:除了代码段,它将内存页面标记为不可执行。这超出了您编译的应用程序的范围,而且有充分的理由。操作系统承担此责任以防止来自系统上执行的任何程序的缓冲区溢出攻击;特别是那些积极尝试执行缓冲区溢出攻击的程序,比如您的程序。
您正在尝试通过buf
在堆栈上编写代码,并覆盖函数的返回地址以跳转执行到新注入的代码。当函数返回时,程序计数器被设置为被覆盖的返回地址,它现在指向堆栈内存。由于堆栈内存页面的执行权限被撤销,当程序计数器尝试执行下一条指令时,会抛出SIGSEGV
。
要从堆栈中执行,必须禁用操作系统的堆栈保护。
幸运的是,Linux提供了execstack,供用户自行决定使用。
查看this Unix & Linux Stack Exchange 帖子以获取更一般的信息。
调试注入数据
如果您在gdb中收到SIGSEGV
错误,那么很可能是您的缓冲区溢出尝试中出现了一些错误。为了避免干扰main
结束时的清理工作,建议创建一个函数,在主函数中调用该函数来执行缓冲区溢出操作:
#include <stdio.h>
#include <string.h>
char injection_code[] = "";
void foo () {
char buf[100];
memcpy(buf, injection_code, 108);
}
int main (int argc, char** argv) {
foo();
printf("Done!\n");
return 0;
}
使用GDB进行调试。建议在
foo
的结尾处设置断点,并使用
info registers
检查寄存器是否与预期相符。您可能最感兴趣的是指令指针,您可以使用
info registers rip
(64位)或
info registers eip
(32位)来检查它。
您可以使用
print
或GDB中的
x/x
探索堆栈的情况。例如,您可以检查
$rbp+8
以查看堆栈上的返回地址(RA)。
如果RA指向无效位置,则GDB将在
ret
指令上发生
SIGSEGV
异常:
Program received signal SIGSEGV, Segmentation fault.
0x00000000004005bc in foo () at bufferoverflow.c:27
你可以通过检查故障时指令指针地址(在上面的示例中为
0x00000000004005bc
)与程序的汇编代码(在GDB中使用
disassemble function_name
)来检查是否在
ret
指令上出现故障。
如果返回地址指向堆栈,可能会抛出
SIGILL
表示非法指令,这意味着你的返回地址可能没有正确对齐,或者你注入的指令存在问题。
Program received signal SIGILL, Illegal instruction.
0x00007fffffffdc13 in ?? ()
再次,您可以使用GDB来探索您的堆栈以查看原因。
GDB对于正确获取缓冲区溢出的结构非常有用。但是一旦它按预期工作,记住当在没有调试标志(-g
)的情况下编译时,内存地址可能会发生偏移,特别是在执行时超出GDB环境。您将不得不稍微调整返回地址,以使其在“实时”环境中达到所需位置。
一个旁白
一般来说,直接运行注入代码的缓冲区溢出攻击在今天的堆栈保护机制下通常是不可行的。通常需要存在其他漏洞才能执行任意代码。
gcc -z execstack
和sudo execstack -s vuln
两种方式来使用execstack
,并通过readelf -l vuln
验证了GNU栈被设置为RWE(读、写、执行)。但是我仍然遇到了类似的错误。您认为我做错了什么?我注意到我需要溢出缓冲区的数量不是108字节,而是116字节。 - nrabbitlea ecx,[esp+0x4]
等等。在函数的结尾处也会有类似的操作,这些操作会使堆栈指针发生一些偏移,并且会影响堆栈的布局。 - nrabbit