понедельник, 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.