Показаны сообщения с ярлыком samples. Показать все сообщения
Показаны сообщения с ярлыком samples. Показать все сообщения

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

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

воскресенье, 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.

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