IT-Блог о программировании и операционных системах

Отладка Delphi-приложений в OllyDbg

Отличная всё таки вещь – привычка. Нет, я не про вредные привычки говорю, не переживайте, я имею ввиду правильные, профессиональные привычки. Пить и курить безусловно вредно, а вот использовать совместно прекрасные программные средства – нисколько, одна только польза. Почему именно в Olly, если есть прекрасный интегрированный в среду Delphi отладчик? Нет, друзья мои, я не пытаюсь сказать, что штатный отладчик плох, или не справляется со своими обязанностями, ни в коем случаи, просто, это дело привычки. И чтоб меня десять раз ударили томом Кнутта, если я такой один. И действительно, отладчик Olly приобрёл большую популярность как среди программистов, так и среди любителей покопаться в кишках некой платной утилиты. Ещё бы: куча возможностей, удобство использования, куча плагинов в конце концов, всё это делает отладчик Olly прекрасным инструментом для любого мало-мальски профессионального разработчика. Так же скажу, что данная заметка больше будет интересна тем программистам, которые не используют OllyDbg, но хотели бы познакомиться с этим инструментом поближе.
Предвкушаю шквал вопросов, летящих в мою сторону. Как нам, Delphi разработчикам отлаживать программу, если эта Olly просто пестрит ассемблерными инструкциями! Где наш нормальный паскалевский код? Секундочку! А разве в дельфовом отладчике нет окна CPU, с такими-же инструкциями? Есть. Да и окошко с нормальным исходником там тоже имеется, его просто нужно включить. Как, читаем дальше.


Как это и не странно, Olly прекрасно понимает отладочную информацию предназначенную для Борландовского отладчика TD32. Точнее сказать, это не странно, ведь если учесть то, что автор этого инструмента разрабатывал его в среде Borland C++, можно предположить, что он отлаживал свой отладчик в самом себе. Но для того, чтобы отладочная информация в исполняемом файле присутствовала – ее нужно включить в него. Вы наверняка знаете, как это делается, но если вы забыли, я покажу вам:
TD32_Info
На рисунке слева отображено окно настроек проекта среды Delphi 7. Включение отладочной информации в файл производится установкой управляющего флажка “Include TD32 debug info”. Я для примера создал небольшой проект, код которого привожу ниже:

program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils, Windows;
var
 a:integer = 1;
 i:integer;
begin
 if a > 0 then
  writeln('Hello World!');

 for i:=0 to 2 do
  MessageBox(HWND_DESKTOP,'Delphi Day','Hi there!',MB_ICONASTERISK);

 try
  for i:=2 downto 0 do
    a := a div i;
 except
  MessageBox(HWND_DESKTOP,'Деление на нуль!','Ошибка',MB_ICONASTERISK);
end;
end.


Код очень простой, однако, для демонстрации он вполне пригоден. Есть цикл, условие, вызов API, исключительная ситуация, вообщем-то достаточно, для полевых испытаний. После компиляции, исполняемый файл будет намного большего размера, чем обычно. Для сравнения, файл собранный без отладочной информации весит 40,5 кб, когда как бинарник с оной, весит 643 кб. Потолстел он так из за того, что теперь он включает в себя отладочную информацию. Весь этот кусок сала – новая секция .debug.


Отладка



После загрузки исполняемого файла в отладчик, чистого дельфового кода вы конечно не увидите. Вместо этого на дисплее появятся ассемблерные инструкции, в которых порой и сам чёрт ногу сломит. Это поправимо. Можно заставить Olly отображать нормальный код в отдельном окне (для этого мы и включали отладочную информацию), для этого, в главном меню, нужно выбрать пункт View и затем Source files. В появившемся окне выбрать файл *.dpr, или другой модуль, если ваш проект состоит из нескольких файлов. После выбора исходного файла, вы можете смотреть удобный, паскалевский код в отдельном окне. Например у меня оно выглядит вот так:


olly_pascal


Работать в Olly стало намного нагляднее, не так ли? Здесь всё, почти так же как и в Delphi. Исполняемые инструкции (операторы) помечены стрелочками “>”. Жёлтый маркер указывает на следующую выполняемую инструкцию. Красный – точка останова. Точки останова можно обозначить как в окне исходного кода, так и в окне CPU, через контекстное меню или клавишей F2. Пошаговая трассировка выполняется по клавишам F7 (cо входами в подпрограммы) и F8 (без входов). Запуск программы по F9. Кстати, брейкпоинты можно задавать как обычные, так и условные (см. справка по Olly, разделы: Breakpoints, Evaluation of expressions), что как вы знаете очень удобно, если нужно контролировать длинные циклы например. Так же, вызвав контекстное меню, можно посмотреть в окне дисассемблера код инструкции (инструкций), выбрав View in disassembler. Или, находясь в окне дизассемблера просматривать строки кода в справочном окне. Вот как это выглядит:


olly_helpwindow 


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


Меняем код



Патч кода – неотъемлемая часть отладки или взлома программного обеспечения. Olly позволяет менять код даже во время выполнения программы, на лету. Рассмотрим небольшой пример. Изменим программу так, чтобы конструкция:


writeln(‘Hello World!’);


Не выполнялась вообще. Сделаем это несколькими способами. Заодно посмотрим, как менять значения в памяти. Начнём. Первым делом, изменим условие. Для этого, в окне исходника кликните два раза на строку if a > 0 then, тем самым переходим непосредственно к этому сравнению в окне дизассемблера:


olly_strelki


Для удобства, я вставил свои комментарии на некоторые инструкции, они отмечены звёздочкой. Стрелки показывают схематично, как будет выполняться код, в зависимости от условия. По рисунку видно, что чтобы нам обойти вывод сообщения на консоль, необходимо изменить условие так, чтобы переменная “a”, стала меньше, или равнялась нулю, или чтобы сделать так, чтобы вместо сравнение происходило не с нулём, а единицей, тогда условие тоже окажется ложным и надпись мы не увидим. Начнём со второго варианта:


• Встаньте на конструкцию CMP DWORD PTR [ESI],0


• Нажмите пробел (режим Assemble) и введите вместо нуля, единицу


• Нажмите Assemble и сохраните изменения в файл (выделяем изменения –> ПКМ –> Copy to executable –> All modifications –> Copy All –> ПКМ –> Save file)


Или, если не хотите сохранять, просто запустите программу на выполнение клавишей F9. Можно также пройти пошагово клавишей F8, чтобы убедиться в том, что программа ведёт себя так, как мы этого хотим.


Теперь давайте изменим сами данные в программе. То есть сделаем так, чтобы переменная “a” равнялась нулю. Для этого перезапустите программу в контексте отладчика (Ctrl+F2) и снова обратите свой взгляд на предыдущий рисунок. Смотрите, самая первая инструкция MOV ESI,00409298. Она помещает в регистр ESI некий адрес (адрес переменной), а следующая инструкция сравнивает двойное слово по этому адресу с нулём. Следовательно в ESI, адрес переменной “a”. Посмотрим ее значение:


• Встаём на конструкцию MOV ESI,00409298


• Вызываем контекстное меню (ПКМ) –> Follow in dump –> Immediate constant


• Смотрим значение типа DWORD в окне дампа:


olly_dump 


Это значение следует читать как “00 00 00 01”. Теперь изменим один байт. Встаём на “01” –> Ctrl+E, и меняем “01” на “00”. Теперь можно запустить программу, а можно сначала сохранить изменения на диск в файл, а уже потом запустить. Сохранение производится почти так же, как вы сохраняли изменённый код.


Рассмотрим последний (но не из последних) пример. В нём мы просто поменяем инструкцию JLE на JMP (можно на: JNB (оно же JAE), подробнее об этих инструкциях см. справочник по ассемблеру). Как вы знаете, JMP – это инструкция безусловного перехода. Мы применим ее, чтобы обойти вывод сообщения на экран. Измените конструкцию JLE SHORT 004081D2 так, чтобы вместо JLE, было бы JMP как вы уже это делали и затем запустите программу.


Мы рассмотрели, как в Olly можно легко менять и сохранять код и данные.


Watches



В Delphi есть прекрасный инструмент инспектирования переменных – Watch list. Позволяет следить за переменными во время отладки приложения. Такое же чудо конечно есть и в Olly. Вызвать его можно так: View –> Watches. Можно добавлять имена регистров, участки памяти, можно использовать целые выражения (подробнее в справке по Olly). Что и говорить, для нормального отладчика – вещь незаменимая. Давайте добавим туда разыменованый  указатель на нашу многострадальную переменную “a”. В окне Watch expressions нажмите пробел и введите “[00409298]”. Очень легко! Попробуйте прогнать программу по F8. Вы увидите, как будет изменяться значение по этому адресу.


Заключение



Хотел привести список плагинов, которые по моему мнению очень помогают при отладке, но не буду. Ведь, навязывать свои привычки другим - себе на погибель :) Но о двух плагинах всё таки упомяну:


• OllyScript – плагин, позволяющий исполнять самописные скрипты распаковки некоторых упаковщиков и т.д.


• CommandBar – командная строка для Olly. Львиную долю операций можно быстро производить с помощью команд данного плагина.


Эти и другие плагины вы можете скачать с этих страниц: http://www.openrce.org/downloads/browse/OllyDbg_Plugins , http://tuts4you.com/download.php?list.9





Я конечно не могу рассказать вам об Olly всё, чего хочу в одной заметке. Да и сам я далеко ещё не профи по данному продукту, хотя пользуюсь им уже достаточно долго. Но одно скажу точно, у Olly есть целый вагон и маленькая тележка возможностей, которые обязательно нужно потыкать/пошмыкать/испробовать. Данную заметку я писал с надеждой на то, что не я один отдаю большее предпочтение нештатному отладчику. Безусловно, у Olly есть минусы перед отладчиком Delphi, но… в нём есть и плюсы. Спасибо за внимание, до следующих постов дорогие друзья.


Всё, устал…



В качестве бонуса всем, кто прочитал всю эту писанину:



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




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




Слепое блуждание в отладчике, скорее всего, непродуктивно. Полезнее использовать отладчик, чтобы выяснить состояние программы, в котором она совершает ошибку, затем подумать о том, как такое состояние могло возникнуть. Отладчики могут быть сложными и запутанными программами, особенно для новичков, у которых они вызовут скорее недоумение, чем принесут какую либо пользу…»




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




— Брайан Керниган и Роб Пайк

14 комментариев:

  1. Отличная статья. Спасибо!
    Буду ждать новых публикаций.

    ОтветитьУдалить
  2. Спасибо и вам на добром слове. Стараемся :)

    ОтветитьУдалить
  3. хорошая и легкоусвояемая статья. спасибо

    ОтветитьУдалить
  4. Хороший стиль изложения. Спасибо, добавил в закладки

    ОтветитьУдалить
  5. Очень редко на практике появляется необходимость смотреть ассемблерный код. Отладчик похоже был заточен не на создателей, а на ломателей. Что скорее вредно, чем полезно. Увы.

    ОтветитьУдалить
  6. Смотря какой проект. Да и как говорится, кому как... Удобно смотреть ассемблерный код например тогда, когда не уверен, что компилятор оптимизирует, когда, и как, и оптимизирует ли вообще. На самом деле есть куча случаев.

    По поводу ломателей... Почему вы думаеете, что это вредно? Следуя вашей логике, так можно и про IDA Pro сказать, и про SICE. Или я вас неправильно понял?

    ОтветитьУдалить
  7. На моей 10-ти летней практике таких случаев было очень мало. Чаще проще и эффективнее оптимизировать алгоритм, а не его реализацию.

    По поводу ломателей - ни к чему хорошему ломание не приводит. А IDA Pro и SoftIce используют в России только может в < 1% случаев для благих целей.

    Так что блог Delphi-разработчика получается так себе ;-)

    ОтветитьУдалить
  8. Значит за 10 лет Вашей практики было очень мало более или менее больших и серьёзных проектов, где бы отладка на низком уровне имела не последнее место.

    Мы Ждём от вас нормальных, "взрослых" обоснований того, что взлом ни к чему хорошему не приводит.

    По поводу: - "А IDA Pro и SoftIce используют в России только может в < 1% случаев для благих целей." можем вам сказать, что вы плохо знаете свою страну и сферы применения данных продуктов.

    Так что получается, что опыта у вас в этих вопросах - так себе ;)

    ОтветитьУдалить
  9. Спасибо за комментарии.

    Толсто троллить от анонима, на мой не искушенный взгляд, моветон.
    Меряться письками у кого сколько стажа, тоже.

    > Очень редко на практике появляется необходимость смотреть ассемблерный код.
    Студентам которые пишут калькуляторы и Paint'ы на зачет, несомненно. Ах да. Есть еще огромная армия программистов ADO + Access. Им тоже отладчик, тем более такой, никчему.
    Хотел бы я посмотреть, как работать с кривым кодом в DCU, без использования...
    А может еще и программирование портов(не сокетов, речь идет, например о COM-портах) пролетает на ура, без этого?
    Мммм... А прикладная математика, тоже оптимизируется очисткой индийского кода?

    "О сколько нам открытий чудных. Готовят просвещенья дух." (c)

    C Уважением.

    ОтветитьУдалить
  10. >>По поводу: - "А IDA Pro и SoftIce используют в России только может в < 1% случаев для благих целей." можем вам сказать, что вы плохо знаете свою страну и сферы применения данных продуктов.

    Чтож Вы сами начали с очень показательных "взрослых" примеров.

    >>Хотел бы я посмотреть, как работать с кривым кодом в DCU, без использования...

    Я не работаю с крвым кодом в DCU. Самое большее, что я использовал без исходного кода, это OBJ от различных стандартных библиотек, таких как ZLIB, PNG, JPEG итд. Чего и Вам желаю.

    С COM-портами действительно не приходилось работать, посыпаю голову пеплом.

    >> Мммм... А прикладная математика, тоже оптимизируется очисткой индийского кода?

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

    ОтветитьУдалить
  11. >> Чтож Вы сами начали с очень показательных "взрослых" примеров.

    Вы так и не ответили на вопрос. Глупо... А если бы я отлаживал мальварь например, интересно, Вы бы тоже так сказали? Ещё раз: это пример, как не стоит защищать свои компоненты и ПО.

    >> Я не работаю с крвым кодом в DCU. Самое большее, что я использовал без исходного кода, это OBJ от различных стандартных библиотек, таких как ZLIB, PNG, JPEG итд. Чего и Вам желаю.

    А если припрёт?

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

    Но есть ведь не только матрицы.

    ОтветитьУдалить
  12. > Я не работаю с крвым кодом в DCU. Самое большее, что я использовал без исходного кода, это OBJ от различных стандартных библиотек
    Demo-компоненты от TMS, и куча других demo. Чтобы покупать продукт, нужно его протестировать. И когда начинают вылезать костыли со всех сторон, возникает резонный вопрос: это лыжи не едут или я чего-то не понимаю. Соотвественно отладчик и ...)

    > таких как ZLIB, PNG, JPEG итд. Чего и Вам желаю.
    Спасибо, но надобности не возникало. По крайней мере в выше перечисленных. Наверное фронт работ несколько другой.
    Если быть точнее возникало, но компонентов которые позволяют работать с изображения(в частности) хватало с избытком, для моих скромных целей.

    > А прикладная математика оптимизируется ассемблером исключительно в редких случаях.
    А вот это очень зря. Довольно много алгоритмов и решений или криво реализовано, или не реализовано на Delphi совсем. Пример - Вейвлет пробразования. И таких примеров...

    Математика не упирается в игры. :)

    С Уважением.

    ОтветитьУдалить
  13. > Хотел бы я посмотреть, как работать с кривым кодом в DCU, без использования...

    У меня на это ответ один: не работать с кривым dcu-кодом. Желательно вообще работать только с кодом, который:
    а) поставляется в исходниках
    б) не кривой

    А вышеупомянутые TMS в народе славятся двумя вещами: нефункциональной красивостью и кривизной кода. =)

    Но в любом случае, статья интересная, спасибо.

    ОтветитьУдалить
  14. >> У меня на это ответ один: не работать с кривым >> dcu-кодом. Желательно вообще работать только с >> кодом, который:
    >> а) поставляется в исходниках

    Да! Безусловно! Всегда приятно, когда "всё на виду". Но вот не всегда так получается, к сожалению...

    >> б) не кривой
    Все мы люди, косячим и ошибаемся. Вообще как мне кажется, совершенного кода нет, но безусловно есть "более правильный", ну или... ну вы меня поняли мужики :) Просто не знаю как лучше выразиться.

    ОтветитьУдалить