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

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

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

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

Предположим, что интересующее нас диалоговое окно создано с помощью функции DialogBoxParamW. Мы запускаем анализируемую программу под отладчиком, устанавливаем точку останова на функцию DialogBoxParamW и даем программе команду на создание диалогового окна. Программа останавливается на только что установленной точке останова. Четвертым параметром функции DialogBoxParamW является указатель на диалоговую функцию создаваемого окна, этот адрес размещается в стеке по адресу [ebp + 14h].

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

Особым случаем является анализ диалоговых функций программ, написанных в среде MFC. Для этих программ не только оконная функция диалогового окна, но и диалоговая функция являются стандартными системными функциями, не имеющими никакого отношения к анализируемой программе. Как правило, программа получает данные, введенные пользователем в диалоговое окно, непосредственно из полей объекта C++, соответствующего диалоговому окну (этот объект принадлежит дочернему классу класса CDialog). Если диалоговое окно является модальным, оно вначале создается как немодальное, а затем для окна вызывается функция CDialog::DoModal, преобразующая его в модальное диалоговое окно.

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

61

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

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

1.5.3. Пример анализа графической программы Windows

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

(рис. 1.12).

Рис. 1.11. Основное окно игры Minesweeper

Рис. 1.12. Окно программы Minesweeper, в котором выводится информация об установленных рекордах

Мы будем использовать Minesweeper, входящий в состав Windows ХР SP2, и отладчик, встроенный в Visual

62

Studio 2005. В других версиях отлаживаемой программы и отладчика процедура анализа может отличаться незначительными деталями.

Судя по внешнему виду окна Fastest Mine Sweepers, оно, скорее всего, является диалоговым. Чтобы проверить эту гипотезу, откроем файл winmine.exe в редакторе ресурсов Visual Studio. Для этого следует нажать комбинацию клавиш [Ctrl-O] (откроется окно, приведенное на рис. 1.13), нажать стрелочку справа от клавиши [Open], выбрать Open With (откроется окно, приведенное на рис. 1.14), затем выбрать Resource Editor и нажать кнопку ОК.

Рис. 1.13. Открытие ЕХЕ-файла в редакторе ресурсов Microsoft Visual Studio, первая стадия

Открыв программу в редакторе ресурсов, находим в списке ресурсов программы шаблон диалогового окна под номером 700 и убеждаемся, что это и есть искомое окно.

Чтобы проверить, является ли диалоговое окно модальным, достаточно щелкнуть мышью по основному окну программы Minesweeper в момент, когда открыто окно Fastest Mine Sweepers. Несложно убедиться, что переключения на главное окно не происходит, а значит, диалоговое окно

63

является модальным.

Рис. 1.14. Открытие ЕХЕ-файла в редакторе ресурсов

Microsoft Visual Studio, вторая стадия

Таким образом, количество системных функций, с помощью которых может создаваться окно Fastest Mine Sweepers, уменьшилось до пяти: DialogBoxParamA, DialogBoxParamW, DialogBoxIndirectParamA, DialogBoxIndirectParamW и DialogBoxIndirectParamAorW. В

Windows XP SP2 все эти функции, кроме последней,

реализованы как обертка над DialogBoxIndirectParamAorW (в

этом несложно убедиться, бегло просмотрев библиотеку user32.dll в дизассемблере). Таким образом, получаем, что для того чтобы узнать адрес оконной функции окна Fastest Mine Sweepers, нам достаточно установить точку останова в функцию DialogBoxIndirectParamAorW библиотеки user32.dll.

Приступим к анализу программы. Прежде всего запишем в директорию windows\system32 файл winmine.pdb,

загруженный с сайта Microsoft. Затем откроем программу

Minesweeper в отладчике Visual Studio, поставим точку останова в функцию DialogBoxIndirectParamAorW (не в самое начало функции, а после команды mov ebp, esp, чтобы сразу видеть правильный кадр стека), запустим программу на выполнение и выберем в меню Minesweeper пункт Game / Best

64

times. Программа остановится на точке останова.

 

 

Адрес диалоговой функции создаваемого диалогового

окна

является

четвертым

параметром

функции

DialogBoxIndirectParamAorW, он размещается в стеке по адресу [ebp + 14h]. Посмотрев в дамп памяти по этому адресу, находим адрес 010016FAh, перейдя по которому, мы обнаруживаем, что с него начинается функция BestDlgProc. Судя по названию функции, адрес диалоговой функции определен правильно.

Интересующие нас строки появляются в окне Fastest Mine Sweepers немедленно после создания окна. Обычно начальное заполнение элементов диалогового окна осуществляется диалоговой функцией в (ответ на сообщение

WM_INITDLALOG. Вряд ли авторы программы Minesweeper

отказались от этой технологии, по крайней мере, никаких оснований для этого пока не видно.

Посмотрим внимательно на начало функции

BestDlgProc:

010016FD mov eax,dword ptr [ebp + OCh] 01001700 sub eax,53h

push ebx

je _BestDlgProc@16 + 106h (1001800b.) 0100170A sub eax,28h

0100170D je _BestDlgProc@16 + 0F5h (10017EFh)

01001713 sub eax,95h

01001718 mov ebx,offset preferences + 0B8h (1005758h)

0100171D je _BestDlgProc@16 + 9Fh

(1001799h)

0100171F dec eax

01001720 jne _BestDlgProc@ 16 + HEh

(1001818b.)

01001726 movzx eax, word ptr [ebp +

lOh]

0100172A test eax,eax

65

0100172C jle _BestDlgProc@16 + HEh

(1001818h)

01001732 cmp eax, 2

01001735 jle _BestDlgProc@16 + 0E5h (10017DFh)

0100173B cmp eax,64h

0100173E je _BestDlgProc016 + 0E5h (10017DFh)

01001744 cmp eax,6Dh

01001747 je _BestDlgProc@16 + 0E5h (10017DFh)

0100174D cmp eax,2C3h

01001752 jne _BestDlgProc016 + HEh

(1001818h)

Вначале в регистр eax заносится идентификатор пришедшего сообщения (второй параметр функции), а затем выполняются последовательные сравнения этого идентификатора с различными константами. Вначале проверяется совпадение с константой WM_HELP (53h), затем с

WM_CONTEXTMENU (7Bh = 53h + 28h, обратите внимание,

что для оптимизации кода последовательные сравнения реализованы компилятором не с помощью команд cmp, а с помощью команд sub и dec). Далее проверяется константа

WM_INITDIALOG (110h), это как

раз то, что нам нужно. Устанавливаем точку останова на адрес 1001799b., на который передается управление по приходу сообщения WM_ INITDIALOG. Запускаем программу на выполнение, она немедленно останавливается на только что установленной точке останова.

Посмотрим внимательно на код, который будет сейчас выполняться:

01001799 push offset _Preferences + 38h (10056D8h)

0100179E push dword ptr [preferences + 2Ch (10056CCh)]

010017A4 push 2BDh

66

010017A9 push dword ptr [ebp + 8] 010017AC call _SetDText@16 (10016BAh) 010017B1 push offset _Preferences+78h

(1005718h)

010017B6 push dword ptr [preferences +

30h (10056D0h)] 010017BC push 2BFh

010017C1 push dword ptr [ebp + 8]

010017C4 call _SetDText@16 (10016BAh)

010017C9 push ebx

010017CA push dword ptr [preferences + 34h (10056D4h)]

010017D0 push 2Clh

010017D5 push dword ptr [ebp + 8]

010017D8 call _SetDText@16 (10016BAh) 010017DD jmp _BestDlgProc@l6 + OFOh

(10017EAh)

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

идентификатор текущего окна (первый параметр текущей функ ции, [ebp+ 8]);

числовая константа, каков ее смысл – пока неясно; указатель на какую-то глобальную переменную.

Посмотрев в дамп памяти по этому адресу, обнаруживаем там число, которое будет записано в одно из трех числовых полей диалогового окна Fastest Mine Sweepers;

указатель на какую-то непонятную структуру данных. Судя по всему, функция SetDText выполняет запись

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

67

Чтобы проверить справедливость наших предположений, с помощью отладчика изменим в памяти значения глобальных переменных по адресам 10056CCh, 10056D0h и 10056D4h на соответственно 1, 2 и 3. Для этого достаточно поместить курсор в окно дампа памяти на нужный байт и ввести новое значение. Продолжим выполнение программы, откроем окно Fastest Mine Sweepers, и увидим картину, приведенную на рис. 1.15.

Рис. 1.15. Результат ручной модификации глобальных переменных в памяти программы Minesweeper

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

Установим на указанный адрес аппаратную точку останова и перезапустим программу. Программа останавливается на команде

01002СВЗ mov dword ptr [_Preferences + 2Ch (10056CCh)],eax

Чуть выше мы видим вызов функции

01002СА9 call _ReadInt@16 (1002B27h)

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

Непосредственно под курсором мы видим другой вызов этой же функции:

01002СВ8 call _ReadInt@16 (1002B27h)

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

68

Функция Readlnt очень проста: push ebp

mov ebp,esp 01002В2А push ecx 01002B2B lea eax, [ebp - 4] 01002B2E push eax 01002B2F lea eax, [ebp + 8] 01002B32 push eax

010 02B33 mov eax, dword ptr [ebp + 8]

01002B36 push 0

01002B38 push 0

01002B3A push dword ptr _rgszBref (10050D0h) [eax * 4]

01002B41 mov dword ptr [ebp - 4], 4

01002B48 push dword ptr [_g_hReg (1005950h)]

01002B4E call dword ptr [imp RegQueryValueExW@2 4

01002B54 test eax,eax

01002B56 je _ReadInt@16+36h (1002B5Dh)

01002B58 mov eax, dword ptr [ebp + OCh] 01002В5В jmp _ReadInt@16 + 55h (1002B7Ch) 01002B5D mov edx, dword ptr [ebp + 14h] 01002B60 push esi

01002B61 mov esi, dword ptr [ebp + 8]

01002B64 cmp edx,esi

01002B66 mov ecx,edx

01002B68 jl _ReadInt@16 + 45h (1002B6Ch) 01002B6A mov ecx,esi

01002B6C mov eax, dword ptr [ebp + lOh] 01002B6F cmp eax,ecx

01002B71 jg _ReadInt@16 + 54h (1002B7Bh)

01002B73 cmp edx,esi

01002B75 mov eax,edx

01002B77 jl _ReadInt@16 + 54h (1002B7Bh)

01002B79 mov eax,esi 01002B7B pop esi 01002B7C leave 01002B7D ret lOh

69

При беглом просмотре ее кода сразу бросается в глаза системный вызов RegQueryValueEx. Очевидно, интересующее нас значение счи-тывается программой из реестра.

Далее можно просмотреть значения параметров функции RegQueryValueEx и проследить, откуда программа берет эти значения, но мы пойдем более простым путем. Запустим утилиту RegMon (или более современную утилиту РгосМоп) и сразу увидим, в каком месте реестра хранятся интересующие нас данные (рис. 1.16).

Рис. 1.16. Протокол взаимодействия программы Minesweeper с реестром Windows, полученный с помощью

программы RegMon

1.5.4. Особенности анализа параллельного кода

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

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

70