1.内核重载:
2.内核重载的步骤
- 将内核文件加载到内存中
- 将文件展开到内存中的状态
- 通过LDR链获取ntkrnlpa.exe的基址
- 修复新内核的重定位
- 修复新的SSDT表
- 拦截系统调用,HOOK KiFastCallEntry,让自己的HOOK函数判断是走新内核还是老内核
注意:
#include <ntifs.h>
#include <ntimage.h>
PDRIVER_OBJECT g_pDriver = NULL;
#pragma pack(1)
typedef struct _ServiceDesriptorEntry
{
ULONG *ServiceTableBase; // 服务表基址
ULONG *ServiceCounterTableBase; // 计数表基址
ULONG NumberOfServices; // 表中项的个数
UCHAR *ParamTableBase; // 参数表基址
}SSDTEntry, *PSSDTEntry;
#pragma pack()
// 导入SSDT
NTSYSAPI SSDTEntry KeServiceDescriptorTable;
PSSDTEntry g_pNewSSDT;//新的SSDT
ULONG g_JmpPoint;
PUCHAR pHookPoint;
HANDLE KernelCreateFile(
IN PUNICODE_STRING pstrFile, // 文件路径符号链接
IN BOOLEAN bIsDir) // 是否为文件夹
{
HANDLE hFile = NULL;
NTSTATUS Status = STATUS_UNSUCCESSFUL;
IO_STATUS_BLOCK StatusBlock = { 0 };
ULONG ulShareAccess =
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
ULONG ulCreateOpt =
FILE_SYNCHRONOUS_IO_NONALERT;
// 1. 初始化OBJECT_ATTRIBUTES的内容
OBJECT_ATTRIBUTES objAttrib = { 0 };
ULONG ulAttributes =
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE;
InitializeObjectAttributes(
&objAttrib, // 返回初始化完毕的结构体
pstrFile, // 文件对象名称
ulAttributes, // 对象属性
NULL, NULL); // 一般为NULL
// 2. 创建文件对象
ulCreateOpt |= bIsDir ?
FILE_DIRECTORY_FILE : FILE_NON_DIRECTORY_FILE;
Status = ZwCreateFile(
&hFile, // 返回文件句柄
GENERIC_ALL, // 文件操作描述
&objAttrib, // OBJECT_ATTRIBUTES
&StatusBlock, // 接受函数的操作结果
0, // 初始文件大小
FILE_ATTRIBUTE_NORMAL, // 新建文件的属性
ulShareAccess, // 文件共享方式
FILE_OPEN_IF, // 文件存在则打开不存在则创建
ulCreateOpt, // 打开操作的附加标志位
NULL, // 扩展属性区
0); // 扩展属性区长度
if (!NT_SUCCESS(Status))
return (HANDLE)-1;
return hFile;
}
ULONG64 KernelGetFileSize(IN HANDLE hfile)
{
// 查询文件状态
IO_STATUS_BLOCK StatusBlock = { 0 };
FILE_STANDARD_INFORMATION fsi = { 0 };
NTSTATUS Status = STATUS_UNSUCCESSFUL;
Status = ZwQueryInformationFile(
hfile, // 文件句柄
&StatusBlock, // 接受函数的操作结果
&fsi, // 根据最后一个参数的类型输出相关信息
sizeof(FILE_STANDARD_INFORMATION),
FileStandardInformation);
if (!NT_SUCCESS(Status))
return 0;
return fsi.EndOfFile.QuadPart;
}
ULONG64 KernelReadFile(
IN HANDLE hfile, // 文件句柄
IN PLARGE_INTEGER Offset, // 从哪里开始读取
IN ULONG ulLength, // 读取多少字节
OUT PVOID pBuffer) // 保存数据的缓存
{
// 1. 读取文件
IO_STATUS_BLOCK StatusBlock = { 0 };
NTSTATUS Status = STATUS_UNSUCCESSFUL;
Status = ZwReadFile(
hfile, // 文件句柄
NULL, // 信号状态(一般为NULL)
NULL, NULL, // 保留
&StatusBlock, // 接受函数的操作结果
pBuffer, // 保存读取数据的缓存
ulLength, // 想要读取的长度
Offset, // 读取的起始偏移
NULL); // 一般为NULL
if (!NT_SUCCESS(Status)) return 0;
// 2. 返回实际读取的长度
return StatusBlock.Information;
}
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;
union {
LIST_ENTRY HashLinks;
struct {
PVOID SectionPointer;
ULONG CheckSum;
};
};
union {
struct {
ULONG TimeDateStamp;
};
struct {
PVOID LoadedImports;
};
};
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
void * SearchMemory(char * buf, int BufLenth, char * Mem, int MaxLenth)
{
int MemIndex = 0;
int BufIndex = 0;
for (MemIndex = 0; MemIndex < MaxLenth; MemIndex++)
{
BufIndex = 0;
if (Mem[MemIndex] == buf[BufIndex] || buf[BufIndex] == '?')
{
int MemIndexTemp = MemIndex;
do
{
MemIndexTemp++;
BufIndex++;
} while ((Mem[MemIndexTemp] == buf[BufIndex] || buf[BufIndex] == '?') && BufIndex < BufLenth);
if (BufIndex == BufLenth)
{
return Mem + MemIndex;
}
}
}
return 0;
}
void OffProtected()
{
__asm { //关闭内存保护
cli;
push eax;
mov eax, cr0;
and eax, ~0x10000;
mov cr0, eax;
pop eax;
}
}
void OnProtected()
{
__asm { //恢复内存保护
push eax;
mov eax, cr0;
or eax, 0x10000;
mov cr0, eax;
pop eax;
sti;
}
}
ULONG32 MyGetModuleHandle(PUNICODE_STRING pModuleName)
{
PLDR_DATA_TABLE_ENTRY pLdr =
(PLDR_DATA_TABLE_ENTRY)g_pDriver->DriverSection;
LIST_ENTRY *pTemp = &pLdr->InLoadOrderLinks;
do
{
PLDR_DATA_TABLE_ENTRY pDriverInfo =
(PLDR_DATA_TABLE_ENTRY)pTemp;
if (RtlCompareUnicodeString(pModuleName, &pDriverInfo->BaseDllName, FALSE) == 0)
{
return pDriverInfo->DllBase;
}
pTemp = pTemp->Blink;
} while (pTemp != &pLdr->InLoadOrderLinks);
return 0;
}
//windows根据不同的环境,会加载不同的内核文件
//单核,开了PAE
//单核,没开PAE
//多核,开了PAE
//多核,没开PAE
void ReadKernelToBuf(PWCHAR pPath, PUCHAR* pBuf)
{
//-----------------------------------------
UNICODE_STRING pKernelPath; //内核文件路径
HANDLE hFile = 0; //内核文件句柄
LARGE_INTEGER Offset = { 0 };//读取的偏移值
//-----------------------------------------
//1 打开文件
RtlInitUnicodeString(
&pKernelPath,
pPath);
hFile = KernelCreateFile(&pKernelPath, FALSE);
//2 获取文件大小
ULONG64 ulFileSize = KernelGetFileSize(hFile);
*pBuf = ExAllocatePool(NonPagedPool, ulFileSize);
RtlZeroMemory(*pBuf, ulFileSize);
//3 读取文件到内存
KernelReadFile(hFile, &Offset, ulFileSize, *pBuf);
}
void ZKKernel(PUCHAR * pZkBUf, PUCHAR buf)
{
_asm int
//1 获得DOS头,继而获得NT头,再获得扩展头
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buf;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buf);
ULONG uZkSize = pNt->OptionalHeader.SizeOfImage;
//2 申请空间
*pZkBUf = ExAllocatePool(NonPagedPool, uZkSize);
RtlZeroMemory(*pZkBUf, uZkSize);
//3 开始展开
//3.1 先拷贝头部
memcpy(*pZkBUf, buf, pNt->OptionalHeader.SizeOfHeaders);
//3.2再拷贝区段
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt);
for (int i = 0; i < pNt->FileHeader.NumberOfSections; i++)
{
memcpy(
*pZkBUf + pSection[i].VirtualAddress,//本区段内存中的起始位置
buf + pSection[i].PointerToRawData, //本区段在文件中的位置
pSection[i].Misc.VirtualSize //本区段的大小
);
}
}
typedef struct _TYPE {
USHORT Offset : 12;
USHORT Type : 4;
}TYPE, *PTYPE;
void FixReloc(PUCHAR ZkBuf, PUCHAR OldBase)
{
//1 获得DOS头,继而获得NT头,再获得扩展头
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)ZkBuf;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + ZkBuf);
//2 获得重定位表
PIMAGE_DATA_DIRECTORY pRelocDir = (pNt->OptionalHeader.DataDirectory + 5);
PIMAGE_BASE_RELOCATION pReloc = (PIMAGE_BASE_RELOCATION)
(pRelocDir->VirtualAddress + ZkBuf);
//2.5 得到一个老内核与默认基址间的一个差值
ULONG uOffset = (ULONG)OldBase - pNt->OptionalHeader.ImageBase;
//3 开始修复重定位
while (pReloc->SizeOfBlock != 0)
{
ULONG uCount = (pReloc->SizeOfBlock - 8) / 2;//本0x1000内,有多少需要重定位的地方
ULONG uBaseRva = pReloc->VirtualAddress; //本0x1000的起始位置
PTYPE pType = (PTYPE)(pReloc + 1);
for (int i = 0; i < uCount; i++)
{
if (pType->Type == 3)
{
PULONG pRelocPoint = (uBaseRva + pType->Offset + ZkBuf);
//重定位后的地址 - 新基址 = 没重定位的地址 - 默认基址
//所以:重定位后的地址 = 新基址 - 默认基址 + 没重定位的地址
*pRelocPoint = uOffset + *pRelocPoint;
}
pType++;
}
pReloc = (PIMAGE_BASE_RELOCATION)((ULONG)pReloc + pReloc->SizeOfBlock);
}
}
void FixSSDT(PUCHAR pZKBuf, PUCHAR OldBase)
{
//新内核某位置1 - 新内核基址 = 老内核某位置1 - 老内核基址;
//新内核某位置1 = 新内核基址 - 老内核基址 + 老内核某位置1;
LONG Offset = (ULONG)pZKBuf - (ULONG)OldBase;
//1 得到新内核中的SSDT
g_pNewSSDT = (PSSDTEntry)((LONG)&KeServiceDescriptorTable + Offset);
//2 填充系统服务个数
g_pNewSSDT->NumberOfServices = KeServiceDescriptorTable.NumberOfServices;
//3 填充SSDT表
g_pNewSSDT->ServiceTableBase = (ULONG *)((PUCHAR)KeServiceDescriptorTable.ServiceTableBase + Offset);
//让所有的SSDT中保存的函数地址,都指向新内核
for (int i = 0; i < g_pNewSSDT->NumberOfServices; i++)
{
g_pNewSSDT->ServiceTableBase[i] = g_pNewSSDT->ServiceTableBase[i] + Offset;
}
//4 填充参数表
g_pNewSSDT->ParamTableBase = (PULONG)((PUCHAR)KeServiceDescriptorTable.ParamTableBase + Offset);
memcpy(g_pNewSSDT->ParamTableBase,
KeServiceDescriptorTable.ParamTableBase,
g_pNewSSDT->NumberOfServices
);
}
ULONG GetKiFastCallEntry()
{
ULONG uAddress = 0;
_asm
{
push eax;
push ecx;
mov ecx, 0x176;
rdmsr;
mov uAddress, eax;
pop ecx;
pop eax;
}
return uAddress;
}
ULONG FilterFun(ULONG SSdtBase, PULONG OldFun, ULONG Id)
{
//如果相等,说明调用的是SSDT中的函数
if (SSdtBase == (ULONG)KeServiceDescriptorTable.ServiceTableBase)
{
//使用思路:
//假如进程是OD,并且函数调用是190号,就走新内核中的函数,这样通过hookOpenProcess就无法拦住OD了。
return g_pNewSSDT->ServiceTableBase[Id];
}
return OldFun;
}
_declspec(naked)void MyHookFun()
{
//eax 里面是调用号,edx里面是老函数地址,edi里面是SSDT基址
_asm {
pushad;
pushfd;
push eax;
push edx;
push edi;
call FilterFun;
mov dword ptr ds : [esp + 0x18], eax;
popfd;
popad;
sub esp, ecx
shr ecx, 2
jmp g_JmpPoint
}
}
UCHAR Old_Code[5] = { 0 };
void OnHookKiFastCallEntry()
{
char buf[] = { 0x2b, 0xe1, 0xc1, 0xe9, 0x02 };
ULONG KiFastCallEntryAdd = GetKiFastCallEntry();
pHookPoint = SearchMemory(buf, 5, (char*)KiFastCallEntryAdd, 0x200);
g_JmpPoint = (ULONG)(pHookPoint + 5);
memcpy(Old_Code, pHookPoint, 5);
OffProtected();
pHookPoint[0] = 0xE9;
*(ULONG*)(&pHookPoint[1]) = (ULONG)MyHookFun - (ULONG)pHookPoint - 5;
OnProtected();
}
void KernelReload()
{
PUCHAR pBuf = NULL;
PUCHAR pZKBuf = NULL;
UNICODE_STRING KernelName;
//1 首先把内核文件读取到内存里
//ReadKernelToBuf(L"\\??\\C:\\Windows\\System32\\ntoskrnl.exe", &pBuf);
ReadKernelToBuf(L"\\??\\C:\\Windows\\System32\\ntoskrnl.exe", &pBuf);
//2 把读到内存中的内核给展开成0x1000对齐
ZKKernel(&pZKBuf, pBuf);
ExFreePool(pBuf);
//3 修复新内核的重定位
RtlInitUnicodeString(&KernelName, L"ntoskrnl.exe");
ULONG32 uBase = MyGetModuleHandle(&KernelName);
FixReloc(pZKBuf, (PUCHAR)uBase);
//4 修复新的SSDT表
FixSSDT(pZKBuf, (PUCHAR)uBase);
//5 Hook掉KiFastCallEntry,在自己的Hook函数中判断应该走新内核还是老内核
OnHookKiFastCallEntry();
}
void EnumDirver1(PDRIVER_OBJECT pDriver)
{
PLDR_DATA_TABLE_ENTRY pLdr =
(PLDR_DATA_TABLE_ENTRY)pDriver->DriverSection;
LIST_ENTRY *pTemp = &pLdr->InLoadOrderLinks;
do
{
PLDR_DATA_TABLE_ENTRY pDriverInfo =
(PLDR_DATA_TABLE_ENTRY)pTemp;
KdPrint(("%wZ\n", &pDriverInfo->BaseDllName));
pTemp = pTemp->Blink;
} while (pTemp != &pLdr->InLoadOrderLinks);
}
void UnHook()
{
OffProtected();
memcpy(pHookPoint, Old_Code, 5);
OnProtected();
}
VOID DriverUnload(PDRIVER_OBJECT pDriver)
{
pDriver;
UnHook();
KdPrint(("Leave"));
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pPath)
{
UNREFERENCED_PARAMETER(pPath);
DbgBreakPoint();
g_pDriver = pDriver;
//EnumDirver1(pDriver);
KernelReload();
pDriver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}