首先了解代码的实现原理, 在内核中钩子实现与用户层的原理都差不多,利用 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 都会经过我们的代码
我们利用各种方式现在都无法打开计算器
2020安全开发者峰会(2020 SDC)议题征集 中国.北京 7月!
最后于 6天前 被灵幻空间编辑 ,原因: 图片不显示