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

zms*_*200 2 c stack buffer-overflow shellcode

我正在尝试遵循有关缓冲区溢出的教程(Vivek Ramachandran 的缓冲区溢出入门)。我实际上是在遵循他的代码,该代码在演示中对他有效,并且到目前为止对我也有效。

下面的 C 程序的目标是将退出系统调用的 shellcode 分配给一个变量,然后用 shellcode 变量的内存地址替换 main 函数的默认返回地址(指向 __lib_start_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;

}
Run Code Online (Sandbox Code Playgroud)
  1. 首先定义一个名为 shellcode 的变量,该变量保存 shellcode。
  2. main函数被调用并定义了ret变量,该变量被加载到栈顶
  3. ret 变量的内存位置加上 2 个整数空间,即栈下 8 个字节的内存位置(返回指针的地址)被指定为 ret 变量的值。
  4. shellcode 变量的内存地址被写入由 ret 变量的值表示的内存地址 - 即返回地址。
  5. 当函数到达return指令时,就会执行shellcode,也就是exit函数。

我已经通过 gdb 运行了这个,一切似乎都检查出来了: shellcode 变量的内存位置是 0x804a01c

在 main 执行开始时,返回值位于第 3 个十六进制字并指向 __lib_start_main

执行完 ret = (ret *)&ret +2 后,ret 的值入栈,并且比栈首多了 8 个字节

执行 (*ret) = (int)shellcode 后,返回指针(第三个十六进制字)包含 shellcode 的地址,而不是 __lib_start_main

该程序似乎移动到 shellcode 的内存地址处继续执行,但仍然以分段错误结束。

提前致谢!

zwo*_*wol 5

传统的缓冲区溢出漏洞确实涉及在堆栈上执行代码,但您的程序不会这样做。您的shellcode数组不在堆栈上,并且用于破坏main返回地址以指向该shellcode数组的构造不涉及在堆栈上执行代码。当我在我的 Linux 机器上运行你的程序(也在 x86 CPU 上运行)时,用 编译gcc -O0 -m32,它最终会设置 EIP 寄存器以指向shellcode. 但是,正如它为您所做的那样,它会因分段错误而崩溃。

它崩溃的原因是因为加载到标记为不可执行的shellcode内存区域。(这个内存区域的名称是“数据段”。)处理器拒绝从该区域执行机器指令,而是生成一个“异常”(这是一个硬件概念,与 C++ 异常不同),内核转换为 SIGSEGV 信号。

有关编写 shellcode 和缓冲区溢出漏洞的旧教程不会警告您这种可能性,因为老一代的 x86 架构无法将内存标记为不可在每页上执行。在大多数基于 x86 的 32 位操作系统使用的“平面”段寄存器配置中,任何可读的页面也都是可执行的。然而,最后几代架构已经能够将各个页面标记为不可执行,您必须解决这个问题。(如果我没记错的话,每页可执行性是在 2003 年左右添加到 x86 架构中的,同时也是 64 位模式,但是操作系统支持花了相当长的时间才变得普遍。)

在我的 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;
}
Run Code Online (Sandbox Code Playgroud)

除了mprotect操作本身之外,请注意添加该代码块如何更改堆栈布局并将返回地址放在不同的位置。如果我在优化的情况下进行编译,堆栈布局会再次更改,并且返回地址不会被覆盖。另请注意我是如何制作shellcodeconst char。如果我没有这样做,我将需要PROT_READ|PROT_WRITE|PROT_EXECmprotect调用中使用以避免过早崩溃,因为当 C 库期望它可以写时,某些随机全局变量突然不可写,然后内核可能会导致调用mprotect失败由于“ W^X ”安全策略。

根据您的内核和 C 库的年龄, make shellcodebeconst char本身可能就足够了,但是对于内核 4.19 和 glibc 2.28(我拥有的),只读数据也无法执行。