Различия

Здесь показаны различия между двумя версиями данной страницы.

Ссылка на это сравнение

articles:memory-war-ii [2017/09/05 02:55] (текущий)
Строка 1: Строка 1:
 +====== memory-war-II ======
 +<​sub>​{{memory-war-II.odt|Original file}}</​sub>​
 +
 +====== __параллельные миры: война на выживание__\\ tag-line-1: вторжение уже началось\\ tag-line-2: битва за память продолжается ======
 +
 +крис касперски ака мыщъх, no-email
 +
 +**вторжение в адресное пространство чужого процесса — вполне типичная задача,​ без которой не обходятся ни черви, ни вирусы,​ ни шпионы,​ ни распаковщики,​ ни… даже легальные программы! возможных решений — много, а способов противостояния еще больше. чтобы не завязнуть в этом болоте мыщхъ решил обобщить весь свой хвостовой опыт в единой статье,​ относящейся главным образом к ****LIUNUX****'​у и различным кланам ****BSD****.**
 +
 +===== введение в историческую ретроспективу =====
 +
 +Еще в стародавние время в UNIX'​ах существовала игра "​Дарвин"​ (чем-то напоминающая морской бой), где в раздельных адресных пространствах ползали черви, периодически наносящие удары друг по другу. К концу восьмидесятых игры кончились,​ а потребность в легальных средствах межпроцессорного взаимодействия (InterProcessCommunication или, сокращенно,​ IPC) осталась. В UNIX (в отличии от MS-DOS, Windows 3.x и отчасти Windows 9x) все процессы выполняются в независимых и невидимых друг для друга адресных пространствах,​ похожих по своему устройству на параллельные миры, знакомые нам по фантастическим фильмам,​ вот только в реальной жизни, в отличии от сказок,​ параллельным пространствам приходится как-то взаимодействовать,​ обмениваясь данными друг с другом.
 +
 +Просто так взять и залезть в адресное пространство чужого процесса нельзя — политика безопасности не позволяет! UNIX не для того строили,​ чтобы по нему всякие хакеры шастали! Тем не менее, без хакеров никакая операционная система не обходится и интерес к атакам на UNIX все растет и растет. К сожалению (или к счастью — это смотря по какой стороны баррикады стоять),​ с правами непривилегированного пользователя под UNIX'​ом практически ничего хорошего нельзя сделать. И хотя периодически появляются сообщения о новых дырах, дающих любому пользователю абсолютный контроль над системой,​ они (дыры, а не пользовали,​ хотя и пользователи тоже) довольно быстро затыкаются.
 +
 +Мыщъх главным образом будет говорить о механизмах,​ требующих наличия прав администратора. А вот как их получить — это уже тема отдельной статьи,​ никак не связанной ни с адресными пространствами,​ ни с атаками на них. Так же, следуя своим традициям,​ мыщъх рассматривает LINUX наряду с FreeBSD в котором многое реализовано сильно по другому. Тем не менее, в умелых руках обе системы уязвимы и подвержены атакам,​ о которых осведомлен далеко не каждый администратор.
 +
 +{{memory-war-ii_Image_0.jpg}}
 +
 +Рисунок 1 в раздельных адресных пространствах межу светом и тьмой хакеры и разработчики осей ведут невидимую войну, скрытую от простых пользователей кучей уровней абстракций
 +
 +===== ptrace – белые начинают и проигрывают =====
 +
 +**ptrace** – древнейший механизм межпроц… стоп! межадресного взаимодействия! Чтобы продолжить дальше,​ придется сделать небольшое лирическое отступление,​ пробившись через бурелом терминологической путаницы. Средства межпроцессорного взаимодействия (IPC), охватывают широкий круг механизмов,​ включающий в себя в том числе сокеты,​ пайпы и другой потребительский ширпотреб. Для внедрения в чужое адресное пространство они непригодны,​ если конечно,​ процесс-жертва "​добровольно"​ не установит обработчик на пайп/​сокет,​ позволяющий читать/​писать содержимое принадлежащей ему памяти,​ плюс отсутствуют ошибки переполнения. Ну, обработчик,​ это, конечно,​ безумие (по имени back-door), а вот ошибки переполнения достаточно часто встречается,​ однако,​ увы, довольно быстро затыкаются и хотя на их место приходят другие,​ все это неуниверсально и неинтересно.
 +
 +{{memory-war-ii_Image_1.jpg}}
 +
 +Рисунок 2 ptrace – очень сложный механизм,​ значительная часть которого реализована на ядерном уровне
 +
 +Сосредоточимся на подклассе средств межпроцессорного взаимодействия,​ рассматривая лишь механизмы,​ работающие непосредственно с физической или виртуальной памятью целевого процесса,​ к которым принадлежит вышеупомянутая библиотека ptrace. "​Библиотека"​ — потому,​ что изначально она была реализована как обособленный модуль,​ много позже интегрированный в ядро, поэтому,​ //​**теперь**//​ более правильно говорить о наборе функций семейства ptrace, реализованных как на прикладном,​ так и на ядреном уровне. Собственно говоря,​ на прикладном режиме доступна всего одна функция:​ **ptrace****((****int**** _****request****,​ ****pid****_****t**** _****pid****,​ ****caddr****_****t**** _****addr****,​ ****int**** _****data****))**,​ принимающая кучу аргументов и позволяющая решать кучу задач: трассировать процесс,​ приостанавливая или возобновляя его выполнение,​ читать/​писать содержимое виртуальной памяти,​ обращаться с контекстом регистров и т. д.
 +
 +{{memory-war-ii_Image_2.png}}
 +
 +Рисунок 3 принцип работы ptrace
 +
 +Формально,​ ptrace реализована на всех UNIX-подобных системах,​ но особенности реализации добавляют программистам много хлопот ​ и прежде,​ чем составить переносимый код придется изрядно потрудится. Львиную долю работы мыщъх уже сделал за вас в виде необходимых пояснений в нужных местах.
 +
 +Алгоритм внедрения,​ работающий на всех платформах,​ в общем случае выглядит так:
 +
 +  - запускаем отладочный процесс-жертву вызовом fork()/​exec()/​ptrace(PTRACE_TRACEME [в BSD — PT_TRACE_ME,​ в дальнейшем BSD-объявления приводятся через слеш]) или подключаемся к уже запущенному через ptrace(PTRACE_ATTACH/​PT_ATTACH,​ pid, 0, 0);
 +  - процессу-жертве автоматически посылается SIGSTOP, приводящий к его остановке,​ момент которой легко определяется функцией wait();
 +  - читаем содержимое контекста регистров общего назначения вызовом ptrace(PTRACE_GETREGS/​PT_GETREGS,​ pid, 0,*data), находим среди них регистр $PC (на x86 платформе он зовется EIP) и запоминаем его;
 +  - читаем содержимое памяти под *$PC: ptrace(PTRACE_PEEKTEXT/​PT_READ_I,​pid,​addr,​0),​ запоминая его в своем внутреннем буфере;​
 +  - вызовом ptrace(PTRACE_POKETEXT/​PT_WRITE_I,​ pid, addr, *data) внедряем поверх *$PC свой собственный shell-код,​ обеспечивающий загрузку остального "​хакерского"​ кода (например,​ можно выделить память из кучи, не забыв присвоить ей атрибуты исполняемой,​ т. к. с поддержкой флагов NX/XD исполнение кода в области данных стало невозможным,​ как вариант,​ еще можно загрузить свою динамическую библиотеку); ​
 +  - возобновляем работу процесса-жертвы:​ ptrace(PTRACE_CONT/​PT_CONTINUE,​ pid, 0/1,0), давая shell-коду некоторое время на выполнение всех ранее запланированных действий,​ какими бы коварными они ни были;
 +  - восстанавливаем оригинальное содержимое модифицированной памяти вызовом ptrace(PTRACE_POKETEXT/​PT_WRITE_I,​ pid, addr, *data), при этом о восстановлении регистров shell-код должен позаботиться самостоятельно (вообще-то это можно сделать и через ptrace, но через shell-код технически проще);​
 +  - отсоединяемся процесса-жертвы через ptrace(PTRACE_DETACH/​PT_DEATACH,​ pid, 0, 0), оставляя глубоко в его чреве внедренный хакерский код;
 +{{memory-war-ii_Image_3.png}}
 +
 +Рисунок 4 раскуривание man'а по ptrace
 +
 +==== защита ====
 +
 +Защититься от такого метода внедрения процессу-жертве проще простого. Поскольку функция ptrace нереентерабельна,​ то есть не допускает вложенного выполнения,​ процессу-жертве достаточно сделать ptrace()… самому себе! Это никак не повлияет на производительность,​ но вот предотвратит вторжение. Впрочем,​ вместе с вторжением отвалится и отладка. За исключением небольшого количества отладчиков (таких,​ например,​ как Linice), весь остальной конгломерат (включающий и могущественный gdb) работает именно через ptrace и попытка отладки защищенного процесса накрывается медным Тазом.
 +
 +Процесс-жертва может легко очиститься от хакерского кода и вовсе не через сжигание на костре,​ а… повторным вызовом exec() самому себе. Системный загрузчик перечитает исходный образ elf-файла с диска и все изменения в кодовом сегменте будут потеряны. Правда,​ вместе с ними будут потеряны и оперативные данные,​ которые в этом случае процессу придется хранить в разделяемой области памяти. Это существенно затруднит программирование,​ однако,​ затраченные усилия стоят того, поскольку атаки через ptrace (в силу их известности и простоты реализации) самые популярные из всех на сегодняшний день и в обозримом будущем их активность снижаться не собирается.
 +
 +===== /dev/mem или рокировка наоборот =====
 +
 +Практически на всех UNIX'​а имеется файл /dev/mem, представляющий собой образ _физической_ оперативной памяти компьютера (не путать с виртуальной!). Там же, где этого файла нет (например,​ удален по соображениям безопасности),​ его нетрудно создать и вручную своими лапами и хвостом: ​
 +
 +mknod -m 660 /dev/mem c 1 1
 +
 +chown root:kmem /dev/mem
 +
 +Листинг 1 создание файла-устройства /dev/mem, предоставляющего доступ к физической памяти компьютера с прикладного уровня
 +
 +Здесь: 660 – права доступа,​ /​dev/​mem – имя файла (может быть любым, например /​home/​kpnc/​nezumi),​ "​c"​ – тип устройства (символьное устройство),​ "​1 1"​ – устройство (физическая память). Файл /dev/mem (или как вы там его назовете) свободно доступен с прикладного уровня,​ но только для root, что не есть хорошо,​ но… лучше это, чем вообще ничего.
 +
 +Поскольку в операционных системах со страничной организаций оперативная память используется как кэш, одни и те же физические страницы в различное время могут соответствовать различным виртуальным адресам,​ поэтому,​ код процесса-жертвы (вместе с подходящим местом для внедрения) приходится искать по заранее выделенной сигнатуре.
 +
 +
 +
 +{{memory-war-ii_Image_4.png}}
 +
 +Рисунок 5 файл /dev/mem в шестнадцатеричном редакторе
 +
 +При этом нас подстерегают следующие проблемы. Первая (и самая главная):​ при недостатке физической оперативной памяти,​ наименее нужные страницы виртуального адресного пространства выгружаются на диск и в их число могут попасть и страницы,​ принадлежащие нашему процессу-жертве,​ причем не все сразу, а так… частями. Как решето или как дуршлаг. Следовательно,​ а) внедряться нужно в _часто_ используемые участки кода, вероятность вытеснения которых минимальна;​ б) если с сигнатурным поиском в /dev/mem произошел облом, не паникуйте,​ а просто подождите некоторое время и повторите операцию сканирования вновь, рано или поздно виртуальные страницы считаются операционной системой в память (если, конечно,​ процесс не будет скоропостижно прибит злым пользователем).
 +
 +Вторая проблема настолько несерьезна,​ что и упоминать ее не стоит, но… все-таки! Соседние виртуальные страницы адресного пространства зачастую оказываются в различных частях файла /dev/mem, поэтому:​ а) размер внедряемого shell-кода не может превышать размеров одной страницы — а это 1000h байт на x86; б) базовые адреса виртуальных страниц при вытеснении на диск всегда кратных их размеру,​ т. е. мы можем внедрить 200h байт shell-кода,​ начиная с адреса XXXX1000h, но не можем сделать это же самое с XXXX1EEEh.
 +
 +Остается только определиться с местом внедрения. А внедряться предпочтительнее всего в начало часто вызываемых функций. Если это будут "​внутренние"​ функции процесса-жертвы,​ то наш хакерский код окажется привязан к конкретному версии исполняемого файла. После выхода новой версии или даже компиляции старой версии другим компилятором (или с иными ключами),​ все смещения неизбежно изменяться и…
 +
 +Гораздо перспективнее внедряться в библиотечные функции. Такие, например,​ как printf(), расположенные в разделяемой области памяти и позволяющие определить свой адрес штатными средствами операционной системы без всякого дизассемблера. Естественно,​ внедрение в разделяемую функцию затронет все процессы,​ ее использующие и потому писать shell-код следует очень аккуратно. Но… задумаемся,​ что произойдет,​ если в момент внедрения,​ разделяемая функция _уже_ выполняется каким-то процессом?​! Правильно! С процессом произойдет крах! Ну туда ему и дорога! Зато при внедрении в виртуальные функции проблема загрузки виртуальных страниц с диска решается их простым вызовом. Короче говоря,​ нет худа без добра!
 +
 +Следующий листинг демонстрируют технику чтения/​записи ядерной памяти с прикладного уровня:​
 +
 +#include <​fcntl.h>​
 +
 +#define PAGE_SIZE0x1000
 +
 +int fd;
 +
 +char buf[PAGE_SIZE];​
 +
 +// открываем /dev/mem на чтение и запись
 +
 +// (подробнеесм. "man 2 open")
 +
 +if ((fd=open("/​dev/​mem",​ O_RDWR, 0))==-1) return printf("/​dev/​mem open error\n"​);​
 +
 +// перемещаемся в начало (необяз.)
 +
 +if (lseek(fd, 0, SEEK_SET) == -1) return printf("/​dev/​mem seek error\n"​);​
 +
 +// чтение данных из /dev/mem
 +
 +static inline int rkm(int fd, int offset, void *buf, int size)
 +
 +{
 +
 +if (lseek(fd, offset, 0) != offset) return 0;
 +
 +if (read(fd, buf, size) != size) return 0; return size;
 +
 +}
 +
 +// запись данных в /dev/mem
 +
 +static inline int wkm(int fd, int offset, void *buf, int size)
 +
 +{
 +
 +if (lseek(fd, offset, 0) != offset) return 0;
 +
 +if (write(fd, buf, size) != size) return 0; return size;
 +
 +}
 +
 +Листинг 2 работа с /dev/mem
 +
 +//​**Замечание**//:​ //под 4.5////​ BSD////​ (более свежие версии мыщъх не проверял) функция ////​read////​ _всегда_ возвращает позитивный результат даже файл /////​dev/////////​mem////​ уже закончился//​
 +
 +Универсальный вариант кода, работающий на всех платформах выглядит так:
 +
 +// читаем 0x1000 байт в буфер
 +
 +if (read(fd, buf, 0x1000) != 0x1000) return printf("/​dev/​mem read error\n"​);​
 +
 +Листинг 3 универсальный вариант чтения /dev/mem, работающий и под LINUX'​ом и под BSD
 +
 +==== защита ====
 +
 +На прикладном уровне у процесса-жертвы никаких защитных средств в оборонительном арсенале в общем-то и нет (в "​общем-то",​ потому что процесс может использовать динамическую шифровку кода, контроль целостности библиотечных функций перед их вызовом и т. д., но это уже явный перебор). На уровне ядра, создание файла /dev/mem блокируется элементарно,​ но… вместе с этим блокируются и многие полезные программы (в частности X'ы), так что остается только разграничение доступа к /dev/mem с ведением списка "​доверительных"​ лиц которые к нему могут обращаться,​ что отчасти реализовано в OpenBSD.
 +
 +Тем не менее, в _общем_ случае,​ надежной защиты от внедрения через /dev/mem _нет_ и не будет! Успокаивает лишь тот факт, что доступ к нету имеет лишь root, а root может делать все, что угодно,​ как и положено богу, ну а богов не судят.
 +
 +===== dl_load – мат в три хода =====
 +
 +Практически все приложения (за исключением небольшого круга системных утилит) используют динамически загружаемые библиотеки,​ которые так же могут быть использованы для внедрения в чужое адресное пространство. Самое простое — взять готовую библиотеку и подменить ее своей, но это слишком заметно,​ да и как-то по пионерски. Короче,​ такое решение не катит. Поэтому,​ запасаемся свежим пивом и курим man (в LINUX'​e – "​man ld.so",​ во FreeBSD – "​man ld"​).
 +
 +{{memory-war-ii_Image_5.png}}
 +
 +Рисунок 6 раскуривание man'а по ld.so
 +
 +Оттуда мы узнаем,​ что порядок поиска динамических библиотек — очень интересная штука и в LINUX'​е системный загрузчик,​ сосредоточенный в файлах "​ld.so"​ и "​ld-linux.so*",​ в общем случае поступает так (а в не общем - как ему скажет утилита ldconfig, см "​man ldconfig"​):​
 +
 +  - если в ELF-файле присутствует секция DT_RPATH с именем/​путем к динамической библиотеке,​ и такая библиотека по данному пути действительно обнаруживается,​ то подключается именно она, в противном случае осуществляется поиск в директории DT_RUNPATH (если есть);
 +  - если атрибуты setuid/​setgid сброшены,​ анализируется переменная окружения LD_LIBRARY_PATH,​ содержащая пути к динамическим библиотекам,​ которые там могут быть или… не быть;
 +  - если же их там нет, загрузчик как последнее средство использует пути по умолчанию /lib, а затем /usr/lib;
 +  - если требуемой библиотеки нет ни в одном из вышеперечисленных мест, то это облом!
 +Для ускорения поиска загрузчик использует файл /​etc/​ld.so.cache,​ содержащий таблицу хинтов (от англ. hint – подсказка,​ наводка),​ или, попросту говоря,​ перечь путей к ранее найденным библиотекам. Это нетекстовой формат,​ да к тому же доступный для модификации одному лишь root'​у,​ так что не будем на нем подробно останавливаться,​ а лучше посмотрим на файл /​etc/​ld.so.config,​ который задает порядок поиска динамических библиотек и в свежеустановленном KNOPPIX'​е выглядит так:
 +
 +/lib
 +
 +/usr/lib
 +
 +/​usr/​X11R6/​lib
 +
 +/​usr/​i486-linuxlibc1/​lib
 +
 +/​usr/​local/​lib
 +
 +/​usr/​lib/​mozilla
 +
 +Листинг 4 файл /​etc/​ld.so.config изсвежеустановленного KNOPPIX'​а
 +
 +Разумеется,​ модифицировать файл /​etc/​ld.so.config может только root, зато читать может любой желающий,​ а для успешной атаки большего и не надо!!! В частности,​ чтобы "​поиметь"​ Mozill'​y достаточно поместить библиотеку "​спутник"​ (термин пришел из MS-DOS) в одну из вышележащих директорий и тогда она будет загружена первой!!! Спутнику остается только "​похозяйничать"​ внутри чужого адресного пространства — подвигать стулья,​ сожрать кашу, поспать на постели и всю ее выспать,​ после чего благополучно ретироваться,​ загрузив оригинальную библиотеку и передав ей управление.
 +
 +{{memory-war-ii_Image_6.png}}
 +
 +Рисунок 7 нетекстовой формат файла /​etc/​ld.so.cache
 +
 +Вот только на этом пути нас ждут две большие проблемы. Первое — создать новые файлы в каталогах /lib, /​user/​lib,​… может только root, а его еще как-то заполучить надо, однако… анализ показывает,​ что файл /​etc/​ld.so.config зачастую содержит пути к несуществующим каталогам (в данном случае это /​usr/​i486-linuxlibc1/​lib),​ которые может создавать кто угодно и ложить в них что угодно!!! Вот только… прежде чем открывать на радостях свежее пиво следует решить вторую проблему. А именно — скорректировать или очистить кэш в лице файла /​etc/​ld.so.cache,​ к которому опять-таки имеет доступ только root, однако,​ кэш на то и кэш, чтобы хранить не все, а лишь последние найденные библиотеки. Что мы делаем:​ грузим все библиотеки,​ которые только установлены в системе (за исключением "​нашей"​),​ в результате чего, "​нашей"​ библиотеке в /​etc/​ld.so.cache очень скоро уже не окажется и она будет взята не из /​usr/​lib/​mozilla,​ а из /​usr/​i486-linuxlibc1/​lib!!!
 +
 +Но что делать,​ если в /​etc/​ld.so.config отсутствуют несуществующие пути?! Тогда — добывать root'​а любой ценой и ложить "​свою"​ библиотеку в /lib или /usr/lib. Во всяком случае это намного менее заметно,​ чем прямая модификация атакуемой библиотеки на диске (то есть ее "​заражение"​).
 +
 +Все, сказанное выше, относится главным образом к LINUX'​у. У BSD-систем порядок поиска динамических библиотек слегка иной, хотя суть остается той же (вот неплохая статья на эту тему, найденная в сети: http://​www.codecomments.com/​archive286-2004-4-172158.html):​
 +
 +  - анализируется переменная окружения LD_RUN_PATH;​
 +  - если нужной библиотеки там нет, анализируется переменная LD_LIBRARY_PATH;​
 +  - при наличии секций DT_RUNPATH/​DT_RPATH поиск происходит в них, причем DT_RUNPATH имеет приоритет над DT_RPATH;
 +  - библиотеки ищутся в общепринятых каталогах:​ сначала в /lib, потом в /usr/lib;
 +  - если существует файл /​etc/​ld.so.conf,​ загрузчик просматривает все упомянутые в нем каталоги;​
 +Изменение переменных окружения — еще один возможный способ атаки, но, увы, доступный одному лишь root'​у,​ да к тому же слишком заметный,​ но в некоторых случаях — просто незаменимый (если все остальные попытки атаки закончились крахом).
 +
 +==== защита ====
 +
 +Защититься от атак этого типа очень просто. Достаточно убедиться,​ что: а) во все "​библиотечные"​ каталоги писать может только root, б) файл /​etc/​ld.so.conf не содержит путей к отсутствующим каталогам. Тем не менее, не смотря на кажущуюся простоту,​ достаточно многие системы в конфигурации по умолчанию могу быть легко атакованы.
 +
 +===== >>>​ врезка ссылки на уязвимости в .so загрузчике =====
 +
 +  - **Unix****Programming**** - ****Q****) ****Order****of****dynamic linkage:**
 +    - __www.codecomments.com/​archive286-2004-4-172158.html__;​
 +  - **what hack in ld.so**?
 +    - __lists.debian.org/​debian-devel/​1999/​01/​msg02598.html__;​
 +  - **Gentoo vlnx Insecure DT_RPATH Vulnerability**:​
 +    - __secunia.com/​advisories/​23429__;​
 +  - **McAfee VirusScan For Linux Insecure DT_RPATH Remote Code Execution**:​
 +    - __www.securityfocus.com/​bid/​21592__;​
 +  - **LWN: virusscan: DT_RPATH vulnerability**:​
 +    - __lwn.net/​Articles/​214247__;​
 +  - **Shared object dependencies**:​
 +    - __osr5doc.ca.caldera.com:​457/​topics/​ELF_shared_obj_depend.html__;​
 +===== заключение =====
 +
 +За рамками статьи осталось множество интересных способ внедрения (в частности,​ директория /proc и ее содержимое),​ однако,​ одним хвостом всего ведь не охватишь,​ верно? ​
 +
 +Главное — не собрать огромную коллекцию способов внедрения (многие из которых быстро устаревают,​ превращаясь в антиквариат),​ главное — дать толчок к новым идеям, показать,​ что UNIX защищена намного слабее,​ чем это принято считать и, что несмотря на тщательно продуманную политику безопасности,​ _концептуальные_ дыры в ней все-таки есть.
 +
 +