0%

Heap

堆学习笔记

glibc堆内存分配原理.

留坑待填

Use After Free

uaf漏洞主要原因是释放了一个堆块后, 并没有将该指针置为NULL, 再次malloc的时候可能会重新申请到这块内存, 如果被恶意构造数据就会被利用.
UAF源码,


输出

可以看到c的地址和a的地址一样, 现在a的指针指向了”this is C!”

一段小代码

#include <stdio.h>
#include <stdlib.h>
typedef void (*func_ptr)(char *);
void evil_fuc(char command[])
{
    system(command);
}
void echo(char content[])
{
    printf("%s",content);
}
int main()
{
    func_ptr *p1=(int*)malloc(4*sizeof(int));
    printf("malloc addr: %pn",p1);
    p1[3]=echo;
    p1[3]("hello worldn");
    free(p1); //在这里free了p1,但并未将p1置空,导致后续可以再使用p1指针
    p1[3]("hello againn"); //p1指针未被置空,虽然free了,但仍可使用.
    func_ptr *p2=(int*)malloc(4*sizeof(int));//malloc在free一块内存后,再次申请同样大小的指针会把刚刚释放的内存分配出来.
    printf("malloc addr: %pn",p2);
    printf("malloc addr: %pn",p1);//p2与p1指针指向的内存为同一地址
    p2[3]=evil_fuc; //在这里将p1指针里面保存的echo函数指针覆盖成为了evil_func指针.
    p1[3]("whoami");
    return 0;
}

在这里, p1被free掉了, 但是p2 malloc出来的内存的地址和p1是一样的, 这时候调用p1[3]就相当于调用了p2[3].

Double Free 漏洞

C中free有个漏洞, 判断一块内存有没有被free过是直接和链表顶端的节点比较, 如果不是的话, 则可以free.
那么就存在这样一个漏洞.

char *a = malloc(8);
char *b = malloc(8);
free(a);
free(b);
free(a);

char *d = malloc(8);
char *e = malloc(8);

这时, a这块内存被free了两次.
且d == a.
chunk在free时和分配给用户时不同的格式.
QQ截图20200127095800.png
这为分配给用户之后的chunk, P代表前一个是否被使用, P = 0表示前一个空闲, 这时prev_size才有效, 可以用prev_size找到前一个chunk的开始地址.
M代表当前chunk从那个内存区域获得的虚拟内存, M=1表示从mmap映射区域, 否则从heap区域分配.
A代表是不是非主分配区, A=1贼属于非主分配区.
QQ截图20200127095818.png
这是free chunk的结构, fd是下一个chunk, bk是上一个chunk.


那么上面这个例子, 修改d的时候, 相当于修改了fd, d = (char )(&stack - sizeof(d))
相当于把stack这个变量放到了size of chunk 这个位置, 减size of d 的原因是要把size of prev chunk 这个位置空出来, 0x20 是size of chunk的值, 这样我们下一次malloc的时候, 可以得到&stack + 8.

Unlink漏洞


这个技术可以被用于当你在一个已知区域内(比如bss段)有一个指针,并且在这个区域内可以调用unlink的时候.
最常见的情况就是存在一个可以溢出的带有全局指针的缓冲区.
这个练习的关键在于利用free()来改写全局指针chunk0_ptr以达到任意地址写入的目的.
我们在chunk0这个全局变量里面伪造一个fake chunk.
先设置一个fd指针,使得chunk->fd->bk == chunk
再设置一个bk指针,使得chunk->bk->fd == chunk
chunk0_ptr[2]为fake chunk的fd, chunk0_ptr[3]为fake chunk的bk.
fake chunk -> fd -> bk = &chunk0_ptr - 0x18 + 0x8 * 3 = &chunk.
这点是这样.
我们还需要确保,fake chunk的size字段和下一个堆块的presize字段(fd->presize)的值是一样的.
经过了这个设置,就可以过掉“(chunksize(P) != prev_size (next_chunk(P)) == False”的校验了.
我们假设我们在chunk0处有一个溢出,让我们去修改chunk1的头部的信息.
我们缩小chunk1的presize(表示的是chunk0的size),好让free认为chunk0是从我们伪造的堆块开始的.
这里比较关键的是已知的指针正确地指向fake chunk的开头,以及我们相应地缩小了chunk.
如果我们正常地free掉了chunk0的话,chunk1的presize应该是0x90,但是这里被我们修改为了0x80.
我们通过设置“previous_in_use”的值为False,将chunk0标记为了free(尽管它并没有被free)
现在我们free掉chunk1,好让consolidate backward去unlink我们的fake chunk,然后修改chunk_ptr.

修改的步骤.

1.修改fd, bk
2.修改prev_size
3.修改prev_in_use

利用.

free掉chunk1后, chunk1和fake chunk会合并, 调用unlink, 这时候fake chunk -> fd -> bk = fake chunk -> fd
即chunk0_ptr = &chunk0_ptr - 0x18
则chunk0_ptr[3] = addr, chunk0_ptr[3] = chunk0_ptr + 3* 0x8 = &chunk0_ptr, 修改chunk0_ptr的值就可以修改addr内存的值.