![]() |
Заметка про 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 { 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(); Код:
uname(&us); Код:
prepare_kernel_cred = get_ksym("prepare_kernel_cred"); Код:
mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0); Это легко проверить в дебаггере (компилируем: gcc newrootme.c -o newrootme -ggdb): gdb ./newrootme Цитата:
Код:
int fd = open("/sys/kernel/debug/nullderef/null_call", O_WRONLY); Код:
printf("UID %d, EUID:%d GID:%d, EGID:%d\n", getuid(), geteuid(), getgid(), getegid()); Функция get_root(): Теперь функция get_root(), в которой и происходит всё действо. В этой функции мы уже находимся в режиме ядра, поэтому надо быть осторожным и не сделать ядру oops :) Код:
void (*printk)(const char*fmt, ...) = (void *) a_printk; Код:
int i; Немного теории: стек ядра делит 4кб/8кб (в зависимости от дистрибутива, чаще - 8кб) размер со структурой thread_info как видно в include/linux/sched.h: Код:
union thread_union { Поэтому мы обнуляем младшие 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() В эксплойтах также используется вставка на асме для нахождения адреса thread_info: Код:
# get current task_struct... Едем дальше. Код:
printk("i addr: 0x%lx\n", (unsigned long)&i); Код:
struct thread_info { Код:
struct task_struct { <2.6.29 Код:
if(!new_style) // kernel<2.6.29 В /var/log/messages выводится адрес uid и euid для любопытных. Код:
p = (unsigned *) ((char *)(p + 8) + sizeof(void *)); >=2.6.29 Теперь рассмотрим код для ядер >=2.6.29. В этих ядрах id'ы хранятся не в самой task_struct, а в отдельной структуре cred, указатель на которую хранится в task_struct: Код:
struct task_struct { Код:
struct cred { Вот так это выглядит в коде: Код:
else // kernel>=2.6.29 и ищем 2 одинаковых указателя на память в адресном пространстве ядра - if (cred == (unsigned long *)p[i+1] && cred >(unsigned long *)0xc0000000) { cred++; - пропускаем первое поле (atomic_t usage) А дальше проверяем id'ы и обнуляем, если это то, что искали. В /var/log/messages будет: Цитата:
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); Для проверки работы этого способа раскомментируйте строчку //#define USECOMMITCREDS 1 в начале newrootme.c и перекомпилируйте его. Если ядро >=2.6.29 и символы prepare_kernel_cred, commit_creds экспортируются, то данный код из get_root() даст рута: Код:
#else 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 |
Всё прелестно, но, как ми пониманю, printk юзается только в исследовательских целях - для чтения логов, иначе ненужное палево
|
Да, printk здесь, чтобы понимать, что происходит. Весь код здесь для исследования и понимания потрохов.
|
а в чем практическая ценность этого task_struct ?
т.е. наш код уже сумел попасть в ядро, зачем заниматься поиском именно task_struct ? чтобы изменить uid/gid уже существующего процесса ? или task_struct используется в руткитах для стелса процессов/соединений и т.д. ? я это к тому, что наверное есть более универсальные и простые методы извлечения выгоды из того, что мы сумели запустить наш код в режиме ядра - например запуск нового процесса с правами рута. |
Цитата:
Цитата:
|
Оставлю здесь, может кому-нибудь пригодится однажды.
kleak LKM для просмотра или дампа памяти ядра. Вроде аналога когда-то существовавшего /dev/kmem. Использование: Цитата:
На x64 тоже должно работать. Код:
/* Код:
obj-m = kleak.o |
Цитата:
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
Согласен почти со всем. Единственное, трогать /etc/passwd, по-моему, неэлегантно :) и насколько знаю, не все сисколлы из ядра работают, желательно проверять. А вот идея выставлять суид бит - отличная. think outside of the box :) Кстати, если о файлах, - можно также /etc/ld.so.preload создать с mask 666. |
Как и обещал, раскрутил тему. Написал небольшой модуль ядра, вызывающий write на консоль из кернелспейса. Итак, немного черношляпной вуду-магии :)
Код:
/* на сегмент данных текущего процесса. Что в коде собственно и делается. В реальном эксплойте вместо макросов можно использовать asm-вставки. upd. Даже не подумал, что проверок uid в сисколлах никто не отменял. Скорее всего прийдётся вызывать нижележащие функции. |
Вложений: 1
Нашёл способ запустить бинарник из ring0 в качестве хелпера keventd.
Накодил небольшой пример, запускающий Sh. Просто распакуйте и напишите make. http://rghost.ru/download/39141720/3...0c8/run.tar.gz Проверено на ядре 3.2 |
Часовой пояс GMT +3, время: 06:39. |
Powered by vBulletin® Version 3.8.5
Copyright ©2000 - 2021, Jelsoft Enterprises Ltd. Перевод: zCarot