Сам себе Руссинович: перезагрузка

ctypes Среди утилит SysInternals есть те, что не обновляются годами, а их повседневное использование сомнительно. И все же назвать их абсолютно бесполезными язык не поворачивается. Внутреннее устройство таких утилит довольно просто, разбирать которое на Python весьма занимательно; не то, чтобы разбирать, скорее писать аналоги, не используя при этом сторонних расширений.По слухам некогда исходники утилит SysInternals были в открытом доступе, и если покопаться в интернете, наверняка их еще можно где-то отыскать. Правда тогда это отобьет всякую охоту понять не только устройство утилит, но и используемые ими механизмы. И голова дана не только для начала пищеварительного процесса, верно? Так что достаточно будет терпения и настойчивости, остальное приложится.Первое препятствие на пути — с чего начать? Понятно, что с головой в омут бросаться не стоит и начинать лучше с самого простого, а еще прежде нужно позаботиться о внештатных ситуациях, способных возникнуть во время работы сценария Python. Под определения API’шных функций и иже с ними выделим отдельный модуль с незатейливым названием russinovich.py, в котором напишем следующее:

from ctypes import ( byref, c_ulong, c_void_p, c_wchar_p, windll )

FORMAT_MESSAGE_ALLOCATE_BUFFER = 0×00000100 FORMAT_MESSAGE_IGNORE_INSERTS = 0×00000200 FORMAT_MESSAGE_FROM_SYSTEM = 0×00001000 LANG_NEUTRAL = 0×00000000 SUBLANG_DEFAULT = 0×00000001

FormatMessage = windll.kernel32.FormatMessageW GetLastError = windll.kernel32.GetLastError LocalFree = windll.kernel32.LocalFree RtlNtStatusToDosError = windll.ntdll.RtlNtStatusToDosError

def printerror (err): def MAKELANGID (p, s): return c_ulong ((s << 10) | p) msg = c_void_p() err = RtlNtStatusToDosError(err) if err != 0 else GetLastError() FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM, None, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), byref(msg), 0, None ) print(c_wchar_p(msg.value).value.strip()) LocalFree(msg) Эдакая функция по Фрейду Рихтеру, правда здесь мы пошли на одну хитрость: ненулевое значение переданное функции printerror будет трактоваться как NTSTATUS, в противном случае обрабатывается значение возвращаемое GetLastError.Так как clockres — самое простое, что есть в SysInternals Suite, с него и начнем. Добавим в russinovich.py определение функции NtQueryTimerResolution:

… NtQueryTimerResolution = windll.ntdll.NtQueryTimerResolution … Создаем файл clockres.py: from russinovich import printerror, NtQueryTimerResolution from ctypes import byref, c_ulong from sys import exit

if __name__ == '__main__': _max, _min, _cur = c_ulong (), c_ulong (), c_ulong ()

ntstatus = NtQueryTimerResolution (byref (_max), byref (_min), byref (_cur)) if ntstatus!= 0: printerror (ntstatus) exit (1) print ('Maximum timer interval: %.3f ms' % (_max.value / 10000)) print ('Minimum timer interval: %.3f ms' % (_min.value / 10000)) print ('Current timer interval: %.3f ms' % (_cur.value / 10000)) А откуда, собственно, взята NtQueryTimerResolution? Естественным путем: >>> from os.path import abspath >>> with open (abspath ('clockres.exe'), 'rb') as f: … raw = str (f.read (), 'utf7', 'replace') … >>> from re import compile, findall >>> for i in compile ('[\x20-\x7E]{13,}').findall (raw): print (i) … ! This program cannot be run in DOS mode. tM>> Можно и дизассемблером — кому как хочется.Clockres — слишком просто, стоит попробовать что-то посложнее. Например, pipelist, тем паче, что на официальной странице утилиты Руссинович не стал ходить вокруг да около, а в открытую заявил, мол, использовал NtQueryDirectoryFile, описание которой (правда с приставкой Zw) можно найти либо в MSDN, либо в заголовочном файле ntifs.h:

… #if (NTDDI_VERSION >= NTDDI_WIN2K) __drv_maxIRQL (PASSIVE_LEVEL) NTSYSAPI NTSTATUS NTAPI ZwQueryDirectoryFile ( __in HANDLE FileHandle, __in_opt HANDLE Event, __in_opt PIO_APC_ROUTINE ApcRoutine, __in_opt PVOID ApcContext, __out PIO_STATUS_BLOCK IoStatusBlock, __out_bcount (Length) PVOID FileInformation, __in ULONG Length, __in FILE_INFORMATION_CLASS FileInformationClass, __in BOOLEAN ReturnSingleEntry, __in_opt PUNICODE_STRING FileName, __in BOOLEAN RestartScan ); #endif … Объявляем NtQueryDirectoryFile в russinovich.py: … NtQueryDirectoryFile = windll.ntdll.NtQueryDirectoryFile … Там же объявляем прочие типы, которые нам понадобятся: from ctypes import ( …, Structure, Union, c_long, c_longlong, addressof, c_wchar, sizeof )

… class LARGE_INTEGER_UNION (Structure): _fields_ = [ ('LowPart', c_ulong), ('HighPart', c_ulong), ]

class LARGE_INTEGER (Union): _fields_ = [ ('liu1', LARGE_INTEGER_UNION), ('liu2', LARGE_INTEGER_UNION), ('QuadPart', c_longlong), ]

class FILE_DIRECTORY_INFORMATION (Structure): _fields_ = [ ('NextEntryOffset', c_ulong), ('FileIndex', c_ulong), ('CreationTime', LARGE_INTEGER), ('LastAccessTime', LARGE_INTEGER), ('LastWriteTime', LARGE_INTEGER), ('ChangeTime', LARGE_INTEGER), ('EndOfFile', LARGE_INTEGER), ('AllocationSize', LARGE_INTEGER), ('FileAttributes', c_ulong), ('FileNameLength', c_ulong), ('_FileName', c_wchar * 1), ] @property def FileName (self): addr = addressof (self) + type (self)._FileName.offset name = c_wchar * (self.FileNameLength // sizeof (c_wchar)) return name.from_address (addr).value … Так как поле _FileName в структуре FILE_DIRECTORY_INFORMATION описывает лишь первый символ имени, мы дополнили структуру свойством FileName, извлекающее имя целиком. Пара штрихов к russinovich.py: … GENERIC_READ = 0×80000000 FILE_SHARE_READ = 0×00000001 OPEN_EXISTING = 0×00000003 INVALID_HANDLE_VALUE = -1 FileDirectoryInformation = 1 … CloseHandle = windll.kernel32.CloseHandle CreateFile = windll.kernel32.CreateFileW … И можно создавать pipelist.py: from russinovich import ( CloseHandle, CreateFile, NtQueryDirectoryFile, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, INVALID_HANDLE_VALUE, printerror, IO_STATUS_BLOCK, FILE_DIRECTORY_INFORMATION, FileDirectoryInformation ) from ctypes import ( POINTER, addressof, byref, cast, create_string_buffer ) from sys import exit

def NT_SUCCESS (ntstatus): return True if ntstatus >= 0 else False

if __name__ == '__main__': pipes = None try: isb = IO_STATUS_BLOCK () dir_inf = cast (create_string_buffer (1024), POINTER (FILE_DIRECTORY_INFORMATION)) query = True pipes = CreateFile ('\\\\.\\pipe\\', GENERIC_READ, FILE_SHARE_READ, None, OPEN_EXISTING, 0, None) if pipes == INVALID_HANDLE_VALUE: printerror (0) exit (1) print (»%-40s%14s%20s» % ('Pipe Name', 'Instances', 'Max Instances')) print (»%-40s%14s%20s» % ('-' * 9, '-' * 9, '-' * 13)) while (1): ntstatus = NtQueryDirectoryFile ( pipes, None, None, 0, byref (isb), dir_inf, 1024, FileDirectoryInformation, False, None, query ) if not NT_SUCCESS (ntstatus): break cur_inf = dir_inf while (1): cur = cur_inf.contents print (»%-40s%14s%20s» % (cur.FileName, cur.EndOfFile.liu1.LowPart, cur.AllocationSize.liu1.LowPart)) if cur.NextEntryOffset == 0: break cur_inf = cast (int (addressof (cur)) + cur.NextEntryOffset, POINTER (FILE_DIRECTORY_INFORMATION)) query = False except Exception as e: print (e) finally: CloseHandle (pipes) Вид таблицы максимально подогнан под тот, что выводит pipelist Руссиновича, так что, называется, найдите различия. А если различий нет, какая разница что использовать? Утилит SysInternals достаточно, так что не исключено, что продолжение все же последует…

© Habrahabr.ru