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

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

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

первую команду после цикла

0100552Е lea еах,[ebx+ebx]

идаем команду Go to cursor ([Ctrl-F10]). Идем дальше.

Врезультате вызова

 

01005534 call edi

 

 

анализируемый буфер заполняется нулями, а в

результате вызова

 

 

 

01005558 call _DumpMessage (10057D0h)

 

в буфере появляется нечто похожее на интересующую

нас информацию:

 

 

 

0x000A4BF0 20 00 43 00 6f 00 бе 00 66 00

69

00 67 00 75.С.о.п.f.1.g.и OxOOOA4BFF 00 72

00

61

00 74

00 69 00 6f 00 бе 00

Od

00.г.а.t.1.о.п... 0х000А4С0Е 0а 00 Od 00

00

00

00 00 00

00 00 00 00 00

 

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

Перейдем в окне дампа памяти на адрес 000A4BD8 и продолжим трассировку программы. Дойдя до вызова

010056AF call _FormatNetworkInfо@28 (100628Dh)

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

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

010063СВ call _FormatPerInterfaceInfo@20

51

(1005B8Ch)

Перезапускаем программу и переходим к следующей итерации метода Step-Trace. По адресу 01005С08 мы видим вызов функции DumpMessage, в результате которого в буфер записывается название первого сетевого адаптера.

На следующей итерации метода Step-Trace мы понимаем, что данные записываются в буфер системной функцией FormatMessage из библиотеки kernel32.dll. При этом используются следующие параметры:

dwFlags = 0x800 (FORMAT_MESSAGE_FROM_HMODULE) -взять строку из таблицы ресурсов загруженного в память программного модуля;

IpSource = 0 – использовать загруженный образ ЕХЕфайла те кущего процесса;

dwMessageld – третий параметр текущей функции (лежит в стеке по адресу [ebp+10h]). В нашем случае это число 10016 (0x2720), которому соответствует строка L "Ethernet adapter%l";

dwLanguageld = 0 (LANG INEUTRAL) – нейтральный, «никакой» язык;

lpBuffer – берется из регистра еах, который заполняется следующим образом:

010057D7 mov esi,dword ptr [ebp + 8] 010057DA lea еах, [ebp + 14h] 010057DD push esi

010057E1 call dwordptr [imp wcslen (10011A8h)]

010057F2 lea eax, [esi + eax * 2]

т. е. к первому параметру (ebp + 8) функции DumpMessage прибавляется длина в байтах строки, указателем на которую является четвертый (ebp + 14h) параметр функции DumpMessage. Фактически это адрес внутри анализируемого буфера, начиная с которого в буфер записывается название сетевого адаптера;

nSize – берется из регистра есх, который заполняется

52

следующим образом:

010057D7 mov esi,dword ptr [ebp + 8] 010057DD push esi

010057E1 call dword ptr [ imp wcslen (10011A8h)]

010057EC mov ecx, dword ptr [ebp + OCh] 010057EF sub ecx, eax

т. е. из второго параметра (ebp + OCh) функции DumpMessage вычитается длина в Unicode-символах строки, указателем на которую является четвертый (ebp+ 14h) параметр функции DumpMessage. Фактически это длина буфера, переданного в функцию Dump-Message, за вычетом длины уже заполненной части данного буфера;

• Arguments (указатель на список строк, которые подставляются в результирующую строку вместо%1,%2, ...) – берется из регистра есх, который заполняется следующим образом:

010057DA lea eax, [ebp + 14h] 010057DE mov dword ptr [ebp - 4 ] , eax 010057E8 lea ecx, [ebp - 4]

т. е. данное значение попросту является указателем на четвертый параметр (ebp + 14h) функции DumpMessage.

Таким образом, указатель на адрес строки с названием сетевого адаптера содержится в четвертом параметре функции DumpMessage. В нашем случае этот указатель равен 000А4А18, т.е. указывает в область динамически распределяемой памяти. Посмотрим, откуда программа берет эту строку.

Шансы на успех метода аппаратной точки останова сомнительны, поэтому сразу воспользуемся методом StepTrace. Не будем описывать применение метода столь же подробно, как раньше, ограничимся лишь перечислением адресов, на которых дается команда Trace:

010066А5 call _main (1005382h)

010054C1 call _GetNetworkInformationg8 (1003E42h)

010042C4 call _GetPerInterfacelnfo@40 (10039C4h)

53

01003B0F call _MapFriendlyAndConnectionNames@28 (1002899b.)

010029F4 call dword ptr [imp wcscpy (1001178h)]

В результате применения метода Step-Trace мы обнаруживаем, что интересующая нас строка помещается в буфер функцией wcscpy (копирование Unicode-строки), содержащейся в библиотеке msvcrt.dll. Исходная строка берется из буфера по адресу 0007ВВ1С (ebp-3ECh в кадре стека функции MapFriendlyAndConnectionNames). Посмотрим, как она туда попадает, снова с помощью метода Step-Trace:

010066А5 call _main (1005382h)

010054C1 call _GetNetworkInf ormation@8 (1003E42h)

010042C4 call _GetPerInterfacelnf0@40 (10039C4h)

01003B0F call _MapFriendlyAndConnectionNames@28

0 1002985 call HrLanConnectionNameFromGuidOrPathS16

77D017EA call HrRegQueryValueEx (77D01410h)

77D01426 call dword ptr [imp RegQueryValueExW@24]

Оказывается, строка с именем адаптера размещается в буфере по адресу 0007ВВ1С буквально за несколько команд до того, как скопироваться в буфер по адресу 000А4А18. Поэтому применение метода Step-Trace проходит очень быстро, нам ни разу не приходится перезагружать отлаживаемую программу.

Функция RegQueryValueExW библиотеки advapi32.dll

документирована и является стандартной функцией для чтения данных из реестра.

Изучая параметры функции, мы понимаем, что значение реестра, откуда считывается строка, имеет имя Name. Осталось только определить имя ключа, к которому относится данное

54

значение.

Это сделать очень просто. Поставим точку останова на стандартную функцию открытия ключа реестра – функцию

RegOpenKeyExW из библиотеки advapi32.dll. В адресной строке окна встроенного дизассемблера Visual Studio напишем _RegOpenKeyExW@20 (имя функции, полученное после преобразования компилятором), установим курсор на команду, следующую за командой mov ebp, esp (чтобы на момент остановки программы кадр стека был уже сформирован), и нажмем клавишу [F9]. Затем запустим программу на выполнение, нажав [F5],

Когда программа остановится на точке останова, просмотрим в окне дампа памяти текущий кадр стека, начиная с адреса [ebp + 8]. Мы видим, что первый параметр функции равен 0x80000002 (HKEY_ LOCAL_MACHLNE), а второй параметр указывает на Unicode-строку

"SYSTEM\CurrentControlSet\ControlVNetwork\{4D36E972- E325-HCE-BFCl-08002BE103 18}\{5844482A-2394-42E2-8838- F7133864Е53A}\Connection".

Открыв данный ключ в реестре, мы убеждаемся, что внутри ключа FKEY_LOCAL_MACHINE\Network\{4D36E972- E325-llCE-BFCl-08002BE10318} имеются несколько подключений, каждый из которых соответствует одному сетевому интерфейсу, и что в значении Name подключения Connection каждого из этих подключений указано имя соответствующего сетевого адаптера.

Таким образом, мы получили ответ на вопрос, откуда утилита ipconfig получает информацию о сетевых интерфейсах

– из соответствующего ключа реестра. Правда, при внимательном изучении реестра можно заметить, что утилита ipconfig как-то отличает включенные сетевые адаптеры от выключенных и выдает информацию только о включенных адаптерах. Исследование вопроса, как утилита ipconfig отличает включенные сетевые адаптеры от выключенных, предлагается для самостоятельного упражнения.

При первом прочтении приведенного выше текста

55

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

1.5.Особенности анализа некоторых видов программ

1.5.1.Особенности анализа оверлейных программ

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

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

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

56

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

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

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

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

57

1.5.2. Особенности анализа графических программ Windows

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

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

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

При использовании модифицированного метода StepTrace для анализа оконных функций и функций, вызываемых из них, следует вначале выбрать точку входа в программу, из которой имеется прямая передача управления в интересующую нас функцию х. Другими словами, нужно получить адрес оконной функции окна, в котором пользователь дает команду выполнить действие, ведущее к вызову функции х. Проще всего сделать это, воспользовавшись утилитой Spy++, поставляемой в составе пакета Microsoft Visual Studio. Данная утилита позволяют, указав мышью окно, получить практически полную информацию об этом окне, в том числе и адрес его оконной функции. Использование утилиты Spy++ иллюстрирует рис. 1.10. В данном случае утилита Spy++

58

применена для получения адреса оконной функции главного окна калькулятора Windows. Этот адрес указан в строке Window Ргос и равен 01006118.

Рис. 1.10. Окно свойств окна, выдаваемое утилитой Spy-I

После того как адрес оконной функции получен, нужно поставить в нее точку останова. Не следует ставить точку останова в самое начало функции – в этом случае программа будет останавливаться на ней при приходе в окно любых сообщений, в том числе и сообщений, совершенно не интересных с точки зрения анализа программы (например, сообщения о том, что окно необходимо перерисовать). Обычно оконная функция начинает свою работу с анализа идентификатора пришедшего сообщения (это второй параметр оконной функции, он располагается в стеке по адресу [ebp + ОСЬ]). В подавляющем большинстве случаев аналитика интересует лишь реакция оконной функции на сообщение WM_COMMAND (lllh), приходящего в окно, когда пользователь выбирает пункт меню, нажимает специальную комбинацию клавиш, например [Ctrl-Ins], или выполняет какоето действие с одним из контрольных элементов окна (например, нажимает кнопку ОК). Для того чтобы точка останова срабатывала только при приходе сообщения WM_COMMAND, нужно просмотреть код оконной функции, найти там команды типа

59

mov еах, [ebp + ОС] cmp еах,lllh jne...

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

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

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

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

60