Linux遵循W^X原则:它将内存页面标记为非可执行文件,除非它们是代码部分的一部分。这超出了你编译的应用程序的范围,并有很好的理由。操作系统承担这一责任,以防止来自系统上执行的任何程序的缓冲区溢出攻击;尤其是那些积极尝试执行缓冲区溢出攻击的程序,就像你的程序一样。
您试图通过buf
在堆栈上编写代码,并覆盖该函数的返回地址以将执行跳转到新注入的代码中。当函数返回时,程序计数器被设置为重写的返回地址,该地址现在指向堆栈内存。当程序计数器由于堆栈内存页面的撤销执行权限而尝试执行下一条指令时,将抛出SIGSEGV
。
要从堆栈执行,必须禁用OS堆栈保护。
幸运的是,Linux提供了execstack这种情况下,用户可以自行决定使用。
请参阅this Unix & Linux Stack Exchange发布更多一般信息。
调试插入的数据
如果您收到广发行SIGSEGV
,它可能意味着在你的缓冲区溢出尝试一些错误。为了避免在main
末清理搞乱,我建议让你从主打电话做你的缓冲区溢出的函数:
#include <stdio.h>
#include <string.h>
char injection_code[] = ""; // buffer overrun data here
void foo() {
char buf[100];
// memcpy used to copy the full injection string, including any nested 0s
memcpy(buf, injection_code, 108); // +8 here to handle 64-bit system RAs
}
int main (int argc, char** argv) {
foo();
printf("Done!\n");
return 0;
}
使用GDB调试。我建议在foo
的末尾加上一个断点,然后检查寄存器与info registers
的期望值一致。你可能对指令指针最感兴趣,你可以用info registers rip
(64位)或info registers eip
(32位)来检查它。
您可以在GDB中使用print
或x/x
来探索堆栈的外观。例如,您可以检查$rbp+8
以查看堆栈上的返回地址(RA)。
GDB会SIGSEGV
在ret
指令,如果RA指向无效的位置:
Program received signal SIGSEGV, Segmentation fault.
0x00000000004005bc in foo() at bufferoverflow.c:27
您可以检查,看它是否故障的ret
指令通过在故障时检查指令指针地址(在上面的例子中,它将是0x00000000004005bc
)针对程序的程序集(在GDB中使用disassemble function_name
)。
如果返回地址堆栈中的点背,它可能会抛出一个SIGILL
的非法指令,这意味着你的寄信人地址可能不正确对齐,或者您的注入指令格式有误:
Program received signal SIGILL, Illegal instruction.
0x00007fffffffdc13 in ??()
再次,您可以使用GDB来探索您的堆栈以了解其原因。
GDB对于获得正确的缓冲区溢出结构很有用。 但是,一旦按预期工作,请记住,在GDB之外执行时可能会移动内存地址,特别是如果您编译时没有调试标志(-g
)。你将不得不随身带回地址,以便在'现场'环境中获得它想要的位置。
旁白
一般情况下,直接运行注入的代码缓冲区溢出攻击是一般不能与今天的堆栈保护机制是可行的。通常情况下,需要额外的漏洞才能执行任意代码。
当然,这个问题的关键是准确地揭示OP所打算的那种攻击。此功能的广泛使用使得此类攻击比以前更加可行。 –
我使用execstack和gcc'-z execstack'并使用'sudo execstack -s vuln',并验证GNU堆栈被设置为RWE(读,写执行)'readelf -l vuln',我仍然得到类似的错误。关于我在做什么错的想法?我注意到我需要溢出缓冲区的数量超过了108个字节,而是116个字节。 – nrabbit
@nrabbit我现在没有时间去做,但会在我的机器上做实验,然后回到你身边。 – sdsmith