EreTIk's Box » Cтатьи, исходники » Реализация клиентского RPC-вызова из драйвера режима ядра: запуск и останов системных сервисов


Очень часто можно наблюдать случаи, когда вместе с некоторым драйвером поставляется, например, системный сервис. Это зачастую обусловлено тем, что несмотря на то, что в драйвере Windows разработчик обладает широкими возможностями и привилегиями, он достаточно серьезно ограничен предоставляемым ему API. Если для работы с подсистемами ввода/вывода, дерева объектов, безопасности и т.п. уже давно написано немалое количество оберток, то остается еще одно "сугубо user-mod’ное" место - RPC: Remote Procedure Call. Отчасти это связано с тем, что вся основная реализация RPC-вызова находится не в ядре, а во внешнем процессе (RPC-сервере), который в общем случае может исполняться на удаленной машине. И до некоторого времени Microsoft не предоставляла никаких средств для работы с механизмами RPC в ядре. Но не так давно мне на глаза попался интересная kernel-mode библиотека msrpc.sys. И вот наконец-то у меня дошли руки ее опробовать, о чем далее и пойдет речь.


Начиная с Windows Vista, среди системных драйверов появилась уже упомянутая kernel-библиотека msrpc.sys. Если взглянуть на экспорты этой библиотеки, то мы увидим набор функций для реализации клиентского RPC-вызова. К сожалению, RPC-сервер на ее основе создать не получится, но кто знает, что будет в будущих версиях ОС. Для того, что бы продемонстрировать ее работу я решил поставить достаточно злободневную задачу – запуск и останов системных сервисов из ядра. Конечно, формат хранимых в реестре данных о сервисах уже давно известен и "проинсталлировать" системный сервис, который запустится в следующую загрузку системы - не проблема. Но запустить системный сервис немедленно - это всегда было головной болью kernel-разработчика. Как правило, эта задача решается передачей некоторой команды своему user-mode процессу, который уже общается с SCM через advapi32.dll.


В реализации поставленной задачи сильно поможет то, что Microsoft начала открывать протоколы RPC-взаимодействия со своими системными сервисами. В том числе и с SCM: в MSDN опубликован полный IDL-файл для вызова SCM-функций - интерфейс svcctl. Если взглянуть на него чуть внимательней, то можно понять, что слово полный стоит понимать в лучших традициях Microsoft: несколько функций имеют имена OpnumDDNotUsedOnWire. Настощие имена этих функций можно получить прямо в WinDbg из символов, например моим скрипом RpcServer_IfHandle.dcmd. В примере приведен разбор интерфейса svcctl (SCM-функций), а недокументированные функции сразу бросаются в глаза префиксом RI_. Параметры вызова этих функций несложно получить при помощи IDA, но для реализации запуска и останова системных сервисов эти функции нам не нужны, поэтому я не буду здесь их приводить.


Демонстрационный проект, состоящий из консольного приложения и драйвера режима ядра, я назвал KScProxy. Общая архитектура проекта довольно проста: консольное приложение kcs.exe распаковывает и стартует драйвер KScProxy.sys. Затем, в зависимости от параметров командной строки, процесс отсылает запрос управляющему устройству драйвера либо на старт, либо на останов указанного в командной строке системного сервиса. В свою очередь драйвер, используя библиотеку msrpc.sys, отсылает клиентский RPC-запрос в services.exe. Именно в services.exe реализован RPC-сервер svcctl (SCM).


Для того, что бы собрать драйвер, реализующий клиентский RPC-вызов, нужно получить stub-файлы из IDL, используя компилятор MIDL. Первой строчкой svcctl.idl идет использование файла ms-dtyp.idl, который так же можно скачать из статьи MSDN: Full MS-DTYP IDL. В этом файле представлены объявления необходимых типов. Но генерировать по нему h-файл не обязательно, так как все эти типы описаны в заголовочных файлах SDK/DDK. Для себя я создал пустой файл ms-dtyp.h, что бы не исправлять файлы, сгенерированные компилятором midl. Для сборки stub-файлов можно использовать следующий командный файл ~\KScProxy\midl-svcctl\midl-svcctl.cmd (здесь и далее путь c префиксом ~\ указывает в директорию \KScProxy\@src\ архива KScProxy.rar):


midl /server none /win32 /cstub svcctl_i386.c /h svcctl.h svcctl.idl midl /server none /x64 /cstub svcctl_amd64.c /h svcctl.h svcctl.idl

На выходе будут сгенерированы 3-и файла:

  • svcctl_i386.c - реализация клиентского stub-файла для платформы i386
  • svcctl_amd64.c - реализация клиентского stub-файла для платформы amd64
  • svcctl.h - заголовочный клиентский файл

Сгенерированные c-файлы имеют препроцессор, включающий или исключающий содержимое файла по дерективе_M_AMD64. Об этой директиве можно почитать в статье MSDN Magazine: Everything You Need To Know To Start Programming 64-Bit Windows Systems.


Так как заголовочный файл не отличается для платформ i386 и amd64, то можно генерировать его единожды (в приведенном примере второй вызов midl пересоздаст файл svcctl.h). В этом же заголовочном файле следует включение SDK-файлов rpc.h и rpcndr.h. Это порождает множество ошибок компиляции. Но если посмотреть на содержимое этого файла, то одной из причин не включать файл windows.h будет объявленное определение _KRPCENV_. Как несложно догадаться из названия это указание на использование RPC в режиме ядра. Хотя это нигде не документировано, но именно определение _KRPCENV_ позволяет включать файлы rpc.h и rpcndr.h при компиляции драйверов.


Но, к сожалению, lib-файла для использования msrpc.sys Microsoft не предоставляет. Поэтому при попытке собрать драйвер мы получаем ошибку линкера о неизвестной функции NdrClientCall2. Выше я уже привел файл экспортов msrpc.sys. На его основе, что бы не реализовывать динамический импорт, несложно сгенерировать нужные lib-файлы: ~\KScProxy\lib-msrpc\i386\msrpc.lib и ~\KScProxy\lib-msrpc\amd64\msrpc.lib. Небольшая особенность заключается в том, что msrpc.sys предоставляет только UNICODE-версии функций, что не является серьезным минусом для разработчиков драйверов.


На этом ошибки линкера не заканчиваются, не найдены функции:

  • MIDL_user_allocate
  • MIDL_user_free
  • SVCCTL_HANDLEW_bind
  • SVCCTL_HANDLEW_unbind
  • SVCCTL_HANDLEA_bind
  • SVCCTL_HANDLEA_unbind

С первыми двумя функциями вопросов возникнуть не должно, это стандартные функции, реализующие выделение/освобождение памяти. Их можно реализовать вызовами выделения и освобождения памяти из Paged-пула.


А вот функции *_[un]bind должны реализовывать создание RPC-описателя и привязку этого описателя к серверному RPC-интерфейсу. Так как перед нами не стоит задачи управления сервисами на удаленной машине, реализация этих функций сильно упрощается. Статья в MSDN Selecting a Protocol Sequence предлагает использовать функции RpcBindingFromStringBinding(...) и RpcStringBindingCompose(...), которые не экспортируются из msrpc.sys. Поэтому создание и привязку RPC-описателей следует реализовывать семейством функций RpcBindingXxx(...):


Отмечу, что я не использовал описанный в MSDN RPC well-known endpoint для svcctl. Вместо именованного канала svcctl я предпочел [A]LPC-порт ntsvcs. Именованный канал стоит использовать при доступе к удаленной машине, так как механизм [A]LPC-портов локальный, или для более ранних версий Windows, где процесс services.exe еще не создавал LPC-endpoint ntsvcs.


Думаю, что не нужно останавливаться на важности заполнения Security-QoS параметров описателя. В большинстве случае заранее известен SID RPC-сервера (в нашем случае SYSTEM: S-1-5-18). Так же не стоит давать RPC-серверу возможность имперсонации по клиенту, без явной необходимости. Из особенностей создания RPC-описателя хочу выделить то, что хотя параметр Security функции RpcBindingCreate(...) объявлен опциональным, реализация в msrpc.sys содержит следующий код:


.000165F8:  xor         ebx,ebx

.00016617:  mov         esi,[ebp][00C]  ; esi <- Security 
.0001661A:  cmp         esi,ebx         ; if (!Security)
.0001661C:  jz         .000016694       ;   return STATUS_INVALID_PARAMETER

.00016694:  mov         eax,0C000000D
.00016699:  pop         edi
.0001669A:  pop         esi
.0001669B:  pop         ebx
.0001669C:  leave
                

То есть при вызове msrpc.sys!RpcBindingCreateW обязательно указывать опции безопасности. Так же эта функция вернет STATUS_INVALID_PARAMETER, если не заполнен одно из полей RPC_BINDING_HANDLE_SECURITY_V1.ServerPrincName или Sid в QoS (это поле появилось начиная с третьей версии структуры). За это отвечает неэкспортируемая функция msrpc.sys!ValidateSecurityQOS


Стоит так же отметить, что примеры использования msrpc.sys (дизассемблерные листинги win32k.sys, ntfs.sys и т.п.) всегда используют асинхронную модель вызова RPC-сервера. Остальной код драйвера и консольного приложения достаточно очевиден. Поэтому, после успешно сборки, перейдем к тестированию. Для тестирования будем использовать следующий командный файл:


sc query vds sc query w32time echo -------------------------------------------------------------------------- ksc start vds ksc stop w32time echo -------------------------------------------------------------------------- sc query vds sc query w32time

Результат его исполнения показывает работоспособность проекта (протестировано на i386 и amd64 платформах):


SERVICE_NAME: vds TYPE : 10 WIN32_OWN_PROCESS STATE : 1 STOPPED WIN32_EXIT_CODE : 0 (0x0) SERVICE_EXIT_CODE : 0 (0x0) CHECKPOINT : 0x0 WAIT_HINT : 0x0 SERVICE_NAME: w32time TYPE : 20 WIN32_SHARE_PROCESS STATE : 4 RUNNING (STOPPABLE, NOT_PAUSABLE, ACCEPTS_SHUTDOWN) WIN32_EXIT_CODE : 0 (0x0) SERVICE_EXIT_CODE : 0 (0x0) CHECKPOINT : 0x0 WAIT_HINT : 0x0 -------------------------------------------------------------------------- svcctl-proxy over kernel, ver. 0.1.0.0 Operation successfully finished svcctl-proxy over kernel, ver. 0.1.0.0 Operation successfully finished -------------------------------------------------------------------------- SERVICE_NAME: vds TYPE : 10 WIN32_OWN_PROCESS STATE : 4 RUNNING (STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN) WIN32_EXIT_CODE : 0 (0x0) SERVICE_EXIT_CODE : 0 (0x0) CHECKPOINT : 0x0 WAIT_HINT : 0x0 SERVICE_NAME: w32time TYPE : 20 WIN32_SHARE_PROCESS STATE : 1 STOPPED WIN32_EXIT_CODE : 0 (0x0) SERVICE_EXIT_CODE : 0 (0x0) CHECKPOINT : 0x0 WAIT_HINT : 0x0

В заключении хочется сказать, что Microsoft предоставила огромные возможности kernel-разработчикам, предоставив msrpc.sys, пусть это пока и не документировано. Сейчас еще можно встреть единичные случаи ручной сборки RPC-пакетов и взаимодействия с RPC-сервером, например через механизм именованных каналов или [A]LPC. В случае c msrpc.sys есть достаточно полная документация на rpcrt4.dll, которая может быть использована с некоторыми оговорками.




Скачать demo-утилиту KScProxy с исходным кодом.


ΞρεΤΙκ