В природе существует немало модулей, позволяющих исполнять python’вские скрипты в WinDbg. Зачем? Для автоматизации процесса отладки с необходимыми возможностями использования логики. Конечно, для простых if-else/for конструкций можно использовать встроенный язык WinDbg, но если нужно чуть больше уже начинаются проблемы.
В качестве питоновского движка я предлагаю использовать pykd.pyd. Это развивающееся расширение к WinDbg с открытым исходным кодом, использующее boost. Для того, что бы начать работу нужно немного:
- установить WinDbg: с сайта Microsoft (в составе SDK или WDK). Или можно воспользоваться прямыми ссылками
- установить Python 2.6.x
- скачать сам pykd. Из архива необходимо выбрать pykd.pyd соответствующий сборке WinDbg и скопировать его в директорию расширений, например winext
- перед выполнением скрипта нужно загрузить расширение командой !load pykd.pyd
На этом, пожалуй, приготовления закончены. Теперь немного теории о том, что именно будем искать и каким образом. В качестве цели я выбрал движок phide2, опубликованный в 8-ом номере 29A (в архиве его можно найти ~\Utilities\29A-8.011\src\). В частности, будем тестировать драйвер hidecmd, который при загрузке выкашивает себя из списка загруженных модулей (функция HideDriver) и скрывает все процессы cmd.exe: как работающие в настоящее время, так и те, что будут запущены. После выгрузки драйвера все новосозданные cmd.exe будут видны в Task Manager’е. Нас интересует только функционал скрытия процесса. Если не вникать в детали поиска не экспортируемого символа ядра, то способ скрытия процесса достаточно простой: процесс удаляется из списка nt!PsActiveProcessHead. Это гарантирует, что при перечислении процессов функциями ядра, процесс никогда не будет найден. Плюс такого подхода в том, что список nt!PsActiveProcessHead используется только для перечисления процессов и удаление элемента из него никак не отражается на работоспособности системы или самого процесса. В целом, движок phide2 это много больше, но сейчас рассматривается именно эта его функциональность
Я уже касался не экспортируемой переменной nt!PspCidTable в статье про описатели Windows. В нашем случае это будет эталонное хранилище всех процессов работающей системы. nt!PspCidTable это таблица описателей, хранящая созданные объекты процессов и нитей. Если уничтожить запись о процессе в таблице nt!PspCidTable, то будет невозможно получить процесс по его PID’у. Например функция PsLookupProcessByProcessId вернет статус STATUS_INVALID_PARAMETER. Это чревато ошибками при работе процесса, поэтому метод скрытия процесса унчтожением записи в nt!PspCidTable будет не универсальным.
Для разбора таблицы описателей будем использовать функцию ntobj.getListByHandleTable(pHandleTable, pType=0, bContainHeaders=True), которая реализована на python’е в модуле ntobj.py.
Формат организации таблицы описателей достаточно прост и описывается структурой nt!_HANDLE_TABLE. Элемент таблицы описывается структурой nt!_HANDLE_TABLE_ENTRY. Для перечисления описателей в таблице ключевыми полями структуры nt!_HANDLE_TABLE являются:
- .TableCode - указатель на содержимое таблицы. Важно, что двух в младших битах этого поля содержится численное значение уровня вложенности таблицы описателей
- .NextHandleNeedingPool - максимальный индекс элемента таблицы описателей. Фактически, количество элементов таблицы, умноженное на HANDLE_VALUE_INC(== 4)
Обработав эти два поля, мы получаем три параметра, необходимых для перечисления параметра: указатель на содержимое, количество элементов и уровень вложенности таблицы. Уровень вложенности таблицы может быть 0, 1 или 2. Если уровень вложенности 0, то содержимое таблицы трактуется как массив структур nt!_HANDLE_TABLE_ENTRY. Если уровень 1, то содержимое является массивом указателей на массивы уровня 0. И, соответственно, если уровень 2, то содержимое это массив указателей на таблицы уровня 1. Детали можно подсмотреть в функции nt!ExpLookupHandleTableEntry, реализация которой есть в файле WRK: ~\base\ntos\ex\handle.c. Небольшая тонкость заключается в том, что обычные таблицы описателей (nt!_EPROCESS.ObjectTable) содержат указатели на заголовок объекта - nt!_OBJECT_HEADER . A таблица nt!PspCidTable содержит указатели на сами объекты.
А теперь перейдем к реализации метода детекта скрытых процессов. Тут все достаточно просто: формируем список объектов nt!PsActiveProcessHead. Затем формируем список из объектов таблицы nt!PspCidTable, имеющих тип nt!PsProcessType. Найдя элемент из второго списка, не содержащегося в первом мы детектируем скрытый процесс:
# build list from PsActiveProcessHead pActivePrcList = getOffset("nt", "PsActiveProcessHead") lstTypedActiveProcesses = typedVarList(pActivePrcList, "nt", "_EPROCESS", "ActiveProcessLinks") lstActiveProcesses = [process.getAddress() for process in lstTypedActiveProcesses] # build list from PspCidTable pCidTable = ptrPtr(getOffset("nt", "PspCidTable")) pProcessType = ptrPtr(getOffset("nt", "PsProcessType")) lstProcessTable = ntobj.getListByHandleTable(pCidTable, pProcessType, False) # compare lists and print result founded = 0 for processFromTable in lstProcessTable: if (0 == lstActiveProcesses.count(processFromTable)): dprintln("!process 0x%X removed from PsActiveProcessHead" % processFromTable) founded += 1 resout = "checked %u processes" % len(lstProcessTable) dprintln(resout + (", %u hidden" % founded if (0 != founded) else ", hidden not found"))
Описанные выше скрипты можно скачать одним архивом
ΞρεΤΙκ