python

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

  • вторник, 21 апреля 2015 г. в 02:10:51
http://habrahabr.ru/post/256161/

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 = 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 достаточно, так что не исключено, что продолжение все же последует…