驱动内存加载[实战]

08 驱动内存加载[项目]

目标:实现一个加载器,让驱动不落地加载

DOS头和NT头

DOS 头

kd> dt _IMAGE_DOS_HEADER -r3
ntdll!_IMAGE_DOS_HEADER
   +0x000 e_magic          : Uint2B
   +0x002 e_cblp           : Uint2B
   +0x004 e_cp             : Uint2B
   +0x006 e_crlc           : Uint2B
   +0x008 e_cparhdr        : Uint2B
   +0x00a e_minalloc       : Uint2B
   +0x00c e_maxalloc       : Uint2B
   +0x00e e_ss             : Uint2B
   +0x010 e_sp             : Uint2B
   +0x012 e_csum           : Uint2B
   +0x014 e_ip             : Uint2B
   +0x016 e_cs             : Uint2B
   +0x018 e_lfarlc         : Uint2B
   +0x01a e_ovno           : Uint2B
   +0x01c e_res            : [4] Uint2B
   +0x024 e_oemid          : Uint2B
   +0x026 e_oeminfo        : Uint2B
   +0x028 e_res2           : [10] Uint2B
   +0x03c e_lfanew         : Int4B

获取DOS头

//获取dos头
//FileBuffer就是PE文件的缓冲区
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)FileBuffer;

NT头

kd> DT _IMAGE_NT_HEADERS -r3
ntdll!_IMAGE_NT_HEADERS
   +0x000 Signature        : Uint4B
   +0x004 FileHeader       : _IMAGE_FILE_HEADER
      +0x000 Machine          : Uint2B
      +0x002 NumberOfSections : Uint2B
      +0x004 TimeDateStamp    : Uint4B
      +0x008 PointerToSymbolTable : Uint4B
      +0x00c NumberOfSymbols  : Uint4B
      +0x010 SizeOfOptionalHeader : Uint2B
      +0x012 Characteristics  : Uint2B
   +0x018 OptionalHeader   : _IMAGE_OPTIONAL_HEADER
      +0x000 Magic            : Uint2B
      +0x002 MajorLinkerVersion : UChar
      +0x003 MinorLinkerVersion : UChar
      +0x004 SizeOfCode       : Uint4B
      +0x008 SizeOfInitializedData : Uint4B
      +0x00c SizeOfUninitializedData : Uint4B
      +0x010 AddressOfEntryPoint : Uint4B
      +0x014 BaseOfCode       : Uint4B
      +0x018 BaseOfData       : Uint4B
      +0x01c ImageBase        : Uint4B
      +0x020 SectionAlignment : Uint4B
      +0x024 FileAlignment    : Uint4B
      +0x028 MajorOperatingSystemVersion : Uint2B
      +0x02a MinorOperatingSystemVersion : Uint2B
      +0x02c MajorImageVersion : Uint2B
      +0x02e MinorImageVersion : Uint2B
      +0x030 MajorSubsystemVersion : Uint2B
      +0x032 MinorSubsystemVersion : Uint2B
      +0x034 Win32VersionValue : Uint4B
      +0x038 SizeOfImage      : Uint4B
      +0x03c SizeOfHeaders    : Uint4B
      +0x040 CheckSum         : Uint4B
      +0x044 Subsystem        : Uint2B
      +0x046 DllCharacteristics : Uint2B
      +0x048 SizeOfStackReserve : Uint4B
      +0x04c SizeOfStackCommit : Uint4B
      +0x050 SizeOfHeapReserve : Uint4B
      +0x054 SizeOfHeapCommit : Uint4B
      +0x058 LoaderFlags      : Uint4B
      +0x05c NumberOfRvaAndSizes : Uint4B
      +0x060 DataDirectory    : [16] _IMAGE_DATA_DIRECTORY
         +0x000 VirtualAddress   : Uint4B
         +0x004 Size             : Uint4B

根据DOS头获取NT头

NT头 = 文件的起始位置 + dos头的e_lfane

//获取NT头
//NT头 = 文件的起始位置 + dos头的e_lfanew
//NT头也可以通过`RtlImageNtHeader`获得,不过这个函数没有文档化,需要提前声PIMAGE_NT_HEADERS* RtlImageNtHeader(void* Base)
PIMAGE_NT_HEADERS pNts = (PIMAGE_NT_HEADERS)(FileBuffer + pDos->e_lfanew);

接下来我们按照理论里面的一步步修复

  1. 解析PE头,将对应的数据目录存放到内存相关的偏移地址
  2. 修复重定位,有可能出现随机基址,或者指定的地址被占用,这样的话基址就被修改了,所以要修复重定位
  3. 修复IAT(系统相关函数)导入表,比如在win7、win10都有一个函数,但是函数地址不一样
  4. 修复cookie
  5. 获取入口点
  6. call 入口点

1.修复PE头获取基地址

//先解析PE文件获取image(拉伸)
PUCHAR FileToImange(char* FileBuffer)
{
	if (!FileBuffer) return FALSE;
	//获取dos头
	PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)FileBuffer;
	//获取NT头
	//NT头 = 文件的起始位置 + dos头的e_lfanew
	//NT头也可以通过`RtlImageNtHeader`获得,不过这个函数没有文档化,需要提前声明PIMAGE_NT_HEADERS* RtlImageNtHeader(void* Base)
	PIMAGE_NT_HEADERS pNts = (PIMAGE_NT_HEADERS)(FileBuffer + pDos->e_lfanew);

	/*
	创建ImageBuffer
	*/
	//获取PE文件的Image大小
	ULONG SizeOfImage = pNts->OptionalHeader.SizeOfImage;
	//申请一块内存
	//使用非分页内存来保证可执行,使用PE的Image大小作为申请的大小
	PUCHAR ImageBuffer = ExAllocatePool(NonPagedPool, SizeOfImage);
	//将申请的内存清空
	memset(ImageBuffer, 0, SizeOfImage);


	//复制PE头,将pe文件复制到ImageBuffer,按照所有头部总和的大小复制
	memcpy(ImageBuffer, FileBuffer, pNts->OptionalHeader.SizeOfHeaders);
	//获取PE文件的节区数量
	ULONG NumberOfSections = pNts->FileHeader.NumberOfSections;

	//获得第一个节区
	PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNts);
	//复制节区
	for (ULONG i = 0; i < NumberOfSections; i++)
	{
		//将文件的PE头复制到image中
		memcpy(ImageBuffer + pSection->VirtualAddress, FileBuffer + pSection->PointerToRawData, pSection->SizeOfRawData);
		//获取下一个pSection
		pSection++;
	}
	//将存储拉伸后的PE头的ImageBuffer返回
	return ImageBuffer;
}

2.修复重定位信息

//修复重定位,传入拉伸后的ImageBuffer
BOOLEAN UpdataRelocation(char* ImageBuffer)
{
	if (!ImageBuffer) return FALSE;
	//获取PE的NT头,这里使用未文档化的函数进行获取
	PIMAGE_NT_HEADERS pNts = RtlImageNtHeader(ImageBuffer);
	//如果没有获取到,返回FALSE
	if (!pNts) return FALSE;
	//取目录表,目录表是第五个,也就是IMAGE_DIRECTORY_ENTRY_BASERELOC
	//因为是`PIMAGE_DATA_DIRECTORY`是个指针,所以后面的pNts要取地址
	PIMAGE_DATA_DIRECTORY iReplocation = &pNts->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
	//取重定位信息的位置,ImageBuffer + RVA(偏移)
	PIMAGE_BASE_RELOCATION pBase = (PIMAGE_BASE_RELOCATION)(ImageBuffer + iReplocation->VirtualAddress);

	//当这两个都存在的时候就证明还存在需要修复的重定向
	//当块的大小和虚拟地址都是空的退出
	while (pBase->SizeOfBlock && pBase->VirtualAddress)
	{

		//指向块的位置
		//PIMAGE_RELOC RelocationBlock = (PIMAGE_RELOC)(pBase->VirtualAddress + ImageBuffer + sizeof(IMAGE_BASE_RELOCATION));
		PIMAGE_RELOC RelocationBlock = (PIMAGE_RELOC)((PUCHAR)pBase + sizeof(IMAGE_BASE_RELOCATION));
		//计算需要修复的重定向的数目
		UINT32 NumberOfRelocation = (pBase->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(IMAGE_RELOC);
		//遍历每一个节区
		for (int i = 0; i < NumberOfRelocation; i++)
		{
			if (RelocationBlock[i].Type == IMAGE_REL_BASED_DIR64)
			{
				//64位
				PUINT64 Address = (PUINT64)((PUINT8)ImageBuffer + pBase->VirtualAddress + RelocationBlock[i].Offset);
				UINT64 Delta = *Address - pNts->OptionalHeader.ImageBase + (PUINT8)ImageBuffer;
				*Address = Delta;
			}
			else if (RelocationBlock[i].Type == IMAGE_REL_BASED_HIGHLOW)
			{
				//32位
				PUINT32 Address = (PUINT32)((PUINT8)ImageBuffer + pBase->VirtualAddress + (RelocationBlock[i].Offset));
				UINT32 Delta = *Address - pNts->OptionalHeader.ImageBase + (PUINT8)ImageBuffer;
				*Address = Delta;
			}

		}
		//循环完一次就到下一个模块
		pBase = (PIMAGE_BASE_RELOCATION)((PUCHAR)pBase + pBase->SizeOfBlock);

	}
	return TRUE;
}

3.修复IAT

修复IAT需要分成3

  1. 要有一个遍历模块,根据IAT表中的函数名查询其模块基地址
  2. 根据函数名字找到其对应模块中对应的函数地址
  3. 修复IAT

1.遍历模块

//根据提供的模块名,遍历内核模块,返回模块基址
//这个返回值(ULONG_PTR)有个好处,就是当编译为32位的时候他是32,64位是64
//moduleName示例:”Win.dll“
ULONG_PTR QueryModule(PUCHAR moduleName, ULONG_PTR* moduleSize)
{
	if (moduleName == NULL) return 0;
	RTL_PROCESS_MODULES rtlModule = { 0 };
	PRTL_PROCESS_MODULES SystemModules = &rtlModule;
	//是否开辟内存了,默认为没开辟内存
	BOOLEAN isAlloc = FALSE;
	//测量长度,确认一下需要多少内存来存放我们查询出来的结果
	ULONG* retLength = 0;
	//查询,这一次查询主要是为了确认我们需要申请多少内存存放结果
	NTSTATUS status = ZwQuerySystemInformation(SystemModuleInformation, SystemModules, sizeof(RTL_PROCESS_MODULES), &retLength);
	//如果返回值是大小不匹配,也就是代表着我们给的结构体小了,那么就会在retLength参数中返回实际需要的大小
	//分配实际大小的内存,也就是上面最终返回的retLength
	if (status == STATUS_INFO_LENGTH_MISMATCH)
	{
		//申请内存,不需要执行,大小拿返回出来的正确大小+结构大小
		SystemModules = ExAllocatePool(PagedPool, retLength + sizeof(RTL_PROCESS_MODULES));
		//如果申请失败
		if (!SystemModules) return 0;
		//申请成功的话进行初始化
		memset(SystemModules, 0, retLength + sizeof(RTL_PROCESS_MODULES));

		//再查一次,这回提供正确的大小(模块总长度)
		NTSTATUS status = ZwQuerySystemInformation(SystemModuleInformation, SystemModules, retLength + sizeof(RTL_PROCESS_MODULES), &retLength);
		//如果还不成功,那就释放内存
		if (!NT_SUCCESS(status))
		{
			ExFreePool(SystemModules);
			return 0;
		}
		//证明内存申请了
		isAlloc = TRUE;
	}


	/*
	由于我们接下来会有比较,但是我们传进来的不是完整路径只有一个名字的模块名字,和遍历获得的模块全路径作比较
	所以我们只能通过是否包含来确定是否一致,而这要求2者都是大写
	但是由于我们传进来的`moduleName`是一个常量,正常情况下无法修改,所以这里采用创建内存然后拷贝的方法修改
	*/
	//存放我们转换成大写的传进来的模块名
	PUCHAR KernelModuleName = NULL;

	//用来存放找到的模块的基址
	ULONG_PTR moduleBase = 0;

	//这里使用do-while结构主要是为了方便当我们想获取ntoskrn.exe模块的时候可以获取到后直接break出去然后释放内存
	do
	{
		//在32位的29912分页下,会有两个内核,也就是会有两个`ntoskrn.exe`内核模块
		if (_stricmp(moduleName, "ntoskrnl.exe") == 0 || _stricmp(moduleName, "ntkrnlpa.exe") == 0)
		{
			//将当前第一个模块存储在ModuleInfo中,内核模块永远是第一个
			PRTL_PROCESS_MODULE_INFORMATION ModuleInfo = &SystemModules->Modules[0];
			//根据ModuleInfo获取模块基地址
			moduleBase = ModuleInfo->ImageBase;
			//如果我们传进来的moduleSize不为空,那么也将size返回
			if (moduleSize) *moduleSize = ModuleInfo->ImageSize;
			break;
		}

		//我们传进来的ModuleName转大写后的结果。
		//复制一块内存,用来将传进来的常量`FullPathName`转大写,+1会正好有一个0结尾
		KernelModuleName = ExAllocatePool(PagedPool, strlen(moduleName) + 1);
		//初始化内存
		memset(KernelModuleName, 0, strlen(moduleName) + 1);
		//复制,将我们传进来的常量`moduleName`拷贝到我们申请出来的KernelModuleName里面
		memcpy(KernelModuleName, moduleName, strlen(moduleName));
		//将名字转大写
		_strupr(KernelModuleName);
		//根据模块的数量遍历
		for (int i = 0; i < SystemModules->NumberOfModules; i++)
		{
			//将当前遍历到的模块存储在ModuleInfo中
			PRTL_PROCESS_MODULE_INFORMATION ModuleInfo = &SystemModules->Modules[i];
			//做测试,打印所有模块
			//DbgBreakPoint();
			DbgPrint("baseName = %s;FullPath = %s\r\n", ModuleInfo->FullPathName + ModuleInfo->OffsetToFileName, ModuleInfo->FullPathName);
			//将FullPathName转换为大写,这里获得的是全路径
			PUCHAR pathName = _strupr(ModuleInfo->FullPathName);
			//如果找到了(我们传进来的不完整名字被全路径包含了)
			if (strstr(pathName, KernelModuleName))
			{
				moduleBase = ModuleInfo->ImageBase;
				//如果我们传进来的moduleSize不为空,那么也将size返回
				if (moduleSize) *moduleSize = ModuleInfo->ImageSize;
				break;
			}
		}
	} while (0);

	//如果不为空,那么释放
	if (KernelModuleName)
	{
		ExFreePool(KernelModuleName);
	}
	//根据我们定的标志,判断我们是否申请了内存,如果申请了,进行释放
	if (isAlloc)
	{
		ExFreePool(SystemModules);
	}
	//将我们得到的指定的模块基址返回
	return moduleBase;
}

调用

//获取指定内核模块的基地址
ULONG_PTR module = QueryModule("ntoskrnl.exe", NULL);

2.根据函数名字找到其对应模块中对应的函数地址

//根据函数名找到对应的地址,在win7下RtlFindExportedRoutineByName不可以使用,所以需要重写
ULONG64 ExportTableFuncByName(char* pData, char* funcName)
{
	PIMAGE_DOS_HEADER pHead = (PIMAGE_DOS_HEADER)pData;
	PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pData + pHead->e_lfanew);
	int numberRvaAndSize = pNt->OptionalHeader.NumberOfRvaAndSizes;
	PIMAGE_DATA_DIRECTORY pDir = (PIMAGE_DATA_DIRECTORY)&pNt->OptionalHeader.DataDirectory[0];

	PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(pData + pDir->VirtualAddress);

	ULONG64 funcAddr = 0;
	for (int i = 0; i < pExport->NumberOfNames; i++)
	{
		int* funcAddress = pData + pExport->AddressOfFunctions;
		int* names = pData + pExport->AddressOfNames;
		short* fh = pData + pExport->AddressOfNameOrdinals;
		int index = -1;
		char* name = pData + names[i];
		if (strcmp(name, funcName) == 0)
		{
			index = fh[i];
		}
		if (index != -1)
		{
			funcAddr = pData + funcAddress[index];
			break;
		}
	}
	if (!funcAddr)
	{
		KdPrint(("没有找到函数%s\r\n", funcName));
	}
	else
	{
		KdPrint(("找到函数%s addr %p\r\n", funcName, funcAddr));
	}
	return funcAddr;
}

3.修复IAT

//原理是获取IAT表中的每一个导入的函数所属的模块名字
//修复IAT表
BOOLEAN UpdataIAT(char* ImageBuffer)
{
	if (!ImageBuffer) return FALSE;
	//获取PE的NT头,这里使用未文档化的函数进行获取
	PIMAGE_NT_HEADERS pNts = RtlImageNtHeader(ImageBuffer);
	//如果没有获取到,返回FALSE
	if (!pNts) return FALSE;
	//取IAT表,IAT表是第一个
	//因为是`PIMAGE_DATA_DIRECTORY`是个指针,所以后面的pNts要取地址
	PIMAGE_DATA_DIRECTORY pImportDir = &pNts->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
	//取IAT位置,ImageBuffer + RVA(偏移)
	PIMAGE_IMPORT_DESCRIPTOR import = (PIMAGE_IMPORT_DESCRIPTOR)(ImageBuffer + pImportDir->VirtualAddress);
	//提供标志
	BOOLEAN isSucess = TRUE;

	//循环获取模块的导入表信息
	//如果这个模块已经被修复过了,那么就没有import->Name等信息
	for (; import->Name; import++)
	{
		//获取IAT表中使用的函数的所属模块名,ImageBuffer + offset(import->name)
		PUCHAR libName = (ImageBuffer + import->Name);
		//DbgPrint("%s\r\n", libName);
		//根据模块名查询其基地址
		ULONG_PTR base = QueryModule(libName, NULL);
		//如果没找到模块对应的基地址的话
		if (!base)
		{
			isSucess = FALSE;
			break;
		}
		//根据前面获得的IAT表中的函数所对应的模块基址`base`

		//根据`ImageBuffer+offset`的方式获取Buffer中的IAT中所对应的函数的名字和地址(最开始写在缓冲区的值)
		PIMAGE_THUNK_DATA pThuckName = (PIMAGE_THUNK_DATA)(ImageBuffer + import->OriginalFirstThunk);
		PIMAGE_THUNK_DATA pThuckFunc = (PIMAGE_THUNK_DATA)(ImageBuffer + import->FirstThunk);

		//循环直到不存在函数名字
		for (; pThuckName->u1.ForwarderString; ++pThuckName, ++pThuckFunc)
		{
			//根据imageBuffer+offset的形式获取函数名字
			PIMAGE_IMPORT_BY_NAME FuncName = (PIMAGE_IMPORT_BY_NAME)(ImageBuffer + pThuckName->u1.AddressOfData);
			//根据函数名字找到对应的地址
			ULONG_PTR func = ExportTableFuncByName((char*)base, FuncName->Name);
			if (func)
			{
				//修复函数地址
				pThuckFunc->u1.Function = (ULONG_PTR)func;
			}
			else
			{
				isSucess = FALSE;
				break;
			}
		}
		if (!isSucess) break;
	}
	return isSucess;
}

修复IAT的前2步我放到tools.c里面了,下面贴一下tools的完整代码

tools.c

#include "tools.h"

//根据提供的模块名,遍历内核模块,返回模块基址
//这个返回值(ULONG_PTR)有个好处,就是当编译为32位的时候他是32,64位是64
//moduleName示例:”Win.dll“
ULONG_PTR QueryModule(PUCHAR moduleName, ULONG_PTR* moduleSize)
{
	if (moduleName == NULL) return 0;
	RTL_PROCESS_MODULES rtlModule = { 0 };
	PRTL_PROCESS_MODULES SystemModules = &rtlModule;
	//是否开辟内存了,默认为没开辟内存
	BOOLEAN isAlloc = FALSE;
	//测量长度,确认一下需要多少内存来存放我们查询出来的结果
	ULONG* retLength = 0;
	//查询,这一次查询主要是为了确认我们需要申请多少内存存放结果
	NTSTATUS status = ZwQuerySystemInformation(SystemModuleInformation, SystemModules, sizeof(RTL_PROCESS_MODULES), &retLength);
	//如果返回值是大小不匹配,也就是代表着我们给的结构体小了,那么就会在retLength参数中返回实际需要的大小
	//分配实际大小的内存,也就是上面最终返回的retLength
	if (status == STATUS_INFO_LENGTH_MISMATCH)
	{
		//申请内存,不需要执行,大小拿返回出来的正确大小+结构大小
		SystemModules = ExAllocatePool(PagedPool, retLength + sizeof(RTL_PROCESS_MODULES));
		//如果申请失败
		if (!SystemModules) return 0;
		//申请成功的话进行初始化
		memset(SystemModules, 0, retLength + sizeof(RTL_PROCESS_MODULES));

		//再查一次,这回提供正确的大小(模块总长度)
		NTSTATUS status = ZwQuerySystemInformation(SystemModuleInformation, SystemModules, retLength + sizeof(RTL_PROCESS_MODULES), &retLength);
		//如果还不成功,那就释放内存
		if (!NT_SUCCESS(status))
		{
			ExFreePool(SystemModules);
			return 0;
		}
		//证明内存申请了
		isAlloc = TRUE;
	}


	/*
	由于我们接下来会有比较,但是我们传进来的不是完整路径只有一个名字的模块名字,和遍历获得的模块全路径作比较
	所以我们只能通过是否包含来确定是否一致,而这要求2者都是大写
	但是由于我们传进来的`moduleName`是一个常量,正常情况下无法修改,所以这里采用创建内存然后拷贝的方法修改
	*/
	//存放我们转换成大写的传进来的模块名
	PUCHAR KernelModuleName = NULL;

	//用来存放找到的模块的基址
	ULONG_PTR moduleBase = 0;

	//这里使用do-while结构主要是为了方便当我们想获取ntoskrn.exe模块的时候可以获取到后直接break出去然后释放内存
	do
	{
		//在32位的29912分页下,会有两个内核,也就是会有两个`ntoskrn.exe`内核模块
		if (_stricmp(moduleName, "ntoskrnl.exe") == 0 || _stricmp(moduleName, "ntkrnlpa.exe") == 0)
		{
			//将当前第一个模块存储在ModuleInfo中,内核模块永远是第一个
			PRTL_PROCESS_MODULE_INFORMATION ModuleInfo = &SystemModules->Modules[0];
			//根据ModuleInfo获取模块基地址
			moduleBase = ModuleInfo->ImageBase;
			//如果我们传进来的moduleSize不为空,那么也将size返回
			if (moduleSize) *moduleSize = ModuleInfo->ImageSize;
			break;
		}

		//我们传进来的ModuleName转大写后的结果。
		//复制一块内存,用来将传进来的常量`FullPathName`转大写,+1会正好有一个0结尾
		KernelModuleName = ExAllocatePool(PagedPool, strlen(moduleName) + 1);
		//初始化内存
		memset(KernelModuleName, 0, strlen(moduleName) + 1);
		//复制,将我们传进来的常量`moduleName`拷贝到我们申请出来的KernelModuleName里面
		memcpy(KernelModuleName, moduleName, strlen(moduleName));
		//将名字转大写
		_strupr(KernelModuleName);
		//根据模块的数量遍历
		for (int i = 0; i < SystemModules->NumberOfModules; i++)
		{
			//将当前遍历到的模块存储在ModuleInfo中
			PRTL_PROCESS_MODULE_INFORMATION ModuleInfo = &SystemModules->Modules[i];
			//做测试,打印所有模块
			//DbgBreakPoint();
			DbgPrint("baseName = %s;FullPath = %s\r\n", ModuleInfo->FullPathName + ModuleInfo->OffsetToFileName, ModuleInfo->FullPathName);
			//将FullPathName转换为大写,这里获得的是全路径
			PUCHAR pathName = _strupr(ModuleInfo->FullPathName);
			//如果找到了(我们传进来的不完整名字被全路径包含了)
			if (strstr(pathName, KernelModuleName))
			{
				moduleBase = ModuleInfo->ImageBase;
				//如果我们传进来的moduleSize不为空,那么也将size返回
				if (moduleSize) *moduleSize = ModuleInfo->ImageSize;
				break;
			}
		}
	} while (0);





	//如果不为空,那么释放
	if (KernelModuleName)
	{
		ExFreePool(KernelModuleName);
	}
	//根据我们定的标志,判断我们是否申请了内存,如果申请了,进行释放
	if (isAlloc)
	{
		ExFreePool(SystemModules);
	}
	//将我们得到的指定的模块基址返回
	return moduleBase;
}


//根据函数名找到对应的地址,在win7下RtlFindExportedRoutineByName不可以使用,所以需要重写
ULONG64 ExportTableFuncByName(char* pData, char* funcName)
{
	PIMAGE_DOS_HEADER pHead = (PIMAGE_DOS_HEADER)pData;
	PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pData + pHead->e_lfanew);
	int numberRvaAndSize = pNt->OptionalHeader.NumberOfRvaAndSizes;
	PIMAGE_DATA_DIRECTORY pDir = (PIMAGE_DATA_DIRECTORY)&pNt->OptionalHeader.DataDirectory[0];

	PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(pData + pDir->VirtualAddress);

	ULONG64 funcAddr = 0;
	for (int i = 0; i < pExport->NumberOfNames; i++)
	{
		int* funcAddress = pData + pExport->AddressOfFunctions;
		int* names = pData + pExport->AddressOfNames;
		short* fh = pData + pExport->AddressOfNameOrdinals;
		int index = -1;
		char* name = pData + names[i];
		if (strcmp(name, funcName) == 0)
		{
			index = fh[i];
		}
		if (index != -1)
		{
			funcAddr = pData + funcAddress[index];
			break;
		}
	}
	if (!funcAddr)
	{
		KdPrint(("没有找到函数%s\r\n", funcName));
	}
	else
	{
		KdPrint(("找到函数%s addr %p\r\n", funcName, funcAddr));
	}
	return funcAddr;
}

tools.h

#pragma once
#include <ntifs.h>
#include <ntimage.h>



typedef enum _SYSTEM_INFORMATION_CLASS {
	SystemBasicInformation,
	SystemProcessorInformation,             // obsolete...delete
	SystemPerformanceInformation,
	SystemTimeOfDayInformation,
	SystemPathInformation,
	SystemProcessInformation,
	SystemCallCountInformation,
	SystemDeviceInformation,
	SystemProcessorPerformanceInformation,
	SystemFlagsInformation,
	SystemCallTimeInformation,
	SystemModuleInformation,
	SystemLocksInformation,
	SystemStackTraceInformation,
	SystemPagedPoolInformation,
	SystemNonPagedPoolInformation,
	SystemHandleInformation,
	SystemObjectInformation,
	SystemPageFileInformation,
	SystemVdmInstemulInformation,
	SystemVdmBopInformation,
	SystemFileCacheInformation,
	SystemPoolTagInformation,
	SystemInterruptInformation,
	SystemDpcBehaviorInformation,
	SystemFullMemoryInformation,
	SystemLoadGdiDriverInformation,
	SystemUnloadGdiDriverInformation,
	SystemTimeAdjustmentInformation,
	SystemSummaryMemoryInformation,
	SystemMirrorMemoryInformation,
	SystemPerformanceTraceInformation,
	SystemObsolete0,
	SystemExceptionInformation,
	SystemCrashDumpStateInformation,
	SystemKernelDebuggerInformation,
	SystemContextSwitchInformation,
	SystemRegistryQuotaInformation,
	SystemExtendServiceTableInformation,
	SystemPrioritySeperation,
	SystemVerifierAddDriverInformation,
	SystemVerifierRemoveDriverInformation,
	SystemProcessorIdleInformation,
	SystemLegacyDriverInformation,
	SystemCurrentTimeZoneInformation,
	SystemLookasideInformation,
	SystemTimeSlipNotification,
	SystemSessionCreate,
	SystemSessionDetach,
	SystemSessionInformation,
	SystemRangeStartInformation,
	SystemVerifierInformation,
	SystemVerifierThunkExtend,
	SystemSessionProcessInformation,
	SystemLoadGdiDriverInSystemSpace,
	SystemNumaProcessorMap,
	SystemPrefetcherInformation,
	SystemExtendedProcessInformation,
	SystemRecommendedSharedDataAlignment,
	SystemComPlusPackage,
	SystemNumaAvailableMemory,
	SystemProcessorPowerInformation,
	SystemEmulationBasicInformation,
	SystemEmulationProcessorInformation,
	SystemExtendedHandleInformation,
	SystemLostDelayedWriteInformation,
	SystemBigPoolInformation,
	SystemSessionPoolTagInformation,
	SystemSessionMappedViewInformation,
	SystemHotpatchInformation,
	SystemObjectSecurityMode,
	SystemWatchdogTimerHandler,
	SystemWatchdogTimerInformation,
	SystemLogicalProcessorInformation,
	SystemWow64SharedInformation,
	SystemRegisterFirmwareTableInformationHandler,
	SystemFirmwareTableInformation,
	SystemModuleInformationEx,
	SystemVerifierTriageInformation,
	SystemSuperfetchInformation,
	SystemMemoryListInformation,
	SystemFileCacheInformationEx,
	MaxSystemInfoClass  // MaxSystemInfoClass should always be the last enum
} SYSTEM_INFORMATION_CLASS;


typedef struct _RTL_PROCESS_MODULE_INFORMATION {
	HANDLE Section;                 //节区
	PVOID MappedBase;
	PVOID ImageBase;//起始地址
	ULONG ImageSize;//模块大小
	ULONG Flags;
	USHORT LoadOrderIndex;
	USHORT InitOrderIndex;
	USHORT LoadCount;//这个模块被加载过多少次
	USHORT OffsetToFileName;//名字的偏移
	UCHAR  FullPathName[256];//完整路径
} RTL_PROCESS_MODULE_INFORMATION, * PRTL_PROCESS_MODULE_INFORMATION;

typedef struct _RTL_PROCESS_MODULES {
	ULONG NumberOfModules;//查出来的模块的数量
	RTL_PROCESS_MODULE_INFORMATION Modules[1];
} RTL_PROCESS_MODULES, * PRTL_PROCESS_MODULES;

//未文档化的函数`ZwQuerySystemInformation`,`SYSTEM_INFORMATION_CLASS`枚举在.h声明里面
NTSTATUS ZwQuerySystemInformation(SYSTEM_INFORMATION_CLASS SystemInformationClass, void* SystemInformation, ULONG SystemInformationLength, ULONG* ReturnLength);


ULONG_PTR QueryModule(PUCHAR moduleName, ULONG_PTR* moduleSize);

//未文档化的函数需要提前定义
PIMAGE_NT_HEADERS* RtlImageNtHeader(void* Base);

//根据函数名找到对应的地址,在win7下RtlFindExportedRoutineByName不可以使用,所以需要重写
ULONG64 ExportTableFuncByName(char* pData, char* funcName);

4.修复cookie

//修复cookie
VOID UpdataCookie(char* ImageBuffer)
{
	//DbgBreakPoint();
	if (!ImageBuffer) return FALSE;
	//获取PE的NT头,这里使用未文档化的函数进行获取
	PIMAGE_NT_HEADERS pNts = RtlImageNtHeader(ImageBuffer);
	//如果没有获取到,返回FALSE
	if (!pNts) return FALSE;
	//第十号(IMAGE_DIRECTORY_ENTRY_BASERELOC)就是cookie
	PIMAGE_DATA_DIRECTORY pConfigDir = &pNts->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG];
	//imagebuffer +offset  = config位置
	PIMAGE_LOAD_CONFIG_DIRECTORY config = (PIMAGE_LOAD_CONFIG_DIRECTORY)(ImageBuffer + pConfigDir->VirtualAddress);
	//COOKIE 随便一个值就行只要不是危险值,具体就看理论篇有提到cookie的逻辑
	*(PULONG_PTR)(config->SecurityCookie) += 10;
}

5.获取入口点+call 入口点

//加载驱动
BOOLEAN LoadDriver(PUCHAR FileBuffer)
{
	//1.先拉伸,修复PE头,获取PE基址
	PUCHAR imageBase = FileToImange(FileBuffer);
	//拉伸失败返回FALSE
	if (!imageBase) return FALSE;
	//标志位
	BOOLEAN isSucess = FALSE;
	do
	{
		//2.根据PE基址修复重定位
		isSucess = UpdataRelocation(imageBase);
		//失败释放内存
		if (!isSucess) break;

		//3.根据PE基址修复IAT表
		isSucess = UpdataIAT(imageBase);
		//失败释放内存
		if (!isSucess) break;

		//4.修复cookie(Win732位下可以不修)
		UpdataCookie(imageBase);

		//call 入口点
		//获取PE的NT头,这里使用未文档化的函数进行获取
		PIMAGE_NT_HEADERS pNts = RtlImageNtHeader(imageBase);
		//5.获取入口点
		ULONG_PTR entry = pNts->OptionalHeader.AddressOfEntryPoint;
		//定义一个函数入口点的位置(设置这个函数的地址)
		DriverEntrypProc EntryPoint = (DriverEntrypProc)(imageBase + entry);
		//6.call 入口点
		//DbgBreakPoint();
		NTSTATUS status = EntryPoint(NULL, NULL);
		if (!NT_SUCCESS(status))
		{
			isSucess = FALSE;
			break;
		}
		//将PE头去掉
		memset(imageBase, 0, PAGE_SIZE);
	} while (0);

	if (!isSucess)
	{
		ExFreePool(imageBase);
		return isSucess;
	}
	return isSucess;
}

完整代码

tools.c

#include "tools.h"

//根据提供的模块名,遍历内核模块,返回模块基址
//这个返回值(ULONG_PTR)有个好处,就是当编译为32位的时候他是32,64位是64
//moduleName示例:”Win.dll“
ULONG_PTR QueryModule(PUCHAR moduleName, ULONG_PTR* moduleSize)
{
	if (moduleName == NULL) return 0;
	RTL_PROCESS_MODULES rtlModule = { 0 };
	PRTL_PROCESS_MODULES SystemModules = &rtlModule;
	//是否开辟内存了,默认为没开辟内存
	BOOLEAN isAlloc = FALSE;
	//测量长度,确认一下需要多少内存来存放我们查询出来的结果
	ULONG* retLength = 0;
	//查询,这一次查询主要是为了确认我们需要申请多少内存存放结果
	NTSTATUS status = ZwQuerySystemInformation(SystemModuleInformation, SystemModules, sizeof(RTL_PROCESS_MODULES), &retLength);
	//如果返回值是大小不匹配,也就是代表着我们给的结构体小了,那么就会在retLength参数中返回实际需要的大小
	//分配实际大小的内存,也就是上面最终返回的retLength
	if (status == STATUS_INFO_LENGTH_MISMATCH)
	{
		//申请内存,不需要执行,大小拿返回出来的正确大小+结构大小
		SystemModules = ExAllocatePool(PagedPool, retLength + sizeof(RTL_PROCESS_MODULES));
		//如果申请失败
		if (!SystemModules) return 0;
		//申请成功的话进行初始化
		memset(SystemModules, 0, retLength + sizeof(RTL_PROCESS_MODULES));

		//再查一次,这回提供正确的大小(模块总长度)
		NTSTATUS status = ZwQuerySystemInformation(SystemModuleInformation, SystemModules, retLength + sizeof(RTL_PROCESS_MODULES), &retLength);
		//如果还不成功,那就释放内存
		if (!NT_SUCCESS(status))
		{
			ExFreePool(SystemModules);
			return 0;
		}
		//证明内存申请了
		isAlloc = TRUE;
	}


	/*
	由于我们接下来会有比较,但是我们传进来的不是完整路径只有一个名字的模块名字,和遍历获得的模块全路径作比较
	所以我们只能通过是否包含来确定是否一致,而这要求2者都是大写
	但是由于我们传进来的`moduleName`是一个常量,正常情况下无法修改,所以这里采用创建内存然后拷贝的方法修改
	*/
	//存放我们转换成大写的传进来的模块名
	PUCHAR KernelModuleName = NULL;

	//用来存放找到的模块的基址
	ULONG_PTR moduleBase = 0;

	//这里使用do-while结构主要是为了方便当我们想获取ntoskrn.exe模块的时候可以获取到后直接break出去然后释放内存
	do
	{
		//在32位的29912分页下,会有两个内核,也就是会有两个`ntoskrn.exe`内核模块
		if (_stricmp(moduleName, "ntoskrnl.exe") == 0 || _stricmp(moduleName, "ntkrnlpa.exe") == 0)
		{
			//将当前第一个模块存储在ModuleInfo中,内核模块永远是第一个
			PRTL_PROCESS_MODULE_INFORMATION ModuleInfo = &SystemModules->Modules[0];
			//根据ModuleInfo获取模块基地址
			moduleBase = ModuleInfo->ImageBase;
			//如果我们传进来的moduleSize不为空,那么也将size返回
			if (moduleSize) *moduleSize = ModuleInfo->ImageSize;
			break;
		}

		//我们传进来的ModuleName转大写后的结果。
		//复制一块内存,用来将传进来的常量`FullPathName`转大写,+1会正好有一个0结尾
		KernelModuleName = ExAllocatePool(PagedPool, strlen(moduleName) + 1);
		//初始化内存
		memset(KernelModuleName, 0, strlen(moduleName) + 1);
		//复制,将我们传进来的常量`moduleName`拷贝到我们申请出来的KernelModuleName里面
		memcpy(KernelModuleName, moduleName, strlen(moduleName));
		//将名字转大写
		_strupr(KernelModuleName);
		//根据模块的数量遍历
		for (int i = 0; i < SystemModules->NumberOfModules; i++)
		{
			//将当前遍历到的模块存储在ModuleInfo中
			PRTL_PROCESS_MODULE_INFORMATION ModuleInfo = &SystemModules->Modules[i];
			//做测试,打印所有模块
			//DbgBreakPoint();
			DbgPrint("baseName = %s;FullPath = %s\r\n", ModuleInfo->FullPathName + ModuleInfo->OffsetToFileName, ModuleInfo->FullPathName);
			//将FullPathName转换为大写,这里获得的是全路径
			PUCHAR pathName = _strupr(ModuleInfo->FullPathName);
			//如果找到了(我们传进来的不完整名字被全路径包含了)
			if (strstr(pathName, KernelModuleName))
			{
				moduleBase = ModuleInfo->ImageBase;
				//如果我们传进来的moduleSize不为空,那么也将size返回
				if (moduleSize) *moduleSize = ModuleInfo->ImageSize;
				break;
			}
		}
	} while (0);





	//如果不为空,那么释放
	if (KernelModuleName)
	{
		ExFreePool(KernelModuleName);
	}
	//根据我们定的标志,判断我们是否申请了内存,如果申请了,进行释放
	if (isAlloc)
	{
		ExFreePool(SystemModules);
	}
	//将我们得到的指定的模块基址返回
	return moduleBase;
}


//根据函数名找到对应的地址,在win7下RtlFindExportedRoutineByName不可以使用,所以需要重写
ULONG64 ExportTableFuncByName(char* pData, char* funcName)
{
	PIMAGE_DOS_HEADER pHead = (PIMAGE_DOS_HEADER)pData;
	PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pData + pHead->e_lfanew);
	int numberRvaAndSize = pNt->OptionalHeader.NumberOfRvaAndSizes;
	PIMAGE_DATA_DIRECTORY pDir = (PIMAGE_DATA_DIRECTORY)&pNt->OptionalHeader.DataDirectory[0];

	PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(pData + pDir->VirtualAddress);

	ULONG64 funcAddr = 0;
	for (int i = 0; i < pExport->NumberOfNames; i++)
	{
		int* funcAddress = pData + pExport->AddressOfFunctions;
		int* names = pData + pExport->AddressOfNames;
		short* fh = pData + pExport->AddressOfNameOrdinals;
		int index = -1;
		char* name = pData + names[i];
		if (strcmp(name, funcName) == 0)
		{
			index = fh[i];
		}
		if (index != -1)
		{
			funcAddr = pData + funcAddress[index];
			break;
		}
	}
	if (!funcAddr)
	{
		KdPrint(("没有找到函数%s\r\n", funcName));
	}
	else
	{
		KdPrint(("找到函数%s addr %p\r\n", funcName, funcAddr));
	}
	return funcAddr;
}

tools.h

#pragma once
#include <ntifs.h>
#include <ntimage.h>



typedef enum _SYSTEM_INFORMATION_CLASS {
	SystemBasicInformation,
	SystemProcessorInformation,             // obsolete...delete
	SystemPerformanceInformation,
	SystemTimeOfDayInformation,
	SystemPathInformation,
	SystemProcessInformation,
	SystemCallCountInformation,
	SystemDeviceInformation,
	SystemProcessorPerformanceInformation,
	SystemFlagsInformation,
	SystemCallTimeInformation,
	SystemModuleInformation,
	SystemLocksInformation,
	SystemStackTraceInformation,
	SystemPagedPoolInformation,
	SystemNonPagedPoolInformation,
	SystemHandleInformation,
	SystemObjectInformation,
	SystemPageFileInformation,
	SystemVdmInstemulInformation,
	SystemVdmBopInformation,
	SystemFileCacheInformation,
	SystemPoolTagInformation,
	SystemInterruptInformation,
	SystemDpcBehaviorInformation,
	SystemFullMemoryInformation,
	SystemLoadGdiDriverInformation,
	SystemUnloadGdiDriverInformation,
	SystemTimeAdjustmentInformation,
	SystemSummaryMemoryInformation,
	SystemMirrorMemoryInformation,
	SystemPerformanceTraceInformation,
	SystemObsolete0,
	SystemExceptionInformation,
	SystemCrashDumpStateInformation,
	SystemKernelDebuggerInformation,
	SystemContextSwitchInformation,
	SystemRegistryQuotaInformation,
	SystemExtendServiceTableInformation,
	SystemPrioritySeperation,
	SystemVerifierAddDriverInformation,
	SystemVerifierRemoveDriverInformation,
	SystemProcessorIdleInformation,
	SystemLegacyDriverInformation,
	SystemCurrentTimeZoneInformation,
	SystemLookasideInformation,
	SystemTimeSlipNotification,
	SystemSessionCreate,
	SystemSessionDetach,
	SystemSessionInformation,
	SystemRangeStartInformation,
	SystemVerifierInformation,
	SystemVerifierThunkExtend,
	SystemSessionProcessInformation,
	SystemLoadGdiDriverInSystemSpace,
	SystemNumaProcessorMap,
	SystemPrefetcherInformation,
	SystemExtendedProcessInformation,
	SystemRecommendedSharedDataAlignment,
	SystemComPlusPackage,
	SystemNumaAvailableMemory,
	SystemProcessorPowerInformation,
	SystemEmulationBasicInformation,
	SystemEmulationProcessorInformation,
	SystemExtendedHandleInformation,
	SystemLostDelayedWriteInformation,
	SystemBigPoolInformation,
	SystemSessionPoolTagInformation,
	SystemSessionMappedViewInformation,
	SystemHotpatchInformation,
	SystemObjectSecurityMode,
	SystemWatchdogTimerHandler,
	SystemWatchdogTimerInformation,
	SystemLogicalProcessorInformation,
	SystemWow64SharedInformation,
	SystemRegisterFirmwareTableInformationHandler,
	SystemFirmwareTableInformation,
	SystemModuleInformationEx,
	SystemVerifierTriageInformation,
	SystemSuperfetchInformation,
	SystemMemoryListInformation,
	SystemFileCacheInformationEx,
	MaxSystemInfoClass  // MaxSystemInfoClass should always be the last enum
} SYSTEM_INFORMATION_CLASS;


typedef struct _RTL_PROCESS_MODULE_INFORMATION {
	HANDLE Section;                 //节区
	PVOID MappedBase;
	PVOID ImageBase;//起始地址
	ULONG ImageSize;//模块大小
	ULONG Flags;
	USHORT LoadOrderIndex;
	USHORT InitOrderIndex;
	USHORT LoadCount;//这个模块被加载过多少次
	USHORT OffsetToFileName;//名字的偏移
	UCHAR  FullPathName[256];//完整路径
} RTL_PROCESS_MODULE_INFORMATION, * PRTL_PROCESS_MODULE_INFORMATION;

typedef struct _RTL_PROCESS_MODULES {
	ULONG NumberOfModules;//查出来的模块的数量
	RTL_PROCESS_MODULE_INFORMATION Modules[1];
} RTL_PROCESS_MODULES, * PRTL_PROCESS_MODULES;

//未文档化的函数`ZwQuerySystemInformation`,`SYSTEM_INFORMATION_CLASS`枚举在.h声明里面
NTSTATUS ZwQuerySystemInformation(SYSTEM_INFORMATION_CLASS SystemInformationClass, void* SystemInformation, ULONG SystemInformationLength, ULONG* ReturnLength);


ULONG_PTR QueryModule(PUCHAR moduleName, ULONG_PTR* moduleSize);

//未文档化的函数需要提前定义
PIMAGE_NT_HEADERS* RtlImageNtHeader(void* Base);

//根据函数名找到对应的地址,在win7下RtlFindExportedRoutineByName不可以使用,所以需要重写
ULONG64 ExportTableFuncByName(char* pData, char* funcName);

Loader.c

#include "Loader.h"
//PE相关的内容
#include <ntimage.h>
//提供我们自己写的QueryModule函数
#include "tools.h"

typedef NTSTATUS(NTAPI* DriverEntrypProc)(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg);

//定义一个未导出结构
typedef struct _IMAGE_RELOC
{
	UINT16 Offset : 12;//低12位--偏移
	UINT16 Type : 4;//高4位--类型
}IMAGE_RELOC, * PIMAGE_RELOC;


//先解析PE文件获取image(拉伸)
PUCHAR FileToImange(char* FileBuffer)
{
	if (!FileBuffer) return FALSE;
	//获取dos头
	PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)FileBuffer;
	//获取NT头
	//NT头 = 文件的起始位置 + dos头的e_lfanew
	//NT头也可以通过`RtlImageNtHeader`获得,不过这个函数没有文档化,需要提前声明PIMAGE_NT_HEADERS* RtlImageNtHeader(void* Base)
	PIMAGE_NT_HEADERS pNts = (PIMAGE_NT_HEADERS)(FileBuffer + pDos->e_lfanew);

	/*
	创建ImageBuffer
	*/
	//获取PE文件的Image大小
	ULONG SizeOfImage = pNts->OptionalHeader.SizeOfImage;
	//申请一块内存
	//使用非分页内存来保证可执行,使用PE的Image大小作为申请的大小
	PUCHAR ImageBuffer = ExAllocatePool(NonPagedPool, SizeOfImage);
	//将申请的内存清空
	memset(ImageBuffer, 0, SizeOfImage);


	//复制PE头,将pe文件复制到ImageBuffer,按照所有头部总和的大小复制
	memcpy(ImageBuffer, FileBuffer, pNts->OptionalHeader.SizeOfHeaders);
	//获取PE文件的节区数量
	ULONG NumberOfSections = pNts->FileHeader.NumberOfSections;

	//获得第一个节区
	PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNts);
	//复制节区
	for (ULONG i = 0; i < NumberOfSections; i++)
	{
		//将文件的PE头复制到image中
		memcpy(ImageBuffer + pSection->VirtualAddress, FileBuffer + pSection->PointerToRawData, pSection->SizeOfRawData);
		//获取下一个pSection
		pSection++;
	}
	//将存储拉伸后的PE头的ImageBuffer返回
	return ImageBuffer;
}

//修复重定位,传入拉伸后的ImageBuffer
BOOLEAN UpdataRelocation(char* ImageBuffer)
{
	if (!ImageBuffer) return FALSE;
	//获取PE的NT头,这里使用未文档化的函数进行获取
	PIMAGE_NT_HEADERS pNts = RtlImageNtHeader(ImageBuffer);
	//如果没有获取到,返回FALSE
	if (!pNts) return FALSE;
	//取目录表,目录表是第五个,也就是IMAGE_DIRECTORY_ENTRY_BASERELOC
	//因为是`PIMAGE_DATA_DIRECTORY`是个指针,所以后面的pNts要取地址
	PIMAGE_DATA_DIRECTORY iReplocation = &pNts->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
	//取重定位信息的位置,ImageBuffer + RVA(偏移)
	PIMAGE_BASE_RELOCATION pBase = (PIMAGE_BASE_RELOCATION)(ImageBuffer + iReplocation->VirtualAddress);

	//当这两个都存在的时候就证明还存在需要修复的重定向
	//当块的大小和虚拟地址都是空的退出
	while (pBase->SizeOfBlock && pBase->VirtualAddress)
	{

		//指向块的位置
		//PIMAGE_RELOC RelocationBlock = (PIMAGE_RELOC)(pBase->VirtualAddress + ImageBuffer + sizeof(IMAGE_BASE_RELOCATION));
		PIMAGE_RELOC RelocationBlock = (PIMAGE_RELOC)((PUCHAR)pBase + sizeof(IMAGE_BASE_RELOCATION));
		//计算需要修复的重定向的数目
		UINT32 NumberOfRelocation = (pBase->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(IMAGE_RELOC);
		//遍历每一个节区
		for (int i = 0; i < NumberOfRelocation; i++)
		{
			if (RelocationBlock[i].Type == IMAGE_REL_BASED_DIR64)
			{
				//64位
				PUINT64 Address = (PUINT64)((PUINT8)ImageBuffer + pBase->VirtualAddress + RelocationBlock[i].Offset);
				UINT64 Delta = *Address - pNts->OptionalHeader.ImageBase + (PUINT8)ImageBuffer;
				*Address = Delta;
			}
			else if (RelocationBlock[i].Type == IMAGE_REL_BASED_HIGHLOW)
			{
				//32位
				PUINT32 Address = (PUINT32)((PUINT8)ImageBuffer + pBase->VirtualAddress + (RelocationBlock[i].Offset));
				UINT32 Delta = *Address - pNts->OptionalHeader.ImageBase + (PUINT8)ImageBuffer;
				*Address = Delta;
			}

		}
		//循环完一次就到下一个模块
		pBase = (PIMAGE_BASE_RELOCATION)((PUCHAR)pBase + pBase->SizeOfBlock);

	}
	return TRUE;
}

//原理是获取IAT表中的每一个导入的函数所属的模块名字
//修复IAT表
BOOLEAN UpdataIAT(char* ImageBuffer)
{
	if (!ImageBuffer) return FALSE;
	//获取PE的NT头,这里使用未文档化的函数进行获取
	PIMAGE_NT_HEADERS pNts = RtlImageNtHeader(ImageBuffer);
	//如果没有获取到,返回FALSE
	if (!pNts) return FALSE;
	//取IAT表,IAT表是第一个
	//因为是`PIMAGE_DATA_DIRECTORY`是个指针,所以后面的pNts要取地址
	PIMAGE_DATA_DIRECTORY pImportDir = &pNts->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
	//取IAT位置,ImageBuffer + RVA(偏移)
	PIMAGE_IMPORT_DESCRIPTOR import = (PIMAGE_IMPORT_DESCRIPTOR)(ImageBuffer + pImportDir->VirtualAddress);
	//提供标志
	BOOLEAN isSucess = TRUE;

	//循环获取模块的导入表信息
	//如果这个模块已经被修复过了,那么就没有import->Name等信息
	for (; import->Name; import++)
	{
		//获取IAT表中使用的函数的所属模块名,ImageBuffer + offset(import->name)
		PUCHAR libName = (ImageBuffer + import->Name);
		//DbgPrint("%s\r\n", libName);
		//根据模块名查询其基地址
		ULONG_PTR base = QueryModule(libName, NULL);
		//如果没找到模块对应的基地址的话
		if (!base)
		{
			isSucess = FALSE;
			break;
		}
		//根据前面获得的IAT表中的函数所对应的模块基址`base`

		//根据`ImageBuffer+offset`的方式获取Buffer中的IAT中所对应的函数的名字和地址(最开始写在缓冲区的值)
		PIMAGE_THUNK_DATA pThuckName = (PIMAGE_THUNK_DATA)(ImageBuffer + import->OriginalFirstThunk);
		PIMAGE_THUNK_DATA pThuckFunc = (PIMAGE_THUNK_DATA)(ImageBuffer + import->FirstThunk);

		//循环直到不存在函数名字
		for (; pThuckName->u1.ForwarderString; ++pThuckName, ++pThuckFunc)
		{
			//根据imageBuffer+offset的形式获取函数名字
			PIMAGE_IMPORT_BY_NAME FuncName = (PIMAGE_IMPORT_BY_NAME)(ImageBuffer + pThuckName->u1.AddressOfData);
			//根据函数名字找到对应的地址
			ULONG_PTR func = ExportTableFuncByName((char*)base, FuncName->Name);
			if (func)
			{
				//修复函数地址
				pThuckFunc->u1.Function = (ULONG_PTR)func;
			}
			else
			{
				isSucess = FALSE;
				break;
			}
		}
		if (!isSucess) break;
	}
	return isSucess;
}

//修复cookie
VOID UpdataCookie(char* ImageBuffer)
{
	//DbgBreakPoint();
	if (!ImageBuffer) return FALSE;
	//获取PE的NT头,这里使用未文档化的函数进行获取
	PIMAGE_NT_HEADERS pNts = RtlImageNtHeader(ImageBuffer);
	//如果没有获取到,返回FALSE
	if (!pNts) return FALSE;
	//第十号(IMAGE_DIRECTORY_ENTRY_BASERELOC)就是cookie
	PIMAGE_DATA_DIRECTORY pConfigDir = &pNts->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG];
	//imagebuffer +offset  = config位置
	PIMAGE_LOAD_CONFIG_DIRECTORY config = (PIMAGE_LOAD_CONFIG_DIRECTORY)(ImageBuffer + pConfigDir->VirtualAddress);
	//COOKIE 随便一个值就行只要不是危险值,具体就看理论篇有提到cookie的逻辑
	*(PULONG_PTR)(config->SecurityCookie) += 10;
}


//加载驱动
BOOLEAN LoadDriver(PUCHAR FileBuffer)
{
	//1.先拉伸,修复PE头,获取PE基址
	PUCHAR imageBase = FileToImange(FileBuffer);
	//拉伸失败返回FALSE
	if (!imageBase) return FALSE;
	//标志位
	BOOLEAN isSucess = FALSE;
	do
	{
		//2.根据PE基址修复重定位
		isSucess = UpdataRelocation(imageBase);
		//失败释放内存
		if (!isSucess) break;

		//3.根据PE基址修复IAT表
		isSucess = UpdataIAT(imageBase);
		//失败释放内存
		if (!isSucess) break;

		//4.修复cookie(Win732位下可以不修)
		UpdataCookie(imageBase);

		//call 入口点
		//获取PE的NT头,这里使用未文档化的函数进行获取
		PIMAGE_NT_HEADERS pNts = RtlImageNtHeader(imageBase);
		//5.获取入口点
		ULONG_PTR entry = pNts->OptionalHeader.AddressOfEntryPoint;
		//定义一个函数入口点的位置(设置这个函数的地址)
		DriverEntrypProc EntryPoint = (DriverEntrypProc)(imageBase + entry);
		//6.call 入口点
		//DbgBreakPoint();
		NTSTATUS status = EntryPoint(NULL, NULL);
		if (!NT_SUCCESS(status))
		{
			isSucess = FALSE;
			break;
		}
		//将PE头去掉
		memset(imageBase, 0, PAGE_SIZE);
	} while (0);

	if (!isSucess)
	{
		ExFreePool(imageBase);
		return isSucess;
	}
	return isSucess;
}

Loader.h

#include <ntifs.h>

//更新IAT表
BOOLEAN UpdataIAT(char* ImageBuffer);

//加载驱动
BOOLEAN LoadDriver(PUCHAR FileBuffer);

main.c

#include <ntifs.h>
#include "tools.h"
#include "Loader.h"
#include "dll.h"
VOID Unload(PDRIVER_OBJECT pDriver)
{
	DbgPrint("unload\r\n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
	//获取加密后的需要执行的sys的大小
	ULONG dwImageSize = sizeof(sysData);
	//开辟一块内存,内存属性可以执行,按照dll中的大小创建
	unsigned char* pMemory = (unsigned char*)ExAllocatePool(NonPagedPool, dwImageSize);
	//将加密了的内容放进去
	memcpy(pMemory, sysData, dwImageSize);
	//循环解密
	for (ULONG i = 0; i < dwImageSize; i++)
	{

		pMemory[i] ^= 0xd8;
		pMemory[i] ^= 0xcd;
	}
	//DbgBreakPoint();
	//加载解密后的驱动
	LoadDriver(pMemory);
	//内存释放
	ExFreePool(pMemory);
	//pDriver->DriverUnload = Unload;
	//DbgBreakPoint();
	DbgPrint("TEST_Entry\r\n");
	//因为我们这个驱动目的是创建另一个驱动,所所以执行完创建的操作后就可以推出了,所以后面才是UNSUCESS
	return STATUS_UNSUCCESSFUL;
}

相关文件

TestDriver.sys 用来被内存加载执行的驱动文件

main.c

#include <ntifs.h>

VOID Unload(PDRIVER_OBJECT pDriver)
{
	DbgPrint("unload\r\n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
	DbgPrint("---------------------------------TEST-------------------------\r\n");
	//DbgBreakPoint();
	DbgPrint("TEST_Entry\r\n");
	return STATUS_SUCCESS;
}

不要挂卸载函数,因为这个是内存加载的,没法卸载

Build.exe 将sys转成字节码dll.h

main.cpp

#include <stdio.h>
#include <memory.h>
#include <stdlib.h>

int main(int argc, char* args[], char** env)
{
	if (argc < 2)
	{
		printf("参数个数不对\r\n");
		printf("举个例子:bulid.exe dll路径\r\n");
		return 0;
	}
	//取dll路径
	char* dllpath = args[1];
	//文件指针
	FILE* file = NULL;
	//将dll路径存到file中
	fopen_s(&file, dllpath, "rb");
	//如果打开失败
	if (!file)
	{
		printf("文件不存在\r\n");
		return 0;
	}

	//求文件大小
	//将文件指针移动到末尾
	fseek(file, 0, SEEK_END);
	//获取文件的字节数
	unsigned int len = ftell(file);
	//指针移动到头部
	rewind(file);
	//根据文件大小申请内存
	unsigned char* fileBuffer = (unsigned char*)malloc(len);
	//清空内存
	memset(fileBuffer, 0, len);
	//将文件内容放到内存中
	fread_s(fileBuffer, len, len, 1, file);
	//关闭文件句柄
	fclose(file);


	//创建一个文件,写入我们转换后的硬编码
	//如果只传进来1个参数,也就是没有指定名字,那么就用默认的名字
	if (argc == 2)
	{
		fopen_s(&file, "dll.h", "wb");
	}
	else
	{
		fopen_s(&file, args[2], "wb");
	}
	//如果文件缓冲区里面没有内容,那么就释放内存
	if (file == NULL)
	{
		free(fileBuffer);
		return 0;
	}
	//从这里开始写新文件进行处理
	fputs("#pragma once\n", file);
	char buftemp[1000] = { 0 };
	fprintf_s(file, "unsigned char sysData[%d] =  {\n", len);
	fprintf_s(file, "\t");
	for (int i = 0; i < len; i++)
	{
		//加密,使用异或加密
		fileBuffer[i] ^= 0xcd;
		fileBuffer[i] ^= 0xd8;

		//写入硬编码,格式是`0x222, `,注意后面有一个空格
		fprintf_s(file, "0x%02X, ", fileBuffer[i]);
		//每30个一行
		if ((i + 1) % 30 == 0)
		{
			//到30个就自动换行,每一行开始有一个空格\\t
			fprintf_s(file, "\r\n\t");
		}

	}
	//全部写完后换行和括号分号收尾
	fprintf_s(file, "\r\n};");
	//如果文件缓冲区内有内容,就关闭文件
	fclose(file);
	//释放缓冲区
	free(fileBuffer);
	return 0;
}

encode.bat 自动调用Build.exe将需要内存加载的驱动转成字节码的文件

encode.bat

set "ProjectPath=%cd%"
cd ../
set "PreProjectPath=%cd%"
cd %ProjectPath%
set "SignFullPath=%PreProjectPath%/Debug/TestDriver.sys"
Build.exe %SignFullPath%

使用说明

  1. 先生成需要内存加载的驱动TestDriver.sys文件

  2. 再生成Build.exe文件,用来将TestDriver.sys转成字节码dll.h

  3. Debug路径下执行encode.bat批处理,通过Build.exeTestDriver.sys转成字节码dll.h

  4. dll.h附加到DriverLoader.sys的头文件里面,具体看上面的代码DriverLoader的main.c代码

    下面的dll.h就是加密后的硬编码dll.h文件

image-20230122233747232

这个SysData就是硬编码的TestDriver文件

其中dll.h是硬编码的需要内存加载的驱动文件的框架,sysData是硬编码的驱动文件内容

DriverLoader加载dll.h前的Debug路径下为文件如下

然后将dll.h放到DriverLoader的头文件里面并引用他

总流程图

posted @ 2024-05-18 17:22  MuRKuo  阅读(109)  评论(0编辑  收藏  举报