/**************************************************
Драйвер предоставления прямого доступа для процесса
режима пользователя. Драйвер основан на коде драйвера
GIVEIO.SYS от Dale Roberts, но в него добавлена
возможность предоставления доступа другим процессам 
с помощью команды 0x900

Компиляция: Используйте средство DDK BUILD
**************************************************/

#include <ntddk.h>
#include "giveioex.h"

/* Имя нашего драйвера устройства */
#define DEVICE_NAME_STRING L"giveioex"

/* 
Это "структура" IOPM. Это просто массив байт размером 0x2000. 
Он содержит 8К * 8 бит -> 64К бит IOPM, которые покрывают всё 
64 килобайтное адресное пространство в/в x86 процессора. Каждый 
нулевой бит предоставляет доступ к соответствующему порту для 
user-mode процесса. Каждый единичный бит запрещает доступ к 
в/в через соответствующий порт.
*/

#define IOPM_SIZE 0x2000

typedef UCHAR IOPM[IOPM_SIZE];

/* 
Это будет содержать просто массив нулей, который будет копироваться 
в настоящую IOPM в TSS через Ke386SetIoAccessMap(). Память выделяется 
во время загрузки драйвера.
*/

IOPM * IOPM_local = 0;

/* 
Это две недокументированных функции которые мы используем чтобы дать 
доступ к в/в вызывающему процессу. Ke386IoSetAccessMap() копирует 
переданную карту в/в в TSS. Ke386IoSetAccessProcess() изменяет указатель 
смещения IOPM после чего только что скопированная карта в/в начинает 
использоваться. Иначе смещение IOPM указывает за предел сегмента TSS, 
что приведёт генерации исключения при попытке доступа к в/в любым 
user-mode процессом.
*/

void Ke386SetIoAccessMap(int, IOPM *);
void Ke386QueryIoAccessMap(int, IOPM *);
void Ke386IoSetAccessProcess(PEPROCESS, int);
NTSTATUS PsLookupProcessByProcessId(IN ULONG ulProcId,OUT struct _EPROCESS ** pEProcess);


/*
Сервисная функция драйвера.
*/
NTSTATUS GiveioDeviceControl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);


/***** Освободить все выделенные ранее объекты ******/
VOID GiveioUnload(IN PDRIVER_OBJECT DriverObject)
{
 WCHAR DOSNameBuffer[] = L"\\DosDevices\\" DEVICE_NAME_STRING;
 UNICODE_STRING uniDOSString;
 if(IOPM_local)
  MmFreeNonCachedMemory(IOPM_local, sizeof(IOPM));
 RtlInitUnicodeString(&uniDOSString, DOSNameBuffer);
 IoDeleteSymbolicLink (&uniDOSString);
 IoDeleteDevice(DriverObject->DeviceObject);
}

/*
Устанавливаем IOPM (карту разрешения в/в) процесса ProcessID так, 
что ему предоставляется полный доступ к в/в. Наш массив OPM_local[] 
содержит только нули, соответственно IOPM обнулится. Если OnFlag == 1, 
процессу предоставляется доступ к в/в. Если он равен 0, доступ 
запрещается.
*/

VOID SetIOPermissionMap(PEPROCESS ProcessID, int OnFlag)
{
 Ke386IoSetAccessProcess(ProcessID, OnFlag);
 Ke386SetIoAccessMap(1, IOPM_local);
}

/*
Точка входа в драйвер. Выполняется при загрузке.
Установка разрешения доступа ко всем портам 
для вызывающего процесса.
*/
void GiveIO(void)
{
 SetIOPermissionMap(PsGetCurrentProcess(), 1);
}

/*
Служебный обработчик для user-mode вызова CreateProcess(). Эта функция 
введена в таблицу вызовов функций объекта драйвера с помощью DriverEntry(). 
Когда user-mode приложение вызывает CreateFile(), эта функция получает 
управление всё ещё в контексте вызвавшего приложения, но с CPL 
(текущий уровень привилегий процессора) установленным в 0. Это позволяет 
производить операции возможные только в kernel mode. GiveIO() вызывается
для предоставления вызывающему процессу доступа к в/в. Все что приложение 
режима пользователя которому нужен доступ к в/в должно сделать - это 
открыть данное устройство используя CreateFile(). Никаких других действий 
не нужно.
*/

NTSTATUS GiveioCreateDispatch(
 IN PDEVICE_OBJECT DeviceObject,
 IN PIRP Irp
)
{
 GiveIO(); // give the calling process I/O access
 Irp->IoStatus.Information = 0;
 Irp->IoStatus.Status = STATUS_SUCCESS;
 IoCompleteRequest(Irp, IO_NO_INCREMENT);
 return STATUS_SUCCESS;
}

/*
Процедура входа драйвера. Эта процедура вызывается только раз 
после загрузки драйвера в память. Она выделяет необходимые 
ресурсы для работы драйвера. В нашем случае она выделяет память 
для массива IOPM, и создаёт устройство которое может открыть 
приложение режима пользователя. Она также создаёт символическую 
ссылку на драйвер устройства. Это позволяет user-mode приложению 
получить доступ к нашему драйверу используя \\.\giveioex нотацию.
*/

NTSTATUS DriverEntry(
 IN PDRIVER_OBJECT DriverObject,
 IN PUNICODE_STRING RegistryPath
)
{
 PDEVICE_OBJECT deviceObject;
 NTSTATUS status;

 WCHAR NameBuffer[] = L"\\Device\\" DEVICE_NAME_STRING;
 WCHAR DOSNameBuffer[] = L"\\DosDevices\\" DEVICE_NAME_STRING;
 UNICODE_STRING uniNameString, uniDOSString;

 // Выделим буфер для локальной IOPM и обнулим его.
 IOPM_local = MmAllocateNonCachedMemory(sizeof(IOPM));
 if(IOPM_local == 0)
  return STATUS_INSUFFICIENT_RESOURCES;

 RtlZeroMemory(IOPM_local, sizeof(IOPM));

 // Инициализируем драйвер устройства и объект 
 // устройства (device object)

 RtlInitUnicodeString(&uniNameString, NameBuffer);
 RtlInitUnicodeString(&uniDOSString, DOSNameBuffer);

 status = IoCreateDevice(DriverObject, 0, &uniNameString,
		 FILE_DEVICE_UNKNOWN, 0, FALSE, &deviceObject);
 if(!NT_SUCCESS(status))
  return status;

 status = IoCreateSymbolicLink (&uniDOSString, &uniNameString);
 if (!NT_SUCCESS(status))
  return status;

 // Инициализируем точки входа драйвера в объекте 
 // драйвера. Всё что нам нужно это операции создания 
 // (Create) и выгрузки (Unload)
 DriverObject->MajorFunction[IRP_MJ_CREATE        ] = GiveioCreateDispatch;
 DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = GiveioDeviceControl;
 DriverObject->DriverUnload = GiveioUnload;

 return STATUS_SUCCESS;
}

NTSTATUS
GiveioDeviceControl(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP pIrp
    )

{
 NTSTATUS            ntStatus = STATUS_SUCCESS;   
 PIO_STACK_LOCATION  irpSp;
 ULONG               inBufLength;   /* Длина входного буфера  */
 ULONG               outBufLength;  /* Длина выходного буфера */
 PULONG              LongBuffer;    /* Указатель на входной буфер */


 irpSp       = IoGetCurrentIrpStackLocation( pIrp );
 inBufLength = irpSp->Parameters.DeviceIoControl.InputBufferLength;
 LongBuffer  = (PULONG) pIrp->AssociatedIrp.SystemBuffer;

 switch ( irpSp->Parameters.DeviceIoControl.IoControlCode )
 {
  case IOCTL_IOPM_GET_ALL_ACCESS:
  {
   if (inBufLength >= 4) {
    ULONG ProcessID = LongBuffer[0];
    struct _EPROCESS *Process;

    PsLookupProcessByProcessId(ProcessID, &Process);
    Ke386SetIoAccessMap(1, IOPM_local);
    Ke386IoSetAccessProcess(Process, 1);
    ntStatus = STATUS_SUCCESS;
   } else {
    ntStatus = STATUS_BUFFER_TOO_SMALL;
    pIrp->IoStatus.Information = 0; /* Output Buffer Size */
   }
   break;
  }
 }

 pIrp->IoStatus.Status = ntStatus;
 IoCompleteRequest( pIrp, IO_NO_INCREMENT );
 return ntStatus;
}
