Python: ListDlls и Handles

Приятно, когда твои труды кого-то способны заинтересовать, но печально, когда твой пост может оказаться для тебя последним, — если ваш покорный слуга в ближайшее время перестанет делиться своими мыслями и исследованиями, можете быть уверены: угрожавшие физической расправой привели свои слова в исполнение. Ну, а пока я еще жив, то и не будем о грустном.

Если посмотреть на большинство third party утилит критически, окажется что стоящих из них единицы, однако это вовсе не мешает им обретать своих пользователей в том числе и среди системных администраторов. Сисадмины очень любят делать упор на то, дескать они ленивы, но это скорее более самоирония, нежели правда, хотя если учесть, что системное администрирование уже немыслимо без навыков программирования, все же доля правды в этом есть. Имея в своем распоряжении, скажем, всего лишь интерпретатор Python, можно вполне всего за пять-десять минут написать аналог какой-нибудь утилиты, что оказывается несомненным плюсом в случае, когда ошибки в последней не правятся годами, а помимо этого хотелось бы что-то исключить\дополнить из\в ее функционал[а|е]. Хотелось бы, например, подстроить вывод ListDlls и Handle (утилиты из набора Sysinternals Suite) под себя, и здесь видется два варианта решения: либо писать сценарий-обертку, перехватывающий и затем переопределяющий вывод, либо написать все самому с нуля. Последнее многие исключают по, в общем-то, банальной причине, мол, решение требуется здесь и сейчас (хотя такое тоже бывает), но, как уже было сказано выше, сисадмин без навыков программирования таковым уже не считается — нынешнии реалии.
Впрочем, к чему все эти разговоры о системных администраторах? В провинциях —, а это выражено остро главным образом там, — при поступлении на службу малого бизнеса стоит быть готовым к тому, что вас оформят не как сисадмина, а как программиста или наоборот, при этом сам работодатель, имея смутное представление об IT-сфере, может вполне «навесить» на вас и то, к чему вы, согласно занимаемой должности, отношения можете не иметь. Аргументом в таких случаях со стороны работодателя служит «ну это же в компьютере» (уровень абстракции прямо-таки зашкаливает). Это и приводит в конечном итоге к тому, о чем говорилось ранее. Что же до написания собственных утилит… можно рассматривать это как экзерсисы програмирования с одной стороны, с другой — более глубокому пониманию устройства системы. Разумеется у каждого есть свои тузы в рукаве, которые придерживаются до определенного случая, да и раскрывать карты полностью вряд ли кто-то станет по причине… ммм, причины бывают разные.

Handle


Но давайте вернемся к утилитам. В Python получить хэндлы некоторого процесса можно не объявляя каких-либо структур, достаточно воспользоваться методами unpack и create_string_buffer; без [Win|NT]API также не обойдется. Последовательность дейстий будет такой: открываем процесс с флагом PROCESS_DUP_HANDLE, с помощью NtQuerySystemInformation перебираем хэндлы, дублируя их NtDuplicateObject в текущий процесс, после чего устанавливаем тип объекта и его имя с помощью NtQueryObject. В переводе на Python это будет примерно выглядеть так:

#!/usr/bin/python3

from sys import argv, exit, platform

if platform == 'cygwin':
   from ctypes import cdll as windll
else:
   from ctypes import windll

from ctypes import (
   POINTER, Structure, byref, cast, create_string_buffer,
   c_long, c_ulong, c_ushort, c_void_p, c_wchar_p
)
from struct import unpack

#единственная объявленная структура - для удобства получения имен
#хотя вполне можно было бы обойтись и без нее
class UNICODE_STRING(Structure):
   _fields_ = [
      ('Length',        c_ushort),
      ('MaximumLength', c_ushort),
      ('Buffer',        c_wchar_p),
   ]

def printerrmessage(err):
   msg = c_void_p()
   windll.kernel32.FormatMessageW(
      0x00001100, None, windll.ntdll.RtlNtStatusToDosError(err) \
      if err else windll.kernel32.GetLastError(), 1024,
      byref(msg), 0, None
   )
   err = c_wchar_p(msg.value).value
   windll.kernel32.LocalFree(msg)
   print(err.strip() if err else 'Unknown error has been occured.')

STATUS_INFO_LENGTH_MISMATCH = c_long(0xC0000004).value

if __name__ == '__main__':
   if len(argv) != 2:
      print('Usage: %s [PID]' % argv[0])
      exit(1)
   #открываем процесс
   proc = windll.kernel32.OpenProcess(0x40, False, int(argv[1]))
   if not proc:
      printerrmessage(0)
      exit(1)
   #получаем размер буфера хэндлов
   sz = 0x10000
   shi = create_string_buffer(sz) #SYSTEM_HANDLE_INFORMATION
   while (windll.ntdll.NtQuerySystemInformation(
      16, shi, sz, None
   ) == STATUS_INFO_LENGTH_MISMATCH):
      sz *= 2
      shi = create_string_buffer(sz)
   #количество хэндлов, начало и окончание "структуры" первого хэндла
   handles, b, e = unpack('@L', shi[0:4])[0], 4, 20
   #спикок хэндлов
   for i in range(0, handles):
      handle = unpack('HHccHPL', shi[b:e]) #SYSTEM_HANDLE_TABLE_ENTRY_INFO
      duple = c_void_p()
      #если хэндлы не принадлежат указанному процессу
      #переходим к следующей итерации
      if handle[0] != int(argv[1]):
         b += 16;e += 16;continue
      #дублируем хэндл для установки его типа и имени
      nts = windll.ntdll.NtDuplicateObject(
        proc, handle[4], windll.kernel32.GetCurrentProcess(), byref(duple), 0, 0, 0
      )
      if nts:
         printerrmessage(nts)
         b += 16;e += 16;continue
      #узнаем тип хэндла
      page = 0x1000 #размер буфера равен странице
      oti = create_string_buffer(page)
      nts = windll.ntdll.NtQueryObject(duple, 2, oti, page, None)
      if nts:
         printerrmessage(nts)
         windll.kernel32.CloseHandle(duple)
         b += 16;e += 16;continue
      if handle[6] == 0x12019F: #если GrantedAccess == 0x12019F может зависнуть
         windll.kernel32.CloseHandle(duple)
         b += 16;e += 16;continue
      obj_type = cast(oti[0:8], POINTER(UNICODE_STRING)).contents.Buffer
      #узнаем имя
      nameinf, length = create_string_buffer(page), c_ulong()
      nts = windll.ntdll.NtQueryObject(duple, 1, nameinf, page, byref(length))
      if nts:
         nameinf = create_string_buffer(length.value)
         nts = windll.ntdll.NtQueryObject(duple, 1, nameinf, length.value, None)
         if nts:
            windll.kernel32.CloseHandle(duple)
            b += 16;e += 16;continue
      obj_name = cast(nameinf, POINTER(UNICODE_STRING)).contents
      #выводим даные в консоль
      if obj_name.Length:
         print('%5X %-13s: %s' % (handle[4], obj_type, obj_name.Buffer))
      windll.kernel32.CloseHandle(duple)
      b += 16;e += 16 #sizeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO) = 16
   windll.kernel32.CloseHandle(proc)


Суть, полагаю, ясна, а при желании и должной работы напильником можно и вовсе сделать конфетку.

ListDlls


Наиболее распространенным способом получения списка модулей процесса является использование Module32[First|Next], однако есть способ проще — EnumerateLoadedModules.

#!/usr/bin/python3

from sys import exit, platform

if platform == 'cygwin':
   from ctypes import CFUNCTYPE as WINFUNCTYPE, cdll as windll
else:
   from ctypes import WINFUNCTYPE, windll

from ctypes import (
   POINTER, Structure, addressof, byref, cast, create_string_buffer,
   c_bool, c_byte, c_char_p, c_long, c_ulong, c_ushort, c_void_p, c_wchar_p
)

class UNICODE_STRING(Structure):
   _fields_ = [
      ('Length',        c_ushort),
      ('MaximumLength', c_ushort),
      ('Buffer',        c_wchar_p),
   ]

#несколько урезанная версия структуры
class SYSTEM_PROCESS_INFORMATION(Structure):
   _fields_ = [
      ('NextEntryOffset', c_ulong),
      ('Skipped1',        c_byte * 52),
      ('ImageName',       UNICODE_STRING),
      ('BasePriority',    c_long),
      ('UniqueProcessId', c_ulong),
      ('Skippe2',         c_byte * 112),
   ]

#наш колбэк
EnumLoadedModulesCallback = WINFUNCTYPE(
   c_bool, c_char_p, c_ulong, c_ulong, c_void_p
)

def getmodules(m_name, m_base, m_size, usr_context):
   print('0x%-10.8x0x%-8x%s' % (m_base, m_size, m_name.decode('utf-8')))
   return True

STATUS_INFO_LENGTH_MISMATCH = c_long(0xC0000004).value

if __name__ == '__main__':
   sz = c_ulong()
   ptr = create_string_buffer(1024)
   #получаем снимок процессов
   nts = windll.ntdll.NtQuerySystemInformation(
      5, ptr, 1024, byref(sz)
   )
   if nts == STATUS_INFO_LENGTH_MISMATCH:
      ptr = create_string_buffer(sz.value)
      nts = windll.ntdll.NtQuerySystemInformation(
         5, ptr, sz.value, None
      )
      if nts != 0:
         msg = c_void_p()
         windll.kernel32.FormatMessageW(
            0x00001100, None, windll.ntdll.RtlNtStatusToDosError(nts),
            1024, byref(msg), 0, None
         )
         err = c_wchar_p(msg.value).value
         windll.kernel32.LocalFree(msg)
         print(err.strip() if err else 'Unknown error has been occured.')
         exit(1)
   tmp = cast(ptr, POINTER(SYSTEM_PROCESS_INFORMATION))
   #прогуливаемся по списку загруженных модулей в каждом доступном процессе
   while (1):
      spi = tmp.contents
      proc = windll.kernel32.OpenProcess(0x400, False, spi.UniqueProcessId)
      if not proc:
         pass
      else:
         print('-' * 78)
         print('%s pid: %lu\n' % (spi.ImageName.Buffer, spi.UniqueProcessId))
         print('Base        Size      Path')
         windll.dbghelp.EnumerateLoadedModules(
            proc, EnumLoadedModulesCallback(getmodules), None
         )
         windll.kernel32.CloseHandle(proc)
      if not spi.NextEntryOffset: break
      tmp = cast(addressof(spi) + spi.NextEntryOffset,
         POINTER(SYSTEM_PROCESS_INFORMATION)
      )


Такие вот премудрости.

© Habrahabr.ru