Драйверы режима ядра: Часть 6: Базовая техника: Работа с памятью. Использование системных куч

Дата публикации 2 окт 2003

Драйверы режима ядра: Часть 6: Базовая техника: Работа с памятью. Использование системных куч — Архив WASM.RU

В этой и нескольких следующих статьях будут рассмотрены некоторые базовые техники, без которых трудно представить себе создание мало-мальски полезного драйвера. Причем, я хочу подчеркнуть слово "некоторые", т.к. драйверы могут очень многое. Не всё что они могут, можно отнести к базовой технике, но даже того, что можно отнести к таковой будет достаточно для того, чтобы еще очень долго иметь возможность привлекать Ваше внимание к моей скромной персоне. Не уверен, что у меня хватит на это сил. Поэтому круг вопросов, которых мы коснемся, будет все же ограничен.

Поскольку умение работать с памятью является абсолютно необходимым и без освещения этого вопроса, хотя бы в общих чертах, невозможно перейти к остальным, то с него и начнем.

Пользовательским процессам система, точнее диспетчер памяти (Memory Manager), предоставляет довольно богатый API, для работы с памятью, в который входят три группы функций: операции со страницами виртуальной памяти, проецирование файлов в память и управление кучами (динамически распределяемыми областями памяти).

Компоненты ядра, и драйверы в том числе, имеют несколько более широкие возможности. Например, драйвер может выделить диапазон памяти непрерывный в физическом пространстве адресов. Имена функций предоставляемых диспетчером памяти драйверам начинаются с префикса Mm. Кроме того, существует набор функций исполнительной системы (Executive) для выделения и освобождения памяти из системных куч и для манипуляции ассоциативными списками (память из ассоциативных списков выделяется быстрее, чем из кучи, но только блоками заранее определенного фиксированного размера). Имена таких функций начинаются с префикса Ex.

6.1 Системные кучи

Системные кучи (к пользовательским кучам не имеют никакого отношения) представлены двумя так называемыми пулами памяти, которые, естественно, располагаются в системном адресном пространстве:

  • Пул неподкачиваемой памяти (Nonpaged Pool). Назван так потому, что его страницы никогда не сбрасываются в файл подкачки, а значит, никогда и не подкачиваются назад. Т.е. этот пул всегда присутствует в физической памяти и доступен при любом IRQL. Одна из причин его существования в том, что обращение к такой памяти не может вызвать ошибку страницы (Page Fault). Такие ошибки приводят к краху системы, если происходят при IRQL >= DISPATCH_LEVEL.
  • Пул подкачиваемой памяти (Paged Pool). Назван так потому, что его страницы могут быть сброшены в файл подкачки, а значит должны быть подкачаны назад при последующем к ним обращении. Эту память можно использовать только при IRQL строго меньше DISPATCH_LEVEL.

Оба пула находятся в системном адресном пространстве, а значит, доступны из контекста любого процесса. Для выделения памяти в системных пулах существует набор функций ExAllocatePoolXxx, а для возвращения выделенной памяти всего одна - ExFreePool.

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

  1. Обращение к памяти сброшенной в файл подкачки при IRQL >= DISPATCH_LEVEL, как я уже говорил, приводят к краху системы.

    Имейте в виду, что если в момент обращения к подкачиваемой памяти она физически присутствует, то краха не будет, даже при IRQL >= DISPATCH_LEVEL. Но можете быть уверены, что рано или поздно ее не окажется на месте и тогда BSOD обеспечен


  2. Не стоит использовать неподкачиваемую память везде, где не попадя. Этот ресурс дороже, чем подкачиваемая память. Забирая его себе, Вы тем самым отнимаете его у тех, кому он нужен, возможно, больше чем Вам.


  3. Если Вы выделили память из любого системного пула, то вне зависимости от того, что дальше случится с Вашим драйвером, эта память не будет возвращена системе назад до тех пор, пока Вы явно не вызовите ExFreePool. Т.е. если драйвер не освободит выделенную ему память явно, то она так и останется бесполезно болтаться в системе, даже если драйвер будет выгружен. Я уже неоднократно говорил, что все выделяемые драйвером ресурсы должны быть явно возвращены назад.


  4. Выделяемая из системных пулов память не очищается системой (не заполняется нулями) и, возможно, будет содержать "мусор", оставленный предыдущими владельцами. Если Вы намереваетесь выполнять какие-то критичные к этому операции, например, манипулировать строками, то лучше ее явно очистить перед использованием.

Определить какой тип памяти (подкачиваемая или неподкачиваемая) Вам нужен очень просто. Если какой-либо код должен обращаться к памяти при IRQL >= DISPATCH_LEVEL, то нужно использовать только неподкачиваемую память. Причем, как сам код, так и данные должны располагаться в неподкачиваемой памяти. По умолчанию весь драйвер загружается в неподкачиваемую память, кроме секции с именем "INIT" и секций, имена которых начинаются с "PAGE". Если кроме этого вы не предпринимали никаких действий по изменению атрибутов памяти принадлежащих драйверу, например, не вызывали функцию MmPageEntireDriver, делающую весь образ драйвера подкачиваемым, то о самом драйвере беспокоится не стоит - он всегда будет присутствовать в физической памяти.

В предыдущих статьях мы достаточно подробно разобрали, при каком IRQL вызываются стандартные процедуры (DriverEntry, DriverUnload, DispatchXxx) драйвера.

Кроме того, в DDK в описании каждой функции указано при каком IRQL ее можно вызывать или при каком IRQL она вызывается системой, если это функция обратного вызова (callback). Например, в одной из следующих статей мы будем использовать функцию IoInitializeTimer. В описании этой функции сказано, что процедура, вызываемая системой при срабатывании таймера, выполняется при IRQL = DISPATCH_LEVEL. Это недвусмысленно говорит нам о том, что эта процедура и любая память, к которой она будет обращаться должны быть неподкачиваемыми.

На худой конец, если уж Вы совсем не уверены в текущем IRQL, его можно определить функцией KeGetCurrentIrql таким образом:

Код (Text):
  1.  
  2.  
  3.  invoke KeGetCurrentIrql
  4.  .if eax < DISPATCH_LEVEL
  5.      ; используем любую память
  6.  .else
  7.      ; используем только неподкачиваемую память
  8.  .endif
  9.  
  10.  

6.2 Выделяем память из системного пула

В качестве примера рассмотрим очень простой драйвер SystemModules. Все действо будет происходить в процедуре DriverEntry. Мы быстренько выделим немного подкачиваемой памяти (Вы, несомненно, помните, что DriverEntry работает на IRQL = PASSIVE_LEVEL. Поэтому мы обойдемся подкачиваемой памятью.), что-нибудь полезное в нее запишем, освободим и заставим систему выгрузить драйвер.

Для экономии места я привожу только исходный текст процедуры DriverEntry. Это собственно и есть весь драйвер.

Код (Text):
  1.  
  2.  
  3.  ;@echo off
  4.  ;goto make
  5.  
  6.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  7.  ;
  8.  ;  SystemModules - Выделяем память из системного пула и используем её.
  9.  ;
  10.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  11.  
  12.  .386
  13.  .model flat, stdcall
  14.  option casemap:none
  15.  
  16.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  17.  ;                              В К Л Ю Ч А Е М Ы Е    Ф А Й Л Ы                                    
  18.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  19.  
  20.  include \masm32\include\w2k\ntstatus.inc
  21.  include \masm32\include\w2k\ntddk.inc
  22.  include \masm32\include\w2k\native.inc
  23.  include \masm32\include\w2k\ntoskrnl.inc
  24.  
  25.  includelib \masm32\lib\w2k\ntoskrnl.lib
  26.  
  27.  include \masm32\Macros\Strings.mac
  28.  
  29.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  30.  ;               В Ы Г Р У Ж А Е М Ы Й   П Р И   Н Е О Б Х О Д И М О С Т И   К О Д                  
  31.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  32.  
  33.  .code INIT
  34.  
  35.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  36.  ;                                       DriverEntry                                                
  37.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  38.  
  39.  DriverEntry proc uses esi edi ebx pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING
  40.  
  41.  local cb:DWORD
  42.  local p:PVOID
  43.  local dwNumModules:DWORD
  44.  local pMessage:LPSTR
  45.  local buffer[256+40]:CHAR
  46.  
  47.      invoke DbgPrint, $CTA0("\nSystemModules: Entering DriverEntry\n")
  48.  
  49.      and cb, 0
  50.      invoke ZwQuerySystemInformation, SystemModuleInformation, addr p, 0, addr cb
  51.      .if cb != 0
  52.  
  53.          invoke ExAllocatePool, PagedPool, cb
  54.          .if eax != NULL
  55.              mov p, eax
  56.  
  57.              invoke DbgPrint, \
  58.                     $CTA0("SystemModules: %u bytes of paged memory allocted at address %08X\n"), cb, p
  59.  
  60.              invoke ZwQuerySystemInformation, SystemModuleInformation, p, cb, addr cb
  61.              .if eax == STATUS_SUCCESS
  62.                  mov esi, p
  63.  
  64.                  push dword ptr [esi]
  65.                  pop dwNumModules
  66.  
  67.                  mov cb, (sizeof SYSTEM_MODULE_INFORMATION.ImageName + 100)*2
  68.  
  69.                  invoke ExAllocatePool, PagedPool, cb
  70.                  .if eax != NULL
  71.                      mov pMessage, eax
  72.  
  73.                      invoke DbgPrint, \
  74.                             $CTA0("SystemModules: %u bytes of paged memory allocted at address %08X\n"), \
  75.                             cb, pMessage
  76.  
  77.                      invoke memset, pMessage, 0, cb
  78.  
  79.                      add esi, sizeof DWORD
  80.                      assume esi:ptr SYSTEM_MODULE_INFORMATION
  81.  
  82.                      xor ebx, ebx
  83.                      .while ebx < dwNumModules
  84.  
  85.                          lea edi, [esi].ImageName
  86.                          movzx ecx, [esi].ModuleNameOffset
  87.                          add edi, ecx
  88.  
  89.                          invoke _strnicmp, edi, $CTA0("ntoskrnl.exe", szNtoskrnl, 4), sizeof szNtoskrnl - 1
  90.                          push eax
  91.                          invoke _strnicmp, edi, $CTA0("ntice.sys", szNtIce, 4), sizeof szNtIce - 1
  92.                          pop ecx
  93.  
  94.                          and eax, ecx
  95.                          .if ZERO?
  96.                              invoke _snprintf, addr buffer, sizeof buffer, \
  97.                                      $CTA0("SystemModules: Found %s base: %08X size: %08X\n", 4), \
  98.                                      edi, [esi].Base, [esi]._Size
  99.                              invoke strcat, pMessage, addr buffer
  100.                          .endif
  101.  
  102.                          add esi, sizeof SYSTEM_MODULE_INFORMATION
  103.                          inc ebx
  104.  
  105.                      .endw
  106.                      assume esi:nothing
  107.  
  108.                      mov eax, pMessage
  109.                      .if byte ptr [eax] != 0
  110.                          invoke DbgPrint, pMessage
  111.                      .else
  112.                          invoke DbgPrint, \
  113.                                 $CTA0("SystemModules: Found neither ntoskrnl nor ntice.\n")
  114.                      .endif
  115.  
  116.                      invoke ExFreePool, pMessage
  117.                      invoke DbgPrint, $CTA0("SystemModules: Memory at address %08X released\n"), pMessage
  118.  
  119.                  .endif
  120.              .endif
  121.  
  122.              invoke ExFreePool, p
  123.              invoke DbgPrint, $CTA0("SystemModules: Memory at address %08X released\n"), p
  124.  
  125.          .endif
  126.      .endif
  127.  
  128.      invoke DbgPrint, $CTA0("SystemModules: Leaving DriverEntry\n")
  129.  
  130.      mov eax, STATUS_DEVICE_CONFIGURATION_ERROR
  131.      ret
  132.  
  133.  DriverEntry endp
  134.  
  135.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  136.  ;                                                                                                  
  137.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  138.  
  139.  end DriverEntry
  140.  
  141.  :make
  142.  
  143.  set drv=SystemModules
  144.  
  145.  \masm32\bin\ml /nologo /c /coff %drv%.bat
  146.  \masm32\bin\link /nologo /driver /base:0x10000 /align:32 /out:%drv%.sys /subsystem:native /ignore:4078 %drv%.obj
  147.  
  148.  del %drv%.obj
  149.  
  150.  echo.
  151.  pause
  152.  
  153.  

В качестве чего-нибудь полезного мы возьмем список модулей загруженных в системное адресное пространство (в этот список войдут модули самой системы: ntoskrnl.exe, hal.dll и т.п. и драйверы устройств) и попытаемся найти в нем два модуля: ntoskrnl.exe и ntice.sys. Список системных модулей можно получить, вызвав функцию ZwQuerySystemInformation с информационным классом SystemModuleInformation. Описание этой функции можно найти в книге Гэри Неббета "Справочник по базовым функциям API Windows NT/2000". Кстати, ZwQuerySystemInformation уникальная функция. С её помощью можно получить просто огромное количество самой различной информации о системе.

Программы управления драйвером не будет. Используйте KmdManager (входит в пакет KmdKit) или аналогичную утилиту, а отладочные сообщения, выдаваемые драйвером, контролируйте с помощью утилиты DebugView (www.sysinternals.com) или консоли SoftICE.

Код (Text):
  1.  
  2.  
  3.      and cb, 0
  4.      invoke ZwQuerySystemInformation, SystemModuleInformation, addr p, 0, addr cb
  5.  
  6.  

Для начала нам нужно определить, сколько места будет занимать интересующая нас информация. Вызвав ZwQuerySystemInformation так, как показано выше, мы получим STATUS_INFO_LENGTH_MISMATCH (что вполне естественно, т.к. размер переданного буфера равен нулю), а в переменной cb мы получим искомое количество байт. Таким способом можно узнать какой размер буфера необходим. Адрес переменной p, в данном случае, нужен только для нормальной работы этой функции: по нему все равно ничего записано не будет.

Код (Text):
  1.  
  2.  
  3.      .if cb != 0
  4.          invoke ExAllocatePool, PagedPool, cb
  5.  
  6.  

Если размер требуемого буфера не равен нулю, мы выделяем необходимое количество памяти из подкачиваемого пула (об этом говорит первый параметр - PagedPool. Значение NonPagedPool будет означать запрос неподкачиваемой памяти). Функция ExAllocatePool даже проще чем ее аналог режима пользователя HeapAlloc. Всего два параметра. Первый определяет пул: подкачиваемый или неподкачиваемый, второй - размер требуемой памяти. Проще не бывает.

Код (Text):
  1.  
  2.  
  3.          .if eax != NULL
  4.  
  5.  

Если ExAllocatePool вернет ненулевое значение, то это указатель на выделенный буфер.

Когда будете изучать отладочную информачию выводимую драйвером в окно DebugView, обратите внимание на то, что адрес буфера, возвращенный ExAllocatePool, будет кратен размеру страницы. Дело в том, что если размер запрашиваемой памяти больше или равен размеру страницы (а в данном случае размер требуемой памяти значительно больше одной страницы), то выделенная область памяти будет начинаться с новой страницы.

Код (Text):
  1.  
  2.  
  3.              mov p, eax
  4.              invoke ZwQuerySystemInformation, SystemModuleInformation, p, cb, addr cb
  5.  
  6.  

Сохраняем указатель на выделенный буфер в переменной p и вызываем ZwQuerySystemInformation еще раз, передавая ей указатель на буфер и его размер.

Код (Text):
  1.  
  2.  
  3.              .if eax == STATUS_SUCCESS
  4.                  mov esi, p
  5.  
  6.  

Если ZwQuerySystemInformation возвращает STATUS_SUCCESS, то её удовлетворили параметры нашего буфера и теперь он содержит список системных модулей в виде массива структур SYSTEM_MODULE_INFORMATION (определена в файле include\w2k\native.inc).

Код (Text):
  1.  
  2.  
  3.  SYSTEM_MODULE_INFORMATION STRUCT        ;Information Class 11
  4.      Reserved            DWORD   2 dup(?)
  5.      Base                PVOID   ?
  6.      _Size               DWORD   ?
  7.      Flags               DWORD   ?
  8.      Index               WORD    ?
  9.      Unknown             WORD    ?
  10.      LoadCount           WORD    ?
  11.      ModuleNameOffset    WORD    ?
  12.      ImageName           CHAR 256 dup(?)
  13.  SYSTEM_MODULE_INFORMATION ENDS
  14.  
  15.  

Действительное количество байт скопированных в буфер вернется в переменной cb, но нам оно не нужно.

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

Код (Text):
  1.  
  2.  
  3.                  push dword ptr [esi]
  4.                  pop dwNumModules
  5.  
  6.  

В самом первом двойном слове буфера, заполненного ZwQuerySystemInformation, содержится количество структур SYSTEM_MODULE_INFORMATION равное количеству модулей и сразу за ним (двойным словом) начинается их массив. Запоминаем количество модулей в переменной dwNumModules.

Код (Text):
  1.  
  2.  
  3.                  mov cb, (sizeof SYSTEM_MODULE_INFORMATION.ImageName + 100)*2
  4.                  invoke ExAllocatePool, PagedPool, cb
  5.                  .if eax != NULL
  6.                      mov pMessage, eax
  7.  
  8.  

Для дальнейшей плодотворной работы нам потребуется еще один буфер, в который будут помещаться имена двух искомых модулей и кое-какая дополнительная информация. Мы предполагаем, что (sizeof SYSTEM_MODULE_INFORMATION.ImageName + 100)*2 должно как раз хватить.

Обратите внимание на адрес буфера - он не будет кратен размеру страницы, т.к. его размер меньше страницы.

Код (Text):
  1.  
  2.  
  3.                      invoke memset, pMessage, 0, cb
  4.  
  5.  

На всякий случай затрем буфер нулями т.к. мы собираемся заполнить его символьной информацией. Это гарантирует нам, что строки будут завершаться нулями.

Код (Text):
  1.  
  2.  
  3.                      add esi, sizeof DWORD
  4.                      assume esi:ptr SYSTEM_MODULE_INFORMATION
  5.  
  6.  

Пропускаем DWORD содержащий число модулей и регистр esi теперь указывает на первую структуру SYSTEM_MODULE_INFORMATION.

Код (Text):
  1.  
  2.  
  3.                      xor ebx, ebx
  4.                      .while ebx < dwNumModules
  5.  
  6.  

Организуем цикл, повторяющийся dwNumModules раз. В цикле перебираем массив структур SYSTEM_MODULE_INFORMATION и ищем там структуры соответствующие модулям ntoskrnl.exe и ntice.sys.

В многопроцессорной системе модуль ntoskrnl.exe будет иметь имя ntkrnlmp.exe. А в системе с поддержкой PAE - ntkrnlpa.exe и ntkrpamp.exe, соответственно. Здесь я предполагаю, Вы не являетесь счастливым обладателем подобной системы.

Код (Text):
  1.  
  2.  
  3.                          lea edi, [esi].ImageName
  4.                          movzx ecx, [esi].ModuleNameOffset
  5.                          add edi, ecx
  6.  
  7.  

Поля ImageName и ModuleNameOffset содержат полный путь к модулю и относительное смещение имени модуля в пути, соответственно.

Код (Text):
  1.  
  2.  
  3.                          invoke _strnicmp, edi, $CTA0("ntoskrnl.exe", szNtoskrnl, 4), sizeof szNtoskrnl - 1
  4.                          push eax
  5.                          invoke _strnicmp, edi, $CTA0("ntice.sys", szNtIce, 4), sizeof szNtIce - 1
  6.                          pop ecx
  7.  
  8.  

Поиск осуществляем простым сравнением имен модулей. Функция _strnicmp сравнивает две ANSI-строки независимо от регистра букв. Сравнивает она только то количество символов, которое передано в третьем параметре. В данном случае это не обязательно, т.к. имена модулей в структурах SYSTEM_MODULE_INFORMATION завершаются нулями и можно было бы использовать _stricmp. Я использую _strnicmp для пущей безопастности.

Кстати, ntoskrnl.exe экспортирует большое количество стандартных функций по работе со строками: strcmp, strcpy, strlen и т.п.

Код (Text):
  1.  
  2.  
  3.                          and eax, ecx
  4.                          .if ZERO?
  5.                              invoke _snprintf, addr buffer, sizeof buffer, \
  6.                                      $CTA0("SystemModules: Found %s base: %08X size: %08X\n", 4), \
  7.                                      edi, [esi].Base, [esi]._Size
  8.                              invoke strcat, pMessage, addr buffer
  9.                          .endif
  10.  
  11.                          add esi, sizeof SYSTEM_MODULE_INFORMATION
  12.                          inc ebx
  13.  
  14.                      .endw
  15.                      assume esi:nothing
  16.  
  17.  

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

Цифра 4 в макросах $CTA0 означает, что определяемая ими строка выравнивается по границе DWORD (здесь этого можно и не делать). А метки szNtoskrnl и szNtIce нужны для того, чтобы передать их в директиву sizeof. Кстати, вы можете менять местами метку и выравнивание в моих строковых макросах - они распознаются автоматически. Либо можете использовать только метку или только выравнивание (подробнее см. macros\Strings.mac).

Код (Text):
  1.  
  2.  
  3.                      mov eax, pMessage
  4.                      .if byte ptr [eax] != 0
  5.                          invoke DbgPrint, pMessage
  6.                      .else
  7.                          invoke DbgPrint, \
  8.                                  $CTA0("SystemModules: Found neither ntoskrnl nor ntice. Is it possible?\n")
  9.                      .endif
  10.  
  11.  

Поскольку перед использованием мы обнулили буфер, то если первый его байт не равен нулю, значит, мы что-то нашли. Выводим содержимое буфера.

Код (Text):
  1.  
  2.  
  3.                      invoke ExFreePool, pMessage
  4.                  .endif
  5.              .endif
  6.              invoke ExFreePool, p
  7.          .endif
  8.      .endif
  9.  
  10.  

Возвращаем выделенную из системных пулов память.

Код (Text):
  1.  
  2.  
  3.      mov eax, STATUS_DEVICE_CONFIGURATION_ERROR
  4.      ret
  5.  
  6.  

Заставляем систему выгрузить драйвер.

Как видите, использование системных пулов даже проще, чем работа с кучами в режиме пользователя. Задача только правильно определить тип пула.

Многие ZwXxx функции экспортируются также библиотекой ntdll.dll режима пользователя и являются простыми переходниками в режим ядра, где и происходит вызов соответствующей функции, которая и проделывает всю полезную работу. Примечательно то, что количество параметров и их смысл полностью совпадают. Из этой ситуации можно извлечь большую выгоду. Поскольку ошибки в ядре приводят к краху системы, можно сначала отладить код в режиме пользователя, а потом перенести его в драйвер с минимальными изменениями, а иногда даже и без изменений. Например, в нашем случае, будучи вызвана из ntdll.dll в режиме пользователя, функция ZwQuerySystemInformation вернет ту же самую информацию, что и одноименная функция, вызванная из ntoskrnl.exe в драйвере. Пользуясь этим нехитрым приемом, можно сэкономить немалое число перезагрузок.

Исходный код драйвера в архиве. Для компиляции требуется последняя версия KmdKit - берите на сайте.

© Four-F

0 1.805
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532