Учебное пособие 1977
.pdfвиду диалогового окна можно быстро определить, с помощью какой функции оно создавалось.
Предположим, что интересующее нас диалоговое окно создано с помощью функции 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