Знакомство с WinDBG – Часть 2

Знакомство с WinDBG – Часть 2

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

Автор: Брэд Антониевич (Brad Antoniewicz)

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

Перечень всех статей, входящих в этот цикл:

  • Часть 1 – установка, интерфейс, символы, удаленная/локальная отладка, система помощи, модули, регистры.
  • Часть 2 – точки останова.
  • Часть 3 – инспектирование памяти, пошаговая отладка программ, советы и трюки.

Точки останова

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

Точки останова можно выставить как внутри приложения, так и внутри процессора (аппаратно). Рассмотрим оба этих способа:

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

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

В WinDBG программные точки останова устанавливаются при помощи команд bp, bm, или bu. Команда bp (Break Point), вероятно, наиболее часто используемая команда, при помощи которой устанавливаются точки останова. В самом простейшем случае достаточно указать один адрес, по которому должна быть установлена точка останова:

0:001> bp 00e61018

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

Давайте рассмотрим, как устанавливать программные точки останова на примере приложения notepad.exe, которое мы будем запускать через WinDBG. По умолчанию, при запуске программы через WinDBG, отладчик устанавливает точку останова в самом начале программы. Для начала найдем адрес памяти, куда загрузился notepad.exe:

Рисунок 1: Нахождение адреса загрузки notepad.exe

Далее, используя адрес загрузки и команду !dh, находим точку входа программы:

Рисунок 2: Нахождение точки входа программы

Теперь устанавливаем точку останова в точку входа (адрес загрузки + 0x3689):

Рисунок 3: Установление точки останова

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

Рисунок 4: Сообщение, появляющееся по достижении точки останова

В большинстве случаев вы будете использовать программные точки останова, однако иногда вам понадобятся аппаратные точки останова (например, когда у памяти установлен атрибут «только чтение» или когда информация защищена и т. д.).

Аппаратные точки останова

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

В WinDBG аппаратные точки останова устанавливаются при помощи команды ba (Break on Access). Обычно в эту команду передается три аргумента:

0:001> ba e 1 00453689

При помощи этой команды, вероятно, можно было добиться того же самого, что и в примере с командой bp, но только не в случае с аппаратными точками останова (вскоре мы узнаем почему). Первый аргумент e – тип доступа к памяти (на выполнение), второй аргумент – размер (всегда 1 для этого типа доступа). Последний аргумент – адрес. Давайте рассмотрим установку аппаратной точки останова, учитывая то, что наши адреса загрузки различаются из-за ASLR.

Из-за способа, при помощи которого Windows сбрасывает контексты потока, и места, где появляется WinDBG после порождения процесса, мы не сможем установить точку останова тем же самым способом, как делали это в предыдущем примере. Ранее мы устанавливали точку останова во входной точке приложения, но если сделать так же и здесь, то возникнет ошибка:

0:000> lmf m notepad
start end module name
00e60000 00e90000 notepad notepad.exe
0:000> ba e 1 00e63689
^ Unable to set breakpoint error
The system resets thread contexts after the process
breakpoint so hardware breakpoints cannot be set.
Go to the executable's entry point and set it then.
'ba e 1 00e63689'

Для решения этой проблемы нам нужно использовать команду g, чтобы программа добралась до определенного адреса памяти. Когда мы устанавливали программную точку останова, то делали примерно то же самое, но не совсем. Таким образом, мы заставляем WinDBG дойти до начального контекста потока, что впоследствии позволит нам устанавливать аппаратные точки останова.

0:000> g 00e63689
ModLoad: 76be0000 76bff000 C:\Windows\system32\IMM32.DLL
ModLoad: 76c00000 76ccc000 C:\Windows\system32\MSCTF.dll
eax=77081162 ebx=7ffd7000 ecx=00000000 edx=00e63689 esi=00000000 edi=00000000
eip=00e63689 esp=0022fbb4 ebp=0022fbbc iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
notepad!WinMainCRTStartup:
00e63689 e8c5f9ffff call notepad!__security_init_cookie (00e63053)

Теперь мы можем установить аппаратную точку останова:

Рисунок 5: Установка корректной аппаратной точки останова

Чтобы убедиться в том, что точка останова хранится в регистре процессора, можно использовать команду r (подробнее о ней мы поговорим позже). Атрибут M нужен для использования маски регистра 0x20:

0:000> rM 20

Рисунок 6: Просмотр отладочных регистров

Из Рисунка 6 видно, что что-то мы сделали не так, и во всех регистрах находятся нули! Такое произошло потому, что WinDBG еще ничего не занес ни в один из этих регистров. После того, как вы продвинетесь на один шаг вперед, используя команду p (подробнее об этом поговорим чуть позже), то в регистр dr0 установится адрес нашей точки останова:

Рисунок 7: При продвижении вперед на один шаг, в отладочный регистр устанавливается адрес аппаратной точки останова

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

Команды для работы с точками останова

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

Просмотр точек останова

Для просмотра всех точек установленных точек останова используется команда bl (Breakpoint List).

0:000> bl
0 e 00523689 e 1 0001 (0001) 0:**** notepad!WinMainCRTStartup

Результат выполнения команды разделен на несколько колонок. Рассмотрим назначение каждой из них:

  • 0 – Идентификатор точки останова
  • e – Статус точки останова - активирована (enabled) или деактивирована (disabled).
  • 00523689 – Адрес памяти
  • e 1 – Флаг доступа к адресу памяти (execute) и размер – только для аппаратных точек останова
  • 0001 (0001) – сколько раз должна сработать точка останова перед тем, как она станет активной. В скобках указано общее количество проходов (для особого варианта использования)
  • 0:**** - Информация о потоке и процессе. Данное обозначение показывает, что точка останова не специфична для потока
  • notepad!WinMainCRTStartup – соответствующий модуль и смещение функции, связанное с адресом памяти.

Удаление точек останова

Чтобы удалить точку останова, используйте команду bc:

0:000> bc 0

В команду передается только один параметр – идентификатор точки останова (можно узнать, выполнив команду bl). По желанию можно указать символ *, чтобы удалить все точки останова.

Некоторые трюки при работе с точками останова

Существует несколько трюков, которыми я обычно пользуюсь при работе с точками останова. Ниже приводятся некоторые из них. Если вы также пользуетесь какими-либо трюками, расскажите о них в комментариях!

Вычисление адресов

Самый простейший трюк при работе с точками останова – вычисление адреса памяти. Вместо того чтобы производить эти вычисления в голове, вы можете сделать это прямо в WinDBG. К примеру, в вышеупомянутых примерах мы знаем адрес загрузки notepad.exe и смещение входной точки программы (0x3689). Теперь мы можем вычислить адрес для установки точки останова:

0:000> lmf m notepad
start end module name
00770000 007a0000 notepad notepad.exe
0:000> bp 00770000 + 3689
0:000> bl
0 e 00773689 0001 (0001) 0:**** notepad!WinMainCRTStartup

Использование имен и смещений в качестве адресов

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

0:000> u 00770000 + 3689
notepad!WinMainCRTStartup:
00773689 e8c5f9ffff call notepad!__security_init_cookie (00773053)
0077368e 6a58 push 58h

Теперь мы знаем, что имя notepad!WinMainCRTStartup более красивый аналог адресу 00770000 + 3689. Поскольку в конце этого имени не стоит числового смещения, мы можем сделать вывод, что для этой функции существуют символы. Давайте проверим инструкцию, которая следует сразу за первой функцией:

0:000> u 0077368e
notepad!_initterm_e+0x61:
0077368e 6a58 push 58h

В этот раз мы получили имя функции notepad!_initterm_e плюс смещение (+0x61). Я точно не знаю, почему WinDBG добавляет смещение к notepad!_initterm_e вместо notepad!WinMainCRTStartup. Вероятно, влияет порядок сортировки во время поиска символов. Тем не менее, мы можем добавить смещение к notepad!WinMainCRTStartup, чтобы сослаться на ту же самую область памяти:

0:000> u notepad!WinMainCRTStartup+0x5
notepad!_initterm_e+0x61:
0077368e 6a58 push 58h

Вся фишка в том, что мы можем использовать это смещение во время установки точки останова, и эти смещения всегда корректны, даже когда работает ASLR. То есть нам не нужно высчитывать адреса при каждом запуске.

0:000> bp notepad!WinMainCRTStartup+0x5
0:000> bl
0 e 0077368e 0001 (0001) 0:**** notepad!_initterm_e+0x61

Прерывание загрузки модуля

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

0:000> sxe ld IMM32.DLL

Выполняя эту команду, мы установили first chance исключение (sxe) во время загрузки модуля (ld) и определили конкретный модуль (IMM32.DLL), в котором возникает это исключение.

Мы можем увидеть все установленные исключения, используя команду sx (Set Exceptions). Если посмотреть на список под именем Load Module, то можно увидеть прерывание на модуле IMM32.DLL.

Рисунок 8: Прерывание на модуле IMM32.DLL

Убрать исключение можно при помощи команды sxi (Set Exception Ignore):

0:000> sxi ld IMM32.DLL

Выполнение команд при срабатывании точки останова

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

0:000> bp notepad!WinMainCRTStartup ".echo \"Here are the values on the stack:\n\"; dd esp;"
0:000> bl
0 e 00ae3689 0001 (0001) 0:**** notepad!WinMainCRTStartup ".echo \"Here are the values on the stack:\n\"; dd esp;"

Теперь давайте посмотрим, что произойдет при срабатывании точки останова:

Рисунок 9: При срабатывании точки останова отображается информация из стека

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

Оставайтесь на связи

В следующей статье мы рассмотрим инспектирование памяти и пошаговую отладку программы! Не переключайтесь!

Домашний Wi-Fi – ваша крепость или картонный домик?

Узнайте, как построить неприступную стену