Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Учебное пособие 1977

.pdf
Скачиваний:
15
Добавлен:
30.04.2022
Размер:
3.44 Mб
Скачать

защиты более целенаправленно. Аналитик может запускать и просматривать именно те участки программного обеспечения,

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

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

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

Также удобно комбинировать динамический метод со статическим. В этом случае аналитик заранее подготавливает листинг дизассемблера, а затем, в ходе анализа программы под отладчиком, регулярно заглядывает в этот листинг. Листинг, сгенерированный «умным» дизассемблером, намного информативнее, чем набор ассемблеров-ских команд, который можно наблюдать в соответствующем окне отладчика. Использование внешнего листинга часто позволяет быстро понять суть действий, выполняемых теми или иными блоками и узлами анализируемой программы, даже не начиная их трассировки.

Перечислим некоторые факторы, ограничивающие возможности отладчика.

1.Отладчик не может установить точку останова (кроме аппаратных) в ПЗУ, поскольку данный участок памяти физически не может быть изменен. Если программа на низком уровне работает с системными буферами базовой системы ввода-вывода (BIOS), расположенной в ПЗУ, может возникнуть необходимость подробного анализа отдельных функций BIOS. Поскольку при этом доступны лишь пошаговый проход и

31

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

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

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

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

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

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

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

32

может быть исследована под отладчиком. Говорят, что такая программа защищена от отладчика. Более подробно эти приемы будут рассмотрены далее.

1.4.2. Методика изучения программ динамическим методом

Анализ программы динамическим методом разбивается на три основных этапа:

поиск подходов к интересующим функциям программы (найти путь, по которому идти);

поиск интересующих функций программы (пройти этот самый путь);

3)анализ интересующих функций программы.

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

Метод маяков Метод маяков основан на установке точек останова на

так называемые маяки – точки программы, в которых программа выполняет действия, легко понимаемые без знания контекста, в котором эти действия выполняются. В роли маяков обычно выступают системные вызовы. Для примера рассмотрим фрагмент листинга кода, полученный с помощью встроенного дизассемблера отладчика Microsoft Visual Studio:

0042D877 lea eax, [ebp-228h] 0042D87D push eax 0042D87E push 104h

0042D883 call dword ptr [imp GetCurrentDirectoryW@8]

0042D889 cmp esi, esp

0042D88B call @ILT + 3505(RTC_CheckEsp) (42BDB6h)

0042D890 push offset string L"\\" (483C8Ch)

33

0042D895 lea eax, [ebp-228h] 0042D89B push eax

0042D89C call SILT + 1695( wcscat) (42B6A4h)

0042D8A1 add esp, 8

0042D8A4 push offset string f'GO.sys" (483C7Ch)

0042D8A9 lea eax, [ebp-228h] 0042D8AF push eax

0042D8B0 call @ILT + 1695(_wcscat) (42B6A4h)

0042D8B5 add esp, 8 0042D8B8 mov esi, esp 0042D8BA push 0 0042D8BC push 0 0042D8BE push 0 0042D8C0 push 0 0042D8C2 push 0

0042D8C4 lea eax, [ebp-228h] 0042D8CA push eax

0042D8CB push 0 0042D8CD push 3 0042D8CF push 1 0042D8D1 push OFOlFFh

0042D8D6 push offset string L"GO" (483C90h)

0042D8DB push offset string L"GO" (483C90h)

0042D8E0 mov ecx, dword ptr [ebp-OCh] 0042D8E3 push ecx

0042D8E4 call dword ptr [imp CreateServiceW@52]

0042D8EA cmp esi, esp

0042D8EC call @ILT+3505( RTC_CheckEsp) (42BDB6h)

0042D8F1 mov dword ptr [ebp-18h], eax

34

0042D8F4 mov esi, esp 0042D8F6 push 0 0042D8F8 push 0

0042D8FA mov eax, dword ptr [ebp-18h] 0042D8FD push eax

0042D8FE call dword ptr [ imp StartServiceW@ 12]

0042D904 cmp esi, esp

0042D906 call @ILT + 3505 (RTC_CheckEsp) (42BDB6h)

0042D90B mov esi, esp 0042D90D push 0 0042D90F push 80h 0042D914 push 3 0042D916 push 0 0042D918 push 3 0042D91A push 0

0042D91C push offset string L"\\\\.\\GO" (483C6Ch)

0042D921 call dword ptr [imp CreateFileW@28 (4A62BCh)]

0042D927 cmp esi, esp

0042D929 call @ILT + 3505 (RTC_CheckEsp) (42BDB6h)

0042D92E mov dword ptr [494618h],eax 0042D933 mov esi, esp

0042D935 push 0

0042D937 lea eax,[ebp-234h] 0042D93D push eax

0042D93E push 4 0042D940 push 494600h 0042D945 push 4 0042D947 push 494600h 0042D94C push 29A0004h

0042D951 mov ecx,clword ptr [494618h]

0042D957 push ecx

35

0042D958 call word ptr [imp DeviceIoControl@32]

При просмотре данного участка кода сразу бросается в глаза вызов системной функции GetCurrentDirectoryW (получить путь в текущую директорию в кодировке Unicode). В нашем случае эта функция получает расположенный в стеке по адресу ebp-228h буфер длиной 260 (10 4h) символов Unicode и записывает в него путь в текущую директорию. Далее к полученной строке приписывается Unieode-строка «\ GO. sys». Похоже, что в рассмотренном участке кода происходит преобразование короткого имени драйвера GO. sys в полное (вместе с путем в директорию). Судя по всему, далее с этим драйвером будут выполняться какие-то действия. И действительно, при беглом взгляде на дальнейшие машинные команды мы опознаем в них стандартную процедуру открытия устройства, обслуживаемого самоинсталлирующимся драйвером, с последующей передачей драйверу нестандартного код управления 29А0004Й, в котором сразу привлекает внимание тип устройства 29Ah (десятичное 666). Судя по всему, рассматриваемый код является вредоносным.

Таким образом, обнаружив в анализируемом коде маяк, мы смогли легко выяснить суть выполняемых программой действий и даже выдвинуть гипотезу о вредоносности рассматриваемой программы. Время, затраченное на анализ участка кода, не превышает 1 ... 2 мин.

Для контраста приведем участок кода той же программы, не содержащий маяков:

0042DA6D mov dword ptr [ebp - 4],0 0042DA74 jmp 42DA7Fh

0042DA76 mov eax, dword ptr [ebp - 4] 0042DA79 add eax, 1

0042DA7C mov dword ptr [ebp - 4], eax 0042DA7F mov eax, dword ptr [ebp-4] 0042DA82 imul eax,eax,208h

0042DA88 movzx ecx,word ptr 4953E0h[eax] 0042DA8F test ecx,ecx

36

0042DA91 je 42DAC9h

0042DA93 cmp dword ptr [ebp - 4 ] , 6 4 h 0042DA97 jae 42DAC9h

0042DA99 mov eax,dword ptr [ebp - 4] 0042DA9C imul eax,eax,208h

0042DAA2 add eax,4953E0h 0042DAA7 push eax 0042DAA8 push 483C98h

0042DAAD call @ILT + 4500 (42C199h) 0042DAB2 add esp, 8

0042DAB5 mov eax, dword ptr [ebp - 4] 0042DAB8 imul eax,eax,208h

0042DABE mov word ptr 4 953E0h[eax] , 0 0042DAC7 jmp 4 2DA7 6h

В этом случае аналитику вряд ли удастся быстро разобраться в сути действий, выполняемых анализируемым кодом. Лишь через 3 ... 5 мин можно понять, что это цикл из 100 итераций, переменная цикла лежит по адресу [ebp-4], внутри цикла анализируется глобальный массив Unicode-строк длиной по 260 символов каждая, пустая строка вызывает досрочный выход из цикла, а для каждой непустой строки вызывается функция по адресу 42С199Й, причем адрес строки является вторым параметром функции. Однако, что это за строки и что с ними делает функция, расположенная по адресу 42С199Й, остается загадкой и требует дальнейшего изучения.

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

Это можно сделать двумя способами:

1)найти в программе все маяки и установить на каждый точку останова;

2)установить точку останова в обработчик соответствующего системного вызова.

37

Второй способ намного проще (требуется устанавливать меньшее количество точек останова), но может привести к «зависанию» отладчика. Если отладчик использует данный системный вызов для своих нужд, то внутри отладчика возможно зацикливание. Кроме того, поведение многих системных вызовов операционных систем (например,

системного вызова Windows NtQuerySystemlnformation)

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

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

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

Метод Step-Trace первого этапа

Метод Step-Trace получил свое название по двум режимам трассировки программы отладчиком: с входом во вложенные функции программы (Trace) и без входа (Step). Суть метода заключается в пошаговом проходе изучаемой программы с попеременным использованием обоих режимов.

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

а) функция х реализует интересующие аналитика алгоритмы;

б) вызов функции х легко обнаруживается

38

аналитиком по внешним проявлениям программы.

Этим условиям удовлетворяет, например, функция ввода пароля с клавиатуры, или функция, выводящая на экран компьютера результат проверки пароля.

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

На втором этапе программа перезагружается, главная функция проходится в режиме Step до команды call fl. Затем с помощью команды Trace производится переход внутрь этой функции и тело функции fl пошагово проходится в режиме Step до тех пор, пока не встретится вызов функции, скажем, J2, внутри которого вызывается функция х.

После того как обнаружена функция J2, программа снова перезагружается, главная функция проходится в режиме Step до вызова fl, в режиме Trace управление передается внутрь fl, далее fl проходится и режиме Step до вызова/2, управление передается внутрь/2, внутри /2 ищется вызов функции, вызываемой из /2, в котором выполняются интересующие нас действия. Когда такая функция найдена, программа опять перезагружается и т.д.

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

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

39

Рис. 1.6. Типичная ошибка аналитика при применении метода Step-Trace

Предположим, что аналитик, проведя две итерации метода Step-Trace, убедился, что интересующая его функция х вызывается внутри функции /3 в последовательности вложенных вызовов, обозначенных двумя нижними стрелками. Если после перезагрузки программы аналитик вместо того, чтобы заново пошагово пройти эту последовательность, поставит точку останова в начале функции /3, то после запуска программы отладчик в первый раз остановится на этой точке, когда функция /3 будет вызвана из fl в последовательности вызовов, обозначенной верхней стрелкой. Функция х в этой последовательности не вызывается (в противном случае это было бы обнаружено на первой итерации). Таким образом, в процессе дальнейшего анализа программы будет изучаться последовательность вызовов, не ведущая к искомой функции х.

Обычно при использовании данного метода требуется пройти 10 ... 20 итераций.

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

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

40