二进制入门学习笔记-20.Windows 驱动开发

1.驱动程序开发环境

1.1 基础软件安装

这里使用的驱动开发环境为xp作为测试靶机,VS2010+WDK7600。二者安装的过程只需要下载对应的安装包一路下一步即可。WDK安装完成后即可在开始菜单看到如下内容,WDK相关的文档也包含在这个文件夹中。

1.2 项目配置修改

vs开发的时候如果需要调用某些新安装的库需要自己在配置中手工配置,或者保存一个统一的配置文件模版,当需要开发同类项目时直接打开。参考模版如下,使用时注意修改文件夹路径即可。修改完后可将文件保存为xxxxxx.props,如需使用在项目中添加即可。

<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <ImportGroup Label="PropertySheets" />

  <PropertyGroup Label="UserMacros" />

  <PropertyGroup>

    <ExecutablePath>C:\WinDDK\7600.16385.1\bin\x86;$(ExecutablePath)</ExecutablePath>

  </PropertyGroup>

  <PropertyGroup>

    <IncludePath>C:\WinDDK\7600.16385.1\inc\api;C:\WinDDK\7600.16385.1\inc\ddk;C:\WinDDK\7600.16385.1\inc\crt;$(IncludePath)</IncludePath>

  </PropertyGroup>

  <PropertyGroup>

    <LibraryPath>C:\WinDDK\7600.16385.1\lib\wxp\i386;$(LibraryPath)</LibraryPath>

    <TargetExt>.sys</TargetExt>

    <LinkIncremental>false</LinkIncremental>

    <GenerateManifest>false</GenerateManifest>

  </PropertyGroup>

  <ItemDefinitionGroup>

    <ClCompile>

      <PreprocessorDefinitions>_X86_;DBG</PreprocessorDefinitions>

      <CallingConvention>StdCall</CallingConvention>

      <ExceptionHandling>false</ExceptionHandling>

      <BasicRuntimeChecks>Default</BasicRuntimeChecks>

      <BufferSecurityCheck>false</BufferSecurityCheck>

      <CompileAs>Default</CompileAs>

      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>

    </ClCompile>

    <Link>

      <AdditionalDependencies>ntoskrnl.lib;wdm.lib;wdmsec.lib;wmilib.lib;ndis.lib;Hal.lib;MSVCRT.LIB;LIBCMT.LIB;%(AdditionalDependencies)</AdditionalDependencies>

    </Link>

    <Link>

      <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>

      <EnableUAC>false</EnableUAC>

      <SubSystem>Native</SubSystem>

      <EntryPointSymbol>DriverEntry</EntryPointSymbol>

      <BaseAddress>0x10000</BaseAddress>

      <RandomizedBaseAddress>

      </RandomizedBaseAddress>

      <DataExecutionPrevention>

      </DataExecutionPrevention>

      <GenerateDebugInformation>true</GenerateDebugInformation>

      <Driver>Driver</Driver>

    </Link>

  </ItemDefinitionGroup>

  <ItemGroup />

</Project>

1.3 第一个驱动程序

1.新建一个空项目 2.为项目添加配置 3.新建一个.c文件,添加如下内容,编译测试。成功说明开发环境以部署完成。

#include <Ntddk.h> //编写内核驱动需要包含NTddk头文件.
VOID DriverUnload(PDRIVER_OBJECT driver)
{
    DbgPrint("驱动程序已停止!\n\r");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT driver,PUNICODE_STRING reg_path)
{
    __asm{
        int 3
        mov eax,eax
        mov eax,eax
        mov eax,eax
    }
    DbgPrint("驱动程序已加载!\n\r");
    driver->DriverUnload = DriverUnload;
    return STATUS_SUCCESS;
}

4.注意双击调试的时候,记得在windbg的Symbol Search path里面加上这个程序pdb文件所在的路径,这样驱动里下断点就可以看到程序源码了,方便调试。

2.内核空间与内核模块

2.1 驱动对象

我们所编写的sys文件也就是所谓的驱动,每一个驱动在高2G内核空间中都存在一个DRIVER_OBJECT对象来描述这个驱动的相关信息,该结构体如下:

kd> dt _DRIVER_OBJECT
nt!_DRIVER_OBJECT
   +0x000 Type             : Int2B
   +0x002 Size             : Int2B
   +0x004 DeviceObject     : Ptr32 _DEVICE_OBJECT
   +0x008 Flags            : Uint4B
   +0x00c DriverStart      : Ptr32 Void  //驱动模块的基址
   +0x010 DriverSize       : Uint4B  //驱动大小
   +0x014 DriverSection    : Ptr32 Void  //结构体指针,指向的是一个LDR_DATA_TABLE_ENTRY 对象。通过这个指针,我们可以遍历出来所有的驱动对象
   +0x018 DriverExtension  : Ptr32 _DRIVER_EXTENSION
   +0x01c DriverName       : _UNICODE_STRING  //驱动的名称
      +0x024 HardwareDatabase : Ptr32 _UNICODE_STRING
   +0x028 FastIoDispatch   : Ptr32 _FAST_IO_DISPATCH
   +0x02c DriverInit       : Ptr32     long 
   +0x030 DriverStartIo    : Ptr32     void 
   +0x034 DriverUnload     : Ptr32     void   //这里面存的是卸载函数
   +0x038 MajorFunction    : [28] Ptr32     long  //这里面存的是派遣函数地址

我们编写的DriverEntry函数中所传入的两个对象,一个就是DRIVER_OBJECT还有一个是注册表中该模块对应的位置。事实上驱动的加载过程中,第一步加载并没有将驱动文件写入高2G内存,而是在注册表中填写了驱动的位置。第二步运行时才会将其写入内存。我们可以通过程序本身读取这个驱动对象的指针:

#include <Ntddk.h> //编写内核驱动需要包含NTddk头文件.
VOID DriverUnload(PDRIVER_OBJECT driver)
{
    DbgPrint("驱动程序已停止!\n\r");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT driver,PUNICODE_STRING reg_path)
{
    __asm{
        int 3
        mov eax,eax
        mov eax,eax
        mov eax,eax
    }
    DbgPrint("驱动程序已加载!\n\r");
    DbgPrint("%x\n",driver);
    DbgPrint("%wZ\n",reg_path);
    driver->DriverUnload = DriverUnload;
    return STATUS_SUCCESS;
}

而这个驱动对象,我们可以去读取他的具体内容:

kd> p
驱动程序已加载!
testDriver!DriverEntry+0x17:
f9cf5037 8b4508          mov     eax,dword ptr [ebp+8]
kd> p
81536c10
testDriver!DriverEntry+0x28:
f9cf5048 8b4d0c          mov     ecx,dword ptr [ebp+0Ch]
kd> dt _DRIVER_OBJECT 81536c10
nt!_DRIVER_OBJECT
   +0x000 Type             : 0n4
   +0x002 Size             : 0n168
   +0x004 DeviceObject     : (null) 
   +0x008 Flags            : 2
   +0x00c DriverStart      : 0xf9cf4000 Void
   +0x010 DriverSize       : 0x6000
   +0x014 DriverSection    : 0x81858ae0 Void    //通过这个对象可以访问到链表上其他的驱动对象
   +0x018 DriverExtension  : 0x81536cb8 _DRIVER_EXTENSION
   +0x01c DriverName       : _UNICODE_STRING "\Driver\testDriver"
   +0x024 HardwareDatabase : 0x80671ae0 _UNICODE_STRING "\REGISTRY\MACHINE\HARDWARE\DESCRIPTION\SYSTEM"
   +0x028 FastIoDispatch   : (null) 
   +0x02c DriverInit       : 0xf9cf5020     long  testDriver!DriverEntry+0
   +0x030 DriverStartIo    : (null) 
   +0x034 DriverUnload     : (null) 
   +0x038 MajorFunction    : [28] 0x804f454a     long  nt!IopInvalidDeviceRequest+0

遍历其他drvier对象

kd> dt _DRIVER_OBJECT 81536c10
nt!_DRIVER_OBJECT
   +0x000 Type             : 0n4
   +0x002 Size             : 0n168
   +0x004 DeviceObject     : (null) 
   +0x008 Flags            : 2
   +0x00c DriverStart      : 0xf9cf4000 Void
   +0x010 DriverSize       : 0x6000
   +0x014 DriverSection    : 0x81858ae0 Void
   +0x018 DriverExtension  : 0x81536cb8 _DRIVER_EXTENSION
   +0x01c DriverName       : _UNICODE_STRING "\Driver\testDriver"
   +0x024 HardwareDatabase : 0x80671ae0 _UNICODE_STRING "\REGISTRY\MACHINE\HARDWARE\DESCRIPTION\SYSTEM"
   +0x028 FastIoDispatch   : (null) 
   +0x02c DriverInit       : 0xf9cf5020     long  testDriver!DriverEntry+0
   +0x030 DriverStartIo    : (null) 
   +0x034 DriverUnload     : (null) 
   +0x038 MajorFunction    : [28] 0x804f454a     long  nt!IopInvalidDeviceRequest+0
kd> dt _LDR_DATA_TABLE_ENTRY  0x81858ae0 
nt!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x80554fc0 - 0x818b8448 ]
   +0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0xffffffff - 0xffffffff ]
   +0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x630069 - 0x0 ]
   +0x018 DllBase          : 0xf9cf4000 Void
   +0x01c EntryPoint       : 0xf9cf5020 Void
   +0x020 SizeOfImage      : 0x6000
   +0x024 FullDllName      : _UNICODE_STRING "\??\C:\Documents and Settings\john\桌面\testDriver.sys"
   +0x02c BaseDllName      : _UNICODE_STRING "testDriver.sys"
   +0x034 Flags            : 0x1104000
   +0x038 LoadCount        : 1
   +0x03a TlsIndex         : 0x6f
   +0x03c HashLinks        : _LIST_ENTRY [ 0xffffffff - 0xe8db ]
   +0x03c SectionPointer   : 0xffffffff Void
   +0x040 CheckSum         : 0xe8db
   +0x044 TimeDateStamp    : 0xfffffffe
   +0x044 LoadedImports    : 0xfffffffe Void
   +0x048 EntryPointActivationContext : (null) 
   +0x04c PatchInformation : 0x00650074 Void
kd> dt _LDR_DATA_TABLE_ENTRY 0x80554fc0 
nt!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x819fc398 - 0x81858ae0 ]
   +0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x018 DllBase          : (null) 
   +0x01c EntryPoint       : (null) 
   +0x020 SizeOfImage      : 0x8055c460
   +0x024 FullDllName      : _UNICODE_STRING ""
   +0x02c BaseDllName      : _UNICODE_STRING ""
   +0x034 Flags            : 0
   +0x038 LoadCount        : 0
   +0x03a TlsIndex         : 0
   +0x03c HashLinks        : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x03c SectionPointer   : (null) 
   +0x040 CheckSum         : 0
   +0x044 TimeDateStamp    : 0
   +0x044 LoadedImports    : (null) 
   +0x048 EntryPointActivationContext : (null) 
   +0x04c PatchInformation : (null) 

以下为使用代码遍历其他的内核模块名称:

#include <Ntddk.h>
typedef struct _LDR_DATA_TABLE_ENTRY{
    LIST_ENTRY32 InLoadOrderLinks;
    LIST_ENTRY32 InMemoryOrderLinks;
    LIST_ENTRY32 InInitializationOrderLinks;
    ULONG DllBase;
    ULONG EntryPoint;
    ULONG SizeOfImage;
    UNICODE_STRING32 FullDllName;
    UNICODE_STRING32 BaseDllName;
    //后面还一长串懒得写了 反正光遍历个名字也用不上那么多
}LDR_DATA_TABLE_ENTRY;



void GetKernelModuleName(PDRIVER_OBJECT driver)
{
    if(driver){
        LDR_DATA_TABLE_ENTRY* pListHead=NULL;
        LDR_DATA_TABLE_ENTRY* pCurrentNode=NULL;
        pListHead = driver->DriverSection;
        DbgPrint("%S\r\n", pListHead->FullDllName.Buffer);
        pCurrentNode = (LDR_DATA_TABLE_ENTRY *)pListHead->InLoadOrderLinks.Flink;
        while(pCurrentNode!=pListHead){//当前节点不是入口节点时循环输出
            DbgPrint("%S\r\n", pCurrentNode->FullDllName.Buffer);
            pCurrentNode = (LDR_DATA_TABLE_ENTRY *)pCurrentNode->InLoadOrderLinks.Flink;
        }
    }
}


VOID DriverUnload(PDRIVER_OBJECT driver)
{
    DbgPrint("驱动程序已停止!\n\r");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT driver,PUNICODE_STRING reg_path)
{
    DbgPrint("驱动程序已加载!\n\r");
    GetKernelModuleName(driver);
    driver->DriverUnload = DriverUnload;
    return STATUS_SUCCESS;
}

3.R3与R0通信

3环和0环通信一般是采用设备对象的方式,通信的消息被封装在了IRP(I/O Request Package)中,而负责接受这个消息的就是设备对象

4.自己加载驱动程序

// driverLoader.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <stdio.h>
#include <Windows.h>
char* fileName;
SC_HANDLE mySCHandler=NULL;
SC_HANDLE myDriverService=NULL;
int main(int argc,char* argv[]){ 
    fileName = argv[1];
    printf("Driver Path:%s\n",fileName);
    mySCHandler = OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);//(本机,默认数据库,打开权限)
    if(!mySCHandler){
         printf("Open SC Manger Failed! Use Administarator to Run it!\n");
         CloseServiceHandle(mySCHandler);
         getchar();
    }
    myDriverService = CreateService(
        mySCHandler,
        "testDriver",
        "testDriver",
        SERVICE_ALL_ACCESS,//访问权限
        SERVICE_KERNEL_DRIVER,//服务类型(驱动程序)
        SERVICE_DEMAND_START,//启动方式(需要时启动,注册表驱动程序的Start值)
        SERVICE_ERROR_IGNORE,//错误忽略
        fileName,
        NULL,//加载组命令
        NULL,//TagId
        NULL,//依存关系
        NULL,//服务启动名
        NULL//密码
    );
    if (!myDriverService){
        if (GetLastError()==ERROR_SERVICE_EXISTS){
            printf("Service Exist");
        }else{
            printf("Error while install, Error Code : %p\n",GetLastError());
        }
    }else{
        printf("Driver Install Success!!!\n");
    }
    StartService(myDriverService,NULL,NULL);
    CloseServiceHandle(myDriverService);
    CloseServiceHandle(mySCHandler);
    getchar();



}

5.实战

5.1 PspTerminateProcess杀进程

这里我们用notepad作为实验对象,打开一个notepad,如下图得到进程id为2036

代码的整体思路为,先查找ntoskrnl.exe模块的基址,然后在这个基址开始到基址+SizeOfImage进行内存搜索,对比Opcode是否匹配,匹配则认为找到了PspTerminateProcess杀进程函数地址。声明函数指针,传参杀进程即可。 代码如下:

#include <Ntddk.h>
typedef struct _LDR_DATA_TABLE_ENTRY{
    LIST_ENTRY InLoadOrderLinks;
    LIST_ENTRY InMemoryOrderLinks;
    LIST_ENTRY InInitializationOrderLinks;
    ULONG DllBase;
    ULONG EntryPoint;
    ULONG SizeOfImage;
    UNICODE_STRING FullDllName;
    UNICODE_STRING BaseDllName;
    //后面还一长串懒得写了 反正光遍历个名字也用不上那么多
}LDR_DATA_TABLE_ENTRY;
UNICODE_STRING targetModelName = RTL_CONSTANT_STRING(L"ntoskrnl.exe");
LONG Res;
ULONG targetDllBase;
ULONG targetSizeOfImage;
ULONG targetFuncAddr=0;
ULONG code1_sp2=0x8b55ff8b,code2_sp2=0xa16456ec,code3_sp2=0x00000124,code4_sp2=0x3b08758b;//函数开头对应的opcode,在windbg找的,作为特征码遍历内存
ULONG i;
typedef  NTSTATUS  (*PSPTERPROC) ( PEPROCESS Process, NTSTATUS ExitStatus );
NTSTATUS PsLookupProcessByProcessId( IN HANDLE ProcessId, OUT PEPROCESS *Process );
PSPTERPROC MyPspTerminateProcess ;
PEPROCESS hProcess;
//这个感觉特别蠢,所有的变量声明必须在最前面,饮用的函数也要在这里先定义一下

ULONG GetKernelModuleAddressByDllBaseName(PDRIVER_OBJECT driver)
{
    if(driver){
        LDR_DATA_TABLE_ENTRY* pListHead=NULL;
        LDR_DATA_TABLE_ENTRY* pCurrentNode=NULL;
        pListHead = driver->DriverSection;
        DbgPrint("%S\r\n", pListHead->BaseDllName.Buffer);
        pCurrentNode = (LDR_DATA_TABLE_ENTRY *)pListHead->InLoadOrderLinks.Flink;
        while(pCurrentNode!=pListHead){//当前节点不是入口节点时循环输出
            DbgPrint("%S\r\n", pCurrentNode->BaseDllName.Buffer);
            Res = RtlCompareUnicodeString(&pCurrentNode->BaseDllName, &targetModelName, TRUE); //对比模块名称是否为ntoskrnl.exe
            if (Res == 0)
            {
                KdPrint(("Find ntoskrnl.exe Success!\n"));
                DbgPrint("ntoskrnl Driver Object Addr:%x\n",pCurrentNode);
                targetDllBase = pCurrentNode->DllBase;
                targetSizeOfImage = pCurrentNode->SizeOfImage;
                DbgPrint("ntoskrnl dll Base:%x\n",targetDllBase);
                DbgPrint("ntoskrnl SizeOfImage:%x\n",targetSizeOfImage);
                for (i=targetDllBase;i<targetDllBase+targetSizeOfImage;i++){
                    if ((*((ULONG *)i)==code1_sp2)&&(*((ULONG *)(i+4))==code2_sp2)&&(*((ULONG *)(i+8))==code3_sp2)&&(*((ULONG*)(i+12))==code4_sp2)){
                        DbgPrint("Target Function adress is:%x\n",i);
                        targetFuncAddr = i;
                        return targetFuncAddr;
                    }
                }
                break;
            }
            pCurrentNode = (LDR_DATA_TABLE_ENTRY *)pCurrentNode->InLoadOrderLinks.Flink;
        }




    }
}


VOID DriverUnload(PDRIVER_OBJECT driver)
{
    DbgPrint("驱动程序已停止!\n\r");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT driver,PUNICODE_STRING reg_path)
{
    DbgPrint("驱动程序已加载!\n\r");

    targetFuncAddr = GetKernelModuleAddressByDllBaseName(driver);
    if (targetFuncAddr!=0){
        MyPspTerminateProcess = (PSPTERPROC)targetFuncAddr;
        if(PsLookupProcessByProcessId(2036,&hProcess)==STATUS_SUCCESS){
            MyPspTerminateProcess(hProcess,0);
        }   
    }
    driver->DriverUnload = DriverUnload;
    return STATUS_SUCCESS;
}

5.2绕过写拷贝

核心思想就是写拷贝首先检查PTE,如果PTE那里没有说这个区域是只读的那么这个地方是不会产生缺页异常的。

参考

  • 滴水中级班
  • https://bbs.pediy.com/thread-62450.htm inline hook未导出函数PspTerminateProcess
  • https://blog.csdn.net/cmdasm/article/details/9961411 PspTerminateProcess 结束冰刃进程
  • https://www.cnblogs.com/IMyLife/p/4826230.html 驱动读写进程内存R3,R0通信
  • http://www.doc88.com/p-3177537434502.html 再谈PspTerminateProcess及其简单实现
  • https://likte.me/Windows_Kernel_String_Operations.html Windows内核编程之字符串操作
  • https://bbs.pediy.com/thread-249708.htm 编写驱动加载程序