понедельник, 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, иногда бывает полезно, надеемся, будет и вам.