一、ObRegisterCallbacks的实现
ObRegisterCallbacks可以在调用在NtOpenProcess调用时进行权限的过滤, 比如清除(终止进程)PROCESS_TERMINATE, (对进程内存读写)PROCESS_VM_OPERATION 等操作。
实现代码就直接给出非常简单的几行代码。
#include <ntddk.h>
PVOID pRegistrationHandle;
VOID DriverUnload(
_In_ struct _DRIVER_OBJECT *DriverObject
)
{
KdPrint(("DriverUnload\n"));
// 卸载驱动
if (NULL != pRegistrationHandle)
{
KdPrint(("ObUnRegisterCallbacks\n"));
ObUnRegisterCallbacks(pRegistrationHandle);
}
PDEVICE_OBJECT pDev;
pDev = DriverObject->DeviceObject;
if (NULL != pDev)
{
IoDeleteDevice(pDev); // 删除设备
}
}
#define PROCESS_CREATE_PROCESS (0x80)
extern "C" UCHAR *
PsGetProcessImageFileName(
__in PEPROCESS Process
);
//extern int* ObTypeIndexTable;
OB_PREOP_CALLBACK_STATUS PreProcessHandle(
_In_ PVOID RegistrationContext,
_Inout_ POB_PRE_OPERATION_INFORMATION OperationInformation
)
{
PVOID hProcess = OperationInformation->Object; // 操作句柄
UCHAR *szImageName = PsGetProcessImageFileName((PEPROCESS)hProcess);
if (0 != strcmp((const char *)szImageName, "calc.exe"))
{
return OB_PREOP_SUCCESS;
}
// 区分操作类型
switch (OperationInformation->Operation)
{
case OB_OPERATION_HANDLE_DUPLICATE:
KdPrint(("OB_OPERATION_HANDLE_DUPLICATE\n"));
break;
case OB_OPERATION_HANDLE_CREATE:
KdPrint(("OB_OPERATION_HANDLE_CREATE\n"));
OperationInformation->Parameters->CreateHandleInformation.DesiredAccess = 0;
break;
}
return OB_PREOP_SUCCESS;
}
VOID PostProcessHandle(
_In_ PVOID RegistrationContext,
_In_ POB_POST_OPERATION_INFORMATION OperationInformation
)
{
}
#pragma code_seg("INIT")
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegisterPath)
{
KdPrint(("DriverEntry\n"));
// 指定驱动卸载函数
pDriverObject->DriverUnload = DriverUnload;
UNICODE_STRING usDeviceName; // 设备对象名称
RtlInitUnicodeString(&usDeviceName, L"\\Device\\ObTestDevice");
PDEVICE_OBJECT pDev;
NTSTATUS status;
status = IoCreateDevice(pDriverObject, 0, &usDeviceName, FILE_DEVICE_UNKNOWN, 0, true, &pDev);
if (!NT_SUCCESS(status))
{
return status;
}
pDev->Flags |= DO_BUFFERED_IO;
OB_OPERATION_REGISTRATION obr;
obr.ObjectType = PsProcessType;
obr.Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE; // 创建和复制
obr.PreOperation = PreProcessHandle;
obr.PostOperation = PostProcessHandle;
OB_CALLBACK_REGISTRATION ocr;
ocr.Version = OB_FLT_REGISTRATION_VERSION; // 版本
ocr.RegistrationContext = NULL; // 自定义数据
ocr.OperationRegistrationCount = 1; // 回调函数个数
ocr.OperationRegistration = &obr;
RtlInitUnicodeString(&ocr.Altitude, L"321000"); // 加载顺序
pRegistrationHandle = NULL;
if (NT_SUCCESS(ObRegisterCallbacks(&ocr, &pRegistrationHandle)))
{
// 注册保护成功
KdPrint(("Protect Success!!!"));
}
else
{
// 注册保护失败
KdPrint(("Protect Failure!!!"));
}
return STATUS_SUCCESS;
}
但是ObRegisterCallbacks这个函数会验证驱动, 这里我们直接PATCH掉就好了。
用IDA加载内核查看ObRegisterCallbacks这个函数
在调试的时候用WINDBG 把 JZ 指令替换为NOP就可以了。
我们这里保护的是计算器(calc.exe)。
驱动启动成功之后任务管理器就不管结束calc.exe进程,当然OD也不可以附加调试。
二、如何反ObRegisterCallBacks
相对于实现,估计大家都是比较关心如何处理ObRegisterCallBacks保护的进程,对于ObRegisterCallbacks保护的进程如何使用OD进行附加调试。
首先我们打开 XT 查看一下钩子:
发现在Object钩子下有PreOperation和PostOperation的钩子。这个就是我们驱动挂的钩子。那么XT是何如检测这个钩子的?
WINDBG 首先设置好符号路劲
然后我们在我们的回调函数 PreProcessHandle 下一个断点。
bp KMDFDriver2!PreProcessHandle
运行起来就会立马断下来.
查看一下堆栈,确认是从NtOpenProcess调用过来的,否则继续运行。
可以看到堆栈的第一层
01 ae4fb7a0 83eeb1f3 nt!ObpCallPreOperationCallbacks+0x163
是从ObpCallPreOperationCallbacks函数调用过来的。
我们切换到第一层堆栈, 然后查看一下上文的代码
.frame 1
ub ObpCallPreOperationCallbacks+0x163 L10
发现他是通过CALL eax 去调用我们的回调函数的。
eax 是通过 [edi + 0x18] 获取到的。
那 edi 的值是怎么来的呢。 通过查看windbg去看上面的汇编代码不好找。我们用IDA 查看一下 ObpCallPreOperationCallbacks这个函数的代码。
我们选中edi 让edi变成高亮的状态,然后查找给edi赋值的地方。
从这幅图我们可以看出
edi -> eax
eax -> edi+80h
[ebp+var_c] -> eax
eax -> [ebp+var_c]
edi -> [eax]
而edi + 80h中的edi 来源于上层的 nt!ObpPreInterceptHandleCreate+0x6f 传入的 eax
我们继续用IDA查看一下nt!ObpPreInterceptHandleCreate+0x6f这个函数看看eax的值是怎么来的。
mov edx, _ObTypeIndexTable[eax*4]
eax的值最终来源于ObTypeIndexTable这个对象数组。但是具体的下标值我们还不知道。
我们在ObpPreInterceptHandleCreate下断点看一下NtOpenProcess调用时的下标的值。
bp ObpPreInterceptHandleCreate
发现eax = 7。
现在我们来总结一下得到的数据
[[_ObTypeIndexTable[eax * 4] + 0x80] + 0x18] 就是我们回调函数的地址。
数据是找到了。但是这只是其中一个回调函数,但是ObRegisterCallbacks是可以注册多个回调函数的。其他的回调函数呢?
我们注意在IDA中查看这个函数
ObpCallPreOperationCallbacks
他会去 比较 edi 和 eax的值 当他们相等的时候,回调函数就不会执行。
eax = [_ObTypeIndexTable[eax * 4] + 0x80
mov edi, [eax]
cmp edi, eax
他会去比较 [_ObTypeIndexTable[eax 4] + 0x80 地址的值和 [[_ObTypeIndexTable[eax 4] + 0x80]的值。
我们用windbg查看一下 [_ObTypeIndexTable[eax * 4] + 0x80的值,我们把程序运行到比较的地方
dd eax
dd edi
我们发现 [eax] 中保存了 edi 的地址.
但是 [edi] 中也保存了 eax 的地址.
看到这里大家应该知道了 其实[_ObTypeIndexTable[eax * 4] + 0x80其实是一个循环单链表的结构,如果头结点和尾节点相同代表就是一个空链表。
大家可以多注册一些回调函数 这样可以看的更明白一些。
好了 现在我们开始写代码,来解决 ObRegisterCallbacks 的保护。
首先我们要获取到_ObTypeIndexTable的地址。但是很不幸的是 _ObTypeIndexTable 这个全局变量不是一个导出的符号我们不能直接使用。
我们用IDA 查看一个 _ObTypeIndexTable 的交叉应用。看看能不能找到合适的函数。
我找到半天发现这个函数是可以通过传入一个结构体,结构体里面设置一个索引。来获取到_ObTypeIndexTable的值。
完整代码如下:
#include <ntddk.h>
PVOID pRegistrationHandle;
VOID DriverUnload(
_In_ struct _DRIVER_OBJECT *DriverObject
)
{
KdPrint(("DriverUnload\n"));
PDEVICE_OBJECT pDev;
pDev = DriverObject->DeviceObject;
if (NULL != pDev)
{
IoDeleteDevice(pDev); // 删除设备
}
}
struct ObIdxInfo
{
int idx;
int _4;
int _8;
int _c;
};
void AntiObRegister()
{
typedef int *(*ObGetObjectType)(int *);
UNICODE_STRING name;
RtlInitUnicodeString(&name, L"ObGetObjectType");
ObGetObjectType GetObjectType = (ObGetObjectType)MmGetSystemRoutineAddress(&name);
if (NULL == GetObjectType)
{
KdPrint(("ObGetObjectType Get Failure"));
return ;
}
const int OBLink = 7; // ObRegisterCallbacks 链表 [ObTypeIndexTable[7 * 4] + 0x80]
ObIdxInfo info;
info.idx = OBLink;
int *LinkContext = GetObjectType(&info._c); // 取出 OBLink的值
int *LinkHead = (int *)((char *)LinkContext + 0x80); // 取出 + 80
int *NodeHead = (int *)(*LinkHead);
if (NodeHead == LinkHead)
{
KdPrint(("没有检测到ObRegisterCallbacks HOOK!"));
return ;
}
do
{
DbgPrint("PRE HOOK FUNC ADDR: %0X\n", *(int *)((char *)NodeHead + 0x18));
NodeHead = (int *)(*NodeHead); // 指向下一个节点
} while (LinkHead != NodeHead);
__asm {//去掉内存保护
cli
mov eax, cr0
and eax, not 10000h
mov cr0, eax
}
KdPrint(("清除所有HOOK\n"));
*LinkHead = (int)LinkHead; // 将下一个节点指向自己 变成一个空链表
__asm {//恢复内存保护
mov eax, cr0
or eax, 10000h
mov cr0, eax
sti
}
}
#pragma code_seg("INIT")
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegisterPath)
{
KdPrint(("DriverEntry\n"));
// 指定驱动卸载函数
pDriverObject->DriverUnload = DriverUnload;
UNICODE_STRING usDeviceName; // 设备对象名称
RtlInitUnicodeString(&usDeviceName, L"\\Device\\ObAntiTest");
PDEVICE_OBJECT pDev;
NTSTATUS status;
status = IoCreateDevice(pDriverObject, 0, &usDeviceName, FILE_DEVICE_UNKNOWN, 0, true, &pDev);
if (!NT_SUCCESS(status))
{
return status;
}
pDev->Flags |= DO_BUFFERED_IO;
AntiObRegister();
return STATUS_SUCCESS;
}
编译完成我们运行一下,在DebugView中会输出信息
会打印出所有的回调函数并且清除回调函数。这个时候我们就可以用OD附加调试了。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课