среда, 30 января 2013 г.

Работа со стеком в Kernel Mode

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

Вывод всех потоков для всех процессов:
!process 0 7
Поиск процесса и вывод стеков только для него
!process 0 0 proga.exe
!process addr 7

Еще вариант:
!stacks 2
Последний вариант интересен тем, что выводит также потоки для потока с ID = 0 ( IDLE ), команда !process "IDLE" за процесс не считает :).
Есть еще команда для вывода стеков активных потоков:
!running -i -t
Все варианты страдают двумя недостатками: проблемы с отображением юзермодных частей стеков ( чтобы они нормально отображались нужно переключаться в контекст конкретного процесса и получать список юзермодных модулей ); стеков много, а метод для фильтрации вывода по сути один - скопировать вывод и анализировать его какой нибудь внешней программой.

Поэтому мы решили немного упростить жизнь kernel-mode разработчиков и сделали скрипт stkwalk

Скрипт stkwalk

Если вы следите за нашим проектом, то, верно, знаете, что не давно мы выпустили новую версию pykd - 0.2.0.16. Если не знаете, то нужно поскорей узнать и обновить, потому что это необходимо для наших дальнейших упражнений. Если вы ленивы и воспользовались для установки pykd автоматический инсталлятор, то вы будете вознаграждены установкой га вашу машину целой толпы очень полезных скриптов. Среди них будет и stkwalk. Начнем знакомство:
!py stkwalk
Получим вывод стеков почти такой же, как выдает !process 0 0, но юзермодные части стеков выводятся более тщательно. Более того, если вы запустите скрипт на x64 системе, вы увидите также и WOW64 части стека. Польза +1.

Тут пора посмотреть, какие возможности предоставляет нам stkwalk. Спросим у него самого

3: kd> !py stkwalk --help
Stack walker. ver 1.0
Usage: stkwalk [options]

Options:
  -h, --help            show this help message and exit
  -p PROCESSFILTER, --process=PROCESSFILTER
                        process filter: boolean expression with python syntax
  -m MODULEFILTER, --module=MODULEFILTER
                        module filter: boolean expression with python syntax
  -f FUNCFILTER, --function=FUNCFILTER
                        function filter: boolean expression with python syntax
  -t THREADFILTER, --thread=THREADFILTER
                        thread filter: boolean expresion with python syntax
  -u, --unique          show only unique stacks
Мы тут видим возможность фильтрации по различным параметрам, с этим мы подробнее разберемся ниже. Последняя опция позволяет выводить только уникальные стеки. Пробуем:
3: kd> !py stkwalk -u
Stack walker. ver 1.0
Process ffffffff855a1c40
Name: System  Pid: 0x4

......

Thread ffffffff855f7d48, Process: System (ffffffff855a1c40)
ffffffff82c7de06 ffffffff8db6fc1c nt!KiSwapContext+26
ffffffff82c84fa5 ffffffff8db6fc30 nt!KiSwapThread+266
ffffffff82c838a3 ffffffff8db6fc68 nt!KiCommitThreadWait+1df
ffffffff82c84afd ffffffff8db6fc90 nt!KeRemoveQueueEx+4f8
ffffffff82c84393 ffffffff8db6fcf4 nt!ExpWorkerThread+e5
ffffffff82e25ad1 ffffffff8db6fd58 nt!PspSystemThreadStartup+9e
ffffffff82cd7539 ffffffff8db6fd98 nt!KiThreadStartup+19

.......

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

Фильтрация по процессам

Рассмотрим сначала пример - вывод стеков только для процессов с определенным именем:

3: kd> !py stkwalk -p "name=='winlogon.exe'"
Stack walker. ver 1.0
Process ffffffff88395d40
Name: winlogon.exe  Pid: 0x30c

Thread ffffffff88393840, Process: winlogon.exe (ffffffff88395d40)
ffffffff82c7de06 ffffffff90ea3bd4 nt!KiSwapContext+26
ffffffff82c84fa5 ffffffff90ea3be8 nt!KiSwapThread+266
ffffffff82c838a3 ffffffff90ea3c20 nt!KiCommitThreadWait+1df
ffffffff82c7d76f ffffffff90ea3c48 nt!KeWaitForSingleObject+393
ffffffff82e48b45 ffffffff90ea3cc0 nt!NtWaitForSingleObject+c6
ffffffff82c596ea ffffffff90ea3d28 nt!KiFastCallEntry+12a
00000000771b6194 000000000006fad8 ntdll!KiFastSystemCallRet
00000000771b5b0c 000000000006fadc ntdll!NtWaitForSingleObject+c
00000000753b179c 000000000006fae0 KERNELBASE!WaitForSingleObjectEx+98
000000007699efe3 000000000006fb4c kernel32!WaitForSingleObjectExImplementation+75
000000007699ef92 000000000006fb64 kernel32!WaitForSingleObject+12
0000000000df1c9e 000000000006fb78 winlogon!SignalManagerWaitForSignal+e4
0000000000df1a50 000000000006fb98 winlogon!StateMachineRun+271
0000000000dfe343 000000000006fd54 winlogon!WlStateMachineRun+16
0000000000dfe199 000000000006fd68 winlogon!WinMain+af4
0000000000dfe479 000000000006fde8 winlogon!_initterm_e+1a1
00000000769a1154 000000000006fe78 kernel32!BaseThreadInitThunk+e
00000000771cb299 000000000006fe84 ntdll!__RtlUserThreadStart+70
00000000771cb26c 000000000006fec4 ntdll!_RtlUserThreadStart+1b

.....

Как видим, фильтр задается с помощью ключа -p ( --process ) и последующей строки, задающей сам фильтр. В примере мы закрыли строку, задающую фильтр, в кавычки. В принципе, внешние кавычки можно было бы опустить, но тогда мы должны гарантировать отсутствие пробелов в строке ( иначе, парсер командной строки просто разобьет строку на два параметра и все сломается ). Поэтому, для внутреннего спокойствия лучше поставит кавычки. Само выражение должно быть корректно с точки зрения python. Иначе мы увидим сообщение об ошибке:
3: kd> !py stkwalk -p name='winlogon.exe'
Stack walker. ver 1.0


Traceback (most recent call last):

  File "C:\proj\pykd-0.2\Snippets\stkwalk.py", line 238, in 
    main()

  File "C:\proj\pykd-0.2\Snippets\stkwalk.py", line 231, in main
    printProcess( process, processFilter, threadFilter, moduleFilter, funcFilter, printopt )

  File "C:\proj\pykd-0.2\Snippets\stkwalk.py", line 147, in printProcess
    if processFilter and not processFilter(process, process.UniqueProcessId, processName ):

  File "C:\proj\pykd-0.2\Snippets\stkwalk.py", line 212, in 
    processFilter = lambda process, pid, name: eval( options.processfilter )

  File "", line 1

    name='winlogon.exe'

        ^

SyntaxError: invalid syntax
Выражение должно вернуть булевский результат. В нем могут использоваться следующие переменные:
  • name - имя процесса
  • pid- PID процесса
  • process - объект типа typedVar( "nt!_EPROCESS", addr )
Кроме того, может задавать маски через fnmatch. Все это лучше показать на примерах:
3: kd> !py stkwalk -p fnmatch(name,'winlogon.*')
3: kd> !py stkwalk -p "pid>100 and pid<1000"
3: kd> !py stkwalk -p "process.Win32Process==0"

Фильтрация по модулям

Разобравшись с фильтрацией по процессам, перейдем к фильтрации по модулям. Фильтрация по модулю задается с помощью ключа --module или, сокращенно, -m. В качестве параметра выступает также логическое выражение, удовлетворяющее синтаксису Python. В выражении можно использовать:
  • name - имя модуля
  • module - объект типа module
Несколько примеров:
0: kd> !py stkwalk -m fnmatch(name,'HTTP*')
0: kd> !py stkwalk -m "module.checksum()==0x8d29e"
0: kd> !py stkwalk -m "name=='HTTP' or name=='tcpip'"

Фильтрация по функциям

Для задания фильтра по функциям служит ключ --function, -m. В выражении, задающем предикат фильтра, можно использовать переменную name - имя функции.

0: kd> !py stkwalk -f fnmatch(name,'*WaitFor*')

Фильтрация по потокам

Для задания фильтра по функциям служит ключ --thread, -t. В выражении, задающем предикат фильтра, можно использовать:

  • tid- идентификатор потока
  • thread- объект типа typedVar("nt!_KTHREAD", AddressOfThread )
0: kd> !py stkwalk -t tid<0xF

FAQ

Q: У меня нет никакого stkwalk.py. Где его, черт возьми, искать?
A: Попробуйте скачать скрипт вручную здесь
Q: Какого хрена, ничего не работает!
A: Убедитесь, что в находитесь в режиме отладки ядра.

Скрипт stkdelta

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

0: kd> !py stkdelta

Stack Base: ffffffff9f387fd0 Limit: ffffffff9f385000 Current: ffffffff9f387754 Used: 87c Unused: 2754
Stack Delta: Function:
          56 nt!KiSwapThread
          40 nt!KiCommitThreadWait
         380 nt!KeWaitForMultipleObjects
         652 nt!ObpWaitForMultipleObjects
         336 nt!NtWaitForMultipleObjects
           0 nt!KiFastCallEntry
-18446744072066663752 ntdll!KiFastSystemCallRet
           4 ntdll!ZwWaitForMultipleObjects
         156 KERNELBASE!WaitForMultipleObjectsEx
          72 kernel32!WaitForMultipleObjectsExImplementation
          28 kernel32!WaitForMultipleObjects
          36 mpengine!FreeSigFiles
          76 mpengine!GetSigFiles
          12 mpengine!GetSigFiles
          56 msvcrt!_endthreadex
           8 msvcrt!_endthreadex
          12 kernel32!BaseThreadInitThunk
          64 ntdll!__RtlUserThreadStart
          24 ntdll!_RtlUserThreadStart

0: kd> !py stkdelta stat
Stack Base: ffffffff9f387fd0 Limit: ffffffff9f385000 Current: ffffffff9f387754 Used: 87c Unused: 2754

Stack usage: Module:
==============================
   1464(%11) nt
    156 (%1) KERNELBASE
    124 (%1) mpengine
    112 (%0) kernel32
     92 (%0) ntdll
     64 (%0) msvcrt

Stack usage: Function
==============================
    652 (%5) nt!ObpWaitForMultipleObjects
    380 (%3) nt!KeWaitForMultipleObjects
    336 (%2) nt!NtWaitForMultipleObjects
    156 (%1) KERNELBASE!WaitForMultipleObjectsEx
     88 (%0) mpengine!GetSigFiles
     72 (%0) kernel32!WaitForMultipleObjectsExImplementation
     64 (%0) ntdll!__RtlUserThreadStart
     64 (%0) msvcrt!_endthreadex
     56 (%0) nt!KiSwapThread
     40 (%0) nt!KiCommitThreadWait
     36 (%0) mpengine!FreeSigFiles
     28 (%0) kernel32!WaitForMultipleObjects
     24 (%0) ntdll!_RtlUserThreadStart
     12 (%0) kernel32!BaseThreadInitThunk
      4 (%0) ntdll!ZwWaitForMultipleObjects

понедельник, 14 января 2013 г.

Release 0.2.0.14

Почти год прошел с момента прошлого релиза ( май 2012 ). Но мы не сидели сложа руки. Все это время мы напряженно работали над новой версией pykd - 0.2.x. В этой версии мы полностью переработали архитектуру. И хотя, возможно, список изменений и нововведений не покажется впечатляющим, но внутреннее устройство претерпело кардинальные изменения. Результат на наш взгляд крайне положительный: pykd стал быстрее и стабильнее, заложены архитектурные решения, которые позволят развивать проект дальше.

Скачать новую версию можно здесь. Обратите внимание: появились инсталляторы с поддержкой python 2.7. Рекомендуем именно эту версию.

Начнем обзор нововведений:

CTRL+BREAK
При работе в windbg зависший скрипт теперь можно остановить с помощью комбинации клавиш CTRL+BREAK. Отлаживать скрипты стало удобнее.

Функция writeDump
Создает файл креш-дампа

loadModule
Данная функции была удалена из API. Вместо нее нужно пользоваться конструктором класса module.

Новые методы класса module:
enumSymbols - возвращает список отладочных символов для данного модуля
getVersion - возвращает версию модуля ( из ресурсов )
queryVersion - возвращает дополнительные параметры версии модуля ( например, copyright ) из ресурсов

Новые функции для работы с памятью:
getVaProtect - возвращает атрибуты защиты страницы виртуальной памяти
findMemoryRegion - поиск непрерывного региона виртуальной памяти

Класс typeBuilder
Вспомогательный класс для создания собственных типов.

Отладочные события
Функции обратного вызова, используемые для обработки отладочных событий теперь должны возвращать другие значения:
Proceed - продолжить выполнение
NoChange - не менять статус отладчика ( имеет смысл при наличии нескольких обработчиков )
Break - остановить отладчик.
Также можно вернуть:
True - остановить отладчик
False - не менять статус отладчика

Новые методы класса typeInfo
ptrTo - создает новый экземпляр класса typeInfo - указатель на оригинальный тип
arrayOf - создает новый экземпляр класса typeInfo - массив элементов оригинального типа и указанного размера

Доступ к полям по указателю на структуру
Раньше необходимо было выполнить разыменование указателя:
print structPtr.deref().structField
Теперь можно опустить вызов deref():
print structPtr.structField

Функция getStackTraceWow64
Возвращает WOW64 часть стека

Функция getProcessThreads
Возвращает список потоков для отлаживаемого процесса ( только для User Mode ).

Вот такой краткий список изменений.
За более подробной информацией обращаемся сюда:
Руководство пользователя
Руководство для начинающих
Справочник по API
Если кому то документация кажется неполной/неправильной/не подробной - пожалуйста, сообщите нам. А лучше, помогите исправить! Так же очень нужен качественный перевод на английский язык.

Что будет дальше? Во-первых, в ближайшее время мы планируем опубликовать несколько новых статей, касающихся новых возможностей pykd. Читайте наш блог, следите за нами в Twitter. Во-вторых, разработка не оканчивается, в ближайшее время будут выпущены версии 0.2.0.15, 16, ... с исправлениями ошибок и незначительными доработками. В-третьих после выполнения всех задач по доработкам 0.2.x будет начата работа над 0.3.x.

На сегодня - все. Как всегда, большое спасибо нашим пользователям! Ждем от вас комментариев, замечаний и предложений! И громадное спасибо нашим разработчикам!!!

пятница, 8 июня 2012 г.

IOCTL декодер

Бывает нужно из IOCTL извлечь номер функции или определить буферизированный запрос или нет. Есть вот такой онлайн декодер . Мы написали аналогичный по функциональности скрипт для windbg.
Пользоваться просто:
2: kd> !py ctlcode 0x1401c8
Device type: FILE_DEVICE_NETWORK_FILE_SYSTEM   Function: 0x72(114)   Method: BUFFERED  Access:  FILE_ANY_ACCESS
Взять можно здесь.

пятница, 1 июня 2012 г.

Отладка WFP callout драйверов

Начиная с Windows Vista MS упорно продвигает свой фреймворк WFP ( Windows Filtering Platform ). В предверии Windows 8 Microsoft еще более закручивают гайки: теперь продукт не может получить логотип о совместимоcти с Windows 8, если он содержит TDI или LSP фильтры. Между нами, скажу по секрету шепотом свое мнение: WFP - это полное говно, писать на нем определенные вещи крайне неудобно. Но это все эмоции, говно - не говно, отлаживать драйвера надо. Кто знаком с WFP, тот помнит, что там все делает через одно место, а именно через функцию classsifyFn. В зависимости от версии WFP, эта функция может имееть разные сигнатуры, но в любом случае, они содержат параметеры inFixedValues и inMetaValues в которых, собственно, и содержится вся информация о сетевом событии. И конечно, при отладке хочется посмотреть, что содержат эти переменные. Но это не так просто, inFixedValues - это массив переменной длины, а в inMetaValues доступность полей регулируется флагами. Поэтому, я решил написать python скрипт, который будет выводить эту информацию.

Сам скрипт можно взять здесь: wfp.py download.
Использовать его довольно просто: Ставим брейкпойнт на интересующей нас ф. classifyFn. При срабатывании брейкпойнта, вводим команду: !py wfp /fixed poi(inFixedValues).
Примерный вывод команды:

FWPS_INCOMING_VALUES0:
 Layer: FWPS_LAYER_STREAM_V4
 Value: 7
    FWPS_FIELD_STREAM_V4_IP_LOCAL_ADDRESS
      Type: FWP_UINT32
      Value: 0xaf4087c
    FWPS_FIELD_STREAM_V4_IP_LOCAL_ADDRESS_TYPE
      Type: FWP_UINT8
      Value: 0x1
    FWPS_FIELD_STREAM_V4_IP_REMOTE_ADDRESS
      Type: FWP_UINT32
      Value: 0x40040b19
    FWPS_FIELD_STREAM_V4_IP_LOCAL_PORT
      Type: FWP_UINT16
      Value: 0xc02b
    FWPS_FIELD_STREAM_V4_IP_REMOTE_PORT
      Type: FWP_UINT16
      Value: 0x50
    FWPS_FIELD_STREAM_V4_DIRECTION
      Type: FWP_UINT32
      Value: 0x0
    FWPS_FIELD_STREAM_V4_FLAGS
      Type: FWP_UINT32
      Value: 0x0
Для 64 битной платформы, потребуется указать регистр rcx ( именно через него передается inFixedValues ):
!py wfp /fixed @rcx.
Для вывода метаинформации следует использовать команду:
!py wfp /meta poi(inMetaValues)
FWPS_INCOMING_METADATA_VALUES0:
    FWPS_METADATA_FIELD_FLOW_HANDLE: 0x210
    FWPS_METADATA_FIELD_SYSTEM_FLAGS: 0x1

Теперь на примере данного скрипта рассмотрим некоторые приемы написания скрипта для pykd. Первым делом, нам нужно получить доступ к полям переменной inFixedValues. Это делается так:

inFixedValue = typedVar( "FWPS_INCOMING_VALUES0_", addr )
dprintln( " Layer: " + fwpsLayer[ inFixedValue.layerId ] )
dprintln( " Value: %d" % inFixedValue.valueCount )
Класс typedVar позволяет осуществить удобный доступ к полям стурктур - просто как к аттрибутам. Именно так выводится количество записей в массиве:
dprintln( " Value: %d" % inFixedValue.valueCount )
Поле inFixedValue.layerId содержит численный идентификатор уровня фильтрации. Но мы хотим получить его символьное представление, которое задается энумератором FWPS_BUILTIN_LAYERS_. И тут нам приходит на помощь новый функционал pykd версии 0.1.x:
fwpsLayer = typeInfo( "FWPS_BUILTIN_LAYERS_" ).asMap()
Вызов typeInfo( "FWPS_BUILTIN_LAYERS_" ) возвращает информацию о типе FWPS_BUILTIN_LAYERS_. А метод asMap() конвертирует его в словарь вида: { число : "имя" }. Таким образом, зная идентификатор уровня фильтрации мы легко конвертируем его в символьное представление:
dprintln( " Layer: " + fwpsLayer[ inFixedValue.layerId ] )
Теперь нам необходимо вывести информацию о каждом элементе массива inFixedValue.incomingValue. Это можно было бы сделать с помощью цикла:
values = []
for i in range( 0, inFixedValue.valueCount ):
    values.append( typedVar( "FWPS_INCOMING_VALUE0_", inFixedValue.incommingValue + typeInfo("FWPS_INCOMING_VALUE0_").size() * i ).value )
Но есть способ гораздо проще, через специальный метод для работы с массивами:
values = [ x.value for x in typedVarArray( inFixedValue.incomingValue, "FWPS_INCOMING_VALUE0_", inFixedValue.valueCount ) ]
typedVarArray возвращает список переменных типа typedVar с inFixedValue.valueCount элементов и начинающийся с адреса inFixedValue.incomingValue. Далее мы используем генератор списков, чтобы трансформировать список.

Ну вот, собственно и все. Надеюсь данный скрипт будет полезен в работе и в качестве примера работы c pykd.

среда, 30 мая 2012 г.

Release 0.1.0.14

Доброго всем дня! Новая ветка pykd 0.1.x переведена в публичный статус. Релиз 0.1.0.14 рекомендован для использования. Страница загрузки: Pykd 0.1.0.14

Основные отличия от предыдущей ветки 0.0.x:

1. Для доступа к символьной информации используется библиотека MS DIA. Это позволило нам реализовать то, что не получалось сделать через Debug Engine: Доступ к битовым полям; Работа с многомерными массивами; Работа с энумераторами.

2. Новый класс dbgClient позволяет работать одновременно с несколькими отладочными сессиями.

3. Команда !pycmd реализует полноценную python консоль

4. Улучшена работа с классами исключений

5. Исправлено множество багов, написано более 200 тестов, покрывающих практически весь функционал.

6. Наконец то написано ( пока с некоторыми пробелами ) руководство пользователя

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

Большое спасибо всем, кто использует наш продукт! Как всегда, мы будем очень рады любым отзывам, предложениям и, особенно, указанием на наши ошибки.

Большое спасибо всем участникам проекта, трудившимся над веткой 0.1.x! Работа длилась почти целый год. За это время код был серьезно модифицирован, существенная часть его была переписана заново. Надеемся, пользователи оценят наши усилия.


понедельник, 6 февраля 2012 г.

Обработка исключений в Boost.Python

Мы в нашем проекте стараемся использовать исключения для сигнализации об ошибках. Почему? Причин несколько: удобно при отладке - если что-то пошло не так, мы сразу видим где и что произошло и можем перейти непосредственно к исправлению ошибки не теряя времени на отладку; пользователю приходится быть более дисциплинированным - ведь исключение нельзя проигнорировать, в отличие от проверки возвращаемого значения; удобно при разработке - вместо прокидывания кода ошибки через все функции, мы просто кидаем исключение, которое к тому же может быть гораздо информативнее. В результате, мы верим, что весь комплекс pykd + скрипт становится надежным инструментом.

Но разберемся, как же исключения попадают из С++ кода, написанного с помощью Boost::Python в виртуальную машину Python? Будем двигаться итеративно.

Итерация 0: Генерируем python исключение

Сгенерировать исключение в ВМ python просто. Для этого служит функция PyErr_SetString. В нее нужно передать указатель на тип исключения и сторку с описанием ошибки. Например так:
PyErr_SetString( PyExc_IndexError, "Index out of range" );
Не очень то удобно использовать эту конструкцию внтури С++ кода. Хотелось бы кидать С++ исключение и чтобы оно волшебным образом превращалось в python исключение. К счастью, boost::python предоставляет эту возможность.

Итерация 1: транслируем исключение из C++ в python

В общем все довольно просто. Любое исключение, возникшее во время выполнения python программы внутри модуля, использующего boost::python отлавливается. Существует механизм, с помощью которого можно поучаствовать в обработке такого исключения. Для этого необходимо зарегистрировать функцию-транслятор с помощью вызова register_exception_translator. Вот как это делается:

// класс исключения, для использования в C++ коде
class PyException : public std::exception
{
public:

    PyException( PyObject*  pyObj, const std::string &desc ) :
        std::exception( desc.c_str() ),
        m_typeObj( pyObj )
        {}    

    static
    void
    exceptionTranslate(const PyException &e ) {
        PyErr_SetString( e.m_typeObj, e.what() );
    }

private:

    PyObject*       m_typeObj;
};

void myFunc() {
    throw PyException( PyExc_IndexError, "Index out of range" );
}


BOOST_PYTHON_MODULE( my_module )
{
    // регистрируем транслятор
    boost::python::register_exception_translator<PyException>( &PyException::exceptionTranslate );

    // функция, вызываемая из python программы
    boost::python::def( "myFunc", &myFunc );
}

Прекрасно, генерировать исключения python теперь удобно. Но я хочу использовать собственный тип исключений. Возможно ли это? В общем да...

Итерация 2: Использование собственного типа исключений.


Идея простая. Boost::python дает возможность импортировать типы в python программу. Наш план таков: импортируем тип в python, регистрируем функцию-транслятор С++ исключения и в коде функции-трансялтора используем наш тип исключения, который мы ранее импортировали ( и запомнили! ) в python.
// класс исключения, для использования в C++ коде
class MyException : public std::exception
{
public:

    MyException ( const std::string &desc ) :
        std::exception( desc.c_str() )
        {}    

    static void setTypeObject(PyObject *p) {
        exceptTypeObject = p;
        python::register_exception_translator<MyException>( &exceptionTranslate );
    }

private:

   static PyObject  *exceptTypeObject;

    static
    void
    exceptionTranslate(const MyException &e ) {
        boost::python::object  pyExcept(e);
        PyErr_SetObject( exceptTypeObject , pyExcept.ptr());
    }
};

void myFunc() {
    throw MyException( "Something's wrong" );
}


BOOST_PYTHON_MODULE( my_module )
{
    // функция, вызываемая из python программы
    boost::python::def( "myFunc", &myFunc );

    // регистрирем тип исключения и его транслятор
    MyException::setTypeObject( 
        boost::python::class_<MyException>( "MyException", "MyException error" ).ptr()
    );
}

Вот на этом мы могли бы остановиться. Могли бы, если бы мир был совершенен. На самом деле, после чуть более тщательного тестирования, мы приходим к заключению, что работают наши исключения не совсем верно. А именно:
не работает инструкция raise MyException - вместо генерации исключения обозначенного типа генерирует питоновское исключение, общий смысл которого в двух словах сводится к тому, что тип MyException не пригоден для генерации исключений. Вторая проблема заключается в том, что при попытке сделать иерархию исключений мы сталкиваемся с тем, что неверно работает фильтрация исключений, а именно: инструкция except BaseException не ловит исключения дочерних типов. Короче говоря, имплементация наших исключений весьма далека не только от идеала, но и просто от обычных python исключений. Будем разбираться.

Итерация 3: разбираемся

Изучив внимательно текст ошибки при попытке возбудить исключение raise MyExceptionException. Кроме того, тут надо сделать небольшой ( самостоятельный ;) ) экскурс в анатомию питонов и изучить вопрос о типах и метатипах. Если в двух словах, то тип в питоне - это такой же объект и ,соответственно, может быть динамически создан вызовом конструктора у некого типа. Вот этот "некий" тип и является метатипом. Покопавшись внутрях boost::python можно придти к заключению, что все типы, которые он импортирует, создаются через один и тот же метаттип. В итоге наш MyException имеет метатип такой же, как и другие типы, импортированные из boost::python. И он не имеет родственных связей с типами питоновских исключений. А это негативно влияет на обработку исключений питоном. Наш дальнейший план каким-то образом унаследовать MyException от питоновского типа Exception. Как же это сделать то????

Итерация 4: Как же это сделать то????

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

using boost::python;

// Базовым типом у нас будет PyExc_Exception
handle<>   basedtype = handle<>(PyExc_Exception);

// Всякий объект в питоне имеет свой список свойств
dict       ob_dict;
ob_dict["__doc__"] = "MyException"

// Объект в питоне кстате может иметь много пап. Но у нас будет один - самый лучший! 
tuple      ob_bases = make_tuple( basedtype );

// Вот тут весь оргазм: 
// Py_TYPE(basedtype.get()) - вернет нам тип от объекта PyExc_Exception. Тип типа - это метатип, мы это выясняли
// object( boost::python::handle<>(Py_TYPE(basedtype.get()) ) ) - это будет у нас объект меаттипа
// Что такоей вызов SomeType( a, b, c) ? - это конструирование объекта этого типа
// Теперь собираем пазл во-едино: мы создаем новый тип, который имеет такой же метатип как и PyExc_Exception
// и является наследником PyExc_Exception
object     ob = object( boost::python::handle<>(Py_TYPE(basedtype.get()) ) )( "MyException", ob_bases, ob_dict );

// остается пристроить новорожденного в приличное место, а именно в скоуп модуля
scope().attr( "MyException" ) = ob;

Теперь остается привести все в божеский вид.

Итерация 5: божеский вид

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

template< class TExcept >
struct exceptPyType{

     static python::handle<>     pyExceptType;

};

template< class TExcept, class TBaseExcept = python::detail::not_specified >
class exception  {


public:

    exception( const std::string& className, const std::string& classDesc ) 
    {
        python::handle<>   basedtype;      

        if ( boost::is_same<TBaseExcept, python::detail::not_specified>::value )
        {
            basedtype = python::handle<>(PyExc_Exception);
        }
        else
        {
            basedtype = exceptPyType<TBaseExcept>::pyExceptType;
        }

        python::dict       ob_dict;
       
        ob_dict["__doc__"] = classDesc;

        python::tuple      ob_bases = python::make_tuple( basedtype );

        python::object     ob = python::object( python::handle<>(Py_TYPE(basedtype.get()) ) )( className, ob_bases, ob_dict );

        python::scope().attr( className.c_str() ) = ob;

        exceptPyType<TExcept>::pyExceptType = python::handle<>( ob.ptr() );

        python::register_exception_translator<TExcept>( &exceptionTranslate );
    }

    static
    void
    exceptionTranslate(const TExcept &e ) {

        python::object      exceptObj = python::object( exceptPyType<TExcept>::pyExceptType )( e.what() );

        PyErr_SetObject( exceptPyType<TExcept>::pyExceptType.get(), exceptObj.ptr());
    }

};

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

BOOST_PYTHON_MODULE( mymodule )
{
  exception<MyException>( "MyException", "description" );
  exception<MyExceptionEx,MyException>( "MyExceptionEx", "description" );
}

Заключение

Почему же нам пришлось прибегать к таким извращениям? Очевидно, что boost::python далек от совершенства )). Чего же в нем не хватает? Было бы классно, если бы существовала форма функции class_, позволяющая задать базовый класс через указатель на питоноский тип.





пятница, 16 декабря 2011 г.

Новая линейка Pykd 0.1.x доступна для тестирования

Хочу представить вам новую, кардинально переработанную версию pykd.

Страница для закачки: download

Сейчас ветка 0.1.x находится в статусе альфа и имеет ряд недоделок, которые будут постепенно устраняться. Мы очень надеемся, чтои вы найдете время протестировать новую версию и сообщить нам свое мнение о новом функционале и найденных ошибках. Это позволит быстрее довести продукт до стабильного состояния.

Основные фичи новой ветки:
1) Работа с символами через библиотеку MS DIA. Это позволило реализовать доступ к битовым полям, типам-энумераторам, работать с многомерными массивами и указателями на массивы
2) Доступ к DIA выведен в интерфейс для Python. Так что можно работать с отладочной информацией самостоятельно
3) Поддерживается работа с несколькими клиентами одновременно, т.е. можно из одного скрипта управлять отладкой нескольких приложений или систем.
4) Несколько изменен API
5) Для windbg команда !pycmd предоставляет теперь полноценную консоль python, идентичную стандартной. Для команды !py улучшен вывод информации об ошибках, в том числе синтаксических

Повторю еще раз, нам очень нужены ваши отзывы, в первую очередь по API. Так как мы отказались от полной обратной совместимости с предыдущей версией, мы сейчас имеем возможность исправить API и сделать его более понятным и удобным.

Дальнейший план развития такой:
Ветка 0.0.x больше развиваться не будет, будем только фиксить баги и, возможно, что-то потом интегрируем из 0.1.x.

Спасибо, что используете наш продукт :).

понедельник, 10 октября 2011 г.

Обработка исключений и точек останова

В pykd v. 0.0.20 появился новый полезный функционал: обработка исключений и точек останова. Возможность обработки этих событий реализована новыми методами класса debugEvent: onBreakpoint и onException. Для использования этого функционала, как и в случае с событиями загрузки/выгрузки модулей, необходимо реализовать свои вышеописанные обработчики. Сведения о произошедшем событии передаются в эти методы словарями, ключи и типы значений которых указаны в документации. Следует отметить, что для события срабатывания точки останова количество сформированных пар, может быть меньше, чем описано: если точка останова не обладает каким-либо свойством, данные не заносятся в словарь. А состав словаря, передаваемого в обработчик исключения, более стабилен: варьируется только список параметров исключения. Но даже, если у исключения нет параметров, то в словаре все равно окажется список, который будет пуст.

У pykd уже был класс bp, который позволяет ставить/снимать точку останова и назначать собственный обработчик. Этот класс так остался, так как имеет более простой интерфейс, но он был расширен новым конструктором, в который передается только целевой адрес. При установке такой точки, вместо вызова обработчика просто возвращается статус DEBUG_STATUS_BREAK.

Новый функционал debugEvent::onBreakpoint реализован не как замена, а как альтернатива существующему механизму. То есть в одном и том же скрипте можно использовать как механизм bp, так и переопределение метода debugEvent::onBreakpoint. Это было сделано для того, что бы можно было управлять исключениями и точками останова из одного класса - debugEvent.

Мы достаточно мало освещаем тот факт, что pykd не только расширение к WinDbg, но и полноценный модуль для языка python, который позволяет получить доступ к API Debug Engine. Поэтому пример использования обработки исключений будет нацелен именно на такой подход: samples\watchDog.py. В начале скрипт, если переданы аргументы командной строки, стартует указанный отлаживаемый процесс. Затем он ставит обработчик исключения и ожидает возникновения исключения. Но это так же полноценный скрипт для pykd, функционирующего как расширение WinDbg. Например, при вызове из WinDbg можно не указывать параметров, тогда скрипт будет следить за исключениями на отлаживаемом объекте.

Важной особенностью логики скрипта является пропуск исключений, для которых FirstChance == True. Это необходимо для того, что бы пропускать исключения, которые возможно будут обработаны самим отлаживаемым объектом, например если исключение произошло в блоке SEH'а.

Теперь запускаем скрипт командной строкой "python watchDog.py test.exe". Тем самым pykd от лица процесса python.exe создаст отлаживаемый процесс test.exe, который запустится на исполнение с предварительно установленным обработчиком исключений. В test.exe специально был включен следующий код:

__try { *(char *)0 = 1; } __except(EXCEPTION_EXECUTE_HANDLER) {}
*(char *)3 = 1;


Результат исполнения скрипта следующий:

*** shit happens
Exception code : EXCEPTION_ACCESS_VIOLATION
Exception flags : 0
Exception record : 0x0
Exception address : 0x40122C(00401010) test!main+0x21c | (00401250) test!Define_the_symbol__ATL_MIXED::Thank_you::Thank_you

Parameters :
0x1
0x3

eax=00000000 ebx=0018fa64 ecx=00000000 edx=00000000 esi=00401222 edi=00000000eip=774c15ee esp=0018fa50 ebp=0018ff40 iopl=0 nv up ei pl zr na pe nccs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246ntdll!ZwRaiseException+0x12:774c15ee 83c404 add esp,4

ChildEBP RetAddr Args to Child
0018ff40 004017f3 00000001 00302330 00302370 ntdll!ZwRaiseException+0x120018ff88
76ae339a 7efde000 0018ffd4 774d9ed2 test!__tmainCRTStartup+0xfb0018ff94
774d9ed2 7efde000 62b6a849 00000000 kernel32!BaseThreadInitThunk+0x120018ffd4
774d9ea5 0040184a 7efde000 ffffffff ntdll!RtlInitializeExceptionChain+0x630018ffec
00000000 0040184a 7efde000 00000000 ntdll!RtlInitializeExceptionChain+0x36

Как видно по второму параметру исключения (адрес памяти - 0x3) скрипт поймал именно необрабатываемое исключение: использования неверного указателя *(char *)3 = 1. Если включить дополнительную трассировку (например, вывод в консоль) для FirstChance == True, то можно увидеть, что onException срабатывает 3-а раза, первые 2-а из которых игнорируются, так как могут быть обработаны отлаживаемым объектом.

воскресенье, 9 октября 2011 г.

Release 0.0.20

Всем доброго дня! Спешу представить очередной релиз pykd - версия 0.0.20.

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

Управление отладчиком:


Добавлены следующие функции: breakin, attachProcess, attachKernel, debuggerPath
Наличие функции breakin позволяет написать с помощью pykd свой отладчик. А функции attachProcess и attachKernel в этом помогут ( напомню, что ранее уже были реализованы функции loadDump и createProcess ).

Функция debuggerPath возвращает полный путь к исполняемому модулю программы, использующей pykd. Если вы, к примеру, захотите модифицировать файл symsrv.ini - она может быть полезна.

В ближайшее время мы опубликуем тутор по разработке отладчика на PySide. Следите за блогом!

класс cpuReg


Дополнен такой казалось бы малозначительной деталью, как конструктор с параметром "индекс регистра". Это позволяет реализовать перечисление всех регистров CPU:
def getRegisterSet(self):
    regSet=[]
    try:
        i = 0
        while True:
            reg = pykd.cpuReg(i)
            regSet.append(reg)
            i += 1

    except pykd.BaseException:
        pass

    return regSet

класс typedVar


Добавлен конструктор, принимающий имя переменной нужного типа. При этом сам тип указывать не надо, он будет взят из отладочной информации. Т.е достаточно написать:
var = typedVar( myModule.myVar )

Это особенно удобно при работе с переменными, тип которых заранее не известен. Например, с шаблонами С++.

класс disasm


Добавлен метод assembly, позволяющий изменять код по укзанному ( по умолчанию: по текущему ) смещению:
nt = loadModule( "nt" )
dasm = disasm( nt.NtCreateFile )
dasm.assembly( "int 3" )

getProcessorType


Возвращает тип процессора. Функция вернет строку, описывающую тип физического процессора. Сравните с getProcessorMode, которая возвращает текущий режим. Вот так, к примеру, можно было бы реализовать команду !sw из стандартного расширения wow64ext:
if getProcessorType() == "X64":
  setProcessorMode( {"X64": "X86", "X86": "X64"}[ getProcessorMode() ] )

багфикс


9219: улучшена поддержка многопоточности. Функции, которые могут надолго заблокировать исполнение python программы ( такие как go() и dbgCommand() ) сохраняют состояние потока, что позволяет планировщику python выполнять код в других потоках

9518: Исправлен баг с выводом текста через команду dprint(ln). До этого текст, содержащий символ '%' выводился неправильно ( обычно, обрезался ).

9555: Метод name() класса dbgModuleClass возвращал строку, содержащую нулевой символ. В итоге при использовании данного метода вывод мог повреждаться ( обрезаться )

пятница, 29 июля 2011 г.

Release 0.0.19

Представляем ( не без затаенной гордости конечно ) очередной релиз pykd 0.0.19. Мы немного запоздали по срокам выпуска, надеемся на ваше понимание. И так, что новенького:

Доработан интерфейс для windbg


!pycmd теперь поддерживает многострочный ввод. Пробуем:
kd>!pycmd
>>>def hello():
...    print "hello"
...
>>>hello()
hello

!py и !pycmd выводят теперь traceback при возникновении исключения при исполнении. Надеемся, это сделает отладку скриптов чуть-чуть удобнее.

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

class disasm


Это дизассемблер. Конечно не полноценный, он не разбирает инструкции на операнды. Тем не менее, простые задачи ему вполне по силам. Пользоваться просто:
kd>!pycmd
>>>nt = loadModule("nt")
>>>da = disasm( nt.ZwCreateFile )
>>>print da.next()
804fd55d 8d542404        lea     edx,[esp+4]
>>>print da.next()
804fd561 9c              pushfd
>>>print da.next()
804fd562 6a08            push    8
>>>print da.next()
804fd564 e8e8f00300      call    nt!KiSystemService (8053c651)

Прочее:


setMSR - функция меняет значение MSR регистра.

классу typedVar добавили метод data, возвращающий данные в виде питоновской строки. Такую строку можно обработать например с помощью ctypes или записать в файл

Фиксы:


issue 8655
Игнорировались поля безымянных структур и объединений. Теперь - не игнорируются.

issue 8927
Если поле структуры имеет перечисляемый тип, то узнать его числовое значение нельзя. Т.е конечно можно с помощью такого трика:
val = ptrDword( structVar.enumField.getAddress() )
Хорошая новость, теперь можно писать:
val = structVar.enumField
Плохая новость: предыдущий трик перестал работать, потому что у типа long нет метода getAddress().
Но не все уж так плохо: в настоящий релиз вошел класс intBase, позволяющий включать его в операции с целыми числами. В следующей версии мы планируем переделать класс typedVar и хранить атрибуты, представляющие целые числа и указатели, в объектах-наследниках типа intBase. Это, к примеру позволит обращаться не только к членам структуры, но также и к данным, доступным по указателям. Короче говоря, планов много, будем стараться их осуществить.

В заключение хотел напомнить: Друзья, у вас все еще есть блестящая возможность поучаствовать в разработке pykd. Очень нужны: С++ кодер - для работы с ядром pykd, python кодер - для разработки тестов ( у нас есть тесты! целых два!!! ), python кодер - для разработки библиотеки, русско-англо-xxx-язычный технический писатель.

среда, 22 июня 2011 г.

Qt: "A QApplication instance already exists"

Не так давно мы публиковали пример графического плагина на основе связки Qt и PySide. Использование графического интерфейса предоставляет широчайшие возможности. К сожалению, приведенный код содержал один существенный недостаток: при повторном запуске скрипта появлялось сообщение: "A QApplication instance already exists".

Чтобы избежать этого, нужно совсем чуть-чуть переработать процедуру инициализации Qt. А именно: проверить существование синглтона QApplication c помощью вызова QCoreApplication::instance()

Таким образом, прототип расширения для windbg c графическим интерфейсом мог бы выглядеть примерно так:

from PySide.QtCore import *
from PySide.QtGui import *              

class MainForm( QDialog ):
    
    def __init__( self ):
        QDialog.__init__( self, None )            
        self.setWindowTitle("Hello")      

def main():      

    app = QCoreApplication.instance()

    if app == None:
        app = QApplication( [] )

    mainForm = MainForm()
    mainForm.show()
    exitres = app.exec_()

if __name__ == "__main__":
    main()

четверг, 26 мая 2011 г.

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

Одно из нововведений релиза pykd 0.0.18 это базовый класс для обработки событий от Debug Engine. В текущей версии реализованы только события загрузки и выгрузки модулей. Об этом было рассказано в обзоре релиза.
Реализация сделана через базовый класс debugEvent, реализация которого расположена в модуле pykd. Для обработки соответствующего события необходимо написать свой класс, в котором переопределить методы onLoadModule(...) или onUnloadModule(...), в зависимости от того, какие события необходимо обрабатывать. Входным параметром для этих методов выступает экземпляр класса загруженного модуля dbgModuleClass.
В качестве демонстрации этого функционала рассмотрим часть скрипта samples\goLoad.py:
from pykd import *

import fnmatch
import sys

class loadHandler(debugEvent):
def __init__(self, mask):
debugEvent.__init__(self)
self.mask = mask
  def onLoadModule(self, module):

if fnmatch.fnmatch( module.name(), self.mask ):
return DEBUG_STATUS_BREAK
return DEBUG_STATUS_NO_CHANGE

if __name__ == "__main__":
if len(sys.argv) == 2:
loadHandler = loadHandler( sys.argv[1] )
go()

Фактически, скрипт продолжает исполнение целевой системы, пока не будет загружен модуль, имя которого будет совпадать с указанной маской (передается первым параметром в скрипт). Передаваемая маска имени модуля может содержать wildcard'ы: символы * и ?. То есть, например, что бы при старте системы дождаться загрузки модуля beep.sys в память, нужно выполнять команду: "!py goLoad *beep*". Это может быть полезно, когда нужно, что бы отладчик остановился перед вызовом точки входа целевого модуля, но уже при загруженном целевом модуле. В этот момент можно расставлять точки останова в загруженном модуле или, например, дождаться исполнения точки входа в драйвер командой "g beep!DriverEntry"

P.S. Сейчас в планах развивать функционал фильтрации отладочных событий. Хотелось бы услышать мнение конечных пользователей о том, какие событий представляют наибольший интерес (соответственно, реализация этих событий будет более приоритетной задачи). В число событий, для которых в pykd планируется реализовать механизмы фильтрации, входят:
  • Создание и уничтожение процесса
  • Возникновение исключения
  • Создание и уничтожение нити (thread)
  • Изменение состояния отлаживаемой сесии
  • Срабатывание точки останова. pykd уже предоставляет класс bp. Но планируется так же продублировать функционал в debugEvent, что бы была возможность написать единый диспетчер событий Debug Engine.

среда, 25 мая 2011 г.

Release 0.0.18

Хочу представить очередной релиз pykd. Хотя список изменений может показаться не впечатляющим, но за этими изменениями стоит напряженная работа. Были серьезно переработаны ключевые части проекта. Но сначала приведем формальный список изменений:

Новые функции:

loadWChars - юникодная версия существующей loadChars
rdmsr - возвращает значение MSR регистра

Поддержка исключений:

BaseException - базовый класс для всех исключений
MemoryException - класс исключений для ошибок чтения памяти
TypeException - класс исключений при работе с типами

Новые классы:

typedVar - переработанная версия класса typedVarClass
typeInfo - переработанная версия класса typeClass
debugEvent - базовый класс для создания обработчиков событий отладчика

Bugs fixed:

8669 - функция typedVar (теперь это класс ) создает объект для несуществующего типа
8655 - не поддерживается работа с неименованными членами структур

Прочее:

Добавили мелкую, но очень удобную фичу для команды pycmd: теперь модуль pykd импортируется при старте расширения автоматически. Иными словами, не надо писать from pykd import *.

Теперь подробнее о ключевых моментах. Их три:

Исключения

Начиная с этого релиза мы переходим к концепции возвращения ошибок через исключения. По нашему мнению, это упрощает и делает более стройным и логичным как код скриптов на питоне, так и внутреннюю реализацию на С++. В текущем релизе исключения возвращают все функции работы с типами ( через классы typeInfo и typedVar ) и все функции работы с памятью. Предлагаю попробовать выполнить в интерпретаторе python следующие команды:
>>> typeInfo( "nt", "AAAAAAA" )
>>> typedVar( "nt", "AAAAAA", 0xDEADBEEF )
>>> typedVar( "nt", "_EPROCESS", 0xDEADBEEF )
>>> loadWChars( 0xDEADBEEF, 100 )

Динамическое определение типов

Теперь создать типизированную переменную можно, даже если нет соответствующей символьной информации. Для этого у класса typeInfo предусмотрен метод append. Работать с ним не сложно:
>>>a = typeInfo()
>>>a.append( ushort_t, "ShortField")
>>>a.append( uchar_t, "CharArray", 10 )
>>>print a
unnamed   size = 12(0xc)
 +0  unsigned short   ShortField
 +2  unsigned char[]   CharArray
Хочу обратить внимание на переменные uchar_t и ushort_t. Откуда они взялись? Для базовых типов в модуль pykd добавлены глобальные переменные с типом typeInfo. Вот их полный список:
char_t
uchar_t
short_t
ushort_t
long_t
ulong_t
int_t
uint_t
ptr_t
double_t
longlong_t
ulonglong_t

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

Обработка событий отладчика

Если вы планируете с помощью pykd написать автоотладочное средство, а может даже и полноценный отладчик, скорее всего вам понадобится получать и обрабатывать различные события: загрузку и выгрузку модулей, старт процессов и так далее. Для этого мы разработали класс debugEvent. Он содержит виртуальные функции-обработчики событий. Если вы хотите реализовать собственную обработку того или иного события, вам достаточно создать класс-наследник debugEvent и переопределить нужные вам методы-обработчики. Текущая реализация содержит только два обработчика: onLoadModule и onUnloadModule.

Проиллюстрирую-ка я все это примером, а то слишком много слов и мало дела:
class ModuleEventMgr( debugEvent ):
 
    def onLoadModule( self, module ):
        dprintln( module.name() )
        return DEBUG_STATUS_GO_HANDLED  

evnetMgr = ModuleEvent()

Данный код будет выводить имена загружаемых модулей на экран. Все просто.


На этом хотелось бы закончить обзор релиза 0.0.18. Спасибо вам за то, что внимательно прочли. Как всегда, ждем от вас вопросов, предложений, благодарности и/или кусочки ненависти - все сгодится :).

И спасибо разработчикам за их нелегкий труд.

воскресенье, 24 апреля 2011 г.

Анализ сетевых пакетов в Vista/Windows 7

Сетевые пакеты в Vista/Windows 7 описываются структурой NET_BUFFER и списками NET_BUFFER_LIST. Мы написали скрипт, который анализирует содержимое пакетов с учетом сетевых протоколов. Сам скрипт можно взять здесь: nbl.py

Использовать его просто. Установим брейкпойнт:
bp ndis!NdisFIndicateReceiveNetBufferLists

При его срабатывании (а это при наличии сетевого подключения случится очень быстро ) выполним команду:
!py nbl @rdx ( для amd64 в rdx лежит указатель на NET_BUFFER_LIST )

В результате получим что то вроде:

Length: 92 bytes
Ethernet header: OK
    Dest MAC: ff-ff-ff-ff-ff-ff
    Src MAC: 20-cf-30-70-25-e2
    Type: IPv4
IPv4 header: OK
    version: 4
    header length: 20 bytes
    total length: 78 bytes
    protocol: UDP
    TTL: 128
    Src addr: 10.244.0.50
    Dest addr: 10.244.7.255
UDP header: OK
    Src port: 137
    Dest port: 137
    Length: 58
    Checksum: 0xec8a

Поодерживается разбор заголовков Ethernet, IPv4, UDP, TCP. В будущем, возможно, добавим поддержку и других протоколов. Если кому то скрипт окажется полезен - пишите, и это будущее может стать настоящим :).

UPD1:
Eще у нас есть скрипт, отображающий внтуренние структуры NDIS ( минпорты, протоколы и.т.д ) и их взаимосвязь: ndis.py, иногда бывает полезно, надеемся, будет и вам.

пятница, 22 апреля 2011 г.

Работа с памятью

Все данные лежат в памяти, это очевидно. Ранее, мы уже рассмотрели (ссылка), как с помощью фцнкции typedVar получить доступ к содержимому переменной с учетом ее типа. Но бывают ситуации, когда мы не знаем тип, или содержимое памяти представляет собой сырой нетипизированный массив. Для работы с такими массивами предназначены следующие функции:
loadBytes
loadWords
loadDWords
loadQWords
- эти функции возвращают списки ( list ) беззнаковых целых соответствующей разрядности;

loadSignBytes
loadSignWords
loadSignDWords
loadSignQWords
- эти функции возвращают списки целых чисел со знаком.

Все функции имеют одинаковый прототип:

list  loadIntegerType( address, number, phyAddr = False )

address - адрес, по которому расположен массив
number - количество элементов массива
phyAddr - является ли адрес физическим ( не виртуальным ).

Остается привести какой-нибудь разумный пример:
from pykd import *

def dd( addr ):
    buf = loadDWords( addr, 16 )
    for i in xrange( len(buf) ):
        dprint( "%08x" % buf[i] ) 
        dprint( (i % 4 < 3) and "  " or "\n" )
Данный код выводит на экран дамп 4 байтных чисел. Можно доработать данный код и получить полный аналог команды отладчика dd.

Для обработки сырых буферов средставами станадртных или сторонних библиотек, добавлена функция loadChars. Она работает также как и loadBytes, но возвращает не список, а строку ( string ). К примеру, разбирать данные можно с помощью стандартного модуля struct:
from struct import unpack
shortField1, shortField2, longField = unpack('hhl', loadChars( addr, 8 ) )

От функции loadChars плавно перейдем к функциям работы со строками: loadCStr и loadWStr. Эту функции предназначены для работы с 0-терминированными строками. Первая для работы с ANSI-строками, вторая - с UNICODE. На правах КО обращу внимание, что работа с этими функциями небезопасна в том смысле, что наличие терминирующего нуля никем не гарантируется.

Для горячо любимых нами разработчиков системного ПО для Windows есть еще две полезных функции: loadUnicodeString и loadAnsiString. Они трактуют переданный адрес как указатель на структуру ANSI_STRING или UNICODE_STRING и, соответственно, избавляют от необходимости в ручную разбирать эти стуркутры для получения строки.

понедельник, 18 апреля 2011 г.

Release 0.0.17

Представляем очередной релиз pykd: 0.0.17. И так что нового:

Документация

Прежде всего, мы сделали нечеловеческое усилие над собой и добавили документацию для всех функций и классов. Правда документация пока очень-очень-очень скупая, но мы обещаем над этим поработать. А чтобы продемонстрировать, как это работает, мы написали скрипт help.py ( его можно взять тут ) выводящий интерактивный хелп в windbg. Пробуем:
0. загружаем плагин .load pykd.pyd
1. нажимаем ctrl+N
2. в строке ввода набираем !py help
4. наслаждаемся.

Добавили в API несколько функций:

bool isDumpAnalyzing()
Позволяет определить, анализируется ли в данным момент дамп или производится отладка живой системы. Если в скрипте используются функции управления отладчиком ( go, управления точками останова и.т.д ), имеет смысл вставить данный вызов и уберечь пользователя от неизбежных ошибок при попытке исполнения скрипта во время анализа дампа

bool isWindbgExt()
Позволяет определить, исполняется ли скрипт внутри WinDbg или вызван процессом интерпретатором python. Ранее для этого использовался неявный метод и вызов isSesionStart. Теперь isSessionStart удалена, этот вызов следует заменить в коде на isWindbgExt.

string loadChars(address, number)
В стандартной библиотеке python существует ряд средств для работы с массивами "сырых" данных. Все они для представления буфера данных используют класс string. Для того, чтобы не заниматься преобразованием списка байт, которые возвращает функция loadBytes мы добавили функцию loadChars.
Ее удобно использовать, например, вместе с модулем struct:
>>> from struct import unpack
>>> shortField1, shortField2, longField = unpack('hhl', loadChars( addr, 8 ) )

Методы checksum() and timestamp() класса dbgModuleClass
Из названия методов должно быть ясно, их назначение. Если Вам придет в голову написать собственный загрузчик символов ( почему бы и нет? ), то они вам могут пригодится.

callbacks for bp class ( breakpoint )
Это, можно сказать, изюминка данного релиза. До сих пор, пользоваться точками останова ( breakpoint ) из pykd было не слишком удобно. Теперь при создании новой точки останова можно задать функцию обратного вызова и обрабатывать из в событийно-ориентированном стиле.

Пример:
from pykd import *

def bpCallback():

    if is64bitSystem():
        objAttr = typedVar( "ntdll", "_OBJECT_ATTRIBUTES", reg("r8") ) 
    else:
        objAttr = typedVar( "ntdll", "_OBJECT_ATTRIBUTES", ptrPtr(reg("esp") + 0xC) )  

    name = loadUnicodeString( objAttr.ObjectName )

    dprintln( "NtCreateFile: " + name )

    return DEBUG_STATUS_GO_HANDLED

if not isWindbgExt():
    startProcess("notepad.exe")

if not isDumpAnalyzing() and not isKernelDebugging():        
    nt = loadModule("ntdll")
    b1 = bp( nt.NtCreateFile, bpCallback )   
    # wait for user break, exceptions or process exit
    go()
    dprintln( "stopped" )    
else:
    dprintln( "The debugger must be connected to live usermode process" )

typedVarList
При работе со связанными списками в (ядре) Windows часто используется структура LIST_ENTRY. Изначально функция typedVarList была ориентирована на такие списки. Однако, гораздо чаще используется другой подход для создания связанных списков: когда структура содержит указатель ( Next ) на начало следующей структуры. Теперь, typedVarList поддерживает оба случая. Какой вариант связного списка использовать, определяется по типу поля-указателя на следующий элемент.

Новый инсталлятор

К релизу 0.0.17 мы существенно переработали инсталятор:
  • Новый дизайн
  • Добавили деинсталлятор ( вдруг кому-нибудь понадобится? )
  • Добавили примеры в комплект иснталлятора
  • Выбор дополнительных компонентов по желанию пользователя ( VCRedist, python, примеры )

Bug fixed

8470 ( python.exe crashes after first pykd call )
8614 ( go() works incorrectly while process is terminating )
8499 ( !py command crashs with wrong script's path )
8578 ( findModule returns None for WOW64 process )
8493 ( loadPtrs returns dict instead list )
8469 ( dprintln does not work in console mode )

Надеемся, релиз 0.0.17 сделает вашу работу с pykd более приятной и плодотворной!

воскресенье, 3 апреля 2011 г.

HOWTO

1. Как получить указатель на PEB ?
peb = typedVar( "ntdll", "_PEB", getCurrentProcess() ) 
2. Какому модулю принадлежит адрес?
module = findModule( addr )
print module==None and "module not found" or module.name() 
3. Как вывести текст с DML разметкой?
dprintln( "<u>Hello <b>World</b></u>", True )
4. Как передать в скрипт значение регистра?

Также, как и в любую команду windbg:
!py myscript @eax
Чтобы это работало, в скрипте нужно предусмотреть вычисление выражений:
param1 = expr( sys.argv[1] ) 
5. Как выполнить команду отладчика и получить ее результат?
print dbgCommand("!for_each_module")
Результат команды можно обработать к примеру с помощью RE.

6. Как получить размер структуры?

Проще всего так:
print hex( sizeof( "nt", "_ERESOURCE") ) 
Можно с через класс, предсталяющий информацию о типе ( typeClass):
print hex(getTypeClass("nt", "_ERESOURCE").sizeof())
Справедливо и для объектов дочернего класса typedVarClass, например:
print hex(typedVar("nt", "_ERESOURCE", getOffset("nt", "CmpRegistryLock")).sizeof())

7. Как получить смещение поля структры?

Через класс, предсталяющий информацию о типе ( typeClass):
print hex(getTypeClass("nt", "_ETHREAD").Tcb.ThreadListEntry.offset())
Справедливо и для объектов дочернего класса typedVarClass, например:
print hex(typedVar("nt", "_ETHREAD", getImplicitThread()).Tcb.ThreadListEntry.offset()) 
Но сравните результат выполнения двух команд:
print hex(getTypeClass("nt", "_ETHREAD").Tcb.ThreadListEntry.Flink.offset())
print hex(typedVar("nt", "_ETHREAD", getImplicitThread()).Tcb.ThreadListEntry.Flink.
offset()) 
Первая выводит ожидаемое значение, вторая - возвращает ошибку. Дело в том, что аттрибут 'Flink' в объекте typedVarClass имеет встроенный тип python-а long и, соответственно, не имеет метода offset ( а также, getAddress() )

8. Есть ли в pykd команда help или другой способ получить справку?

В pykd - нет. Но такая возможность встроена в сам python! Пробуем:

>!pycmd
>>> help()
help>pykd.typedVar

9. Как отлаживать скрипт?

Небольшие скрипты можно отладить прямо в windbg ( или в консоле ). Для этого используем встроенный отладчик pdb:

>!py pdb my_script.py
(pdb) s
-> import pykd
(pdb)
В более сложных случаях можно использовать любой отладчик python кода. Например, eсlipse или Visual Studio 2010 с плагином pytools

10. Как сравнивать адреса?

Вопрос не так прост. DbgEng и все функции pykd возращают адреса в 64 битном формате, даже для x86 платформы. Адреса, находящиеся в верхней половине адресного пространства, расширяются:
0x804f8925 -> 0xFFFFFFFF'804f8925
В следствии этого, в коде могут возникнуть непрогнозируемые ошибки. Рассмотрим пример:
if reg("eax")==getOffset( "nt", "NtCreateFile")
    print "eax point to NtCreateFile"
На x86 платформе данный код будет работать неправильно. Переменная a будет трактоваться как 32 битное целое, а функция getOffset вернет адрес, расширенный до 64 бит. И даже если регистр eax действительно указывает на NtCreateFile, проверка на равенство не сработает. Чтобы избежать этого, скастим значение регистра к указателю с помощью функции addr64:
if addr64(reg("eax"))==getOffset("nt","NtCreateFile")
    print "eax point to NtCReateFile" 
 
Надеюсь, эта информация поможет начать работу с pykd. На все вопросы мы с удовольствием ответим в этом блоге или по электронной почте: pykd@hotmail.com.