19.04.2002
(c) 2002 Pavel Pindora .
Pro více informací je zde formulář
Důležité, přečtěte si červeně vybarvené texty než začnete s programováním
Veškeré pokusy, s programováním v Kernel oblasti, mohou mít vlivem zanesených chyb katastrofální následky, projevující se nestabilitou systému, porušením integrity dat, nemožností najet Windows a následnou nutností jeho přeinstalování se všemi důsledky, které mohou vzniknout.
Je výhodné mít zvláštní k těmto účelům vyčleněný počítač .
Uvedené postupy jsou určeny ryze a více méně pro teoretické nastudování problematiky programovacích postupů a autor tohoto článku nenese žádnou odpovědnost za případné škody.
Kompilace ,tvorba INF souboru, zavedení do systému
Aplikační program, popis činnosti
Výpis zdrojáku pro aplikační exe program
Způsob tvorby "kernel" ovladače pro Win NT4 ,původní z 23.1.2001
New 6.3.2004 Driver pro simulaci tiskárny
New 13.3.2004 Vteřinový časovač pro timeout
Co je kernel vrstva je vysvětleno zde a hlavním důvodem proč psát programy pro tuto vrstvu, je možnost používat privilegované instrukce jako např. out dx,al což představuje zápis registru al na port určený hodnotou v registru dx. Pokud tedy tuto instrukci použijeme v MS Visual C++, tak překlad proběhne bez problému ale spuštěním nám systém ukončí proces hláškou o nemožnosti používat tyto instrukce.Jak dokumentuje následující obrázek.
Pro psaní do kernelu musíme mít Microsoft DDK , který je možno stáhnout z internetu na http://www.microsoft.com/ddk/installW2k.htm? Pro vyzkoušení funkce ovládače budeme potřebovat i program, který bude pracovat v normální aplikační vrstvě a bude se na tento ovládač odkazovat, využíváním jeho dat .
Co vše budeme při tvorbě kernel ovládače potřebovat ? Nejlépe si připravíme složku se vším potřebným a bude se jmenovat např. Pokus_DDK
V ní je
1. d_kernel.chm help soubor s vysvětlením všeho potřebného
zástupce WINDDK\2195\help\d_kernel.chm
2.Generate Inf Wizard pro generování instalačního souboru, který ovládač zaregistruje do systému
3. kompilace.bat textový soubor bat , kompilující zdroják a vytvářející sys soubor
jeho výpis:
cls
path=G:\WINDDK\2195\bin;g:\Microsoft Visual Studio\VC98\bin
build -ceZ
pause
nmake
pause
4. Pokus_read.exe Zástupce aplikačního programu, není potřeba,
budeme ho spouštět přímo z Visual C++
5. Zástupce - kompilace.bat bat soubor provádějící kompilaci
Funkce ovládače bude jednoduchá , inicializuje se timer a v jeho obslužné rutině se inkrementuje proměnná Ldata , její hodnota se pak přenese do aplikačního programu, !!! navíc se tato hodnota přenese na LPT port s adresou 378 hexa a je nutné se ujistit , že to má počítač ( kde se budou dělat pokusy s driverem ) stejné, jinak je potřeba změnit nebo úplně odstavit _asm blok ve funkci TimerRoutine souboru Pokus_sys.c !!!,
Název kernel binárního souboru je Pokus_sys.sys a aplikačního Pokus_read.exe.
Základem ovládače jsou vnořené funkce :
//------------------------ Volána při startu Windows 2000 ------------------------
NTSTATUS DriverEntry
(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
//------------------------ Příkazem hTest = CreateFile v exe aplikaci, je zavolána tato funkce v sys
NTSTATUS LdUnldOpen
¨(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
// -----------------------Příkaz dwStatus = ReadFileEx v exe volá ------------------------
NTSTATUS LdUnldRead
(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
Zde je mimo jiné vlastní předávání dat
// Tady vkladam data pro predani do aplikacni casti
*(pIoBuff+0) = 0x77;
*(pIoBuff+1) = Ldata;
a v aplikačním exe jsou vypsána do konzoly příkazy printf
data = buffer[0];
if( data==0x77 && (data1!=(long)buffer[1]) )
{
data1 = (long)buffer[1];
printf("data naplnena v Kernel pokus_sys.sys Ln79 = 0x%02x\n",buffer[0] );
printf("data naplnena v Kernel pokus_sys.sys Ln80 = 0x%08x\n",(long)buffer[1] ); // %d\n" ,(long)buffer[1] )
printf("data naplnena v Kernel pokus_sys.sys Ln81 = 0x%02x\n",buffer[2] );
};
//---------------------- Vlastní asi vteřinový časovač volá ------------------------
VOID *TimerRoutine
(
IN PDEVICE_OBJECT DeviceObject,
IN PVOID Context
)
{
Ldata++;
__asm // __asm block
{
mov eax, Ldata
mov dx, 0378h
out dx, al
}
}
Tady se inkrementuje proměnná Ldata a tato je vyslána na LPT port s adresou 378hexa. Pozor přečtěte si výše popsané červené upozornění. .
Další funkce jsou LdUnldClose, LdUnldUnload...
Pokud Zástupcem - kompilace.bat program úspěšně přeložíme, vytvoří se Pokus_sys.sys viz následující obrázek,
tento pak zkopírujeme do adresáře Win2k\system32\drivers , tím to ale nekončí , ještě je potřeba sys zaregistrovat a zavést ho do složky Porty (COM a LPT) .
To
obstará INF soubor , který můžeme vygenerovat pomocí, již výše zmiňovaného,
Inf Wizardu , následující obrázky stručně přibližují popsanou činnost.
Vygenerovaný INF soubor, pak pomocí správce hardware, zavede sys do systému
a počítač musíme restartovat, driver je zaveden až po restartu. Pokud uděláme
chybu a místo Windows 2000 se zobrazí modrá obrazovka, je nutné W2k spustit
v nouzovém režimu, ovládač odinstalovat a znovu provést restart. Modrou
smrt bezpečně při startu provedou zakomentované příkazy, počínaje funkcí KeInitializeDpc
a konče KeSetTimerEx
v
DriverEntry
Vložíme informace o tvůrci ovládače
Ovládač nebude digitálně podepsán
Ovládač není v seznamu nabízených
Driver bude zaveden do složky Portů proto je nutné zvolit Ports
Zvolíme platformu pro Intel Pentium
Přidělíme název a způsob zavedení do systému
Driver nebude obsluhovat žádnou kartu jedná se jen o jakýsi virtuální hardware
Navolíme cestu k přeloženému sys souboru
Nakonec vše dokončíme generováním INF souboru
Z obrázku je zřejmé, že se jedná o jednoduchou konzolovou aplikaci , která zobrazí data poskytována drivrem je to mnou libovolně zvolené hexadecimální číslo 0x77, pak stav proměnné Ldata ( 0xffffffef ) .
Jednoduše
si popíšeme jak se driver přilinkuje k aplikaci, je to stejné jako při otevírání
souboru pomocí příkazu CreateFile přičemž jako název souboru se vloží (
pro DOS konzolu ) název LOADTEST
ten
je určen ve zdrojáku sys souboru Pokus_sys.sys následujícími řádky.
#define
NT_DEVICE_NAME L"\\Device\\Ldunld"
#define DOS_DEVICE_NAME L"\\DosDevices\\LOADTEST"
hTest = CreateFile (
"\\\\.\\LOADTEST",// Jedna se DOS konzolu viz Pokus_sys.c Ln6
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
Pokud vše proběhne v pořádku tak můžeme načíst data (
0x77,Ldata ) pomocí příkazuReadFileEx
, kde do ovládače předáme buffer, kde se uloží data a jeho velikost.
dwStatus = ReadFileEx(
hTest,
buffer,
255,
DriverlpOverlapped,
ReadCompleteApc
);
A nakonec je třeba zdůraznit ošetření veškerých hazardů, aby se aplikace nezhroutila,
před jakýmikoliv pokusy si znovu přečtěte červeně vybarvené texty.
//********************************************************************************************
#include <ntddk.h>
//#include "gpioctl.h" // Get IOCTL interface definitions
#define NT_DEVICE_NAME L"\\Device\\Ldunld"
#define DOS_DEVICE_NAME L"\\DosDevices\\LOADTEST"
#define FILE_DEVICE_SIMPLDRV 0x00008300
//------------------------------------------------------
typedef struct _IO_BUFFER
{
ULONG QueueTime;
ULONG DequeueTime;
ULONG Data;
}
IO_BUFFER, *PIO_BUFFER;
typedef struct _RING_BUFF
{
ULONG Size;
PULONG pBase;
PULONG pHead;
PULONG pTail;
}
RING_BUFF, *PRING_BUFF;
typedef struct _DEVICE_EXTENSION
{
PDEVICE_OBJECT DeviceObject;
RING_BUFF RingBuff;
ULONG OpenInstances; // sanity check
}
DEVICE_EXTENSION, *PDEVICE_EXTENSION;
VOID *Context;
long Ldata;
PRKDPC Dpc;
PKTIMER Timer;
LARGE_INTEGER DueTime;//LARGE_INTEGER
//------------------------------------------------------
NTSTATUS LdUnldOpen
(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
);
NTSTATUS LdUnldClose
(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
);
VOID LdUnldUnload
(
IN PDRIVER_OBJECT DriverObject
);
NTSTATUS LdUnldRead
(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
PDEVICE_EXTENSION deviceExtension;
PIO_STACK_LOCATION irpStack;
char *pIoBuff;
ULONG inputBufferLength;
ULONG outputBufferLength;
irpStack = IoGetCurrentIrpStackLocation (Irp);
deviceExtension = DeviceObject->DeviceExtension;
KdPrint( ("LDUNLD: Opened!!\n") );
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 555;
IoCompleteRequest( Irp, IO_NO_INCREMENT );
pIoBuff = (char*)Irp->AssociatedIrp.SystemBuffer;
inputBufferLength = irpStack->Parameters.DeviceIoControl.InputBufferLength;
outputBufferLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
// Tady vkladam data pro predani do aplikacni casti
*(pIoBuff+0) = 0x77;
*(pIoBuff+1) = Ldata;
//*(pIoBuff+2) = 0x99;
return STATUS_SUCCESS;
};
NTSTATUS LdUnldReadDevice
(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
return STATUS_SUCCESS;
};
//*************************************************************************
VOID *TimerRoutine (
IN PDEVICE_OBJECT DeviceObject,
IN PVOID Context
)
{
Ldata++;
__asm // __asm block
{
mov eax, Ldata
mov dx, 0378h
out dx, al
}
}
//*************************************************************************
VOID *DpcTimerRoutine (
IN PKDPC Dpc,
IN PVOID DeferredContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
)
{
Ldata++;
}
//*************************************************************************/
NTSTATUS DriverEntry
(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
PDEVICE_OBJECT deviceObject = NULL;
PDEVICE_EXTENSION pDevExt;
NTSTATUS status;
UNICODE_STRING uniNtNameString;
UNICODE_STRING uniWin32NameString;
KdPrint( ("LDUNLD: Entered the Load/Unload driver!\n") );
RtlInitUnicodeString( &uniNtNameString, NT_DEVICE_NAME );
status = IoCreateDevice(
DriverObject,
sizeof (DEVICE_EXTENSION),// We don't use a device extension
&uniNtNameString,
FILE_DEVICE_SIMPLDRV,
0, // No standard device characteristics
TRUE, // This isn't an exclusive device
&deviceObject
);
if ( NT_SUCCESS(status) )
{
//---------------22.6.99----------------------------------
deviceObject->Flags |= DO_BUFFERED_IO;
pDevExt = deviceObject->DeviceExtension;
pDevExt->DeviceObject = deviceObject;
pDevExt->OpenInstances = 0;
pDevExt->RingBuff.pHead =
pDevExt->RingBuff.pTail =
pDevExt->RingBuff.pBase = ExAllocatePoolWithTag
(
NonPagedPool,
255, // SizeInBytes
'QPRI'
);
DriverObject->MajorFunction[IRP_MJ_CREATE] = LdUnldOpen;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = LdUnldClose;
DriverObject->MajorFunction[IRP_MJ_READ] = LdUnldRead;
DriverObject->DriverUnload = LdUnldUnload;
KdPrint( ("LDUNLD: just about ready!\n") );
RtlInitUnicodeString( &uniWin32NameString, DOS_DEVICE_NAME );
status = IoCreateSymbolicLink( &uniWin32NameString, &uniNtNameString );
if (!NT_SUCCESS(status))
{
KdPrint( ("LDUNLD: Couldn't create the symbolic link\n") );
IoDeleteDevice( DriverObject->DeviceObject );
}
else
{
KdPrint( ("LDUNLD: All initialized!\n") );
status = IoInitializeTimer ( deviceObject,
TimerRoutine,
Context
);
IoStartTimer( deviceObject ); //*/
/*
KeInitializeDpc (
Dpc,
TimerRoutine,
Context
);
KeInitializeTimerEx (
Timer,
SynchronizationTimer
);
KeSetTimerEx(
Timer,
DueTime,
10,
Dpc
);//*/
}
}
else
{
KdPrint( ("LDUNLD: Couldn't create the device\n") );
}
return status;
}
NTSTATUS LdUnldOpen
(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
KdPrint( ("LDUNLD: Opened!!\n") );
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest( Irp, IO_NO_INCREMENT );
return STATUS_SUCCESS;
}
NTSTATUS LdUnldClose
(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
KdPrint( ("LDUNLD: Closed!!\n") );
IoCompleteRequest( Irp, IO_NO_INCREMENT );
return STATUS_SUCCESS;
}
VOID LdUnldUnload
(
IN PDRIVER_OBJECT DriverObject
)
{
UNICODE_STRING uniWin32NameString;
KdPrint( ("LDUNLD: Unloading!!\n") );
RtlInitUnicodeString( &uniWin32NameString, DOS_DEVICE_NAME );
IoDeleteSymbolicLink( &uniWin32NameString );
IoDeleteDevice( DriverObject->DeviceObject );
}
/********************************************************************************************
#include "windows.h"
#include "stdio.h"
/********************Testovaci soft**************************/
#define MAXBUFF 1000
DWORD IoBuff[MAXBUFF];
DWORD dwStatus;
DWORD dwRetries = 0;
LPOVERLAPPED *DriverlpOverlapped;
typedef enum _OPERATION_TYPE
{
eRead,
eWrite,
eIoControl,
eMaxOpTypes
}
OP_TYPE, *POP_TYPE;
DWORD data,data1;
typedef struct _IO_BUFFER
{
ULONG QueueTime;
ULONG DequeueTime;
ULONG Data;
}
IO_BUFFER, *PIO_BUFFER;
//----------------------------------------------
VOID WINAPI ReadCompleteApc
(
ULONG ulError,
ULONG ulTransfered,
LPOVERLAPPED lpOvl
)
{
//--------Jen Testovani--------
//-----------------------------
return;
};
//----------------------------------------------
DWORD SLEEPEX
(
DWORD dT,
int n
)
{
DWORD dwStatus;
switch ( dwStatus = SleepEx( dT, TRUE) )
{
case STATUS_WAIT_0:
break;
case WAIT_IO_COMPLETION:
#ifdef DBG_SLEEP
printf("SleepEx returned: WAIT_IO_COMPLETION\n" );
#endif
break;
default:
printf("Unhandled SleepEx: %d\n", dwStatus);
break;
}
return dwStatus;
};
/**************************************************************/
int __cdecl main(int argc, char **argv)
{
char *buffer;
HANDLE hTest = 0;
COORD curPos;
HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE),
hStdIn = GetStdHandle(STD_INPUT_HANDLE);
ULONG drv1,drv2;
curPos.X = 0;
curPos.Y = 1;
/*__asm // __asm block
{
mov eax, 0ffh
mov dx, 0378h
out dx, al
}*/
hTest = CreateFile (
"\\\\.\\LOADTEST",// Jedna se DOS konzolu viz Pokus_sys.c Ln6
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if ( hTest != ((HANDLE)-1))
{
printf("Fajn - Nasel jsem tvuj prvni Kernel driver!!!\n");
buffer = (char*) malloc( 1255 );
if( buffer==NULL )
{
printf("Konec, chyba alokace buff \n");
getch();
return 0;
};
DriverlpOverlapped = (LPOVERLAPPED) malloc( sizeof(LPOVERLAPPED));
while( !_kbhit() )
{
SetConsoleCursorPosition( hStdOut,curPos );
printf("Ukonci stiskem klavesy\n");
if( DriverlpOverlapped==0 )
{
printf("Konec, chyba alokace lpO \n");
break;
};
dwStatus = ReadFileEx (
hTest,
buffer,
255,
DriverlpOverlapped,
ReadCompleteApc
);
if ( !dwStatus )// Nastala chyba ve cteni
{
//( hStdOut );
CloseHandle(hTest);
FillConsoleOutputCharacter(
hStdOut,
' ',
1000,
curPos,
0
);
dwStatus = GetLastError();
printf("ReadFileEx error: %d\n", dwStatus );
if( ERROR_INVALID_FUNCTION == dwStatus )
{
printf("ReadFileEx error=Incorrect function\n");
};
if( ERROR_IO_DEVICE == dwStatus )
{
if ( ++dwRetries < 12 )
{
printf("ReadFileEx => _Retry : %d\n", dwRetries );
SLEEPEX( 1000, eRead );
}
else
{
printf("ReadFileEx => MAX_READ_RETRIES %d\n", dwRetries );
return dwStatus;
};
};
printf("Stopnu cteni z drivru Pokus_sys\n");
if( dwStatus==12 )
printf("ReadFileEx => ERROR_INVALID_ACCESS %d\n", dwStatus );
break;
};
data = buffer[0];
if( data==0x77 && (data1!=(long)buffer[1]) )
{
data1 = (long)buffer[1];
printf("data naplnena v Kernel pokus_sys.sys Ln79 = 0x%02x\n",buffer[0] );
printf("data naplnena v Kernel pokus_sys.sys Ln80 = 0x%08x\n",(long)buffer[1] ); // %d\n" ,(long)buffer[1] )
printf("data naplnena v Kernel pokus_sys.sys Ln81 = 0x%02x\n",buffer[2] );
};
//drv1 = getch();
//if( drv1==0x20 )
/// break;
};
CloseHandle(hTest);
}
else
{
printf("Can't get a handle to LOADTEST\n");
};
getch();
return 1;
}
Způsob tvorby "kernel" ovladače pro Win NT4:
Výpis programu VisualSys.sys, zaváděného do kernelové oblasti Windows NT
Výpis aplikačního programu testující VisualSys.sys pro Windows NT
Nejdříve zkompilujeme VisualSys.c,uvedený
Výpisem programu pro kernel
,jako sys knihovnu a umístíme ji do adresáře /WINNT/SYSTEM32,
pro kompilaci musíme vlastnit DDK pro Windows NT od Microsoft Corporation,
potom ji pomocí regini s parametrem VisualSys.ini zaregistrujeme do registrů a počítač restartujeme.
Výpis VisualSys.ini:
"\\\\.\\LOADTEST",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
printf("Wow - it really worked!!!\n");
lpOverlapped = (LPOVERLAPPED) malloc( sizeof(LPOVERLAPPED));
buffer = (char*) malloc( 255 );
dwStatus = ReadFileEx(
hTest,
buffer,
255,
lpOverlapped,
ReadCompleteApc
);
if ( !dwStatus )
{
dwStatus = GetLastError();
printf("ReadFileEx error: %d\n", dwStatus );
if( ERROR_INVALID_FUNCTION == dwStatus )
printf("ReadFileEx error=Incorrect function");
if( ERROR_IO_DEVICE == dwStatus )
{
if ( ++dwRetries < 12 ) {
printf("ReadFileEx => _Retry : %d\n", dwRetries );
SLEEPEX( 1000, eRead );
}
else
{
printf("ReadFileEx => MAX_READ_RETRIES %d\n", dwRetries );
return dwStatus;
};
};
};
if( data==555 )
{
printf("??? data naplnena Irp->IoStatus.Information = 555 v ldunld.sys");
}
CloseHandle(hTest);
}
printf("Can't get a handle to LOADTEST\n");
Potřeboval jsem simulovat tiskárnu pomocí PCčka, použil jsem k tomu notebook s Windows XP Prof. Vlastní sys driver je zkompilován pomocí DDK pro Windows NT4 .
Aplikační
část má klasickou konstrukci pro přístup k portům pomocí CreateFile.
Je potřeba řící ,že do bodu DriverEntry ,se vstupuje
porestartu systému a zavádění ovládačů , nikoli při volání CreateFile .
|
Pomocí WriteFile je zapsáno řídící slovo do Control Byte LPT1, v našem případě je jeho adresa 0x378+2 a zapisovaná
hodnota je 0x32 , což znamená :
povolení interruptu od ACK, (musí být povoleno ve Windows, properties portu LPT1 v Device Manager)
povolení obousměnrného portu (musí být navoleno v Biosu Bidirectional)
inicializace BUSY a ACK
Volá se InterruptWrite v driveru.
|
Pro načtení dat je použit ReadFileEx a musí být v samostatném vlákně , protože (pro jednoduchost)
dokud není nic načteno driver čeká ve funkci InterruptRead , kde je nekonečná smyčka
for(;;)
{
if(indxBuffer>0x10)
break;
}
přerušená dokud není bufer naplněn
zvoleným počtem znaků z LPT1 v našem případě 0x10.
|
Velikost bufru, který má driver k dipozici je v ExAllocatePoolWithTag(
NonPagedPool,
MAXBYTE, // SizeInBytes
'QPRI'
);
umístěného v DriverEntry a dán MAXBYTE.
Pro úplnost ještě kompletní výpis driveru ve funkční ale zjednodušené verzi.
#include
<ntddk.h> |
Protože je potřeba opustit driver,
když uplyne nějaký čas, je vhodné zavést do ovládače časovač. Použil
jsem k tomu účelu vteřinový timer . Jeho inicializace je v následujícím
listingu a je umístěna ve funkci DriverEntry, TimerRoutine
je bod který obsluhuje vteřinové
přerušení od časovače.
|
TimerRoutine
je bod který obsluhuje vteřinové
přerušení od časovače.
|
Pokud potřebujeme jiné časové tiky použijeme KeSetTimer ale to příště.