Раздел: Документация
0 ... 84 85 86 87 88 89 90 ... 102 Листинг А.8. Содержимое окна Call Stacks отладчика Microsoft Visual Studio Debugger teSTCF-DIT! 004C1362O MFC42! 6c2922ae() MFC42! 6c298fc5() MFC42! 6c292976() tf№\ 6c29dcc() MFC42! 6c291cea() №С42! 6c291c73() HFC42! 6c291bfb() №С42! 6c291bba() Попробуем найти в стеке адреса 6C2922AEh и 6C298FC5h, соответствующие двум последним ступеням исполнения. Нажимаем Alt+б для перехода в окно дамна и, воспользовавшись комбинацией клавиш Ctrl+G в качестве базового адреса отображения, выбираем ESP. Прокручивая окно дамна вниз, мы обнаруживаем оба адреса возврата (в листинге А.9 они выделены рамками). Листинг А.9. Содержимое стека после раскрутки C012F488 0012FA64 0012FA64 004012FF <г 0040136F.ret. 8 первый адрес возврата 0012F494 00000Q0C C0000Q64 00403458 <г 30401328:рор esi 0012F4AO FFFFFFFF 0012F4C4 6С291СЕА 0012F4AC 00000019 00000000 6C32FAF0 0012F4B8 0012F4C0 0012FA64 01100059 0D12F4C4 00320774 002F5788 00000000 0012F4DO 00320701 77Е16383 004C1F20 0D12F4DC 00320774 002F5788 00000000 C012F4E8 000003E8 0012FA64 004F8CD8 0012F4F4 0012F4DC 002F5788 0012F560 0Q12F5CC 77F.6ID49 6C2923D8 00403458 <r C04G132C:ret: 0012F5CC 00000111 0£M0(6§9pAE]<-6C29237t:pop cbx/pop ebp/ret ICh 0012F518 C012FA64 0000C3F8 00000000 0012F53S 0012FA64 000003Е8 00000000 00I2F524 0C4012F0 00000000 0000000С M12F530 00000000 00000000 C012FA64 0012F53C 0СО003Е8 0012F564 ГбС298ТС5] 0012F548 OOOC03E8 00000000 00000000 °°12F554 00000000 000003Е8 0012FA64 Ячейки памяти, лежащие выше адресов возврата, представляют собой значения регистров, сохраненные в стеке при входе в функцию и восстанавливаемые ее завершении. Ячейки памяти, лежащие ниже адресов возврата, оккупи-аны аргументами функции (если, конечно, у функции есть аргументы) или Ия "ИНаллежат локальным переменным материнской функции, если дочер-4 Функция не принимает никаких аргументов. вращаясь к листингу A.G, отметим, что два двойных слова, лежащие на вер-;ia Кестека, соответствуют машинным командам POP EDI и POP ESI, а следующий umii адрес - 4012FFh — :>то тот самый адрес, управление которому нередает-40136Fh:RET 8. Для продолжения раскрутки стека мы должны ди-г,мблировать код по этому адресу (листинг АЛО). Листинг А.10. Дизассемблерный листинг праматеринской функции («бабушки»)
Прокручивая экран вниз, мы замечаем инструкцию ADD ESP, 64, закрывающую текущий кадр стека. Еще восемь байт снимает инструкция 40136Fh:RET 8, и четыре байта оттягивает на себя 401328:POP ESI. Таким образом, позиция адреса возврата в стеке равна: currentESP + 64h + 8 + 4 == 70h. Спускаемся на 70h байт ниже и видим: 0012F5G0 77E61D49 6C2923D8 00403458 <- 004С1328:Р0Р ESI/ret: Первое двойное слово — это значение регистра ESI, который нам предстоит вручную восстановить; второе — адрес возврата из функции. Нажатием Ctrl+G, 0x6C2923D8 мы продолжаем раскручивать стек (листинг АЛ 1). Листинг А.11. Дизассемблерный листинг прапраматеринской функции 6C2923D8 jrnp 6С29237В
Вот мы и добрались до восстановления регистров! Сместившись на одно двойное слово вправо (оно только что было вытолкнуто из стека командой RET), переходим в окно Registers и восстанавливаем регистры, ESI, EBX, ЕВР, извлекая со храненные значения из стека: 0012F500 77E61D49 6C2923D8 ШШШ «- 6C29237D:pop esi 0012F5CC 00000111 Ш12£МД[6C2922AEJ «-6C29237E:pop ebx/pop ebp/ret ICh Как вариант можно переместить регистр EIP на адрес 6C29237Dh, а регистр ESP на адрес 12F508h, после чего нажать F5 для продолжения выполнения программы- Программная часть 271 1д этот прием действительно срабатывает! Причем реанимированная программа уже «ис ругается» на ошибку последнем операции (как это было при восстановлении путем принудительного выхода из функции), а просто со не выполняет. Красота! ПЕРЕДАЧА УПРАВЛЕНИЯ НА ФУНКЦИЮ ОБРАБОТКИ СООБЩЕНИЙ Двум предыдущим способам «реанимации» приложений присуши серьезные ограничения и недостатки. При тяжелых разрушениях стека, вызванных атаками типа buffer overfull или же просто алгоритмическими ошибками, содержимое важнейших регистров процессора окажется искажено, и мы уже иссякшем ни совершить откат (стек утерян), пи выйти из текущей функции (EIP «смотрит в космос»). В консольных приложениях и такой ситуации действительно очень мало что можно сделать... Вот GUI —другое дело! Концепция событийно ориентированной архитектуры наделяет всякое оконное приложение определенными серверными функциями. Даже если текущий контекст выполнения необратимо утерян, мы можем передать управление на цикл извлечения ц диспетчеризации сообщений, заставляя программу продолжить обработку действий пользователя. Классический цикл обработки сообщений выглядит так (листинг А.12). Листинг А.12. Классический цикл обработки сообщений while (Getltesage(tasg. MULL. 0. 0)) { TranslateMessage(Smsg): Dispatch"essage(&msg): } Все, что нам нужно, — это передать управление на цикл while, даже не заботясь о настройке кадра стека, поскольку оптимизированные программы (а таковых большинство) адресуют свои локальные переменные не через ЕВР, а непосредственно через сам ESP. Конечно, при обращении к переменной msg, функция «угробит» содержимое стека, лежащее ниже его вершины, но это уже неважно. Правда, при выходе из приложения оно упадет окончательно (ведь вместо адреса возврата из функции обработки сообщений машинная команда RET обнаружит на вершине стека неизвестно что), но это произойдет после сохранения Всех данных и потому никакой угрозы не несет. Исключение составляют приложения, «забывающие» закрыть все открытые файлы и перекладывающие эту Раооту на плечи функции ExitProcess. Что ж! Можно так подправить адрес возврата, чтобы он указывал на ExitProcess! Давайте создадим простейшее Windows-приложение и поэкспериментируем с Ним. Запустив Microsoft Visual Studio выберем New ► Project ► Win32 Application итам — Typical Hello, World application. Добавим новый пункт меню, а в нем: char *р; *р= о,- и откомпилируем этот проект с отладочной информацией. *Роняем» приложение на пол, запустив отладчик, подгоняем мышь к первой строке цикла обработки сообщений и в появившемся контекстном Меню 0 ... 84 85 86 87 88 89 90 ... 102
|