使用PCHunter工具查看object钩子时发现有一种Hook是ObjectType_Callback,Object类型为Process
WRK中的全局类型对象变量中没有这一类型
查看ObTypeIndexTable表,对比有HOOK和无HOOk的情况下,看元素是否有增加,如果是自创类型,在这个表里,应该有增加一项
ObTypeIndexTable在XP系统中不存在,前两项是无效的
当有HOOK的情况下WINDBG中显示如下:
没有HOOK的情况下显示如图:
可以看出数量是没有变化的,从而可以推断出这个HOOK不是新增加对象类型完成的,只是一种描述
断点此类HOOK的函数,看函数什么时候被调用,看看栈回溯,分析下经历的函数
第一个如下:
第一个函数是空的
第二个函数如下:
断点第二个函数,很快会断下来,查看栈回溯
可以看到调用NtOpenProcess的时候会调用到他,这正起到了保护作用,其中最接近他的ObpCallPreOperationCallbacks函数和ObpPreInterceptHandleCreate函数在WRK中都没有。只有ObpCreateHandle在WRK中可以查到,这个函数解释为:This function creates a new handle to an existing object。相当于为了引用一个对象,新建一个句柄,这个句柄就是这个对象的引用方式。至此我们知道了,是引用一个对象的时候才会触发到这个保护函数,看来PCHunter大牛还是很强大的,把它分到了object钩子这类里边很精确。
由于WRK中没有ObpCallPreOperationCallbacks函数和ObpPreInterceptHandleCreate这两个函数,所以为了弄清楚什么时候调用的它必须要反汇编以上两个函数,使用IDA反汇编看,IDA打开内核文件ntkrnlpa,exe在左侧列表中搜索obpPreInterceptHandleCreate函数反汇编如图:
可以看到对象类型表ObTypeIndexTable,看来还是和对象类型有关,在往下看记录找到调用ObpCallPreOperationCallbacks函数的地方
可以看到在开头不远处,发现这个函数代码量也很小,继续跟进这个函数,定位CALL相关的代码,寻找保护函数调用点,一直往下都是系统调用直到这里:
在WINDBG中栈回溯显示的是
9d158758 840df832 xxxx!SampleDouble+0xcdf8
9d1587a0 840dfa1f nt!ObpCallPreOperationCallbacks+0x163
9d1587e8 8402dbfb nt!ObpPreInterceptHandleCreate+0x6f
对比下代码看看是不是这个地方,也可以计算下位置看看是不是这个地方,直接对比下吧,如图:
看来应该这就是调用点,再从CALL EAX中的EAX反推回去,看看怎么来的。通过IDA的鼠标点击变量寄存器变色可以轻松定位出以下过程EAX-》
006D581F mov eax, [edi+18h]-》
006D572C mov edi, [eax]-》
006D5729 mov eax, [ebp+var_C]-》
006D56E7 mov [ebp+var_C], eax-》
006D56DF lea eax, [edi+80h]-》
006D56DD mov edi, eax
说明在这个函数调用时 EAX就是关键所在了,分析上一个函数入调用点
通过前边的截图可以看到 EAX-》
006D5A12 mov eax, edx-》
006D59C0 mov edx, _ObTypeIndexTable[eax*4]
看来一切原因都来自于对象类型。
006D5A12位置的EAX是一个_OBJECT_TYPE WINDBG中看看WIN7中的结构成员
可以推测006D56DF中的EAX就是一个CallbackList了,看看这个名字再看看PCHunter中的ObjectType_Callback真是完全一样啊,回想先前也看了下_OBJECT_TYPE结构但是没注意到这个成员,想必一定是他保存了回调,这是一个双链表。现在就来看看这个对象类型里边的具体内容,想必就应该可以找到保护函数的地址了。
通过前边的分析可以知道再调用ObpCallPreOperationCallbacks函数时的EAX就是当前对象类型的地址,所以在这里设置断点,当然也有可能是其它的函数调用它,虽然知道不正确但是也只有赌一把了,具体操作如下:
可以看到名称是Process这和工具里边显示的是一样,感觉应该就是它了,在具体查看一下CallbackList 看看链表的内容
第一项链表头节点指向首节点a83175f8,首节点也指向首节点86ad3f30,看来只有双向链表中只有一项,查看内容发现红框处就是保护函数的地址,双链表结构也说明还可以增加其他的callback函数。
整体回顾一下 当NtOpenProcess被调用时就会调用到保护函数,这个函数是放在进程对象类型中的CallbackList中的,系统会调用它,且这个结构是双链表,还可以增加其他函数,至此疑问1基本就解决了。
接着来看疑问3,既然知道系统有这么一个机制,肯定应该有相应的系统函数吧,总不可能手工来添加删除吧,既然知道了这个新成员名称就应该搜索下,看看有什么信息 GOOGLE搜索下发现有以下内容
再翻译了下大致就是说ObRegisterCallbacks函数来注册的回调,MSDN搜索发现有这样的解释:The ObRegisterCallbacks routine registers a list of callback routines for thread and process handle operations.
这和我们遇到的情况一样,看来保护函数就是用这个函数来添加的了,怎么卸载呢?看看MSDN的左侧栏
发现一个对应的UN函数 点击查看解释:The ObUnRegisterCallbacks routine unregisters a set of callback routines that were registered with the ObRegisterCallbacks routine.
看来就是它了,两个函数的参数和进程创建卸载的系统回调差不多,具体可以参考《教你在64位Win7系统下使用ObRegisterCallbacks内核函数来实现进程保护》http://bbs.pediy.com/showthread.php?t=168023
我们一起来看一看WDK文档里面对ObRegisterCallbacks()的主要描述:
The ObRegisterCallbacks routine registers a list of callback routines for thread and process handle operations.
NTSTATUS
ObRegisterCallbacks(
IN POB_CALLBACK_REGISTRATION CallBackRegistration,
OUT PVOID *RegistrationHandle
);
CallBackRegistration
A pointer to an OB_CALLBACK_REGISTRATION structure that specifies the list of callback routines and other registration information.
RegistrationHandle
A pointer to a variable that receives a value that identifies the set of registered callback routines. The caller passes this value to the ObUnRegisterCallbacks routine to unregister the set of callbacks.
我们可以看见,有一个对应的ObUnRegisterCallbacks函数,以及其他的说明。
其实保护进程,也就是修改结构体POB_PRE_OPERATION_INFORMATION里面对应的成员值了。
PVOID obHandle;//定义一个void*类型的变量,它将会作为ObRegisterCallbacks函数的第2个参数。
NTSTATUS ProtectProcess(BOOLEAN Enable)
{
OB_CALLBACK_REGISTRATION obReg;
OB_OPERATION_REGISTRATION opReg;
memset(&obReg, 0, sizeof(obReg));
obReg.Version = ObGetFilterVersion();
obReg.OperationRegistrationCount = 1;
obReg.RegistrationContext = NULL;
RtlInitUnicodeString(&obReg.Altitude, L"321000");
memset(&opReg, 0, sizeof(opReg)); //初始化结构体变量
//下面 请注意这个结构体的成员字段的设置
opReg.ObjectType = PsProcessType;
opReg.Operations = OB_OPERATION_HANDLE_CREATE|OB_OPERATION_HANDLE_DUPLICATE;
opReg.PreOperation = (POB_PRE_OPERATION_CALLBACK)&preCall; //在这里注册一个回调函数指针
obReg.OperationRegistration = &opReg; //注意这一条语句
return ObRegisterCallbacks(&obReg, &obHandle); //在这里注册回调函数
}
现在好了,到这里为止,我们已经用上了ObRegisterCallbacks函数了。事情有了一个好的开始,接着马上就要讲实现了。 在这里,我希望你再次打开WDK帮助文档,当然还是有关ObRegisterCallbacks函数的帮助。
我们首先注意上面代码的一条语句:
opReg.PreOperation = (POB_PRE_OPERATION_CALLBACK)&preCall; //在这里注册一个回调函数指针
我们在WDK帮助文档里面找到有关结构体OB_OPERATION_REGISTRATION的说明。我们在这个结构体里面找到 PreOperation结构体成员变量的帮助说明:
PreOperation
A pointer to an ObjectPreCallback routine. The system calls this routine before the requested operation occurs.
我们不需要使用另外一个 成员变量–PostOperation
PostOperation
A pointer to an ObjectPostCallback routine. The system calls this routine after the requested operation occurs.
PostOperation成员不是我们关心的,我们不去讨论它。
好了,这下,你找到了 PreOperation的用法说明了。我们知道了PreOperation是一个函数指针,我们的进程保护程序就在这个函数指针里面进行。
我们再回过头来看上面的程序代码,ProtectProcess函数的实现。 我们刚才就注意到了opReg.PreOperation = (POB_PRE_OPERATION_CALLBACK)&preCall;
现在,我们来实现 preCall函数,代码如下:
OB_PREOP_CALLBACK_STATUS
preCall(PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION pOperationInformation)
{
HANDLE pid = PsGetProcessId((PEPROCESS)pOperationInformation->Object);
char szProcName[16]={0};
UNREFERENCED_PARAMETER(RegistrationContext);
strcpy(szProcName,GetProcessNameByProcessId(pid));
if( !_stricmp(szProcName,“calc.exe”) )
{
if (pOperationInformation->Operation == OB_OPERATION_HANDLE_CREATE)
{
if ((pOperationInformation->Parameters->CreateHandleInformation.OriginalDesiredAccess & PROCESS_TERMINATE) == PROCESS_TERMINATE)
{
pOperationInformation->Parameters->CreateHandleInformation.DesiredAccess &= ~PROCESS_TERMINATE;
}
if ((pOperationInformation->Parameters->CreateHandleInformation.OriginalDesiredAccess & PROCESS_VM_OPERATION) == PROCESS_VM_OPERATION)
{
pOperationInformation->Parameters->CreateHandleInformation.DesiredAccess &= ~PROCESS_VM_OPERATION;
}
if ((pOperationInformation->Parameters->CreateHandleInformation.OriginalDesiredAccess & PROCESS_VM_READ) == PROCESS_VM_READ)
{
pOperationInformation->Parameters->CreateHandleInformation.DesiredAccess &= ~PROCESS_VM_READ;
}
if ((pOperationInformation->Parameters->CreateHandleInformation.OriginalDesiredAccess & PROCESS_VM_WRITE) == PROCESS_VM_WRITE)
{
pOperationInformation->Parameters->CreateHandleInformation.DesiredAccess &= ~PROCESS_VM_WRITE;
}
}
}
return OB_PREOP_SUCCESS;
}
还有,在驱动卸载的时候,要调用ObUnRegisterCallbacks函数。像这样:
VOID
Unload(IN PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
DbgPrint(“driver unloading…\n”);
ObUnRegisterCallbacks(obHandle); //obHandle是上面定义的 PVOID obHandle;
}
代码就这样吗? 是的,这样就可以实现进程保护了。
另外,还需要注意绕过MmVerifyCallbackFunction函数
你只要在百度或Google上,搜索关键字-----绕过MmVerifyCallbackFunction。这会出来很多的文章供你参考。
NTSTATUS status = STATUS_SUCCESS;
PLDR_DATA ldr;
// 绕过MmVerifyCallbackFunction。
ldr = (PLDR_DATA)DriverObject->DriverSection;
ldr->Flags |= 0x20;
绕过MmVerifyCallbackFunction函数其实很容易的。
其实只要定义一个LDR_DATA的结构体。
typedef struct _LDR_DATA // 24 elements, 0xE0 bytes (sizeof)
{
struct _LIST_ENTRY InLoadOrderLinks; // 2 elements, 0x10 bytes (sizeof)
struct _LIST_ENTRY InMemoryOrderLinks; // 2 elements, 0x10 bytes (sizeof)
struct _LIST_ENTRY InInitializationOrderLinks; // 2 elements, 0x10 bytes (sizeof)
VOID* DllBase;
VOID* EntryPoint;
ULONG32 SizeOfImage;
UINT8 PADDING0[0x4];
struct _UNICODE_STRING FullDllName; // 3 elements, 0x10 bytes (sizeof)
struct _UNICODE_STRING BaseDllName; // 3 elements, 0x10 bytes (sizeof)
ULONG32 Flags;
UINT16 LoadCount;
UINT16 TlsIndex;
union
{
struct _LIST_ENTRY HashLinks;
struct
{
VOID* SectionPointer;
ULONG32 CheckSum;
UINT8 PADDING1[0x4];
};
};
union
{
ULONG32 TimeDateStamp;
VOID* LoadedImports;
};
struct _ACTIVATION_CONTEXT* EntryPointActivationContext;
VOID* PatchInformation;
struct _LIST_ENTRY ForwarderLinks;
struct _LIST_ENTRY ServiceTagLinks;
struct _LIST_ENTRY StaticLinks;
VOID* ContextInformation;
UINT64 OriginalBase;
union _LARGE_INTEGER LoadTime;
}LDR_DATA, *PLDR_DATA;