Introduction
As the title says, three steps down the stairs, our project application will "travel" through the Windows system to finally reach the kernel, from ring 3 to ring 0. It will become more and more privileged, more powerful, and more interesting. I will use the most simple code examples here to make it more clear. How will it look like? Take a look at this scheme:
Pure Win32 --> Mixed Win32 & NT --> Pure NT --> Kernel mode driver
In fact, our application is kind of a container for three applications. What are these three apps? The first one is a Win32 user mode program; the next is a pure Windows native app, and the last is a kernel mode driver. Now, the Win32 user-mode program will contain in its code the code for the pure native app, which will be deployed after its execution, and which contains in its code, the code for the kernel mode driver, which will be deployed after the native app execution. Kind of a chain reaction.
What do you need to build everything. First of all, you need a Windows driver development kit, which you can get from the Microsoft website. The next and the last thing, a MinGW package (GCC and stuff). Sure, you can use MS Visual Studio, but I prefer the GCC compiler. Actually, maybe, you will be able to compile the driver with GCC too because it is a very simple driver, but I haven't tried, so I don't know about that.
What is the main goal of this paper? The purpose of this paper is to show, on the most basic example:
- How a Win32 application could be written with the usage of the Native Windows API
- How a pure native Windows application looks like
- A method for including different, independent applications in one application
- Thread injection with the usage of a Windows Native API example
OK, let's start with the Win32 application.
Using the Code
The first thing I should mention about is a way of packing an executable file into another. To better understand this, let's use a hex editor to see how our executable looks like from the inside.
Actually, we need to dump the code into one single char
container, which will look like this:
unsigned char my_app [] ={
0x4d,0x5a,0x90,0x00,0x03,0x00,0x00,0x00
.............
}
For this, I am using a binary file to source text convertor (v1.01 (c) 1992,93 by Alex Kopylov) which I have included into the package with the source from this paper. But, if you are afraid of unknown applications, the code for dumping the EXE will look like this:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]){
FILE *f;
int i, c;
char *arr_name;
if (argc < 2) {
fprintf(stderr, "Usage: %s input_file [array_name] [> output_file]\n", argv[0]);
return 1;
}
f = fopen(argv[1], "rb");
if (f == NULL) {
fprintf(stderr, "%s: fopen(%s) failed", argv[0], argv[1]);
return 1;
}
if (argc >= 3) arr_name=argv[2]; else arr_name="filedata";
printf("unsigned char %s[] = {", arr_name);
for (i=0;;i++) {
if ((c = fgetc(f)) == EOF) break;
if (i != 0) printf(",");
if ((i % 12) == 0) printf("\n\t");
printf("0x%.2X", (unsigned char)c);
}
printf("\n};\n");
fclose(f);
return 0;
}
You can easily compile it. Now, you know how to pack an executable into another. There is one more thing you need to know: the size of the packed EXE. Simply check your executable properties like this:
Size, that is what you need. Not the size on the disk. As you can see, my EXE has 23490 bytes. Let's keep that in mind ;) OK, we got all that we need for now. The first part of our project is the Win32 application which is responsible for unpacking the native app.
#include "remove.h"
#include "NATIVE_CONTAINER.h"
#define ADDTOREG_REG "ADD2REG.REG" // this file will add native app to registry
#define RUN_BAT "run.bat" // this file will execute above file and
#define NATIVE_NAME "native.exe" // name of native app
#define NATIVE_SIZE 14617 // size of native app
The installation of the native Windows application takes two steps: the executable file should be copied to the system32 folder, and the appropriate Registry value should be added to make our application start before Win32. To achieve this I will use two installation files, written by Mark Russinovich in his example of a Windows native application. The following function will create these two files:
void Create_File(char *filename, char *data){
FILE *file;
file=fopen(filename,"w");
if(!file) return;
fprintf(file,"%s",data);
fclose(file);
}
Now, a couple of simple steps to create and install everything that is needed:
hNat = CreateFile(NATIVE_NAME, GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL, NULL);
WriteFile(hNat, native_container, NATIVE_SIZE, &dwWritten, NULL);
Create_File(ADDTOREG_REG,
"REGEDIT4\n[HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\"
"Control\\Session Manager]\n\\"BootExecute\"=hex(7):61,75,74,6f,"
"63,68,65,63,6b,20,61,75,74,6f,63,68,6b,20,2a,\\\n\00,"
"6e,61,74,69,76,65,00,00\n"
);
Create_File(RUN_BAT,
"@echo off\n\
copy native.exe %systemroot%\\system32\\.\n\
regedit /s ADD2REG.REG\n"
);
WinExec(RUN_BAT, SW_SHOW);
Sleep(6000);
DeleteFile(ADDTOREG_REG);
DeleteFile(RUN_BAT);
DeleteFile(NATIVE_NAME);
Sure, I can install my native app using C functions, but I'm just too lazy to write it for this paper :P After all, I want my Win32 app be deleted forever from the machine. I will use for it the thread injection method with the usage of native functions, exported from ntdll.dll. Actually, the code below was originally written probably by Killaloop, I have simply modified it to use the native API. So, we are going to a little bit lower level ;) To use native stuff in our Win32, app we first need to write function prototypes as pointers. Just take a look at the Win32 VirtualAllocEx
function prototype (from MSDN):
LPVOID WINAPI VirtualAllocEx(
__in HANDLE hProcess,
__in_opt LPVOID lpAddress,
__in SIZE_T dwSize,
__in DWORD flAllocationType,
__in DWORD flProtect
);
WINAPI
is a Win32 API function. Now, take a look at a similar native function:
NTSYSAPI
NTSTATUS
NTAPI
NtAllocateVirtualMemory(
IN HANDLE ProcessHandle,
IN OUT PVOID *BaseAddress,
IN ULONG ZeroBits,
IN OUT PULONG RegionSize,
IN ULONG AllocationType,
IN ULONG Protect );
NTAPI
is Native API. To use it in a Win32 executable, we will write it as a pointer:
NTSTATUS
(NTAPI *NtAllocateVirtualMemory)
(HANDLE ProcessHandle,
PVOID *BaseAddress,
ULONG ZeroBits,
PULONG RegionSize,
ULONG AllocationType,
ULONG Protect);
Now, we will dynamically link with ntdll.dll and get the function address from it:
BOOL InitializeRoot(){
HMODULE hNtDLL = GetModuleHandle("ntdll.dll");
*(FARPROC *)&NtAllocateVirtualMemory =
GetProcAddress(hNtDLL, "NtAllocateVirtualMemory");
return 0;
}
After the above function execution, we use the native function in our Win32 application. Below are the functions that should be initialized before the thread injection routine could be executed:
LONG
(__stdcall *RtlAdjustPrivilege)(int,BOOL,BOOL,BOOL *);
NTSTATUS
(NTAPI * RtlCreateUserThread)(HANDLE ProcessHandle,
PSECURITY_DESCRIPTOR SecurityDescriptor,
BOOLEAN CreateSuspended,ULONG StackZeroBits,
PULONG StackReserved,PULONG StackCommit,
PVOID StartAddress,PVOID StartParameter,
PHANDLE ThreadHandle,PCLIENT_ID ClientID );
NTSTATUS
(NTAPI *NtOpenProcess)(PHANDLE ProcessHandle, ACCESS_MASK AccessMask,
POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId );
NTSTATUS
(NTAPI *NtAllocateVirtualMemory)(HANDLE ProcessHandle,
PVOID *BaseAddress,ULONG ZeroBits,
PULONG RegionSize,ULONG AllocationType,ULONG Protect);
NTSTATUS
(NTAPI *NtWriteVirtualMemory)(IN HANDLE ProcessHandle,
IN PVOID BaseAddress, IN PVOID Buffer,
IN ULONG NumberOfBytesToWrite,
OUT PULONG NumberOfBytesWritten OPTIONAL);
NTSTATUS
(NTAPI *NtClose)(IN HANDLE ObjectHandle);
NTSTATUS (NTAPI *NtStrCpy)(char *, char *);
You have probably noticed the new stuff like PCLIENT_ID
, POBJECT_ATTRIBUTES
, NTSTATUS
, etc. These structures should be defined too, like:
#ifndef NTAPI
typedef WINAPI NTAPI;
#endif
typedef LONG NTSTATUS;
typedef LONG KPRIORITY;
typedef struct _CLIENT_ID{
HANDLE UniqueProcess;
HANDLE UniqueThread;
} CLIENT_ID, *PCLIENT_ID;
typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES;
typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES;
From where does all this come from? There are three main "strategy" objects which provides such information. One is the Windows Driver Development Kit documentation, the next is the http://undocumented.ntinternals.net/ website, and the last is NDK (probably stands for Native Development Kit, I really dunno, you can find it somewhere on the net) which contains the header files arranged for native applications development.
OK, let's head back to the subject. We are going to inject only one thread with one function, that will delete our Win32 executable file. The whole method in this case looks just like you were using the standard way, known for years, and the complete code can be found in the package, so I only present here a few things for those who are unfamiliar with the subject. First, the function declarations which I will use in the remote thread:
typedef DWORD (WINAPI *RAWGETPROCADDRESS)( HMODULE ,LPCSTR);
typedef HMODULE (WINAPI *RAWLOADLIBRARYA)(LPCTSTR );
typedef BOOL (WINAPI *RAWDELETEFILE)(LPCTSTR );
typedef VOID (WINAPI *RAWSLEEP)(DWORD );
In my remote thread, my code will load the needed library, will get the needed procedure address, and execute the loaded function. Next, we are structuring the remote data like:
typedef struct _RemoteStructure {
char kernel[20];
char delfile[20];
char sleep[20];
char somestring[100];
DWORD dwGetProcAddress;
DWORD dwLoadLibraryA;
DWORD dwFreeLibrary;
HMODULE hKernel32;
} RemoteStructure;
RemoteStructure my_RemoteStructure,*pmy_RemoteStructure;
Here, I should say that I had a lot of problems with making NtAllocateVirtualMemory
to work properly, so I went a little bit across:
LPVOID NTAPI NtVirtualAlloc(IN HANDLE hProcess,IN LPVOID lpAddress,
IN SIZE_T dwSize, IN DWORD flAllocationType, IN DWORD flProtect) {
NTSTATUS Status;
Status = NtAllocateVirtualMemory(hProcess,(PVOID *)&lpAddress,
0,&dwSize,flAllocationType,flProtect);
if (!NT_SUCCESS(Status))return NULL;
return lpAddress;
}
Now, to each parameter from our remote structure, we are assigning the needed functions and values like:
my_RemoteStructure.dwGetProcAddress = (DWORD)GetProcAddress(hKernel, "GetProcAddress");
and:
NtStrCpy(my_RemoteStructure.delfile, "DeleteFileA");
Now, we need to open the process for injection; we need the process' PID so we will get it in a standard way with the usage of such functions as CreateToolhelp32Snapshot
, Process32First
, Process32Next
.
#define INJECT_EXE "svchost.exe"
DWORD pID = GetPID(INJECT_EXE);
Now, we have to convert our so called pID
to CLIENT_ID
and do the injection; here goes the native stuff:
ClientId.UniqueProcess = (HANDLE)pID;
ClientId.UniqueThread = 0;
InitializeObjectAttributes(&ObjectAttributes, NULL, 0,
NULL, NULL);
RtlAdjustPrivilege(20, TRUE, AdjustCurrentProcess, &en);
Status = NtOpenProcess(&hProcess, PROCESS_ALL_ACCESS , &ObjectAttributes, &ClientId);
if( !NT_SUCCESS( Status ))
{ return FALSE;}
pRemoteThread = NtVirtualAlloc(hProcess, 0, dwThreadSize, MEM_COMMIT |
MEM_RESERVE,PAGE_EXECUTE_READWRITE);
NtWriteVirtualMemory(hProcess, pRemoteThread,
&RemoteThread, dwThreadSize,0);
RtlZeroMemory(&my_RemoteStructure,sizeof(RemoteStructure));
Here, we are actually assigning the needed functions and values:
pmy_RemoteStructure =(RemoteStructure *)NtVirtualAlloc (hProcess ,
0,sizeof(RemoteStructure),MEM_COMMIT,PAGE_READWRITE);
NtWriteVirtualMemory(hProcess,
pmy_RemoteStructure,&my_RemoteStructure,
sizeof(my_RemoteStructure),0);
RtlCreateUserThread(hProcess, NULL,FALSE,
0, NULL, NULL,(PVOID)pRemoteThread,
(PVOID)pmy_RemoteStructure, 0, 0);
NtClose(hProcess);
And now, this is how my remote thread looks like:
DWORD __stdcall RemoteThread(RemoteStructure *Parameter){
RAWGETPROCADDRESS NtGetProcAddress =
(RAWGETPROCADDRESS)Parameter->dwGetProcAddress;
RAWLOADLIBRARYA NtLoadLibraryA =
(RAWLOADLIBRARYA)Parameter->dwLoadLibraryA;
Parameter->hKernel32 = NtLoadLibraryA(Parameter->kernel);
RAWDELETEFILE NtDeleteFile =
(RAWDELETEFILE)NtGetProcAddress(Parameter->hKernel32,
Parameter->delfile );
RAWSLEEP NtSleep = (RAWSLEEP)NtGetProcAddress(
Parameter->hKernel32, Parameter->sleep );
while(NtDeleteFile(Parameter->Filename) == 0) {NtSleep(1000);}
return 0;
}
In this way, you can inject a thread in any Windows process, for example, in svchost.exe on Vista. If you will use such types of injection on Windows XP, make sure your remote thread will terminate itself after its execution, else it will cause high CPU usage by csrss.exe and the host process. Anyway, I have added this part with the thread injection just to show how native APIs usage looks like in Win32 applications. If you are interested in digging inside Windows, check out the React OS project - it is a very cool resource.
Our native application will not do much more than our Win32 app. It will simply deploy a kernel-mode driver, register it, and load before the control over your system will be given to your hands. I've heard that a lot of people got problems with compiling native applications, but it is really an easy task. First of all, do not use MS Visual Studio, use the MinGW package with GCC inside :D. Next, when you have already got your code completed, in the command prompt, type: C:\path\to\gcc\bin\gcc native.c -o native.exe -lntdll -nostdlib -Wl,--subsystem,native,-e,_NtProcessStartup and you'll get your app compiled nicely (of course, if your code contains no errors). Anyway, in the package, I have included a makefile (make.bat) so you will be just asked about your GCC path and the batch script will do the rest. Make sure you have in your MinGW include directory the DDK folder, because our application needs some header files from it. OK, so let us define the most important functions that we will need.
NTSTATUS
NTAPI
ZwLoadDriver(PUNICODE_STRING DriverServiceName);
VOID
NTAPI
RtlInitUnicodeString(PUNICODE_STRING DestinationString,PCWSTR SourceString);
LONG
__stdcall
RtlAdjustPrivilege(int,BOOL,BOOL,BOOL *);
NTSTATUS
NTAPI
ZwCreateFile(OUT PHANDLE FileHandle, IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes, OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PLARGE_INTEGER AllocationSize OPTIONAL, IN ULONG FileAttributes,
IN ULONG ShareAccess, IN ULONG CreateDisposition, IN ULONG
CreateOptions, IN PVOID EaBuffer OPTIONAL, IN ULONG EaLength );
NTSTATUS
NTAPI
NtWriteFile(IN HANDLE FileHandle, IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL, IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock, IN PVOID Buffer,
IN ULONG Length, IN PLARGE_INTEGER ByteOffset OPTIONAL, IN PULONG Key OPTIONAL );
NTSTATUS
NTAPI
NtCreateKey( OUT PHANDLE pKeyHandle,
IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES
ObjectAttributes, IN ULONG TitleIndex,
IN PUNICODE_STRING Class OPTIONAL, IN ULONG CreateOptions,
OUT PULONG Disposition OPTIONAL );
NTSTATUS
NTAPI
NtSetValueKey(IN HANDLE KeyHandle, IN PUNICODE_STRING ValueName,
IN ULONG TitleIndex OPTIONAL, IN ULONG Type,
IN PVOID Data, IN ULONG DataSize );
NTSTATUS
NTAPI
NtDelayExecution(IN BOOLEAN Alertable, IN PLARGE_INTEGER DelayInterval );
#define DRIVER "zzzz.sys"
#define DRIVER_SIZE 2560
#define DRIVER_KEY "zzzz"
Now, the driver binary data will be stored here in the same way like the binary data of this native app inside the Win32 app.
unsigned char DriverData[] = {
0x4d,0x5a,0x90,0x00,0x03,0x00,0x00,0x00
.............
}
Now, to make life a little bit easier, here is the function, similar to Sleep
, but the time values will be in seconds, not in milliseconds or any other smaller value.
BOOL NtDelayExecutionEx(DWORD dwSeconds){
LARGE_INTEGER Interval;
Interval.QuadPart = -(unsigned __int64)dwSeconds * 10000 * 1000;
NtDelayExecution (FALSE, &Interval);
}
Let's take a look at how our native application will register a driver. The first thing here which should be clear is that the native apps uses Unicode strings, so our key will look like this:
WCHAR DrvPath[] = L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Services\\";
WCHAR DrvName[] = L"zzzz";
And now, we need to convert it to a Unicode string like this:
HANDLE mainDrvHandle, drvKeyHandle, EnumKeyHandle;
UNICODE_STRING KeyName;
OBJECT_ATTRIBUTES ObjectAttributes, oBj, oa;
KeyName.Buffer = DrvPath;
KeyName.Length = wcslen( DrvPath ) *sizeof(WCHAR);
KeyName.MaximumLength = KeyName.Length + sizeof(WCHAR);
Now, we need to call the InitializeObjectAttributes
function. Actually, it is a macro. From MSDN: The InitializeObjectAttributes
macro initializes the opaque OBJECT_ATTRIBUTES
structure, which specifies the properties of an object handle to routines that open handles.
InitializeObjectAttributes( &oBj, &KeyName, OBJ_CASE_INSENSITIVE, NULL, NULL );
NtCreateKey( &mainDrvHandle, KEY_ALL_ACCESS, &oBj, 0, NULL,
REG_OPTION_NON_VOLATILE,&Disposition );
KeyName.Buffer = DrvName;
KeyName.Length = wcslen( DrvName ) *sizeof(WCHAR);
KeyName.MaximumLength = KeyName.Length + sizeof(WCHAR);
InitializeObjectAttributes( &oBj, &KeyName,OBJ_CASE_INSENSITIVE, mainDrvHandle, NULL );
NtCreateKey(&drvKeyHandle, KEY_ALL_ACCESS, &oBj, 0, NULL,
REG_OPTION_NON_VOLATILE,&Disposition );
Now, look at how the most important key value is created.
WCHAR DrvImagePath[]= L"ImagePath";
WCHAR ImagePathValue[]= L"\\??\\C:\\WINDOWS\\system32\\zzzz.sys";
ValueNameDrv.Buffer = DrvImagePath;
ValueNameDrv.Length = wcslen( DrvImagePath ) *sizeof(WCHAR);
ValueNameDrv.MaximumLength = ValueNameDrv.Length + sizeof(WCHAR);
NtSetValueKey(drvKeyHandle, &ValueNameDrv, 0, REG_EXPAND_SZ,
ImagePathValue, wcslen( ImagePathValue) * sizeof(WCHAR)+ sizeof(WCHAR) );
I will not show here all the code from the project, but other values (your application should at least create two values: ImagePath
- where it should be looking for a driver, and Type
- if you wish to add more values like DisplayName
, ErrorControl
, Start
etc.; check how other services have registered in your system) are created without "+ sizeof(WCHAR)
". Our value type is REG_EXPAND_SZ
, so it needs extra space for terminating zeros (most likely 2, like this: 00 00). If you create this value without "+ sizeof(WCHAR)
", the application will fail to load the driver; it will simply return ((NTSTATUS)0xC0000034L)
, which means STATUS_OBJECT_NAME_NOT_FOUND
.
OK, time to create the driver. As you can see, the initialization of the Unicode string is the same:
FileName.Buffer = L"\\??\\C:\\WINDOWS\\system32\\zzzz.sys";
FileName.Length = wcslen( FileName.Buffer ) *sizeof(WCHAR);
FileName.MaximumLength = FileName.Length + sizeof(WCHAR);
ObjectAttributes.Length =sizeof(ObjectAttributes);
ObjectAttributes.RootDirectory = NULL;
ObjectAttributes.ObjectName = &FileName;
ObjectAttributes.Attributes = OBJ_CASE_INSENSITIVE;
ObjectAttributes.SecurityDescriptor = NULL;
ObjectAttributes.SecurityQualityOfService = NULL;
if(ZwCreateFile(&outFileHandle, GENERIC_WRITE | SYNCHRONIZE,
&ObjectAttributes, &ioStatusBlock,
&fileSize, FILE_ATTRIBUTE_NORMAL, 0, FILE_SUPERSEDE,
FILE_SEQUENTIAL_ONLY|FILE_SYNCHRONOUS_IO_NONALERT,
NULL, 0 ) != STATUS_SUCCESS){
RtlZeroMemory(&dbgMessage, sizeof(dbgMessage));
RtlInitUnicodeString(&dbgMessage, L"ZwCreateFile error!\n");
NtDisplayString( &dbgMessage );
NtDelayExecutionEx(2);
NtTerminateProcess( NtCurrentProcess(), 0 );
}
And write the driver data to the driver file....
if( NtWriteFile(outFileHandle,NULL, NULL, NULL, &ioStatusBlock,DriverData,
DRIVER_SIZE, NULL, NULL) ! = STATUS_SUCCESS){
RtlZeroMemory(&dbgMessage, sizeof(dbgMessage));
RtlInitUnicodeString(&dbgMessage, L"NtWriteFile error!\n");
NtDisplayString( &dbgMessage );
NtDelayExecutionEx(2);
NtTerminateProcess( NtCurrentProcess(), 0 );
}
NtClose(outFileHandle);
I think you have already noticed that writing native application code is very similar to writing a driver. You can replace, for example, NtWriteFile
with ZwWriteFile
, and you get the same functions at this moment. And the last thing, load the driver:
if (ZwLoadDriver(&DriverPath) != STATUS_SUCCESS){
RtlInitUnicodeString(&dbgMessage, L"\nDriver load error!\n");
NtDisplayString( &dbgMessage );
NtDelayExecutionEx(2);
NtTerminateProcess( NtCurrentProcess(), 0 );
} else {
RtlInitUnicodeString(&dbgMessage, L"\nDriver loaded successfully!\n");
NtDisplayString( &dbgMessage );
}
NtTerminateProcess( NtCurrentProcess(), 0 );
At this point, we have two parts of our project explained. Let's take a look at the last part. My driver included in this project has only two silly functions: create a text file in C:\, and write to this file the text OWNED. Here goes the code:
typedef unsigned long DWORD;
NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath){
HANDLE fHandle; OBJECT_ATTRIBUTES ObjectAttributes; LARGE_INTEGER Interval; UNICODE_STRING FileName; IO_STATUS_BLOCK ioStatusBlock; DWORD dwSeconds = 10; char myString[] = {"OWNED!!!"}; Interval.QuadPart = -(unsigned __int64)dwSeconds * 10000 * 1000;
if(KeGetCurrentIrql() != PASSIVE_LEVEL) return STATUS_INVALID_DEVICE_STATE;
RtlInitUnicodeString(&FileName, L"\\DosDevices\\C:\\0WN3ED.TXT");
InitializeObjectAttributes( &ObjectAttributes, &FileName,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL );
ZwCreateFile(&fHandle, GENERIC_WRITE, &ObjectAttributes,
&ioStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OVERWRITE_IF,
FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
ZwWriteFile(fHandle, NULL, NULL, NULL, &ioStatusBlock, myString,
strlen(myString), NULL, NULL); ZwClose(fHandle); KeDelayExecutionThread(KernelMode,FALSE,&Interval );
return STATUS_SUCCESS;
}
And here we go - our three parts ready. You need to compile the driver now. Select from the main Windows menu Driver Build Environment like you see on the picture below:
You'll get this command prompt:
Now, simply navigate to a folder with the driver source and type: build -c. Next, you need to dump the driver binary data, and you can use the tool I have included: the binary file to source text convertor. Or, compile the source from the very beginning of this paper and use it. Then, include the dumped data into a native application source and compile it with the makefile I have provided and dump it in the same way. And, in the end, include the dumped native app in a Win32 application code, then compile it in the standard way. After all that, just launch an executable and enjoy the trip from user-mode to kernel-mode :)