[Heap] The House of Spirit
System/System Hacking

[Heap] The House of Spirit

House of Spirit

fake fastbin chunk를 해제(free)하면서 malloc 할당 시 임의의 포인터를 반환하도록 함으로써 원하는 주소에 값을 쓸 수 있게 하는 공격방식이다.

>> 같은 크기만큼을 재할당할 때, 같은 주소를 반환하는 fastbin의 특성을 이용 (fastbin을 공격하는 기법)

 

stack에 가짜 청크를 쓰고 해당 stack의 주소에서 0x10(64bit기준) 더한 주소로 free()를 호출할 수 있을 때 구현가능하다.

stack에 fastbin에 해당하는 크기의 fake chunk를 작성하고 malloc()으로 메모리를 할당한다. 그리고 fake chunk의 주소+0x10인 주소에 free()를 호출해 해당 chunk가 fastbin에 저장되게 한다. 해당 chunk의 크기로 다시 malloc()을 호출하면 fastbin에 저장된 fake_chunk의 포인터(주소)를 반환한다. 즉, 해당 포인터는 stack 영역의 메모리이다.

 

 

House of Spirit flow

 


Example

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
  
void main(){
    unsigned long *ptr;
    unsigned long fake_chunk[20];
  
    fprintf(stderr,"fakeChunk : %p\n",fake_chunk);
    fprintf(stderr,"ptr : %p\n",&ptr);
  
    fake_chunk[1] = 0x80;
    fake_chunk[17] = 0x1000;
  
    malloc(1000);
 
    ptr = fake_chunk + 2;
    free(ptr);
  
    char *stack = malloc(0x70);
  
    fprintf(stderr,"Stack : %p\n",stack);
}

△ house_of_spirit.c스택에 fake_chunk를 작성한 후 malloc()로 메모리 할당을 요청한다. // 배열 fake_chunk[]를 만들어 스택변수로 작성하고 malloc()로 메모리를 할당한다.free()에 ptr에 저장된 값에 대해 메모리 해제를 요청한다.다시 malloc()에 크기가 0x70인 메모리 할당을 요청한다.

 

b1: stack에서 작성된 fake_chunk 확인

b2: heap 공간의 생성과 arena의 값 확인

b3: free()에 fake_chunk의 포인터를 전달한 후 fastbins[]의 변화 확인

b4: malloc()에서 반환되는 포인터 확인

[1] b1에서 멈춤
[2] ni 두 번 실행(main + 109에서 멈춤)

△ stack에 작성된 fake_chunk 확인

fake_chunk의 크기(0x80)를 0x7fffffffde00 - 0xa8(168) [= b1까지 실행했을 때의 rbp에 저장된 값 - 0xa8]에 저장

다음 chunk의 크기(0x1000)를 0x7fffffffde00 - 0x28(40) [= b1까지 실행했을 때의 rbp에 저장된 값 - 0x28]에 저장

▶ stack에 fake_chunk가 생성되었다!

[3] c (b2에서 멈춤)

$3 = {
  mutex = 0, 
  flags = 1, 
  fastbinsY = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, 
  top = 0x6023f0, 
  last_remainder = 0x0, 
  bins = {0x7ffff7dd1b78 <main_arena+88>, ...
...}, 
  binmap = {0, 0, 0, 0}, 
  next = 0x7ffff7dd1b20 <main_arena>, 
  next_free = 0x0, 
  attached_threads = 1, 
  system_mem = 135168, 
  max_system_mem = 135168
}

[4] ni (main+119에서 멈춤)(너무 길어서 캡쳐할 수 없었다...)

[3]은 malloc()으로 메모리 할당을 요청하기 전이기 때문에 heap 공간이 생성되지 않아 arena에는 최소한의 정보만 가지고 있다.

[4]는 malloc()으로 메모리 할당을 하고 heap 공간이 생성되어 flags, bins. system_mem, max_system_mem 등의 값들이 초기화된다.

[5-1] b3에서 멈춤
[5-2] 
[6] ni (main+152에서 멈춤)
[7] c (b4에서 멈춤)
[8] c (프로세스 종료)

free()에 fake_chunk 포인터(rdi: 0x7fffffffdd60)를 전달하고 메모리를 해제하면 해당 chunk의 포인터는 fastbinsY[6]에 배치된다.

해당 chunk를 재할당받기 위해 malloc()으로 크기가 0x70인 메모리의 할당을 요청하면 fastbinsY[6]에 배치된 chunk를 재할당해 포인터(0x7fffffffdd60)를 반환한다.


_int_free()

doongdangdoongdangdong.tistory.com/78?category=869864 에도 나와있음

- "free(): invalid pointer"

  if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())
 
#if TRIM_FASTBINS
      /*
    If TRIM_FASTBINS set, don't place chunks
    bordering top into fastbins
      */
      && (chunk_at_offset(p, size) != av->top)
#endif
      ) {
 if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
      || __builtin_expect (misaligned_chunk (p), 0))
    {
      errstr = "free(): invalid pointer";
    errout:

메모리가 해제된 chunk의 포인터가 올바르게 정렬(align)된 포인터인지 확인하기 위해 misaligned_chunk () 호출 → 올바르지 않다면 "free(): invalid pointer"라는 오류 메시지와 함께 종료

 

- "free(): invalid size"

if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size)))
  {
    errstr = "free(): invalid size";
    goto errout;
  }

해당 chunk의 size에 저장된 값이 MINSIZE보다 작고 해당 값이 정상적으로 정렬된 값인지 확인 → 정상적이지 않다면 "free(): invalid size" 라는 오류 메시지와 함께 종료

 

- "free(): invalid next size (fast) "

  if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())
 
#if TRIM_FASTBINS
      /*
    If TRIM_FASTBINS set, don't place chunks
    bordering top into fastbins
      */
      && (chunk_at_offset(p, size) != av->top)
#endif
      ) {
 
    if (__builtin_expect (chunksize_nomask (chunk_at_offset (p, size))
              <= 2 * SIZE_SZ, 0)
    || __builtin_expect (chunksize (chunk_at_offset (p, size))
                 >= av->system_mem, 0))
    {
    /* We might not have a lock at this point and concurrent modifications
       of system_mem might have let to a false positive.  Redo the test
       after getting the lock.  */
    if (have_lock
        || ({ assert (locked == 0);
          __libc_lock_lock (av->mutex);
          locked = 1;
          chunksize_nomask (chunk_at_offset (p, size)) <= 2 * SIZE_SZ
            || chunksize (chunk_at_offset (p, size)) >= av->system_mem;
          }))
      {
        errstr = "free(): invalid next size (fast)";
        goto errout;
      }
    if (! have_lock)
      {
        __libc_lock_unlock (av->mutex);
        locked = 0;
      }
    }
 
    free_perturb (chunk2mem(p), size - 2 * SIZE_SZ);
 
    set_fastchunks(av);
    unsigned int idx = fastbin_index(size);
    fb = &fastbin (av, idx);
 
    /* Atomically link P to its fastbin: P->FD = *FB; *FB = P;  */
    mchunkptr old = *fb, old2;
    unsigned int old_idx = ~0u;

해당 chunk의 크기가 fastbin에 해당하는지 → chunk의 size에 저장된 값의 조건 검사 → 조건들을 통과하면 정상적인 청크(=fastbin에 저장)

 

chunk의 size에 저장된 값에 대한 조건

'size'에 저장된 값에서 사용된 flag bit를 모두 제거한 값이 2*SIZE_SZ 보다 작거나 같은지 확인한다.

그리고 chunk의 'size'에 저장된 값이 av->system_mem의 값보다 크거나 같은지 확인한다. 이 때 해당 arena가 잠겨있지 않으면 av->system_mem에 저장된 값이 FALSE일 수 있다. 따라서 해당 arena를 잠근 후에 다시 확인해야 한다.

위의 조건들이 충족되면 다음 청크의 'size'에 저장된 값이 비정상이므로 "free(): invalid next size (fast)" 라는 오류메시지와 함께 종료된다.

SMALL

'System > System Hacking' 카테고리의 다른 글

[Heap] Memory Leak  (0) 2020.11.16
[Heap] House of Force  (0) 2020.11.03
[Heap] UAF(Use-After-Free)  (0) 2020.09.19
[Heap] Double Free 취약점  (0) 2020.09.17
[Heap] Security Check  (0) 2020.09.13