diff --git a/docs/ru/documentation.txt b/docs/ru/documentation.txt new file mode 100644 index 0000000..44b5aad --- /dev/null +++ b/docs/ru/documentation.txt @@ -0,0 +1,898 @@ +{anchor:table} +! Оглавление +* [1. Введение|#intro] +** [1.1 Общие сведения|#commoninfo] +** [1.2 Быстрый старт|#gettingStarted] +** [1.3 Сборка из исходников|#Building] +** [1.4 Ручная установка|#Installing] +** [1.5 Изменения в API|#APIchange] +* [2. Команды для windbg|#windbg] +** [2.1 Загрузка плагина|#loadplugin] +** [2.2 Запуск скрипта|#runscript] +** [ 2.3 режим консоли|#console] +* [3. Управление отладкой|#debugging] +** [3.1 Остановка и возобновление процесса отладки|#break] +** [3.2 Пошаговое выполнение|#step] +** [3.3 Управление отладкой из python приложений|#consoledebug] +** [3.4 Печать отладочной информации|#dbgprint] +** [3.5 Выполнение команд отладчика|#dbgcommand] +** [3.6 Создание креш-дампа|#createcrash] +* [4. Работа с памятью и регистрами|#memory] +** [4.1 Доступ к регистрам общего назначения|#reg] +** [4.2 Доступ к модельно специфичным регистрам|#msr] +** [4.3 Нормализация виртуальных адресов|#addr64] +** [4.4 Прямой доступ к памяти|#memaccess] +** [4.5 Ошибки доступа к памяти|#memerror] +** [4.6 Чтение строк из памяти|#memstr] +* [5. Модули|#modules] +** [5.1 Класс module|#moduleclass] +** [5.2 События загрузки и выгрузки модулей|#moduleload] +* [6. Получение символьной информации|#syminfo] +** [6.1 Символьные ( pdb ) файлы|#pdbfile] +** [6.2 Информация о типах|#types] +* [7. Типизированные переменные|#typedVar] +** [7.1 Класс typedVar|#typedVar] +** [7.2 Создание экземпляра класса typedVar|#typedVarClass] +** [7.3 Методы класса typedVar|#typedVarMethod] +** [7.4 Классы и структуры|#typedVarUDT] +** [7.5 Массивы и указатели|#typedVarArray] +** [7.6 Энумераторы|#typedVarEnum] +** [7.7 Приведениe к другим типам|#typedVarCast] +* [8. Процессы и потоки|#ProcessThreads] +** [8.1 Потоки в пользовательском режиме|#UserModeThreads] +** [8.2 Потоки в режиме ядра|#KernelModeThreads] +** [8.3 Процессы в режиме ядра|#KernelModeProcess] +* [9. Локальные переменные|#Locals] +** [9.1 Текущие локальные переменные|#CurrentLocals] +* [10. Точки останова|#breakpoints] +** [10.1 Задание точек останова|#setBreakpoints] +** [10.2 Условные точки останова|#condBreakpoints] +* [11. Отладочные события|#eventHandler] +* [12. Класс disasm|#disasm] +* [API Reference|PYKD 0.2. API Reference] +{anchor:intro} +! 1. Введение +{anchor:commoninfo} +!!! 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. Последний режим может быть полезен для создания автоматических средств разбора креш-дампов например. +[Содержание|#table] +{anchor:gettingStarted} +!!! 1.2 Быстрый старт +Для быстрого старта лучше всего скачать автоматический инсталлятор. Он сам установит все необходимые компоненты ( в т.ч. и Python если он еще не установлен ). Чтобы убедится, что установка прошла успешно, запускаем winbg и начинаем отладку приложения и или анализ дампа. Загружаем *pykd*: +{{ +.load pykd.pyd +}} +Если не возникло никаких сообщений об ошибках, значит все нормально. Но на всякий случай убедимся, что все действительно работает: +{{ +>!pycmd +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) +>>>print "Hello world!" +Hello world! +>>>quit() +> +}} +Пробуем запустить скрипты из примеров: +{{ +!py help +!py samples +}} +Если все получилось, можно переходить к написанию собственных скриптов. +[Содержание|#table] +{anchor:Building} +!!! 1.3 Сборка из исходников +!!!! Забираем исходники из [url:репозитория|http://pykd.codeplex.com/SourceControl/list/changesets]. +!!!! Устанавливаем python +Скачать нужную версию можно [url:здесь|http://www.python.org]. +!!!! Устанавливаем и настраиваем boost. +Если кто не знает, где взять: [url:http://www.boost.org]. Там же есть инструкция по установке и сборке. +!!!! Устанавливаем переменные окружения. +Для сборки требуются следующие переменные окружения: +{"$(DIA_SDK_ROOT)"} - путь к библиотеке MS DIA. Он должен выглядеть примерно так: C:\Program Files (x86)\Microsoft Visual Studio 9.0\DIA SDK. Библиотека MS DIA устанавливается вместе с Visual Studio. +{"$(DBG_SDK_ROOT)"} - путь к Debug Engine SDK Он должен выглядеть примерно так: C:\Program Files (x86)\Debugging Tools for Windows (x86)\sdk. Debug Engine SDK устанавливается вместе с Debugging Tools for Windows ( в настоящее время, входит в Platform SDK ). +{"$(BOOST_ROOT)"}- путь к каталогу, куда установлен boost +{"$(PYTHON_ROOT)"} - путь к каталогу, куда установлен python. Предполагается, что в системе установлены обе версии x86 и x64 и структура каталогов с python такая: +C:\Python26\x86\... +C:\Python26\x64\... +Переменная {"$(PYTHON_ROOT)"} в этом случае должна быть равна C:\Python26. Если в пути установки python а отсутсвует указани платформы, то надо будет подправить проектный файл. +!!!! Сборка библиотек boost.python +Для сборки потребуются статические библиотеки boost.python. В проектном файле прописаны следеующие пути к библиотекам boost.python: +{"$(BOOST_ROOT)\stage"} - для х86 сборки +{"$(BOOST_ROOT)\stage64"} - для х64 сборки + +Собрать их можно с помощью команд: +{"bjam --stagedir=stage --with-python stage"} +{"bjam address-model=64 --stagedir=stage64 --with-python stage"} + +Если у вас не установлен еще bjam, то скачать его можно [url:здесь|http://www.boost.org/users/download/boost_jam_3_1_18.html] +[Содержание|#table] +{anchor:Installing} +!!! 1.4 Ручная установка +Для ручной установки потребуется сам модуль pykd.pyd {"C+ редистрибутив runtime С++"} от visual studio ( vcredist ), при чем именно тот, с которым был собрано моудль. Если вы собиралм его самостоятельно, с этим проблем быть не должно. Если скачали с сайта - также проблем быть не должно, так как zip архив содержит нужный vcredist ( если конечно мы ничего не напутали при релизе :) ). +!!!! Куда скопировать pykd.pyd? +Это зависит от сценария использования. Если pykd будет использоваться как плагин к windbg, то имеет смысл скопировать его в каталог winext ( он находится в каталоге, куда установлен windbg ). В этом случае, его можно переименовать в pykd.dll, чтобы при загрузке можно было опустить расширение файла: +{{ +kd>.load pykd +}} +Если pykd будет использовать для написания своих python программ, то его нужно разместить там, где его сможет найти интерпретатор python. Есть три варианта: +* Подкаталог Lib в катлоге, куда установлен python +* Любой свой каталог. Путь к нему необходимо указать в переменной окружения $(PATHONPATH} или задать через реестр +* Любой свой, путей нигде не прописывать, запускать python всегда из каталога с pykd.pyd +!!!! Установка vcredist +Конечно vcredist нужно установить. Иначе зачем его было скачивать? +!!!! Регистрация MS DIA +Библиотека MS DIA будет установлена во время инсталляции vcredist. Но для работы ее нужно еще зарегистрировать. Для этого необходимо найти каталог, куда был проинсталлирован модуль msdia90.dll и из этого каталога выполнить команду: +{{ +regsvr32 msdia90.dll +}} +Если вы собирали модуль самостоятельно и использовали Visual Studio, то никаких действий с vcredist ом производить не надо - он уже есть на вашей машине и MS DIA также на месте. +[Содержание|#table] +{anchor:APIchange} +!!! 1.5 Изменения в API +!!!! loadModule +Функция loadModule убрана. Вместо нее необходимо использовать конструктор класса *module* +{{ +# mod = loadModule('mymodule") +mod = module("mymodule") +}} +[Содержание|#table] +{anchor:windbg} +! 2. Команды для windbg +{anchor:loadplugin} +!!! 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 будет загружаться автоматически для данного воркспейса. +[Содержание|#table] +{anchor:runscript} +!!! 2.2 Запуск скрипта +Запуск скрипта осуществляется с помощью команды *!py* +{{ +kd>!py script_path/script_name.py param1 param2 ... +}} +Расширение .py можно опустить. Чтобы не указывать полный путь к скрипту, нужно прописать его в переменную окружения PYTHONPATH или ( это более предпочтительный путь ), добавить ключ pykd в раздел реестра +{"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] +}} +[Содержание|#table] +{anchor:console} +!!! 2.3 режим консоли +Запуск консоли python осуществляется командой !pycmd: +{{ +1: kd> !pycmd +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(). При этом состояние python интерператора сохраняется: +{{ +>>> a = 10 +>>> quit(0) +1: kd> !pycmd +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) +>>> print a +10 +>>> +}} +[Содержание|#table] +{anchor:debugging} +! 3. Управление отладкой +{anchor:break} +!!! 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* внутри обработчиков отладочных событий ( например, в условных точках останова ). +[Содержание|#table] +{anchor:step} +!!! 3.2 Пошаговое выполнение +Для пошаговой отладки ( трассировки ) служат две функции: +*step()* +*trace()* +Из действие аналогично командам отладки "trace into" и "trace over". Обе функции могут вернуть исключение *DbgException*, если отлаживаемый процесс уже завершился. +[Содержание|#table] +{anchor:debugstatus} +{anchor:consoledebug} +!!! 3.3 Управление отладкой из python приложений. +Если вы хотите исполнять свои скрипты вне windbg, то первым шагом, который вам необходимо сделать, будет создание отладочной сессии. Более подробно управление сессиями будет рассмотрено в соответствующем разделе. Если ваше приложение не планрует использовать несколько сессий отладки, то заботится об этом не надо - первая сессия будет создана автоматически при следующих вызовах: +*loadDump( dumpName )* - загружает креш-дамп +*id startProcess( imageName)* - запускает в режиме отладки новый процесс +*id attachProcess( processId)* - присоединяет отладчик к существующему процессу +*attachKernel( parameterStr)* - присоединяет отладчик к ядру отлаживаемой системы + +Для отсоединения отладчика от отлаживаемого процесса служит вызов *detachProcess(id)* + +Для остановки отладки и удаления отлаживаемого процесса служит +функция *killProcess(id)* + +Узнать, режим отладчика можно с помощью вызовов: +* bool isDumpAnalyzing() +* bool isKernelDebugging() +Первая функция позволяет определить, находится ли отладчик в состоянии "живой" отладки или анализируется дамп памяти. Вторая функция позволяет различать отладку режима ядра или пользовательского режима. Если скрипт использует специфичные системные символы ( к примеру, символы ядра Windows ), то будет полезно вставить такую проверку в начале скрипта: это позволит сообщить пользователю, что он пытается запустить скрипт в неподходящей ситуации. + +[Содержание|#table] +{anchor:dbgprint} +!!! 3.4 Печать отладочной информации +Для вывода информации на экран можно воспользоваться стандартным оператором *print*. Но рекомендуется использовать специальные функции: +*dprint( message, dml = False )* +*dprintln( message, dml = False )* +Вторая функция отличается от первой тем, что автоматически добавляет символ перевода строки. Опциональный параметр *dml* включает вывод с DML разметкой ( работает только в windbg ). Разметка DML похожа на очень-очень упрощенный HTML. Форматирование текста осуществляется с помощью специальных тегов: +* - выделенный шрифт +* - курсив +* - шрифт с подчеркиванием +* command - выполнение команды ( похоже на тег в HTML ) +{{ +dprinln("This command reload all symbols", True) +dprinln("reload", True) +}} +[Содержание|#table] +{anchor:dbgcommand} +!!! 3.5 Выполнение команд отладчика +Для выполнения команды отладчика служит функция: +*commandOutput dbgCommand( commandStr )* +{{ +s = dbgCommand("!analyze -v") +dprint(s) +}} +Для вычисления выражения ( аналог команды отладчика "?" ) служит функция +*expressionStr expr( expressionStr )* +{{ +expr("@rax + 10") +}} +При выполнении python программы вам могут понадобиться команды из стандартных расширений windbg ( например, !analyze ). Их придется загрузить вручную. Для этого служит функция: +*long loadExt( extensionPath )* +Эта функция возвращает дескриптор расширения, который нужен для вызова функция расширения: +* str callExt( extHandle, command, params ) +И, если необходимо, для выгрузки расширения: +* removeExt( extHandle ) +*Внимание!* Работа с раcширениями windbg отличается от версии *pykd* 0.1! В версии 0.2 упразднен класс *ext* и можно не заботится о времени жизни загруженного расширения. +[Содержание|#table] +{anchor:createcrash} +!!! 3.6 Создание креш-дампа +Сохранить состояние системы в виде креш-дампа можно с помощью ф. *writeDump*. Функция доступна для режима ядра и пользовательского режима. Второй параметр задает тип дампа ( False - полный дамп, True - минидамп ) +{{ +writeDump( r"C:\dump\fulldump.dmp", False ) +writeDump( r"C:\dump\minidump.dmp", True ) +}} +[Содержание|#table] +{anchor:memory} +! 4. Работа с памятью и регистрами +{anchor:reg} +!!! 4.1 Доступ к регистрам общего назначения. +Доступ к регистрам общего назначения ( GPR ) осуществляется с помощью ф. *reg*: +* cpuReg reg( regName ) +* cpuReg 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: + pass +}} +Оба варианта функции *reg* возвращают экземпляр класса *cpuReg*. Если информация о регистре не может быть получена, будет возбужено исключение *BaseException* + +Класс *cpuReg* имеет два метода: +* name() +* index() +Класс *cpuReg* может использовать в целочисленных вычислениях без дополнительных приведений типа: +{{ +r = reg("eax") +print r / 10 * 234 +}} +*Внимание:* +Текущая реализация *pykd* поддерживает работу только с целочисленными регистрами. Работа с FPU, MMX, SSE регистрами не поддерживается. Поддержка планируется в следующих версиях. +[Содержание|#table] +{anchor:msr} +!!! 4.2 Доступ к модельно-специфичным регистрам ( MSR ) +Доступ к модельно-специфичный регистрам осуществляется через ф. *rdmsr( msrNumber )*: +{{ +>>> print findSymbol( rdmsr( 0x176 ) ) +nt!KiFastCallEntry +}} +[Содержание|#table] +{anchor:addr64} +!!! 4.3 Нормализация виртуальных адресов +Все функции *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" +}} +[Содержание|#table] +{anchor:memaccess} +!!! 4.4 Прямой доступ к памяти +Для доступа к памяти отлаживаемой системы *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*. +[Содержание|#table] +{anchor:memerror} +!!! 4.5 Ошибки доступа к памяти +Все функции работы с памятью при невозможности прочесть данные по указанному адресу возвращают исключение *MemoryException*. +{{ +try: + a = ptrByte( 0 ) +except MemoryException: + print "memory exception ocurred" +}} +Проверить валидность виртуально адреса можно с помощью ф. *isValid(va)* +[Содержание|#table] +{anchor:memstr} +!!! 4.6 Чтение строк из памяти +Часто приходится читать из памяти строковые данные. Конечно, для этого можно было бы использовать ф. *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 +[Содержание|#table] +{anchor:modules} +! 5. Модули +{anchor:moduleclass} +!!! 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 +}} +[Содержание|#table] +{anchor:moduleload} +!!! 5.2 Обработка событий загрузки и выгрузки модуля +Для обработки событий загрузки и выгрузки модуля надо создать наследника класса [eventHandler|#eventHandler]. +Обработка события загрузки модуля осуществляется методом [onLoadModule|#eventHandler_onLoadModule]. Обработка события выгрузки модуля - [onUnloadModule|#eventHandler_onUnloadModule] +[Содержание|#table] +{anchor:syminfo} +! 6. Получение символьной информации +{anchor:pdbfile} +!!! 6.1 Символьные ( pdb ) файлы +При сборке модуля создается файл с символьной ( отладочной ) информацией ( обычно, с раширением pdb ). В зависимости от настроек компилятора он может содержать полную или обрезанную информацию ( т.н "публичные символы" ). Символьные файлы могут содержать следующую информацию: +* Имена, типы и относительные смещения глобальных переменных и констант +* Имена, параметры и относительные смещения функций и методов классов +* Имена типов, определенных пользователем ( структур, классов, перечислений ) +* Значения констант +* Информацию о локальных переменных функций и методов классов. +Для работы с символьными файлами Microsoft предоставляет специальную библиотеку MS DIA. *Pykd* использует ее для работы с символами. Для непосредственного доступа к символьной инфорамции *pykd* реализует свой собственный интерфейс. +[Содержание|#table] +{anchor:types} +!!! 6.2 Информация о типах +{anchor:typeInfo} +!!!! 6.2.1 Класс представления типа +Для представления информации о типе в питон экспортируется класс *typeInfo*. Этим классом описываются структуры, классы, объединения, перечисления, битовые поля, указатели и базовые типы. +Класс представляет следующие методы: +* *name* - получение имени типа +* *size* - получение полного размера типа +* *staticOffset* - получение смещения статического поля +* *fieldOffset* - получение смещения поля +* *bitOffset* - получение смещения битового поля +* *bitWidth* - получение размера битового поля +* *field* - получение поля +* *asMap* - получение словаря для значения преречисления +* *deref* - разыменование указателя +* *ptrTo* - формирование указателя на тип +* *arrayOf* - формирование массива, элементами которого является тип +* *append* - добавление поля (метод для структур и перечислений, созданных с использованием [typeBuilder|#typeBuilder]) + +[Содержание|#table] +{anchor:get_typeInfo} +!!!! 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*, который возвращает список имен типов, информация о которых представлена в отладочных символах модуля. + +[Содержание|#table] +{anchor:typeBuilder} +!!!! 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* +}} + +[Содержание|#table] +{anchor:typedVar} +! 7. Типизированные переменные +{anchor:typedVar} +!!! 7.1 Класс typedVar +Ранее мы рассмотрели пример, как можно прочесть из памяти структурированные данные: +{{ +from struct import unpack +shortField1, shortField2, longField = unpack('hhl', loadChars( addr, 8 ) ) +}} +Очевидно, что работать с большими структрами, содержащими сотни полей, так не очень удобно. Поэтому в *pykd* реализован специальный класс: *typedVar*, позволяющий работать со сложными структурами данных. Информацию о типе данных *typedVar* получает из символьной информации. +[Содержание|#table] +{anchor:typedVarClass} +!!! 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" +}} +[Содержание|#table] +{anchor: typedVarMethod} +!!! 7.3 Методы класса typedVar +* getAddress() - возвращает адрес переменной +* sizeof() - возвращает размер переменной +* offset() - если переменная является полем родительской структуры, то возвращает смещение относительно родителя. +* field( fieldName ) - возвращает поле структуры как экземпляр класса *typedVar* +* deref() - для указателей выполняет т.н. разыменование и возвращает результат в виде экземпляра класса *typedVar* +* type() - возвращает тип переменной в виде экземпляра класса *typeInfo* +[Содержание|#table] +{anchor:typedVarUDT} +!!! 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 просто инкрементирует знаечние адреса. + +[Содержание|#table] +{anchor:typedVarArray} +!!! Массивы и указатели +Класс *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* могут участвовать в арифметический выражениях. Для массивов в качестве значения берется его адрес, для указателя - значение указателя ( т.е куда он указывает ). *Внимание:* при арифметических операциях *не действуют* правила адресной аримфетики Си. + +[Содержание|#table] +{anchor:typedVarEnum} +!!! 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 +}} +[Содержание|#table] +{anchor:typedVarCast} +!!! 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 +}} +[Содержание|#table] +{anchor:ProcessThreads} +! 8. Процессы и потоки +{anchor:UserModeThreads} +!!! 8.1 Потоки в пользовательском режиме +В пользовательском режиме отладчик работает в контексте отлаживаемого процесса. Если процесс имеет несколько потоков, в режиме отладки можно переключить контекст на другой поток. Нужно различать текущий поток и поток, на который переключился отладчик. В оригинале они называются: "current thread" - поток который продолжит выполнение после возобновления отладки, "implicit thread" - поток, в контексте которого находится отладчик. Контекстом потока мы называем совокупность регистров процессора, в том числе и указатель текущей инструкции и стека. +Для смены контекста потока служит функция *setImplicitThread*. В качестве параметра она принимает указатель на TEB (thread enviroment block). Получить указатель на TEB можно с помощью: +* *getImplicitThread* - TEB текущего потока +* *getProcessThreads* - список TEB-ов всех потоков процесса +[Содержание|#table] +{anchor:KernelModeThreads} +!!! 8.2 Потоки в режиме ядра +В режиме ядра есть некоторые особенности. +Во-первых, функции *setImplicitThread* и *getImplicitThread* работают с указателями на ETHREAD, а не с TEB, как в пользовательском режиме. Во-вторых, ф. *getProcessThreads* для режима ядра не доступна. Если нужно получить список потоков какого либо процесса придется делать это вручную разбирая структуру EPROCESS. К счастью, это не сложно: +{{ +nt = module("nt") +process = nt.typedVar( "_EPROCESS", processAddr ) +threadLst = nt.typedVarList(process.ThreadListHead, "_ETHREAD", "ThreadListEntry") +}} +Остается добавить, что переключение контекста отлаживаемого потока не приводит к переключению контекста процесса. Подробнее об этом в следующем разделе. +[Содержание|#table] +{anchor:KernelModeProcess} +!!! 8.3 Процессы в режиме ядра +[Содержание|#table] +{anchor:Locals} +! 9. Локальные переменные +{anchor:CurrentLocals} +!!! 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 +}} +[Содержание|#table] +{anchor:breakpoints} +! 10. Точки останова +{anchor:setBreakpoints} +!!! 10.1 Задание точек останова +Для задания точки останова служит функция *setBp*. Она позволяет устанавливать как программные точки останова, так и аппаратные. Функция возвращает числовой идентификатор, который можно в последствии использовать для удаления точки останова через ф. *removeBp*. +Установка программной точки останова: +{{ +nt = module("nt") +bpid = setBp( nt.NtCreateFile ) +}} +Установка аппаратной точки останова: +{{ +nt = module("nt") +bpid = setBp( nt.NtCreateFile, 1, 4 ) +}} +Второй параметр - размер памяти, к которой осуществляется доступ, Третий параметр - тип доступа ( 1 - чтение, 2- запись, 4 - исполнение, типы доступа работают как флаги и могут быть объединены. Например, 3 - чтение + запись ). + +[Содержание|#table] +{anchor:condBreakpoints} +!!! 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 ) + +[Содержание|#table] +{anchor:eventHandler} +! 11. Отладочные события +[Содержание|#table] +{anchor:eventHandler_onBreakpoint} +!!! 11.1 Обработка точек останова (метод onBreakpoint) +[Содержание|#table] +{anchor:eventHandler_onException} +!!! 11.2 Обработка исключительных ситуаций (метод onException) +[Содержание|#table] +{anchor:eventHandler_onLoadModule} +!!! 11.3 Обработка события загрузки исполняемого модуля (метод onLoadModule) +[Содержание|#table] +{anchor:eventHandler_onUnloadModule} +!!! 11.4 Отработка события выгрузки исполняемого модуля (метод onUnloadModule) +[Содержание|#table] +{anchor:disasm} +! 12. Класс disasm +[disasm reference|PYKD 0.1. API Reference#disasm] +Класс *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. Очевидно, что это значение имеет смысл при дизассемблировании текущей инструкции. +[Содержание|#table] \ No newline at end of file diff --git a/docs/ru/tutorial.txt b/docs/ru/tutorial.txt new file mode 100644 index 0000000..92443b6 --- /dev/null +++ b/docs/ru/tutorial.txt @@ -0,0 +1,125 @@ +!! Введение + +!!!! Шаг 1. Начало работы +Для установки лучше всего воспользоваться автоматическим инсталлятором. Он установит pykd в нужно место, а также установит и зарегистрирует все необходимые компоненты. + +Если установка завершилась без ошибок, пора познакомится с pykd. Для этого стартуем windbg и начинаем отладочную сессию ( открываем процесс, дамп или устанавливаем соединение с отладчиком ядра ). Теперь можно загрузить pykd. Для этого выполняем команду: +.load pykd.pyd +Если во время загрузки случится какая либо ошибка - windbg выдаст сообщение. Отсутствие каких либо сообщений свидетельствует об удачной загрузки расширения. + +Теперь можно начинать работу. Выполним команду !pycmd. После ее выполнения отладчик перейдет в режим ввода пользовательских данных. Весь пользовательский ввод будет обрабатываться интерпретатором python. +{{ +0:000> !pycmd +Python 2.6.6 (r266:84297, Aug 24 2010, 18:13:38) [MSC v.1500 64 bit (AMD64)] on win32 +Type "help", "copyright", "credits" or "license" for more information. +(InteractiveConsole) +>>> print "Hello world!" +Hello world! +>>> +}} +Тут самое время ознакомится с синтаксисом python, если кто еще не знаком. Уверяю, это не должно занять много времени: python очень прост в освоении. + +Давайте вспомним базовые основы синтаксиса python: +{{ +>>> def printHello(): +... i = 0 +... while i < 4: +... print "Hello #%d" % i +... i += 1 +... +>>> printHello() +Hello #0 +Hello #1 +Hello #2 +Hello #3 +>>> +}} +Обратите внимание: вложенность блоков задается количеством лидирующих пробелов. Это, так сказать, "фирменная" особенность python. Пока этих знаний нам будет вполне достаточно. Двигаемся дальше. + +!!!! Шаг 2. Доступ к регистрам. + +Любой отладчик должен предоставлять три базовые возможности: чтение регистров процессора, чтение памяти и управление режимом отладки. Начнем с регистров. С pykd это делается довольно просто: +{{ +>>> print hex(reg("eip")) +0x778ecb60 +>>> print hex(reg("esp")) +0x1ef0e0 +>>> print hex(reg("esp")+4) +0x1ef0e4 +}} +В данном случае, мы используем функцию PYKD *reg*. Она осуществляет чтение регистров процессора по имени. Пытливый читатель может спросить: как мы используем функции из PYKD без импортирования самого модуля? На самом деле, модуль конечно надо импортировать. Просто PYKD это сделал автоматически при конструировании консоли Python. +Давайте напишем небольшой пример и посмотрим, куда указывает текущий счетчик инструкций: +{{ +>>> print findSymbol(reg("eip")) +ntdll!LdrpDoDebuggerBreak+30 +}} +Функция *findSymbol* пытается для данного адреса найти т.н. отладочный символ. В данном случае мы видим, что счетчика инструкций равен смещению 0x30 относительно функции LdrpDoDebuggerBreak, т.е мы попросту находимся внутри функции LdrpDoDebuggerBreak, находящейся в модуле ntdll. Мы это смогли выяснить, поскольку у нас есть отладочная информация для модуля ntdll.dll ( соответствующий pdb файл ). Если у вас по какой то причине символы не показываются, необходимо проверить настройки путей к символам в windbg. + +!!!! Шаг 4. Доступ к памяти +Для доступа к памяти PYKD предлагает большой набор функций. Их можно разделить на 3 группы: +* Чтение значения из памяти: +*ptrByte* +*ptrWord* +*ptrDWord* +*ptrQWord* +И другие, с полным набором можно ознакомится в [справке по API|PYKD 0.2. API Reference] +Все функции принимают в качестве параметра адрес и возвращают значение, хранящееся по данному адресу. +* Чтение массивов +*loadBytes* +*loadWords* +*loadDWords* +*loadQWords* +Все функции принимают в качестве параметров указатель на начало массива и его длину в элементах. Возвращают объект *list* c элементами массива +* Чтение строк +*loadCStr* +*loadWStr* +Функции читают из памяти 0-терминированные строки возвращают python строки. +Давайте модифицируем предыдущий пример и выведем аргументы функции. Будем считать, что функция имеет соглашение о вызове stdcall и ее параметры адресуются регистром ebp +{{ +>>> def printFunc(): +... print findSymbol( reg("eip") ) +... params = [ ptrDWord( reg("ebp") + 4*(i+1) ) for i in range(0,3) ] +... print "var1: %x var2: %x var3: %x" % ( params[0], params[1], params[2] ) +... +>>> print printFunc() +ntdll32!LdrpDoDebuggerBreak+2c +var1: 774b1383 var2: fffdd000 var3: fffde000 +None +>>> +}} +Обратите внимание на конструкцию: +{{ +params = [ ptrDWord( reg("ebp") + 4*(i+1) ) for i in range(0,3) ] +}} +Это т.н генератор списка - специальная конструкция python, которую можно использовать для инициализации списков. Это конструкция эквивалентна следующей +{{ + [ ptrDWord( reg("ebp") + 4) ), ptrDWord( reg("ebp") + 8) ), ptrDWord( reg("ebp") + 0xC) ) ] +}} + +!!!! Шаг 5. Доступ к памяти с учетом типа. + +При отладке программ мы чаще всего сталкиваемся с типизированными переменными. PYKD имеет богатые возможности для доступа к переменным с учетом типа. По сути, эта главная "фишка" всего проекта: доступ к полям структур и классов осуществляется очень похожим на исходный код способом. Например, допустим у нас есть следующий код на Си: +{{ +struct STRUCT_A { + int filed1; + char field2; +}; + +STRUCT_A a = { 100, 2} +}} +Теперь во время отладки мы хотим проверить состояние переменной 'a' c помощью PYKD: +{{ + a = typedVar( "module!STRUCT_A", getOffset("module!a") ) + if a.field1!=100 or a.field2!=2: + print "ERROR! a is not poperly initialized!" +}} + + + + + + + + + +