首页
社区
课程
招聘
[未解决,已结帖] [求助] x64下SSDT Hook *检测* 的一个问题 50.00雪花
2022-11-3 17:46 7601

[未解决,已结帖] [求助] x64下SSDT Hook *检测* 的一个问题 50.00雪花

2022-11-3 17:46
7601

虽然x64下Hook已经过时了, 不过最近有个产品需要Hook检测功能, 所以这个功能还是得有……
然而遇到个奇怪的问题。有点复杂请耐心看完描述。

 

一般的SSDT Hook检测思路: 重载内核,然后比较重载之后内核中SSDT项和系统现有SSDT各项是否一致。

 

俺也一样~
上一点代码:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
VOID temp(ULONG_PTR Addr1, ULONG_PTR Addr2)
{
    ULONG_PTR Index = 0;
    DbgPrint("Addr1: %I64X Addr2: %I64X\r\n", Addr1, Addr2);
    for (Index = 0; Index < 50; ++Index)
    {
        UHALF_PTR Value1 = *(PUHALF_PTR)(Addr1 + Index * sizeof(UHALF_PTR));
        UHALF_PTR Value2 = *(PUHALF_PTR)(Addr2 + Index * sizeof(UHALF_PTR));
        UHALF_PTR Delta = Value2 - Value1;
        HALF_PTR OrgVal = Value2 - Delta;
        HALF_PTR t = OrgVal >> 4;
 
        DbgPrint("Delta: %I64X Value2: %X Value1: %X Original: %X Func: %I64X\r\n", \
            Delta, Value2, Value1, Value2 - Delta, Addr1 + t);
    }
}
 
// 此函数为入口函数
NTSTATUS _utl_KeEnumerateSSDT()
{
    NTSTATUS status = STATUS_UNSUCCESSFUL;
    PKEMODULE_CONTEXT ModuleContext = _utl_KeModuleAllocateContext();
    if (ModuleContext)
    {
        // 遍历内核模块列表
        status = _utl_KeEnumerateKernalModules(ModuleContext);
        if (NT_SUCCESS(status))
        {
            PVOID NewNtoskrnlBase = NULL;
            UNICODE_STRING NtoskrnlName = RTL_CONSTANT_STRING(L"\\??\\C:\\Windows\\System32\\ntoskrnl.exe");
            // Node中存放的是当前内核的各个模块, 第一个是ntoskrnl
            PKE_MODULE_LIST Node = CONTAINING_RECORD(ModuleContext->KeModuleList._next.Flink, KE_MODULE_LIST, _next);
            // 重载ntoskrnl内核
            status = _petl_LdrReloadPE(&NtoskrnlName, ModuleContext, &NewNtoskrnlBase);
            if (NT_SUCCESS(status))
            {
                ULONG_PTR OriginalSSDTBase = NULL, NewSSDTBase = NULL;
                // _utl_KeFixServiceDescriptorTable主要是实现nt!KeCompactServiceTable的逻辑
                // _utl_KeFixServiceDescriptorTable返回当前内核的SSDT表各项的起始地址
                OriginalSSDTBase = _utl_KeFixServiceDescriptorTable(Node->ModuleBase, NewNtoskrnlBase);
                // 计算重载后内核的SSDT表的起始地址: 重载后内核基址 + (原版内核SSDT地址 - 原版内核基址)
                NewSSDTBase = (ULONG_PTR)NewNtoskrnlBase + OriginalSSDTBase - Node->ModuleBase;
                // 打印新老表的信息
                temp(OriginalSSDTBase, NewSSDTBase);
                __debugbreak();
                _petl_LdrFree(NewNtoskrnlBase);
            }
        }
        _utl_KeModuleCleanUp(ModuleContext);
    }
    return status;
}

问题是, 在Win7到Win8的系统上, 新老SSDT各个表项会有一个固定的差值(下图右边的Delta: 0xFFFFFF0),如下图即为上面temp函数的输出:

 

图片描述

 

但是相同的代码, 在Win10及以上的系统上, 却不存在这个差值:

 

图片描述

 

所以我现认为,重载ntoskrnl的代码(_petl_LdrReloadPE函数)应该不存在问题, 不然Windows 10上重载后的SSDT各项应该是对不上原版的值的

所以问题是: Win7上的这个差值是如何产生的呢?

希望做过x64 SSDT Hook检测的同学不吝赐教。

 

下面是一些Win7上关键的数据值:

  • temp函数的输出:

    图片描述

  • 上图中Addr1的符号信息, 以及重载后内核的载入地址(由Addr2减Addr1与原版内核的偏移头得出)

    图片描述

  • 原版内核SSDT表项(nt!KiServiceTable == Addr1)与重载后内核SSDT表项(Addr2)的数据,以及第一个表项的差值

    图片描述


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
点赞0
打赏
分享
最新回复 (12)
雪    币: 687
活跃值: (3261)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
dx苹果的心愿 1 2022-11-3 19:03
2
0
帮顶
雪    币: 1641
活跃值: (3601)
能力值: (RANK:15 )
在线值:
发帖
回帖
粉丝
はつゆき 2022-11-3 20:51
3
0
抛开_petl_LdrReloadPE和_utl_KeFixServiceDescriptorTable,直接从KiServiceTable下手
第一步,映射ntoskrnl.exe进内核,以下称为原始内核
第二步,从原始内核可选头中获取ImageBase,一般情况下是0x140000000
第三步,从原始内核中寻找到KiServiceTable,一般情况下通过SSDT计算得到
第四步,获取并计算原始原始地址,而KiServiceTable中存放的原始数据是会根据系统版本更新变化的
低版本中,为绝对地址,需要KernelBase + (KiServiceTable[Index] - ImageBase)
高版本中,为相对地址,需要KernelBase + KiServiceTable[Index]
最终得到的便是原始函数地址,区分这两种计算方法不难,方法有很多
雪    币: 2329
活跃值: (3259)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
Hacksign 2 2022-11-4 16:40
4
0
はつゆき 抛开_petl_LdrReloadPE和_utl_KeFixServiceDescriptorTable,直接从KiServiceTable下手 第一步,映射ntoskrnl.exe进内核,以下称为 ...
感谢你的回复哈~

我先说一下简短的结论: 你这个算法不对

目前问题我已经解决了, 有点复杂, 正好水一篇文章回回血~ 
雪    币: 1641
活跃值: (3601)
能力值: (RANK:15 )
在线值:
发帖
回帖
粉丝
はつゆき 2022-11-4 17:32
5
0

低版本KernelBase + (KiServiceTable[Index](0x140080C50) - ImageBase(0x140000000))

高版本KernelBase + KiServiceTable[Index](0x33A120)

不太清楚我的算法错在哪了,求指正

雪    币: 2329
活跃值: (3259)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
Hacksign 2 2022-11-4 18:28
6
0
はつゆき 低版本KernelBase + (KiServiceTable[Index](0x140080C50) - ImageBase(0x140000000))高版本KernelBase + KiServi ...

先简单说一下吧, 后面会水文章详细解释。


你要的答案, 全部都在  nt!KeCompactServiceTable 里, 所以强烈建议你看一下这个函数, 简单一点说就是如下逻辑:

        TableSize = ServiceDescriptorTable->ntoskrnl.TableSize;
        ServiceTableBase = ServiceDescriptorTable->ntoskrnl.ServiceTableBase;
        ArgumentTable = ServiceDescriptorTable->ntoskrnl.ArgumentTable;
            
        for (Index = 0; Index < TableSize; ++Index)
        {
            UHALF_PTR FunctionCookie = 0;
            PUHALF_PTR Pointer = ServiceTableBase + Index * 4;
            UINT8 ArgumentCookie = *(PUCHAR)(ArgumentTable + Index);
            switch (OSVersion.dwMajorVersion)
            {
                case NT_MAJOR_VERSION_6:
                    FunctionCookie = (UHALF_PTR)*(PUHALF_PTR)(ServiceTableBase + Index * 8) \
                        - (UHALF_PTR)ServiceTableBase;
                    *Pointer = (16 * FunctionCookie) | (ArgumentCookie >> 2);
                break;
                case NT_MAJOR_VERSION_10:
                    FunctionCookie = (UHALF_PTR)*(PUHALF_PTR)(ServiceTableBase + Index * 4) \
                        - (UHALF_PTR)ServiceTableBase + (UHALF_PTR)NewImageBase;
                    *Pointer = (16 * FunctionCookie) | (ArgumentCookie >> 2);
                break;
            }
        }


Win7-Win8.1的内核, 是把8个字节压缩到4个字节。 Win10开始本身就是4个字节。


第一点, 计算过程需要各个函数参数数据参与 (也就是 | (ArtumentCookie >>1)这个操作), 你的代码中没有考虑到参数的问题,这可能导致你最终计算函数地址最后一个字节会出现偏差。


第二点, 不是什么大问题只是一个实现的时候会遇到的细节, 实操中重载的内核大概率会经过重定位, 重定位后的代码本身就已经减过默认地址 ImageBase(0x140000000) 了, 所以你的公式并不通用, 只能在IDA这个数据下(实际上IDA也给你重定位过了)成立。


任意版本的公式是(X64下):


PUCHAR KiServiceTable = xxxxxx; // xxxxxx为KiServiceTable基址

PVOID FunctionAddr = KiServiceTable + (HALF_PTR)(*(PHALF_PTR)(KiServiceTable + Index * 4)) >> 4;


而上面的*(PHALF_PTR)(KiServiceTable + Index * 4), 即为 *Pointer 的值, 也就是经过 nt!KeCompactServiceTable的值。

雪    币: 1641
活跃值: (3601)
能力值: (RANK:15 )
在线值:
发帖
回帖
粉丝
はつゆき 2022-11-4 18:34
7
0
Hacksign 先简单说一下吧, 后面会水文章详细解释。 你要的答案, 全部都在 &nbsp;nt!KeCompactServiceTable 里, 所以强烈建议你看一下这个函数, 简单一点说就是如下逻辑:& ...
为什么要重载内核?直接映射有问题吗?而且你是不是对IDA的数据有一些误解?为什么IDA还会帮你重定位?
雪    币: 2329
活跃值: (3259)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
Hacksign 2 2022-11-4 18:45
8
0
はつゆき 为什么要重载内核?直接映射有问题吗?而且你是不是对IDA的数据有一些误解?为什么IDA还会帮你重定位?
单就计算SSDT原始值这块, 没有必要重载内核,可以直接映射。
不过我一楼一开始说了, 这是个产品, 同样要兼容x86, 要保证重载的内核可用(老技术了, 例如重载内核过TP……),所以我上面说“不是什么太大的问题”

对于IDA帮你重定位, 只是一种逻辑上的理解, 你可以不认同。 我的证据是,如果你edit->segments->rebase, 然后随便输入个地址(当然要勾选对应选项), 那么IDA的KiServiceTable处的值依然是正确指向对应函数的。那如果你是IDA的作者,在默认载入的情况下,是不是应该用OptionalHeader.ImageBase处取出默认值作为基址,然后发现KiServiceTable“不需要重定位”……
雪    币: 1641
活跃值: (3601)
能力值: (RANK:15 )
在线值:
发帖
回帖
粉丝
はつゆき 2022-11-4 18:57
9
0
Hacksign 单就计算SSDT原始值这块, 没有必要重载内核,可以直接映射。 不过我一楼一开始说了, 这是个产品, 同样要兼容x86, 要保证重载的内核可用(老技术了, 例如重载内核过TP……),所以我上面说“不 ...
KiServiceTable在内核加载以后数据会被修改,当然算法和纯映射的不一样,我告诉你的是把内核文件映射到内存中以后的算法。你自己用我给你的算法去算重载以后的内核,然后又来说我的算法不对。我是没有告诉你这个算法是给文件映射用的吗?
雪    币: 1641
活跃值: (3601)
能力值: (RANK:15 )
在线值:
发帖
回帖
粉丝
はつゆき 2022-11-4 18:59
10
0
Hacksign 单就计算SSDT原始值这块, 没有必要重载内核,可以直接映射。 不过我一楼一开始说了, 这是个产品, 同样要兼容x86, 要保证重载的内核可用(老技术了, 例如重载内核过TP……),所以我上面说“不 ...
我第一步的时候就说了让你映射文件,你又没说你一定要重载内核,我当然把我的方法告诉你,毕竟单从检测钩子来看,重载内核会有很多无用代码
雪    币: 2329
活跃值: (3259)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
Hacksign 2 2022-11-4 19:04
11
0
はつゆき KiServiceTable在内核加载以后数据会被修改,当然算法和纯映射的不一样,我告诉你的是把内核文件映射到内存中以后的算法。你自己用我给你的算法去算重载以后的内核,然后又来说我的算法不对。我是没有 ...
说你的算法不对主要原因是: 没有考虑到参数的运算。
我想再次强调一下, “第二点, 并不是什么太大问题”, 不要过于纠结文件映射到内存的事情。

如果你一定要纠结,那我想按照你的逻辑反驳一下:

我问的是把内核文件重载后如何计算正确地址的问题, 你自己用IDA的数据描述了一遍静态环境下的计算方式, 我是没有在一楼强调用_petl_LdrReloadPE 重载了内核吗?
雪    币: 1641
活跃值: (3601)
能力值: (RANK:15 )
在线值:
发帖
回帖
粉丝
はつゆき 2022-11-4 19:09
12
0
Hacksign 说你的算法不对主要原因是: 没有考虑到参数的运算。 我想再次强调一下, “第二点, 并不是什么太大问题”, 不要过于纠结文件映射到内存的事情。 如果你一定要纠结,那我想按照你的逻辑反驳一下: ...
你什么时候强调过一定要重载内核?你对“强调”这个词似乎也有一些误解。
雪    币: 2329
活跃值: (3259)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
Hacksign 2 2022-11-4 19:12
13
0
はつゆき 你什么时候强调过一定要重载内核?你对“强调”这个词似乎也有一些误解。

或许吧……是我对强调理解的有问题……可能红色还是不够明显。


游客
登录 | 注册 方可回帖
返回