Quantcast
Channel: Python extension for WinDbg
Viewing all articles
Browse latest Browse all 1625

Updated Wiki: PYKD 0.3. Руководство пользователя

$
0
0

Оглавление

1. Введение

1.1 Общие сведения

Проект pykd стартовал в 2010 году. Основным мотивом для разрботки было неудобство встроенных средств для написания отладочных скриптов для windbg. Язык python был выбран в качестве альтернативного скриптового двжика по многим причинам: легкость изучения самого языка, наличие большой стандартной библиотеки, наличие мощного и удобного фреймворка для создания модулей расширения. Pykd представляет собой модуль для интерпретатора CPython. Сам pykd написан на C++ и использует Boost.Python для экспорта функция и классов в Python. Pykd предоставляет доступ к управлению отладкой на платформе Windows через библиотеку Debug Engine и получению символьной информации через библиотеку MS DIA. Отметим, что pykd не дает прямого доступа к COM интерфейсам Debug Engine и MS DIA. Вместо этого он реализует собственный интерфейс, делающий процесс разработки более быстрым и удобным ( мы на это надеемся ).

Отметим, что pykd может работать в двух режимах: как плагин для windbg, в этом случае он предоставляет команды для запуска скриптов в контексте отладочной сесии; как отдельный модуль для интерпретатора python. Последний режим может быть полезен для создания автоматических средств разбора креш-дампов например.
Содержание

1.2 Быстрый старт

Содержание

1.3 Сборка из исходников

Содержание

1.4 Ручная установка

1.5 Изменения в API

2. Команды для windbg

2.1 Загрузка плагина

Для загрузки плагина в windbg необходимо выполнить команду:
kd>.load pykd_path/pykd.pyd
 

Если pykd.pyd находится в каталоге winext ( подкаталог в Debugging Tools for Windows ), то путь к pykd можно не указывать:
kd>.load pykd.pyd

Если pykd.pyd переименовать в pykd.dll, то расширение можно не указывать:
kd>.load pykd

Просмотреть загруженные расширения windbg можно с помощью команды .chain
kd>.chain

Выгрузить плагин:
kd>.unload pykd_path/pykd.pyd
kd>.unload pykd.pyd
kd>.unload pykd

Чтобы не загружать pykd каждый раз, можно после загрузки плагина выполнить команду "Save Workspace". После этого pykd будет загружаться автоматически для данного воркспейса.
Содержание

2.2 Запуск скрипта

Запуск скрипта осуществляется с помощью команды !py
kd>!py script_path/script_name.py  param1 param2 ... 

Расширение .py можно опустить. Чтобы не указывать полный путь к скрипту, нужно прописать его в переменную окружения PYTHONPATH или добавить ключ в раздел реестра
HKEY_LOCAL_MACHINE\SOFTWARE\Python\PythonCore\2.6\PythonPath
В этом случае пути к скриптам задаются в Default значении созданного ключа.

К параметрам в скрипте можно получить доступ через список sys.argv:
import sys
print "script path: " + sys.argv[0]
print "param1: " + sys.argv[1]
print "param2: " + sys.argv[2]

Содержание

2.3 режим консоли

Запуск консоли python осуществляется командой !py без параметров
1: kd> !py
Python 2.6.5 (r265:79096, Mar 19 2010, 18:02:59) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> 

Перед запуском будет автоматически выполнен импорт pykd, так что сразу можно вызывать функции pykd. Напомним, что выход из консольного режима осуществляется через функцию quit().
>>> quit(0)

2.4 локальный и глобальный режим

Запуск скриптов в может осуществляться в различных окружениях: в глобальном, при этом время жизни всех глобальных объектов совпадает со временем загрузки pykd в памяти windbg; и в локальном, при этом все глобальное пространство переменных будет очищено при окончании работы скрипта. По умолчанию, все скрипты выполняются в локальном окружении, а консоль запускается в глобальном. Можно задать явно режим работы с помощью параметров --global ( -g ) и --local ( -l ).
Параметры вызова Описание
!py script_name выполняется скрипт в локальном окружении
!py python консоль в глобальном окружении
!py -g script_name выполняется скрипт в глобальном окружении
!py -l python консоль в локальном окружении

Содержание

3. Управление отладкой

3.1 Остановка и возобновление процесса отладки

В windbg для этого служат команды Break ( Ctrl+Break ) и Go ( F5 ).
Их аналоги в pykd:
go()
breakin()

goвозобновляет процесс отладки и вернет управление только когда отладчик будет снова остановлен - сработает точка останова или отладка будет остановлена вручную через Ctrl+Break.
Это нужно учитывать при написании скриптов. Функция может вернуть исключение DbgException. Обычно это происходит если отлаживаемый процесс завершается.
try:
     while True:
         go()
         print "break"
 except:
     print "process terminted"

Данный скрипт будет обрабатывать любые остановки отладчика и автоматически возобновлять исполнение.

Использовать функцию breakinпри обычной работе врядли понадобится. Дело в том, что скрипты как правило выполняются только во время остановки отладчика. А в этот момент вызов функции breakinне имеет смысла. Для того, чтобы можно было останавливать процесс отладки из скрипта, придется создать отдельный поток в котором и вызывать эту функцию.
Внимание!Не пытайтесь использовать функции breakin, go, traceвнутри обработчиков отладочных событий ( например, в условных точках останова ).
Содержание

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

Для пошаговой отладки ( трассировки ) служат две функции:
step()
trace()
Из действие аналогично командам отладки "trace into" и "trace over". Обе функции могут вернуть исключение DbgException, если отлаживаемый процесс уже завершился.
Содержание

3.3 Управление отладкой из python приложений.

Если вы хотите исполнять свои скрипты вне windbg, то первым шагом, который вам необходимо сделать, будет создание отладочной сессии. Более подробно управление сессиями будет рассмотрено в соответствующем разделе. Если ваше приложение не планрует использовать несколько сессий отладки, то заботится об этом не надо - первая сессия будет создана автоматически при следующих вызовах:
loadDump( dumpName ) - загружает креш-дамп
id startProcess( imageName) - запускает в режиме отладки новый процесс
id attachProcess( processId) - присоединяет отладчик к существующему процессу
attachKernel( parameterStr) - присоединяет отладчик к ядру отлаживаемой системы

Для отсоединения отладчика от отлаживаемого процесса служит вызов detachProcess(id)

Для остановки отладки и удаления отлаживаемого процесса служит
функция killProcess(id)

для закрытия креш-дампа, открытого ранее через ф. loadDump, служит функция closeDump(id).

Узнать, режим отладчика можно с помощью вызовов:
  • bool isDumpAnalyzing()
  • bool isKernelDebugging()
Первая функция позволяет определить, находится ли отладчик в состоянии "живой" отладки или анализируется дамп памяти. Вторая функция позволяет различать отладку режима ядра или пользовательского режима. Если скрипт использует специфичные системные символы ( к примеру, символы ядра Windows ), то будет полезно вставить такую проверку в начале скрипта: это позволит сообщить пользователю, что он пытается запустить скрипт в неподходящей ситуации.

Содержание

3.4 Печать отладочной информации

Для вывода информации на экран можно воспользоваться стандартным оператором print. Но рекомендуется использовать специальные функции:
dprint( message, dml = False )
dprintln( message, dml = False )
Вторая функция отличается от первой тем, что автоматически добавляет символ перевода строки. Опциональный параметр dmlвключает вывод с DML разметкой ( работает только в windbg ). Разметка DML похожа на очень-очень упрощенный HTML. Форматирование текста осуществляется с помощью специальных тегов:
  • <b></b> - выделенный шрифт
  • <i></i> - курсив
  • <u></u> - шрифт с подчеркиванием
  • <link cmd = "cmdStr">command</link> - выполнение команды ( похоже на тег <a> в HTML )
dprinln("<b><u>This command reload all symbols</b></u>", True)
dprinln("<link cmd=\".reload /f\">reload</link>", True)

Содержание

3.5 Выполнение команд отладчика

Для выполнения команды отладчика служит функция:
str dbgCommand( commandStr, suppressOutput = True )
Это функция в зависимости выполняет команду windbg ( в том числе и вызов расширений, и за исключением команд специфичных для windbg, например, .cls). В зависимости от опционального параметра suppressOutputвывод будет перехвачен ( и возвращен как результат функции ) или обработан обычным порядком.
s = dbgCommand("!analyze -v")
dprint(s)


Для вычисления выражения ( аналог команды отладчика "?" ) служит функция
str expr( expressionStr )
expr("@rax + 10")

При выполнении python программы вам могут понадобиться команды из стандартных расширений windbg ( например, !analyze ). Их придется загрузить вручную. Для этого служит функция:
long loadExt( extensionPath )
Эта функция возвращает дескриптор расширения, который нужен для вызова функция расширения:
  • str callExt( extHandle, command, params )
И, если необходимо, для выгрузки расширения:
  • removeExt( extHandle )
Содержание

3.6 Создание креш-дампа

Сохранить состояние системы в виде креш-дампа можно с помощью ф. writeDump. Функция доступна для режима ядра и пользовательского режима. Второй параметр задает тип дампа ( False - полный дамп, True - минидамп )
writeDump( r"C:\dump\fulldump.dmp", False )
writeDump( r"C:\dump\minidump.dmp", True )

Содержание

4. Работа с памятью и регистрами

4.1 Доступ к регистрам общего назначения.

Доступ к регистрам общего назначения ( GPR ) осуществляется с помощью ф. reg:
  • reg( regName )
  • reg( regIndex )
Первый варинат принимает символьное имя регистра, второй - целочисленных индекс. Вторую форму можно использовать для перечисления регистров:
import pykd

try:
    i = 0
    while True:
        r = pykd.reg(i)
        pykd.dprintln( "%s    %x ( %d )" % ( r.name(), r, r )
        i += 1
except pykd.BaseException, IndexError
       pass

Если информация о регистре не может быть получена, будет возбужено исключение BaseException
Содержание

4.2 Доступ к модельно-специфичным регистрам ( MSR )

Доступ к модельно-специфичный регистрам осуществляется через ф. rdmsr( msrNumber ):
>>> print findSymbol( rdmsr( 0x176 ) )
nt!KiFastCallEntry

Содержание

4.3 Управление режимами процессора

Режим процессора определяется константами:
  • CPUType.I386
  • CPUType.AMD64
Другие типы процессоров в данный момент не поддерживаются.
Для определения типа процессора служит функция getCPUType. Данная функция не имеет никаого отношения к получиню информации о физическом процессоре. Например, если процессор имеет расширение команд AMD64 но работает под управлением 32 битной операционной системы, функция getCPUTypeвернет CPUType.I386. Таким образом, данную функцию можно рассматривать как аналог функции is64bitSystem.

При работе на 64 битной операционной системе можно столкнуться с отладкой WOW64 процессов. При этом процессор находясь в 64 битном режиме выполняет 32 битный код. Для определения данной ситуации моежт помочь функция getProcessorMode. Если процессор выполняет 32 битный код WOW64 процесса, эта функция вернет значение CPUType.I386 ( при этом getCPUTypeбудет возвращать "настоящий" режим процессора - CPUType.AMD64)
При отладке WOW64 процессов часто бывает нужно обращаться и к "32 битной части" процесса и к ""64 битной". Сменить режим виртуализации можно с помощью функции setCPUMode(mode). В некоторых случаях может быть полезна альтернативная функция switchCPUMode(). Последняя функция не требует параметров и работает по приницпу триггера. При пытке сменить режим процессора не в контексте WOW64 потока будет возбуждено исключение DbgException.
Содержание

4.4 Нормализация виртуальных адресов

Все функции pykdвозвращают виртуальные адреса в т.н нормализованном виде. Он представляет собой 64 битное целое число. Для 32 битных платформ адрес раширяется с учетом знака до 64 битного. Эта операция на С выглядит так:
ULONG64   addr64 = (ULONG64)(LONG)addr;

Таким образом адреса будет преобразовываться так:
0x00100000 -> 0x00000000 00100000
0x80100000 -> 0xFFFFFFFF 80100000
Это нужно учитывать, если адреса, которые возвращают функции pykd, участвуют в арифметических операциях. Для исключения возможных ошибок сравнения рекомендуется использовать ф. addr64():
import pykd
nt = pykd.module("nt")
if nt > addr64( 0x80000000 ):
    print "nt module is in highest address space"

Содержание

4.5 Прямой доступ к памяти

Для доступа к памяти отлаживаемой системы pykdпредставляет большой набор функций.
Для чтения целых беззанковых чисел служат следующие функции:
  • ptrByte( va )
  • ptrWord( va )
  • ptrDWord( va )
  • ptrQWord( va )
Для получения результата в виде целых чисео со знаком служат аналогичные функции:
  • ptrSignByte( va )
  • ptrSignWord( va )
  • ptrSignDWord( va )
  • ptrSignQWord( va )
Для удобства разработки кроссплатформенных скриптов служат функции:
  • ptrMWord(va)
  • ptrSignMWord(va)
  • ptrPtr(va)
Они возвращают результат в зависимости от битности платформы - 32 или 64 бита
Часто требуется прочесть блок памяти. Для этого служат функции:
  • loadBytes( va, count )
  • loadWords( va, count )
  • loadDWords( va, count )
  • loadQWords( va, count )
  • loadSignBytes( va, count )
  • loadSignWords( va, count )
  • loadSignDWords( va, count )
  • loadSignQWords( va, count )
  • loadPtrs( va, count )
Все функции возвращают объект list.
Содержание

4.6 Ошибки доступа к памяти

Все функции работы с памятью при невозможности прочесть данные по указанному адресу возвращают исключение MemoryException.
try:
    a = ptrByte( 0 )
except MemoryException:
    print "memory exception ocurred"

Проверить валидность виртуально адреса можно с помощью ф. isValid(va)
Содержание

4.7 Чтение строк из памяти

Часто приходится читать из памяти строковые данные. Конечно, для этого можно было бы использовать ф. loadBytes, но это не всегда удобно. Поэтому в pykdдобавлен набор функций, возвращающих данные в виде строки.
В первую очередь это:
  • loadChars( va, count )
  • loadWChars( va, count )
Они работают совершенно также как loadBytes и loadWords. Отличие только в возвращаемом значении ( stringвместо list ). Это позволяет использовать их, например, совместно с модулем struct:
from struct import unpack
shortField1, shortField2, longField = unpack('hhl', loadChars( addr, 8 ) )

Для чтения 0-терминированных строк из памяти служат функции:
  • loadСStr( va )
  • loadWStr( va )
Обе возвращают строки ( loadWStr - UNICODE ). Отметим небезопасность использования данных функций - ведь наличие терминирующего нуля никто не гарантирует! Внимание!Максимальная длина строки ограничена 64K. При попытке прочесть строку длинее, будет возвращено исключение MemoryException.

В ядре Windows для представления строк используются структуры UNICODE_STRING и ANSI_STRING. Для работы с ними существуют соответствующие функции:
  • loadAnsiString
  • loadUnicodeString
Содержание

5. Модули

5.1 Класс module

Модуль - это исполняемый файл, отображенный на память. Обычная программа состоит из главного модуля ( как правило, с расширением .exe ) и набора библиотек. Работа с модулями осуществляется с помощью класса module.

5.1.1 Создание экземпляра класса module:

Класс moduleимеет две формы конструктора:
  • module( moduleName )
  • module( va )
Первая форма создает модуль по его имени, второя - по виртуальному адресу, принадлежащему модулю. Если модуль не найден, конструктор возбудит исключение BaseException.
Пример:
from pykd import *
try
    ntdll = module( "ntdll" )
    print ntdll.name(), hex(ntdll.begin()), hex(ntdll.size()) 
except BaseException:
    print "module not found"

5.1.2 Получение информации о модуле

Для этого служат слежующие методы класса module:
  • name() - возвращает имя модуля
  • image() - возвращает имя исполняемого файла
  • pdb() - возвращает имя и полный путь к файлу с символьной информацией
  • begin() - возвращает виртуальный адрес, по которому загржен модуль
  • end() - возвращает виртуальный адрес конца модуля
  • checksum() - возвращает контрольную сумму
  • timestamp() - возвращает временную метку
  • getVersion() - возвращает кортеж, предсталяющий версию модуля. Например: ( 1, 0, 6452, 0 )
  • queryVersion( valueName ) - возвращает значение из ресурсов моудля

5.1.3 Загрузка и доступ к символам.

Для загрузки символьной информации служит метод reload()
Для поиска виртуального адреса, соответствующего нужному символу служит метод offset( symName ). Если указаный символ не найден, будет возбужедено исключение BaseException. Вместо явного вызова ф. offsetможно получить адрес, соответствующий символу, обратившись к нему как свойству класса module
>>> nt = module("nt")
>>> print hex( nt.offset("PsLoadedModuleList") )
0xfffff801acb5ae80L
>>> print hex( nt.__getattr__("PsLoadedModuleList") )
0xfffff801acb5ae80L
>>> print hex( nt.PsLoadedModuleList )
0xfffff801acb5ae80L

Иногда может понадобится RVA символа, получить его можно с помощью ф. rva( symbolName ).

5.1.4 Приведения к другим типам

Экземпляр класса moduleимеет операторы приведения к строке ( str ) и целому числу:
>>> nt = module("nt")
>>> print nt
Module: nt
Start: fffff801ac882000 End: fffff801acfc8000 Size: 746000
Image: ntkrnlmp.exe
Pdb: c:\sym\ntkrnlmp.pdb\569F266AE67D457D969D92298F8F98082\ntkrnlmp.pdb
Timestamp: 4f7118bb
Check Sum: 6b3b15

>>> print hex(nt)
fffff801ac882000

Кроме того, экземпляр класса moduleможет участвовать в арифметических операциях:
>>> print hex( nt + 10 )
0xfffff801ac88200aL

5.1.5 Получение информации о типе

Кроме символов, описывающих переменные и функции ( сущности, которые имеют RVA ), могут быть символы, описывающие типы. Для них, естественно, RVA не задан.
Если для модуля есть информация о типах, то ее можно получить через функцию type( typeName ). Эта функция возвращает экземпляр класса typeInfo, работа с которым будет рассмотрена позже.
>>> nt = module("nt")
>>> print nt.type("_MDL")
struct/class: _MDL Size: 0x1c (28)
   +0000 Next                : _MDL*
   +0004 Size                : Int2B
   +0006 MdlFlags            : Int2B
   +0008 Process             : _EPROCESS*
   +000c MappedSystemVa      : Void*
   +0010 StartVa             : Void*
   +0014 ByteCount           : ULong
   +0018 ByteOffset          : ULong

5.1.6 Типизированные переменные

pykdпозволяет упростить работу с сложными типами, такими как классы и структуры. За это отвечает специальный класс typedVar. Получить экземпляр класса typedVarможно через методы класса module:
  • typedVar( va )
  • typedVar( symbolName )
  • typedVar( typeName, va )
>>> nt = module("nt")
>>> print nt.typedVar( "_LIST_ENTRY", nt.PsLoadedModuleList )
struct/class: _LIST_ENTRY at 0xfffff8000369c650
   +0000 Flink               : _LIST_ENTRY*   0xfffffa8003c64890
   +0008 Blink               : _LIST_ENTRY*   0xfffffa80092f8f30

Содержание

5.2 Обработка событий загрузки и выгрузки модуля

Для обработки событий загрузки и выгрузки модуля надо создать наследника класса eventHandler.
Обработка события загрузки модуля осуществляется методом onLoadModule. Обработка события выгрузки модуля - onUnloadModule
Содержание

6. Получение символьной информации

6.1 Символьные ( pdb ) файлы

При сборке модуля создается файл с символьной ( отладочной ) информацией ( обычно, с раширением pdb ). В зависимости от настроек компилятора он может содержать полную или обрезанную информацию ( т.н "публичные символы" ). Символьные файлы могут содержать следующую информацию:
  • Имена, типы и относительные смещения глобальных переменных и констант
  • Имена, параметры и относительные смещения функций и методов классов
  • Имена типов, определенных пользователем ( структур, классов, перечислений )
  • Значения констант
  • Информацию о локальных переменных функций и методов классов.
Для работы с символьными файлами Microsoft предоставляет специальную библиотеку MS DIA. Pykdиспользует ее для работы с символами. Для непосредственного доступа к символьной инфорамции pykdреализует свой собственный интерфейс.
Содержание

6.2 Информация о типах

6.2.1 Класс представления типа

Для представления информации о типе в питон экспортируется класс typeInfo. Этим классом описываются структуры, классы, объединения, перечисления, битовые поля, указатели и базовые типы.
Класс представляет следующие методы:
  • name - получение имени типа
  • size - получение полного размера типа
  • staticOffset - получение смещения статического поля
  • fieldOffset - получение смещения поля
  • bitOffset - получение смещения битового поля
  • bitWidth - получение размера битового поля
  • field - получение поля
  • asMap - получение словаря для значения преречисления
  • deref - разыменование указателя
  • ptrTo - формирование указателя на тип
  • arrayOf - формирование массива, элементами которого является тип
  • append - добавление поля (метод для структур и перечислений, созданных с использованием typeBuilder)

Содержание

6.2.2 Получение объекта типа

Объект типа можно получить вызовом конструктора, передав в него имя типа. Передаваемая строка может содержать как полную спецификацию типа ("имямодуля!имятипа"), так и просто "имя_типа".
Объект типа можно получить косвеннымобразом:
  • метод typeу объекта типа module - формирование объекта типа по имени
  • метод typeу объекта типа typedVar - формирование объекта типа, который имеет переменная

Пример (печать структуры _UNICODE_STRING из ntdll):
>>> us = module("ntdll").type("_UNICODE_STRING")
>>> print us
class/struct : _UNICODE_STRING Size: 0x10 (16)
   +0000 Length                  : UInt2B
   +0002 MaximumLength           : UInt2B
   +0008 Buffer                  : UInt2B*

Для получения всех типов модуля можно использовать enumTypesу объекта типа module, который возвращает список имен типов, информация о которых представлена в отладочных символах модуля.

Содержание

6.2.3 Создание типов, не представленных в отладочных символах

Часто при отладке нужные типы данных отсутствуют в отладочных символах. Для удобства работы был написан отдельный класс по созданию собственных структур и объединений - typeBuilder.
Сложные типы данных, как правило, сводятся к набору базовых типов. Для получения базовых типов объект typeBuilderимеет следующие поля:
  • UInt1B
  • UInt2B
  • UInt4B
  • UInt8B
  • Int1B
  • Int2B
  • Int4B
  • Int8B
  • Long
  • ULong
  • Bool
  • Char
  • WChar
  • VoidPtr

Отдельно стоит сказать и поле VoidPtr - указатель на void. Размер этого типа зависит от платформы и текущего режима отладки. Но для удобства при создании объекта typeBuilderв конструктор можно передать желаемый размер указателя.
Имея базовые типы, а так же типы из отладочной информации модулей, можно строить произвольные типы. Для этого объект typeBuilderимеет два метода: createStructи createUnion. Оба метода первым параметром методы принимают строку имени типа. При создании структуры вторым необязательным параметром можно указать желаемое выравнивание.
Пример (создание и печать собственной структуры _UNICODE_STRING):
>>> tb = typeBuilder()
>>> us = tb.createStruct("_UNICODE_STRING")
>>> us.append("Length", tb.UInt2B)
>>> us.append("MaximumLength", tb.UInt2B)
>>> us.append("Buffer", tb.WChar.ptrTo())
>>> print us
class/struct : _UNICODE_STRING Size: 0x10 (16)
   +0000 Length                  : UInt2B
   +0002 MaximumLength           : UInt2B
   +0008 Buffer                  : WChar*


Содержание

7. Типизированные переменные

7.1 Класс typedVar

Ранее мы рассмотрели пример, как можно прочесть из памяти структурированные данные:
from struct import unpack
shortField1, shortField2, longField = unpack('hhl', loadChars( addr, 8 ) )

Очевидно, что работать с большими структрами, содержащими сотни полей, так не очень удобно. Поэтому в pykdреализован специальный класс: typedVar, позволяющий работать со сложными структурами данных. Информацию о типе данных typedVarполучает из символьной информации.
Содержание

7.2 Создание экземпляра класса typedVar

Существует несколько перегруженных конструкторов класса typedVar:
  • typedVar( symbolName )
  • typedVar( typeName, va )
  • typedVar( typeInfo, va )
t1 = typedVar( "MyModule!MyVar" )
t2 = typedVar( "MyModule!MyType", addr )
ti = typeInfo( "MyModule!MyType" )
t3 = typedVar( ti, addr )

Все три способа приведут к одинаковому результату, если addr - адрес переменной MyVar. Отметим, что все эти способы ( и в особенности первый ) не являются оптимальными в плане производительности, так как существенное время может уходить на поиск символьной информации. Если есть возможность, лучше воспользоваться методами класса module:
  • module.typedVar( va )
  • module.typedVar( symbolName )
  • module.typedVar( typeName, va )
mod = module("MyModule")
t4 = mod.typedVar( addr )
t5 = mod.typedVar( "MyVar" )
t6 = mod.typedVar( "MyType", addr )

Результат будет аналогичный прямому вызову конструктора. Однако экземпляр класса moduleоптимизирует доступ к символьной информации.

В случае ошибки в задании имени переменной или типа будет возбуждено исключение SymbolException.
try:
    typedVar( "MyModule!NotExistVar")
except SymbolException:
    print "The var does not exist"

Содержание

7.3 Методы класса typedVar

  • getAddress() - возвращает адрес переменной
  • sizeof() - возвращает размер переменной
  • offset() - если переменная является полем родительской структуры, то возвращает смещение относительно родителя.
  • field( fieldName ) - возвращает поле структуры как экземпляр класса typedVar
  • deref() - для указателей выполняет т.н. разыменование и возвращает результат в виде экземпляра класса typedVar
  • type() - возвращает тип переменной в виде экземпляра класса typeInfo
Содержание

7.4 Классы и структуры

Получить доступ к полям структуры можно с помощью метода field. Для удобства использования добавлен метод для доступ к полям как к аттрибутам класса:
>>>tv = typedVar( "structVar")
>>>tv.field("m_field) == tv.m_field
True

Кроме того, можно получить доступ к полям структуры по индексу:
tv = typedVar( "structVar")
for i in range(0,len(tv) )
    fieldName, fieldValue = tv[i]  
    print fieldName, fieldValue

Как видно из примера, в этом случае возвращается кортеж ( tuple ) из имени поля и его значения. Тот же пример можно записать короче:
tv = typedVar( "structVar")
for fieldName, fieldValue in tv:
    print fieldName, fieldValue


Переменные типа typedVarмогут участвовать в арифметических операцих. В качестве значения берется адрес переменной. Внимание:при арифметических операциях не действуютправила адресной аримфетики Си. Адрес будет трактоваться просто как число и , соответственно, var+1 просто инкрементирует знаечние адреса.

Содержание

Массивы и указатели

Класс typedVarпозволяет работать с массивами, в том числе многомерными. Для доступа к элементам массива нужно использовать оператор индекса []:
>>> tv = typedVar( "intMatrix" )
>>> print tv
Int4B[2][3] at 0x13f159150
>>> print tv[1]
Int4B[3] at 0x13f15915c
>>> print tv[1][2]
Int4B at 0x13f159164 Value: 0x5 (5)

Класс typedVarможет работать так же и с указателями. Для "разыменования" указателя служит функция deref():
>>> tv = typedVar("ptrIntMatrix")
>>> print tv
Ptr Int4B(*)[2][3] at 0x13f1591c0 Value: 0x13f159150
>>> print tv.deref()
Int4B[2][3] at 0x13f159150
>>> print tv.deref()[1][2]
Int4B at 0x13f159164 Value: 0x5 (5)


Переменные typedVarмогут участвовать в арифметический выражениях. Для массивов в качестве значения берется его адрес, для указателя - значение указателя ( т.е куда он указывает ). Внимание:при арифметических операциях не действуютправила адресной аримфетики Си.

Содержание

7.6 Энумераторы

Для работы с энумераторами будет полезно получить доуступ к информации о типе энумератора, так как именно через нее можно получить соответствие численных констант и символьных имен. Сделать это можно через метод type, который возвращает ссылку на переменную типа typeInfo:
var = typedVar( "myStruct" )
if  var.structType == var.structType.type().TYPE_ONE:
    print "TYPE_ONE"
else:
    print "ANOTHER_TYPE"

Класс typeInfoимеет метод asMap(), который для энумераторов возвращает объект типа dict, в котором ключами являются числовые константы, а значениями - их символьное представление:
var = typedVar( "myStruct" )
{
"TYPE_ONE" :  lambda var.field_one
"TYPE_TWO" : lambda var.field_two 
}[ var.type().asMap[ var.structType ] ]()

Данный пример во-первых демонстрирует как сделать на python логическую структуру, аналогичную оператору switch в Cи: для этого можно использовать тип dict, значения полей которого являются лямбда-выражениями. Во-вторых, он показывает как для энумератора получить соответствие численной константы и символьного имени.

Экземпляры typedVar, содержащие энумераторы, можно использовать в арифметических операциях:
>>>var = typedVar( "myStruct" )
>>>print var.structType *2 + 10

Содержание

7.7 Приведениe к другим типам

Класс typedVarимеет операторы приведения к строке (str) и к целому числу ( long ).
>>> print str( typedVar("g_struct") )
struct/class: struct3 at 0x13f4391f8
   +0000 m_arrayField            : Int4B[2]   
   +0008 m_noArrayField       : Int4B   0x3 (3)

Целочисленное значение, возвращаемое функций long(), зависит от типа данных, хранящизся в typedVar:
  • Базовые типы - возвращается непосредственное значение
  • Структуры, классы и объединения - возвращается значение указателя на начало данных
  • Энумераторы - возвращается непосредственное занчение
  • Указатели - возвращается непосредственное значение
  • Массивы - возвращается значение указателя на начало данных
>>> long( typedVar("g_struct").m_noArrayField )
3L
>>> hex( long( typedVar("g_struct").m_arrayField ) )
'0x13f4391f8L'
>>> 
>>> long( typedVar("g_struct").m_arrayField[1] )
2L

Содержание

8. Процессы и потоки

8.1 Потоки в пользовательском режиме

В пользовательском режиме отладчик работает в контексте отлаживаемого процесса. Если процесс имеет несколько потоков, в режиме отладки можно переключить контекст на другой поток. Нужно различать текущий поток и поток, на который переключился отладчик. В оригинале они называются: "current thread" - поток который продолжит выполнение после возобновления отладки, "implicit thread" - поток, в контексте которого находится отладчик. Контекстом потока мы называем совокупность регистров процессора, в том числе и указатель текущей инструкции и стека.
Для смены контекста потока служит функция setImplicitThread. В качестве параметра она принимает указатель на TEB (thread enviroment block). Получить указатель на TEB можно с помощью:
  • getImplicitThread - TEB текущего потока
  • getProcessThreads - список TEB-ов всех потоков процесса
Содержание

8.2 Потоки в режиме ядра

В режиме ядра есть некоторые особенности.
Во-первых, функции setImplicitThreadи getImplicitThreadработают с указателями на ETHREAD, а не с TEB, как в пользовательском режиме. Во-вторых, ф. getProcessThreadsдля режима ядра не доступна. Если нужно получить список потоков какого либо процесса придется делать это вручную разбирая структуру EPROCESS. К счастью, это не сложно:
nt = module("nt")
process = nt.typedVar( "_EPROCESS", processAddr )
threadLst = nt.typedVarList(process.ThreadListHead, "_ETHREAD", "ThreadListEntry")

Остается добавить, что переключение контекста отлаживаемого потока не приводит к переключению контекста процесса. Подробнее об этом в следующем разделе.
Содержание

8.3 Процессы в режиме ядра

Содержание

9. Локальные переменные

9.1 Текущие локальные переменные

При отладке приложения, ядра системы или анализе аварийного дампа, всегда присутствует текущий поток, а в этом потоке есть текущий фрейм. Если у нас есть отладочная информация о модуле, котрому принадлежит данный фрейм и в отладочной информации присутствует информация о локальных переменных, то мы можем получить доступ к ним в удобной форме, без явных операций с регистрами и стеком. Для этого служит ф. getLocals(). Она возвращает объект типа dict, ключом является имя переменной, а значением - экземпляр класса typedVar:
# print local variable "argc"
print getLocals()["argc"]

# print all local vairables in the current frame
for varName, varValue in  getLocals().items():
    print varName, varValue

Содержание

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

10.1 Задание точек останова

Для задания точки останова служит функция setBp. Она позволяет устанавливать как программные точки останова, так и аппаратные. Функция возвращает числовой идентификатор, который можно в последствии использовать для удаления точки останова через ф. removeBp.
Установка программной точки останова:
nt = module("nt")
bpid = setBp( nt.NtCreateFile )

Установка аппаратной точки останова:
nt = module("nt")
bpid = setBp( nt.NtCreateFile, 1, 4 )

Второй параметр - размер памяти, к которой осуществляется доступ, Третий параметр - тип доступа ( 1 - чтение, 2- запись, 4 - исполнение, типы доступа работают как флаги и могут быть объединены. Например, 3 - чтение + запись ).

Содержание

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

Для организации точек останова с условием используется функция обратного вызова, которая передается в качестве параметра в вызов setBp. Эта функция обязана принимать один параметр ( туда передается идентификатор сработавшей точки останова ). Чтобы точка останова сработала, функция обратного вызова должна вернуть True
import fnmatch
from pykd import *

nt = module('nt')
objAttrType = nt.type(  "_OBJECT_ATTRIBUTES" )

def onCreateFile( id ):
   objattr = typedVar( objAttrType, ptrPtr( reg('esp') + 0xC ) )
   return fnmatch.fnmatch(  loadUnicodeString( objattr.ObjectName ), '*.exe' )

setBp( nt.NtCreateFile, onCreateFile )

Нужно обратить внимание, что в качестве функции обратного вызова может выступать лямбда-функция:
setBp( myAddr, lambda id: reg('rax') > 0x1000 )

Помните о времени жизни объектов, создаваемых в скриптах! Функции - в python такие же объекты и при завершении виртуальной машины python они будут удалены. И тут возможна следующая ловушка: выполнив предыдущий скрипт с помощью команды "!py setmybreak.py" мы не получим ожидаемого срабатывания точки останова, она будет удалена во время завершения работы скрипта. Что же делать? Есть два варианта:
1. Использовать в скрипте управление отладкой, примерно так:
setBp( nt.NtCreateFile, onCreateFile )
go() 

В таком случае, мы поймаем ровно одно срабатывание точки останова, далее скрипт завершится.
2. Использовать для установки точки останова команду !pycmd.
Напомним данная команда создает глобальныйинтерпретатор python и все объекты python продолжают быть доступными даже после выполнения команды quit():
>!pycmd
>>>import  setmybreak
>>>quit()
>g

При импортировании модуля будут выполнены все действия по установке точке останова и даже после выхода из консоли функции обратного вызова на python будут работать!

Внимание!
Функции обратного вызова имеют некоторые ограничения на использование API pykd:
  • Нельзя вызывать функции, которые могут изменить состояние отладчика: go, breakin, trace
  • Нельзя вызывать функции, которые могут привести к появлению или уничтожению отладочных сессий: startProcess, killProcess, openDump и.т.д
  • Нельзя манипулировать контекстами потоков и процессов ( setCurrentProcess, setImplicitThread )

Содержание

11. Отладочные события

Содержание

11.1 Обработка точек останова (метод onBreakpoint)

Содержание

11.2 Обработка исключительных ситуаций (метод onException)

Содержание

11.3 Обработка события загрузки исполняемого модуля (метод onLoadModule)

Содержание

11.4 Отработка события выгрузки исполняемого модуля (метод onUnloadModule)

Содержание

12. Класс disasm

disasm reference
Класс disasmявляется оболочкой над дизассемблером из Debug Engine. Соответственно, результаты его работы такие же, как и у команды u в kd/cdb/windbg.
Класс disasmимеет следующие методы:
  • __init__() - создает дизассемблер, который начнет работу с текущей инструкции
  • __init__( offset ) - создает дизассемблер, который начнет работу с указанного смещения
  • disasm() - возвращает дизассемблированное представление инструкции CPU с текущего смещения и переходит к следующей инструкции
  • disasm( offest ) - возвращает дизассмблированное представление инструкции CPU с текущего смещения и переходит к следующей инструкции
  • asm( code ) - ассемблирует указанную инструкциб и меняет машинный код по указанному смещению
  • begin() - возвращает смещение, заданное при созданни экземпляра класса disasm
  • current() - возвращает текущее смещение
  • length() - возвращает длину текущей инструкции CPU
  • instruction() - возвращает дизассемблированное представление инструкции CPU по текущему смещению
  • ea() - возвращает эффективный адрес последней дизассемблированной инструкции или 0
  • reset() - аналог вызова self.disasm( self.begin() )

Эффективный адрес - это адрес операнда находящегося в памяти. Например, для инструкции
mov ecx, [esi+0x10]
Эффективным адресом будет занчение esi + 0x10. Очевидно, что это значение имеет смысл при дизассемблировании текущей инструкции.
Содержание

Viewing all articles
Browse latest Browse all 1625

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>