Среди утилит SysInternals есть те, что не обновляются годами, а их повседневное использование сомнительно. И все же назвать их абсолютно бесполезными язык не поворачивается. Внутреннее устройство таких утилит довольно просто, разбирать которое на Python весьма занимательно; не то, чтобы разбирать, скорее писать аналоги, не используя при этом сторонних расширений.
По слухам некогда исходники утилит SysInternals были в открытом доступе, и если покопаться в интернете, наверняка их еще можно где-то отыскать. Правда тогда это отобьет всякую охоту понять не только устройство утилит, но и используемые ими механизмы. И голова дана не только для начала пищеварительного процесса, верно? Так что достаточно будет терпения и настойчивости, остальное приложится.
Первое препятствие на пути — с чего начать? Понятно, что с головой в омут бросаться не стоит и начинать лучше с самого простого, а еще прежде нужно позаботиться о внештатных ситуациях, способных возникнуть во время работы сценария Python. Под определения API'шных функций и иже с ними выделим отдельный модуль с незатейливым названием russinovich.py, в котором напишем следующее:
from ctypes import (
byref, c_ulong, c_void_p, c_wchar_p, windll
)
FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100
FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200
FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000
LANG_NEUTRAL = 0x00000000
SUBLANG_DEFAULT = 0x00000001
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<it-<ot)<ut%<xt!<Xt
<dty<itu<otq<utm<xti<Xte
Current timer interval: %.03f ms
Minimum timer interval: %.03f ms
Maximum timer interval: %.03f ms
NtQueryTimerResolution
...
>>>
Можно и дизассемблером — кому как хочется.
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 = 0x80000000
FILE_SHARE_READ = 0x00000001
OPEN_EXISTING = 0x00000003
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 достаточно, так что не исключено, что продолжение все же последует…
Автор: gregzakharov