在XP下的HOOK方式
在XP下可以使用的HOOK方式,包含 SSDTHOOK
INLINEHOOK
OBJECTHOOK
等,
其中 SSDTHOOK
OBJECTHOOK
相对于 INLINEHOOK
更稳定,但是也更容易被移除。
SSDTHOOK的原理
SSDT全称 System Services Descriptor Table
中文叫 系统服务描述符表
它是一个地址数组,
保存着由R3切换到R0时,所用函数的索引地址。在XP中导出了一个描述SSDT的结构体
1 2 3 4 5 6
| typedef struct _SSDT_TABLE { PULONG ServiceTableBase; PULONG ServiceCounterTableBase; ULONG NumberOfServices; PUCHAR ParamTableBase; } SSDT_TABLE, *PSSDT_TABLE;
|
我们在使用时,需要先声明一下这个导出的结构体变量
1
| NTSYSAPI SSDT_TABLE KeServiceDescriptorTable;
|
那么如何查找某函数对应的索引呢,比如 ZwTerminateProcess
这个函数,我们先看一下反汇编
1 2 3 4 5 6 7 8
| 0: kd> u ZwTerminateProcess nt!ZwTerminateProcess: 80502140 b801010000 mov eax,101h 80502145 8d542404 lea edx,[esp+4] 80502149 9c pushfd 8050214a 6a08 push 8 8050214c e800030400 call nt!KiSystemService (80542451) 80502151 c20800 ret 8
|
这个函数的功能就是,把 0x101
索引放入到 eax
中,然后通过 KiSystemService
访问索引的函数,
索引 0x101
处存储的就是 NtTerminateProcess
函数,这个函数才是真实的 退出进程
功能函数。
可以看到 0x101
正是 ZwTerminateProcess
函数 起始地址+1字节
的位置,所以可以这样定义一个宏
1 2
| #define SERVICE_ID(Service) (*(PULONG)((ULONG)(Service) + 1))
|
同时我们也可以借助于一些ARK工具来验证这个索引是否正确,如下为PCHunter显示的信息
而我们HOOK函数 NtTerminateProcess
就是用我们自己的函数地址,替换 0x101
索引处的地址,
这样系统调用 NtTerminateProcess
函数时就会调用我们的函数,当我们检测完放行时,还得调用原始
函数,执行正常功能,如果我们不放行,就可以直接返回 STATUS_ACCESS_DENIED
进行拦截操作。
1 2 3 4
| NTSTATUS NtTerminateProcess( _In_opt_ HANDLE ProcessHandle, _In_ NTSTATUS ExitStatus );
|
如下为HOOK函数,以及相关调用代码
1 2 3 4 5 6 7
| void SSDTHook(IN PULONG FuncAddr, IN PVOID ProxyAddr, OUT PULONG OrigianlAddr) { WPOff(); *OrigianlAddr = *(PULONG)FuncAddr; *(PULONG)FuncAddr = (ULONG)ProxyAddr; WPOn(); }
|
1 2 3 4 5 6
| ULONG OriginalNtTerminateProcess = 0;
SSDTHook( &KeServiceDescriptorTable.ServiceTableBase[SERVICE_ID(ZwTerminateProcess)], ProxyNtTerminateProcess, &OriginalNtTerminateProcess);
|
由于我们需要写系统关键处的内存,所以就需要先关闭写保护,如下为相关汇编代码
1 2 3 4 5 6 7 8 9 10 11
| void WPOff() { __asm { cli mov eax, cr0 and eax, not 10000h mov cr0, eax } }
|
1 2 3 4 5 6 7 8 9 10 11
| void WPOn() { __asm { mov eax, cr0 or eax, 10000h mov cr0, eax sti } }
|
最后是我们自定义函数的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| typedef NTSTATUS(*PNTP)(HANDLE, NTSTATUS);
NTSTATUS ProxyNtTerminateProcess(HANDLE ProcessHandle, NTSTATUS ExitStatus) { NTSTATUS Status = STATUS_SUCCESS; do { if (ProcessHandle == NULL) break; if (ProcessHandle == (HANDLE)-1) break; PsGetCurrentProcessId(); if (!NT_SUCCESS(Status)) return STATUS_ACCESS_DENIED; } while (0); if (OriginalNtTerminateProcess != 0) return ((PNTP)OriginalNtTerminateProcess)(ProcessHandle, ExitStatus); return STATUS_ACCESS_DENIED; }
|
OBJECTHOOK的原理
所谓的OBJECTHOOK就是对进程内核对象结构体中,对应的几个进程相关操作函数地址进行替换,
一般情况下我们都是替换OpenProcedure函数的地址,这个函数在打开进程句柄时会被调用。
如何找到这些信息?在不同的操作系统中,内核对象结构体是不同的,所以需要我们搭建VMWARE调试
环境,通过WINDBG来实际查看目标系统的信息,这里以 Windows Server 2003 x64
系统为例进行说明。
操作系统中的所有进程内核对象,是一个链表结构,链表头部是一个全局变量,名称为 PsProcessType
1
| extern POBJECT_TYPE *PsProcessType;
|
这个全局变量已经被导出,我们可以使用 dq
指令来查看64位内存地址
1 2 3 4 5
| 1: kd> dq PsProcessType fffff800`011d1fb8 fffffadf`e7a6da00 fffffadf`e7a6d6c0 fffff800`011d1fc8 fffffa80`00002ba0 00000000`00000002 fffff800`011d1fd8 00000000`00000000 00000000`00000000 fffff800`011d1fe8 00000000`00000000 00000000`00000000
|
因为它是一个 OBJECT_TYPE
结构体类型指针,我们查看对应结构体信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 1: kd> dt _OBJECT_TYPE fffffadf`e7a6da00 ACPI!_OBJECT_TYPE +0x000 Mutex : _ERESOURCE +0x068 TypeList : _LIST_ENTRY [ 0xfffffadf`e7a6da68 - 0xfffffadf`e7a6da68 ] +0x078 Name : _UNICODE_STRING "Process" +0x088 DefaultObject : (null) +0x090 Index : 5 +0x094 TotalNumberOfObjects : 0x16 +0x098 TotalNumberOfHandles : 0x70 +0x09c HighWaterNumberOfObjects : 0x18 +0x0a0 HighWaterNumberOfHandles : 0x70 +0x0a8 TypeInfo : _OBJECT_TYPE_INITIALIZER +0x118 Key : 0x636f7250 +0x120 ObjectLocks : [4] _ERESOURCE
|
其中 TypeList
成员,是一个链表头,可以通过遍历这个链表,来找到所有的进程对象。
然后查看 TypeInfo
成员,对应结构体为 OBJECT_TYPE_INITIALIZER
类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 1: kd> dx -r1 (*((ACPI!_OBJECT_TYPE_INITIALIZER *)0xfffffadfe7a6daa8)) (*((ACPI!_OBJECT_TYPE_INITIALIZER *)0xfffffadfe7a6daa8)) [Type: _OBJECT_TYPE_INITIALIZER] [+0x000] Length : 0x70 [Type: unsigned short] [+0x002] UseDefaultObject : 0x0 [Type: unsigned char] [+0x003] CaseInsensitive : 0x0 [Type: unsigned char] [+0x004] InvalidAttributes : 0xb0 [Type: unsigned long] [+0x008] GenericMapping [Type: _GENERIC_MAPPING] [+0x018] ValidAccessMask : 0x1f0fff [Type: unsigned long] [+0x01c] SecurityRequired : 0x1 [Type: unsigned char] [+0x01d] MaintainHandleCount : 0x0 [Type: unsigned char] [+0x01e] MaintainTypeList : 0x0 [Type: unsigned char] [+0x020] PoolType : NonPagedPool (0) [Type: _POOL_TYPE] [+0x024] DefaultPagedPoolCharge : 0x1000 [Type: unsigned long] [+0x028] DefaultNonPagedPoolCharge : 0x438 [Type: unsigned long] [+0x030] DumpProcedure : 0x0 [Type: void (__cdecl*)(void *,_OBJECT_DUMP_CONTROL *)] [+0x038] OpenProcedure : 0x0 [Type: long (__cdecl*)(_OB_OPEN_REASON,_EPROCESS *,void *,unsigned long,unsigned long)] [+0x040] CloseProcedure : 0x0 [Type: void (__cdecl*)(_EPROCESS *,void *,unsigned long,unsigned __int64,unsigned __int64)] [+0x048] DeleteProcedure : 0xfffff800012875b0 [Type: void (__cdecl*)(void *)] [+0x050] ParseProcedure : 0x0 [Type: long (__cdecl*)(void *,void *,_ACCESS_STATE *,char,unsigned long,_UNICODE_STRING *,_UNICODE_STRING *,void *,_SECURITY_QUALITY_OF_SERVICE *,void * *)] [+0x058] SecurityProcedure : 0xfffff800012884f0 [Type: long (__cdecl*)(void *,_SECURITY_OPERATION_CODE,unsigned long *,void *,unsigned long *,void * *,_POOL_TYPE,_GENERIC_MAPPING *,char)] [+0x060] QueryNameProcedure : 0x0 [Type: long (__cdecl*)(void *,unsigned char,_OBJECT_NAME_INFORMATION *,unsigned long,unsigned long *,char)] [+0x068] OkayToCloseProcedure : 0x0 [Type: unsigned char (__cdecl*)(_EPROCESS *,void *,void *,char)]
|
到这里可以看到 OpenProcedure
函数指针目前内容为 0x0
,对应的函数类型为
1
| long (__cdecl*)(_OB_OPEN_REASON,_EPROCESS *,void *,unsigned long,unsigned long)
|
经过计算,可以得出 OpenProcedure
指针的地址,相对于 OBJECT_TYPE
类型偏移为 0xE0
,也就是说
((ULONGLONG)*PsProcessType + 0xE0)
表示当前 OpenProcedure
指针的位置,通过替换该指针的内容,
就可以实现OBJECTHOOK操作,如下为处理函数的相关定义和内容
1 2 3 4 5 6 7 8 9
| typedef enum _OB_OPEN_REASON { ObCreateHandle = 0x0, ObOpenHandle = 0x1, ObDuplicateHandle = 0x2, ObInheritHandle = 0x3, ObMaxOpenReason = 0x4 } OB_OPEN_REASON, *POB_OPEN_REASON;
typedef NTSTATUS(*OPENPROCEDURE)(OB_OPEN_REASON, PEPROCESS, PVOID, ULONG, ULONG);
|
1 2 3 4 5 6 7
| ULONGLONG OldOpenProcedure = 0;
void SetObjectHook() { OldOpenProcedure = *(ULONGLONG*)((ULONGLONG)*PsProcessType + 0xE0); *(ULONGLONG*)((ULONGLONG)*PsProcessType + 0xE0) = (ULONGLONG)MyOpenProcedure; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| NTSTATUS MyOpenProcedure(OB_OPEN_REASON Reason, PEPROCESS Process, PVOID Object, ULONG arg4, ULONG arg5) { BOOLEAN bDeny = FALSE; UNREFERENCED_PARAMETER(arg4); UNREFERENCED_PARAMETER(arg5); do { if (PsGetProcessId(Process) == (HANDLE)4) break; if (PsGetProcessId(Object) == (HANDLE)4) break; if (Reason == ObCreateHandle) break; if ((PVOID)Object == (PVOID)Process) break; (PUNICODE_STRING*)((PUCHAR)Process + 0x318); if (bDeny) return STATUS_ACCESS_DENIED; } while (0); return STATUS_SUCCESS; }
|
注意,不同的系统中 OpenProcedure
的 指针位置
以及 函数类型
有可能是不同的,不能照搬使用。