Усложняем анализ кода с помощью векторной обработки исключенийr0064, r0064@mail.ru Наверное, тебе уже доводилось читать об обработке исключений в x64. А если нет, то поясню, что механизм структурной обработки исключений изменили до неузнаваемости. Если быть точнее, его сделали статическим. Никакие манипуляции с сегментным регистром fs уже не помогут. Но решения есть. Пусть они пока не описаны, зато проверены теорией и практикой. Позволь довести их до тебя в этой небольшой статье. Не SEH единымМогущественный и вездесущий в 32-разрядной системе регистр [fs] остался фактически не у дел: часть его функций забрал на себя сегментный регистр gs (например, указатель на TEB в ring-3 ныне лежит по адресу gs:30h, на peb - gs:60h). С каждой процедурой отныне связан свой обработчик, эта связь закрепляется в EXCEPTION_DIRECTORY PE-файла. Ну да, адрес обработчика теперь в стеке не хранится, fs использовать не можем, то есть SEH теряет свою привлекательность для применения в защите. То ли дело раньше - регистрация SEH «на лету», какие были времена... Но SEH ли единым, как говорится. Один мощный механизм почему-то остался не упомянутым в контексте 64-битных ОС Windows. Векторная обработка исключений (она же VEH). О ней ты знаешь по опыту работы с 32-разрядными Windows (один только Крис не раз писал об этой вопиющей теме). Если ты не в курсе, или немного выбился из него, прочитай статью Мэта Питрека, посвященную 32-битному VEH - wasm.ru/article.php?article=veh. Теория методаПодобно SEH, VEH позволит нам получать управление при возникновении эксепшена в программе: достаточно зарегистрировать свой обработчик (он же хендлер) функцией из ntdll RtlAddVectoredExceptionHandler.
Большое преимущество векторных обработчиков в том, что они выполняются до SEH. Кто не верит, может посмотреть функцию RtlDispatchException в ntdll — что вызывается первым? Внутренняя функция RtlpCallVectoredHandlers, а потом уже идет работа со структурными обработчиками. А теперь о самом интересном! Как нам, собственно, подменить существующий обработчик? Скажем, в случае, если мы по какой-то причине не хотим ставить новый вызовом RtlAddVectoredExceptionHandler. Ведь чертовски палевно вызывать функцию каждый раз, когда хочешь зарегистрировать обработчик исключений. Неэкспортируемая переменная в ntdll RtlpCalloutEntryList хранит указатель на список зарегистрированных обработчиков (в win xp, в vista список по-другому называется). Каждый элемент списка представлен некой структурой. Для удобства назовем ее (по аналогии со статьей Питрека) - VECTORED_EXCEPTION_NODE64. Ниже приводится частичный листинг RtlAddVectoredExceptionHandler в windows XP x64:
Как видно из дизассемблерного листинга, по смещению 18h (запомни его!) в структуре, содержащей инфу об обработчике, хранится его шифрованный адрес.
Сама структура, в целом, немногим отличается от той, что дана в статье Питрека (о 32-битном VEH). Обратимся снова к анализу RtlpAddVectoredHandler. На самом деле вызов NtQueryInformationProcess и последующий ксор адреса обработчика здесь не что иное, как функция RtlEncodePointer, вставленная в код оптимизирующим компилятором. А теперь о замене адресов обработчиков «на лету». Это можно сделать потому, что RtlAddVectoredExceptionHandler возвращает указатель на выделенную структуру VECTORED_EXCEPTION_NODE64. А значит, чтобы подменить хендлер динамически, нужно:
CodingПопробуем применить прелести VEH на практике. Писать будем в shellcode-стиле, как и полагается. Для начала, опишу алгоритм. Потребуется:
Базу загрузки ntdll ищем через PEB: А поиск необходимых функций выполняем через разбор таблицы экспорта ntdll. Здесь его не привожу, полная реализация кодеса прилагается к статье. Функция у нас не одна, а значит, лучше свернуть имена в хеш простым алгоритмом:
В качестве параметра передаем в rdx указатель на строку с именем функции и, в конце концов, регистрируем свой хендлер (hndlr01). Тут же заменяем «вручную» его адрес на другой (hndlr02). Затем мы вызываем исключение привилегированной командой rdmsr (чтение машинно-зависимого регистра), и после этого будет вызван не hndlr01 (первоначальный обработчик), а hndlr02. Код, демонстрирующий сказанное:
Понимаешь, в чем суть? Достаточно единожды вызвать RtlAddVectoredExceptionHandler и установить хендлер, а дальше можно менять динамически адрес обработчика! От нас требуется только хранить указатель на VECTORED_EXCEPTION_NODE64. То есть, фактически потеря fs скомпенсирована (ну, почти). Этим же способом мы можем добавить в список новый обработчик, не вызывая RtlAddVectoredExceptionHandler. Отслеживаем выполнение VEH-обработчиковПредставим на минуту, что нам пришлось исследовать код, хитро манипулирующий с VEH, как описано выше. Не скажу, что динамическая смена обработчиков сильно затруднит отладку (но не статический анализ!), так как отловить вызываемый хендлер можно через функцию RtlDispatchException. Правда, она неэкспортируемая, но есть экспортируемая функция RtlRaiseException, из которой вызывается RtlDispatchException.
После того, как нашли RtlDispatchException - переходим по первому call в ней - это и есть вызов RtlpCallVectoredHandlers:
Дальше — ключевые для постановки бряка фрагменты RtlpCallVectoredHandlers. WinXP:
Vista: Внимательно посмотрев на листинги, ты, наверняка, понял, что необходимо найти цикл вызова обработчиков - и дело в шляпе. Найти его труда не составляет - в нем обязательно находится вызов функции NtQueryInformationProcess/RtlDecodePointer. После того, как отыскали цикл - ставим бряк на инструкцию call-регистр. Звучит это пугающе, но при наличии Иды все делается за считанные минуты. Очень желательны здесь отладочные символы, если же сноровки достаточно, то можно и без них обойтись. Кстати, на Vista появилась возможность и вовсе «отключить» выполнение VEH-обработчиков через PEB. Как? В начале RtlpCallVectoredHandlers встречается такой код: То есть можно наставить кучу обработчиков, а потом сбросить флаг в PEB и ни один из них не будет выполнен! Сохрани памяткуВот как на 64-битных версиях Windows, в условиях тотального контроля над SEH, у нас в запасе остался VEH. Пригодиться он может много где. Ну, например, в различных пакерах, протекторах или в разных интересных программах. Вкупе с обфускацией описанная в статье техника может затруднить (пускай и незначительно) анализ кода. В последующих статьях я косвенно буду касаться изложенных здесь приемов, поэтому храни этот материал как своего рода памятку. |