65.9K
CodeProject 正在变化。 阅读更多。
Home

三步下楼:从 Win32 用户空间到原生 API 再到内核

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (9投票s)

2009 年 4 月 1 日

CPOL

10分钟阅读

viewsIcon

56982

downloadIcon

1625

这个项目应用程序将“穿越”Windows 系统,最终到达内核,从环 3 到环 0。

Image

引言

正如标题所示,楼梯上的三个步骤,我们的项目应用程序将“穿越”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_IDPOBJECT_ATTRIBUTESNTSTATUS 等。这些结构也应该被定义,例如

#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,所以我们将以标准方式获取它,使用诸如 CreateToolhelp32SnapshotProcess32FirstProcess32Next 等函数。

#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 - 如果你想添加更多值,如 DisplayNameErrorControlStart 等;检查你系统中的其他服务是如何注册的)是这样创建的,不带“+ 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 应用程序代码中,然后以标准方式进行编译。完成所有这些之后,只需运行可执行文件,享受从用户模式到内核模式的旅程吧!:)

© . All rights reserved.