Введение
WinDbg – это отличный дебагер, но у него множество команд на которые нужно потратить время ради дальнейшей комфортной работы. Я буду очень краток и лаконичен, по этому, вы не должны умереть со скуки. Я покажу вам только необходимый набор команд и наиболее важные опции. Мы ознакомимся с дополнительными командами и опциями в следующих главах, когда они нам будут нужны.
Версия
Во избежание проблем используйте 32-х разрядную версию WinDbg для отладки 32-х битных выполняемых файлов. Для отладки 64-х битных исполняемых файлов соответственно используйте 64-х битную версию WinDbg.
Альтернативный вариант: Вы можете переключать WinDbg между 32-х и 64-х битными режимами следующей командой:
!wow64exts.sw
Символы
Откройте новый экземпляр WinDbg (если Вы отлаживаете процесс с помощью WinDbg, закройте WinDbg и откройте снова).
В окне File→Symbol File Path введите:
SRV*C:\windbgsymbols*http://msdl.microsoft.com/download/symbols
Сохраните рабочее окружение (File→Save Workspace).
Звездочки это разделители. WinDbg будет использовать первую директорию которую мы указали выше в качестве локального кэша для символов. Путь после второй звёздочки (разделен «;» если более одного) указывает местоположение где могут быть найдены символы.
Добавление символов на протяжении отладки
Для добавления пути поиска символов к стандартному на протяжении отладки используйте:
.sympath+ c:\symbolpath
(Команда без «+» заменит стандартный путь поиска символов а не добавит ещё один.)
Теперь перезагрузите символы:
.reload
Проверка символов
Символы, если они доступны, загружаются по мере надобности. Что бы посмотреть какие модули имеют загруженные символы, используйте следующую команду:
x *!
Команда «х» поддерживает шаблоны (подстановочные знаки) и может быть использована для поиска символов в одном или более модулях. Для примера, мы можем найти все символы в kernel32 имя которых начинается с «virtual» следующим путем:
0:000> x kernel32!virtual*
757d4b5f kernel32!VirtualQueryExStub (<no parameter info>)
7576d950 kernel32!VirtualAllocExStub (<no parameter info>)
757f66f1 kernel32!VirtualAllocExNuma (<no parameter info>)
757d4b4f kernel32!VirtualProtectExStub (<no parameter info>)
757542ff kernel32!VirtualProtectStub (<no parameter info>)
7576d975 kernel32!VirtualFreeEx (<no parameter info>)
7575184b kernel32!VirtualFree (<no parameter info>)
75751833 kernel32!VirtualAlloc (<no parameter info>)
757543ef kernel32!VirtualQuery (<no parameter info>)
757510c8 kernel32!VirtualProtect (<no parameter info>)
757ff14d kernel32!VirtualProtectEx (<no parameter info>)
7575183e kernel32!VirtualFreeStub (<no parameter info>)
75751826 kernel32!VirtualAllocStub (<no parameter info>)
7576d968 kernel32!VirtualFreeExStub (<no parameter info>)
757543fa kernel32!VirtualQueryStub (<no parameter info>)
7576eee1 kernel32!VirtualUnlock (<no parameter info>)
7576ebdb kernel32!VirtualLock (<no parameter info>)
7576d95d kernel32!VirtualAllocEx (<no parameter info>)
757d4b3f kernel32!VirtualAllocExNumaStub (<no parameter info>)
757ff158 kernel32!VirtualQueryEx (<no parameter info>)
Символы подстановки также могут быть использованы в модульной части:
0:000> x *!messagebox*
7539fbd1 USER32!MessageBoxIndirectA (<no parameter info>)
7539fcfa USER32!MessageBoxExW (<no parameter info>)
7539f7af USER32!MessageBoxWorker (<no parameter info>)
7539fcd6 USER32!MessageBoxExA (<no parameter info>)
7539fc9d USER32!MessageBoxIndirectW (<no parameter info>)
7539fd1e USER32!MessageBoxA (<no parameter info>)
7539fd3f USER32!MessageBoxW (<no parameter info>)
7539fb28 USER32!MessageBoxTimeoutA (<no parameter info>)
7539facd USER32!MessageBoxTimeoutW (<no parameter info>)
Вы можете заставить WinDbg загрузить символы для всех модулей:
ld*
Выполнение данной команды займет некоторое время. Выполните Debug→Break для остановки выполнения операции.
Помощь
Просто введите:
.hh
или нажмите F1 для открытия окна помощи.
Для получения помощи по определенной команды наберите следующее:
.hh <команда>
Где <команда> это интересующая Вас команда, или нажмите F1 и выберите раздел, в котором вы можете найти интересующую вас команду или тему.
Режимы отладки
Локально
Вы можете отлаживать новый процесс, или процесс, который уже запущен:
- Запустить новый процесс для отладки выполнив File→Open Executable.
- Присоединиться к уже запущенному процессу выполнив File→Attach to a Process.
Удаленно
Для отладки программы удаленно есть по крайней мере два варианта:
- Если Вы в данный момент отлаживаете программу локально на машине А, Вы можете ввести следующую команду (выберите порт (port) который вам надо):
.server tcp:port=1234
Это запустит сервер в WinDbg.
На машине В запустите WinDbg и выполните File→Connect to Remote Session и введите:
tcp:Port=1234,Server=<IP машины А>
Укажите правильный порт и IP.
На машине А запустите dbgsrv так, как показано ниже:
dbgsrv.exe -t tcp:port=1234
Это запустит сервер на машине А.
- На машине В запустите WinDbg и выполните File→Connect to Remote Stub, после чего введите:
tcp:Port=1234,Server=< IP машины А >
с соответствующими параметрами.
Вы увидите что File→Open Executable не активна, но Вы можете выбрать File→Attach to a Process. В данном случае Вы увидите список процессов на машине А. Что бы остановить сервер на машине А можно использовать диспетчер задач и убить процесс dbgsrv.exe.
Модули
Когда вы загружаете исполняемый файл или присоединяетесь к процессу, WinDbg выведет список загруженных модулей. Если вы хотите увидеть список модулей еще раз, введите:
lmf
Что бы перечислить конкретные модули, скажем ntdll.dll, используйте:
lmf m ntdll
Получить информация образа заголовка (image header) модуля, скажем, ntdll.dll, можно набрав:
!dh ntdll
Знак «!» означает что команда это расширение, к примеру, внешняя команда, которая экспортируется из внешней DLL и вызывается внутри WinDbg. Пользователи могут создавать собственные расширения с целью увеличения функциональности WinDbg.
Вы можете так же использовать начальный адрес модуля:
0:000> lmf m ntdll
start end module name
77790000 77910000 ntdll ntdll.dll
0:000> !dh 77790000
Выражения
WinDbg поддерживает выражения. Выражение полезны тогда, когда требуется значение, и Вы можете ввести значение напрямую или ввести выражение которые в дальнейшем сведется к тому самому значению.
К примеру, если EIP равен 77c6cb70, тогда:
bp 77c6cb71
и
bp EIP+1
эквивалентны.
Вы также можете использовать символы:
u ntdll!CsrSetPriorityClass+0x41
и регистры:
dd ebp+4
Числа по стандарту в шестнадцатеричной системе исчисления. Что бы явно указать какую базу использовать (2, 8, 10, 16) используйте префикс:
0x123: base 16 (hexadecimal)
0n123: base 10 (decimal)
0t123: base 8 (octal)
0y111: base 2 (binary)
Для отображения значения в разных форматах используйте команду .format:
0:000> .formats 123
Evaluate expression:
Hex: 00000000`00000123
Decimal: 291
Octal: 0000000000000000000443
Binary: 00000000 00000000 00000000 00000000 00000000 00000000 00000001 00100011
Chars: .......#
Time: Thu Jan 01 01:04:51 1970
Float: low 4.07778e-043 high 0
Double: 1.43773e-321
Для вычисления выражения используйте «?»:
? eax+4
Регистры и псевдорегистры
WinDbg поддерживает несколько псевдорегистров которые содержат определенные значения. Псевдорегистры обозначаются префиксом «$». Когда используются регистры или псевдорегистры, одни могут добавлять префикс «@» который говорит WinDbg о том, что следует после значений регистр а не символ. Если «@» не используется WinDbg сперва попытается интерпретировать имя как символ.
Вот несколько примеров псевдорегистров:
- $teb или @$teb (адрес TEB)
- $peb или @$peb (адрес PEB)
- $thread или @$thread (текущий поток)
Исключения
Что бы остановиться на определенном исключении, используйте команду sxe. К примеру, чтобы остановиться когда загружен модуль, наберите:
sxe ld <имя модуля 1>,...,<имя модуля N>
Пример:
sxe ld user32
Увидеть список исключений:
sx
Игнорировать исключение:
sxi ld
Это отменит эффект от первой нашей команды.
WinDbg прерывается на первичные и вторичные исключения (single-chance exceptions and second-chance exceptions пр. переводчика). Это не разные виды исключений. Как только появляется исключение, WinDbg останавливает выполнение и сообщает что появилось первичное исключение. Первичное исключение – это исключение, которое еще не было отослано отлаживаемой программе но уже возбудилось. Когда мы продолжим выполнение WinDbg пошлет исключение в отлаживаемую программу. Если отлаживаемая программа не обработает исключение, WinDbg снова останавливает выполнение и сообщает о вторичном исключении.
Когда будем исследовать EMET 5.2, нам надо будет игнорировать первичные исключение на первых шагах программы. Что бы сделать это мы можем использовать следующую команду:
sxd sse
Точки останова
Программные точки останова
Когда вы ставите точку останова на инструкцию, WinDbg сохраняет в память первый байт инструкции и перезаписывает остальную часть байтом 0xCC который является опкодом для “int 3“.
Когда “int 3“ выполнен, срабатывает точка останова, выполнение останавливается и WinDbg восстанавливает инструкцию методом восстановления её первого байта.
Для установки точки останова на инструкцию по адресу 0x4110a0 введите:
bp 4110a0
Вы можете также указать количество пропусков для активации точки останова:
bp 4110a0 3
Это означает, что точка останова будет игнорироваться первые 2 раза при попадании на инструкцию. Что бы продолжить выполнение (и остановиться на первой встречной точке останова) наберите:
g
что является сокращение для “go“.
Выполнять программу до определенного адреса (содержащего код), введите:
g <размещение кода>
Внутренний механизм работы данной команды таков: WinDbg установит программную точку останова, но после её срабатывания он её удалит. То есть, такая точка останова одноразовая.
Аппаратная точка останова
Аппаратные точки останова используют конкретные регистры процессора и являются более универсальными нежели программные точки останова. По сути, одни могут срабатывать на выполнении или на доступе к памяти. Аппаратные точки останова не изменяют код, так что они могут быть использованы вместе с модифицирующимся кодом. К несчастью, Вы не можете установить более 4-х таких точек останова.
В простой форме, формат команды следующий:
ba <режим> <размер> <адрес> <пропуски (по стандарту=1)>
где <режим> может быть
- ‘e‘ для выполнения
- ‘r‘ для установки доступа к памяти на чтение/запись
- ‘w‘ для установки доступа к памяти на запись
<размер> указывает размер местоположения в байтах для мониторинга доступа (всегда 1 если <режим> ‘e‘). <адрес> это местоположение установки точки останова и <пропуски> это количество нужных пропусков для активации точки останова (смотрите ‘bp‘ в качестве примера использования).
К сведению: Невозможно использовать аппаратные точки останова для процесса до начала его запуска из-за того, что устанавливаются изменением регистров процессора (dr0, dr1, и т.д.) и когда процесс запускается, его поток создается и сбрасывает регистры.
Обработка точек останова
Вывести список точек останова можно так:
bl
где ‘bl‘ означает «список точек останова».
Пример:
0:000> bl
0 e 77c6cb70 0002 (0002) 0:**** ntdll!CsrSetPriorityClass+0x40
Где поля слева на право значат следующее:
- 0: идентификатор точки останова (ID)
- e: статус точки останова; может быть включена (e) или выключена (d)
- 77c6cb70: адрес памяти
- 0002 (0002): количество пропусков оставшееся до активации
- 0:****: связанные процесс или поток. Звездочки указывают на то, что точка останова не потоко-зависимая.
- ntdll!CsrSetPriorityClass+0x40: модуль, функция и смещение где точка останова размещена
Отключить точку останова:
bd <идентификатор точки останова>
Удалить точку останова:
bd < идентификатор точки останова >
Удалить все точки останова:
bc *
Команды точек останова
Если вы хотите выполнять некоторую команду автоматически каждый раз когда срабатывает точка останова, Вы можете указать команду следующим образом:
bp 40a410 ".echo \"Тут регистры:\n\"; r"
Вот другой пример:
bp jscript9+c2c47 ".printf \"new Array Data: addr = 0x%p\\n\",eax;g"
Пошаговое выполнение
Есть по крайней мере три типа пошагового выполнение программы:
-
step-in / trace (команда: t)
Данная команда делает прерывание после каждой инструкции. Если вы на call или int, команда прервется на первой инструкции вызываемой функции или на обработчике прерывания int соответственно. -
step-over (команда: p)
Данная команда делает прерывание после каждой инструкции без захода в функции по call или в обработчик прерывания int. К примеру, если вы на инструкции call или int, то команда прервется на инструкции сразу после call или int. -
step-out (команда: gu)
Эта команда (go up) продолжает выполнение и прерывается сразу после следующей ret инструкции.
Две другие команды для выхода из функций:- tt (trace to next return): эквивалентно использованию команды ‘t‘ неоднократно и остановки на первой встречной инструкции ret.
- pt (step to next return): эквивалентно использованию команды ‘p‘неоднократно и остановки на первой встречной инструкции ret.
Заметьте, tt также заходит внутрь функций, если Вы хотите получить ret инструкцию текущей функции, используйте pt.
Разница между pt и gu в том, что pt прерывается на ret инструкцию, в то время как gu прерывается на инструкцию после ret.
Ниже перечислены варианты ‘p‘ и ‘t‘:
* [I][U]pa/ta[/U][/I] <address>: step/trace к адресу
* [I][U]pc/tc[/U][/I]: step/trace к следующей инструкции call/int
* [I][U]pt/tt[/U][/I]: step/trace к следующей инструкции ret (рассказано в 3 пункте)
* [I][U]pct/tct[/U][/I]: step/trace к следующей call/int ret инструкцие
* [I][U]ph/th[/U][/I]: step/trace к следующей условной инструкции
Отображение памяти
Отобразить содержимое памяти Вы можете используя ‘d‘ или один из её вариантов:
- db: отобразить байты
- dw: отобразить слова(2 байта)
- dd: отобразить двойные слова(4 байта)
- dq: отобразить учетверенные слова(8 байт)
- dyb: отобразить биты
- da: отобразить нуль-терминальные ASCII строки
- du: отобразить нуль-терминальные Unicode строки
Напечатайте .hh d что бы увидеть другие разновидности команды.
Команда ‘d‘ отображает данные в том же формате что и последние d* команды.
Упрощенный формат этих команд таков:
d* [диапазон]
Здесь звездочка используется для представления всех вариаций который мы перечислили выше. Скобки обозначают диапазон который не обязателен. Если диапазон отсутствует, d* отобразит участок памяти сразу после участка, который был отображен предыдущей командой d*.
Диапазоны можно указывать разными способами:
- <начальный адрес> <конечный адрес>
К примеру:
db 77cac000 77cac0ff
- <начальный адрес> L<количество элементов>
К примеру:
dd 77cac000 L10
Отобразит десять двойных слова (DWORD) начиная с указанного адреса (77cac000).
- <начальный адрес>
Когда указана только начальный адрес, WinDbg отобразит следующих 128 байт.
Редактирование памяти
Вы можете редактировать память используя:
e[d|w|b] <адрес> [<новое значение 1> ... <новое значение N>]
где e[d|w|b] необязательные параметры и задают размер элементов для редактирования (d = dword, w = word, b = byte). Если новые значения опущены, WinDbg запросит у вас их интерактивный ввод.
Пример:
ed eip cc cc
Данная команда перепишет первые два dword по адресу в eip значением 0xCC.
Поиск в памяти
Для поиска по памяти используйте команду ‘s‘. Её формат таков:
s [-d|-w|-b|-a|-u] <начальный адрес> L?<количество элементов > <искомые значения>
где d, w, b, a и u означают dword, word, byte, ascii и unicode.
<искомые значения> - это последовательность значений для поиска.
Пример:
s -d eip L?1000 cc cc
Ищет две последовательности двойных слов 0xcc 0xcc в памяти в диапазоне [eip, eip + 1000*4 – 1].
Указатели
Вам может понадобиться разыменовать указатель. Что бы сделать это, надо использовать оператор poi:
dd poi(ebp+4)
В данной команде poi(ebp+4) приводиться к dword (или qword в 64-х битном режиме) по адресу ebp+4.
Другие полезные команды
Отобразить регистры:
r
Отобразить конкретные регистры, скажем eax и edx:
r eax, edx
Вывести первые 3 инструкции на которые указывает EIP:
u EIP L3
где ‘u‘ это сокращение от unassemble (дизассемблировать) и ‘L‘ позволяет вам указать количество строк для отображения.
Вывести стек вызовов:
k
Дамп структур
Здесь находятся команды для отображения структур:
Предполагаемая настройка
Сохраните рабочее окружение (File→Save Workspace) после настройки всех окон.