Статья ASM. Драйверы WDM (2). Объект драйвера и его окружение

Marylin

Mod.Assembler
Red Team
05.06.2019
325
1 444
BIT
654
Предыдущая часть была посвящена линиям запроса на прерывания IRQ, и приоритетам IRQL. Но пока ещё к практике приступать рано, поскольку нужно дать определение самому драйверу, и его объектам устройств. Эти два понятия занимают огромную нишу в теме, и уложить материал в рамки одной статьи врядли получится. Поэтому сконцентрируем внимание только на основных моментах, оставив за бортом детали.

В этой части:
1. Схема передачи управления драйверам​
2. Driver & Device object​
3. Стеки драйверов​



1. Схема передачи управления драйверам

Вспомним организацию виртуальной памяти системы.
Значит вся физическая ОЗУ делится на фреймы по 4 Кбайт. Такого-же размера кластер на жёстком диске, а так-же одна страница вирт.памяти. Суть виртуализации в том, что разным фреймам можно назначить одинаковый вирт.адрес, поэтому обращаясь по указателю например 0x00400000, процесс(А) читает именно свою память, а не процесса(В). Когда фреймы физ.памяти заканчиваются, какой-нибудь неиспользуемый можно выгрузить в кластер жёсткого диска, а по требованию вернуть обратно. Диспетчер памяти Win держит под контролем 2 типа фреймов – которые можно сбрасывать на диск в файл подкачки PagedPool, и которые нельзя NonPagedPool.

При создании процесса система выделяет ему вирт.память в виде фреймов. Указатели на эту память хранятся в каталоге процесса PageDirectory, а линк на сам каталог в регистре CR3. При смене контекста обновляется и CR3, указывая таким образом на память следущего процесса, и т.д.

64-битный адрес, которым мы оперируем в коде считается линейным и состоит из 5-ти частей, что представлено на рисунке ниже (из 64 используются только 48-бит). Обратите внимание, что для адресации записей Entry, во-всех таблицах выделяются по 9-бит, а значит всего записей в каждой из таблиц по 2^9=512. Другими словами, корневая таблица PML4E одна, а вот кол-во таблиц PDPT, PD и PT уже может достигать 512-ти штук. Это позволяет в архитектуре х64 адресовать: (512*512*512*512)*4096=256 ТераБайт памяти (юзер и кернел делят их пополам по 127 каждому). Зато оффсет 12-битный – он выбирает смещение внутри 4 КБайтного фрейма (отмечен зелёным).

PTE.png

Значения записей PTE (PageTableEntry) всегда кратны 0x1000 – они адресуют фреймы физ.памяти, а потому младшие 12-бит отводятся под флаги. В частности бит под кличкой Durty является индикатором того, что фрейм (в терминологии майков) «грязный», т.е. в его ячейки производилась запись новых значений. При нехватке ОЗУ, в своп выгружаются только фреймы с атрибутом D=1, а нетронутые с D=0 сразу отдаются клиентам на растерзание – чем гонять их на диск и обратно, проще загрузить оригинал.

Все 64-битные версии Win распределяют память ядра следующим образом:

Код:
0: kd> !cmkd.kvas

###  Start              End                                Length   Type
000  ffff0800`00000000  fffff67f`ffffffff   ee8000000000 ( 238 TB)  SystemSpace
001  fffff680`00000000  fffff6ff`ffffffff     8000000000 ( 512 GB)  PageTables
002  fffff700`00000000  fffff77f`ffffffff     8000000000 ( 512 GB)  HyperSpace
003  fffff780`00000000  fffff780`00000fff           1000 (   4 KB)  SharedSystemPage
004  fffff780`00001000  fffff7ff`ffffffff     7ffffff000 ( 511 GB)  CacheWorkingSet
005  fffff800`00000000  fffff87f`ffffffff     8000000000 ( 512 GB)  LoaderMappings
006  fffff880`00000000  fffff89f`ffffffff     2000000000 ( 128 GB)  SystemPTEs
007  fffff8a0`00000000  fffff8bf`ffffffff     2000000000 ( 128 GB)  PagedPool
008  fffff900`00000000  fffff97f`ffffffff     8000000000 ( 512 GB)  SessionSpace
009  fffff980`00000000  fffffa7f`ffffffff    10000000000 (   1 TB)  DynamicKernelVa
010  fffffa80`00000000  fffffa80`017fffff        1800000 (  24 MB)  PfnDatabase
011  fffffa80`01600000  fffffa80`5e5fffff       5d000000 (   1 GB)  NonPagedPool
012  ffffffff`ffc00000  ffffffff`ffffffff         400000 (   4 MB)  HalReserved

В арсенале отладчика WinDbg есть несколько расширений для просмотра информации в каталогах любых процессов, только нужно передавать им в качестве базы значение регистра CR3. Вот их перечень:

!vtop преобразует вирт.адрес в соответствующий физ.адрес (Virtual-To-Physical).​
!pfn отображает информацию о конкретном фрейме (Page-Frame-Number).​
!pte отображает записи всех четырёх каталогов для указанного вирт.адреса.​
!pte2va отображает вирт.адрес, соответствующий указанной записи PTE.​

Код:
0: kd> !process 0 0 Codeby.exe
PROCESS fffffa8001fd9b10
    Peb  : 7fffffdd000    DirBase: 754e3000  <----// линк на каталог = CR3
    Image: Codeby.EXE

0: kd> dt _peb 7fffffdd000 ImageBase*  <----------// запросим вирт.базу загрузки образа
ntdll!_PEB
   +0x010 ImageBaseAddress : 0x00000000`00400000  void

0: kd> !vtop 754e3000 00400000   <----------------// получить физ.адрес по виртуальному!
   Amd64VtoP:  PML4E  754e3000
   Amd64VtoP:  PDPE   69543000
   Amd64VtoP:  PDE    6a244010
   Amd64VtoP:  PTE    2ac05000
   Virtual address 400000 translates to physical address 70747000.

0: kd> !pte 7fffffdd000
                                           VA 000007fffffdd000
PXE at FFFFF6FB7DBED078   PPE at FFFFF6FB7DA0FFF8   PDE at FFFFF6FB41FFFFF8   PTE at FFFFF683FFFFFEF8 ---+
pfn 2f000   ---DA--UWEV   pfn 72d01   ---DA--UWEV   pfn 2a902   ---DA--UWEV   pfn 6d2c3   ---D---UW-V    |
                                                                                                         |
0: kd> !pfn 6d2c3                                                                                        |
    PFN 0006D2C3 at address FFFFFA8001478490                                                             |
    flink       0000000B   share count  00000001  pteaddress FFFFF683FFFFFEF8  <-------------------------+
    reference count 0001   used  count      0000  Active     Modified  <----------------// Бит(D)=1

    ------------------------------------------ PTE flags ---
    V – Valid      D – Durty     A – Accesed
    E – Execute    G – Global    L – Large page (1Gb)
    W/R – Writeable/ReadOnly     U/K – User/Kernel

Это к тому, что когда для поддержки многозадачности планировщик переключает потоки, то системе приходится сохранять не только значения всех регистров, но и номера фреймов физ.памяти PFN, чтобы позже вернуться в прежнее состояние. Иначе поток не сможет найти свои данные, и план с треском провалится. Этот процесс известен как смена контекста потоков, или Context Switching. На склад отправляется так-же вложенная в EPROCESS основная структура процесса PCB «Process Control Block». На х32 контекст переключался аппаратно через сегмент состояния задачи TSS «Task State Segment», но сейчас реализация исключительно программная, а в TSS остался только указатель на стек ядра текущего потока:

Код:
0: kd> dt _kpcr @$pcr TssBase
hal!_KPCR
   +0x008 TssBase : 0xfffff800`03ff7080 _KTSS64

0: kd> dt _ktss64 0xfffff800`03ff7080
hal!_KTSS64
   +0x000 Reserved0    : 0
   +0x004 Rsp0         : 0xfffff800`03ffdd70
   +0x00c Rsp1         : 0
   +0x014 Rsp2         : 0
   +0x01c Ist          : [8] 0
   +0x05c Reserved1    : 0
   +0x064 Reserved2    : 0
   +0x066 IoMapBase    : 0x68
0: kd>

Драйверы не имеют своих потоков в системе, а всегда исполняются только в контексте чужих. Например точка-входа DriverEntry() садится на один из многочисленных потоков процесса System – она исполняется только при загрузке драйвера, и по окончании инициализации освобождает System от своего присутствия. Зато остальные процедуры драйвера типа обработки прерываний ISR выполняются уже в контексте того потока, который был активным в момент возникновения самого прерывания. Часто в качестве транспорта драйвера используют потоки пользовательских приложений, что демонстрирует рис.ниже.

Смена контекста в пространстве пользователя может произойти всего по трём причинам:
1. Когда у юзер-потока истёк выделенный ему квант времени​
2. Когда его вытесняет более приоритетный (например диспетчер задач)
3. Когда внезапно какое-то из устройств послало процессору прерывание.​

DrvThread.png


2. Объект драйвера, и объект устройства

То, что мы привыкли называть драйвером, по факту является структурой DRIVER_OBJECT. Это основная база, которая (явно или через указатели) описывает буквально весь функционал и окружение драйвера. В нёдрах системы структуру создаёт диспетчер ввода-вывода, а в нашу процедуру DriverEntry() передаёт лишь её адрес. Радует то, что диспетчер берёт на себя все проблемы по оформлению связанных с драйвером структур, при этом большая часть их полей доступна только на чтение. Таким образом, соло играет именно диспетчер, а мы ему лишь в терцию подпеваем.

У любого драйвера WDM должно быть как минимум одно физическое или виртуальное/псевдо устройство, которым он будет управлять. Эти устройства драйвер создаёт сам функцией IoCreateDevice(). Например у pci.sys аж 20 подчинённых устройств, а у acpi вообще 44 девайса, и каждого из них олицетворяет своя структура DEVICE_OBJECT.

Однако вновь испечённое устройство пока скрыто от всех, и чтобы сделать его видимым тому-же пользователю, необходимо через IoCreateSymbolicLink() зарегать девайс в системном пространстве имён, обозвав его хоть именем любимого кота. Только после этого можно будет открыть устройство функцией юзера CreateFile(), и посылать ему команды IOCTL посредством DeviceIoControl().

WinDbg имеет следующие команды и расширения для сбора информации о драйверах:

lmsm – список всех драйверов
lmsm m a* – список всех драйверов, имена которых начинаются на «А»
!drvobj – базовая информация из структуры объекта драйвера
!devobj – базовая информация из структуры объекта устройства
!devstack – позиция драйвера в стеке драйверов
!devnode – сведения о корне древа устройств

Рассмотрим их на примере драйвера порта PS/2 i8042prt.sys.
В списке List видно, что дров обслуживает всего 2 устройства – проводную мышь и клаву.
Пока нам нужен только указатель на структуру DRIVER_OBJECT, чтобы сдампить её содержимое:

Код:
0: kd> lmsm m i*
start              end                 module name
fffff880`04934000  fffff880`04952000   i8042prt   c:\symbols\i8042prt.pdb
fffff880`01999000  fffff880`019a3000   IaNVMeF    (deferred)
fffff880`04015000  fffff880`045f9fe0   igdkmd64   (deferred)
fffff880`00e89000  fffff880`00e91000   intelide   (deferred)
fffff880`03bd7000  fffff880`03bed000   intelppm   (deferred)
fffff880`00e40000  fffff880`00e4a000   iusb3hcs   (deferred)

0: kd> !drvobj i8042prt
   Driver Object (fffffa8002c1c8a0) is for: \Driver\i8042prt
   Device Object list:
   ----------------------------------
   fffffa8002bf89f0  fffffa8002c939f0  <-----// линки на структуры DEVICE_OBJECT

0: kd> dt _driver_object fffffa8002c1c8a0
nt!_DRIVER_OBJECT
   +0x000 Type             : 0n4
   +0x002 Size             : 0n336
   +0x008 DeviceObject     : 0xfffffa80`02bf89f0 _DEVICE_OBJECT
   +0x010 Flags            : 0x12
   +0x018 DriverStart      : 0xfffff880`04934000  void
   +0x020 DriverSize       : 0x1e000
   +0x028 DriverSection    : 0xfffffa80`02b181c0  void
   +0x030 DriverExtension  : 0xfffffa80`02c1c9f0 _DRIVER_EXTENSION
   +0x038 DriverName       : _UNICODE_STRING "\Driver\i8042prt"
   +0x048 HardwareDatabase : 0xfffff800`03195568 _UNICODE_STRING "\REGISTRY\MACHINE\HARDWARE\DESCRIPTION\SYSTEM"
   +0x050 FastIoDispatch   : (null)
   +0x058 DriverInit       : 0xfffff880`0494c070       long  i8042prt!GsDriverEntry+0
   +0x060 DriverStartIo    : 0xfffff880`049366f8       void  i8042prt!I8xStartIo+0
   +0x068 DriverUnload     : 0xfffff880`04948ae0       void  i8042prt!I8xUnload+0
   +0x070 MajorFunction    : [28] 0xfffff880`04943cf8  long  i8042prt!I8xCreate+0

Все поля до смещения 0x60 диспетчер заполнил сам, а мы явно должны будем прописать только адрес своей процедуры DispatchUnload(), чтобы драйвер не остался в памяти до следущего ребута. Так-же в массиве указателей MajorFunction нужно выбрать лишь те коды, которые планируем реализовать во-внутренних процедурах обратного вызова. Просмотреть мажорные коды подопытного i8042prt можно той-же командой !drvobj с ключом(3):

Код:
0: kd> !drvobj i8042prt 3
   Driver object (fffffa8002c1c8a0) is for: \Driver\i8042prt
   Device Object list:
   ----------------------------------
   fffffa8002bf89f0  fffffa8002c939f0

   DriverEntry:   fffff8800494c070  i8042prt!GsDriverEntry
   DriverStartIo: fffff880049366f8  i8042prt!I8xStartIo
   DriverUnload:  fffff88004948ae0  i8042prt!I8xUnload
   AddDevice:     fffff88004947f80  i8042prt!I8xAddDevice

   Dispatch routines:
   [00] IRP_MJ_CREATE                      fffff88004943cf8    i8042prt!I8xCreate
   [01] IRP_MJ_CREATE_NAMED_PIPE           fffff80002ca8c60    nt!IopInvalidDeviceRequest
   [02] IRP_MJ_CLOSE                       fffff88004948390    i8042prt!I8xClose
   [03] IRP_MJ_READ                        fffff80002ca8c60    nt!IopInvalidDeviceRequest
   [04] IRP_MJ_WRITE                       fffff80002ca8c60    nt!IopInvalidDeviceRequest
   [05] IRP_MJ_QUERY_INFORMATION           fffff80002ca8c60    nt!IopInvalidDeviceRequest
   [06] IRP_MJ_SET_INFORMATION             fffff80002ca8c60    nt!IopInvalidDeviceRequest
   [07] IRP_MJ_QUERY_EA                    fffff80002ca8c60    nt!IopInvalidDeviceRequest
   [08] IRP_MJ_SET_EA                      fffff80002ca8c60    nt!IopInvalidDeviceRequest
   [09] IRP_MJ_FLUSH_BUFFERS               fffff8800493a660    i8042prt!I8xFlush
   [0a] IRP_MJ_QUERY_VOLUME_INFORMATION    fffff80002ca8c60    nt!IopInvalidDeviceRequest
   [0b] IRP_MJ_SET_VOLUME_INFORMATION      fffff80002ca8c60    nt!IopInvalidDeviceRequest
   [0c] IRP_MJ_DIRECTORY_CONTROL           fffff80002ca8c60    nt!IopInvalidDeviceRequest
   [0d] IRP_MJ_FILE_SYSTEM_CONTROL         fffff80002ca8c60    nt!IopInvalidDeviceRequest
   [0e] IRP_MJ_DEVICE_CONTROL              fffff880049487c0    i8042prt!I8xDeviceControl
   [0f] IRP_MJ_INTERNAL_DEVICE_CONTROL     fffff88004935920    i8042prt!I8xInternalDeviceControl
   [10] IRP_MJ_SHUTDOWN                    fffff80002ca8c60    nt!IopInvalidDeviceRequest
   [11] IRP_MJ_LOCK_CONTROL                fffff80002ca8c60    nt!IopInvalidDeviceRequest
   [12] IRP_MJ_CLEANUP                     fffff80002ca8c60    nt!IopInvalidDeviceRequest
   [13] IRP_MJ_CREATE_MAILSLOT             fffff80002ca8c60    nt!IopInvalidDeviceRequest
   [14] IRP_MJ_QUERY_SECURITY              fffff80002ca8c60    nt!IopInvalidDeviceRequest
   [15] IRP_MJ_SET_SECURITY                fffff80002ca8c60    nt!IopInvalidDeviceRequest
   [16] IRP_MJ_POWER                       fffff88004943f40    i8042prt!I8xPower
   [17] IRP_MJ_SYSTEM_CONTROL              fffff88004943c7c    i8042prt!I8xSystemControl
   [18] IRP_MJ_DEVICE_CHANGE               fffff80002ca8c60    nt!IopInvalidDeviceRequest
   [19] IRP_MJ_QUERY_QUOTA                 fffff80002ca8c60    nt!IopInvalidDeviceRequest
   [1a] IRP_MJ_SET_QUOTA                   fffff80002ca8c60    nt!IopInvalidDeviceRequest
   [1b] IRP_MJ_PNP                         fffff88004943680    i8042prt!I8xPnP
0: kd>

Здесь видим точки-входа в процедуры DriverEntry(), Unload(), AddDevice(), после которых представлен массив запросов от клиентов IRP_MJ_xx (в структуре driver_object он начинается со-смещения 0x70, т.е. является её частью). Диспетчер сам заполняет все 28 полей данного массива, прописывая в них линки на свою заглушку IoCompleteRequest(), с кодом возврата невалидного запроса к устройству 0xC0000010.

Код:
0: kd> uf /i nt!IopInvalidDeviceRequest
   8 instructions scanned

   nt!IopInvalidDeviceRequest:
   fffff800`02ca8c60  4883ec28        sub    rsp,28h
   fffff800`02ca8c64  488bca          mov    rcx,rdx
   fffff800`02ca8c67  c74230100000c0  mov    dword ptr [rdx+30h],0C0000010h
   fffff800`02ca8c6e  33d2            xor    edx,edx
   fffff800`02ca8c70  ff1582672500    call   qword ptr [nt!pIofCompleteRequest (fffff800`02eff3f8)]
   fffff800`02ca8c76  b8100000c0      mov    eax,0C0000010h
   fffff800`02ca8c7b  4883c428        add    rsp,28h
   fffff800`02ca8c7f  c3              ret
0: kd>

Если в коде нашего драйвера мы планируем обрабатывать запросы API типа Create-Close-Read-WriteFile() и другие, то должны прописать в этот массив указатели на свои процедуры коллбек, тупо затирая предоставленные диспетчером заглушки. Все драйверы WDM обязаны обрабатывать коды POWER и PNP, а остальные уже на своё усмотрение. Если отсеить из этого списка всё лишнее, то получается, что дров i8042prt.sys может принимать только следующие запросы:

Код:
Dispatch routines:
   [00] IRP_MJ_CREATE                      fffff88004943cf8    i8042prt!I8xCreate
   [02] IRP_MJ_CLOSE                       fffff88004948390    i8042prt!I8xClose
   [09] IRP_MJ_FLUSH_BUFFERS               fffff8800493a660    i8042prt!I8xFlush
   [0f] IRP_MJ_INTERNAL_DEVICE_CONTROL     fffff88004935920    i8042prt!I8xInternalDeviceControl
   [16] IRP_MJ_POWER                       fffff88004943f40    i8042prt!I8xPower
   [17] IRP_MJ_SYSTEM_CONTROL              fffff88004943c7c    i8042prt!I8xSystemControl
   [1b] IRP_MJ_PNP                         fffff88004943680    i8042prt!I8xPnP


2.1. Объект устройства

Драйвер и его устройство всегда ходят парой. Если в функцию DriverEntry() диспетчер передаёт указатель на DRIVER_OBJECT, то все процедуры обработки запросов IRP_MJ_xx аргументом принимают уже указатель на DEVICE_OBJECT. Каждый девайс имеет свои свойства, которые задаются при его создании функцией IoCreateDevice(). Свойства включают в себя следущие поля в структуре:

• DeviceType – тип оборудования (у нас будет всегда file_device_unknown)
• Flags – текущие настройки (см.инклуд wdm в скрепке)
• Characteristics – флаги с доп.информацией об устройстве (см.инклуд)
• SecurityDesc – дескриптор безопасности, для контроля доступа к устройству (обычно нуль = в дефолте).​

Запросим у отладчика объект устройства подопытного i8042prt.sys, чтобы ознакомиться с полным его прототипом:

Код:
0: kd> !drvobj i8042prt
   Driver object (fffffa8002ae0550) is for: \Driver\i8042prt
   Device object list:  fffffa8002ae58c0, fffffa8002ae09f0  <-----// создаёт 2 устройства

0: kd> dt _device_object fffffa8002ae58c0
ntdll!_DEVICE_OBJECT
   +0x000 Type             : 0n3      <-----------// тип самой структуры IO_TYPE_DEVICE
   +0x002 Size             : 0x598
   +0x004 ReferenceCount   : 0n0
   +0x008 DriverObject     : 0xfffffa80`02ae0550 _DRIVER_OBJECT  <---// родитель
   +0x010 NextDevice       : 0xfffffa80`02ae09f0 _DEVICE_OBJECT  <---// линк на структуру сл.устройства (если есть)
   +0x018 AttachedDevice   : 0xfffffa80`02ae7060 _DEVICE_OBJECT  <---// к кому приаттачен в стеке драйверов
   +0x020 CurrentIrp       : (null)   <------------------------------// линк на пакет запроса IRP
   +0x028 Timer            : (null)
   +0x030 Flags            : 0x2004   <-----------// DO_POWER_PAGABLE + DO_BUFFERED_IO
   +0x034 Characteristics  : 0
   +0x038 Vpb              : (null)
   +0x040 DeviceExtension  : 0xfffffa80`02ae5a10
   +0x048 DeviceType       : 0x27     <-----------// FILE_DEVICE_8042_PORT
   +0x04c StackSize        : 6
   +0x050 Queue            : <unnamed-tag>
   +0x098 AlignRequirement : 0
   +0x0a0 DeviceQueue      : _KDEVICE_QUEUE
   +0x0c8 Dpc              : _KDPC
   +0x108 ActiveThreads    : 0
   +0x110 SecurityDesc     : (null)
   +0x118 DeviceLock       : _KEVENT
   +0x130 SectorSize       : 0
   +0x132 Spare1           : 1
   +0x138 DeviceObjectExt  : 0xfffffa80`02ae5e58 _DEVOBJ_EXTENSION  <----// обсудим позже
   +0x140 Reserved         : (null)
0: kd>

После инициализации драйвер бездействует. Пробудить его из глубокого сна может только приход пакета IRP от диспетчера ввода-вывода (Input/Output Request Packet). Физически это происходит в момент записи указателя в поле DEVICE_OBJECT --> CurrentIrp. Драйвер без объекта устройства не получает никаких IRP !!!

Структура IRP содержит буквально всю информацию о запросе, предписывая драйверу дальнейший фронт работы. Как только драйвер обработает текущий запрос и пошлёт IoCompleteRequest(), диспетчер удалит пакет этого запроса, и войдёт в цикл ожидания следующего. Таким образом IRP можно считать джокером во-всей подсистеме ввода-вывода, поскольку именно он задаёт всем темп.

IRP одна из немногих структур, поля которой доступны нам на запись. Это особенно актуально при двустороннем взаимодействии с кодом пользовательского режима. Например в аргументах DeviceIoControl() юзер передаёт адреса двух своих буферов – один на приём, другой на передачу. Так вот адреса этих буферов драйвер может получить только прочитав поля структуры IRP. Поскольку пакет создаётся самим диспетчером где-то в нёдрах системы (и мы не знаем где точно), он передаёт указатель на IRP во все наши процедуры обратного вызова DispatchRoutine().

Все тонкости пакета запроса оставим до лучших времён, а пока посмотрим на общую схему взаимосвязей структур в подсистеме ввода-вывода. Здесь в серые блоки заключены наиболее важные для разработчиков поля, и в некоторые из них мы можем явно или коссвенно производить запись. Структуры, которые не предваряются указателем Ptr64 (pointer) являются вложенными, например UNICODE_STRING, LIST_ENTRY и прочие.

DriverScheme.png

Код:
0: kd> !drvobj acpi
   Driver object (fffffa8001851e70) is for: \Driver\ACPI <-----// создаёт 44 устройства!
   Device object list:
   ----------------------------------
   fffffa800265a520  fffffa8002657e40  fffffa80022de520  fffffa800233eb20
   fffffa800233fe40  fffffa80022c4a90  fffffa80022c4c90  fffffa800224ca00
   fffffa800224cc20  fffffa800224ce40  fffffa800224c060  fffffa800224b310
   fffffa800224b530  fffffa800224b750  fffffa800224b970  fffffa800224b060
   fffffa800224bb90  fffffa800224be40  fffffa80022460f0  fffffa80022463a0
   fffffa8002246650  fffffa8002246870  fffffa8002246b90  fffffa8001863440
   fffffa8001863640  fffffa8001863840  fffffa8001863a40  fffffa8001863c40
   fffffa8001863e40  fffffa8001863040  fffffa80018627f0  fffffa80018617f0
   fffffa80018607f0  fffffa800185f7f0  fffffa800185e7f0  fffffa800185d7f0
   fffffa800185c730  fffffa800186d190  fffffa8002614b80  fffffa8002614da0
   fffffa8002291200  fffffa8002291420  fffffa80022916f0  fffffa80018515a0

0: kd> !drvobj pci
   Driver object (fffffa8002291960) is for: \Driver\pci  <-----// создаёт 20 устройств
   Device object list:
   ----------------------------------
   fffffa80022b6a10  fffffa80022449f0  fffffa8002244040  fffffa8001864cb0
   fffffa8001862a10  fffffa8001862060  fffffa8001861a10  fffffa8001861060
   fffffa8001860a10  fffffa8001860060  fffffa800185fa10  fffffa800185f060
   fffffa800185ea10  fffffa800185e060  fffffa800185da10  fffffa800185d060
   fffffa800185ca10  fffffa800185c060  fffffa800185b900  fffffa8001857750

0: kd> !cmkd.kvas fffffa80022b6a10  <----------// Kernel Virtual Address Space
   ###  Start              End                Length         Type
   011  fffffa80`01600000  fffffa80`5e5fffff  5d000000 (1GB) NonPagedPool  <----// нельзя выгружать в своп

0: kd>


3. Стеки драйверов

Ещё одна базовая концепция драйвера – это стек (не путать с программным стеком).
В модели драйверов WDM каждое аппаратное устройство имеет как минимум 2 драйвера устройств. Первый числится в литературе как FDO «Functional Device Object», а второй PDO или «Physical Device». Есть ещё FiDO «Filter Driver», но он прилагается в качестве бонуса, без которого вполне можно обойтись. Выстраиваясь сверху-вниз в порядке FiDO-FDO-PDO, эти три драйвера и образуют свой стек.

Важно понять, что драйвер физ.устройства PDO в стеке единственный, а вот число функциональных драйверов FDO ограничено только фантазией разработчика. Однако следует учитывать, что чем глубже стек, тем дольше обрабатывается запрос. Поэтому Microsoft советует создавать стек не более чем из трёх драйверов, и в большинстве случаев на практике это именно так.

Полезный объём работы выполняет функциональный драйвер FDO – он в курсе всех тонкостей общения со-своим оборудованием. В свою очередь PDO обычно драйвер шины, но не всегда. Он находится на уровне ниже, и отвечает за программную поддержку подключения девайса, например, в слот PCI или порт USB. Таким образом PDO и FDO работают в тесном сотрудничестве, только выполняют разные задачи. Как сверху (upper), так и с низу (lower) драйвера FDO, в цепочку может вклиниться фильтр FiDO по большей части для того, чтобы с благими намерениями расширить его возможности.

Драйверы PDO входят в штатную поставку Win, а мы будем писать исключительно фильтры, или драйверы функций FDO. Для большинства известных устройств, Win имеет драйверы класса, которые (как и следует из их названия) отвечают за поддержку не одного, а целого класса устройств. Например для клавы и мыши имеются kbdclass и mouclass.sys, которым без разницы, проводной девайс или беспроводной. А вообще понятия PDO/FDO на логическом уровне сильно переплетаются – здесь всё зависит от типа устройства. То-есть, что для драйвера файловой системы является PDO, то для драйвера сетевой карты может преобразиться в FDO, и наоборот.

Диспетчер ввода-вывода передаёт пакет запроса IRP самому верхнему драйверу в стеке. Пощупав пакет (и может что-то сделав с ним) верхний драйвер передаёт его на уровень ниже, и т.д. При этом любой из FDO в цепочке может самостоятельно обработать, и таким образом прервать передачу на следующий уровень, просто послав диспетчеру сигнал IoCompleteRequest().

После создания устройства чз IoCreateDevice(), драйвер добавляет себя в стек уже существующих драйверов вызовом IoAttachDeviceToDeviceStackSafe(). Функция подключит нас самым первым на вершину стека устройств, и вернёт указатель на тот девайс, который раньше был первым. Последующие вызовы этой функции делают тоже самое – всегда аттачат девайс только на макушку стека. Возвращённый указатель является обязательным аргументом для функции передачи пакета IRP следующему драйверу IoCallDriver(). На случай, если мы забыли сохранить его при первом подключении в стек, можно потянуть за IoGetDeviceObjectPointer() – она возвращает линк на низлежащий драйвер.

Всё вышесказанное касается только многоуровневых драйверов, которые решают проблему сообща. Но в природе встречаются и монолитные драйверы – весь функционал они реализуют сами, и не нуждаются в посторонней поддержке. В эту категорию подпадают, например, драйверы различных контроллёров типа Arduino, или самопальных устройств эпохи неолита. Всё-что им нужно – это просто подключиться к корневой шине компьютера. Соответственно монолитные драйверы не имеют стека устройств, что представлено на рис.ниже. Здесь вариант, когда FDO устройства сам обработал запрос IRP, не передавая его на более низкие уровни.

DriverStack.png

Просмотреть стек драйверов в отладчике можно специальной командой !devstack, передав ей предварительно взятый у !drvobj указатель на объект устройства. Помнится наш подопытный кролик i8042prt.sys создавал 2 девайса, и теперь можем заглянуть в их стеки драйверов так. В данном случае acpi.sys является нижним PDO в стеке, i8042 это FDO, а класс-драйвера должны быть тоже FDO, но почему-то софт «PCI-Scope» считает, что это фильтры верхнего уровня FiDO. По аналогии можно зайти и в стек самого класс-драйвера, и обнаружить в качестве нижнего, диспетчера PnP:

Код:
0: kd> !drvobj i8042prt
Driver object (fffffa8002ac8e70) is for: \Driver\i8042prt
Device object list:  fffffa8002aa1800, fffffa8002ac1040

0: kd> !devstack fffffa8002aa1800
  !DevObj           !DrvObj            !DevExt           ObjectName
  fffffa8002aa2060  \Driver\mouclass   fffffa8002aa21b0  PointerClass0
> fffffa8002aa1800  \Driver\i8042prt   fffffa8002aa1950
  fffffa800224b750  \Driver\ACPI       fffffa80018686b0  00000077

0: kd> !devstack fffffa8002ac1040
  !DevObj           !DrvObj            !DevExt           ObjectName
  fffffa8002abe060  \Driver\kbdclass   fffffa8002abe1b0  KeyboardClass0
> fffffa8002ac1040  \Driver\i8042prt   fffffa8002ac1190
  fffffa800224b970  \Driver\ACPI       fffffa8001868a00  00000076

***********************************************************************

0: kd> !drvobj kbdclass
Driver object (fffffa8002a9f7c0) is for: \Driver\kbdclass
Device object list:  fffffa8002b03060, fffffa8002abe060

0: kd> !devstack fffffa8002b03060
  !DevObj           !DrvObj            !DevExt           ObjectName
> fffffa8002b03060  \Driver\kbdclass   fffffa8002b031b0  KeyboardClass1
  fffffa8002b04040  \Driver\TermDD     fffffa8002b04190
  fffffa80018f14d0  \Driver\PnpManager fffffa80018f1620  0000005c

ScopeKeyb.png

Если хотим копнуть глубже, можно запросить у команды !pcitree устройства на всех шинах PCI. У меня всего одна корневая Bus#0, и три подключаются к ней через мосты Bridge (в зависимости от топологии у вас может быть больше/меньше). В скобках первого столбца получим Device/Function на шине, во-втором столбце Vid/Did устройства, и в предпоследнем Class/Subclass. Вся эта информация хранится в конфигурационном пространстве «PCI-Config-Space». Далее прямо из этого лога можно просматривать стеки драйверов любых из перечисленных устройств – очень удобно:

Код:
0: kd> !pcitree
Bus 0x0 (FDO Ext fffffa80018578a0)
  (d=0,  f=0) 808629c0  devext 0xfffffa800185ba50  devstack 0xfffffa800185b900  0600  Bridge/HOST to PCI
  (d=2,  f=0) 808629c2  devext 0xfffffa800185c1b0  devstack 0xfffffa800185c060  0300  Display Controller/VGA
  (d=1b, f=0) 808627d8  devext 0xfffffa800185cb60  devstack 0xfffffa800185ca10  0403  Multimedia Device/Unknown Sub Class
  (d=1c, f=0) 808627d0  devext 0xfffffa800185d1b0  devstack 0xfffffa800185d060  0604  Bridge/PCI to PCI

Bus 0x1 (FDO Ext fffffa8001864e00)
  (d=1c, f=1) 808627d2  devext 0xfffffa800185db60  devstack 0xfffffa800185da10  0604  Bridge/PCI to PCI

Bus 0x2 (FDO Ext fffffa8002244190)
  (d=0,  f=0) 10ec8136  devext 0xfffffa80022b6b60  devstack 0xfffffa80022b6a10  0200  Network Controller/Ethernet
  (d=1d, f=0) 808627c8  devext 0xfffffa800185e1b0  devstack 0xfffffa800185e060  0c03  Serial Bus Controller/USB
  (d=1d, f=1) 808627c9  devext 0xfffffa800185eb60  devstack 0xfffffa800185ea10  0c03  Serial Bus Controller/USB
  (d=1d, f=2) 808627ca  devext 0xfffffa800185f1b0  devstack 0xfffffa800185f060  0c03  Serial Bus Controller/USB
  (d=1d, f=3) 808627cb  devext 0xfffffa800185fb60  devstack 0xfffffa800185fa10  0c03  Serial Bus Controller/USB
  (d=1d, f=7) 808627cc  devext 0xfffffa80018601b0  devstack 0xfffffa8001860060  0c03  Serial Bus Controller/USB
  (d=1e, f=0) 8086244e  devext 0xfffffa8001860b60  devstack 0xfffffa8001860a10  0604  Bridge/PCI to PCI

Bus 0x3 (FDO Ext fffffa8002244b40)
  (d=1f, f=0) 808627b8  devext 0xfffffa80018611b0  devstack 0xfffffa8001861060  0601  Bridge/PCI to ISA
  (d=1f, f=1) 808627df  devext 0xfffffa8001861b60  devstack 0xfffffa8001861a10  0101  Mass Storage Controller/IDE
  (d=1f, f=2) 808627c0  devext 0xfffffa80018621b0  devstack 0xfffffa8001862060  0101  Mass Storage Controller/IDE
  (d=1f, f=3) 808627da  devext 0xfffffa8001862b60  devstack 0xfffffa8001862a10  0c05  Serial Bus Controller/COM

Total PCI Root busses processed = 1
Total PCI Segments processed    = 1

0: kd> !devstack 0xfffffa8001862060
  !DevObj           !DrvObj           !DevExt           ObjectName
  fffffa80022c4060  \Driver\intelide  fffffa80022c41b0  PciIde0
  fffffa80018617f0  \Driver\ACPI      fffffa800186b360
> fffffa8001862060  \Driver\pci       fffffa80018621b0  NTPNP_PCI0013

0: kd> !devext 0xfffffa80018621b0
PDO Extension, Bus 0x0, Device 1f, Function 2.
  DevObj 0xfffffa8001862060, Parent FDO DevExt 0xfffffa80018578a0
  Device State = PciStarted
  VID 8086, DID 27C0
  Class Base/Sub 01/01 (Mass Storage Controller/IDE)
  ProgInterface: 8f, Revision: 01, IntPin: 02, RawLine 13
  Logical Device Power State:  D0
  Device Wake Level:           D3
  WaitWakeIrp:                <none>

  Requirements:     Alignment Length    Minimum          Maximum
    BAR0     Io:    00000008  00000008  0000000000000000 000000000000ffff
    BAR1     Io:    00000004  00000004  0000000000000000 000000000000ffff
    BAR2     Io:    00000008  00000008  0000000000000000 000000000000ffff
    BAR3     Io:    00000004  00000004  0000000000000000 000000000000ffff
    BAR4     Io:    00000010  00000010  0000000000000000 000000000000ffff
  Resources:        Start             Length
    BAR0     Io:    000000000000d080  00000008
    BAR1     Io:    000000000000d000  00000004
    BAR2     Io:    000000000000cc00  00000008
    BAR3     Io:    000000000000c880  00000004
    BAR4     Io:    000000000000c800  00000010
  Interrupt Resource: Type - LineBased, Irq = 0x13

0: kd>

Раньше лежала крутая утилита для сбора информации о драйверах "DeviceTree", но сейчас почему-то линк указывает в никуда. Не смотря на то, что интерфейс её пахнет нафталином, функционал конечно мощный, ..а главное есть поддержка х64 (по крайней мере на моей Win7 воркает). На всякий положу её в скрепку – можете потестить. Парсит содержимое структур DRIVER & DEVICE_OBJECT, и даже видит нижнее от себя Attached устройство в стеке:

OsrDevTree.png


Заключение

Следующие две части полностью посвятим практике – сначала напишем Legacy драйвер, а потом и WDM. Особое внимание уделим отложенным вызовам DPC, объектам синхронизации типа Mutant, блокировкам доступа к структурам SpinLock, работе с памятью, и конечно взаимодействию из кернела с пользовательским софтом. Всем удачи, пока!

i8042prt fdo-driver:


Keyb&Mouse PS/2 class-driver:


Keyb&Mouse USB class-driver:
 

Вложения

  • DeviceTree.zip
    1,2 МБ · Просмотры: 202
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!