三步下楼:从 Win32 用户空间到原生 API 再到内核
这个项目应用程序将“穿越”Windows 系统,最终到达内核,从环 3 到环 0。
引言
正如标题所示,楼梯上的三个步骤,我们的项目应用程序将“穿越”Windows 系统,最终到达内核,从环 3 到环 0。它将变得越来越特权,越来越强大,越来越有趣。我将在此使用最简单的代码示例来使其更加清晰。它看起来会是怎样的?看看这个图示
Pure Win32 --> Mixed Win32 & NT --> Pure NT --> Kernel mode driver
实际上,我们的应用程序就像是三个应用程序的容器。这三个应用程序是什么?第一个是 Win32 用户模式程序;第二个是纯 Windows 原生应用程序,最后一个是内核模式驱动程序。现在,Win32 用户模式程序将在其代码中包含纯原生应用程序的代码,该代码将在其执行后部署;它又将包含内核模式驱动程序代码,该代码将在原生应用程序执行后部署。有点像连锁反应。
你需要什么来构建所有东西?首先,你需要一个 Windows 驱动程序开发工具包,你可以从微软网站获取。下一个也是最后一个,一个 MinGW 包(GCC 等)。当然,你可以使用 MS Visual Studio,但我更喜欢 GCC 编译器。实际上,也许你也可以用 GCC 编译驱动程序,因为它是一个非常简单的驱动程序,但我没试过,所以不知道。
本文的主要目标是什么?本文的目的是在最基本的示例中展示
- 如何使用 Native Windows API 编写 Win32 应用程序
- 纯原生 Windows 应用程序是什么样的
- 一种将不同的、独立的应用程序包含在一个应用程序中的方法
- 使用 Windows Native API 示例进行线程注入
好的,让我们从 Win32 应用程序开始。
Using the Code
我首先应该提到的是一种将可执行文件打包到另一个文件中的方法。为了更好地理解这一点,让我们使用十六进制编辑器来查看我们的可执行文件内部的样子。
实际上,我们需要将代码转储到一个单一的 char
容器中,它看起来会像这样
unsigned char my_app [] ={
0x4d,0x5a,0x90,0x00,0x03,0x00,0x00,0x00
.............
}
为此,我使用了一个二进制文件到文本转换器(v1.01 (c) 1992,93 by Alex Kopylov),我已将其包含在本论文源代码包中。但是,如果你害怕未知应用程序,转储 EXE 的代码会像这样
#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; }你可以轻松地编译它。现在,你知道如何将一个可执行文件打包到另一个中。还有一件事你需要知道:打包后的 EXE 的大小。只需像这样检查你的可执行文件属性
大小,这就是你需要的。不是磁盘上的大小。正如你所见,我的 EXE 有 23490 字节。我们先记住这一点 ;) 好的,我们现在有了需要的所有东西。我们项目的第一部分是负责解包原生应用程序的 Win32 应用程序。
#include "remove.h" // here will be the code for deletion of our win32 exe #include "NATIVE_CONTAINER.h" // here we will store hex code of our native app #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 // copy native //app to system32 folder #define NATIVE_NAME "native.exe" // name of native app #define NATIVE_SIZE 14617 // size of native app
原生 Windows 应用程序的安装分两步:可执行文件应被复制到 *system32* 文件夹,并且应添加相应的注册表值,以便我们的应用程序在 Win32 之前启动。为了实现这一点,我将使用两个安装文件,由 Mark Russinovich 在他的 Windows 原生应用程序示例中编写。以下函数将创建这两个文件
void Create_File(char *filename, char *data){ FILE *file; file=fopen(filename,"w"); if(!file) return; fprintf(file,"%s",data); fclose(file); }
现在,只需几个简单的步骤即可创建和安装所有必需的东西
// now simple function that will deploy native application // NATIVE_NAME - is a name of our EXE. hNat = CreateFile(NATIVE_NAME, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL, NULL); // this will write binary data to created executable file // native_container - is our char container where binary data is stored // NATIVE_SIZE - is the exact size of our EXE, if it will not be valid, // Native application will fail to start WriteFile(hNat, native_container, NATIVE_SIZE, &dwWritten, NULL); // here we are creating file that will add our native application to windows registry, // native app will be executed after computer reboot 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" // 6e,61,74,69,76,65,00,00 : n, a, t, i, v, e, 0, 0 ); // here we are creating batch file that will install our native app Create_File(RUN_BAT, "@echo off\n\ copy native.exe %systemroot%\\system32\\.\n\ regedit /s ADD2REG.REG\n" ); WinExec(RUN_BAT, SW_SHOW); // executing run.bat Sleep(6000); // just to make sure everything is done OK DeleteFile(ADDTOREG_REG); // delete run.bat DeleteFile(RUN_BAT); // delete add2reg.reg DeleteFile(NATIVE_NAME); // delete native app from desktop
当然,我也可以使用 C 函数安装我的原生应用程序,但我懒得为这篇论文写!P 说到底,我想让我的 Win32 应用永远从机器上删除。我将为此使用线程注入方法,并使用从 *ntdll.dll* 导出的原生函数。实际上,下面的代码最初可能是 Killaloop 写的,我只是修改了它以使用原生 API。所以,我们要深入一点点底层 ;) 要在我们的 Win32 应用中使用原生内容,我们首先需要将函数原型写成指针。只需看一下 Win32 VirtualAllocEx
函数原型(来自 MSDN)
LPVOID WINAPI VirtualAllocEx( __in HANDLE hProcess, __in_opt LPVOID lpAddress, __in SIZE_T dwSize, __in DWORD flAllocationType, __in DWORD flProtect );
WINAPI
是一个 Win32 API 函数。现在,看看一个类似的原生函数
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
是 Native API。要在 Win32 可执行文件中使用它,我们将将其写成一个指针
NTSTATUS (NTAPI *NtAllocateVirtualMemory) (HANDLE ProcessHandle, PVOID *BaseAddress, ULONG ZeroBits, PULONG RegionSize, ULONG AllocationType, ULONG Protect);
现在,我们将动态链接 *ntdll.dll* 并从中获取函数地址
BOOL InitializeRoot(){ HMODULE hNtDLL = GetModuleHandle("ntdll.dll"); *(FARPROC *)&NtAllocateVirtualMemory = GetProcAddress(hNtDLL, "NtAllocateVirtualMemory"); return 0; }
在上述函数执行后,我们在 Win32 应用程序中使用该原生函数。以下是应该在线程注入例程执行之前初始化的函数
// this we give us debug privileges LONG (__stdcall *RtlAdjustPrivilege)(int,BOOL,BOOL,BOOL *); // this will do the injection stuff 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 ); // this will open process for injection NTSTATUS (NTAPI *NtOpenProcess)(PHANDLE ProcessHandle, ACCESS_MASK AccessMask, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId ); // this will allocate virtual memory inside target process NTSTATUS (NTAPI *NtAllocateVirtualMemory)(HANDLE ProcessHandle, PVOID *BaseAddress,ULONG ZeroBits, PULONG RegionSize,ULONG AllocationType,ULONG Protect); // this will write virtual memory into target process NTSTATUS (NTAPI *NtWriteVirtualMemory)(IN HANDLE ProcessHandle, IN PVOID BaseAddress, IN PVOID Buffer, IN ULONG NumberOfBytesToWrite, OUT PULONG NumberOfBytesWritten OPTIONAL); // that is simply to close a handle to process NTSTATUS (NTAPI *NtClose)(IN HANDLE ObjectHandle); // that is actually strcpy function, exported from // ntdll, I know, I know, don't say a word :P NTSTATUS (NTAPI *NtStrCpy)(char *, char *);
你可能已经注意到了新的内容,如 PCLIENT_ID
、POBJECT_ATTRIBUTES
、NTSTATUS
等。这些结构也应该被定义,例如
#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; // Points to type SECURITY_DESCRIPTOR PVOID SecurityQualityOfService; // Points to type SECURITY_QUALITY_OF_SERVICE } OBJECT_ATTRIBUTES; typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES;
这一切来自哪里?有三个主要的“策略”对象提供此类信息。一个是 Windows Driver Development Kit 文档,下一个是 http://undocumented.ntinternals.net/ 网站,最后一个是 NDK(可能代表 Native Development Kit,我真的不知道,你可以在网上找到它)它包含了为原生应用程序开发而设计的头文件。
好的,让我们回到主题。我们将只注入一个线程和一个函数,该函数将删除我们的 Win32 可执行文件。在这种情况下,整个方法看起来就像你使用了标准方法,多年来为人所知,并且完整的代码可以在包中找到,所以我只在这里展示一些东西给那些不熟悉该主题的人。首先,我将在远程线程中使用的函数声明
typedef DWORD (WINAPI *RAWGETPROCADDRESS)( HMODULE ,LPCSTR); typedef HMODULE (WINAPI *RAWLOADLIBRARYA)(LPCTSTR ); typedef BOOL (WINAPI *RAWDELETEFILE)(LPCTSTR ); typedef VOID (WINAPI *RAWSLEEP)(DWORD );
在我的远程线程中,我的代码将加载所需的库,获取所需的程序地址,并执行加载的函数。接下来,我们正在构建远程数据,如下所示
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;
这里,我应该说,我在让 NtAllocateVirtualMemory
正常工作方面遇到了很多问题,所以我稍微绕了一下
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; }
现在,我们将需要的函数和值分配给我们的远程结构中的每个参数,如下所示
my_RemoteStructure.dwGetProcAddress = (DWORD)GetProcAddress(hKernel, "GetProcAddress");
和
NtStrCpy(my_RemoteStructure.delfile, "DeleteFileA");
现在,我们需要打开要注入的进程;我们需要进程的 PID,所以我们将以标准方式获取它,使用诸如 CreateToolhelp32Snapshot
、Process32First
、Process32Next
等函数。
#define INJECT_EXE "svchost.exe" DWORD pID = GetPID(INJECT_EXE);
现在,我们必须将我们所谓的 pID
转换为 CLIENT_ID
并进行注入;这里是原生部分
ClientId.UniqueProcess = (HANDLE)pID; // process has its own ID ClientId.UniqueThread = 0; // and thread has its own ID InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL); // in this case its useles RtlAdjustPrivilege(20, TRUE, AdjustCurrentProcess, &en); // 20 - debugging privileges Status = NtOpenProcess(&hProcess, PROCESS_ALL_ACCESS , &ObjectAttributes, &ClientId); if( !NT_SUCCESS( Status )) { return FALSE;} //NtOpenProcess will return handle to process pRemoteThread = NtVirtualAlloc(hProcess, 0, dwThreadSize, MEM_COMMIT | MEM_RESERVE,PAGE_EXECUTE_READWRITE); // reserve virtual memory size NtWriteVirtualMemory(hProcess, pRemoteThread, &RemoteThread, dwThreadSize,0); // --||-- RtlZeroMemory(&my_RemoteStructure,sizeof(RemoteStructure)); //free memory
在这里,我们实际上正在分配所需的函数和值
pmy_RemoteStructure =(RemoteStructure *)NtVirtualAlloc (hProcess , 0,sizeof(RemoteStructure),MEM_COMMIT,PAGE_READWRITE); // allocate remote data memory NtWriteVirtualMemory(hProcess, pmy_RemoteStructure,&my_RemoteStructure, sizeof(my_RemoteStructure),0); // write remote data memory RtlCreateUserThread(hProcess, NULL,FALSE, 0, NULL, NULL,(PVOID)pRemoteThread, (PVOID)pmy_RemoteStructure, 0, 0); // create remote thread NtClose(hProcess); // close handle to target process
现在,这就是我的远程线程的样子
DWORD __stdcall RemoteThread(RemoteStructure *Parameter){ // we will point to first 2 major functions // from our remote data structure RAWGETPROCADDRESS NtGetProcAddress = (RAWGETPROCADDRESS)Parameter->dwGetProcAddress; RAWLOADLIBRARYA NtLoadLibraryA = (RAWLOADLIBRARYA)Parameter->dwLoadLibraryA; // all other functions we might need, we will load dynamically 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; }
通过这种方式,你可以将线程注入到任何 Windows 进程中,例如,在 Vista 上的 *svchost.exe* 中。如果你在 Windows XP 上使用这种类型的注入,请确保你的远程线程在执行后会终止自身,否则它将导致 *csrss.exe* 和宿主进程占用高 CPU。无论如何,我只是为了展示原生 API 在 Win32 应用程序中的用法而添加了线程注入部分。如果你有兴趣深入了解 Windows,请查看 React OS 项目 - 这是一个非常酷的资源。
我们的原生应用程序将不会比我们的 Win32 应用做太多。它将简单地部署一个内核模式驱动程序,注册它,并在控制权交给你之前加载它。我听说很多人在编译原生应用程序时遇到了问题,但它确实是一项简单的任务。首先,不要使用 MS Visual Studio,使用带有 GCC 的 MinGW 包 :D。接下来,当你的代码完成后,在命令提示符中输入:*C:\path\to\gcc\bin\gcc native.c -o native.exe -lntdll -nostdlib -Wl,--subsystem,native,-e,_NtProcessStartup* 这样你的应用程序就能完美编译(当然,如果你的代码没有错误)。无论如何,在包中,我包含了一个 makefile(*make.bat*),所以你只需要输入你的 GCC 路径,批处理脚本就会完成其余的工作。确保你的 MinGW include 目录中有 DDK 文件夹,因为我们的应用程序需要其中的一些头文件。好的,让我们来定义我们最需要的一些函数。
// function to load our driver NTSTATUS NTAPI ZwLoadDriver(PUNICODE_STRING DriverServiceName); // initialization of unicode string VOID NTAPI RtlInitUnicodeString(PUNICODE_STRING DestinationString,PCWSTR SourceString); // we need to set privileges before we will load driver LONG __stdcall RtlAdjustPrivilege(int,BOOL,BOOL,BOOL *); // this will create driver file 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 ); // this will write data to driver file 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 ); // create driver registry key 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 ); // set key values NTSTATUS NTAPI NtSetValueKey(IN HANDLE KeyHandle, IN PUNICODE_STRING ValueName, IN ULONG TitleIndex OPTIONAL, IN ULONG Type, IN PVOID Data, IN ULONG DataSize ); // that is like Sleep function NTSTATUS NTAPI NtDelayExecution(IN BOOLEAN Alertable, IN PLARGE_INTEGER DelayInterval ); // define some values #define DRIVER "zzzz.sys" #define DRIVER_SIZE 2560 #define DRIVER_KEY "zzzz"
现在,驱动程序的二进制数据将以与此原生应用程序的二进制数据在 Win32 应用程序中相同的方式存储在这里。
unsigned char DriverData[] = { 0x4d,0x5a,0x90,0x00,0x03,0x00,0x00,0x00 ............. }
现在,为了让生活更轻松一点,这里有一个函数,类似于 Sleep
,但时间单位将是秒,而不是毫秒或其他更小的值。
BOOL NtDelayExecutionEx(DWORD dwSeconds){ LARGE_INTEGER Interval; Interval.QuadPart = -(unsigned __int64)dwSeconds * 10000 * 1000; NtDelayExecution (FALSE, &Interval); }
让我们看看我们的原生应用程序将如何注册驱动程序。这里首先应该清楚的是,原生应用程序使用 Unicode 字符串,所以我们的键将看起来像这样
// registry path in system format WCHAR DrvPath[] = L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Services\\"; WCHAR DrvName[] = L"zzzz";
现在,我们需要将其转换为 Unicode 字符串,如下所示
// some constants ... HANDLE mainDrvHandle, drvKeyHandle, EnumKeyHandle; UNICODE_STRING KeyName; OBJECT_ATTRIBUTES ObjectAttributes, oBj, oa; // start of conversion KeyName.Buffer = DrvPath; KeyName.Length = wcslen( DrvPath ) *sizeof(WCHAR); KeyName.MaximumLength = KeyName.Length + sizeof(WCHAR); // end of conversion
现在,我们需要调用 InitializeObjectAttributes
函数。实际上,它是一个宏。来自 MSDN:InitializeObjectAttributes
宏初始化不透明的 OBJECT_ATTRIBUTES
结构,该结构指定例程打开句柄的对象句柄的属性。
InitializeObjectAttributes( &oBj, &KeyName, OBJ_CASE_INSENSITIVE, NULL, NULL ); NtCreateKey( &mainDrvHandle, KEY_ALL_ACCESS, &oBj, 0, NULL, REG_OPTION_NON_VOLATILE,&Disposition ); //In this case NtCreateKey will open our DrvPath and return handle //to it. Now we we will use that handle to create our //driver registry key. 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 );
现在,看看最重要的键值是如何创建的。
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) );
我不会在这里展示项目中的所有代码,但其他值(你的应用程序至少应该创建两个值:ImagePath
- 它应该在哪里查找驱动程序,以及 Type
- 如果你想添加更多值,如 DisplayName
、ErrorControl
、Start
等;检查你系统中的其他服务是如何注册的)是这样创建的,不带“+ sizeof(WCHAR)
”。我们的值类型是 REG_EXPAND_SZ
,所以它需要额外的空间来存储终止符(最可能是 2,如下所示:00 00)。如果你在创建此值时没有“+ sizeof(WCHAR)
”,应用程序将无法加载驱动程序;它将简单地返回 ((NTSTATUS)0xC0000034L)
,这意味着 STATUS_OBJECT_NAME_NOT_FOUND
。
好的,该创建驱动程序了。如你所见,Unicode 字符串的初始化是相同的
FileName.Buffer = L"\\??\\C:\\WINDOWS\\system32\\zzzz.sys"; FileName.Length = wcslen( FileName.Buffer ) *sizeof(WCHAR); FileName.MaximumLength = FileName.Length + sizeof(WCHAR); //Below is manual initialization of object attributes ObjectAttributes.Length =sizeof(ObjectAttributes); ObjectAttributes.RootDirectory = NULL; ObjectAttributes.ObjectName = &FileName; ObjectAttributes.Attributes = OBJ_CASE_INSENSITIVE; ObjectAttributes.SecurityDescriptor = NULL; ObjectAttributes.SecurityQualityOfService = NULL; //So finally lets create our driver file 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){ // set memory for debug message RtlZeroMemory(&dbgMessage, sizeof(dbgMessage)); // init Unicode string RtlInitUnicodeString(&dbgMessage, L"ZwCreateFile error!\n"); NtDisplayString( &dbgMessage ); // display it on blue screen NtDelayExecutionEx(2); // sleep for 2 seconds NtTerminateProcess( NtCurrentProcess(), 0 ); // terminate own process }
然后将驱动程序数据写入驱动程序文件……
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); // close handle to file
我认为你已经注意到,编写原生应用程序代码与编写驱动程序非常相似。例如,你可以将 NtWriteFile
替换为 ZwWriteFile
,此时你会得到相同的函数。最后,加载驱动程序
if (ZwLoadDriver(&DriverPath) != STATUS_SUCCESS){
//if loading driver failed
RtlInitUnicodeString(&dbgMessage, L"\nDriver load error!\n");
NtDisplayString( &dbgMessage );
NtDelayExecutionEx(2);
NtTerminateProcess( NtCurrentProcess(), 0 );
} else { // if success
RtlInitUnicodeString(&dbgMessage, L"\nDriver loaded successfully!\n");
NtDisplayString( &dbgMessage );
}
//At the end our application must terminate itself:
NtTerminateProcess( NtCurrentProcess(), 0 );
至此,我们已经解释了项目中的两个部分。让我们看看最后一部分。我包含在此项目中的驱动程序只有两个无聊的函数:在 *C:\* 中创建一个文本文件,并将文本 *OWNED* 写入该文件。这是代码
typedef unsigned long DWORD;
// main function
NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath){
HANDLE fHandle; // handle to file
OBJECT_ATTRIBUTES ObjectAttributes; // object attributes
LARGE_INTEGER Interval; // integer, we will use it for sleep function
UNICODE_STRING FileName; // file name
IO_STATUS_BLOCK ioStatusBlock; // IO status block
DWORD dwSeconds = 10; // time to sleep
char myString[] = {"OWNED!!!"}; // string to print to file
Interval.QuadPart = -(unsigned __int64)dwSeconds * 10000 * 1000;
// initialize our time interval
if(KeGetCurrentIrql() != PASSIVE_LEVEL) return STATUS_INVALID_DEVICE_STATE;
RtlInitUnicodeString(&FileName, L"\\DosDevices\\C:\\0WN3ED.TXT");
// our file name -> unicode string
InitializeObjectAttributes( &ObjectAttributes, &FileName,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL );
ZwCreateFile(&fHandle, GENERIC_WRITE, &ObjectAttributes,
&ioStatusBlock, NULL, //create file
FILE_ATTRIBUTE_NORMAL, 0, FILE_OVERWRITE_IF,
FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
ZwWriteFile(fHandle, NULL, NULL, NULL, &ioStatusBlock, myString,
strlen(myString), NULL, NULL); // write data to file
ZwClose(fHandle); // close handle to file
KeDelayExecutionThread(KernelMode,FALSE,&Interval );
// sleep for some time to let user read our
//message from native application
return STATUS_SUCCESS;
}
//--------------------------------------------------------------------------------------
好了,我们的三个部分都准备好了。现在你需要编译驱动程序。从 Windows 主菜单中选择 *Driver Build Environment*,如下图所示
你将得到这个命令提示符
现在,只需导航到驱动程序源代码所在的文件夹并输入:*build -c*。接下来,你需要转储驱动程序的二进制数据,你可以使用我包含的工具:二进制文件到文本转换器。或者,编译本论文最开始的源代码并使用它。然后,将转储的数据包含到原生应用程序源代码中,并使用我提供的 makefile 进行编译,并以相同的方式转储。最后,将转储的原生应用程序包含到 Win32 应用程序代码中,然后以标准方式进行编译。完成所有这些之后,只需运行可执行文件,享受从用户模式到内核模式的旅程吧!:)