我正在尝试遵循有关缓冲区溢出的教程(Vivek Ramachandran的《 Buffer Overflow Primer》)。我从字面上看是遵循他的代码,该代码在演示中对他有用,并且到目前为止一直对我有用。
下面的C程序的目标是将退出系统调用的shellcode分配给一个变量,然后用shellcode变量的内存地址替换指向__lib_start_main的main函数的默认返回地址,这样程序就可以在完成main函数后执行shellcode,然后以值为20正常退出程序(如执行“ exit(20)”一样)。不幸的是,该程序以段错误结束。我在32位Linux Mint上运行它。我正在使用gcc编译代码,并使用--ggdb和-mpreferred-stack-boundary = 2选项对其进行了编译,并且尝试使用-fno-stack-protector选项。 >
代码如下:
#include<stdio.h>
char shellcode[] = "\xbb\x16\x00\x00\x00"
"\xb8\x01\x00\x00\x00"
"\xcd\x80";
int main(){
int *ret;
ret = (int *)&ret +2;
(*ret) = (int)shellcode;
}
我已经通过gdb运行了此程序,所有内容似乎都已检出: The memory location of the shellcode variable is 0x804a01c
谢谢!
答案 0 :(得分:2)
传统的缓冲区溢出漏洞利用 did 涉及在堆栈上执行代码,但是您的程序却不这样做。您的shellcode
数组不在堆栈上,并且用于破坏main
的返回地址以指向shellcode
数组的构造不涉及在堆栈上执行代码。当我在用gcc -O0 -m32
编译的Linux机器(也在x86 CPU上运行)上运行程序时,它确实设置了EIP寄存器以指向shellcode
中的机器代码。但是然后,就像为您所做的那样,它会因分段错误而崩溃。
它崩溃的原因是因为shellcode
已加载到标记为不可执行的内存区域中。 (此内存区域的名称为“数据段”。)处理器拒绝从该区域执行机器指令,而是生成内核的“异常”(这是硬件概念,与C ++异常不同)。转换为SIGSEGV信号。
有关编写shellcode和缓冲区溢出漏洞的旧教程并未警告您这种可能性,因为较早的x86架构无法将内存标记为每页都不可执行。在大多数32位基于x86的操作系统使用的“平面”段寄存器配置中,任何可读页面也是可执行的。但是,该体系结构的最后几代能够将单个页面标记为不可执行,因此您必须解决此问题。 (如果我没记错的话,每页可执行性是在2003年左右(与64位模式同时)添加到x86体系结构中的,但是要使操作系统支持变得普遍起来,要花更长的时间。)
在我的Linux机器上,如上所述,该程序的修改后的版本已成功将控制转移到shellcode
中的机器代码并执行了该机器代码。它使用mprotect
系统调用来使包含shellcode
的内存区域可执行。
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
const char shellcode[] =
"\xbb\x16\x00\x00\x00"
"\xb8\x01\x00\x00\x00"
"\xcd\x80";
int main(void)
{
uintptr_t pagesize = sysconf(_SC_PAGESIZE);
if (mprotect((void *)(((uintptr_t)shellcode) & ~(pagesize - 1)),
pagesize, PROT_READ|PROT_EXEC)) {
perror("mprotect");
return 1;
}
void **ret;
ret = (void **) &ret;
ret[9] = (void *)shellcode;
return 0;
}
与mprotect
操作本身一样,请注意添加代码块如何更改堆栈布局并将返回地址放置在其他位置。如果编译时启用了优化,则堆栈布局将再次更改,并且返回地址不会被覆盖。还要注意我如何使shellcode
成为const char
。如果我没有这样做,我将需要在PROT_READ|PROT_WRITE|PROT_EXEC
调用中使用mprotect
以避免崩溃太早,因为当C库期望它是可写状态时,某些随机全局变量突然不可写,然后由于“ W^X”安全策略,内核可能无法通过mprotect
调用。
根据您的内核和C库的年龄,将shellcode
设为const char
本身可能就足够了,但是对于我拥有的内核4.19和glibc 2.28,它是只读的数据也不可执行。
答案 1 :(得分:0)
在编译时添加以下选项可解决该问题:
-z execstack
答案 2 :(得分:0)
您的 SHellcode 包含空字节,尝试使用最小寄存器并在需要将寄存器清零时使用异或,空字节问题是当 C 看到此空字节时,它在此空字节 '\x00' 后停止读取,导致执行问题,如分段错误。