пятница, 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.

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