内核重载需求的产生
在内核中有很多HOOK, 例如:KiFastCallEntry, SSDT,IDT,OBJECT HOOK,甚至是内核API的内联HOOK , 有些HOOK很容易找到,并还原, 有些HOOK就很难找到. 在某些时候(例如一个病毒HOOK了int3
中断,这样调试器就无法得到断点事件),清除HOOK是一种反反调试的技术. 也是防护与反防护的技术. —总之内核层的对抗非常的激烈, 为了能够一举将所有的HOOK或者其它防护技术统统屏蔽, 内核重载技术应运而生. 简单来说, 内核重载就是将文件中的内核(ntkrnlpa.exe
)重新加载到内存中, 这样一来, 所有对内核中的修改都会被还原.
但是, 直接加载并覆盖到原来的内存的话会产生很多错误,例如, 系统开机之后,会对很多的数据进行初始化, 这些数据被初始化之后系统才能正常运行, 如果直接将内核文件的数据覆盖了,就没有这些数据了. 因此, 可以只将原始内核的代码段加载到内存 , 并通过一些设置, 让原始
内核重载的目标
破掉所有的KiFastCallEntry HOOK 1.1 替换KiFastCallEntry函数.
破掉所有SSDT HOOK 2.1 使用新的SSDT表(需要修复非常多的数据)
破掉所有的Inline HOOK 3.1 直接使用新的内核代码段(需要修复非常非常多的数据)
kernelReload.c
#include <ntifs.h>
#include <ntimage.h>
#include <ntddk.h>
#include "kernelFunction.h"
////////////////////////////////////////////
///////////// 内核重载相关函数 /////////////
////////////////////////////////////////////
// 导入SSDT全局变量
NTSYSAPI SSDTEntry KeServiceDescriptorTable;
static char* g_pNewNtKernel; // 新内核
static ULONG g_ntKernelSize; // 内核的映像大小
static SSDTEntry* g_pNewSSDTEntry; // 新ssdt的入口地址
static ULONG g_hookAddr; // 被hook位置的首地址
static ULONG g_hookAddr_next_ins;// 被hook的指令的下一条指令的首地址.
// 读取NT内核模块
// 将读取到的缓冲区的内容保存到pBuff中.
NTSTATUS loadNtKernelModule( UNICODE_STRING* ntkernelPath, char** pBuff );
// 修复重定位.
void fixRelocation( char* pDosHdr, ULONG curNtKernelBase );
// 填充SSDT表
// char* pDos - 新加载的内核堆空间首地址
// char* pCurKernelBase - 当前正在使用的内核的加载基址
void initSSDT( char* pDos, char* pCurKernelBase );
// 安装HOOK
void installHook( );
// 卸载HOOK
void uninstallHook( );
// inline hook KiFastCallEntry的函数
void myKiFastEntryHook( );
////////////////////////////////////////////
/////////////// 驱动入口函数 //////////////
////////////////////////////////////////////
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pPath)
{
NTSTATUS status = STATUS_SUCCESS;
DbgBreakPoint( );
// 1. 找到内核文件路径
// 1.1 通过遍历内核链表的方式来找到内核主模块
LDR_DATA_TABLE_ENTRY* pLdr = ((LDR_DATA_TABLE_ENTRY*)pDriver->DriverSection);
// 1.2 内核主模块在链表中的第2项.
for(int i =0;i<2;++i)
pLdr = (LDR_DATA_TABLE_ENTRY*)pLdr->InLoadOrderLinks.Flink;
g_ntKernelSize = pLdr->SizeOfImage;
// 1.3 保存当前加载基址
char* pCurKernelBase = (char*)pLdr->DllBase;
KdPrint( ("base=%p name=%wZ ", pCurKernelBase, &pLdr->FullDllName) );
// 2. 读取nt模块的文件内容到堆空间.
status = loadNtKernelModule( &pLdr->FullDllName, &g_pNewNtKernel );
if(STATUS_SUCCESS != status)
{
return status;
}
// 3. 修复新nt模块的重定位.
fixRelocation( g_pNewNtKernel, (ULONG)pCurKernelBase );
// 4. 使用当前正在使用的内核的数据来填充
// 新内核的SSDT表.
initSSDT( g_pNewNtKernel, pCurKernelBase );
// 5. HOOK KiFastCallEntry,使调用号走新内核的路线
installHook( );
pDriver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
VOID DriverUnload(PDRIVER_OBJECT pDriver)
{
UNREFERENCED_PARAMETER(pDriver);
uninstallHook( );
}
// 关闭内存页写入保护
void _declspec(naked) disablePageWriteProtect( )
{
_asm
{
push eax;
mov eax, cr0;
and eax, ~0x10000;
mov cr0, eax;
pop eax;
ret;
}
}
// 开启内存页写入保护
void _declspec(naked) enablePageWriteProtect( )
{
_asm
{
push eax;
mov eax, cr0;
or eax, 0x10000;
mov cr0, eax;
pop eax;
ret;
}
}
// 加载NT内核模块
// 将读取到的缓冲区的内容保存到pBuff中.
NTSTATUS loadNtKernelModule( UNICODE_STRING* ntkernelPath, char** pOut )
{
NTSTATUS status = STATUS_SUCCESS;
// 2. 获取文件中的内核模块
// 2.1 将内核模块作为文件来打开.
HANDLE hFile = NULL;
char* pBuff = NULL;
ULONG read = 0;
char pKernelBuff[0x1000];
status = createFile( ntkernelPath->Buffer,
GENERIC_READ,
FILE_SHARE_READ,
FILE_OPEN_IF,
FALSE,
&hFile );
if(status != STATUS_SUCCESS)
{
KdPrint( ("打开文件失败 ") );
goto _DONE;
}
// 2.2 将PE文件头部读取到内存
status = readFile( hFile, 0, 0, 0x1000, pKernelBuff, &read );
if(STATUS_SUCCESS != status)
{
KdPrint( ("读取文件内容失败 ") );
goto _DONE;
}
// 3. 加载PE文件到内存.
// 3.1 得到扩展头,获取映像大小.
IMAGE_DOS_HEADER* pDos = (IMAGE_DOS_HEADER*)pKernelBuff;
IMAGE_NT_HEADERS* pnt = (IMAGE_NT_HEADERS*)((ULONG)pDos + pDos->e_lfanew);
ULONG imgSize = pnt->OptionalHeader.SizeOfImage;
// 3.2 申请内存以保存各个区段的内容.
pBuff = ExAllocatePool( NonPagedPool, imgSize );
if(pBuff == NULL)
{
KdPrint( ("内存申请失败失败 ") );
status = STATUS_BUFFER_ALL_ZEROS;//随便返回个错误码
goto _DONE;
}
// 3.2.1 拷贝头部到堆空间
RtlCopyMemory( pBuff,
pKernelBuff,
pnt->OptionalHeader.SizeOfHeaders );
// 3.3 得到区段头, 并将按照区段头将区段读取到内存中.
IMAGE_SECTION_HEADER* pScnHdr = IMAGE_FIRST_SECTION( pnt );
ULONG scnCount = pnt->FileHeader.NumberOfSections;
for(ULONG i = 0; i < scnCount; ++i)
{
//
// 3.3.1 读取文件内容到堆空间指定位置.
//
status = readFile( hFile,
pScnHdr[i].PointerToRawData,
0,
pScnHdr[i].SizeOfRawData,
pScnHdr[i].VirtualAddress + pBuff,
&read );
if(status != STATUS_SUCCESS)
goto _DONE;
}
_DONE:
ZwClose( hFile );
//
// 保存新内核的加载的首地址
//
*pOut = pBuff;
if(status != STATUS_SUCCESS )
{
if(pBuff != NULL)
{
ExFreePool( pBuff );
*pOut = pBuff = NULL;
}
}
return status;
}
// 修复重定位.
void fixRelocation( char* pDosHdr , ULONG curNtKernelBase )
{
IMAGE_DOS_HEADER* pDos = (IMAGE_DOS_HEADER*)pDosHdr;
IMAGE_NT_HEADERS* pNt = (IMAGE_NT_HEADERS*)((ULONG)pDos + pDos->e_lfanew);
ULONG uRelRva = pNt->OptionalHeader.DataDirectory[5].VirtualAddress;
IMAGE_BASE_RELOCATION* pRel =
(IMAGE_BASE_RELOCATION*)(uRelRva + (ULONG)pDos);
while(pRel->SizeOfBlock)
{
typedef struct
{
USHORT offset : 12;
USHORT type : 4;
}TypeOffset;
ULONG count = (pRel->SizeOfBlock - 8) / 2;
TypeOffset* pTypeOffset = (TypeOffset*)(pRel + 1);
for(ULONG i = 0; i < count; ++i)
{
if(pTypeOffset[i].type != 3)
{
continue;
}
ULONG* pFixAddr = (ULONG*)(pTypeOffset[i].offset + pRel->VirtualAddress + (ULONG)pDos);
//
// 减去默认加载基址
//
*pFixAddr -= pNt->OptionalHeader.ImageBase;
//
// 加上新的加载基址(使用的是当前内核的加载基址,这样做
// 是为了让新内核使用当前内核的数据(全局变量,未初始化变量等数据).)
//
*pFixAddr += (ULONG)curNtKernelBase;
}
pRel = (IMAGE_BASE_RELOCATION*)((ULONG)pRel + pRel->SizeOfBlock);
}
}
// 填充SSDT表
// char* pNewBase - 新加载的内核堆空间首地址
// char* pCurKernelBase - 当前正在使用的内核的加载基址
void initSSDT( char* pNewBase, char* pCurKernelBase )
{
// 1. 分别获取当前内核,新加载的内核的`KeServiceDescriptorTable`
// 的地址
SSDTEntry* pCurSSDTEnt = &KeServiceDescriptorTable;
g_pNewSSDTEntry = (SSDTEntry*)
((ULONG)pCurSSDTEnt - (ULONG)pCurKernelBase + (ULONG)pNewBase);
// 2. 获取新加载的内核以下三张表的地址:
// 2.1 服务函数表基址
g_pNewSSDTEntry->ServiceTableBase = (ULONG*)
((ULONG)pCurSSDTEnt->ServiceTableBase - (ULONG)pCurKernelBase + (ULONG)pNewBase);
// 2.3 服务函数参数字节数表基址
g_pNewSSDTEntry->ParamTableBase = (ULONG*)
((ULONG)pCurSSDTEnt->ParamTableBase - (ULONG)pCurKernelBase + (ULONG)pNewBase);
// 2.2 服务函数调用次数表基址(有时候这个表并不存在.)
if(pCurSSDTEnt->ServiceCounterTableBase)
{
g_pNewSSDTEntry->ServiceCounterTableBase = (ULONG*)
((ULONG)pCurSSDTEnt->ServiceCounterTableBase - (ULONG)pCurKernelBase + (ULONG)pNewBase);
}
// 2.3 设置新SSDT表的服务个数
g_pNewSSDTEntry->NumberOfServices = pCurSSDTEnt->NumberOfServices;
//3. 将服务函数的地址填充到新SSDT表(重定位时其实已经修复好了,
// 但是,在修复重定位的时候,是使用当前内核的加载基址的, 修复重定位
// 为之后, 新内核的SSDT表保存的服务函数的地址都是当前内核的地址,
// 在这里要将这些服务函数的地址改回新内核中的函数地址.)
disablePageWriteProtect( );
for(ULONG i = 0; i < g_pNewSSDTEntry->NumberOfServices; ++i)
{
// 减去当前内核的加载基址
g_pNewSSDTEntry->ServiceTableBase[i] -= (ULONG)pCurKernelBase;
// 换上新内核的加载基址.
g_pNewSSDTEntry->ServiceTableBase[i] += (ULONG)pNewBase;
}
enablePageWriteProtect( );
}
void installHook( )
{
g_hookAddr = 0;
// 1. 找到KiFastCallEntry函数首地址
ULONG uKiFastCallEntry = 0;
_asm
{
;// KiFastCallEntry函数地址保存
;// 在特殊模组寄存器的0x176号寄存器中
push ecx;
push eax;
push edx;
mov ecx, 0x176; // 设置编号
rdmsr; ;// 读取到edx:eax
mov uKiFastCallEntry, eax;// 保存到变量
pop edx;
pop eax;
pop ecx;
}
// 2. 找到HOOK的位置, 并保存5字节的数据
// 2.1 HOOK的位置选定为(此处正好5字节,):
// 2be1 sub esp, ecx ;
// c1e902 shr ecx, 2 ;
UCHAR hookCode[5] = { 0x2b,0xe1,0xc1,0xe9,0x02 }; //保存inline hook覆盖的5字节
ULONG i = 0;
for(; i < 0x1FF; ++i)
{
if(RtlCompareMemory( (UCHAR*)uKiFastCallEntry + i,
hookCode,
5 ) == 5)
{
break;
}
}
if(i >= 0x1FF)
{
KdPrint( ("在KiFastCallEntry函数中没有找到HOOK位置,可能KiFastCallEntry已经被HOOK过了 ") );
uninstallHook( );
return;
}
g_hookAddr = uKiFastCallEntry + i;
g_hookAddr_next_ins = g_hookAddr + 5;
// 3. 开始inline hook
UCHAR jmpCode[5] = { 0xe9 };// jmp xxxx
disablePageWriteProtect( );
// 3.1 计算跳转偏移
// 跳转偏移 = 目标地址 - 当前地址 - 5
*(ULONG*)(jmpCode + 1) = (ULONG)myKiFastEntryHook - g_hookAddr - 5;
// 3.2 将跳转指令写入
RtlCopyMemory( uKiFastCallEntry + i,
jmpCode,
5 );
enablePageWriteProtect( );
}
void uninstallHook( )
{
if(g_hookAddr)
{
// 将原始数据写回.
UCHAR srcCode[5] = { 0x2b,0xe1,0xc1,0xe9,0x02 };
disablePageWriteProtect( );
// 3.1 计算跳转偏移
// 跳转偏移 = 目标地址 - 当前地址 - 5
_asm sti
// 3.2 将跳转指令写入
RtlCopyMemory( g_hookAddr,
srcCode,
5 );
_asm cli
g_hookAddr = 0;
enablePageWriteProtect( );
}
if(g_pNewNtKernel)
{
ExFreePool( g_pNewNtKernel );
g_pNewNtKernel = NULL;
}
}
// SSDT过滤函数.
ULONG SSDTFilter( ULONG index,/*索引号,也是调用号*/
ULONG tableAddress,/*表的地址,可能是SSDT表的地址,也可能是Shadow SSDT表的地址*/
ULONG funAddr/*从表中取出的函数地址*/ )
{
// 如果是SSDT表的话
if(tableAddress == KeServiceDescriptorTable.ServiceTableBase)
{
// 判断调用号(190是ZwOpenProcess函数的调用号)
if(index == 190)
{
// 返回新SSDT表的函数地址
// 也就是新内核的函数地址.
return g_pNewSSDTEntry->ServiceTableBase[190];
}
}
// 返回旧的函数地址
return funAddr;
}
// inline hook KiFastCallEntry的函数
void _declspec(naked) myKiFastEntryHook( )
{
_asm
{
pushad; // 压栈寄存器: eax,ecx,edx,ebx, esp,ebp ,esi, edi
pushfd; // 压栈标志寄存器
push edx; // 从表中取出的函数地址
push edi; // 表的地址
push eax; // 调用号
call SSDTFilter; // 调用过滤函数
;// 函数调用完毕之后栈控件布局,指令pushad将
;// 32位的通用寄存器保存在栈中,栈空间布局为:
;// [esp + 00] <=> eflag
;// [esp + 04] <=> edi
;// [esp + 08] <=> esi
;// [esp + 0C] <=> ebp
;// [esp + 10] <=> esp
;// [esp + 14] <=> ebx
;// [esp + 18] <=> edx <<-- 使用函数返回值来修改这个位置
;// [esp + 1C] <=> ecx
;// [esp + 20] <=> eax
mov dword ptr ds : [esp + 0x18], eax;
popfd; // popfd时,实际上edx的值就回被修改
popad;
; //执行被hook覆盖的两条指令
sub esp, ecx;
shr ecx, 2;
jmp g_hookAddr_next_ins;
}
}
KernelFunction.h
#pragma once
#include <ntifs.h>
#include <ntddk.h>
//////////////////////////////////////////////////////////////////////////
//////////////////////////// 内存操作 /////////////////////////////////////
//************************************
// Method: alloc 申请内存
// Returns: void* 返回内存空间首地址, 申请失败返回NULL
// Parameter: ULONG size 要申请的字节数
//************************************
void* alloc(ULONG size);
//************************************
// Method: reAlloc 重新分配空间
// Returns: void* 返回新空间的内存地址
// Parameter: void * src 原始内存空间(必须由alloc函数所返回)
// Parameter: ULONG size 重新分配的字节数
//************************************
void* reAlloc(void* src, ULONG size);
//************************************
// Method: free 释放内存空间
// Returns: void
// Parameter: void *
//************************************
void free(void* data);
//////////////////////////////////////////////////////////////////////////
//////////////////////////// 文件操作 /////////////////////////////////////
//************************************
// Method: createFile 创建文件
// Returns: NTSTATUS
// Parameter: const wchar_t * filepath 文件路径,路径必须是设备名"\device\volumn\"或符号连接名"\??\C:\1.txt"
// Parameter: ULONG access 访问权限, GENERIC_READ, GENERIC_XXX
// Parameter: ULONG share 文件共享方式: FILE_SHARE_XXX
// Parameter: ULONG openModel 打开方式: FILE_OPEN_IF,FILE_CREATE ...
// Parameter: BOOLEAN isDir 是否为目录
// Parameter: HANDLE * hFile
//************************************
NTSTATUS createFile(wchar_t* filepath,
ULONG access,
ULONG share,
ULONG openModel,
BOOLEAN isDir,
HANDLE* hFile);
//************************************
// Method: getFileSize 获取文件大小
// Returns: NTSTATUS
// Parameter: HANDLE hFile 文件句柄
// Parameter: ULONG64 * size 文件大小
//************************************
NTSTATUS getFileSize(HANDLE hFile,
ULONG64* size);
//************************************
// Method: readFile 读取文件内容
// Returns: NTSTATUS
// Parameter: HANDLE hFile 文件句柄
// Parameter: ULONG offsetLow 文件偏移的低32位, 从此位置开始读取
// Parameter: ULONG offsetHig 文件偏移的高32位, 从此位置开始读取
// Parameter: ULONG sizeToRead 要读取的字节数
// Parameter: PVOID pBuff 保存文件内容的缓冲区 , 需要自己申请内存空间.
// Parameter: ULONG * read 实际读取到的字节数
//************************************
NTSTATUS readFile(HANDLE hFile,
ULONG offsetLow,
ULONG offsetHig,
ULONG sizeToRead,
PVOID pBuff,
ULONG* read);
NTSTATUS writeFile(HANDLE hFile,
ULONG offsetLow,
ULONG offsetHig,
ULONG sizeToWrite,
PVOID pBuff,
ULONG* write);
NTSTATUS copyFile(wchar_t* srcPath,
wchar_t* destPath);
NTSTATUS moveFile(wchar_t* srcPath,
wchar_t* destPath);
NTSTATUS removeFile(wchar_t* path);
//************************************
// Method: listDirGet 列出一个目录下的文件和文件夹
// Returns: NTSTATUS
// Parameter: wchar_t * dir 目录名, 目录名必须是设备名"\device\volumn\"或符号连接名"\??\C:\1.txt"
// Parameter: FILE_BOTH_DIR_INFORMATION ** fileInfo 保存文件内容的缓冲区, 该缓冲区由函数内部申请空间, 必须通过函数`listDirFree`来释放.
// Parameter: ULONG maxFileCount 要获取的最大文件个数.如果目录下有100个文件,此参数传了5,则只能获取到5个文件.
//************************************
NTSTATUS listDirGet(wchar_t* dir ,
FILE_BOTH_DIR_INFORMATION** fileInfo,
ULONG maxFileCount);
//************************************
// Method: firstFile 获取一个目录下的第一个文件
// Returns: NTSTATUS
// Parameter: wchar_t * dir 目录名, 目录名必须是设备名"\device\volumn\"或符号连接名"\??\C:\1.txt"
// Parameter: HANDLE * hFind 函数输出值,是一个目录句柄
// Parameter: FILE_BOTH_DIR_INFORMATION * fileInfo 保存文件内容的缓冲区,
// 这个缓冲区的大小最好是: sizeof(FILE_BOTH_DIR_INFORMATION) + 267*2
//************************************
NTSTATUS firstFile(wchar_t* dir, HANDLE* hFind, FILE_BOTH_DIR_INFORMATION* fileInfo,int size);
//************************************
// Method: nextFile 获取一个目录下的下一个文件.
// Returns: NTSTATUS
// Parameter: HANDLE hFind 目录句柄, 该句柄是由firstFile函数所返回 .
// Parameter: FILE_BOTH_DIR_INFORMATION * fileInfo 保存文件信息的缓冲区. 这个缓冲区的大小最好是: sizeof(FILE_BOTH_DIR_INFORMATION) + 267*2
//************************************
NTSTATUS nextFile(HANDLE hFind, FILE_BOTH_DIR_INFORMATION* fileInfo, int size);
void listDirFree(FILE_BOTH_DIR_INFORMATION* fileInfo);
#define ListDirNext(Type,fileinfo) ((Type*)((ULONG_PTR)fileinfo + fileinfo->NextEntryOffset))
#define ListDirForEach(FileInfoType,fileInfo, iterator)
for (FileInfoType* iterator = fileInfo;
iterator->NextEntryOffset != 0;
iterator = ListDirNext(FileInfoType,iterator))
#pragma pack(1)
typedef struct _ServiceDesriptorEntry
{
ULONG *ServiceTableBase; // 服务表基址
ULONG *ServiceCounterTableBase; // 计数表基址
ULONG NumberOfServices; // 表中项的个数
UCHAR *ParamTableBase; // 参数表基址
}SSDTEntry, *PSSDTEntry;
#pragma pack()
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks; //双向链表
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
// ...
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
void disablePageWriteProtect( );
void enablePageWriteProtect( );
VOID DriverUnload( PDRIVER_OBJECT pDriver );
kernelFunction.c
#include "KernelFunction.h"
#include <ntifs.h>
void* alloc(ULONG size)
{
return ExAllocatePool(PagedPool, size);
}
void* reAlloc(void* src, ULONG size)
{
if (!src)
{
return NULL;
}
void* data = alloc(size);
RtlCopyMemory(data, src, size);
ExFreePool(src);
return data;
}
void free(void* data)
{
if (data)
{
ExFreePool(data);
}
}
NTSTATUS createFile(wchar_t * filepath,
ULONG access,
ULONG share,
ULONG openModel,
BOOLEAN isDir,
HANDLE * hFile)
{
NTSTATUS status = STATUS_SUCCESS;
IO_STATUS_BLOCK StatusBlock = { 0 };
ULONG ulShareAccess = share;
ULONG ulCreateOpt = FILE_SYNCHRONOUS_IO_NONALERT;
UNICODE_STRING path;
RtlInitUnicodeString(&path, filepath);
// 1. 初始化OBJECT_ATTRIBUTES的内容
OBJECT_ATTRIBUTES objAttrib = { 0 };
ULONG ulAttributes = OBJ_CASE_INSENSITIVE/*不区分大小写*/ | OBJ_KERNEL_HANDLE/*内核句柄*/;
InitializeObjectAttributes(&objAttrib, // 返回初始化完毕的结构体
&path, // 文件对象名称
ulAttributes, // 对象属性
NULL, NULL); // 一般为NULL
// 2. 创建文件对象
ulCreateOpt |= isDir ? FILE_DIRECTORY_FILE : FILE_NON_DIRECTORY_FILE;
status = ZwCreateFile(hFile, // 返回文件句柄
access, // 文件操作描述
&objAttrib, // OBJECT_ATTRIBUTES
&StatusBlock, // 接受函数的操作结果
0, // 初始文件大小
FILE_ATTRIBUTE_NORMAL, // 新建文件的属性
ulShareAccess, // 文件共享方式
openModel, // 文件存在则打开不存在则创建
ulCreateOpt, // 打开操作的附加标志位
NULL, // 扩展属性区
0); // 扩展属性区长度
return status;
}
NTSTATUS getFileSize(HANDLE hFile, ULONG64* size)
{
IO_STATUS_BLOCK isb = { 0 };
FILE_STANDARD_INFORMATION fsi = { 0 };
NTSTATUS status;
status = ZwQueryInformationFile(hFile, /*文件句柄*/
&isb, /*完成状态*/
&fsi, /*保存文件信息的缓冲区*/
sizeof(fsi), /*缓冲区的字节数*/
FileStandardInformation/*要获取的信息类型*/);
if (STATUS_SUCCESS == status)
{
// 保存文件字节数
*size = fsi.EndOfFile.QuadPart;
}
return status;
}
NTSTATUS readFile(HANDLE hFile,
ULONG offsetLow,
ULONG offsetHig,
ULONG sizeToRead,
PVOID pBuff,
ULONG* read)
{
NTSTATUS status;
IO_STATUS_BLOCK isb = { 0 };
LARGE_INTEGER offset;
// 设置要读取的文件偏移
offset.HighPart = offsetHig;
offset.LowPart = offsetLow;
status = ZwReadFile(hFile,/*文件句柄*/
NULL,/*事件对象,用于异步IO*/
NULL,/*APC的完成通知例程:用于异步IO*/
NULL,/*完成通知例程序的附加参数*/
&isb,/*IO状态*/
pBuff,/*保存文件数据的缓冲区*/
sizeToRead,/*要读取的字节数*/
&offset,/*要读取的文件位置*/
NULL);
if (status == STATUS_SUCCESS)
*read = isb.Information;
return status;
}
NTSTATUS writeFile(HANDLE hFile,
ULONG offsetLow,
ULONG offsetHig,
ULONG sizeToWrite,
PVOID pBuff,
ULONG* write)
{
NTSTATUS status;
IO_STATUS_BLOCK isb = { 0 };
LARGE_INTEGER offset;
// 设置要写入的文件偏移
offset.HighPart = offsetHig;
offset.LowPart = offsetLow;
status = ZwWriteFile(hFile,/*文件句柄*/
NULL, /*事件对象,用户异步IO*/
NULL,/*APC例程,用于异步IO*/
NULL, /*APC环境*/
&isb,/*IO状态*/
pBuff,/*写入到文件中的缓冲区*/
sizeToWrite,/*写入的字节数*/
&offset,/*写入到的文件偏移*/
NULL);
if (status == STATUS_SUCCESS)
*write = isb.Information;
return status;
}
NTSTATUS copyFile(wchar_t* srcPath,
wchar_t* destPath)
{
HANDLE hSrc = (HANDLE)-1;
HANDLE hDest = (HANDLE)-1;
NTSTATUS status = STATUS_SUCCESS;
ULONG64 srcSize = 0;
ULONG size = 0;
char* pBuff = NULL;
__try
{
// 1. 先打开源文件
status = createFile(srcPath,
GENERIC_READ,
FILE_SHARE_READ,
FILE_OPEN_IF,
FALSE,
&hSrc);
if (STATUS_SUCCESS != status)
{
__leave;
}
// 2. 获取源文件大小
if (STATUS_SUCCESS != getFileSize(hSrc, &srcSize))
{
__leave;
}
// 3. 分配内存空间保存源文件的数据
pBuff = ExAllocatePool(PagedPool, (ULONG)srcSize);
if (pBuff == NULL)
{
__leave;
}
// 3. 读取源文件的数据到内存中.
status = readFile(hSrc, 0, 0, (ULONG)srcSize, pBuff, &size);
if (STATUS_SUCCESS != status || size != (ULONG)srcSize)
{
__leave;
}
// 4. 打开目标文件
status = createFile(destPath,
GENERIC_WRITE,
FILE_SHARE_READ,
FILE_CREATE,
FALSE,
&hDest);
if (STATUS_SUCCESS != status)
{
__leave;
}
// 5. 将源文件的数据写入到目标文件
status = writeFile(hDest, 0, 0, (ULONG)srcSize, pBuff, &size);
if (STATUS_SUCCESS != status || size != srcSize)
{
__leave;
}
status = STATUS_SUCCESS;
}
__finally
{
// 6. 关闭源文件
if (hSrc != (HANDLE)-1)
{
ZwClose(hSrc);
}
// 7. 关闭目标文件
if (hDest != (HANDLE)-1)
{
ZwClose(hDest);
}
// 8. 释放缓冲区
if (pBuff)
{
ExFreePool(pBuff);
}
}
return status;
}
NTSTATUS moveFile(wchar_t* srcPath, wchar_t* destPath)
{
NTSTATUS status = STATUS_SUCCESS;
// 1. 拷贝一份文件
status = copyFile(srcPath, destPath);
// 2. 如果拷贝成功了,删除源文件
if (status == STATUS_SUCCESS)
{
status = removeFile(srcPath);
}
return status;
}
NTSTATUS removeFile(wchar_t* filepath)
{
UNICODE_STRING path;
RtlInitUnicodeString(&path, filepath);
// 1. 初始化OBJECT_ATTRIBUTES的内容
OBJECT_ATTRIBUTES objAttrib = { 0 };
ULONG ulAttributes = OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE;
InitializeObjectAttributes(&objAttrib, // 返回初始化完毕的结构体
&path, // 文件对象名称
ulAttributes, // 对象属性
NULL, // 根目录(一般为NULL)
NULL); // 安全属性(一般为NULL)
// 2. 删除指定文件/文件夹
return ZwDeleteFile(&objAttrib);
}
NTSTATUS listDirGet(wchar_t* dir, FILE_BOTH_DIR_INFORMATION** fileInfo, ULONG maxFileCount)
{
NTSTATUS status = STATUS_SUCCESS;
IO_STATUS_BLOCK isb = { 0 };
HANDLE hDir = (HANDLE)-1;
VOID* pBuff = NULL;
__try{
// 1. 打开目录
status = createFile(dir,
GENERIC_READ,
FILE_SHARE_READ,
FILE_OPEN_IF,
TRUE,
&hDir);
if (STATUS_SUCCESS != status)
__leave;
// 计算出保存一个文件信息所需的最大字节数 = 结构体大小 + 文件名大小
ULONG signalFileInfoSize = sizeof(FILE_BOTH_DIR_INFORMATION) + 267 * 2;
// 计算出总空间字节数
ULONG totalSize = signalFileInfoSize * maxFileCount;
// 申请内存空间
pBuff = ExAllocatePool(PagedPool, totalSize);
if (pBuff == NULL)
__leave;
// 第一次调用,获取所需缓冲区字节数
status = ZwQueryDirectoryFile(hDir, /*目录句柄*/
NULL, /*事件对象*/
NULL, /*完成通知例程*/
NULL, /*完成通知例程附加参数*/
&isb, /*IO状态*/
pBuff, /*输出的文件信息*/
totalSize,/*文件信息缓冲区的字节数*/
FileBothDirectoryInformation,/*获取信息的类型*/
FALSE,/*是否只获取第一个*/
0,
TRUE/*是否重新扫描目录*/);
// 保存缓冲区的内容首地址.
if (status == STATUS_SUCCESS)
*fileInfo = (FILE_BOTH_DIR_INFORMATION*)pBuff;
}
__finally{
if (hDir != (HANDLE)-1)
{
ZwClose(hDir);
}
if (status != STATUS_SUCCESS && pBuff != NULL)
{
ExFreePool(pBuff);
}
}
return status;
}
NTSTATUS firstFile(wchar_t* dir, HANDLE* hFind, FILE_BOTH_DIR_INFORMATION* fileInfo, int size)
{
NTSTATUS status = STATUS_SUCCESS;
IO_STATUS_BLOCK isb = { 0 };
// 1. 打开目录
status = createFile(dir,
GENERIC_READ,
FILE_SHARE_READ,
FILE_OPEN_IF,
TRUE,
hFind);
if (STATUS_SUCCESS != status)
return status;
// 第一次调用,获取所需缓冲区字节数
status = ZwQueryDirectoryFile(*hFind, /*目录句柄*/
NULL, /*事件对象*/
NULL, /*完成通知例程*/
NULL, /*完成通知例程附加参数*/
&isb, /*IO状态*/
fileInfo, /*输出的文件信息*/
size,/*文件信息缓冲区的字节数*/
FileBothDirectoryInformation,/*获取信息的类型*/
TRUE,/*是否只获取第一个*/
0,
TRUE/*是否重新扫描目录*/);
return status;
}
NTSTATUS nextFile(HANDLE hFind, FILE_BOTH_DIR_INFORMATION* fileInfo, int size)
{
IO_STATUS_BLOCK isb = { 0 };
// 第一次调用,获取所需缓冲区字节数
return ZwQueryDirectoryFile(hFind, /*目录句柄*/
NULL, /*事件对象*/
NULL, /*完成通知例程*/
NULL, /*完成通知例程附加参数*/
&isb, /*IO状态*/
fileInfo, /*输出的文件信息*/
size,/*文件信息缓冲区的字节数*/
FileBothDirectoryInformation,/*获取信息的类型*/
TRUE,/*是否只获取第一个*/
0,
FALSE/*是否重新扫描目录*/);
}
void listDirFree(FILE_BOTH_DIR_INFORMATION* fileInfo)
{
ExFreePool(fileInfo);
}