您是否曾经想过看一下操作系统的内幕,看一下其机制的内部结构,拧紧螺钉并看一下已经打开的机会? 也许他们甚至想直接使用硬件,但认为驱动程序是火箭科学?
我建议沿着桥走到核心,看看兔子洞有多深。
因此,我介绍了用C ++ 17编写的内核黑客驱动程序框架,并在可能的情况下设计了该框架,以消除内核和用户模式之间的障碍或尽可能地消除它们的存在。 此外,还提供了一组用户模式和内核API和包装器,以供初学者和高级程序员在Ring0中快速便捷地进行开发。
主要特点:
- 访问I / O端口,以及通过IOPL将in , out , cli和sti指令转发到用户模式
- 环绕系统高音扬声器
- 访问MSR (特定于模型的寄存器)
- 一组用于访问其他进程的用户模式内存和内核内存的函数
- 使用物理内存, DMI / SMBIOS
- 创建用户模式和核流量,APC交付
- 用户模式Ob ***和Ps ***回调以及文件系统过滤器
- 下载未签名的驱动程序和内核库
...还有更多。
我们将从加载框架并将其连接到我们的C ++项目开始。
Github对于组装,强烈建议使用最新版本的Visual Studio和最新可用的WDK(Windows驱动程序工具包),可以从
Microsoft官方网站上下载这些文件。
对于测试,任何容量的免费VMware播放器都安装了Windows,但不低于Windows 7,是完美的。
该程序集很简单,不会引起任何问题:
- 打开内核桥
- 选择所需的位深度
- Ctrl + Shift + B
结果,我们获得了驱动程序,用户模式库以及相关的实用程序文件(用于手动安装的
* .inf ,用于在Microsoft Hardware Certification Publisher上对驱动程序进行签名的
* .cab等)。
要安装驱动程序(如果x64不需要数字签名-相应的EV证书),则需要将系统置于测试模式,而忽略驱动程序的数字签名。 为此,以管理员身份运行命令行:
bcdedit.exe /set loadoptions DISABLE_INTEGRITY_CHECKS
bcdedit.exe /set TESTSIGNING ON
...然后重新启动计算机。 如果一切操作正确,Windows处于测试模式的右下角将出现一个题词。
完成测试环境的设置,让我们开始在项目中使用API。
该框架具有以下层次结构:
/ Kernel-Bridge / API-一组用于驱动程序和
内核模块的函数,没有外部依赖性,可以在第三方项目中自由使用
/ User-Bridge / API-驱动程序和实用程序功能上的一组用户模式包装器,用于处理PE文件,PDB字符等。
/ SharedTypes / -用户模式和核头均包含必要的通用类型
驱动程序可以通过两种方式加载:作为常规驱动程序和作为微型过滤器。 首选第二种方法,因为 允许访问系统事件的筛选器和用户模式回调的高级功能。
因此,在C ++中创建一个控制台项目,连接必要的头文件并加载驱动程序:
#include <Windows.h> #include "WdkTypes.h" // x32/x64 WDK #include "CtlTypes.h" // IOCTL- #include "User-Bridge.h" // API, int main() { using namespace KbLoader; BOOL Status = KbLoadAsFilter( L"X:\\Folder\\Path\\To\\Kernel-Bridge.sys", L"260000" // ); if (!Status) return 0; // ! // API ... // : KbUnload(); return 0; }
太好了! 现在,我们可以使用API并与内核进行交互。
让我们从作弊开发人员环境中最流行的功能开始-读写另一个进程的内存:
using namespace Processes::MemoryManagement; constexpr int Size = 64; BYTE Buffer[Size] = {}; BOOL Status = KbReadProcessMemory(
没什么复杂的! 让我们下一个层次-读写核存储器:
using namespace VirtualMemory; constexpr int Size = 64; BYTE Buffer[Size];
与铁相互作用的功能如何? 例如,I / O端口。
我们通过在EFlags寄存器中锁定2个IOPL位将它们转发到用户模式,这些位负责
in /
out /
cli /
sti指令可用的特权级别。
因此,我们将能够在用户模式下执行它们而不会出现“特权指令”错误:
#include <intrin.h> using namespace IO::Iopl; // , ! KbRaiseIopl(); // in/out/cli/sti ! ULONG Frequency = 1000; // 1 kHz ULONG Divider = 1193182 / Frequency; __outbyte(0x43, 0xB6); // // : __outbyte(0x42, static_cast<unsigned char>(Divider)); __outbyte(0x42, static_cast<unsigned char>(Divider >> 8)); __outbyte(0x61, __inbyte(0x61) | 3); // ( ) for (int i = 0; i < 5000; i++); // Sleep(), IOPL ! __outbyte(0x61, __inbyte(0x61) & 252); // KbResetIopl();
但是真正的自由呢? 毕竟,人们经常想用内核特权执行任意代码。 我们在用户模式下编写所有内核代码,然后从内核转移控制权(SMEP自动关闭,在调用驱动程序之前保存FPU上下文,并且调用本身在
try..except块内进行):
using namespace KernelShells;
但是,除了纵容外壳程序外,还有一项严肃的功能,可让您基于文件,对象和进程过滤器的子系统创建简单的DLP。
该框架允许过滤
CreateFile /
ReadFile /
WriteFile /
DeviceIoControl ,以及打开/复制句柄的事件(
ObRegisterCallbacks )以及启动进程/线程和模块加载的事件(
PsSet *** NotifyRoutine )。 例如,这将允许阻止访问任意文件或替换有关硬盘序列号的信息。
工作原理:
- 驱动程序注册文件过滤器并安装Ob *** / Ps ***回调
- 驱动程序打开一个通讯端口,希望订阅事件的客户端可以连接到该端口
- 用户模式应用程序连接到端口,并从驱动程序接收有关发生的事件的数据,执行过滤(截断权限中的句柄,阻止对文件的访问等),然后将事件返回内核
- 驱动程序将应用收到的更改。
订阅
ObRegisterCallbacks并减少对当前进程的访问的示例:
#include <Windows.h> #include <fltUser.h> #include "CommPort.h" #include "WdkTypes.h" #include "FltTypes.h" #include "Flt-Bridge.h" ... // ObRegisterCallbacks: CommPortListener<KB_FLT_OB_CALLBACK_INFO, KbObCallbacks> ObCallbacks; // PROCESS_VM_READ: Status = ObCallbacks.Subscribe([]( CommPort& Port, MessagePacket<KB_FLT_OB_CALLBACK_INFO>& Message ) -> VOID { auto Data = static_cast<PKB_FLT_OB_CALLBACK_INFO>(Message.GetData()); if (Data->Target.ProcessId == GetCurrentProcessId()) { Data->CreateResultAccess &= ~PROCESS_VM_READ; Data->DuplicateResultAccess &= ~PROCESS_VM_READ; } ReplyPacket<KB_FLT_OB_CALLBACK_INFO> Reply(Message, ERROR_SUCCESS, *Data); Port.Reply(Reply); // });
因此,我们简要介绍了该框架的用户模式部分的要点,但核心API仍然处于幕后。
所有API和包装器都位于相应的文件夹中:
/ Kernel-Bridge / API /它们包括使用内存,使用进程,使用字符串和锁以及更多内容,从而大大简化了自己的驱动程序的开发。 API和包装器仅依赖于它们自己,而不依赖于外部环境:您可以在自己的驱动程序中自由使用它们。
在内核中使用字符串的一个示例是所有初学者的绊脚石:
#include <wdm.h> #include <ntstrsafe.h> #include <stdarg.h> #include "StringsAPI.h" WideString wString = L"Some string"; AnsiString aString = wString.GetAnsi().GetLowerCase() + " and another string!"; if (aString.Matches("*another*")) DbgPrint("%s\r\n", aString.GetData());
如果要为自己的IOCTL代码实现自己的处理程序,则可以根据以下方案很容易地做到这一点:
- 在/Kernel-Bridge/Kernel-Bridge/IOCTLHandlers.cpp中编写处理程序
- 在同一文件中,向DispatchIOCTL函数中Handlers数组的末尾添加一个处理程序 。
- 将查询索引添加到SAME POSITION中CtlTypes.h中的Ctls :: KbCtlIndices枚举中,如项目2中的Handlers数组中所示
- 通过在User-Bridge.cpp中编写包装器,从用户模式调用处理程序,并使用KbSendRequest函数进行调用
支持所有三种类型的I / O(METHOD_BUFFERED,METHOD_NEITHER和METHOD_IN_DIRECT / METHOD_OUT_DIRECT),默认情况下,使用METHOD_NEITHER。
仅此而已! 本文仅涵盖所有可能性的一小部分。 我希望该框架对内核组件的新手,逆向工程师,作弊,反作弊和防护的开发人员有用。
并且,每个人都应邀参与开发。 在未来的计划中:
- 用于直接处理PTE记录并将核内存转发到用户模式的包装器
- 基于现有APC流程创建和传递功能的注入器
- 用于实时逆向工程和Windows内核研究的GUI平台
- 用于执行部分内核代码的脚本引擎
- 在动态加载的模块中支持SEH
- 通过HLK测试
感谢您的关注!