13.3. Сценарии атаки | Телекоммуникации вчера, сегодня, завтра

Последовательность действий при создании объекта радиосвязи

Бланк формы №1 ТАКТИКО-ТЕХНИЧЕСКИЕ ДАННЫЕ РЭС

Поставка оборудования обеспеченного радиочастотами

Витрина



13.3. Сценарии атаки

Итак, чаще всего защищенная программа отличается от незащищенной по следующим параметрам:

  • код самой программы зашифрован;
  • адрес оригинальной точки входа известен только протектору; О основная часть ресурсов зашифрована;
  • основная часть данных зашифрована;
  • таблицы импорта недоступны (настройку импорта выполняет сам протектор, и только он знает, какие функции должны быть импортированы);
  • присутствует код протектора.

Для того чтобы обезвредить (удалить) протектор и реконструировать программу к виду, максимально близкому к незащищенному, человеку, который пытается снять защиту, необходимо решить следующие задачи:

  • получить расшифрованный код программы;
  • найти адрес оригинальной точки входа;
  • получить расшифрованные ресурсы;
  • получить расшифрованные данные;
  • определить все импортируемые программой функции и восстановить таблицы импорта;
  • удалить код протектора.

Для большинства существующих протекторов разными людьми в разное время были разработаны эвристики, позволяющие успешно решить все или почти все эти задачи.

Так, например, код обычной программы не должен изменяться в процессе выполнения. Следовательно, после того, как протектор передал управление защищенной программе, и до ее завершения код представлен в памяти в таком же виде, как и у незащищенной программы. То есть для восстановления оригинальной секции кода достаточно извлечь ее из памяти после того, как программа была запущена. В современных версиях Windows для этого очень хорошо подходит стандартная функция Win32 API, носящая название ReadProcessMemory.

Найти адрес оригинальной точки входа несколько сложнее. Но, имея в распоряжении расшифрованную секцию кода, можно получить очень много информации, позволяющей эффективно решить эту задачу. По секции кода довольно легко можно определить, в какой среде разработки создана программа и каким компилятором она собрана. А наличие такой информации значительно упрощает отыскание оригинальной точки входа. Так, например, характерной особенностью программ, собранных с помощью Borland C+ + Builder, является то, что точка входа находится в самом начале секции кода. А точка входа в программы, собранные в Borland Delphi, напротив, соответствует началу функции, которая расположена в самом конце секции кода. Также в графических (не консольных) приложениях одной из первых вызываемых функций Win32 API является GetModuleHandle, т.к. значение, возвращаемое ЗЮИ функцией, ДОЛЖНО быть передано в функцию winMain, с которой начинаются почти все \Ут32-программы. Таким образом, если каким-нибудь способом удастся узнать, с какого адреса происходит вызов GetModuieHandie, оригинальная точка входа с большой вероятностью будет где-то неподалеку.

Есть и другой способ выявления оригинальной точки входа. Он заключается в том, чтобы в момент, когда протектор уже расшифровал секцию кода, но еще не передал в нее управление, с помощью функции writeProcessMemory заполнить всю секцию кода байтами со значением ОхСС (что соответствует команде процессора Int3 — вызов отладочного прерывания). Разумеется, такой код не может выполняться, о чем операционная система и известит пользователя. А в некоторых случаях (в частности, под Windows NT, 2000 и ХР) еще и сообщит адрес, по которому произошла ошибка. Этот адрес и будет являться адресом оригинальной точки входа. Данный метод известен со времен DOS, где он использовался, в частности, для поиска оригинальных точек входа в прерывания.

Кстати, во времена DOS также существовали упаковщики и протекторы исполняемых файлов, и для противодействия им разрабатывались автоматические депротекторы. Фактически, труднее всего автоматизации поддавалась именно задача поиска оригинальной точки входа. И существовал депротектор, носивший название Intruder (вторгающийся), который умел в процессе запуска программы определять, каким компилятором она была создана, и, исходя из этого, вычислял правильный адрес точки входа. Intruder "знал" практически все распространенные в то время средства разработки и в подавляющем большинстве случаев успешно снимал навесную защиту в автоматическом режиме.

С извлечением ресурсов больших проблем обычно не возникает. Протектору приходится расшифровывать и настраивать ресурсы в памяти таким образом, чтобы функции Win32 API, отвечающие за доступ к ресурсам, могли нормально работать. Следовательно, действуя точно так же, как действуют функции Win32 API, из загруженной в память программы можно извлечь каждый ресурс в отдельности, и тогда для восстановления секции ресурсов останется только построить дерево ресурсов (в соответствии со спецификацией формата Portable Executable).

Данные программы могут изменяться в процессе выполнения. Следовательно, для получения неискаженных секций данных необходимо читать их из памяти процесса именно в тот момент, когда протектор передает управление основной программе. Если оригинальная точка входа известна, определение такого момента не составит большого труда. Для этого можно использовать отладочные регистры центрального процессора.

Начиная с Intel 80386 все процессоры семейства х86 имеют аппаратные средства для отладки приложений. Процессор позволяет использовать до 4-х аппаратных точек останова. Каждая точка описывается типом доступа, который будет отслеживаться процессором (чтение, запись, выполнение), адресом, при обращении по которому процессор сгенерирует исключение, и размером контролируемой области (BYTE, WORD, dword). ДЛЯ работы с аппаратными точками останова используются так называемые отладочные регистры, которые в системе команд х86 имеют логические имена, начинающиеся с DR (Etebugging Registers). Достаточно установить точку останова на исполнение кода по адресу, соответствующему найденной оригинальной точке входа, обработать генерируемую процессором исключительную ситуацию и прочитать содержимое памяти.

Регистры аппаратной отладки не доступны напрямую пользовательским программам, т. к. операции чтения и записи отладочных регистров являются привилегированными и разрешены только в режиме ядра (в драйверах). Но к содержимому этих регистров можно получить доступ несколькими способами и без драйвера.

Во-первых, содержимое всех регистров в каком-то потоке может быть получено и установлено через такие функции Win32 API, как GetThreadContext и SetThreadcontext. Перед обращением к контексту потока рекомендуется остановить поток функцией suspendThread, а по окончании манипуляций с контекстом запустить его вновь при помощи ResumeThread.

Во-вторых, существует подмножество Win32 API, называемое Etebugging API (программный интерфейс отладки). С помощью функций, входящих в состав Debugging API, очень просто написать собственный отладчик, который будет получать извещения обо всех важных событиях и исключениях, возникающих в отлаживаемой программе. И отладчик также может использовать функции GetThreadContext и SetThreadContext для доступа к отладочным регистрам.

И наконец, в-третьих, изнутри программы доступ к отладочным регистрам может быть осуществлен путем использования механизма структурированной обработки исключений — Structured Exception Handling (SEH). Достаточно установить свой обработчик исключения и выполнить заведомо неправильную операцию (например, обращение по адресу 0x00000000). При возникновении ошибки будет вызван обработчик исключения, которому операционная система передаст указатель на структуру контекста потока, содержащую значение всех регистров. При этом значения, которые окажутся в этой структуре после завершения обработчика исключения, будут занесены в соответствующие регистры центрального процессора, включая отладочные.

Самую значительную трудность при снятии защиты, наверное, представляет собой восстановление таблиц импорта. Однако и эта задача может быть успешно решена.

Следует отметить, что большинство протекторов оставляют в защищенной программе таблицу импорта, содержащую как минимум по одной импортируемой функции из каждой библиотеки, использованной в оригинальной программе, иначе загрузчик, являющийся частью операционной системы, вообще не отобразит библиотеку в адресное пространство процесса. Это позволяет легко определить точный список библиотек, из которых импортируются функции. В крайнем случае, если перечень импортируемых библиотек получить не удалось, существует возможность узнать, какие вообще библиотеки были подключены к процессу. Это будут все библиотеки, на которые есть ссылки в таблицах импорта незащищенной программы, и некоторое количество других библиотек.

После того как все используемые динамические библиотеки оказались отображены в адресное пространство программы, можно получить адрес каждой экспортируемой функции для всех необходимых библиотек.

Теперь остается только найти в оперативной памяти, принадлежащей программе, расположение таблицы адресов импортированных функций и определить размер этой таблицы.

После загрузки и настройки программы в памяти таблица адресов импортируемых функций имеет обычно следующий вид. Сначала идут несколько 32-битовых значений, являющихся адресами функций, импортируемых из первой библиотеки. Последовательность указателей, относящаяся к первой библиотеке, заканчивается нулевым элементом. Сразу за ним идут указатели нa импортируемые функции второй библиотеки, которые снова заканчиваются нулем, и так далее для всех библиотек.

Таким образом, следует искать последовательности 32-битовых значений, заканчивающиеся нулевым элементом, и все элементы последовательности должны совпадать с адресами функций одной из библиотек. Каждая такая последовательность будет относиться к одной из библиотек, а все последовательности вместе будут образовывать таблицу адресов импортируемых пункций.

Редактор связей (linker) обычно размещает таблицу адресов импортируемых функций в секции статических данных, так что имеет смысл начинать поиск именно оттуда.

После того как определены все функции, необходимо построить и остальные таблицы, отвечающие за импорт, но это делается в соответствии со спецификацией формата переносимого исполняемого файла и очень легко автоматизируется.

Решение самой последней задачи — удаление кода протектора — на самом деле не является необходимым для получения работоспособной программы. Ели восстановить зашифрованные секции, индексы ресурсов, таблицы импорта, найти точку входа и создать новый исполняемый файл, в котором будет присутствовать код протектора, он должен работать и так. Но, удалив код и данные, добавленные протектором, можно несколько сократить размер исполняемого файла.



Поиск по сайту


Смотрите также