Пользоваться просто:
2: kd> !py ctlcode 0x1401c8 Device type: FILE_DEVICE_NETWORK_FILE_SYSTEM Function: 0x72(114) Method: BUFFERED Access: FILE_ANY_ACCESSВзять можно здесь.
2: kd> !py ctlcode 0x1401c8 Device type: FILE_DEVICE_NETWORK_FILE_SYSTEM Function: 0x72(114) Method: BUFFERED Access: FILE_ANY_ACCESSВзять можно здесь.
Начиная с 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 ):
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.
__try { *(char *)0 = 1; } __except(EXCEPTION_EXECUTE_HANDLER) {}
*(char *)3 = 1;
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. Как передать в скрипт значение регистра?
param1 = expr( sys.argv[1] )5. Как выполнить команду отладчика и получить ее результат?
print dbgCommand("!for_each_module")Результат команды можно обработать к примеру с помощью RE.
print hex( sizeof( "nt", "_ERESOURCE") )Можно с через класс, предсталяющий информацию о типе ( typeClass):
print hex(getTypeClass("nt", "_ERESOURCE").sizeof())Справедливо и для объектов дочернего класса typedVarClass, например:
print hex(typedVar("nt", "_ERESOURCE", getOffset("nt", "CmpRegistryLock")).sizeof())
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() )
>!pycmd
>>> help()
help>pykd.typedVar
>!py pdb my_script.py
(pdb) s
-> import pykd
(pdb)
В более сложных случаях можно использовать любой отладчик python кода. Например, eсlipse или Visual Studio 2010 с плагином pytoolsif 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. import sysТеперь исполним его в WinDbg. После того, как скрипт построит список процессов, мы увидим окно результатов. Оно не является модальным по отношению к основному окну WinDbg, но отладчик терпеливо ждет завершения работы скрипта (в нашем случае - закрытия окна):
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_()
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 меняет адреса загрузки при каждом ребуте, в пределах одной загрузки адреса во всех процессах одинаковы.