Доступ к командам In/Out через драйвер режима ядраСтатья с рабочим названием «Доступ к портам ввода/вывода под Windows XP» была написана в 2011. «Иллюстрации» к статье kernel-mode драйвера. Все драйвера открывают порты 42h, 43h, 61h, чтобы сыграть системным динамиком «Марш гладиаторов» (Opus 68 march (Vjezd gladiátorů)) чешского композитора Juliusa Fučíka (1872-1916), марш написан в 1897 году (композитор ― это дядя того самого Юлиуса Фучика, чехословацкого журналиста, антифашиста и коммуниста, который в тюрьме написал книгу «Репортаж с петлей на шее»). Переделаем драйвер beeper из KmdTutor by Four-F. Драйвер собирается при помощи следующих строк в bat-файле Код (Text): cls set filename=%1 if exist %filename%.sys del if exist %filename%.sys set masm_path=\masm32 %masm_path%\bin\ml /c /Cp /Gz /I%masm_path%\include /nologo /c /coff %filename%.asm || exit %masm_path%\bin\link /LIBPATH:%masm_path%\lib\ /nologo /driver /base:0x10000 ^ /align:32 /out:%filename%.sys /subsystem:native %filename%.obj || exit if exist %filename%.obj del %filename%.obj Теория На материнской плате находится перепрограммируемый интервальный таймер ― система, состоящая из трех каналов, каждый из которых можно запрограммировать для работы в одном из шести режимов. На многих современных материнских платах расположено два таких таймера, следовательно, число каналов равно шести. таймерпортназначение портаназначение каналапервый таймер40hканал #0отвечает за ход системных часов. Сигнал с этого канала вызывает прерывание времени. 18,2 раз в секунду выполняется процедура, на которую направлен вектор прерывания #8. Эта процедура производит изменения в области памяти, где хранится текущее время. В специальном регистре задвижки хранится число синхроимпульсов, по прошествии которых сигнал таймера должен вызвать прерывание времени. Уменьшая это число (через порт каналов), можно заставить идти системные часы быстрее. Канал #0 используется также для синхронизации некоторых дисковых операций, поэтому при при изменении числа синхроимпульсов необходимо восстановить первоначальное значение перед использованием обращений к дискам.41hканал #1отвечает за регенерацию памяти. Можно уменьшить число циклов регенерации памяти в секунду, однако это можно делать лишь в небольших пределах, так как при увеличении промежутка регенерации возрастает вероятность сбоя памяти42hканал #2обычно задействуют для работы с динамиком, хотя можно использовать и для других целей, например для синхронизации какого-либо внешнего устройства.43hуправляющий регистрвторой таймер для компьютера с шиной Microchannel44hканал #045hканал #146hканал #247hуправляющий регистрвторой таймер для компьютера с шиной EISA48hканал #049hканал #14Ahканал #24Bhуправляющий регистрТаблица #1:Пространство портов ввода/вывода для таймераВторой канал таймера управляет системным динамиком компьютера, генерируя прямоугольные импульсы с частотой 1193180/начальное значение счетчика герц. Начальное значение счетчика является 16-битным, и устанавливается через порт 42h. 1193180 Гц ― частота тактового генератора таймера. Динамик включается и выключается при выводе специального значения в порт управления динамиком с номером 61h, связанный с микросхемой программируемого контроллера периферийного интерфейса Intel 8255. Для включения динамика нужно прочитать байт из порта 61h, установить в единицу его два младших бита (0-ой бит разрешает работу канала таймера, а 1-ый бит включает динамик), а затем записать полученное значение в тот же порт. Чтобы отключить динамик, нужно сбросить значение двух младших битов порта 61h. Все управление таймером осуществляется путем вывода байта в порт 43h. Назначение битов порта 43h приведены в таблице #2. Номер битаназначениеЕсли 7-6 биты не равны 11b, значит байт, посылаемый в порт 43h ― это команда программирования канала[/B]7-6Номер канала:00b ― 0-ой канал01b ― 1-ый канал10b ― 2-ой канал5-4Индикатор считывания/записи00b ― зафиксировать текущее значение счетчика для чтения (в этом случае биты 3-0 не используются)01b ― чтение/запись только младшего байта10b ― чтение/запись только старшего байта11b ― чтение/запись сначала младшего, а потом старшего байта3-1Режим работы канала:000b ― Прерывание IRQ0 при достижении нуля (концу счета). Сигнал GATE=1 разрешает счет, а GATE=0 запрещает счет, причем GATE не влияет на выход OUT. Содержимое CR передается в CE по первому импульсу CLK после того, как процессор осуществил запись в CR, независимо от сигнала на входе GATE. Импульс, который загружает CE, не учитывается при счете. На выходе OUT формируется низкий уровень при записи в регистр управления, который сохраняется до достижения счетчиком 0. Режим 0 предназначен в основном для счета событий.001b ― Аппаратно-перезапускаемый одновибратор (ждущий мультивибратор). После загрузки значения N в CR переход 0->1 на входе GATE вызывает загрузку CE, переход 1->0 на выходе OUT и запускает счет. Когда счетчик достигнет 0, на выходе OUT формируется высокий уровень; таким образом, результатом является отрицательный импульс на выходе OUT с продолжительностью N периодов синхронизации.010b ― Периодический интервальный таймер (генератор импульсов). После загрузки значения N в CR следующий импульс синхронизации осуществляет передачу из CR в CE. На выходе OUT возникает переход 1->0, когда счетчик достигает 0; низкий уровень сохраняется в течение одного импульса CLK. Затем на выходе OUT появляется высокий уровень, производится повторная загрузка CE из CR; в результате на выходе OUT появляется отрицательный импульс через N тактов синхронизации. Сигнал GATE=1 разрешает счет, а GATE=0 запрещает. Переход 0->1 на выходе GATE вызывает ренинициализацию счета следующим импульсом синхронизации. Данный режим применяется для реализации периодического интервального таймера.011b ― Генератор прямоугольного сигнала. Аналогичен режиму 2, но на входе OUT формируется низкий уровень при достижении половины начального счета; этот уровень сохраняется до достижения счетчиком 0. Как и прежде, сигнал GATE разрешает и запрещает счет, а его переход 0->1 реинициализирует счет. Этот режим применяется в генераторах, определяющих скорость передачи в бодах.100b ― Программно-запускаемый строб. Аналогичен режиму 0, но на выходе OUT в процессе счета действует высокий уровень, а при достижении счетчиком 0 появится отрицательный импульс с продолжительностью в один такт синхронизации.101b ― Аппаратно-запускаемый строб с перезапуском. После загрузки CR переход 0->1 на входе GATE вызывает передачу из CR в CE следующим импульсом CLK. В процессе счета на выходе OUT действует высокий уровень, а при достижении счетчиком 0 формируется отрицательный импульс с продолжительностью в один период CLK. Сигнал GATE может в любой момент времени реинициализировать счет.0Формат счетчика:0 ― двоичное 16-разрядное число с диапазоном от 0 до 0FFFFh. Максимальное значение счетчика = 216-11 ― двоично-десятичное число с диапазоном от 0000 до 9999. Максимальное значение счетчика = 104-1Если 7-6 биты равны 11b, значит байт посылаемый в порт 43h ― это команда чтения счетчиков7-611b ― команда чтения счетчиков5-400b ― сначала состояние канала, потом значение счетчиков01b ― значение счетчиков10b ― состояние канала3-1Команда относится к каналам 0-2Таблица #2: Назначение битов порта 43hЧастота генерируемого звука задается с помощью микросхемы программируемого интервального таймера Intel 8253. Этот контроллер в числе прочего определяет, сколько импульсов в секунду следует послать на системный динамик. Таймер вырабатывает базовую частоту 1,193180 МГц. Первоначальное значение делителя частоты установлено в (1)0000h, что эквивалентно 65536. Для этого числа частота прерываний таймера равна 1193180/65536=18,20648193359375 Гц, что ниже граничной частоты восприятия звука человеком. Мы можем посылать Intel 8253 другое число для деления базовой частоты 1,19 МГц. Для установки таймера в правильный рабочий режим посылаем число 0B6h в порт 43h. После этого можно использовать порт 42h для передачи делителя. Если отправить в порт 42h число 5000, то получим частоту следования импульсов 1193180/5000=238,636 Гц, чуть ниже ноты си третьей октавы (для третьей октавы значение частоты из таблицы #3 делят на 2, а для пятой октавы ― умножают на 2). Так как делитель является 16-разрядным числом, поэтому передаем его двумя частями. Сначала отправляем младший байт, а затем старший байт. нотачастота (Гц)делитель = 1193180/частотадо#277,2310CFhре293,660FDFhре#311,130EFAhми329,630E23hфа349,230D58hфа#3700C98hсоль3920BE3hсоль#415,30B39hля4400A97hля#466,169FFhси493,8896Fhдо523,258E8hТаблица #3: Ноты четвертой октавыПрактикаВыводим в порт 43h число 0B6h=10.11.011.0b, мы помещаем в управляющий регистр таймера значение, определяющее: номер канала, которым мы будем управлять (10b=2-ой канал) тип операции (11b = чтение/запись сначала младшего, а потом старшего байта) режим работы канала (011b = генератор прямоугольных импульсов (основной режим)) формат счетчика (0 = 16-разрядное число от 0 до 0FFFFh) Код (ASM): mov al,0B6h out 43h,al Затем, в порт 42h выводим 16-битное начальное значение счетчика. Сначала младший байт, затем старший. Код (ASM): mov esi,offset melody; данные mov ecx,size_melody ; счетчик mov dx,42h ;используем порт 42h для передачи делителя a0: . . . ; создаем задержку между импульсами в 40 мкСек outsb loop a0 Команда OUTSB выводит данные в порт ввода- вывода, номер которого загружен в регистр DX, из ячейки памяти по адресу DS:ESI (массив melody), после выполнения команды OUTSB содержимое регистра ESI увеличивается на единицу, это будет происходить до тех пор, пока содержимое регистра ECX не станет равным нулю. Для получения задержки, чтобы не зависеть от быстродействия конкретного CPU, используем функцию KeStallExecutionProcessor. После проигрыша мелодии выключаем динамик, сбрасывая два младших бита. На время работы с регистрами таймера, запрещаем аппаратные прерывания (команда CLI). На этом работу нашего драйвера можно считать законченной. Драйвер возвращает системе STATUS_DEVICE_CONFIGURATION_ERROR ― код фиктивной ошибки и благополучно удаляется системой из памяти. Код ошибки возвращен только для того, чтобы система сама удалила драйвер и он понапрасну не «болтался» в памяти. Когда мы доберемся до полнофункциональных драйверов, то, естественно, будем возвращать STATUS_SUCCESS. Код (ASM): mov eax, STATUS_DEVICE_CONFIGURATION_ERROR ;возвращаем код ошибки, для ret ;того, чтобы система удалила драйвер из памяти Далее исходный текст драйвера scp00.sys Код (ASM): ; masm windows native # ;написано на основе драйвера режима ядра beeper из «KmdTutor by Four-F» .686P .model flat include ntstatus.inc include ntddk.inc includelib hal.lib extern _imp__KeStallExecutionProcessor@4:dword .code DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING cli in al,61h ;получаем текущий статус порта B or al,00000011b ;включаем системный динамик и таймер out 61h,al ;заменяем байт mov al,0B6h ;установка канала 2 микросхемы 8253 в правильный out 43h, al ;рабочий режим для приема делителя mov esi,offset melody; данные mov ecx,size_melody ; счетчик mov dx,42h ;используем порт 42h для передачи делителя a0: push ecx ;запоминаем содержимое edx и ecx, так как функция push edx ;KeStallExecutionProcessor их обязательно изменит mov ecx,800; 50 мкСек * 800 = 40 мСек a1: push ecx push 50; максимальное количество мкСек для функции KeStallExecutionProcessor call _imp__KeStallExecutionProcessor@4 pop ecx loop a1 pop edx ;восстанавливаем содержимое edx и ecx pop ecx ;команда outs выводит данные в порт ввода/вывода outsb ;номер, которого загружен в регистр DX, loop a0 ;из ячейки памяти по адресу DS:ESI in al,61h ;получаем текущий статус порта B and al,11111100b ;выключаем системный динамик и таймер out 61h,al ;заменяем байт sti mov eax, STATUS_DEVICE_CONFIGURATION_ERROR;возвращаем код ошибки, для ret ;того, чтобы система удалила драйвер из памяти melody dw 2 dup(354h),2,2,2 dup(387h),2,2,2 dup(3BDh),2 dup(387h) dw 2 dup(3BDh),2 dup(3F5h),2 dup(432h),2,2,2 dup(472h),2,2 dw 4 dup(4B5h),4 dup(472h),2 dup(3F5h),2,2,2 dup(432h),2,2 dw 2 dup(472h),2 dup(432h),2 dup(472h),2 dup(4B5h),2 dup(4FDh) dw 2,2,2 dup(549h),2,2,4 dup(599h),4 dup(549h),2 dup(472h) dw 2,2,5EEh,2,5EEh,2,4 dup(649h),4 dup(5EEh),2 dup(472h),2,2 dw 5EEh,2,5EEh,2,4 dup(649h),4 dup(5EEh),2 dup(70Eh),2 dup(6A8h) dw 2 dup(649h),2 dup(5EEh),2 dup(599h),2 dup(549h),2 dup(4FDh) dw 2 dup(4B5h),2 dup(472h),2,2,2 dup(432h),2,2,2 dup(3F5h),2,2 dw 2 dup(387h),2,2,8 dup(354h),2 size_melody = $ - melody user-mode приложение может запустить драйвер следующими способами: использовать API-функции Service Control Manager'a; прописать драйвер в реестре «вручную» и загрузить его с помощью функции ZwLoadDriver. В реестре создается в минимум необходимых записей, после запуска драйвера его раздел удаляется из реестра; загрузить драйвер при помощи функции ZwSetSystemInformation; через динамический загрузчик Свена Шрайбера, прилагаемый к его книге «Недокументированные возможности Windows 2000» (сам загрузчик можно найти на WASM'е). (Ничего нового! Та же загрузка драйвера через API-функции Service Control Manager'a, единственный плюс ― ничего не нужно писать самому, всё, что от вас требуется ― это указать в командной строке динамическому загрузчику полный путь до вашего драйвера) Первый вариант user-mode приложения, которое запускает драйвер scp00.sysuser-mode приложение, которое запускает scp00.sys драйвер, используя API-функции Service Control Manager'a: OpenSCManager, CreateService, StartService, CloseServiceHandle и DeleteService. Этот способ очень прост и хорошо документирован, он подходит для постоянной установки драйверов. Для запуска и управления драйвером необходимы следующие компоненты: диспетчер управления службами (Service Control Menager ― SCM). Именно благодаря ему мы будем иметь возможность легко и просто загружать драйверы; программа управления службами (Service Control Programm ― SCP). Это программа третьего кольца; она работает с диспетчером управления службами (вызывает функции, которые он предоставляет), чтобы установить, запустить драйвер и завершить его работу; собственно сам драйвер; для запуска драйвера его необходимо зарегистрировать. За этот процесс в системе отвечает функция CreateService. Также при работе с драйвером потребуются следующие функции: StartService, ControlService, DeleteService, CloseServiceHandle Кроме того, для возможности работы с диспетчером управления службами необходимо получить к нему доступ при помощи функции OpenSCMenager. Функция CloseServiceHandle закрывает описатель, который возвращает функция OpenSCMenager. Все функции, которые предоставляет диспетчер управления службами, находятся в advapi32.dll. На конечном этапе загрузки системы перед появлением диалога регистрации пользователя запускается SCM (\%SystemRoot%\System32\Services.exe), который, просматривая раздел реестра HKEY_LOCAL_MASHINE\SYSTEM\CurentControlSet\Service\, создает свою внутреннюю базу данных (ServiceActive database или SCM database). Далее SCM находит в созданной базе все драйвера устройств и службы, помеченные для автоматического запуска, и загружает их. Рассмотрим процесс запуска и управления драйвером более подробно. Помещаем драйвер в сервисную базу. База должна быть предварительно открыта функцией OpenSCManager: Код (C): SC_HANDLE OpenSCManager( LPCTSTR lpMachineName, LPCTSTR lpDatabaseName, DWORD dwDesiredAccess ); Параметры:lpMachineName Указатель на строку (завершающуюся нулём), содержащую имя компьютера. Этот параметр мы сразу устанавливаем в NULL, так как будем открывать канал связи с SCM только на локальном компьютере. lpDatabaseName Указатель на строку (завершающуюся нулём), которая содержит имя открываемой базы данных менеджера управления сервисами. Этот параметр должен быть равен SERVICES_ACTIVE_DATABASE. Если этот параметр приравнять NULL, то по умолчанию будет открыта база SERVICES_ACTIVE_DATABASE. Так как мы не собираемся открывать никакую другую базу данных SCM, кроме активной в данный момент, просто, установим этот параметр в NULL. dwDesiredAccess Права доступа к менеджеру управления сервисами. Сообщает SCM, что мы собственно намереваемся делать с его базой данных. Нам могут быть полезны три значения: ЗначениеПредназначениеSC_MANAGER_CONNECTдоступ на установку канала связи с SCM. По умолчанию (то есть если просто передать в этом параметре 0), устанавливается именно это значение. Хотя в документации ничего не говорится, что конкретно мы можем делать с этим уровнем доступа. А делать можно многое: запускать драйвер, останавливать, и даже удалять сведения о нем из базы данных SCM и из реестра.SC_MANAGER_CREATE_SERVICEдоступ на занесение в базу данных SCM нового драйвера. Так как мы собираемся занести туда своего представителя, то именно это значение и используем. Можно подумать, что никаких других прав, кроме регистрации нового драйвера, этот флаг не дает. Это не так. Так как флаг SC_MANAGER_CONNECT считается установленным по умолчанию, то и соответствующие права нам тоже предоставляются. Что тоже совсем не очевидно;SC_MANAGER_ALL_ACCESSпозволяет получить максимальный доступЕсли канал связи с SCM успешно установлен, функция OpenSCManager вернет описатель (handle), предоставляющий доступ к активной базе данных SCM, который мы сохраняем в переменной hSCManager для дальнейшего использования. Получив доступ к базе SCM, мы регистрируем в ней свой драйвер, с помощью функции CreateService. Функция CreateService создает объект службы и добавляет его в указанную базу данных диспетчера управления службами. Код (C): SC_HANDLE CreateService( SC_HANDLE hSCManager, LPCTSTR lpServiceName, LPCTSTR lpDisplayName, DWORD dwDesiredAccess, DWORD dwServiceType, DWORD dwStartType, DWORD dwErrorControl, LPCTSTR lpBinaryPathName, LPCTSTR lpLoadOrderGroup, LPDWORD lpdwTagId, LPCTSTR lpDependencies, LPCTSTR lpServiceStartName, LPCTSTR lpPassword ); Параметры: hSCManager Дескриптор базы данных диспетчера управления службой. Этот дескриптор возвращается функцией OpenSCManager и должен иметь право доступа SC_MANAGER_CREATE_SERVICE. Определяет в какую именно базу мы добавляем сведения о новом драйвере. lpServiceName Указатель на строку с завершающим нулем, которая задает устанавливаемое имя службы. Максимальная длина строки ― 256 символов. База данных диспетчера управления службами сохраняет регистр символов, но при сравнении имени службы ― всегда без учета регистра. Прямой слэш (/) и обратный слэш (\) ― неприменяемые символы в имени службы. lpDisplayName Указатель на строку с завершающим нулем, которая имеет в своем составе отображаемое имя, которое используется пользовательскими программами интерфейса, чтобы идентифицировать службу. Эта строка имеет максимальную длину 256 символов. Имя сохраняется с учетом регистра в диспетчере управления службами. Сравнения отображаемого имени всегда не чувствительны к регистру. dwDesiredAccess Доступ к службе. Перед предоставлением требуемого доступа, система проверяет маркер доступа вызывающего процесса. ТипПредназначениеSERVICE_ALL_ACCESSпозволяет получить максимальный доступSERVICE_STARTдоступ на запуск драйвера вызовом функции StartServiceSERVICE_STOPдоступ на останов драйвера вызовом функции ControlService с параметром SERVICE_CONTROL_STOPDELETEдоступ на удаление сведений о драйвере из базы данных SCM вызовом функции DeleteServiceНам потребуется выполнить всего два действия: запустить драйвер и удалить сведения о нем из базы данных SCM, а следовательно, и из реестра. Поэтому в этом параметре мы передаем комбинацию флагов SERVICE_START и DELETE. Останавливать запущенный драйвер нам не потребуется, так как его инициализация завершится ошибкой. dwServiceType Типы службы. Для драйвера может быть только SERVICE_KERNEL_DRIVER (Соответствует параметру Type в реестре) dwStartType Варианты запуска службы. Этот параметр может быть SERVICE_DEMAND_START Служба, запускается диспетчером управления службами, когда процесс вызывает функцию StartService. (Соответствует параметру Start в реестре) dwErrorControl Серьезность ошибки и предпринимаемое действие, если эта служба не в состоянии запуститься. Этот параметр может быть одним из следующих значений. ЗначениеПредназначениеSERVICE_ERROR_IGNOREПрограмма запуска регистрирует ошибку, но продолжает операцию запуска.SERVICE_ERROR_NORMALПрограмма запуска регистрирует ошибку и показывает всплывающее окно сообщения, но продолжает операцию запуска.SERVICE_ERROR_SEVEREПрограмма запуска регистрирует ошибку. Если запускается последняя, заведомо без ошибок конфигурация, операция запуска продолжается. Иначе, система перезапускается с последней, заведомо без ошибок конфигурацией.SERVICE_ERROR_CRITICALЕсли возможно, программа запуска регистрирует ошибку . Если запускается последняя, заведомо без ошибок конфигурация, операция запуска завершается ошибкой. Иначе, система перезапускается с последней из известных конфигураций без ошибок.lpBinaryPathName Указатель на строку с завершающим нулем, которая имеет в своем составе полный путь доступа к двоичному файлу службы. Если путь имеет в своем составе пробел, он должен быть заключен в кавычки. Соответствует параметру ImagePath в реестре lpLoadOrderGroup Указатель на строку с завершающим нулем, именующую группу очередности загрузки, членом которой является эта служба. Задайте значение NULL или пустую строку, если служба не принадлежит группе. Программа запуска использует очередность загрузки групп, чтобы загрузить группы служб в указанном порядке по отношению к другим группам. Список очередности загрузки групп содержатся в следующем значении реестра: HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\ServiceG roupOrder lpdwTagId Указатель на переменную, которая получает значение признака, являющееся уникальным в группе, заданной в параметре lpLoadOrderGroup. Задайте значение NULL, если Вы не изменяете существующий признак. Вы можете использовать признак для того, чтобы упорядочить запуск службы в пределах очередности загрузки группы, определяя вектор очередности признака в следующем значении реестра: HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\GroupOrd erList Признаки вычисляются только для служб драйвера, которые имеют типы пуска SERVICE_BOOT_START или SERVICE_SYSTEM_START. lpDependencies Указатель на массив имен служб, разделенных нулем, с двойным символом нуля в конце или очередности загрузки групп, которую система должна запустить перед этой службой. Задайте значение NULL или пустую строку, если служба не имеет никаких зависимостей. Зависимость от группы означает, что эта служба может запуститься тогда, если по крайней мере один член группы запущен после попытки запустить все члены группы. Вы должны ставить в начале имен группы SC_GROUP_IDENTIFIER так, чтобы они могли отличаться от имени службы, потому что службы и группы служб совместно используют то же самое пространство имен. Если драйвер не зависит от других драйверов, то в этом параметре можно указать NULL или указатель на пустую строку. lpServiceStartName Указатель на строку с завершающим нулем, которая задает имя учетной записи, с правами которой будет запущен драйвер. Если тип службы SERVICE_KERNEL_DRIVER, то этот параметр должен содержать имя объекта драйвера, которое используется системой для загрузки. Если используется имя объекта "драйвер" созданное подсистемой ввода-вывода, то этот параметр устанавливается равным NULL. lpPassword Указатель на строку с завершающим нулем, которая имеет в своем составе пароль к имени учетной записи, заданному параметром lpServiceStartName. Для служб драйвера пароли игнорируются, поэтому NULL Вызовом функции GetFullPathName, мы формируем строку с полным путем к файлу драйвера, состоящую из текущего каталога и имени файла драйвера, и передаем ее функции CreateService. CreateService регистрирует в базе данных SCM новый драйвер, и заполняет соответствующий подраздел реестра. Запуск драйвера осуществляется функцией StartService Код (C): BOOL StartService( SC_HANDLE hService, DWORD dwNumServiceArgs, LPCTSTR* lpServiceArgVectors );
Параметры: hService Дескриптор службы. Этот дескриптор возвращается функцией OpenService или CreateService, и он должен иметь право доступа SERVICE_START. dwNumServiceArgs Число строк в массиве lpServiceArgVectors. Если lpServiceArgVectors имеет значение NULL, этот параметр может быть нулем. Для драйверов это всегда так. lpServiceArgVectors Указатель на массив указателей на строки с завершающим нулем, который передается службе как параметры. Службы драйвера не получают эти параметры, поэтому мы устанавливаем этот параметр в NULL. Функция StartService заставляет систему произвести действия, очень сильно напоминающие загрузку обыкновенной DLL. Образ файла драйвера проецируется на системное адресное пространство. При этом, возможности управлять адресом загрузки нет никакой. Да это и не нужно. Предопределенный адрес загрузки (preferred base address) у всех наших драйверов будет равен 10000h, что значительно ниже начала системного диапазона адресов. Пытаться установить его в какое-то другое значение не имеет смысла, так как система, все равно, будет загружать драйвер по случайному (для нас) адресу. Поскольку фактический адрес загрузки не совпадает с предопределенным, система производит настройку адресов пользуясь таблицей перемещений (relocation table), находящейся в секции .reloc файла драйвера. Затем производится связывание (fix-up) импорта. Кстати, импорт в файле драйвера раскинут в секции INIT и .idata. В .idata находится таблица адресов импорта (import address table, IAT). В ней содержатся адреса функций во внешних модулях. Она нужна драйверу постоянно. А в секции INIT содержится остальная часть импорта, необходимая только на этапе загрузки (имена внешних модулей и имена импортируемых функций), после которой, память занимаемая этой секцией освобождается. Когда образ драйвера подготовлен, управление передается на точку входа (entry point), которая находится в процедуре DriverEntry. Принципиальная разница, не считая уровня привилегий, в том, что код процедуры DriverEntry всегда выполняется одним из потоков процесса System, и, естественно, в адресном контексте этого процесса. Вызов StartService синхронный. Это значит, что она не вернет управление до тех пор, пока не отработает процедура DriverEntry в драйвере. Если инициализация драйвера прошла успешно, DriverEntry должна вернуть STATUS_SUCCESS, а функция StartService вернет значение отличное от нуля. И мы вновь окажемся в контексте потока вызвавшего StartService, то есть в контексте нашей SCP. Вызов StartService может завершиться неудачей, если база данных SCM заблокирована. Последующий вызов функции GetLastError вернет ERROR_SERVICE_DATABASE_LOCKED. Как написано в документации, в этом случае, следует подождать несколько секунд, и повторить попытку, но мы этого делать не будем, так как такая ситуация крайне маловероятна. И вообще, в данном случае, нас не интересует возвращаемое функцией StartService значение, так как драйвер уже проиграл свою мелодию и вернул код ошибки. То есть мы заранее знаем, что вызов StartService даст ошибку. Осталось привести систему в исходное состояние. Вызовом функции DeleteService мы удаляем сведения о драйвере из базы данных SCM и из реестра. Странно, но передавать описатель самой базы данных SCM в функцию DeleteService не нужно. Функция DeleteService отмечает указанную службу для удаления из базы данных диспетчера управления службами. Код (C): BOOL DeleteService( SC_HANDLE hService ); Параметр: hService Дескриптор службы. Этот дескриптор возвращается функцией OpenService или CreateService и он должен иметь право доступа DELETE. На самом деле, функция DeleteService ничего ниоткуда не удаляет. Она только сообщает системе, что это можно сделать, когда наступит благоприятный момент. А он наступит тогда, когда все описатели службы будут закрыты. Так как мы все еще держим описатель hService открытым, то удаления не происходит. Если попытаться вызвать DeleteService повторно, то он завершится неудачей, а последующий вызов функции GetLastError вернет ERROR_SERVICE_MARKED_FOR_DELETE. Вызовом функции CloseServiceHandle мы закрываем описатель диспетчера управления службами hService. Код (C): BOOL CloseServiceHandle( SC_HANDLE hSCObject ); Параметр: hSCObject Дескриптор объекта диспетчера управления службами или объекта службы, который закрывается. Дескрипторы объектов менеджера управления службами возвращаются функцией OpenSCManager, а дескрипторы объектов служб возвращаются или функцией OpenService или функцией CreateService. Поскольку больше открытых описателей службы нет, то именно в этот момент система приводит базу данных SCM в исходное состояние. Второй вызов CloseServiceHandle закрывает описатель самого SCM. Код (ASM): ; masm windows gui # .686P .model flat include windows.inc includelib kernel32.lib includelib user32.lib includelib advapi32.lib extern _imp__ExitProcess@4:dword extern _imp__GetFullPathNameA@16:dword extern _imp__MessageBoxA@16:dword extern _imp__CloseServiceHandle@4:dword extern _imp__DeleteService@4:dword extern _imp__StartServiceA@12:dword extern _imp__CreateServiceA@52:dword extern _imp__OpenSCManagerA@12:dword .code start proc local hSCManager:HANDLE local acDriverPath[MAX_PATH]:CHAR xor ebx,ebx xor esi,esi;тип ошибки равен 0 mov edi,offset scp00_sys_name ; Open a handle to the SC Manager database push SC_MANAGER_CREATE_SERVICE;определяем нужный тип доступа push ebx;адрес имени базы данных сервисов. Для открытия базы ;по умолчанию равно NULL push ebx;имя (адрес имени) рабочей станции в сети, на которой хотят ;открыть базу. Для локального компьютера равна NULL call _imp__OpenSCManagerA@12;открываем базу сервисов xchg eax,ecx jecxz err;if eax != NULL выводим сообщение об ошибке (тип ошибки равен 0) inc esi;тип ошибки равен 1 mov hSCManager,ecx;при успешном выполнении возвращает дескриптор базы данных push eax push esp lea eax,acDriverPath push eax push MAX_PATH push edi;offset scp1_sys_name call _imp__GetFullPathNameA@16 pop eax mov [edi+5],ebx ; Register driver in SCM active database push ebx;пароль учетной записи. NULL нет пароля push ebx;имя учетной записи, с которой должна запускаться служба. ;NULL предполагает, что служба запускается под именем LocalSystem push ebx;сервисы и группы сервисов от которых зависит наш сервис NULL push ebx;NULL push ebx;порядок загрузки групп служб. В данном случае порядок не важен (NULL) lea ecx,acDriverPath push ecx;строка содержащая имя и полный путь к программе-сервису push ebx;SERVICE_ERROR_IGNORE=0 уровень реакции на ошибку push SERVICE_DEMAND_START;тип старта службы (здесь "запуск по требованию") push SERVICE_KERNEL_DRIVER;тип сервиса "драйвер режима ядра" push SERVICE_START + DELETE;возможный тип доступа к сервису push edi;"scp00" параметр определяет отображаемое имя. push edi;"scp00" адрес строки, содержащей имя одной из логических служб, ;по которому в дальнейшем будет возможно обращение к этой службе. push hSCManager;дескриптор сервисной базы данных call _imp__CreateServiceA@52;помещаем сервис в сервисную базу test eax,eax;if eax == NULL выводим сообщение об ошибке=1 jne a1 err: push MB_ICONSTOP push ebx;NULL push handle[esi*4] push ebx;NULL call _imp__MessageBoxA@16;выводим тип ошибки jmp a2;выходим из программы a1: push eax;hService для CloseServiceHandle push eax;hService для DeleteService push ebx;параметры передаваемые в службу, обычно NULL push ebx;параметры передаваемые в службу, обычно 0 push eax;hService дескриптор возвращенный функцией CreateService call _imp__StartServiceA@12;программный запуск зарегистрированного сервиса ; Here driver scp00.sys plays melody ;and reports error to be removed from memory ;Remove driver from SCM database call _imp__DeleteService@4;удаляем сервис call _imp__CloseServiceHandle@4;закрываем базу сервисов push hSCManager call _imp__CloseServiceHandle@4;закрываем базу сервисов a2: push ebx;0 call _imp__ExitProcess@4 start endp scp00_sys_name db "scp00.sys",0 handle dd can_t_connect,can_t_register can_t_connect db "Can't connect to Service Control Manager.",0 can_t_register db "Can't register driver.",0 end start Второй вариант user-mode приложения, которое запускает драйвер scp00.sysПредварительно прописываем драйвер scp00.sys в реестре «вручную» (используем функции RegOpenKey, RegCreateKey, RegSetValue) и загружаем драйвер scp00.sys с помощью функции ZwLoadDriver. В реестре создается минимум необходимых записей, запускаем драйвер, выгружаем драйвер функцией ZwUnloadDriver и удаляем его раздел из реестра (используем функции RegCloseKey, SHDeleteKey). Этот способ позволяет запускать драйвер быстро и незаметно и подходит для маленьких программ не требующих установки, но требующих запуска своего драйвера. Код (ASM): ; masm windows gui # .686P .model flat include windows.inc includelib kernel32.lib includelib advapi32.lib includelib ntdll.lib includelib shlwapi.lib extern _imp__ExitProcess@4:dword extern _imp__GetFullPathNameA@16:dword extern _imp__RegOpenKeyA@12:dword extern _imp__RegCreateKeyA@12:dword extern _imp__RegSetValueExA@24:dword extern _imp__RegCloseKey@4:dword extern _imp__SHDeleteKeyA@8:dword extern _imp__ZwLoadDriver@4:dword extern _imp__ZwUnloadDriver@4:dword ;--------macros------------------- du macro string irpc c,<string> if '&c'gt 127 db ('&c'- 0B0h),4 else dw '&c' endif endm dw 0 endm .const align 4 us: du <\registry\machine\SYSTEM\CurrentControlSet\Services\scp00> len_us = $-us align 4 cusDevice dw (len_us - 2) dw (len_us) dd us .code start proc local Key2:HKEY local Key:HKEY local acDriverPath[MAX_PATH]:CHAR xor ebx,ebx mov esi,offset scp00_sys_name lea edi,acDriverPath mov eax,'\??\' stosd;путь к драйверу должен начаться с '\??\' push eax;резервирую место в стеке push esp;указатель на пустое место в стеке push edi sub edi,4 push MAX_PATH push esi;offset scp1_sys_name call _imp__GetFullPathNameA@16 ;GetFullPathName(scp00_sys_name, MAX_PATH, PChar(dword(@Image) + 4), Pth); add eax,4 push eax;длина полного пути для RegSetValueEx(Key2,"ImagePath",0, ;REG_SZ,&acDriverPath,lenth(acDriverPath)) lea eax,Key push eax push offset aSystem push HKEY_LOCAL_MACHINE call _imp__RegOpenKeyA@12 mov [esi+5],ebx; из "scp00.sys" делаем "scp00" lea eax,Key2 push eax push esi push Key call _imp__RegCreateKeyA@12 push edi;&acDriverPath push REG_SZ push ebx;0 push offset aImagePath push Key2 call _imp__RegSetValueExA@24;RegSetValueEx(Key2,"ImagePath",0, ;REG_SZ,&acDriverPath,lenth(acDriverPath)) mov eax,esp;указатель на пустое место в стеке push sizeof(dword) mov dword ptr [eax],1;в пустое место в стеке поместим переменную dType=1 push eax;&dType push REG_DWORD;тип переменной dType push ebx;0 push offset aType;название переменной dType push Key2 call _imp__RegSetValueExA@24 ;RegSetValueEx(Key2,"Type",0,REG_DWORD,&dType) push Key2 call _imp__RegCloseKey@4 mov [esp],offset cusDevice call _imp__ZwLoadDriver@4; выравниваем стек после GetFullPathNameA push offset cusDevice call _imp__ZwUnloadDriver@4 push esi push Key call _imp__SHDeleteKeyA@8;удаляем непустую строку из реестра push Key call _imp__RegCloseKey@4 push ebx;0 call _imp__ExitProcess@4 start endp aSystem db 'SYSTEM\CurrentControlSet\Services',0 scp00_sys_name db "scp00.sys",0 aType db "Type",0 aImagePath db "ImagePath",0 end start Третий вариант user-mode приложения, которое запускает драйвер scp00.sysСобственно это модифицированный второй вариант. Произошла замена функций из advapi32.dll и других динамических библиотек на Zw-функции из ntdll.dll, а далее замена Zw-функций на вызов 2Eh прерывания, поэтому за исключением функции RtlGetFullPathName_U наша программа не использует импорт DLLисходная функцияфункция из ntdll.dllномер функции в SDT для Windows XPНазначение Zw-функцийkernel32GetFullPathNameRtlGetFullPathName_U―advapi32RegOpenKeyZwOpenKey119открывает доступ к существующему подразделу реестра или создает новый. Возвращает дескриптор открытого объекта.RegCreateKeyZwCreateKey41открывает доступ к существующему подразделу реестра или создает новый. Возвращает дескриптор открытого объекта.RegSetValueExZwSetValueKey247создает или изменяет значение параметра в открытом подразделе реестра. Для возможности применения этой функции, дескриптор подраздела при открытии должен быть получен с применением маски DesiredAccess, содержащей флаг KEY_SET_VALUERegCloseKeyZwClose25закрывает дескриптор открытого ранее подраздела реестра, фиксирует произведенные изменения на жестком дискеshlwapiSHDeleteKeyZwDeleteKey63удаляет открытый подраздел из реестраntdllZwLoadDriverZwLoadDriver97ZwUnloadDriverZwUnloadDriver262Таблица #7:вызовом функции ZwOpenKey открываем существующий раздел, запрашивая необходимые в данный момент права доступа вызовом функции ZwCreateKey, создаем новый подраздел «scp00» в ветке «HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services» После успешного вызова ZwCreateKey по значению переменной dwDisposition можно определить, был ли создан новый подраздел (REG_CREATED_NEW_KEY) или такой подраздел уже существовал в реестре (REG_OPENED_EXISTING_KEY) и поэтому был открыт При помощи ZwSetValueKey создадим в нашем подразделе: unicode-строковый параметр с именем «ImagePath». Строка содержит путь к исполняемому файлу драйвера или службы. В отличие от служб, для драйверов не обязательно указывать значение этого параметра, но тогда файл драйвера должен находиться в каталоге «\%SystemRoot%\System32\Drivers». Полный путь к нашему драйверу, начинающийся с символов «\??\» содержится в edi. Длину полного пути нам вернула функция RtlGetFullPathName_U. также создаем параметр с именем «Type», определяющим тип службы, типом параметра REG_DWORD и значением SERVICE_KERNEL_DRIVER (=1). Тип параметраОписаниеREG_NONEНетипизированный параметрREG_SZUnicode-строка фиксированной длины с нулём в концеREG_EXPAND_SZUnicode-строка переменной длины с нулём в конце; может включать переменные окруженияREG_BINARYДвоичные данные произвольной длиныREG_DWORD32-битное числоREG_DWORD_LITTLE_ENDIAN32-битное число, в котором первым является младший байт, эквивалентно REG_DWORDREG_DWORD_BIG_ENDIAN32-битное число, в котором первым является старший байтREG_LINKСимвольная строка в формате UnicodeREG_MULTI_SZМассив Unicode-строк с завершающим нулёмREG_RESOURCE_LISTОписание аппаратного ресурсаREG_FULL_RESOURCE_DESCRIPTORОписание аппаратного ресурсаREG_RESOURCE_REQUIREMENTS_LISTСписок требований к ресурсамТаблица #8: Типы параметров реестраПосле проигрыша мелодии удаляем подраздел реестра вызовом ZwDeleteKey при использовании sysenter вместо Zw-функций размер файла 789 байт, при использовании call ds:[7FFE0300h] ― размер файла 762 байта, при использовании int 2Eh ― размер 699 байт Код (ASM): .686P .model tiny include windows.inc ;for WinXP - 699 bytes exebase equ 400000h OBJ_CASE_INSENSITIVE equ 40h SERVICE_KERNEL_DRIVER equ 01h du macro string irpc c,<string> if '&c'gt 127 db ('&c'- 0B0h),4 else dw '&c' endif endm dw 0 endm UNICODE_STRING STRUCT _Length WORD ?; len of string in bytes (not chars) MaximumLength WORD ?; len of Buffer in bytes (not chars) Buffer PWSTR ?; pointer to string UNICODE_STRING ENDS PUNICODE_STRING typedef PTR UNICODE_STRING OBJECT_ATTRIBUTES STRUCT ; sizeof = 18h _Length DWORD ? ; original name Length RootDirectory HANDLE ? ObjectName PUNICODE_STRING ? Attributes DWORD ? SecurityDescriptor PVOID ? ; Points to type SECURITY_DESCRIPTOR SecurityQualityOfService PVOID ? ; Points to type SECURITY_QUALITY_OF_SERVICE OBJECT_ATTRIBUTES ENDS .code main: include capito.asm align 4 us: du <\registry\machine\SYSTEM\CurrentControlSet\Services\scp00.sys> len_us = $-us align 4 cusDevice dw len_us - 10 dw len_us - 8 dd us+exebase pusSystem dw len_us-58; du <\SYSTEM\CurrentControlSet\Services> dw len_us-56 dd us+exebase+36 pusScp00 dw len_us-114; du <scp00> dw len_us-112 dd us+exebase+104 align 4 aImagePath: du <ImagePath> a2: align 4 pusImagePath dw a2-aImagePath-2 dw a2-aImagePath dd aImagePath+exebase align 4 aType: du <Type> a3: align 4 pusType dw a3-aType-2 dw a3-aType dd aType+exebase start: Key2 equ dword ptr [ebp-4] Key equ dword ptr [ebp-8] dwDisposition equ dword ptr [ebp-12] acDriverPath equ dword ptr [ebp-MAX_PATH-12] oa equ dword ptr [ebp-MAX_PATH-12-sizeof(OBJECT_ATTRIBUTES)] enter MAX_PATH+12+sizeof(OBJECT_ATTRIBUTES),0 xor ebx,ebx lea edi,acDriverPath mov eax,3F005Ch;unicode '\?' stosd ror eax,10h stosd invoke RtlGetFullPathName_U,offset us+exebase+104,MAX_PATH,edi,esp,eax sub edi,8;путь к драйверу должен начаться с '\??\' add eax,10 push eax;длина полного пути для ZwSetValueKey ;Для последующего вызова функции ZwOpenKey нам потребуется указатель на ;заполненную структуру OBJECT_ATTRIBUTES lea ecx,oa assume ecx: ptr OBJECT_ATTRIBUTES ;InitializeObjectAttributes mov [ecx]._Length,sizeof(OBJECT_ATTRIBUTES) mov [ecx].RootDirectory,ebx mov [ecx].ObjectName,offset pusSystem+exebase mov [ecx].Attributes,OBJ_CASE_INSENSITIVE mov [ecx].SecurityDescriptor,ebx mov [ecx].SecurityQualityOfService,ebx assume ecx: nothing lea eax,Key ; Адрес Key push ecx;POBJECT_ATTRIBUTES ObjectAttributes push KEY_READ;ACCESS_MASK DesiredAccess push eax;PHANDLE KeyHandle mov eax,119 mov edx,esp int 2Eh;ZwOpenKey(&Key,2000000h,&oa) lea ecx,oa;ObjectAttributes mov (OBJECT_ATTRIBUTES ptr [ecx]).ObjectName,offset pusScp00+exebase lea eax,dwDisposition push eax;PULONG Disposition OPTIONAL push ebx;ULONG CreateOptions=REG_OPTION_NON_VOLATILE push ebx;PUNICODE_STRING Class OPTIONAL push ebx;ULONG TitleIndex push ecx;POBJECT_ATTRIBUTES ObjectAttributes push KEY_READ;ACCESS_MASK DesiredAccess lea eax,Key2 push eax;PHANDLE KeyHandle mov eax,41 mov edx,esp int 2Eh;ZwCreateKey add esp,28+12 push edi;PVOID Data push REG_SZ;ULONG Type push ebx;ULONG TitleIndex OPTIONAL push offset pusImagePath+exebase;PUNICODE_STRING ValueName push Key2;HANDLE KeyHandle mov eax,247 mov edx,esp int 2Eh;ZwSetValueKey(Key2,&pusImagePath,0,REG_SZ,edi) add esp,24 mov eax,esp;указатель на пустое место в стеке mov dword ptr [eax],SERVICE_KERNEL_DRIVER;в пустое место в стеке поместим переменную dType=1 push sizeof(dword);ULONG DataSize push eax;PVOID Data push REG_DWORD;ULONG Type push ebx;ULONG TitleIndex OPTIONAL push offset pusType+exebase;PUNICODE_STRING ValueName push Key2;HANDLE KeyHandle mov eax,247 mov edx,esp int 2Eh;ZwSetValueKey(Key2,&pusType,0,REG_DWORD,eax,sizeof(dword)) push Key2;Handle mov eax,25 mov edx,esp int 2Eh;ZwClose(Key2) mov edi,offset cusDevice+exebase push edi mov eax,97 mov edx,esp int 2Eh;ZwLoadDriver(&cusDevice) push edi mov eax,262 mov edx,esp int 2Eh;ZwUnloadDriver(&cusDevice) push Key2;Handle mov eax,63 mov edx,esp int 2Eh;ZwDeleteKey(Key2) push Key;Handle mov eax,25 mov edx,esp int 2Eh;ZwClose(Key) leave retn;ExitProcess import: dd 0,0,0,ntdll_dll,ntdll_table dd 0,0 ntdll_table: RtlGetFullPathName_U dd _RtlGetFullPathName_U dw 0 _RtlGetFullPathName_U db 0,0,"RtlGetFullPathName_U",0 ntdll_dll db "ntdll" end_import: end main Четвертый вариант user-mode приложения, которое запускает драйвер scp00.sys Используя функцию ZwSetSystemInformation с параметром SystemLoadAndCalllmage можно за одно действие загрузить и запустить драйвер в системе Windows NT. При этом не требуется никакой регистрации драйвера. Однако возникнет «побочный эффект» ― после такой загрузки драйвер невозможно выгрузить «Обычный» (написанный по всем правилам драйвер) будет находиться в памяти до следующей перезагрузки компьютера. Но наш-то драйвер после выполнения возвратит системе STATUS_DEVICE_CONFIGURATION_ERROR (код фиктивной ошибки) и благополучно будет «самовыпилен» (удален системой из памяти) . Другой побочный эффект заключается в том, что мы можем за один сеанс загрузить драйвер несколько раз. Обычно драйвер может загружаться только один раз, но применив наш специальный системный вызов, мы можем загружать и запускать столько копий драйвера, сколько нам нужно. 8-байтовая локальная переменная GregsImage представляет из себя структуру UNICODE_STRING используемую для работы с UNICODE-строками в нулевом кольце Код (ASM): UNICODE_STRING STRUCT _Length WORD ?; len of string in bytes (not chars) MaximumLength WORD ?; len of Buffer in bytes (not chars) Buffer PWSTR ?; pointer to string UNICODE_STRING ENDS Поле _Length содержит текущую длину строки в байтах, не считая двух завершающих нулей. Поле MaximumLength содержит максимальный размер буфера в байтах, в котором эта строка присутствует (MaximumLength = _Length + 2). Поле Buffer содержит указатель на буфер, где находится UNICODE-строка. Код (ASM): call _imp__RtlGetFullPathName_U@16 imul eax,10001h add eax,2 mov edi,esp;edi=&GregsImage mov [edi],eax Для вычисления длины строки используем функцию RtlGetFullPathName умножив возвращенное значение на 10001h и добавив 2 мы получили значения для GregsImage._Length и GregsImage.MaximumLength Код (ASM): lea edi,daPath mov GregsImage.Buffer,edi Помещаем в поле GregsImage.Buffer указатель на UNICODE-строку. Код (ASM): ; masm windows gui # .686P .model flat include windows.inc includelib kernel32.lib includelib ntdll.lib extern _imp__ExitProcess@4:dword extern _imp__RtlGetFullPathName_U@16:dword extern _imp__ZwSetSystemInformation@12:dword ;----------const------------------ SystemLoadAndCallImage equ 26h ;--------macros------------------- du macro string irpc c,<string> if '&c'gt 127 db ('&c'- 0B0h),4 else dw '&c' endif endm dw 0 endm ;--------struct------------------- UNICODE_STRING STRUCT _Length WORD ?; len of string in bytes (not chars) MaximumLength WORD ?; len of Buffer in bytes (not chars) Buffer PWSTR ?; pointer to string UNICODE_STRING ENDS ;--------------------------------- .code start proc local daPath[MAX_PATH]:CHAR local GregsImage:UNICODE_STRING lea edi,daPath mov GregsImage.Buffer,edi mov eax,3F005Ch;eax='\',0,'?',0 stosd;путь к драйверу должен начаться с '\??\' ror eax,16;eax='?',0,'\',0 stosd push eax;резервирую место в стеке push esp;указатель на пустое место в стеке push edi push MAX_PATH push offset scp00_sys_name call _imp__RtlGetFullPathName_U@16 ;GetFullPathName(scp00_sys_name, MAX_PATH, PChar(dword(@Image) + 4), Pth); pop ecx add eax,8;учтем, что путь стал длиннее на '\??\' imul eax,10001h add eax,2 mov edi,esp;edi=&GregsImage mov [edi],eax push sizeof UNICODE_STRING;8 push edi;&GregsImage push SystemLoadAndCallImage;=26h call _imp__ZwSetSystemInformation@12 push 0 call _imp__ExitProcess@4 start endp scp00_sys_name: du <scp00.sys> end start Для написания данной главы использовались работы следующих авторов: Программирование и устройство таймера: Лю Ю-Чжен, Гибсон Г. Микропроцессоры семейства 8086/8088. Архитектура, программирование и проектирование микрокомпьютерных систем: Пер. с англ. ― М.: Радио и связь, 1987. ― 512 с.; ил. Глава 9. Интерфейсы ввода-вывода. Программируемый интервальный таймер. Юров В.И. Assembler. Учебник для вузов 2-е изд. ― СПб.: Питер, 2007. ― 637 с.: ил. Глава 7. Команды обмена данными. Ввод из порта и вывод в порт. Загрузка драйвера с помощью функций Service Control Manager'a и загрузка с помощью функции ZwLoadDriver: KmdTutRu.chm "Драйверы режима ядра" by Four-F главы: Установка канала связи с SCM Регистрация драйвера Запуск драйвера Загрузка драйвера через ZwSetSystemInformation Драйвер m1gB0t by Greg Hoglund, 2004, исходный текст m1gloader Грег Хогланд, Гари Мак-Гроу. Взлом программного обеспечения: анализ и использование кода.: Пер. с англ. ― М.: Издательский дом "Вильямс", 2005. ― 400 с.: ил. ― Парал. тит. англ. Глава 8. Наборы средств для взлома. Текст программы для загрузки драйвера c:\_root_.sys
Доступ к командам In/Out через драйвер режима ядра команды читающие или пишущие в порт ввода/вывода могут быть расположены в «теле» user-mode приложения, но чтобы они выполнились, необходимо оформить этот фрагмент как процедуру и на исполнение ее запустить из kernel-mode драйвера. При этом в этой процедуре не должны встречаться WinAPI функции, используемые в user-mode, иначе BSOD обеспечен. Адрес процедуры в драйвер можно передать: через реестр, через WriteFile "\\.\driver" или через DeviceIoControl. Переделываю драйвер <R>ing-<0> <P>rocedure <C>all & Direct Port I/O (r0pc.sys) by @L.chemist (Andrey A. Meshkov). Драйвер scp01 передает управление на фрагмент кода из scp01-00.exe в котором содержится команда outsb, синхронизация производится через предварительный подсчет тиков. Адрес процедуры musik передается через DeviceIoControl. Полнофункциональный драйвер. Текст драйвера scp01.sys Код (ASM): ; masm windows native # ;написано на основе драйвера режима ядра <R>ing-<0> <P>rocedure ;<C>all & Direct Port I/O (r0pc.sys) by @L.chemist (Andrey A. Meshkov) .686p .model flat include ntstatus.inc include ntddk.inc include Strings.mac includelib ntoskrnl.lib ; -------------------------------------------------------------------------------- extern _imp__IoCreateDevice:dword extern _imp__IoCreateSymbolicLink:dword extern _imp__IoDeleteDevice:dword extern _imp__IoDeleteSymbolicLink:dword extern _imp__IofCompleteRequest:dword DRIVER_QUERY_PROC_NOARGS = 10h ; -------------------------------------------------------------------------------- ; structure for driver query DriverQuery struct iocode dd ? ; user I/O code wparam dd ? ; parameter lparam dd ? ; parameter DriverQuery ends PDriverQuery equ dd ; -------------------------------------------------------------------------------- .const CCOUNTED_UNICODE_STRING "\\Device\\scp01", cusDevice, 4 CCOUNTED_UNICODE_STRING "\\DosDevices\\scp01", cusSymbolicLink, 4 .code ; Процедура входа драйвера. Эта процедура вызывается только раз после загрузки ; драйвера в память. Она выделяет необходимые ресурсы для работы драйвера. В ; нашем случае DriverEntry proc lpDriverObject:PDRIVER_OBJECT, lpusRegistryPath:PUNICODE_STRING local deviceObject:PDEVICE_OBJECT local status:NTSTATUS ; инициализируем драйвер устройства и объект устройства (Device Object) lea eax,deviceObject push eax;&deviceObject push 0 push 0 push FILE_DEVICE_UNKNOWN;22h push offset cusDevice push sizeof(DriverQuery);0Ch push lpDriverObject call _imp__IoCreateDevice test eax,eax;cmp eax,STATUS_SUCCESS; 0 jnz short exit; break on error ; create symbolic link to the user-visible name ; создаем символическую ссылку на драйвер устройства, что позволяет user-mode ; приложению получить доступ к нашему драйверу, используя "\\.\scp01" нотацию push offset cusDevice push offset cusSymbolicLink call _imp__IoCreateSymbolicLink mov status,eax; save status test eax,eax;cmp eax,STATUS_SUCCESS; check result jz short success push deviceObject call _imp__IoDeleteDevice; delete device object if not successful jmp short exit success: ; continue on success ; load structure to point to IRP handlers ; инициализируем точки входа драйвера в объект драйвера ; всё, что нам нужно, это операция создания (Create), управления (Control) ; и выгрузки (Unload) mov eax,lpDriverObject mov (DRIVER_OBJECT ptr [eax]).DriverUnload,offset DriverUnload mov (DRIVER_OBJECT ptr [eax]).MajorFunction[IRP_MJ_CREATE*4],offset DispatchCreate mov (DRIVER_OBJECT ptr [eax]).MajorFunction[IRP_MJ_DEVICE_CONTROL*4],offset DispatchControl mov eax,status; assign result exit: leave retn 8 DriverEntry endp ;------------------------------------------------------ ;освобождаем все выделенные ранее объекты DriverUnload: lpDriverObject equ dword ptr [esp+4];:PDRIVER_OBJECT ; delete symbolic link to the user-visible name push offset cusSymbolicLink call _imp__IoDeleteSymbolicLink ; delete device object mov ecx,lpDriverObject push dword ptr (DRIVER_OBJECT ptr [ecx]).DeviceObject call _imp__IoDeleteDevice retn 4 ;-------------------------------------------------- DispatchCreate: lIrp equ dword ptr [esp+8] mov ecx,lIrp and (_IRP ptr [ecx]).IoStatus.Status,STATUS_SUCCESS;0 and (_IRP ptr [ecx]).IoStatus.Information,0 xor edx,edx;edx = IO_NO_INCREMENT call _imp__IofCompleteRequest;IofCompleteRequest (lpIrp, IO_NO_INCREMENT) xor eax,eax;mov eax,STATUS_SUCCESS; 0 retn 8 ;--------------------------------------------------------- DispatchControl proc pDeviceObject:PDRIVER_OBJECT, lpIrp:PIRP local status:NTSTATUS push edi mov status,STATUS_UNSUCCESSFUL; 0C0000001h mov edi,lpIrp assume edi: ptr _IRP and [edi].IoStatus.Information,0; mov eax,[edi].Tail.Overlay.CurrentStackLocation cmp (IO_STACK_LOCATION ptr [eax]).Parameters.DeviceIoControl.InputBufferLength,4 jne @f mov status,STATUS_NOT_IMPLEMENTED; 0C0000002h cmp (IO_STACK_LOCATION ptr [eax]).Parameters.DeviceIoControl.IoControlCode,DRIVER_QUERY_PROC_NOARGS jne @f mov eax,[edi].AssociatedIrp.SystemBuffer;ioBuffer = pIrp->AssociatedIrp.SystemBuffer call dword ptr [eax] and status,STATUS_SUCCESS;status = 0 @@: push status pop [edi].IoStatus.Status assume edi:nothing mov ecx,edi xor edx,edx;edx = IO_NO_INCREMENT call _imp__IofCompleteRequest;IofCompleteRequest (lpIrp, IO_NO_INCREMENT) mov eax,status pop edi leave retn 8 DispatchControl endp ;---------------------------------------------------- end DriverEntry Первый вариант user-mode приложения, которое запускает драйвер scp01.sys user-mode приложение, которое запускает scp01.sys драйвер, используя API-функции Service Control Manager'a: OpenSCManager, CreateService, StartService, CloseServiceHandle и DeleteService. Этот способ очень прост и хорошо документирован, он подходит для постоянной установки драйверов. Код (ASM): ; masm windows gui # .686P .model flat include windows.inc includelib kernel32.lib includelib user32.lib includelib advapi32.lib extern _imp__ExitProcess@4:dword extern _imp__GetFullPathNameA@16:dword extern _imp__MessageBoxA@16:dword extern _imp__CloseServiceHandle@4:dword extern _imp__DeleteService@4:dword extern _imp__StartServiceA@12:dword extern _imp__CreateServiceA@52:dword extern _imp__OpenSCManagerA@12:dword extern _imp__Sleep@4:dword extern _imp__OpenServiceA@12:dword extern _imp__QueryServiceStatus@8:dword extern _imp__CreateFileA@28:dword extern _imp__DeviceIoControl@32:dword extern _imp__CloseHandle@4:dword extern _imp__ControlService@12:dword SERVICE_ERROR_NORMAL equ 1 MAX_PATH equ 260 DRIVER_QUERY_PROC_NOARGS equ 10h .code start proc local DrvFile:dword local Driver:HANDLE local Service:HANDLE local Manager:HANDLE local acDriverPath[MAX_PATH]:CHAR xor esi,esi xor ebx,ebx rdtsc; замеряем скольким тикам соответствует 40 мСек mov TimerLo,eax mov TimerHi,edx push 40 call _imp__Sleep@4 rdtsc sub eax,TimerLo sbb edx,TimerHi mov TimerLo,eax mov TimerHi,edx mov edi,offset DrvFilename+4;&edi='scp01.sys',0 ;-----инициализируем драйвер push eax push esp lea eax,acDriverPath push eax push MAX_PATH push edi call _imp__GetFullPathNameA@16 pop eax ; Open a handle to the SC Manager database push SC_MANAGER_ALL_ACCESS;CREATE_SERVICE push ebx;NULL push ebx;NULL call _imp__OpenSCManagerA@12 ;control manager on the specified computer and opens the specified database mov Manager,eax mov [edi+5],ebx;&edi=Drvname='scp01',0 test eax,eax jz err;if eax != NULL inc esi push SERVICE_ALL_ACCESS push edi push eax call _imp__OpenServiceA@12 test eax,eax jne short a0 ; Register driver in SCM active database push ebx;NULL push ebx;NULL push ebx;NULL push ebx;NULL push ebx;NULL lea ecx,acDriverPath push ecx;DrvPath push SERVICE_ERROR_NORMAL;IGNORE push SERVICE_DEMAND_START push SERVICE_KERNEL_DRIVER push SERVICE_ALL_ACCESS;START + DELETE push edi push edi push Manager call _imp__CreateServiceA@52 test eax,eax jz a3;if eax != NULL a0: mov Service,eax push offset Status push eax call _imp__QueryServiceStatus@8 xchg eax,ecx jecxz wmDESTROY cmp Status.SERVICE_STATUS.dwCurrentState,SERVICE_RUNNING je short a10 push ebx;offset Vectors push ebx;0 push Service call _imp__StartServiceA@12 a10: sub edi,4;&edi='\\.\scp01',0 push ebx push ebx push OPEN_EXISTING push ebx push FILE_SHARE_READ; or FILE_SHARE_WRITE push GENERIC_READ; or GENERIC_WRITE push edi call _imp__CreateFileA@28 mov Driver,eax inc eax;cmp eax,INVALID_HANDLE_VALUE je short a4 dec eax ; Here driver beeper.sys plays melody mov edi,offset Query mov [edi],offset musik push ebx;адрес OVERLAPPED-структуры push offset Bytes;адрес переменой, куда будет занесено количество байт, ;помещенных в буфер драйвером push ebx; длина буфера push ebx; буфер, куда драйвер поместит свои данные push 4;длина данных push edi;адрес данных для драйвера push DRIVER_QUERY_PROC_NOARGS;номер необходимой операции push eax;дескриптор драйвера, полученный через функцию CreateFile call _imp__DeviceIoControl@32 ; and reports error to be removed from memory ; Remove driver from SCM database wmDESTROY: push Driver call _imp__CloseHandle@4 a4: mov eax,Service push eax;Service push eax;Service push offset Status push SERVICE_CONTROL_STOP push eax;Service call _imp__ControlService@12; Send a control code to a Win32 service call _imp__DeleteService@4 call _imp__CloseServiceHandle@4 a3: push Manager call _imp__CloseServiceHandle@4 jmp short a11 err: push MB_ICONSTOP push ebx;NULL push handle[esi*4];offset can_t_connect push ebx;NULL call _imp__MessageBoxA@16 a11: push ebx;0 call _imp__ExitProcess@4 start endp ;-------------------------------------------------------------- musik: ;set bits 0,1 of PC speaker control register: in al,61h;текущее состояние порта 61h в AL or al,00000011b;установить биты 0 и 1 в 1 out 61h,al ;теперь динамик включен mov al,0B6h out 43h,al ;Timer 8253-5 (AT: 8254.2). mov esi,offset melody; данные mov ecx,size_melody ; счетчик mov dx,42h ;Timer 8253-5 (AT: 8254.2). push edi a15: push edx ;PC/XT PPI port B bits: push ecx ;0: Timer 2 gate OR 03h= speaker ON rdtsc ;1: Timer 2 data AND 0FCh= speaker OFF mov ecx,eax;2: mov edi,edx;3: 1=read high switches add ecx,TimerLo;4: 0=enable RAM parity checking adc edi,TimerHi;5: 0=enable I/O channel check @@: rdtsc; крутимся до тех пор пока не получится sub eax,ecx; задержка в 55 мСек sbb edx,edi jnz @b;6: 0=hold keyboard clock low pop ecx ;7: 0=enable keyboard pop edx ;Команда выводит данные в порт ввода-вывода, outsb ;номер которого загружен в регистр DX, loop a15 ;из ячейки памяти по адресу DS:ESI pop edi in al,61h and al,11111100b;4Dh out 61h,al retn ;---------------------------------------------------------------- .data melody dw 2 dup(354h),2,2,2 dup(387h),2,2,2 dup(3BDh),2 dup(387h),2 dup(3BDh) dw 2 dup(3F5h),2 dup(432h),2,2,2 dup(472h),2,2,4 dup(4B5h),4 dup(472h),2 dup(3F5h) dw 2,2,2 dup(432h),2,2,2 dup(472h),2 dup(432h),2 dup(472h),2 dup(4B5h),2 dup(4FDh) dw 2,2,2 dup(549h),2,2,4 dup(599h),4 dup(549h),2 dup(472h),2,2,2 dup(5EEh,2) dw 4 dup(649h),4 dup(5EEh),2 dup(472h),2,2,2 dup(5EEh,2),4 dup(649h),4 dup(5EEh) dw 2 dup(70Eh),2 dup(6A8h),2 dup(649h),2 dup(5EEh),2 dup(599h),2 dup(549h) dw 2 dup(4FDh),2 dup(4B5h),2 dup(472h),2,2,2 dup(432h),2,2,2 dup(3F5h),2,2 dw 2 dup(387h),2,2,8 dup(354h),2 size_melody = $ - melody ;---------------------------------------------------------------- TimerLo dd 0 TimerHi dd 0 DrvFilename db '\\.\scp01.sys',0 Query dd ? Status db sizeof(SERVICE_STATUS) dup (?) Bytes dd ? handle dd can_t_connect,can_t_register can_t_connect db "Can't connect to Service Control Manager.",0 can_t_register db "Can't register driver.",0 end start Второй вариант user-mode приложения, которое запускает драйвер scp01.sysПредварительно прописываем драйвер scp01.sys в реестре «вручную» (используем функции RegOpenKey, RegCreateKey, RegSetValue) и загружаем драйвер scp00.sys с помощью функции ZwLoadDriver. В реестре создается минимум необходимых записей, запускаем драйвер, выгружаем драйвер функцией ZwUnloadDriver и удаляем его раздел из реестра (используем функции RegCloseKey, SHDeleteKey). Этот способ позволяет запускать драйвер быстро и незаметно и подходит для маленьких программ не требующих установки, но требующих запуска своего драйвера. Код (ASM): ; masm windows gui # .686P .model flat include windows.inc includelib kernel32.lib includelib advapi32.lib includelib ntdll.lib includelib shlwapi.lib extern _imp__ExitProcess@4:dword extern _imp__GetFullPathNameA@16:dword extern _imp__Sleep@4:dword extern _imp__CreateFileA@28:dword extern _imp__DeviceIoControl@32:dword extern _imp__CloseHandle@4:dword extern _imp__RegOpenKeyA@12:dword extern _imp__RegCreateKeyA@12:dword extern _imp__RegSetValueExA@24:dword extern _imp__RegCloseKey@4:dword extern _imp__SHDeleteKeyA@8:dword extern _imp__ZwLoadDriver@4:dword extern _imp__ZwUnloadDriver@4:dword SERVICE_ERROR_NORMAL equ 1 MAX_PATH equ 260 DRIVER_QUERY_PROC_NOARGS equ 10h ;--------macros------------------- du macro string irpc c,<string> if '&c'gt 127 db ('&c'- 0B0h),4 else dw '&c' endif endm dw 0 endm .const align 4 us: du <\registry\machine\SYSTEM\CurrentControlSet\Services\scp01> len_us = $-us align 4 cusDevice dw (len_us - 2) dw (len_us) dd us .code start proc local Key2:HKEY local Key:HKEY local acDriverPath[MAX_PATH]:CHAR xor ebx,ebx rdtsc; замеряем скольким тикам соответствует 40 мСек mov TimerLo,eax mov TimerHi,edx push 40 call _imp__Sleep@4 rdtsc sub eax,TimerLo sbb edx,TimerHi mov TimerLo,eax mov TimerHi,edx mov esi,offset scp01_sys_name+4;&esi='scp01.sys',0 lea edi,acDriverPath mov eax,'\??\' stosd;путь к драйверу должен начаться с '\??\' ;-----инициализируем драйвер push eax;резервирую место в стеке push esp;указатель на пустое место в стеке push edi;&acDriverPath+4 sub edi,4 push MAX_PATH push esi call _imp__GetFullPathNameA@16 add eax,4 push eax lea eax,Key push eax push offset aSystem push HKEY_LOCAL_MACHINE call _imp__RegOpenKeyA@12 mov [esi+5],ebx;&esi=Drvname='scp01',0 lea eax,Key2 push eax push esi push Key call _imp__RegCreateKeyA@12 push edi;&acDriverPath push REG_SZ push ebx;NULL push offset aImagePath push Key2 call _imp__RegSetValueExA@24 mov eax,esp;указатель на пустое место в стеке push sizeof(dword) mov dword ptr [eax],1;в пустое место в стеке поместим переменную dType=1 push eax;&dType push REG_DWORD;тип переменной dType push ebx;0 push offset aType;название переменной dType push Key2 call _imp__RegSetValueExA@24 push Key2 call _imp__RegCloseKey@4 mov [esp],offset cusDevice call _imp__ZwLoadDriver@4; выравниваем стек после GetFullPathNameA sub esi,4;&esi='\\.\scp01',0 push ebx push ebx push OPEN_EXISTING push ebx push FILE_SHARE_READ; or FILE_SHARE_WRITE push GENERIC_READ; or GENERIC_WRITE push esi call _imp__CreateFileA@28 inc eax;cmp eax,INVALID_HANDLE_VALUE je short @f dec eax push eax;дескриптор драйвера для CloseHandle mov ecx,offset Query mov [ecx],offset musik push ebx;адрес OVERLAPPED-структуры push offset Bytes;адрес переменой, куда будет занесено количество байт, помещенных в буфер драйвером push ebx; длина буфера push ebx; буфер, куда драйвер поместит свои данные push 4;длина данных push ecx;адрес данных для драйвера push DRIVER_QUERY_PROC_NOARGS;номер необходимой операции push eax;дескриптор драйвера, полученный через функцию CreateFile call _imp__DeviceIoControl@32 call _imp__CloseHandle@4 @@: push offset cusDevice call _imp__ZwUnloadDriver@4 lodsd;add esi,4 push esi;&esi='scp01',0 push Key call _imp__SHDeleteKeyA@8 push Key call _imp__RegCloseKey@4 push ebx;0 call _imp__ExitProcess@4 start endp ;-------------------------------------------------------------- musik: ;set bits 0,1 of PC speaker control register: in al,61h;текущее состояние порта 61h в AL or al,00000011b;установить 0-ой и 1-ый биты out 61h,al ;теперь динамик включен mov al,0B6h out 43h,al ;Timer 8253-5 (AT: 8254.2). mov esi,offset melody; данные mov ecx,size_melody ; счетчик mov dx,42h ;Timer 8253-5 (AT: 8254.2). push edi a15: push edx ;PC/XT PPI port B bits: push ecx ;0: Timer 2 gate OR 03h= speaker ON rdtsc ;1: Timer 2 data AND 0FCh= speaker OFF mov ecx,eax;2: mov edi,edx;3: 1=read high switches add ecx,TimerLo;4: 0=enable RAM parity checking adc edi,TimerHi;5: 0=enable I/O channel check @@: rdtsc; крутимся до тех пор пока не получится sub eax,ecx; задержка в 55 мСек sbb edx,edi jnz @b;6: 0=hold keyboard clock low pop ecx ;7: 0=enable keyboard pop edx ;Команда выводит данные в порт ввода-вывода, outsb ;номер которого загружен в регистр DX, loop a15 ;из ячейки памяти по адресу DS:ESI pop edi in al,61h and al,11111100b;сбросить 0-ой и 1-ый биты out 61h,al ;теперь динамик выключен retn ;---------------------------------------------------------------- .data melody dw 2 dup(354h),2,2,2 dup(387h),2,2,2 dup(3BDh),2 dup(387h),2 dup(3BDh) dw 2 dup(3F5h),2 dup(432h),2,2,2 dup(472h),2,2,4 dup(4B5h),4 dup(472h),2 dup(3F5h) dw 2,2,2 dup(432h),2,2,2 dup(472h),2 dup(432h),2 dup(472h),2 dup(4B5h),2 dup(4FDh) dw 2,2,2 dup(549h),2,2,4 dup(599h),4 dup(549h),2 dup(472h),2,2,2 dup(5EEh,2) dw 4 dup(649h),4 dup(5EEh),2 dup(472h),2,2,2 dup(5EEh,2),4 dup(649h),4 dup(5EEh) dw 2 dup(70Eh),2 dup(6A8h),2 dup(649h),2 dup(5EEh),2 dup(599h),2 dup(549h) dw 2 dup(4FDh),2 dup(4B5h),2 dup(472h),2,2,2 dup(432h),2,2,2 dup(3F5h),2,2 dw 2 dup(387h),2,2,8 dup(354h),2 size_melody = $ - melody ;---------------------------------------------------------------- TimerLo dd 0 TimerHi dd 0 scp01_sys_name db '\\.\scp01.sys',0 Query dd ? Bytes dd ? aSystem db 'SYSTEM\CurrentControlSet\Services',0 aType db "Type",0 aImagePath db "ImagePath",0 end start Третий вариант user-mode приложения, которое запускает драйвер scp01.sysУстанавливаем драйвер при помощи функции ZwSetSystemInformation Код (ASM): ; masm windows gui # .686P .model flat include windows.inc includelib kernel32.lib includelib advapi32.lib includelib ntdll.lib includelib shlwapi.lib extern _imp__ExitProcess@4:dword extern _imp__GetFullPathNameA@16:dword extern _imp__Sleep@4:dword extern _imp__CreateFileA@28:dword extern _imp__DeviceIoControl@32:dword extern _imp__CloseHandle@4:dword extern _imp__ZwSetSystemInformation@12:dword SERVICE_ERROR_NORMAL equ 1 MAX_PATH equ 260 DRIVER_QUERY_PROC_NOARGS equ 10h SystemLoadAndCallImage equ 26h .code start proc local GregsImage:qword local daPath[MAX_PATH]:CHAR local acDriverPath[MAX_PATH]:CHAR xor ebx,ebx rdtsc; замеряем скольким тикам соответствует 40 мСек mov TimerLo,eax mov TimerHi,edx push 40 call _imp__Sleep@4 rdtsc sub eax,TimerLo sbb edx,TimerHi mov TimerLo,eax mov TimerHi,edx mov esi,offset scp01_sys_name+4;&esi='scp01.sys',0 lea edi,acDriverPath mov eax,'\??\' stosd;путь к драйверу должен начаться с '\??\' ;-----инициализируем драйвер push eax;резервирую место в стеке push esp;указатель на пустое место в стеке push edi;&acDriverPath+4 push MAX_PATH push esi call _imp__GetFullPathNameA@16 pop ecx add eax,4 mov ecx,eax imul eax,20002h add eax,2 mov dword ptr GregsImage,eax lea edi,daPath mov dword ptr GregsImage+4,edi xor eax,eax lea esi,acDriverPath @@: lodsb stosw loop @b push sizeof GregsImage;8 lea edi,GregsImage push edi;&GregsImage push SystemLoadAndCallImage;=26h call _imp__ZwSetSystemInformation@12 mov esi,offset scp01_sys_name mov [esi+5],ebx;&esi=Drvname='scp01',0 push ebx push ebx push OPEN_EXISTING push ebx push FILE_SHARE_READ; or FILE_SHARE_WRITE push GENERIC_READ; or GENERIC_WRITE push esi call _imp__CreateFileA@28 inc eax;cmp eax,INVALID_HANDLE_VALUE je short @f dec eax push eax;дескриптор драйвера для CloseHandle mov ecx,offset Query mov [ecx],offset musik push ebx;адрес OVERLAPPED-структуры push offset Bytes;адрес переменой, куда будет занесено количество байт, помещенных в буфер драйвером push ebx; длина буфера push ebx; буфер, куда драйвер поместит свои данные push 4;длина данных push ecx;адрес данных для драйвера push DRIVER_QUERY_PROC_NOARGS;номер необходимой операции push eax;дескриптор драйвера, полученный через функцию CreateFile call _imp__DeviceIoControl@32 call _imp__CloseHandle@4 @@: push ebx;0 call _imp__ExitProcess@4 start endp ;-------------------------------------------------------------- musik: ;set bits 0,1 of PC speaker control register: in al,61h;текущее состояние порта 61h в AL or al,00000011b;установить 0-ой и 1-ый биты out 61h,al ;теперь динамик включен mov al,0B6h out 43h,al ;Timer 8253-5 (AT: 8254.2). mov esi,offset melody; данные mov ecx,size_melody ; счетчик mov dx,42h ;Timer 8253-5 (AT: 8254.2). push edi a15: push edx ;PC/XT PPI port B bits: push ecx ;0: Timer 2 gate OR 03h= speaker ON rdtsc ;1: Timer 2 data AND 0FCh= speaker OFF mov ecx,eax;2: mov edi,edx;3: 1=read high switches add ecx,TimerLo;4: 0=enable RAM parity checking adc edi,TimerHi;5: 0=enable I/O channel check @@: rdtsc; крутимся до тех пор пока не получится sub eax,ecx; задержка в 55 мСек sbb edx,edi jnz @b;6: 0=hold keyboard clock low pop ecx ;7: 0=enable keyboard pop edx ;Команда выводит данные в порт ввода-вывода, outsb ;номер которого загружен в регистр DX, loop a15 ;из ячейки памяти по адресу DS:ESI pop edi in al,61h and al,11111100b;сбросить 0-ой и 1-ый биты out 61h,al ;теперь динамик выключен retn ;---------------------------------------------------------------- .data melody dw 2 dup(354h),2,2,2 dup(387h),2,2,2 dup(3BDh),2 dup(387h),2 dup(3BDh) dw 2 dup(3F5h),2 dup(432h),2,2,2 dup(472h),2,2,4 dup(4B5h),4 dup(472h),2 dup(3F5h) dw 2,2,2 dup(432h),2,2,2 dup(472h),2 dup(432h),2 dup(472h),2 dup(4B5h),2 dup(4FDh) dw 2,2,2 dup(549h),2,2,4 dup(599h),4 dup(549h),2 dup(472h),2,2,2 dup(5EEh,2) dw 4 dup(649h),4 dup(5EEh),2 dup(472h),2,2,2 dup(5EEh,2),4 dup(649h),4 dup(5EEh) dw 2 dup(70Eh),2 dup(6A8h),2 dup(649h),2 dup(5EEh),2 dup(599h),2 dup(549h) dw 2 dup(4FDh),2 dup(4B5h),2 dup(472h),2,2,2 dup(432h),2,2,2 dup(3F5h),2,2 dw 2 dup(387h),2,2,8 dup(354h),2 size_melody = $ - melody ;---------------------------------------------------------------- TimerLo dd 0 TimerHi dd 0 scp01_sys_name db '\\.\scp01.sys',0 Query dd ? Bytes dd ? end start Доступ к командам In/Out через драйвер режима ядраПеределываю драйвер <R>ing-<0> <P>rocedure <C>all & Direct Port I/O (r0pc.sys) by @L.chemist (Andrey A. Meshkov). Драйвер scp02 передает управление на фрагмент кода из scp02-00.exe в котором содержится команда outsb, синхронизация производится через предварительный подсчет тиков. Адрес процедуры передается через WriteFile("\\.\scp02"). Полнофункциональный драйвер. Далее исходный текст драйвера scp02.sys Код (ASM): ; masm windows native # ;написано на основе драйвера режима ядра <R>ing-<0> <P>rocedure ;<C>all & Direct Port I/O (r0pc.sys) by @L.chemist (Andrey A. Meshkov) .686p .model flat include ntstatus.inc include ntddk.inc include Strings.mac includelib ntoskrnl.lib ; -------------------------------------------------------------------------------- extern _imp__IoCreateDevice@28:dword extern _imp__IoCreateSymbolicLink@8:dword extern _imp__IoDeleteDevice@4:dword extern _imp__IoDeleteSymbolicLink@4:dword extern _imp_@IofCompleteRequest@8:dword IOPM_SIZE equ 2000h ; sizeof I/O permission map DRIVER_QUERY_PROC_NOARGS = 10h ; -------------------------------------------------------------------------------- ; structure for driver query DriverQuery struct iocode dd ? ; user I/O code wparam dd ? ; parameter lparam dd ? ; parameter DriverQuery ends PDriverQuery equ dd ; -------------------------------------------------------------------------------- .const CCOUNTED_UNICODE_STRING "\\Device\\scp02", cusDevice, 4 CCOUNTED_UNICODE_STRING "\\DosDevices\\scp02", cusSymbolicLink, 4 .code ; Процедура входа драйвера. Эта процедура вызывается только раз после загрузки ; драйвера в память. Она выделяет необходимые ресурсы для работы драйвера. В ; нашем случае DriverEntry proc lpDriverObject:PDRIVER_OBJECT, lpusRegistryPath:PUNICODE_STRING local deviceObject:PDEVICE_OBJECT local status:NTSTATUS ; инициализируем драйвер устройства и объект устройства (Device Object) lea eax,deviceObject push eax;&deviceObject push 0 push 0 push FILE_DEVICE_UNKNOWN;22h push offset cusDevice push sizeof(DriverQuery);0Ch push lpDriverObject call _imp__IoCreateDevice@28 test eax,eax;cmp eax,STATUS_SUCCESS; 0 jnz short exit; break on error ; create symbolic link to the user-visible name ; создаем символическую ссылку на драйвер устройства, что позволяет user-mode ; приложению получить доступ к нашему драйверу, используя "\\.\scp02" нотацию push offset cusDevice push offset cusSymbolicLink call _imp__IoCreateSymbolicLink@8 mov status,eax; save status test eax,eax;cmp eax,STATUS_SUCCESS; check result jz short success push deviceObject call _imp__IoDeleteDevice@4; delete device object if not successful jmp short exit success: ; continue on success ; load structure to point to IRP handlers ; инициализируем точки входа драйвера в объект драйвера ; всё, что нам нужно, это операция создания (Create), записи (Write) ; и выгрузки (Unload) mov eax,lpDriverObject mov (DRIVER_OBJECT ptr [eax]).DriverUnload,offset DriverUnload mov (DRIVER_OBJECT ptr [eax]).MajorFunction[IRP_MJ_CREATE*4],offset DispatchCreate mov (DRIVER_OBJECT ptr [eax]).MajorFunction[IRP_MJ_WRITE*4],offset DispatchWrite mov eax,status; assign result exit: leave retn 8 DriverEntry endp ;------------------------------------------------------ ;освобождаем все выделенные ранее объекты DriverUnload: lpDriverObject equ dword ptr [esp+4];:PDRIVER_OBJECT ; delete symbolic link to the user-visible name push offset cusSymbolicLink call _imp__IoDeleteSymbolicLink@4 ; delete device object mov ecx,lpDriverObject push dword ptr (DRIVER_OBJECT ptr [ecx]).DeviceObject call _imp__IoDeleteDevice@4 retn 4 ;-------------------------------------------------- DispatchCreate: lIrp equ dword ptr [esp+8] mov ecx,lIrp and (_IRP ptr [ecx]).IoStatus.Status,STATUS_SUCCESS;0 and (_IRP ptr [ecx]).IoStatus.Information,0 xor edx,edx;edx = IO_NO_INCREMENT call _imp_@IofCompleteRequest@8;IofCompleteRequest (lpIrp, IO_NO_INCREMENT) xor eax,eax;mov eax,STATUS_SUCCESS; 0 retn 8 ;--------------------------------------------------------- DispatchWrite proc pDeviceObject:PDRIVER_OBJECT, lpIrp:PIRP local status:NTSTATUS push edi mov status,STATUS_UNSUCCESSFUL; 0C0000001h mov edi,lpIrp and (_IRP ptr [edi]).IoStatus.Information,0; mov eax,(_IRP ptr [edi]).Tail.Overlay.CurrentStackLocation cmp [eax].IO_STACK_LOCATION.Parameters.Write._Length, sizeof(DriverQuery) jnz a0 mov status,STATUS_NOT_IMPLEMENTED; 0C0000002h mov eax,(_IRP ptr [edi]).UserBuffer cmp (DriverQuery ptr [eax]).iocode,DRIVER_QUERY_PROC_NOARGS jne a0 call (DriverQuery ptr [eax]).wparam and status,STATUS_SUCCESS;status = 0 a0: push status pop (_IRP ptr [edi]).IoStatus.Status mov ecx,edi xor edx,edx;edx = IO_NO_INCREMENT call _imp_@IofCompleteRequest@8;IofCompleteRequest (lpIrp, IO_NO_INCREMENT) mov eax,status pop edi leave retn 8 DispatchWrite endp ;---------------------------------------------------- end DriverEntry
Второй вариант user-mode приложения, которое запускает драйвер scp02.sysПредварительно прописываем драйвер scp02.sys в реестре «вручную» (используем функции RegOpenKey, RegCreateKey, RegSetValue) и загружаем драйвер scp02.sys с помощью функции ZwLoadDriver. В реестре создается минимум необходимых записей, запускаем драйвер, выгружаем драйвер функцией ZwUnloadDriver и удаляем его раздел из реестра (используем функции RegCloseKey, SHDeleteKey). Этот способ позволяет запускать драйвер быстро и незаметно и подходит для маленьких программ не требующих установки, но требующих запуска своего драйвера. Код (ASM): ; masm windows gui # .686P .model flat include windows.inc includelib kernel32.lib includelib advapi32.lib includelib ntdll.lib includelib shlwapi.lib extern _imp__ExitProcess@4:dword extern _imp__GetFullPathNameA@16:dword extern _imp__Sleep@4:dword extern _imp__CreateFileA@28:dword extern _imp__DeviceIoControl@32:dword extern _imp__CloseHandle@4:dword extern _imp__RegOpenKeyA@12:dword extern _imp__RegCreateKeyA@12:dword extern _imp__RegSetValueExA@24:dword extern _imp__RegCloseKey@4:dword extern _imp__SHDeleteKeyA@8:dword extern _imp__ZwLoadDriver@4:dword extern _imp__ZwUnloadDriver@4:dword SERVICE_ERROR_NORMAL equ 1 MAX_PATH equ 260 DRIVER_QUERY_PROC_NOARGS equ 10h ;--------macros------------------- du macro string irpc c,<string> if '&c'gt 127 db ('&c'- 0B0h),4 else dw '&c' endif endm dw 0 endm .const align 4 us: du <\registry\machine\SYSTEM\CurrentControlSet\Services\scp01> len_us = $-us align 4 cusDevice dw (len_us - 2) dw (len_us) dd us .code start proc local Key2:HKEY local Key:HKEY local acDriverPath[MAX_PATH]:CHAR xor ebx,ebx rdtsc; замеряем скольким тикам соответствует 40 мСек mov TimerLo,eax mov TimerHi,edx push 40 call _imp__Sleep@4 rdtsc sub eax,TimerLo sbb edx,TimerHi mov TimerLo,eax mov TimerHi,edx mov esi,offset scp01_sys_name+4;&esi='scp01.sys',0 lea edi,acDriverPath mov eax,'\??\' stosd;путь к драйверу должен начаться с '\??\' ;-----инициализируем драйвер push eax;резервирую место в стеке push esp;указатель на пустое место в стеке push edi;&acDriverPath+4 sub edi,4 push MAX_PATH push esi call _imp__GetFullPathNameA@16 add eax,4 push eax lea eax,Key push eax push offset aSystem push HKEY_LOCAL_MACHINE call _imp__RegOpenKeyA@12 mov [esi+5],ebx;&esi=Drvname='scp01',0 lea eax,Key2 push eax push esi push Key call _imp__RegCreateKeyA@12 push edi;&acDriverPath push REG_SZ push ebx;NULL push offset aImagePath push Key2 call _imp__RegSetValueExA@24 mov eax,esp;указатель на пустое место в стеке push sizeof(dword) mov dword ptr [eax],1;в пустое место в стеке поместим переменную dType=1 push eax;&dType push REG_DWORD;тип переменной dType push ebx;0 push offset aType;название переменной dType push Key2 call _imp__RegSetValueExA@24 push Key2 call _imp__RegCloseKey@4 mov [esp],offset cusDevice call _imp__ZwLoadDriver@4; выравниваем стек после GetFullPathNameA sub esi,4;&esi='\\.\scp01',0 push ebx push ebx push OPEN_EXISTING push ebx push FILE_SHARE_READ; or FILE_SHARE_WRITE push GENERIC_READ; or GENERIC_WRITE push esi call _imp__CreateFileA@28 inc eax;cmp eax,INVALID_HANDLE_VALUE je short @f dec eax push eax;дескриптор драйвера для CloseHandle mov ecx,offset Query mov [ecx],offset musik push ebx;адрес OVERLAPPED-структуры push offset Bytes;адрес переменой, куда будет занесено количество байт, помещенных в буфер драйвером push ebx; длина буфера push ebx; буфер, куда драйвер поместит свои данные push 4;длина данных push ecx;адрес данных для драйвера push DRIVER_QUERY_PROC_NOARGS;номер необходимой операции push eax;дескриптор драйвера, полученный через функцию CreateFile call _imp__DeviceIoControl@32 call _imp__CloseHandle@4 @@: push offset cusDevice call _imp__ZwUnloadDriver@4 lodsd;add esi,4 push esi;&esi='scp01',0 push Key call _imp__SHDeleteKeyA@8 push Key call _imp__RegCloseKey@4 push ebx;0 call _imp__ExitProcess@4 start endp ;-------------------------------------------------------------- musik: ;set bits 0,1 of PC speaker control register: in al,61h;текущее состояние порта 61h в AL or al,00000011b;установить 0-ой и 1-ый биты out 61h,al ;теперь динамик включен mov al,0B6h out 43h,al ;Timer 8253-5 (AT: 8254.2). mov esi,offset melody; данные mov ecx,size_melody ; счетчик mov dx,42h ;Timer 8253-5 (AT: 8254.2). push edi a15: push edx ;PC/XT PPI port B bits: push ecx ;0: Timer 2 gate OR 03h= speaker ON rdtsc ;1: Timer 2 data AND 0FCh= speaker OFF mov ecx,eax;2: mov edi,edx;3: 1=read high switches add ecx,TimerLo;4: 0=enable RAM parity checking adc edi,TimerHi;5: 0=enable I/O channel check @@: rdtsc; крутимся до тех пор пока не получится sub eax,ecx; задержка в 55 мСек sbb edx,edi jnz @b;6: 0=hold keyboard clock low pop ecx ;7: 0=enable keyboard pop edx ;Команда выводит данные в порт ввода-вывода, outsb ;номер которого загружен в регистр DX, loop a15 ;из ячейки памяти по адресу DS:ESI pop edi in al,61h and al,11111100b;сбросить 0-ой и 1-ый биты out 61h,al ;теперь динамик выключен retn ;---------------------------------------------------------------- .data melody dw 2 dup(354h),2,2,2 dup(387h),2,2,2 dup(3BDh),2 dup(387h),2 dup(3BDh) dw 2 dup(3F5h),2 dup(432h),2,2,2 dup(472h),2,2,4 dup(4B5h),4 dup(472h),2 dup(3F5h) dw 2,2,2 dup(432h),2,2,2 dup(472h),2 dup(432h),2 dup(472h),2 dup(4B5h),2 dup(4FDh) dw 2,2,2 dup(549h),2,2,4 dup(599h),4 dup(549h),2 dup(472h),2,2,2 dup(5EEh,2) dw 4 dup(649h),4 dup(5EEh),2 dup(472h),2,2,2 dup(5EEh,2),4 dup(649h),4 dup(5EEh) dw 2 dup(70Eh),2 dup(6A8h),2 dup(649h),2 dup(5EEh),2 dup(599h),2 dup(549h) dw 2 dup(4FDh),2 dup(4B5h),2 dup(472h),2,2,2 dup(432h),2,2,2 dup(3F5h),2,2 dw 2 dup(387h),2,2,8 dup(354h),2 size_melody = $ - melody ;---------------------------------------------------------------- TimerLo dd 0 TimerHi dd 0 scp01_sys_name db '\\.\scp01.sys',0 Query dd ? Bytes dd ? aSystem db 'SYSTEM\CurrentControlSet\Services',0 aType db "Type",0 aImagePath db "ImagePath",0 end start Третий вариант user-mode приложения, которое запускает драйвер scp01.sysУстанавливаем драйвер при помощи функции ZwSetSystemInformation Код (ASM): ; masm windows gui # .686P .model flat include windows.inc includelib kernel32.lib includelib advapi32.lib includelib ntdll.lib includelib shlwapi.lib extern _imp__ExitProcess@4:dword extern _imp__GetFullPathNameA@16:dword extern _imp__Sleep@4:dword extern _imp__CreateFileA@28:dword extern _imp__DeviceIoControl@32:dword extern _imp__CloseHandle@4:dword extern _imp__ZwSetSystemInformation@12:dword SERVICE_ERROR_NORMAL equ 1 MAX_PATH equ 260 DRIVER_QUERY_PROC_NOARGS equ 10h SystemLoadAndCallImage equ 26h .code start proc local GregsImage:qword local daPath[MAX_PATH]:CHAR local acDriverPath[MAX_PATH]:CHAR xor ebx,ebx rdtsc; замеряем скольким тикам соответствует 40 мСек mov TimerLo,eax mov TimerHi,edx push 40 call _imp__Sleep@4 rdtsc sub eax,TimerLo sbb edx,TimerHi mov TimerLo,eax mov TimerHi,edx mov esi,offset scp01_sys_name+4;&esi='scp01.sys',0 lea edi,acDriverPath mov eax,'\??\' stosd;путь к драйверу должен начаться с '\??\' ;-----инициализируем драйвер push eax;резервирую место в стеке push esp;указатель на пустое место в стеке push edi;&acDriverPath+4 push MAX_PATH push esi call _imp__GetFullPathNameA@16 pop ecx add eax,4 mov ecx,eax imul eax,20002h add eax,2 mov dword ptr GregsImage,eax lea edi,daPath mov dword ptr GregsImage+4,edi xor eax,eax lea esi,acDriverPath @@: lodsb stosw loop @b push sizeof GregsImage;8 lea edi,GregsImage push edi;&GregsImage push SystemLoadAndCallImage;=26h call _imp__ZwSetSystemInformation@12 mov esi,offset scp01_sys_name mov [esi+5],ebx;&esi=Drvname='scp01',0 push ebx push ebx push OPEN_EXISTING push ebx push FILE_SHARE_READ; or FILE_SHARE_WRITE push GENERIC_READ; or GENERIC_WRITE push esi call _imp__CreateFileA@28 inc eax;cmp eax,INVALID_HANDLE_VALUE je short @f dec eax push eax;дескриптор драйвера для CloseHandle mov ecx,offset Query mov [ecx],offset musik push ebx;адрес OVERLAPPED-структуры push offset Bytes;адрес переменой, куда будет занесено количество байт, помещенных в буфер драйвером push ebx; длина буфера push ebx; буфер, куда драйвер поместит свои данные push 4;длина данных push ecx;адрес данных для драйвера push DRIVER_QUERY_PROC_NOARGS;номер необходимой операции push eax;дескриптор драйвера, полученный через функцию CreateFile call _imp__DeviceIoControl@32 call _imp__CloseHandle@4 @@: push ebx;0 call _imp__ExitProcess@4 start endp ;-------------------------------------------------------------- musik: ;set bits 0,1 of PC speaker control register: in al,61h;текущее состояние порта 61h в AL or al,00000011b;установить 0-ой и 1-ый биты out 61h,al ;теперь динамик включен mov al,0B6h out 43h,al ;Timer 8253-5 (AT: 8254.2). mov esi,offset melody; данные mov ecx,size_melody ; счетчик mov dx,42h ;Timer 8253-5 (AT: 8254.2). push edi a15: push edx ;PC/XT PPI port B bits: push ecx ;0: Timer 2 gate OR 03h= speaker ON rdtsc ;1: Timer 2 data AND 0FCh= speaker OFF mov ecx,eax;2: mov edi,edx;3: 1=read high switches add ecx,TimerLo;4: 0=enable RAM parity checking adc edi,TimerHi;5: 0=enable I/O channel check @@: rdtsc; крутимся до тех пор пока не получится sub eax,ecx; задержка в 55 мСек sbb edx,edi jnz @b;6: 0=hold keyboard clock low pop ecx ;7: 0=enable keyboard pop edx ;Команда выводит данные в порт ввода-вывода, outsb ;номер которого загружен в регистр DX, loop a15 ;из ячейки памяти по адресу DS:ESI pop edi in al,61h and al,11111100b;сбросить 0-ой и 1-ый биты out 61h,al ;теперь динамик выключен retn ;---------------------------------------------------------------- .data melody dw 2 dup(354h),2,2,2 dup(387h),2,2,2 dup(3BDh),2 dup(387h),2 dup(3BDh) dw 2 dup(3F5h),2 dup(432h),2,2,2 dup(472h),2,2,4 dup(4B5h),4 dup(472h),2 dup(3F5h) dw 2,2,2 dup(432h),2,2,2 dup(472h),2 dup(432h),2 dup(472h),2 dup(4B5h),2 dup(4FDh) dw 2,2,2 dup(549h),2,2,4 dup(599h),4 dup(549h),2 dup(472h),2,2,2 dup(5EEh,2) dw 4 dup(649h),4 dup(5EEh),2 dup(472h),2,2,2 dup(5EEh,2),4 dup(649h),4 dup(5EEh) dw 2 dup(70Eh),2 dup(6A8h),2 dup(649h),2 dup(5EEh),2 dup(599h),2 dup(549h) dw 2 dup(4FDh),2 dup(4B5h),2 dup(472h),2,2,2 dup(432h),2,2,2 dup(3F5h),2,2 dw 2 dup(387h),2,2,8 dup(354h),2 size_melody = $ - melody ;---------------------------------------------------------------- TimerLo dd 0 TimerHi dd 0 scp01_sys_name db '\\.\scp01.sys',0 Query dd ? Bytes dd ? end start Доступ к командам In/Out через драйвер режима ядра Переделываю драйвер <R>ing-<0> <P>rocedure <C>all & Direct Port I/O (r0pc.sys) by @L.chemist (Andrey A. Meshkov). Драйвер scp02 передает управление на фрагмент кода из scp02-00.exe в котором содержится команда outsb, синхронизация производится через предварительный подсчет тиков. Адрес процедуры передается через WriteFile("\\.\scp02"). Полнофункциональный драйвер. Далее исходный текст драйвера scp02.sys Код (ASM): ; masm windows native # ;написано на основе драйвера режима ядра <R>ing-<0> <P>rocedure ;<C>all & Direct Port I/O (r0pc.sys) by @L.chemist (Andrey A. Meshkov) .686p .model flat include ntstatus.inc include ntddk.inc include Strings.mac includelib ntoskrnl.lib ; -------------------------------------------------------------------------------- extern _imp__IoCreateDevice@28:dword extern _imp__IoCreateSymbolicLink@8:dword extern _imp__IoDeleteDevice@4:dword extern _imp__IoDeleteSymbolicLink@4:dword extern _imp_@IofCompleteRequest@8:dword IOPM_SIZE equ 2000h ; sizeof I/O permission map DRIVER_QUERY_PROC_NOARGS = 10h ; -------------------------------------------------------------------------------- ; structure for driver query DriverQuery struct iocode dd ? ; user I/O code wparam dd ? ; parameter lparam dd ? ; parameter DriverQuery ends PDriverQuery equ dd ; -------------------------------------------------------------------------------- .const CCOUNTED_UNICODE_STRING "\\Device\\scp02", cusDevice, 4 CCOUNTED_UNICODE_STRING "\\DosDevices\\scp02", cusSymbolicLink, 4 .code ; Процедура входа драйвера. Эта процедура вызывается только раз после загрузки ; драйвера в память. Она выделяет необходимые ресурсы для работы драйвера. В ; нашем случае DriverEntry proc lpDriverObject:PDRIVER_OBJECT, lpusRegistryPath:PUNICODE_STRING local deviceObject:PDEVICE_OBJECT local status:NTSTATUS ; инициализируем драйвер устройства и объект устройства (Device Object) lea eax,deviceObject push eax;&deviceObject push 0 push 0 push FILE_DEVICE_UNKNOWN;22h push offset cusDevice push sizeof(DriverQuery);0Ch push lpDriverObject call _imp__IoCreateDevice@28 test eax,eax;cmp eax,STATUS_SUCCESS; 0 jnz short exit; break on error ; create symbolic link to the user-visible name ; создаем символическую ссылку на драйвер устройства, что позволяет user-mode ; приложению получить доступ к нашему драйверу, используя "\\.\scp02" нотацию push offset cusDevice push offset cusSymbolicLink call _imp__IoCreateSymbolicLink@8 mov status,eax; save status test eax,eax;cmp eax,STATUS_SUCCESS; check result jz short success push deviceObject call _imp__IoDeleteDevice@4; delete device object if not successful jmp short exit success: ; continue on success ; load structure to point to IRP handlers ; инициализируем точки входа драйвера в объект драйвера ; всё, что нам нужно, это операция создания (Create), записи (Write) ; и выгрузки (Unload) mov eax,lpDriverObject mov (DRIVER_OBJECT ptr [eax]).DriverUnload,offset DriverUnload mov (DRIVER_OBJECT ptr [eax]).MajorFunction[IRP_MJ_CREATE*4],offset DispatchCreate mov (DRIVER_OBJECT ptr [eax]).MajorFunction[IRP_MJ_WRITE*4],offset DispatchWrite mov eax,status; assign result exit: leave retn 8 DriverEntry endp ;------------------------------------------------------ ;освобождаем все выделенные ранее объекты DriverUnload: lpDriverObject equ dword ptr [esp+4];:PDRIVER_OBJECT ; delete symbolic link to the user-visible name push offset cusSymbolicLink call _imp__IoDeleteSymbolicLink@4 ; delete device object mov ecx,lpDriverObject push dword ptr (DRIVER_OBJECT ptr [ecx]).DeviceObject call _imp__IoDeleteDevice@4 retn 4 ;-------------------------------------------------- DispatchCreate: lIrp equ dword ptr [esp+8] mov ecx,lIrp and (_IRP ptr [ecx]).IoStatus.Status,STATUS_SUCCESS;0 and (_IRP ptr [ecx]).IoStatus.Information,0 xor edx,edx;edx = IO_NO_INCREMENT call _imp_@IofCompleteRequest@8;IofCompleteRequest (lpIrp, IO_NO_INCREMENT) xor eax,eax;mov eax,STATUS_SUCCESS; 0 retn 8 ;--------------------------------------------------------- DispatchWrite proc pDeviceObject:PDRIVER_OBJECT, lpIrp:PIRP local status:NTSTATUS push edi mov status,STATUS_UNSUCCESSFUL; 0C0000001h mov edi,lpIrp and (_IRP ptr [edi]).IoStatus.Information,0; mov eax,(_IRP ptr [edi]).Tail.Overlay.CurrentStackLocation cmp [eax].IO_STACK_LOCATION.Parameters.Write._Length, sizeof(DriverQuery) jnz a0 mov status,STATUS_NOT_IMPLEMENTED; 0C0000002h mov eax,(_IRP ptr [edi]).UserBuffer cmp (DriverQuery ptr [eax]).iocode,DRIVER_QUERY_PROC_NOARGS jne a0 call (DriverQuery ptr [eax]).wparam and status,STATUS_SUCCESS;status = 0 a0: push status pop (_IRP ptr [edi]).IoStatus.Status mov ecx,edi xor edx,edx;edx = IO_NO_INCREMENT call _imp_@IofCompleteRequest@8;IofCompleteRequest (lpIrp, IO_NO_INCREMENT) mov eax,status pop edi leave retn 8 DispatchWrite endp ;---------------------------------------------------- end DriverEntry
Доступ к командам In/Out через изменение карты IOPM Manipulating the IOPM (I/O Permission Bitmap) Changing the IOPM within your Kernel Mode Drivers requires the knowledge of a couple of undocumented calls. These are Ke386IoSetAccessProcess, Ke386SetIoAccessMap and PsLookupProcessByProcessIdНа написание статьи меня подтолкнуло чтение Криса Касперски «Методы низкоуровневого управления приводами» и упомянутая в этой работе статья Dale Roberts от 01 мая 1996 «Direct Port I/O and Windows NT». Позже в KmdTutor by Four-F я нашел драйвер giveio, который используя функции Ke386SetIoAccessMap, Ke386QueryIoAccessMap и Ke386IoSetAccessProcess предоставлял доступ к портам ввода/вывода user-mode приложениям.ТеорияСчитается, что в Windows NT/2k/XP прямое обращение к портам возможно только на уровне ядра, а приложения вынуждены общаться с портами через высокоуровневый интерфейс, предоставляемый драйвером. На самом деле, выполнять команды IN/OUT можно и на прикладном уровне, правда не без помощи недокументированных возможностей операционной системы и документированных, но малоизвестных особенностей реализации защищенного режима работы в процессорах Intel. В «Instruction Set Reference» приведен псевдокод инструкции OUT: Код (Text): IF ((PE = 1) and ((CPL > IOPL) or (VM = 1))) THEN (* Protected mode with CPL > IOPL or virtual-8086 mode *) IF (Any I/O Permission Bit for I/O port being accessed = 1) THEN (* I/O operation is not allowed *) #GP(0); ELSE ( * I/O operation is allowed *) DEST <- SRC; (* Read from selected I/O port *) FI; ELSE (* Real Mode or Protected Mode with CPL <= IOPL *) DEST <- SRC; (* Read from selected I/O port *) FI; и псевдокод инструкции IN Код (Text): IF ((PE = 1) and ((CPL > IOPL) or (VM = 1))) THEN (* Protected mode with CPL > IOPL or virtual-8086 mode *) IF (Any I/O Permission Bit for I/O port being accessed = 1) THEN (* I/O operation is not allowed *) #GP(0); ELSE ( * I/O operation is allowed *) DEST <- SRC; (* Writes to selected I/O port *) FI; ELSE (* Real Mode or Protected Mode with CPL <= IOPL *) DEST <- SRC; (* Writes to selected I/O port *) FI; Обнаружив, что полномочий текущего уровня привилегий недостаточно для выполнения команды IN/OUT, процессор не спешит выбросить исключение general protection fault, а осуществляет дополнительную проверку на предмет состояния карты разрешения ввода/вывода (IOPM ― I/O Permission bitMap) и, если бит памяти, соответствующий данному порту равен нулю, то вывод в порт осуществляется несмотря на запрет со стороны CPL. Таким образом, для взаимодействия с портами с прикладного уровня достаточно скорректировать карту разрешения ввода/вывода, после чего подсистема защиты операционной системы Windows NT перестает мешать, поскольку контроль доступа к портам осуществляется не на программном, а на аппаратном уровне и, если процессор перестанет выбрасывать исключения, операционная система ничего не узнает о происходящем. Из «Architecture Software Developer's Manual Volume 1: Basic Architecture», узнаем, что карта ввода/вывода находится в сегменте состояния задачи (TSS ― Task State Segment), смещение IOPM относительно начала TSS определяется 32-битным полем, расположенном в 66h и 67h байтах сегмента состояния задачи. Нулевой бит карты IOPM отвечает за нулевой порт, первый - за первый, второй - за второй и т.д. вплоть до старшего бита 2000h-байта, отвечающего за 65535 порт. Битовую карту завершает так называемый байт-терминатор, имеющий значение 0FFh. Порты, чьи биты сброшены в нулевое значение, доступны с прикладного уровня без ограничений. Сама карта ввода/вывода доступа лишь драйверам, но не приложениям, поэтому без написания собственного драйвера не обойтись. Однако этот драйвер будет работать только на стадии инициализации, а весь дальнейший ввод/вывод пойдет напрямую, даже если выгрузить драйвер из памяти. В Windows NT смещение карты ввода/вывода по умолчанию находится за пределами сегмента состояния задачи и потому модифицировать карту ввода/вывода не так-то просто. То есть карта ввода/вывода в TSS есть, но она заблокирована системой. Попытка подкорректировать указатель на карту ввода/вывода ни к чему не приводит, поскольку Windows NT хранит копию этого значения в контексте процесса, а потому при переключении контекста указатель на прежнюю карту автоматически восстанавливается. Можно увеличить размер сегмента состояния задачи так, чтобы адрес карты ввода/вывода, прежде указывающий за его конец, теперь приходился на подвластную нам область памяти. Поскольку в хвосте последней страницы, занятой TSS, имеется всего лишь 0F55h байт, максимальный размер карты, которую мы можем создать в этом промежутке, охватывает всего лишь 31392 портов ввода/вывода. Дейлом Робертсом («Direct Port I/O and Windows NT» by Dale Roberts) были обнаружены три недокументированные функции: Ke386SetIoAccessMap, Ke386QueryIoAccessMap и Ke386IoSetAccessProcess, которые обеспечивают управление картой ввода/вывода. Эти функции находятся в NTOSKRNL.EXE и они доступы с уровня драйверов. Функция Ke386SetIoAccessMap принимает два аргумента: dwFlag ― длинное целое, которое нужно установить в 1, чтоб функция работала, и pIopm ― указатель на буфер. Функция копирует переданную карту доступа к портам ввода/вывода длинной 2000h из буфера в TSS по смещению 88h. ПараметрОписаниеdwFlagтолько 1 - разрешает копирование. При любом другом значении функция возвращает ошибкуpIopmуказатель на блок памяти для приема IOPM, размером не менее 2000h байт.Функция Ke386QueryIoAccessMap принимает те же аргументы, но делает прямопротивоположное, копируя текущую IOPM из TSS в буфер длинной 2000h. Если аргумент dwFlag установлен в 0, set-функция копирует в IOPM 0FFh, а query-функция копирует 0FFh в пользовательский буфер. ПараметрОписаниеdwFlag0 - заполнить буфер единичными битами (то есть запретить доступ ко всем портам);1 - скопировать текущую IOPM из TSS в буфер.pIopm указатель на блок памяти для приема IOPM, размером не менее 2000h байт. Функция Ke386IoSetAccessProcess принимает два аргумента: pProcess ― указатель на структуру процесса, полученный вызовом функции PsGetCurrentProcess и dwFlag ― целое, которое должно быть установлено в 1 чтобы разрешить доступ к портам ввода/вывода, или 0 чтоб запретить его. Когда аргумент dwFlag равен 0, функция запрещает доступ к портам ввода/вывода установкой смещения IOPM переданного процесса за границу сегмента TSS. Когда аргумент dwFlag равен 1, функция разрешает доступ к портам ввода/вывода, устанавливая смещение IOPM переданного процесса на начало IOPM по смещению 88h в TSS. ПараметрОписаниеpProcessуказатель на структуру KPROCESSdwFlag0 - запретить доступ к портам ввода-вывода, установкой смещения IOPM за границу сегмента TSS;1 - разрешить доступ к портам ввода-вывода, устанавливая смещение IOPM в пределах TSS равным 88h.Используя set- и query- функции можно читать, изменять, и записывать обратно IOPM, предоставляя доступ к нужным портам, устанавливая соответствующие биты для них в 0. ПрактикаДрайвер scp04 написан на основе драйвера giveio by Four-F, открывает порты ввода/вывода через изменение карты IOPM и использует функции Ke386SetIoAccessMap, Ke386QueryIoAccessMap, Ke386IoSetAccessProcess. Значение ProcessId драйвер получает через реестр. Использование драйвера scp04 позволяет одновременно использовать команду outsb и WinAPI функции пользовательского режима, например функцию Sleep. scp04 представляет собой неполнофункциональный драйвер, то есть самостоятельно только стартует и открывает порты ввода/вывода после чего возвращает код ошибки и система удаляет его из памяти. Функции Ke386SetIoAccessMap, Ke386QueryIoAccessMap и Ke386IoSetAccessProcess это только «обертки» для функций вызываемых из hal.dll. Правда, как показала проверка, при запуске на Celeron 466 и на двухядерном Pentium 4 функции из hal.dll, заменяющие функции из ntoskernl.exe могут отличаться, я попытался найти компромиссное решение. В результате команды IN и OUT удалось поместить в user-mode приложение, что дает возможность «написания быстрой программы для взаимодействия с устройством, в которой printf()'ы и getchar()'ы чередовались бы с командами ввода/вывода» (Dale Roberts). Далее исходный текст драйвера scp04.sys Код (ASM): ; masm windows native # ;написано на основе драйвера режима ядра giveio из KmdTutor by Four-F .686 .model flat include ntddk.inc include Strings.mac includelib ntoskrnl.lib extern _imp__ZwQueryValueKey@24:dword extern _imp__ZwOpenKey@12:dword extern _imp__ZwClose@4:dword extern _imp__MmAllocateNonCachedMemory@4:dword extern _imp__PsLookupProcessByProcessId@8:dword extern _imp__ObDereferenceObject@4:dword extern _imp__MmFreeNonCachedMemory@8:dword extern _imp__Ke386SetIoAccessMap@8:dword extern _imp__Ke386QueryIoAccessMap@8:dword extern _imp__Ke386IoSetAccessProcess@8:dword ;-------------------------------------------- IOPM_SIZE equ 2000h ; sizeof I/O permission map .const CCOUNTED_UNICODE_STRING "ProcessId", cusProcessid, 4 ;-------------------------------------------- .code ;-------------------------------------------- DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING local status:NTSTATUS local oa:OBJECT_ATTRIBUTES local hKey:HANDLE local kvpi:KEY_VALUE_PARTIAL_INFORMATION local pIopm:PVOID local pProcess:PVOID xor ebx,ebx mov status, STATUS_DEVICE_CONFIGURATION_ERROR ;Для последующего вызова функции ZwOpenKey нам потребуется указатель на ;заполненную структуру OBJECT_ATTRIBUTES lea ecx, oa mov (OBJECT_ATTRIBUTES ptr [ecx])._Length,sizeof(OBJECT_ATTRIBUTES) mov (OBJECT_ATTRIBUTES ptr [ecx]).RootDirectory,ebx push pusRegistryPath pop (OBJECT_ATTRIBUTES ptr [ecx]).ObjectName mov (OBJECT_ATTRIBUTES ptr [ecx]).Attributes,ebx mov (OBJECT_ATTRIBUTES ptr [ecx]).SecurityDescriptor,ebx mov (OBJECT_ATTRIBUTES ptr [ecx]).SecurityQualityOfService,ebx ;Вызовом функции ZwOpenKey получаем описатель раздела реестра в переменной ;hKey. Вторым параметром в эту функцию передаются права доступа, третьим - ;указатель на структуру OBJECT_ATTRIBUTES, заполненную на предыдущем этапе. push ecx push KEY_READ lea eax,hKey push eax call _imp__ZwOpenKey@12 test eax,eax jnz a7;.if eax == STATUS_SUCCESS ;С помощью функции ZwQueryValueKey получаем значение идентификатора ;процесса, записанное в параметре реестра ProcessId. Вторым параметром ;в эту функцию передается указатель на инициализированную структуру ;UNICODE_STRING, содержащую имя параметра реестра, значение которого мы ;хотим получить. Третий параметр функции ZwQueryValueKey определяет тип ;запрашиваемой информации. KeyValuePartialInformation - символьная константа ;равная 2. Четвертый и пятый параметры - указатель на структуру ;KEY_VALUE_PARTIAL_INFORMATION и ее размер соответственно. В члене Data этой ;структуры мы и получим значение идентификатора процесса. Последний параметр ;указатель на переменную, размером DWORD, в которую будет записано количество ;скопированных из реестра байт. Перед самым вызовом ZwQueryValueKey, мы ;резервируем на стеке для него место, а после вызова извлекаем значение push eax push esp push sizeof(KEY_VALUE_PARTIAL_INFORMATION) lea eax,kvpi push eax push KeyValuePartialInformation push offset cusProcessid push hKey call _imp__ZwQueryValueKey@24 pop ecx cmp eax,STATUS_OBJECT_NAME_NOT_FOUND je a6 test ecx,ecx je a6;.if ( eax != STATUS_OBJECT_NAME_NOT_FOUND ) && ( ecx != 0 ) ;Если вызов ZwQueryValueKey прошел успешно, выделяем с помощью функции ;MmAllocateNonCachedMemory кусок памяти размером 2000h байт - максимальный ;размер карты разрешения ввода-вывода. Сохраняем указатель в переменной pIopm push IOPM_SIZE call _imp__MmAllocateNonCachedMemory@4 xchg eax,ecx jecxz a5;if eax != NULL mov pIopm,ecx ;Передавая в функцию PsLookupProcessByProcessId полученный ранее идентификатор ;процесса, получаем указатель на KPROCESS в переменной pProcess lea ecx,kvpi lea eax,pProcess push eax push dword ptr (KEY_VALUE_PARTIAL_INFORMATION PTR [ecx]).Data call _imp__PsLookupProcessByProcessId@8 test eax,eax jnz short a3;.if eax == STATUS_SUCCESS ;Копируем текущую IOPM размером 2000h из TSS в буфер, указатель на который ;содержится в параметре pIopm push pIopm push ebx;0 call _imp__Ke386QueryIoAccessMap@8 test al,al jz short a1;.if al != 0 ;Открываем доступ к портам 42h, 43h, 61h. Сбрасываем биты соответствующие ;этим портам ввода-вывода и записываем модифицированную IOPM. mov ecx,pIopm and byte ptr [ecx + 42h/8],not(1 shl (42h MOD 8) or (1 shl (43h MOD 8))); I/O access for 42h & 43h port and byte ptr [ecx + 61h/8],not(1 shl (61h MOD 8)); I/O access for 61h port ;Копируем переданную IOPM длинной 2000h из буфера, указатель на который ;содержится в параметре pIopm, в TSS push ecx;pIopm push 1 call _imp__Ke386SetIoAccessMap@8; Set modified IOPM test al,al; If second parameter to Ke386IoSetAccessProcess is 1, jz short a1;the process is given I/O access. If it is 0, access is removed. ;Разрешаем/запрещаем использование IOPM для процесса. Если второй параметр ;равен 1 разрешаем доступ к портам ввода-вывода, устанавливаем смещение ;IOPM в пределах TSS равным 88h. push 1 push pProcess call _imp__Ke386IoSetAccessProcess@8 test al,al jnz short a2 a1: mov status,STATUS_IO_PRIVILEGE_FAILED ;Предыдущий вызов функции PsLookupProcessByProcessId, увеличил количество ;ссылок на обьект "процесс". Система раздельно хранит количество открытых ;описателей обьекта и количество предоставленных ссылок на объект. ;Описателями, в основном, пользуется код режима пользователя, ссылками - ;только код режима ядра. Пока, хотя бы одно из этих значений, не равно нулю, ;система не удаляет объект из памяти, считая что он еще используется ;каким-то кодом. Вызовом функции ObDereferenceObject мы уменьшаем количество ;ссылок на обьект процесса. a2: push pProcess call _imp__ObDereferenceObject@4 jmp short a4 a3: mov status,STATUS_OBJECT_TYPE_MISMATCH ;С помощью функции MmFreeNonCachedMemory освобождаем выделенный буфер a4: push IOPM_SIZE push pIopm call _imp__MmFreeNonCachedMemory@8 jmp short a6 a5: mov status, STATUS_INSUFFICIENT_RESOURCES ;вызовом функции ZwClose закрываем описатель раздела реестра a6: push hKey call _imp__ZwClose@4 ;Т.к. драйвер возвращает один из кодов ошибки, система удаляет его из памяти. ;а user-mode программа получила доступ к 42h, 43h, 61h портам ввода-выводаv a7: mov eax, status leave retn 8 DriverEntry endp end DriverEntry