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)
)
Такие вот премудрости.