堆栈缓冲区溢出文章中的奇怪地址

时间:2018-09-23 02:11:47

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

在阅读此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分解上面的代码后,您将得到如下所示(反汇编主函数):

(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地址的地方

2 个答案:

答案 0 :(得分:1)

  

如果我错了请纠正我

首先,您对eax是正确的,它包含buf的地址(有很多方法)。要确保这一点:首先,最简单的是将args个函数以相反的顺序压入堆栈,因此最后一次压入将是第一个args,如您所见反汇编窗口,调用该函数之前的最后一个窗口是push eax,因此eax中的值必须为buf地址。

  

我不知道这是什么意思,也不清楚他在哪里提出该0xbffff6d8地址

0xbffff6d8ebp的值,0xbffff6d8 - 0xbffff5d0背后的想法是在开始抛出段错误之前了解最大buf大小的多种方法之一

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

答案 1 :(得分:1)

ret2ret漏洞利用的基本思想是在栈上找到一个可以稍作更改的值,以便它指向一个可能溢出的缓冲区。通过执行所需数量的ret指令以达到该值,您将最终能够在缓冲区(漏洞利用)中执行代码。

在执行call 0x80482b0 <_init+56>之前,示例中的堆栈如下所示:

--------------------
|    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中的任何代码。请记住,每次ret在32位x86模式下执行时,它将从堆栈中弹出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处的值设置为retmain的地址,即0x080483b7。同样,所有ebp+24之前(包括ret)的所有4字节位置都必须设置为0x080483b7。这样,ret的前6次执行将跳转到ret,一次又一次地执行。但是最后一次执行evilbuf时,控制权将转移到缓冲区将被覆盖的0xbffff6f0。

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

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

Ret2ret不需要先验缓冲区地址。因此,即使OS将堆栈的基地址随机化,它也可以工作。但这需要知道 pdfView1 = findViewById(R.id.pdfViewer1); pdfView1.fromAsset("cpgTest1.pdf") .scrollHandle(new DefaultScrollHandle(this)) .defaultPage(currentPage) .onPageChange(this) .load(); } @Override public void onPageChanged(int page, int pageCount) { currentPage = page; } @Override public void loadComplete(int nbPages) { if (currentPage >= 0) { pdfView1.jumpTo(currentPage); }} @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(KEY_CURRENT_PAGE, currentPage); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); currentPage = savedInstanceState.getInt(KEY_CURRENT_PAGE); } } 指令的地址。