понедельник, 21 марта 2011 г.

Автоматическая установка pykd

Установить pykd вручную не сложно. Однако есть как минимум один подводный камень: нужно также установить дистрибутив vcredist нужной версии. Но все заботы может взять на себя автоматический инсталлятор. Он найдет python нужной версии, а если его нет - скачает, установит рантайм библиотеки и, конечно, сэкономит вам время.

http://perfect-coding.blogspot.com/2011/03/pykd-python-windbg-extension.html

пятница, 18 марта 2011 г.

PySide+pykd: используем Qt в питоновских скриптах к WinDbg

pykd, как расширении к WinDbg, дает не только возможность использования мощного и гибкого языка Python, но и уже готовых сторонних проектов. Далее пойдет речь о PySide - библиотеке интеграции инструментария Qt в Python. Возьмем простой пример: нужно просмотреть статистику о процессах системы, в частноти размер виртуального адресного пространства и счетчики операций ввода/вывода. Не буду рассказывать об установке PySide, так как на официальной странице загрузок для Windows можно скачать инсталлятор. Набросаем небольшой скрипт, который средствами pykd получает и анализирует список процессов, а затем использует PySide для визуализации собранных данных в виде таблицы в GUI-окне:
import sys
from pykd import *
from PySide import QtCore, QtGui

COL_PID = 0
COL_PRC_NAME = 1
COL_VSIZE = 2
COL_READ = 3
COL_WRITE = 4
COL_OTHER = 5

if __name__ == '__main__':

app = QtGui.QApplication(sys.argv)

nt = loadModule( "nt" )
lstProcesses = typedVarList(nt.PsActiveProcessHead, "nt", "_EPROCESS", "ActiveProcessLinks")

countOfProcesses = len(lstProcesses)
if (countOfProcesses == 0):
print "Build process list failed"
sys.exit()

model = QtGui.QStandardItemModel(countOfProcesses, COL_OTHER+1)
model.setHeaderData(COL_PID, QtCore.Qt.Horizontal, "PID")
model.setHeaderData(COL_PRC_NAME, QtCore.Qt.Horizontal, "Image")
model.setHeaderData(COL_VSIZE, QtCore.Qt.Horizontal, "VirtualSize")
model.setHeaderData(COL_READ, QtCore.Qt.Horizontal, "Read")
model.setHeaderData(COL_WRITE, QtCore.Qt.Horizontal, "Write")
model.setHeaderData(COL_OTHER, QtCore.Qt.Horizontal, "Other")

tableView = QtGui.QTableView()
tableView.setModel(model)

for row in range(countOfProcesses):
process = lstProcesses[row]

index_ = model.index(row, COL_PID, QtCore.QModelIndex())
model.setData(index_, process.UniqueProcessId)

index_ = model.index(row, COL_PRC_NAME, QtCore.QModelIndex())
model.setData(index_, "".join([chr(i) for i in process.ImageFileName.values()]))

index_ = model.index(row, COL_VSIZE, QtCore.QModelIndex())
model.setData(index_, process.VirtualSize)

index_ = model.index(row, COL_READ, QtCore.QModelIndex())
model.setData(index_, process.ReadOperationCount.QuadPart)

index_ = model.index(row, COL_WRITE, QtCore.QModelIndex())
model.setData(index_, process.WriteOperationCount.QuadPart)

index_ = model.index(row, COL_OTHER, QtCore.QModelIndex())
model.setData(index_, process.OtherOperationCount.QuadPart)

tableView.resizeColumnsToContents()

widthWidget = 0
for col in range(COL_OTHER+1): widthWidget += tableView.columnWidth(col)
widthWidget += 50

tableView.setSortingEnabled(True)
tableView.setWindowTitle("Processes table:")
tableView.resize(widthWidget, 500)
tableView.show()

app.exec_()
Теперь исполним его в WinDbg. После того, как скрипт построит список процессов, мы увидим окно результатов. Оно не является модальным по отношению к основному окну WinDbg, но отладчик терпеливо ждет завершения работы скрипта (в нашем случае - закрытия окна):

Конечно, можно было бы выдать эту информацию в текстовой форме. Но GUI таблица, например, позволяет одним кликом поменять колонку сортировки и можно наблюдать какой процесс активно более активно читает, а какой пишет.
Еще один приятный момент использования PySide - лицензия LGPL, позволяющая использовать и распространять библиотеку в любых проектах, включая коммерческие.

среда, 16 марта 2011 г.

Исследуем ASLR

Сегодня неожиданно возник вопрос: одинаков ли адрес загрузки ntdll.dll во всех процессах. Поспорив немного, вопрос разбили на два: влияет ли ASLR на ntdll.dll и как, собственно, работает ASLR: отличаются ли адреса загрузки системных dll в разных процессах. Поскольку практика - мерило истинности, быстренько написали скрипт на питоне и все выяснили. Сам скрипт был написан очень быстро ( по сути, скопипастили пример proclist.py и слегка его модифицировали ). Вот быстрота написания скрипта и сподвигла меня написать этот пост. А заодно хочется продемонстрировать некоторые приемы работы с pykd.

Вот сам код:

import sys
from pykd import *

def moduleBase():

nt = loadModule( "nt" )

processList = typedVarList( nt.PsActiveProcessHead, "nt", "_EPROCESS", "ActiveProcessLinks" )

for process in processList:

dbgCommand( ".process /p %x" % process.getAddress() )
dbgCommand( ".reload /user" )

print "process %x " % process.getAddress(), "".join( [ chr(i) for i in process.ImageFileName.values() ] )

ntdll = loadModule("ntdll")
if ntdll != None: print "\tntdll: %x" % ntdll.begin()

kernel32 = loadModule("kernel32")
if kernel32 != None: print "\tkernel32: %x" % kernel32.begin()

if __name__ == "__main__":

moduleBase()

Запустив данный скрипт в отладчике ядра, мы получим что-то вроде:
process ffffffff859fbbb0   System
process ffffffff8bbf5460   smss.exe
    ntdll: 77d50000
    kernel32: 76b40000
process ffffffff81f6f6a8   csrss.exe
    ntdll: 77d50000
    kernel32: 76b40000
process ffffffff892435a8   wininit.exe
    ntdll: 77d50000
    kernel32: 76b40000
process ffffffff89269b98   csrss.exe
    ntdll: 77d50000
    kernel32: 76b40000
.......
и ответы на свои вопросы. Но не совсем сразу. Перезапустим систему и выполним скрипт еще раз - значения будет другие. Вот теперь уже все ясно. ASLR меняет адреса загрузки при каждом ребуте, в пределах одной загрузки адреса во всех процессах одинаковы.

Хочу обратить внимание на использование функции dbgCommand. Она позволяет выполнять обычные команды windbg. И если какой-то функционал не реализован в pykd, его можно "позаимствовать".

В данном случае, мы позаимстовали команду .process c ключиком /p. Без этого ключика можно было бы использовать функцию setCurrentProcess. Но к сожалению, данная функция не включает неявную трансляцию адресов. В результате, мы не можем получить актуальный список модулей пользовательского режима для каждого процесса. Тут то нам на помощь и  пришла спасительная  функция dbgCommand.

пятница, 4 марта 2011 г.

Release 0.0.16

Хочу представить очередной релиз проекта pykd. Он примечателен тем, что над ним активно работало несколько участников и результат: количество внесенных нововведений, исправлений и улучшений значительно увеличилось. Хочу поблагодарить всех участников проекта и выразить надежду, что далее их работа в команде будет не менее плодотворной. И так, что же мы наделали:

Исправили ряд багов:

#8336: в некоторых ( довольно редких ) случаях функция typedVar возвращала переменную не того типа. Баг был в системе кеширования символьной информации, сделанной для ускорения работы с типизированными переменными

#8458: после завершения отладочной сессии и открытии новой весь вывод в консоль windbg удваивался. 

#8467: при отладке х86 платформы функция typedVarList входила в бесконечный цикл при определенных значениях адреса начала списка.

Внесли ряд изменений в API:

семейство функций loadBytes, loadDWords и.т.д

Ранее, эта функции возвращали инфорамцию в виде питоновского dictinory: {0 : val1, 1 : val2, ...}.
Учитывая, что в данном случае ключ - это просто индекс в массиве, dictionary заменили на list.

typedVar
Если в функцию передать не корректный адрес ( в том числе 0x0 ), она вернет None. Это удобно при отладке.

loadDump
Функция возвращает теперь булевское значение. True - дамп загружен успешно, False - произошла ошибка.

createSession
Функция объявлена устаревшей и не желательной к использованию. Теперь функция loadDump ( и новая функция startProcess ), сами создадут сессию при необходимости ( когда скрипт исполняется в отдельном процессе python, а не в windbg ).


И наконец, что же нового:

isValid( addr )
Функция проверяет валидность адреса.

dbgModuleClass.image() и dbgModuleClass.pdb()
Метода класса возвращают пути к бинарному файлу и файлу с символьной информацией ( если они есть ) в локальном хранилище символов. Хочу отметить, что метод image() вернет полный путь, его можно использовать для открытия файла.

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

getTypeClass( moduleName, symbolName )
Возвращает объект типа typeClass

addSynSymbol( addr, size, name )
delAllSynSymbols()

delSynSymbols( addr )
delSyntheticSymbolsMask( moduleName, symbolName )
Функции для работы с т.н синтетическими символами - т.е символами объявленными пользователем динамически во время работы. О работе с синтетическими символами мы уже писали ранее. Хочу отметить, что pykd сохраняет установленные символы даже если пользователь сделал .reload для модуля.

Добавлен метод __str__  для всех классов, экспортируемых в python. В результате, работа с ними будет удобнее: можно написать print и получить более полезную информацию, чем просто адрес объекта.

startProcess( commandLine )
Позволяет начать отладку пользовательского процесса ( аналог команды отладчика .create ). Теперь можно из питона отлаживать программы, примерно так:
>>> startProcess( "myProgram.exe" )
>>> myProgram = loadModule( "myProgram")
>>> b1 = bp( myProgram.myFaultRoutine )
>>> go()
>>> trace()
>>> print reg("eax")
Конечно, пока о реальной отладке кода речь не идет. Но, возможно, в будущем на базе pykd можно будет сделать неплохую альтернативу windbg.


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

Синтетические символы: делаем дизассемблер WinDbg дружелюбнее

Многие не любят использовать WinDbg для отладки "чужих" модулей, (т.е. тех, к которым нет символов) из-за его не самого дружелюбного дизассемблера. Простой пример это отображение импортов. Например, имеем небольшую функцию:
0:000> uf 00401090
00401090 51 push ecx
00401091 56 push esi
00401092 8b742410 mov esi,dword ptr [esp+10h]
00401096 6a00 push 0
00401098 6a00 push 0
0040109a 8d44240c lea eax,[esp+0Ch]
0040109e 50 push eax
0040109f 6800040000 push 400h
004010a4 56 push esi
004010a5 6a00 push 0
004010a7 6800110000 push 1100h
004010ac ff1510c04000 call dword ptr [image+0xc010 (0040c010)]
004010b2 8b4c2404 mov ecx,dword ptr [esp+4]
004010b6 8b54240c mov edx,dword ptr [esp+0Ch]
004010ba 51 push ecx
004010bb 56 push esi
004010bc 52 push edx
004010bd 6824c24000 push offset image+0xc224 (0040c224)
004010c2 6a10 push 10h
004010c4 68fcc14000 push offset image+0xc1fc (0040c1fc)
004010c9 e832ffffff call image+0x1000 (00401000)
004010ce 8b44241c mov eax,dword ptr [esp+1Ch]
004010d2 83c418 add esp,18h
004010d5 50 push eax
004010d6 ff1514c04000 call dword ptr [image+0xc014 (0040c014)]
004010dc 5e pop esi
004010dd 59 pop ecx
004010de c3 ret

Так мы ее видим в статике дизассемблера WinDbg. Но стоит дойти до адреса 004010ac, как WinDbg вычислит значение по адресу 0040c010 и выведет следующую строку:
call dword ptr [image+0xc010 (0040c010) ds:0023:0040c010={kernel32!FormatMessageW (77e4f831)]
На самом деле, по адресу 0040c010 расположен элемент IAT (директория импортов PE). При этом логика WinDbg ясна: значение по адресу 0040c010 может измениться, поэтому он и не вычисляет значение содержимого IAT при статическом дизассемблировании. Но если мы уверены, что содержимое таблицы импорта уже сформировано, то было бы неплохо добавить символы на обращения по этим адресам.
Для подобного рода задач в Debugger Engine расширений WinDbg введено понятие синтетических символов (Synthetic Symbols). Фактически, это символ, добавленный пользователем по заданному виртуальному адресу. Синтетический символ, как и любой другой символ модуля, имеет атрибуты имени и размера. Смысл атрибута имени очевиден, а размер символа нужен для формирования символов вида: module!SymName+33. Важная особенность: при перезагрузке символов для модуля (reload), все добавленные синтетические символы удаляются.
Для добавления синтетического имени в расширении pykd есть 2-а пути:
  • Функция addSynSymbol(адрес_символа, размер_символа, имя_символа)
  • Метод addSynSymbol(смещение_символа, размер_символа, имя_символа) класса dbgModuleClass
Добавление синтетического символа через метод addSynSymbol касса dbgModuleClass отличается только тем, что в качестве адреса необходимо передать смещение относительно начала модуля.
А теперь вернемся к примеру функции, дизассемблерный листинг которой был приведен ранее. Для демонстрации работы запустим скрипт ~\samples\synimp.py, который принимает на вход адрес внутри модуля. Скрипт, для указанного модуля, добавляет импорты, как синтетические символы с префиксом "_imp_". После работы скрипта функция выглядит намного понятнее и о ее назначении можно догадаться без трассировки:
0:000> uf 00401090
00401090 51 push ecx
00401091 56 push esi
00401092 8b742410 mov esi,dword ptr [esp+10h]
00401096 6a00 push 0
00401098 6a00 push 0
0040109a 8d44240c lea eax,[esp+0Ch]
0040109e 50 push eax
0040109f 6800040000 push 400h
004010a4 56 push esi
004010a5 6a00 push 0
004010a7 6800110000 push 1100h
004010ac ff1510c04000 call dword ptr [image!_imp_kernel32!FormatMessageW (0040c010)]
004010b2 8b4c2404 mov ecx,dword ptr [esp+4]
004010b6 8b54240c mov edx,dword ptr [esp+0Ch]
004010ba 51 push ecx
004010bb 56 push esi
004010bc 52 push edx
004010bd 6824c24000 push offset image+0xc224 (0040c224)
004010c2 6a10 push 10h
004010c4 68fcc14000 push offset image+0xc1fc (0040c1fc)
004010c9 e832ffffff call image+0x1000 (00401000)
004010ce 8b44241c mov eax,dword ptr [esp+1Ch]
004010d2 83c418 add esp,18h
004010d5 50 push eax
004010d6 ff1514c04000 call dword ptr [image!_imp_kernel32!LocalFree (0040c014)]
004010dc 5e pop esi
004010dd 59 pop ecx
004010de c3 ret

Стоит обратить внимание, что функционал синтетических символов будет включен в сборку pykd с версии 0.0.16

воскресенье, 6 февраля 2011 г.

Release 0.0.15

Вышел очередной релиз pykd.

Исправили ряд мелких и не очень багов:

#8229 - При вызове функции loadModule для модулей отличных от nt появлялось сообщение об ошибке. Теперь - все работает четко. 
#8236 - Функции вывода текста ( dprint/dprintln ) не работали с UNICODE. В результате, приходилось писать:
dprint( str( loadUnicodeString( strAddr ) )
Теперь str - можно ( и нужно ) опустить.
#8239 - ф. ptrSignByte возвращала строку, а не число со знаком, что естсетвенно приводило к неправильной работе скриптов.

Добавили следующие функции:
locals()
Возвращает коллекцию локальных переменных в текущей области видимости. Применять можно при отладке, например так:
a = locals().a
while a > 10:
trace()
a = locals().a

Также, локальные переменные можно посмотреть для любого фрейма в коллекции, которую возвращает функция getCurrentStack. Примерно так:
frames = getCurrentStack()
for f in frames: dprintln( f.locals )  
typedVarArray( offset, moduleName, symbolName, arraySize )
Возвращает коллекцию элементов типа moduleName!symbolName, которые располагались в памяти в виде массива размерностью arraySize. Использовать можно примерно так:

nt = loadModule("nt")
poolVector = typedVarArray( nt.PoolVector, "nt", "_POOL_DESCRIPTOR*", 2 )

dprintln( "non paged pool: %x" % poolVector[0] )
dprintln( "paged pool: %x" % poolVector[1] )


четверг, 3 февраля 2011 г.

Сборка pykd вручную.

Без лишней воды, по пунктам:

1. Забираем исходники

2. Настраиваем boost::python 
2.1 Переменная окружения BOOST_ROOT
Должна указывать соответственно на каталог, где лежит используемая версия boost.
У меня это:  С:\lib\boost_1_40_0
2.2 Сборка boost::python
В настройках проекта забиты следующие пути к собранным библиотекам:
$(BOOST_ROOT)\stage - для х86 сборки
$(BOOST_ROOT)\stage64 - для х64 сборки
Чтобы собрать соответствующие библиотеки, нужно выполнить следующие команды, установив текущую директорию в $(BOOST_ROOT)
Для  x86:
bjam --stagedir=stage --with-python stage
 И для x64:
bjam address-model=64 --stagedir=stage64 --with-python stage
Возможно, вам понадобится сначала установить bjam  ( ссылка )
2.3 Указание версии python
Если у вас на машине есть несколько версий python и вы хотите явно указать, какую использовать, необходимо отредактировать файл user-config.jam и добавить в него строчки, к примеру, такие:
using python : 2.6 ;
using python : 2.7 ;
Теперь можно собрать библиотеки явно указав версию python
Для  x86:
bjam --stagedir=stage --with-python stage python=2.7
И для x64:
bjam address-model=64 --stagedir=stage64 --with-python stage python=2.7
Тут можно подробнее почитать:
http://www.boost.org/doc/libs/1_45_0/libs/python/doc/building.html

3. Настройка путей к python
Для сборки понадобятся заголовочные файлы и библиотека экспорта от соответствующей версии python. Для указания путей используется переменная окружения PYTHON_ROOT, которая должна указывать на каталог установки Python. Если при сборки boost::python была задана конкртная версия python, то перемнная PYTHON_ROOT должна ссылаться на инсталляцию python соответствующей версии.
К примеру, у меня на машине переменная PYTHON_ROOT = C:\Python26.  И установлены две версии ( x86 и x64) python:
C:\Python26\x86\ 
C:\Python26\x64\

4. Настройка путей к DBG SDK
Пути к DBG SDK  задаются через переменную окружения  DBG_SDK_ROOT. Сам DBG SDK находится в каталоге установки windbg.