R0 CREW

Exploit Development Course Part 1: WinDbg (Перевод: klaus)

ru
#1

Перейти к содержанию

Введение

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 и выберите раздел, в котором вы можете найти интересующую вас команду или тему.

Режимы отладки

Локально

Вы можете отлаживать новый процесс, или процесс, который уже запущен:

  1. Запустить новый процесс для отладки выполнив File→Open Executable.
  2. Присоединиться к уже запущенному процессу выполнив File→Attach to a Process.

Удаленно

Для отладки программы удаленно есть по крайней мере два варианта:

  1. Если Вы в данный момент отлаживаете программу локально на машине А, Вы можете ввести следующую команду (выберите порт (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

Это запустит сервер на машине А.

  1. На машине В запустите 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)>

где <режим> может быть

  1. ‘e‘ для выполнения
  2. ‘r‘ для установки доступа к памяти на чтение/запись
  3. ‘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"

Пошаговое выполнение

Есть по крайней мере три типа пошагового выполнение программы:

  1. step-in / trace (команда: t)
    Данная команда делает прерывание после каждой инструкции. Если вы на call или int, команда прервется на первой инструкции вызываемой функции или на обработчике прерывания int соответственно.

  2. step-over (команда: p)
    Данная команда делает прерывание после каждой инструкции без захода в функции по call или в обработчик прерывания int. К примеру, если вы на инструкции call или int, то команда прервется на инструкции сразу после call или int.

  3. 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*.

Диапазоны можно указывать разными способами:

  1. <начальный адрес> <конечный адрес>
    К примеру:
db 77cac000 77cac0ff
  1. <начальный адрес> L<количество элементов>
    К примеру:
dd 77cac000 L10

Отобразит десять двойных слова (DWORD) начиная с указанного адреса (77cac000).

  1. <начальный адрес>
    Когда указана только начальный адрес, 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) после настройки всех окон.

© Translated by klaus (r0 Crew)
#2

Спасибо, давно мы не брались за переводы =)