Struct распаковать на win32file.DeviceIoControl

Я пытаюсь понять и работать с win32file. Мне нужно взять USN Journals и с трудом разобраться в фрагментах кода, которые я нашел в Интернете. Это фрагмент кода, который я нашел -

format = 'qqqqqLLLLqqqqq'
length = struct.calcsize(format)
out_buffer = win32file.DeviceIoControl(volh, winioctlcon.FSCTL_GET_NTFS_VOLUME_DATA, None, length)
data = struct.unpack(format, out_buffer)

Теперь я действительно ржавый, когда дело доходит до C и его структур. На данный момент я понял, что format буфер 96 байтов, и он получит вывод из DeviceIoControl

Поэтому я попытался изменить формат на 'QQQQQQQQQQQQQQQQQQQ' чтобы увидеть, что происходит (чтобы увидеть, потому что я вроде ничего не понимаю, что может на самом деле произойти), и оказывается, что я получил больше out_buffer этот раз. Поэтому я подумал распаковать его -

struct.unpack(format, out_buffer)

И неожиданность для меня, я получил -

struct.error: unpack requires a string argument of length 152

Поэтому я добавил еще один "Q" для увеличения размера и получил тот же результат. Я не понимаю, почему "qqqqqLLLLqqqqq" работает, а "QQQQQQQQQQQQQQQQQQQ" - нет. Так что мои вопросы -

  • Насколько я понимаю, мы можем распаковать, если буфер был больше, чем вывод, так почему не работает распаковка?

  • Придется ли мне запоминать эти форматы каждый раз, когда я хочу получить что-то от DeviceIoControl?

Указание мне на ресурсы также было бы дополнительным бонусом, так как мне нужно использовать код для чтения журналов USN, и я не думаю, что использование метода "попробуй и попробуй" меня куда-нибудь приведет

1 ответ

Решение

Давайте разделим проблемы на более мелкие части и рассмотрим каждую из них по очереди.

  • Модуль win32file является частью [GitHub]: Python для Windows (pywin32) Расширения, которые являются оболочкой Python поверх WinAPI.

    • К сожалению, у него нет официальной страницы с документами (или я ее не знаю), поэтому ниже - лучшее, что я смог найти (я использовал это годами). Альтернативный метод, который никогда не выходит из строя (но он менее привлекателен), смотрит прямо на код

    • [ActiveState]: win32file.DeviceIoControl - это оболочка над [MS.Docs]: функция DeviceIoControl

  • DeviceIoControl действует по-разному, в зависимости от dwIoControlCode (2- й аргумент). Для FSCTL_GET_NTFS_VOLUME_DATA он заполняет буфер данными, специфичными для тома. Из [MSDN]: управляющий код FSCTL_GET_NTFS_VOLUME_DATA:

    lpOutBuffer
    Указатель на выходной буфер, структура NTFS_VOLUME_DATA_BUFFER (@CristiFati: !!! Broken URL!!!). Запись файла, связанная с идентификатором файла, указанным во входном буфере, возвращается в этот буфер. Обратитесь к разделу "Примечания" документации для структуры NTFS_VOLUME_DATA_BUFFER для получения конкретной информации о том, как определить правильный размер этого буфера.

    Вот альтернатива вышеприведенному неработающему URL: [MSDN]: структура NTFS_VOLUME_DATA_BUFFER. Поскольку я не уверен, как долго он будет действителен, я вставляю приведенное ниже определение структуры (из Windows Kits 8.1: winioctl.h (строка # 4987)):

    typedef struct {
    
        LARGE_INTEGER VolumeSerialNumber;
        LARGE_INTEGER NumberSectors;
        LARGE_INTEGER TotalClusters;
        LARGE_INTEGER FreeClusters;
        LARGE_INTEGER TotalReserved;
        DWORD BytesPerSector;
        DWORD BytesPerCluster;
        DWORD BytesPerFileRecordSegment;
        DWORD ClustersPerFileRecordSegment;
        LARGE_INTEGER MftValidDataLength;
        LARGE_INTEGER MftStartLcn;
        LARGE_INTEGER Mft2StartLcn;
        LARGE_INTEGER MftZoneStart;
        LARGE_INTEGER MftZoneEnd;
    
    } NTFS_VOLUME_DATA_BUFFER, *PNTFS_VOLUME_DATA_BUFFER;
    
  • [Python 3]: struct - интерпретировать байты как модуль упакованных двоичных данных, используется для преобразования двоичных и "обычных" данных. Он содержит все значения символов формата (q, Q, L,...) и многое другое. Вы также можете взглянуть на поведение [SO]: Python struct.pack() для более (практических) деталей

Изучив вышеперечисленные материалы, все должно проясниться.

Пара заметок:

  • Если кто-то не знает, что делает (возвращает) функция, он, вероятно, не должен ее использовать (конечно, не читая руководство). Хотя в настоящее время как Win (который всегда имел множество ограничений для обычного пользователя), так и Ux "защищают пользователей от себя" (например: вход в систему root больше не разрешен, защита от записи % SystemDrive%)
  • Попытки (методом проб и ошибок) показывают некоторый недостаток опыта (вероятно, каждый делает это в какой-то момент, ключ не в том, чтобы полагаться исключительно на это)
  • " Придется ли мне запоминать эти форматы каждый раз, когда я хочу получить что-то от DeviceIoControl "?
    • Опять же, если вы не знаете, что делает функция, в чем причина ее вызова? Если вы имели в виду изучение NTFS_VOLUME_DATA_BUFFER наизусть, это определенно не тот случай. Вы должны знать его структуру только при его использовании (и, как вы заметили, есть некоторые места, из которых вы можете получить его, включая этот пост:))
  • " Насколько я понимаю, мы можем распаковать, если буфер был больше, чем вывод, так почему же распаковка не работает? "
    • Ваше понимание верно. Но win32file.DeviceIoControl иногда (вероятно, при достижении 1- го NULL после 96 байт) обрезает выходной буфер при передаче значения, большего, чем ожидаемое (через аргумент длины). При прохождении меньшего, он потерпит неудачу (как и ожидалось)

Я также подготовил фиктивный пример Python.

code.py:

#!/usr/bin/env python3

import sys
import struct
import win32file
import win32api
import win32con
import winioctlcon


VOLUME_LETTER = "E"

FILE_READ_ATTRIBUTES = 0x0080
FILE_EXECUTE = 0x0020

vol_data_buf_fmt = "qqqqqLLLLqqqqq"  # This is the format that matches NTFS_VOLUME_DATA_BUFFER definition (96 bytes). Note: Instead of each 'q' you could also use 'Ll' as 'LARGE_INTEGER' is an union

BINARY_FORMAT_LIST = [
    vol_data_buf_fmt,
    "QQQQQQQQQQQQQQQQQQQ",
]


def print_formats():  # Dummy func
    print("Formats and lengths:")
    for format in BINARY_FORMAT_LIST:
        print("    {:s}: {:d}".format(format, struct.calcsize(format)))


def main():
    #print_formats()
    vol_unc_name = "\\\\.\\{:s}:".format(VOLUME_LETTER)
    print("volume: ", vol_unc_name)
    access_flags = FILE_READ_ATTRIBUTES | FILE_EXECUTE  # Apparently, doesn't work without FILE_EXECUTE
    share_flags = win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE  # Doesn't work withou FILE_SHARE_WRITE
    creation_flags = win32con.OPEN_EXISTING
    attributes_flags = win32con.FILE_ATTRIBUTE_NORMAL
    vol_handle = win32file.CreateFile(vol_unc_name, access_flags, share_flags, None, creation_flags, attributes_flags, None)

    buf_len = struct.calcsize(vol_data_buf_fmt)
    for i in [buf_len]:
        print("    Passing a buffer size of: {:d}".format(i))
        buf = win32file.DeviceIoControl(vol_handle, winioctlcon.FSCTL_GET_NTFS_VOLUME_DATA, None, i)
        print("    DeviceIocontrol returned a {:d} bytes long {:}".format(len(buf), type(buf)))
        out = struct.unpack_from(vol_data_buf_fmt, buf)
        print("\n    NumberSectors: {:}\n    TotalClusters: {:}\n    BytesPerCluster: {:}".format(out[1], out[2], out[6]))
    win32api.CloseHandle(vol_handle)


if __name__ == "__main__":
    print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    main()

Выход:

(py35x64_test) e:\Work\Dev\Stackru\q053318932>"e:\Work\Dev\VEnvs\py35x64_test\Scripts\python.exe" ./code.py
Python 3.5.4 (v3.5.4:3f56838, Aug  8 2017, 02:17:05) [MSC v.1900 64 bit (AMD64)] on win32

volume:  \\.\E:
    Passing a buffer size of: 96
    DeviceIocontrol returned a 96 bytes long <class 'bytes'>

    NumberSectors: 494374911
    TotalClusters: 61796863
    BytesPerCluster: 4096

Само собой разумеется, что, умножая TotalClusters на BytesPerCluster, я получаю правильное число байтов (как сообщает Win) для моего диска E:.

Другие вопросы по тегам