首页
社区
课程
招聘
64位Windows系统挂上内核钩子(不过强制签名)
发表于: 2020-2-4 14:05 10048

64位Windows系统挂上内核钩子(不过强制签名)

2020-2-4 14:05
10048
首先了解代码的实现原理,  在内核中钩子实现与用户层的原理都差不多,利用 InlineHook 修改内核函数的代码,让其修改RIP先跳到我们的函数地址起始,执行完我们的函数再执行原来的函数流程
明白了原理开始实现我们的代码, 再实现代码前我们需要反汇编引擎帮我们计算,所需要的硬编码为多少(关于为什么计算请学习硬编码)
GitHub下载,将其导入我们的头文件中(链接: https://github.com/BeaEngine/lde64)
0环驱动代码实现如下 :
#include <Ntifs.h>
#include <WinDef.h>
#include "LDE64x64.h" // 识别硬编码的长度

// 实现内核钩子(针对PsLookupProcessByProcessId函数)

// 导出函数声明
PCHAR PsGetProcessImageFileName(PEPROCESS Process); // 获取内核对象的进程名称

typedef NTSTATUS
(*pPsLookupProcessByProcessId)(
	_In_ HANDLE ProcessId,
	_Outptr_ PEPROCESS *Process
);
pPsLookupProcessByProcessId OriFun;
ULONG PachSize;

KIRQL irQl;
// 修改Cr0寄存器, 去除写保护(内存保护机制)
KIRQL RemovWP()
{
	DbgPrint("RemovWP\n");
	// (PASSIVE_LEVEL)提升 IRQL 等级为DISPATCH_LEVEL,并返回旧的 IRQL
	// 需要一个高的IRQL才能修改
	irQl = KeRaiseIrqlToDpcLevel();
	ULONG_PTR cr0 = __readcr0(); // 内联函数:读取Cr0寄存器的值, 相当于: mov eax,  cr0;

	// 将第16位(WP位)清0,消除写保护
	cr0 &= ~0x10000; // ~ 按位取反
	_disable(); // 清除中断标记, 相当于 cli 指令,修改 IF标志位
	__writecr0(cr0); // 将cr0变量数据重新写入Cr0寄存器中,相当于: mov cr0, eax
	DbgPrint("退出RemovWP\n");
	return irQl;
}
// 复原Cr0寄存器
KIRQL UndoWP()
{
	DbgPrint("UndoWP\n");
	ULONG_PTR cr0 = __readcr0();
	cr0 |= 0x10000; // WP复原为1
	_disable(); // 清除中断标记, 相当于 cli 指令,清空 IF标志位
	__writecr0(cr0); // 将cr0变量数据重新写入Cr0寄存器中,相当于: mov cr0, eax

	// 恢复IRQL等级
	KeLowerIrql(irQl);
	DbgPrint("退出UndoWP\n");
	return irQl;
}

NTSTATUS
HookPsLookupProcessByProcessId(
	_In_ HANDLE ProcessId,
	_Outptr_ PEPROCESS *Process
)
{
	DbgPrint("进入HookPsLookupProcessByProcessId\n");
	NTSTATUS status = STATUS_SUCCESS;
	status = OriFun(ProcessId, Process); // 执行原来的函数代码
	if (!NT_SUCCESS(status))
	{
		return status;
	}

	if (strcmp(PsGetProcessImageFileName(*Process), "calc.exe") == 0) // 如果目标为计算器
	{
		return STATUS_ACCESS_DENIED; // 返回拒绝访问
	}

	return status;
}

VOID HookKernelRoutine(PVOID ApiAddress/*目标函数*/, PVOID Proxy_Address/*我们的函数*/, 
	PVOID * Ori_Addresss/*存储原来的汇编代码空间*/, ULONG * PatchSize/*存储的汇编指令长度*/)
{
	KdBreakPoint();
	DbgPrint("进入HookKernelRoutine\n");
	// 构建 ShellCode 代码
	char jmp_Code[14] = "\xFF\x25\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF";
	// 跳回源代码的 Code
	char jmp_OriCode[14] = "\xFF\x25\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF";

	DWORD AsmLength = 0;
	DWORD CodeLength = 0;
	PCHAR OriCodeStartAddr = (PCHAR)ApiAddress;

	while (CodeLength < 14) // 循环判断指令长度是否能存下 ShellCode 
	{
		// 通过 LDE 函数获取,当前汇编指令长度
		AsmLength = LDE(ApiAddress/*函数地址*/, 64/*系统位数*/);
		OriCodeStartAddr += AsmLength; // 获取返回到原程序的起始执行地址
		CodeLength += AsmLength;
	}

	*PatchSize = CodeLength; // 存储的汇编指令长度
	
	*Ori_Addresss = ExAllocatePool(NonPagedPool, CodeLength + sizeof(jmp_OriCode)); // 开辟空间存储原来的代码 + 返回原代码地址Code
	// 复制原本的硬编码
	RtlMoveMemory(*Ori_Addresss, ApiAddress, CodeLength);

	// 放入跳回地址
	*(PVOID *)&jmp_OriCode[6] = (PCHAR)ApiAddress + *PatchSize;
	// 将其拷贝到 开辟空间存储的原来的代码 的尾部, 这样就组成了原来的函数代码
	RtlCopyMemory((PCHAR)*Ori_Addresss + *PatchSize, jmp_OriCode, sizeof(jmp_OriCode));

	// 将我们的函数地址存入到 ShellCode 中
	*(PVOID *)&jmp_Code[6] = HookPsLookupProcessByProcessId;
	// 关闭写保护
	RemovWP();
	RtlCopyMemory(ApiAddress, jmp_Code, sizeof(jmp_Code)); // 将 ShellCode 写入目标函数中
	// 恢复写保护
	UndoWP();
	DbgPrint("退出HookKernelRoutine\n");
}

VOID Unload(IN PDRIVER_OBJECT DriverObject)
{
	// 恢复源代码
	// 关闭写保护
	RemovWP();
	RtlCopyMemory(PsLookupProcessByProcessId, OriFun, PachSize); // 将 ShellCode 写入目标函数中
	// 恢复写保护
	UndoWP();

	DbgPrint("卸载驱动\n");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegeditPath)
{
	DbgPrint("加载驱动\n");
	NTSTATUS status = STATUS_SUCCESS;
	DriverObject->DriverUnload = Unload;
	// 初始化反汇编引擎
	LDE_init();

	HookKernelRoutine(PsLookupProcessByProcessId, HookPsLookupProcessByProcessId, &((PVOID)OriFun), &PachSize);

	return status;
}

实现效果如下(运行前我们关闭强制签名):
PCHunter 识别出了内核钩子, 可以看出只要3或0环的任何程序调用 PsLookupProcessByProcessId 都会经过我们的代码
我们利用各种方式现在都无法打开计算器 
 
#include <Ntifs.h>
#include <WinDef.h>
#include "LDE64x64.h" // 识别硬编码的长度

// 实现内核钩子(针对PsLookupProcessByProcessId函数)

// 导出函数声明
PCHAR PsGetProcessImageFileName(PEPROCESS Process); // 获取内核对象的进程名称

typedef NTSTATUS
(*pPsLookupProcessByProcessId)(
	_In_ HANDLE ProcessId,
	_Outptr_ PEPROCESS *Process
);
pPsLookupProcessByProcessId OriFun;
ULONG PachSize;

KIRQL irQl;
// 修改Cr0寄存器, 去除写保护(内存保护机制)
KIRQL RemovWP()
{
	DbgPrint("RemovWP\n");
	// (PASSIVE_LEVEL)提升 IRQL 等级为DISPATCH_LEVEL,并返回旧的 IRQL
	// 需要一个高的IRQL才能修改
	irQl = KeRaiseIrqlToDpcLevel();
	ULONG_PTR cr0 = __readcr0(); // 内联函数:读取Cr0寄存器的值, 相当于: mov eax,  cr0;

	// 将第16位(WP位)清0,消除写保护
	cr0 &= ~0x10000; // ~ 按位取反
	_disable(); // 清除中断标记, 相当于 cli 指令,修改 IF标志位
	__writecr0(cr0); // 将cr0变量数据重新写入Cr0寄存器中,相当于: mov cr0, eax
	DbgPrint("退出RemovWP\n");
	return irQl;
}
// 复原Cr0寄存器
KIRQL UndoWP()
{
	DbgPrint("UndoWP\n");
	ULONG_PTR cr0 = __readcr0();
	cr0 |= 0x10000; // WP复原为1
	_disable(); // 清除中断标记, 相当于 cli 指令,清空 IF标志位
	__writecr0(cr0); // 将cr0变量数据重新写入Cr0寄存器中,相当于: mov cr0, eax

	// 恢复IRQL等级
	KeLowerIrql(irQl);
	DbgPrint("退出UndoWP\n");
	return irQl;
}

NTSTATUS
HookPsLookupProcessByProcessId(
	_In_ HANDLE ProcessId,
	_Outptr_ PEPROCESS *Process
)
{
	DbgPrint("进入HookPsLookupProcessByProcessId\n");
	NTSTATUS status = STATUS_SUCCESS;
	status = OriFun(ProcessId, Process); // 执行原来的函数代码
	if (!NT_SUCCESS(status))
	{
		return status;
	}

	if (strcmp(PsGetProcessImageFileName(*Process), "calc.exe") == 0) // 如果目标为计算器
	{
		return STATUS_ACCESS_DENIED; // 返回拒绝访问
	}

	return status;
}

VOID HookKernelRoutine(PVOID ApiAddress/*目标函数*/, PVOID Proxy_Address/*我们的函数*/, 
	PVOID * Ori_Addresss/*存储原来的汇编代码空间*/, ULONG * PatchSize/*存储的汇编指令长度*/)
{
	KdBreakPoint();
	DbgPrint("进入HookKernelRoutine\n");
	// 构建 ShellCode 代码
	char jmp_Code[14] = "\xFF\x25\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF";
	// 跳回源代码的 Code
	char jmp_OriCode[14] = "\xFF\x25\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF";

	DWORD AsmLength = 0;
	DWORD CodeLength = 0;
	PCHAR OriCodeStartAddr = (PCHAR)ApiAddress;

	while (CodeLength < 14) // 循环判断指令长度是否能存下 ShellCode 
	{
		// 通过 LDE 函数获取,当前汇编指令长度
		AsmLength = LDE(ApiAddress/*函数地址*/, 64/*系统位数*/);
		OriCodeStartAddr += AsmLength; // 获取返回到原程序的起始执行地址
		CodeLength += AsmLength;
	}

	*PatchSize = CodeLength; // 存储的汇编指令长度
	
	*Ori_Addresss = ExAllocatePool(NonPagedPool, CodeLength + sizeof(jmp_OriCode)); // 开辟空间存储原来的代码 + 返回原代码地址Code
	// 复制原本的硬编码
	RtlMoveMemory(*Ori_Addresss, ApiAddress, CodeLength);

	// 放入跳回地址
	*(PVOID *)&jmp_OriCode[6] = (PCHAR)ApiAddress + *PatchSize;
	// 将其拷贝到 开辟空间存储的原来的代码 的尾部, 这样就组成了原来的函数代码
	RtlCopyMemory((PCHAR)*Ori_Addresss + *PatchSize, jmp_OriCode, sizeof(jmp_OriCode));

	// 将我们的函数地址存入到 ShellCode 中
	*(PVOID *)&jmp_Code[6] = HookPsLookupProcessByProcessId;
	// 关闭写保护
	RemovWP();
	RtlCopyMemory(ApiAddress, jmp_Code, sizeof(jmp_Code)); // 将 ShellCode 写入目标函数中
	// 恢复写保护
	UndoWP();
	DbgPrint("退出HookKernelRoutine\n");
}

VOID Unload(IN PDRIVER_OBJECT DriverObject)
{
	// 恢复源代码
	// 关闭写保护
	RemovWP();
	RtlCopyMemory(PsLookupProcessByProcessId, OriFun, PachSize); // 将 ShellCode 写入目标函数中
	// 恢复写保护
	UndoWP();

	DbgPrint("卸载驱动\n");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegeditPath)
{
	DbgPrint("加载驱动\n");
	NTSTATUS status = STATUS_SUCCESS;
	DriverObject->DriverUnload = Unload;
	// 初始化反汇编引擎
	LDE_init();

	HookKernelRoutine(PsLookupProcessByProcessId, HookPsLookupProcessByProcessId, &((PVOID)OriFun), &PachSize);

	return status;
}

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2020-2-4 14:09 被灵幻空间编辑 ,原因: 图片不显示
上传的附件:
收藏
免费 2
支持
分享
最新回复 (7)
雪    币: 1
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
支持大佬!学习一下
2020-2-4 17:58
0
雪    币: 2510
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2020-2-4 18:42
3
雪    币: 46
活跃值: (56)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
x64下PG很难受
2020-2-6 12:00
0
雪    币: 1290
活跃值: (2332)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
5
是的,一般过PG的都是大公司才有,这方面国外的资料可以看看
2020-2-9 15:34
0
雪    币: 1290
活跃值: (2332)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
6
黑眼 x64下PG很难受
是的,一般过PG的都是大公司才有,这方面国外的资料可以看看 
2020-2-9 15:34
0
雪    币: 364
活跃值: (1741)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
7
已读
2020-3-13 09:43
0
雪    币: 751
活跃值: (3833)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
8
不错
最后于 2020-3-21 23:09 被appview编辑 ,原因:
2020-3-19 14:09
0
游客
登录 | 注册 方可回帖
返回
//