EreTIk's Box » Cтатьи, исходники » Поиск скрытых процессов python’вским скриптом в WinDbg


В природе существует немало модулей, позволяющих исполнять 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"))
                



Описанные выше скрипты можно скачать одним архивом


ΞρεΤΙκ