tags: windows - defence
Механизмы защиты Windows от эксплуатации уязвимостей. Часть 2 - SAFESEH и SEHOP.
Intro
Использовал:
- компилятор Microsoft (R) C/C++ версии 19.24.28316 для x86
- vscode
- Windows 10 с самыми последними обновлениями
- x64dbg
В этой серии статей мы говорим о механизмах защиты от эксплуатации уязвимостей в Windows.
SEH
Если вы программируете, то знаете, что в работе кода могут возникнуть ситуации, при которых происходит обращение к недоступной вам памяти, запись в несуществующий элемент массива, запись в несуществующий список, запись на переполненный диск и так далее. Такие ситуации называют исключениями. Вы можете их обработать явно - указать, что сделать, если возникнет исключение. Можете и не обрабатывать. В Windows для этого существует специальный механизм SEH или Structured Exception Handling, который отвечает за обработку исключений в вашей программе, даже если вы явно не написали обработчик. Компилятор Microsoft C/C++ сам вставит код для обработки исключений и сама Windows вам поможет его обработать. Поэтому SEH является и технологией уровня компилятора (compile based) и уровня ОС (os based).
Кстати, как бы не критиковали яву, но этот язык вам даже не позволит скомпилировать программу с необработанным исключением. Еще она очень сильно форсит ООП на уровне компилятора, за счет чего долго живет в энтерпрайзе
Важно заметить, что SEH - это механизм x86 систем. В 64-битных механизм другой и рассматриваться не будет. Для начала, разберем, что это такое, потом как его эксплуатируют и существующую защиту.
Заранее предупреждаю - это не статья о всех деталях работы SEH, нам это знать не обязательно, мы лишь обозреваем как его защищают. Также - это не статья об эксплуатации SEH, мы лишь кратко узнаем, в чем ее суть, чтобы просто понимать от чего защищаемся, как это было в первой части. Если же вы хотите увидеть более детальную статью об этом - пишите мне в личку.
Для обработки исключений программы, во время работы создается список обработчиков (handlers). Так как SEH применяется к каждому потоку, то найти указатель на список можно в специальной и очень важной структуре TEB (Thread Environment Block). Она содержит различную информацию о конкретном потоке:
typedef struct _NT_TIB {
struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;
PVOID StackBase;
PVOID StackLimit;
PVOID SubSystemTib;
#if defined(_MSC_EXTENSIONS)
union {
PVOID FiberData;
DWORD Version;
};
#else
PVOID FiberData;
#endif
PVOID ArbitraryUserPointer;
struct _NT_TIB *Self;
} NT_TIB;
typedef NT_TIB *PNT_TIB;
Thread Environment Block обычно расположен по сдвигу от адреса, расположенного в регистре FS (для x86). Дебагер x64dbg также сообщает об этом:
ExceptionList (первый элемент структуры) - содержит указатель на первый элемент списка обработчиков исключений. В x64dbg командой teb()
можно получить абсолютный адрес TEB и изучить ее:
Обработчики представлены в виде структуры EXCEPTION_REGISTRATION_RECORD, которая является недокументированной, но в сети есть ее описание от различных реверсеров.
Так как я проверял все на Windows 10 с самыми последними обновлениями на момент написания (25.03.2020), то пришлось все отдебажить лично и убедиться, что источники в интернете все еще актуальны.
Ее описание выглядит так:
typedef struct _EXCEPTION_REGISTRATION_RECORD
{
PEXCEPTION_REGISTRATION_RECORD Next;
PEXCEPTION_DISPOSITION Handler;
} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;
Next - указатель на следующий элемент списка
Handler - указатель на код обработчика
Вид в дебагере:
Написав простенькую программу и открыв ее в x64dbg, можно проследить весь список:
Когда в программе происходит исключение, специальная функция в ntdll KiUserExceptionDispatcher
начинает обход списка, для поиска необходимого обработчика, подходящего к конкретной ситуации.
Вот так, очень очень кратко, работает SEH. Теперь посмотрим на эксплуатацию этого механизма.
SEH exploitation
Для примера возьмем код из первой части и добавим запись по некорректному адресу в памяти, чтобы вызывать исключение:
#include "stdio.h"
int main()
{
unsigned char buf[64];
FILE *fp = fopen("crash_file", "r");
fread(buf, 1, 1024, fp);
int *a = (int *) 0x41414141;
*a = 0xdeadbeef;
return 0;
}
Код содержит уязвимость buffer overflow, так как читает 1024 байта в 64 байтный буфер.
Откроем в дебагере и посмотрим на список SEH:
Это обработчики конкретно нашей программы. Их адреса расположены на стеке и выглядят вот так:
Схематично и наглядно:
Очевидно, что переполнив буфер, мы можем затереть адреса реальных обработчиков на свои. В данном случае мы затерли адресом 0xDEADBEEF:
Список SEH тоже поменялся:
Схематично:
Отпустив дебагер и продолжив выполнение мы получим access violation, так как адрес 0xDEADBEEF явно некорректный:
Если бы мы вместо 0xDEADBEEF указали реальный адрес вредоносного кода, то, при возникновении исключения, он бы выполнился. Собственно это и есть суть эксплутации SEH overwrite. Данный способ применяется, когда программа скомпилированна с Buffer security check. Как видите на рисунке выше, мы также затерли security cookie, но прикол в том, что его проверка происходит по выходу из функции, а исключение происходит раньше. То есть, перезапись SEH является обходом защиты из первой части. Перейдем к методам защиты.
Обнуление регистров
Начиная с Windows XP SP1, перед вызовом обработчиков исключений, все регистры процессора обнуляются. Это делается для того, чтобы даже если SEH был перезатерт вредоносным кодом, его работала была нарушена отсутствием нужных данных в регистрах. Усложняет написание шеллкодов, так как теперь он не сможет получить на вход важные данные. Данная защита является защитой на уровне ОС.
SEHOP
Structured Exception Handler Overwrite Protection - начиная с Windows Vista SP1, предотвращает перезапись обработчиков, которую мы рассмотрели ранее. Является защитой на уровне ОС и не зависит от того, как вы скомпилировали свою программу. Посмотрим еще раз на скрин с SEH цепочкой:
Суть SEHOP заключается в том, что эта технология проверяет всю валидность цепочки. Это означает, что все адреса следующего элемента списка должны быть корректными и располагаться на стеке. Теперь нельзя просто забить хламом буфер и перезатереть первый обработчик, забив на все остальные. Это очень сильно усложняет эксплуатацию, так как придется подбирать правильные адреса. Также проверяется значение адреса следующего элемента в последнем обработчике цепочки - он должен равняться 0xFFFFFFFF, как на скрине.
SAFESEH
SAFESEH - технология защиты, уровня компилятора, при которой, в заголовках PE файла программы, создается специальная таблица, с заранее предустановленными, относительными адресами обработчиков. Произошедшее исключение, во время работы, проверяется по этой таблице и если не совпадает - программа завершается. SAFESEH включается одноименным ключом компиляции. Чтобы посмотреть на нее в живую, откроем нашу программу в PE Bear от прекрасной hasherezade.
Здесь мы видим количество обработчиков SEHandlerCount и их адреса SEHandlerTable.
Outro
Даже познакомившись с таким малым количеством технологий, можно сделать вывод, что их сила - в комбинации. И чем их больше - тем лучше. Только защита на всех уровнях (компилятор, ОС, процессор) поможет усложнить атаки. Радует, что рассмотренные механизмы стары и все еще действенны. Злоумышленники каждый год находят что-то новое, и защита прогрессирует соответственно, повышая порог вхождения, что не может не радовать. Считаю, что основная суть защиты SEH раскрыта, а в детали погружаться не было задачи. В отличие от обилия статей в интернете, я включил описание эксплуатации, так как в отрыве от нее, непонятно, что защищаем.
Вообще, если честно, это все я пишу для себя, чтобы возвращаясь к определенной теме, не читать по 10-50 закладок в браузере, а быстро пробежаться по основным моментам. Еще мне пришла мысль, из-за того, что я наверное один из немногих пишу статьи в вики - они не индексируются. Ну да ладно.
Всем спасибо!
Вверх