为什么即使返回指针指向看似有效的Shellcode的内存地址,我的C代码也会引发分段错误?

时间:2019-06-27 14:53:35

标签: c stack buffer-overflow shellcode

我正在尝试遵循有关缓冲区溢出的教程(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;

}
  1. 首先定义一个名为shellcode的变量,该变量保存该shellcode。
  2. 调用main函数并定义ret变量,该变量将加载到堆栈顶部
  3. ret变量的内存位置加上2个整数空间(代表堆栈向下8个字节(返回指针的地址)的内存位置)被指定为ret变量的值。
  4. shellcode变量的内存地址被写入由ret变量的值表示的内存地址,即返回地址。
  5. 当函数到达返回指令时,它将执行shellcode,即退出函数。

我已经通过gdb运行了此程序,所有内容似乎都已检出: The memory location of the shellcode variable is 0x804a01c

At the start of the execution of main, the return value is at the 3rd hex-word and points to __lib_start_main

After executing ret = (ret *)&ret +2 , the value of ret in on the stack and is 8 bytes more than the beginning of the stack

After executing (*ret) = (int)shellcode , the return pointer (3rd hex-word) contains the address of the shellcode, rather than __lib_start_main

The program seems to move to resume execution at the memory address of the shellcode, but nevertheless ends in a segmentation fault.

谢谢!

3 个答案:

答案 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' 后停止读取,导致执行问题,如分段错误。