内核重载听起来是一个很高大上的概念,但其实跟PE的知识息息相关,那么为什么会有内核重载的出现呢?
我们知道从ring3进入ring0需要通过int2e/sysenter(syscall)
进入ring0,而进入ring0之后又会通过KiFastCallEntry/KiSystemService
去找SSDT表对应响应的内核函数,那么杀软会在这两个地方进行重点盯防。
首先是对int2e/sysenter
的盯防,我们知道大多数函数都是通过一系列的调用链,最终找到ntdll.dll
里面的函数,找到调用号后通过int2e/sysenter
的方式进入ring0,杀软首先会hook ntdll.dll
来实现监测的效果,这里的话之前已经介绍过了,我们可以通过自己逆向的方式通过汇编定位到int2e/sysenter
的地址自己重写ring3部分的api来达到绕过杀软的效果
那么再看ring0,我们知道ring3函数进入ring0之后会去找SSDT表,那么这里就有两种监测的方式,一种的话直接在KiSystemService/KiFastCallEntry
挂个钩子,因为无论是什么函数,KiSystemService/KiFastCallEntry
是必经之路,还有一种的话就是通过hook SSDT表里面的函数,但是那样的话会很麻烦,所以杀软一般都是通过前者来实现ring0的监控
我们这里以某数字杀软为例,通过汇编代码的对比,发现某数字杀软在804de978
处更改了一个jmp
指令,我们可以看一下前后的对比
hook前:
sub esp,ecx
shr ecx,2
hook后:
jmp 867bf958
我们知道要使用Inline hook必须要有5个字节的空间,但是KiFastCallEntry
这个函数会有很多寄存器的操作,我们如果随便挑选5个字节去操作的话很可能会蓝屏,我们可以看一下某数字杀软挑选的hook点。在这个地方不仅能得到ssdt的地址,还能得到ssdt地址总表,更能得到ssdt索引号,也就是在这个地方不仅不用我们进行寄存器的操作避免蓝屏,还能够直接拿到ssdt表的信息,可谓是风水宝地
那么我们知道了杀软在ring0的监测原理,我们该如何进行绕过呢?
这里就可以使用到内核重载,内核重载顾名思义,就是复制一份内核的代码,当我们复制一份内核的代码之后,让程序走我们自己复制的这一份内核代码,杀软监控只能监控之前的那份内核代码,从而绕过ring0的监控
思路
复制内核也是有讲究的,我们知道内核文件本质上也遵循PE结构,那么PE文件的文件偏移和内存偏移也是我们需要考量的一个点,不能说我们直接将内核文件copy一份就能够跑起来,这里就需要进行PE的拉伸。那么既然有PE的拉伸,就要涉及到重定位表,我们要想定位到函数,这里肯定就需要进行重定位表的修复
在PE拉伸完成和修复重定位表过后,我们获得了一份新的内核,但是这里SSDT因为是直接拿过来的,地址肯定会发生变化,所以这里就需要进行SSDT表的修复
在上面的一系列操作完成之后,我们就可以进行hook操作,这里我们上面已经分析过KiFastCallEntry
的hook方式,我们在同样的位置设置一个hook即可达到内核重载的效果
PE拉伸&重定位表修复
这里我把PE拉伸跟重定位表的修复放到一个函数里面,首先我们要进行打开文件的操作,那么这里就要实现几个关于文件的函数操作
主要用到ZwCreateFile
、ZwReadFile
、ExAllocatePool
、ExFreePool
这几个函数
// 打开文件
VOID OpenFile(PHANDLE phFile, PUNICODE_STRING DllName)
{
HANDLE hFile = NULL;
NTSTATUS status = STATUS_SUCCESS;
IO_STATUS_BLOCK IoStatus;
OBJECT_ATTRIBUTES FileAttrObject; // 创建文件属性对象
// 初始化 OBJECT_ATTRIBUTES 结构体
InitializeObjectAttributes(&FileAttrObject, DllName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
status = ZwCreateFile(&hFile, GENERIC_ALL, &FileAttrObject, &IoStatus, NULL,FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
if (!NT_SUCCESS(status))
{
DbgPrint("文件创建不成功\n");
return FALSE;
}
if (phFile)
{
*phFile = hFile;
}
return TRUE;
}
// 获取指定文件大小
ULONG GetFileSize(HANDLE hFile)
{
IO_STATUS_BLOCK IoStatus;
NTSTATUS status = STATUS_SUCCESS;
FILE_STANDARD_INFORMATION Fileinfo;
// 获取指定文件大小
status = ZwQueryInformationFile(hFile, &IoStatus, &Fileinfo, sizeof(Fileinfo), FileStandardInformation);
if (!NT_SUCCESS(status))
{
DbgPrint("文件信息查询失败\n");
r