HOOK钩子技术4 SSDT HOOK

来源:互联网 发布:java web中启动线程池 编辑:程序博客网 时间:2024/05/22 03:30

API 调用过程

以一个demo测试为例,本测试查看OpenProcessR3-R0 下的调用。

// test.cpp : Defines the entry point for the console application.//#include "stdafx.h"#include <windows.h>#include <stdio.h>int main(int argc, char* argv[]){//  __asm int 3    HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS,FALSE,2548);    printf("handle=%08x",handle);    return 0;}

RING3

载入OD具体分析调用。

ring3 application space00401028  |.  8BF4          mov esi, esp0040102A  |.  68 F4090000   push 0x9F4       ; /ProcessId = 9F40040102F  |.  6A 00         push 0x0         ; |Inheritable = FALSE00401031  |.  68 FF0F1F00   push 0x1F0FFF    ; |Access = PROCESS_ALL_ACCESS00401036  |.  FF15 4CA14200 call dword ptr ds:[<&KERNEL32.OpenProcess>]  ; \OpenProcess

此时由测试进程模块 进入到kernel32 模块

--kernel32.dll (OpenProcess)7C830A0B    8975 E8         mov dword ptr ss:[ebp-0x18], esi7C830A0E    8975 F0         mov dword ptr ss:[ebp-0x10], esi7C830A11    8975 F4         mov dword ptr ss:[ebp-0xC], esi7C830A14    FF15 1C11807C   call dword ptr ds:[<&ntdll.NtOpenProcess>]; ntdll.ZwOpenProcess----ntdll.dll(NtOpenProcess (or ZWOpenProcess))7C92D5E0 >  B8 7A000000     mov eax, 0x7A7C92D5E5    BA 0003FE7F     mov edx, 0x7FFE03007C92D5EA    FF12            call dword ptr ds:[edx]                            ; ntdll.KiFastSystemCall7C92E4F0 >  8BD4            mov edx, esp7C92E4F2    0F34            sysenter                                           ; ring3->ring07C92E4F4 >  C3              retn

在R3下的函数调用过程:本地模块->kernel32(openprocess)->ntdll(zwOpenProcess)->ntdll(sysenter)

ring3级别的函数调用栈回朔地址       堆栈       函数过程 / 参数                       调用来自                      结构........   ........        sysenter                          ntdll.KiFastSystemCall+0xC(7C92E4F2) ...0012FEE0   7C92D5EC   包含ntdll.KiFastSystemCall              ntdll.7C92D5EA                0012FF1C0012FEE4   7C830A1A   ntdll.ZwOpenProcess                   kernel32.7C830A14             0012FF1C0012FF20   0040103C   kernel32.OpenProcess                  test.00401036                 0012FF1C0012FF24   001F0FFF     Access = PROCESS_ALL_ACCESS0012FF28   00000000     Inheritable = FALSE0012FF2C   000009F4     ProcessId = 9F40012FF84   00401239   test.00401005                         test.<模块入口点>+0E4              0012FF80

也可以用windbg 去查看,更简单,但是调试就不能是local kernel 模式了,往windbg 中拖拽一个可执行文件后,查看结构

0:000> u kernel32!openprocess l30            //l30表示取30条指令,只求多,不求少kernel32!OpenProcess:7c8309d1 8bff            mov     edi,edi7c8309d3 55              push    ebp7c8309d4 8bec            mov     ebp,esp7c8309d6 83ec20          sub     esp,20h7c8309d9 8b4510          mov     eax,dword ptr [ebp+10h]7c8309dc 8945f8          mov     dword ptr [ebp-8],eax7c8309df 8b450c          mov     eax,dword ptr [ebp+0Ch]7c8309e2 56              push    esi7c8309e3 33f6            xor     esi,esi7c8309e5 f7d8            neg     eax7c8309e7 1bc0            sbb     eax,eax7c8309e9 83e002          and     eax,27c8309ec 8945ec          mov     dword ptr [ebp-14h],eax7c8309ef 8d45f8          lea     eax,[ebp-8]7c8309f2 50              push    eax7c8309f3 8d45e0          lea     eax,[ebp-20h]7c8309f6 50              push    eax7c8309f7 ff7508          push    dword ptr [ebp+8]7c8309fa 8d4510          lea     eax,[ebp+10h]7c8309fd 50              push    eax7c8309fe 8975fc          mov     dword ptr [ebp-4],esi7c830a01 c745e018000000  mov     dword ptr [ebp-20h],18h7c830a08 8975e4          mov     dword ptr [ebp-1Ch],esi7c830a0b 8975e8          mov     dword ptr [ebp-18h],esi7c830a0e 8975f0          mov     dword ptr [ebp-10h],esi7c830a11 8975f4          mov     dword ptr [ebp-0Ch],esi7c830a14 ff151c11807c    call    dword ptr [kernel32!_imp__NtOpenProcess (7c80111c)]7c830a1a 3bc6            cmp     eax,esi7c830a1c 5e              pop     esi7c830a1d 0f8cdf710000    jl      kernel32!OpenProcess+0x53 (7c837c02)7c830a23 8b4510          mov     eax,dword ptr [ebp+10h]7c830a26 c9              leave7c830a27 c20c00          ret     0Ch

可以看到,从导入表中调用了NtOpenProcess

7c830a14 ff151c11807c    call    dword ptr [kernel32!_imp__NtOpenProcess (7c80111c)]

很遗憾,这里没有告诉NtOpenProcess 属于哪个模块(其实是ntdll 模块),没关系,如果符号唯一的话,可以windbg可以识别出来
再次跟进NtOpenProcess

0:000> u ntopenprocess l4ntdll!ZwOpenProcess:7c92d5e0 b87a000000      mov     eax,7Ah7c92d5e5 ba0003fe7f      mov     edx,offset SharedUserData!SystemCallStub (7ffe0300)7c92d5ea ff12            call    dword ptr [edx]7c92d5ec c21000          ret     10h

这里告诉我们是ntdll 模块。同时可以注意到ntdll下NtOpenProcess 和ZwOpenProcess 是同一个函数。当然,不确定的话可以尝试一下:

0:000> u zwopenprocess l4ntdll!ZwOpenProcess:7c92d5e0 b87a000000      mov     eax,7Ah7c92d5e5 ba0003fe7f      mov     edx,offset SharedUserData!SystemCallStub (7ffe0300)7c92d5ea ff12            call    dword ptr [edx]7c92d5ec c21000          ret     10h

可以看到,它们两个是同一个函数。

跟进 [edx]=[7ffe0300]=?

0:000> dd 7ffe03007ffe0300  7c92e4f0 7c92e4f4 00000000 000000007ffe0310  00000000 00000000 00000000 000000007ffe0320  00000000 00000000 00000000 000000007ffe0330  32c5a0a6 00000000 00000000 000000007ffe0340  00000000 00000000 00000000 000000007ffe0350  00000000 00000000 00000000 000000007ffe0360  00000000 00000000 00000000 000000007ffe0370  00000000 00000000 00000000 000000000:000> u 7c92e4f0 l3*****ntdll!KiFastSystemCall:*****7c92e4f0 8bd4            mov     edx,esp7c92e4f2 0f34            sysenterntdll!KiFastSystemCallRet:7c92e4f4 c3              ret

可以看到7c92e4f2 0f34 sysenter ,实现r3到r0的转化。个人感觉用windbg 更快速一点,更适合查看数据结构,而不适合ring3 下的动态调试(可能也是因为我不熟吧)。

RING0

执行sysenter 之后,就与ring3 没有关系了,此时进入ring0 ,调试器只能选择windbg ,用local kernel 调试分析。

有一点是默认的,进入内核之后,会自动调用对应的nt!ZwOpenProcess .(保留意见-update:5.14)

ring0 kernel system spacent!ZwOpenProcess:804ff720 b87a000000      mov     eax,7Ah804ff725 8d542404        lea     edx,[esp+4]804ff729 9c              pushfd804ff72a 6a08            push    8804ff72c e850ed0300      call    nt!KiSystemService (8053e481)804ff731 c21000          ret     10hnt!ZwOpenProcessToken:804ff734 b87b000000      mov     eax,7Bh804ff739 8d542404        lea     edx,[esp+4]804ff73d 9c              pushfd804ff73e 6a08            push    8804ff740 e83ced0300      call    nt!KiSystemService (8053e481)804ff745 c20c00          ret     0Chnt!ZwOpenProcessTokenEx:804ff748 b87c000000      mov     eax,7Ch804ff74d 8d542404        lea     edx,[esp+4]804ff751 9c              pushfd804ff752 6a08            push    8804ff754 e828ed0300      call    nt!KiSystemService (8053e481)804ff759 c21000          ret     10h call    nt!KiSystemService (8053e481)做了什么:从eax中获得ssdt数组索引,访问ssdt找到nt!NtOpenProcess地址,执行最终的内核函数nt!NtOpenProcessnt!NtOpenProcess:805c2296 68c4000000      push    0C4h805c229b 68a8aa4d80      push    offset nt!ObWatchHandles+0x25c (804daaa8)805c22a0 e86b6cf7ff      call    nt!_SEH_prolog (80538f10).......805c250d eb05            jmp     nt!NtOpenProcess+0x27e (805c2514)805c250f b8300000c0      mov     eax,0C0000030h805c2514 e8326af7ff      call    nt!_SEH_epilog (80538f4b)805c2519 c21000          ret     10h

因此可以得到以下流程顺序:

OpenProcessring3: our module        ->kernel32.dll(OpenProcess)            ->ntdll(zw(nt)OpenProcess) (ssdt 索引号在eax中)                    ->system call                     ring0:                        ->ntoskrnl.exe(nt!ZWOpenProcess) (同样存放ssdt索引号在eax中)                                -> ntoskrnl.exe(nt!KiSystemService)                                        ->ntoskrnl.exe(nt!NtOpenProccess)

update2:5.14
这幅图是我查找资料时得到的,大致说的还行。
这里写图片描述

但是还有些问题,这个图的画法就不说了。记得我前面说了:

有一点是默认的,进入内核之后,会自动调用对应的nt!ZwOpenProcess .(保留意见-update:5.14)

这幅图作者的意思是说,切换时触发中断后,一定是先执行nt!KiSystemService ,也就是一定不是先执行nt!ZwOpenProcess(内部其实也是在调用KiSystemService),这和我之前的说法有些出入 。还有说nt!zw* 不触发中断,什么意思。下个断点去研究了一下。

kd> bc *kd> bp nt!zwOpenProcesskd> bp nt!ntOpenProcesskd> bl 0 e 804ff720     0001 (0001) nt!ZwOpenProcess 1 e 805c2296     0001 (0001) nt!NtOpenProcesskd> gBreakpoint 1 hitnt!NtOpenProcess:805c2296 68c4000000      push    0C4h

***********************
可以看到,打开一个程序后,断点在Nt* 上,没有在ZW* 上,所以我之前的说法是错误的。在本例中使用ntdll.KiFastSystemCall ,并没有进入到nt!ZWOpenProcess 中。
其实最好的方式是在KiSystemService 中下断点。但是这个符号基本上随时都在调用,根本调试不了。我想到的方法是添加条件断点,可惜现在还不会啊。。。
***********************

或者说,要研究的问题是:

既然r3下已经有了nt!nt* 函数在ssdt 中的索引号,通过nt!KiSystemservice 检索就可以直接执行目标内核函数。那么nt!ZW* 再包装一次索引号的价值是什么。优势在哪里。


ntoskrnl.exe(nt!KiSystemService) 我没有研究过,汇编有好多行,不过现在知道主要功能如下。

通过eax(ssdt索引号)找到对应的nt!nt*函数地址,执行此函数,此函数为最终的内核函数。

SSDT技术原理

上面概括性的讲了可以通过ssdt索引号 找到原始API函数地址,现在具体分析下SSDT 的数据结构和查找过程。
首先需要了解如何找到SSDT:

typedef struct _SERVICE_DESCRIPTOR_TABLE{    PULONG ServiceTableBase;  //ssdt的指针    PULONG ServiceCounterTableBase;    ULONG  NumberOfService;   //SSDT中存放的NATIVE API 地址数据,即可索引数目。    PUCHAR ParamTableBase;}SERVICE_DESCRIPTOR_TABLE,*PSERVICE_DESCRIPTOR_TABLE;

ntoskrnl.exe 中导出了PSERVICE_DESCRPITOR_TABLE 类型指针,变量为KeServiceDescriptorTable,在编程中导出即可。

来看下这个结构,使用windbg选择local kernel

lkd> dd KeServiceDescriptorTable l480553fa0  80502b8c 00000000 0000011c 80503000

从上面的数据结构可以看出,ssdt的地址为80502b8c ,可索引的函数有0x11c 个。
SSDT是个数组结构,里面存放了所有的nt!nt* 函数地址,我们来查看下SSDT数组。

lkd> dd 80502b8c l0x11c80502b8c  8059a948 805e7db6 805eb5fc 805e7de880502b9c  805eb636 805e7e1e 805eb67a 805eb6be80502bac  8060cdfe 8060db50 805e31b4 805e2e0c80502bbc  805cbde6 805cbd96 8060d424 805ac5ae...

或者直接输入一下命令。

lkd> dd kiservicetable80502b8c  8059a948 805e7db6 805eb5fc 805e7de880502b9c  805eb636 805e7e1e 805eb67a 805eb6be80502bac  8060cdfe 8060db50 805e31b4 805e2e0c80502bbc  805cbde6 805cbd96 8060d424 805ac5ae...

可以看到,第一种方法是通过KeServiceDescriptorTable 得到ssdt地址,再查看。第二种是直接用符号查看。遗憾的是,编程中只能用第一种方法来获取,因为ntoskrnl.exe 没有导出这个符号变量。来验证一下。

C:\WINDOWS\system32>dumpbin /exports ntoskrnl.exe |findstr "KeService"        609  252 00083220 KeServiceDescriptorTableC:\WINDOWS\system32>dumpbin /exports ntoskrnl.exe |findstr "KiService"C:\WINDOWS\system32>

不管上面的小插曲,来验证下关于SSDT数组存放最终API地址的说法是否正确。

nt!ZwOpenProcess:804ff720 b87a000000      mov     eax,7Ah804ff725 8d542404        lea     edx,[esp+4]804ff729 9c              pushfd804ff72a 6a08            push    8804ff72c e850ed0300      call    nt!KiSystemService (8053e481)

从上方可以得到nt!NTOpenProcess 地址在SSDT 表中的索引为7Ah。接下来查找一下其地址。

lkd> dd kiservicetable +0x7A*4 l180502d74  805c2296lkd> u 805c2296nt!NtOpenProcess:805c2296 68c4000000      push    0C4h805c229b 68a8aa4d80      push    offset nt!ObWatchHandles+0x25c (804daaa8)805c22a0 e86b6cf7ff      call    nt!_SEH_prolog (80538f10)805c22a5 33f6            xor     esi,esi805c22a7 8975d4          mov     dword ptr [ebp-2Ch],esi805c22aa 33c0            xor     eax,eax805c22ac 8d7dd8          lea     edi,[ebp-28h]805c22af ab              stos    dword ptr es:[edi]

可见,猜想是正确的。
这时候再谈论SSDT HOOK就很简单了。SSDT表中存放的是目标函数地址,改写这个函数地址,就可以为所欲为了。从流程上说,我们的挂钩点是在调用ring0下的nt!KiSystemService 时触发的。

DEMO

#include "stdafx.h"#ifdef __cplusplusextern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING  RegistryPath);#endifvoid SSDTHookUnload(PDRIVER_OBJECT);//这个结构不是SSDT,是服务描述表,不过能从成员中能找到SSDT而已。typedef struct _SERVICE_DESCRIPTOR_TABLE{    PULONG ServiceTableBase;    PULONG ServiceCounterTableBase;    ULONG  NumberOfService;    PUCHAR ParamTableBase;}SERVICE_DESCRIPTOR_TABLE,*PSERVICE_DESCRIPTOR_TALBE;//必须extern "C" ,因为文件为CPP//extern "C" __declspec(dllimport) PSERVICE_DESCRIPTOR_TALBE KeServiceDescriptorTable; --此句错误,会出现无法挂钩,找了很长时间的bug。。可能是声明__declspec(dllimport)的姿势不对吧。看来是资料查错了,好好去补基础。extern "C"  PSERVICE_DESCRIPTOR_TALBE KeServiceDescriptorTable;//搬运工typedef NTSTATUS (*NtCreateProcessEx)(                OUT PHANDLE           ProcessHandle,                IN ACCESS_MASK        DesiredAccess,                IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,                IN HANDLE             ParentProcess,                IN BOOLEAN            InheritObjectTable,                IN HANDLE             SectionHandle OPTIONAL,                IN HANDLE             DebugPort OPTIONAL,                IN HANDLE             ExceptionPort OPTIONAL ); //函数指针,本程序中存放的是原始函数地址NtCreateProcessEx ulNtCreateProcessEx = NULL;//SSDT中此函数地址的指针ULONG ulNtCreateProcessExAddr = 0;/*xp及其以后,SSDT内核内存页为只读属性,为了改写需要修改CR0 WP位cr0的第16位是WP位,只要将这一位置0就可写,置1则只读。*/void REMOVE_ONLY_READ(){    __asm    {        push eax        mov eax,CR0        and eax,~10000h //16th bit is 0        mov CR0,eax        pop eax    }}void RESET_ONLY_READ(){    __asm    {        push eax        mov eax,CR0        or eax,10000h   //16th bit is 1        mov CR0,eax        pop eax    }}//fake function ,声明要和NtCreateProcessEx一致。NTSTATUS MyNtCreateProcessEx(                             OUT PHANDLE           ProcessHandle,                             IN ACCESS_MASK        DesiredAccess,                             IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,                             IN HANDLE             ParentProcess,                             IN BOOLEAN            InheritObjectTable,                             IN HANDLE             SectionHandle OPTIONAL,                             IN HANDLE             DebugPort OPTIONAL,                             IN HANDLE             ExceptionPort OPTIONAL                              ){    NTSTATUS Status = STATUS_SUCCESS;    DbgPrint("have hook CreateProcess!\r\n");    Status = ulNtCreateProcessEx(                            ProcessHandle,                            DesiredAccess,                            ObjectAttributes,                            ParentProcess,                            InheritObjectTable,                            SectionHandle ,                            DebugPort ,                            ExceptionPort         );    return Status;}VOID HOOKCreateProcess(){    ULONG ulSsdt = 0;    ulSsdt = (ULONG)KeServiceDescriptorTable->ServiceTableBase;    ulNtCreateProcessExAddr = ulSsdt + 0x30*4 ; //index is 0x30    ulNtCreateProcessEx = (NtCreateProcessEx)*(PULONG)ulNtCreateProcessExAddr; //real addr    REMOVE_ONLY_READ();    *(PULONG)ulNtCreateProcessExAddr = (ULONG)MyNtCreateProcessEx;    RESET_ONLY_READ();}NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING  RegistryPath){// #ifdef DBG//  __asm int 3// #endif    DbgPrint("Hello from SSDTHook!\n");    DriverObject->DriverUnload = SSDTHookUnload;    HOOKCreateProcess();    return STATUS_SUCCESS;}void SSDTHookUnload(IN PDRIVER_OBJECT DriverObject){    REMOVE_ONLY_READ();    //当时两边都写成ulNtCreateProcessExAddr 了。。。。    *(PULONG)ulNtCreateProcessExAddr = (ULONG)ulNtCreateProcessEx;    RESET_ONLY_READ();    DbgPrint("Goodbye from SSDTHook!\n");}

show time

DeBugView输出结果
这里写图片描述

用Kernel Detective查看,发现已被修改
这里写图片描述

0 0
原创粉丝点击