【问题标题】:weird addresses in stack buffer overflow article堆栈缓冲区溢出文章中的奇怪地址
【发布时间】:2018-09-23 02:11:46
【问题描述】:

阅读本文时article 这是关于绕过一些保护

如果你开始阅读那篇文章,你会发现作者做了一个我不明白的非常奇怪的计算:

好的,我们开始吧:

RET2RET

演示代码(包含易受攻击的函数strcpy

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char **argv) {
     char buf[256];
     strcpy(buf, argv[1]);
     return 1;
}

用 GDB 反汇编上面的代码后,你会得到这样的东西(反汇编 main 函数):

(gdb) disassemble main
Dump of assembler code for function main:
0x08048384 <main+0>: push %ebp
0x08048385 <main+1>: mov %esp,%ebp
0x08048387 <main+3>: sub $0x108,%esp
0x0804838d <main+9>: and $0xfffffff0,%esp
0x08048390 <main+12>: mov $0x0,%eax
0x08048395 <main+17>: sub %eax,%esp
0x08048397 <main+19>: sub $0x8,%esp
0x0804839a <main+22>: mov 0xc(%ebp),%eax
0x0804839d <main+25>: add $0x4,%eax
0x080483a0 <main+28>: pushl (%eax)
0x080483a2 <main+30>: lea 0xfffffef8(%ebp),%eax
0x080483a8 <main+36>: push %eax
0x080483a9 <main+37>: call 0x80482b0 <_init+56>
0x080483ae <main+42>: add $0x10,%esp
0x080483b1 <main+45>: mov $0x1,%eax
0x080483b6 <main+50>: leave
0x080483b7 <main+51>: ret
End of assembler dump.
(gdb)

之后我们在 strcpy 上设置一个断点,因为它是一个易受攻击的函数:

(gdb) break *main+37
Breakpoint 1 at 0x80483a9

现在我们运行我们的程序并给它一大块A char 来溢出缓冲区

(gdb) run `perl -e 'print "A"x272'`
Starting program: /tmp/vuln `perl -e 'print "A"x272'`

之后我们打印eax寄存器的内容(我认为eax持有buf地址,如果我错了,请纠正我

(gdb) print (void *) $eax
$1 = (void *) 0xbffff5d0

好吧,文章的作者在下面说这个声明

简单的计算给出'buf'变量范围[0xbffff6d8- 0xbffff5d0 ] / ( 264 bytes ; 0x108h )

我不知道这是什么意思,也不知道他在哪里想出了0xbffff6d8 地址

【问题讨论】:

  • 作者是否说明了main 将从何处加载其返回地址?就像上面ebp 指向的地方一样,因为这个函数使用了旧的堆栈帧?估计计算的重点是找出攻击者应该在哪里放置一个新的返回地址。
  • 我不知道@PeterCordes
  • @PeterCordes 如果你有时间可以查看我放了链接的那篇文章
  • 嗨@PeterCordes你能帮我理解这个请“计算是为了弄清楚攻击者应该把新的返回地址放在哪里”

标签: c assembly x86 64-bit buffer-overflow


【解决方案1】:

如果我错了,请纠正我

首先你对eax是正确的,它包含buf的地址(有很多方法)。要确定这一点:首先也是最简单的函数是args 的函数,以相反的顺序被推入堆栈,所以最后一个推入的将是第一个args,正如您从上面的反汇编窗口中看到的那样调用函数前的最后一个是push eax,所以eax中的值必须是buf地址。

我不明白这是什么意思,也不知道他在哪里想出了0xbffff6d8 地址

0xbffff6d8 这是ebp 的值,0xbffff6d8 - 0xbffff5d0 背后的想法是在开始引发段错误之前了解最大 buf 大小的众多方法之一

另一种无需进行此计算即可了解此信息的方法是从上面的反汇编窗口中看到0x08048387 &lt;main+3&gt;: sub $0x108,%esp,它为本地buf 数组分配空间,但并非在所有情况下都是如此:取决于本地的数量变量(如果有一个局部变量/数组,这将每次都有效,否则您必须聪明地使用此方法)但作者的方法每次都有效。

【讨论】:

    【解决方案2】:

    ret2ret 漏洞利用的基本思想是在堆栈上找到一个可以稍微改变的值,以便它指向一个可以溢出的缓冲区。通过执行尽可能多的ret 指令来获得该值,您最终将能够执行缓冲区中的代码(漏洞利用)。

    在执行call 0x80482b0 &lt;_init+56&gt; 之前示例中的堆栈如下所示:

    --------------------
    |    0xbffff6f0    | ebp+28
    --------------------
    |    0xb7fdb000    | ebp+24
    --------------------
    |    0xb800167c    | ebp+20
    --------------------
    |    0xbffff770    | ebp+16 (previous stack frame)
    --------------------
    |    0xbffff764    | ebp+12 (points to the argv array)
    --------------------
    |       0x2        | ebp+8  (holds the value of argc)
    --------------------
    |   main ret addr  | ebp+4
    --------------------
    |previous ebp value| ebp
    --------------------
    |   appears to be  |
    |      unused      | ebp-8
    --------------------
    |                  |
    |     256-byte     |
    |       buf        |
    |                  |
    |                  |
    |                  | ebp-264
    --------------------
    |                  | ebp-268 (holds the second argument to strcpy)
    --------------------
    |                  | ebp-272 = esp (holds the first argument to strcpy)
    --------------------
    

    那么如何对这个堆栈进行成功的 ret2ret 攻击呢?我们知道main 中的最后一条指令是ret。我们需要找到一种方法来多次执行这条指令,以便最终我们决定放入buf 的任何代码都会被执行。请记住,每次在 32 位 x86 模式下执行 ret 时,它都会从堆栈中弹出一个 4 字节的值。所以我们首先需要弄清楚的是,需要从堆栈中弹出多少个 4 字节的值,才能得到恰好代表指向buf 的指针的值。文章作者显示,第一个这样的值是ebp+28。范围 0xbffff6d8 - 0xbffff5d0 包含 256 字节的缓冲区。但是,ebp+28 处的值是 0xbffff6f0,实际上在缓冲区之外。但是由于strcpy 将在末尾附加一个 NULL 字节,我们可以让它用 NULL 覆盖ebp+28 的第一个字节,以便它包含指向缓冲区内的 0xbffff600。从ebp+4 开始,共有 7 个 4 字节值。所以我们需要执行ret 7次,然后会从buf的某个地方继续执行。

    为此,需要将ebp+4处的值设置为mainret的地址,即0x080483b7。同样,直到并包括ebp+24 在内的所有 4 字节位置都需要设置为 0x080483b7。这样,ret 的前 6 次执行只会跳转到ret 来一次又一次地执行。但最后一次执行ret时,控制权将转移到0xbffff6f0,该缓冲区位于将被覆盖的缓冲区中。

    现在考虑当利用代码中的evilbuf 作为argv[1] 传递时会发生什么。缓冲区包含 261 条单字节 NOP 指令。接下来的 7 个字节是执行退出系统调用的指令(尽管真正的漏洞利用会做一些更有趣的事情)。这 268 个字节将覆盖从 ebp-264ebp+3 的所有字节。那么ret的地址就有6份。最后,strcpy 会慷慨地为我们附加一个 NULL,从而覆盖ebp+28 处的字节。

    strcpy返回,main中的ret第一次执行后,会再执行6次,然后执行0xbffff600处的指令。这是我们的 NOP 之一。然后NOP sled 的其余部分将被执行,直到达到设计的指令序列(有效负载),在这种情况下,它只是执行退出系统调用。

    Ret2ret 不需要事先知道缓冲区地址。因此,即使操作系统随机化堆栈的基地址,它也可以工作。但是需要知道ret指令的地址。

    【讨论】:

      猜你喜欢
      • 2012-03-20
      • 1970-01-01
      • 2017-02-23
      • 1970-01-01
      • 2019-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-11
      相关资源
      最近更新 更多