首页
论坛
课程
招聘
[原创]QQ电脑管家中的TsFltMgr Hook框架分析
2012-2-6 17:43 33853

[原创]QQ电脑管家中的TsFltMgr Hook框架分析

2012-2-6 17:43
33853
QQ电脑管家中的TsFltMgr Hook框架分析

新版的QQ电脑管家中多了一个名字叫TsFltMgr.sys的驱动(应该是Sysnap大牛开发的,膜拜),对该驱动进行了一些简单的分析,看见了一套漂亮的Hook框架,发出来与大家分享。分析不对的地方请多多包涵。

首先TsFltMgr挂钩了KiFastCallEntry函数,Hook点在这里:

kd> u KiFastCallEntry+e3
nt!KiFastCallEntry+0xe3:
8053dbb3 c1e902        shr     ecx,2
-------------------------------------------------------------------------
8053dbb6 90            nop
8053dbb7 90            nop
8053dbb8 90            nop
8053dbb9 e962170c77    jmp     TsFltMgr+0x2320 (f75ff320)
-------------------------------------------------------------------------
8053dbbe 0f83a8010000  jae     nt!KiSystemCallExit2+0x9f (8053dd6c)
8053dbc4 f3a5          rep movs dword ptr es:[edi],dword ptr [esi]
8053dbc6 ffd3          call    ebx


原始的KiFastCallEntry在 shr ecx, 2 指令后面应该是 mov edi,esp;cmp esi, MmUserProbeAddress,共8个字节,在这里被 TsFltMgr 替换成了3个nop和一个jmp。

该jmp会跳转到 KiFastCallEntry_Detour 函数中,KiFastCallEntry_Detour 函数代码如下:

// 保存现场
pushfd        
pushad        

// 调用 KiFastCallEntry_Filter 函数,实现过滤
push edi                    // 本次系统调用对应的SysCall Table的地址(SSDT或SSDTShadow的地址)
push ebx                    // 本次系统调用在SysCall Table中对应的内核函数地址
push eax                    // 本次系统调用对应的内核函数在SysCall Table中的功能号
call KiFastCallEntry_Filter // 调用KiFastCallEntry_Filter,实现过滤
mov  [esp+10h], eax         // 更改本次调用对应的内核函数地址!

// 恢复现场
popad        
popfd

// 执行 KiFastCallEntry 函数中被替换掉的指令,并跳回原函数
mov     edi,esp
cmp     esi, g_7fff0000
push    g_JmpBack
ret


这里需要注意的是 call KiFastCallEntry_Filter 之后的 mov [esp+10h], eax。之前保存现场时的指令pushad会导致寄存器EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI依次入栈,并通过后面的popad指令恢复这些寄存器的值。因此此处的mov [esp+10h], eax实际上是用 KiFastCallEntry_Filter 函数的返回值来改写堆栈中保存的ebx的值,即改写本次系统调用对应的内核函数地址。

KiFastCallEntry_Filter 是真正实现过滤的函数,该函数的参数和返回值上文已经说明了,其具体实现分析整理后,C语言描述如下:

ULONG __stdcall KiFastCallEntry_Filter(ULONG ulSyscallId, ULONG ulSyscallAddr, PULONG pulSyscallTable) 
{
    PFAKE_SYSCALL pFakeSysCall = NULL;

    if ( ulSyscallId >= 0x400 ) 
        return ulSyscallAddr;

    if ( pulSyscallTable == g_KiServiceTable && ulSyscallId <= g_ServiceNum/* 0x11c */ ) 
    {
        pFakeSysCall = g_FakeSysCallTable[ulSyscallId];        // SSDT
    }
    else if (pulSyscallTable == g_KeServiceDescriptorTable && 
             g_KeServiceDescriptorTable && ulSyscallId <= g_ServiceNum/* 0x11c */)
    {
        pFakeSysCall = g_FakeSysCallTable[ulSyscallId];        // SSDT
    }
    else if (pulSyscallTable == g_W32pServiceTableAddr && ulSyscallId <= g_ShadowServiceNum/* 0x29b */)
    {
        pFakeSysCall = g_FakeSysCallTable[ulSyscallId + 1024]; // ShadowSSDT
    }

    if ( pFakeSysCall && pFakeSysCall->ulFakeSysCallAddr )
    {
        pFakeSysCall->ulOrigSysCallAddr = ulSyscallAddr;
        return pFakeSysCall->ulFakeSysCallAddr;
    }
    return ulSyscallAddr;
}


这里需要说明的是,TsFltMgr内部有一张表,暂且命名为 g_FakeSysCallTable,该表中存放的是指向 FAKE_SYSCALL 结构的指针。表中的每一个 FAKE_SYSCALL 结构对应一个系统调用,表的前半部分对应SSDT中的系统调用,1024项以后对应ShadowSSDT里的系统调用。

其中 FAKE_SYSCALL 结构大致如下(其中很多域的作用没弄明白):

typedef struct __FAKE_SYSCALL__ {
    ULONG xxx1;
    ULONG ulSyscallId;        // 该系统调用的功能号
    ULONG xxx3;
    ULONG ulTableIndex;    
    ULONG xxx5;
    ULONG ulCountForPreWork;
    ULONG ulCountForPostWork;
    ULONG xxx8;
    ULONG ulOrigSysCallAddr;    // 真实的系统调用地址
    ULONG ulFakeSysCallAddr;    // 假的系统调用地址
    ULONG xxx11;
    ULONG xxx12;
    ULONG xxx13;
    ……
} FAKE_SYSCALL, *PFAKE_SYSCALL, **PPFAKE_SYSCALL;


因此 KiFastCallEntry_Filter 函数的所做的就是根据系统调用的功能号在 g_FakeSysCallTable 中索引出对应的 pFakeSysCall 对象,然后判断该系统调用是否需要hook,如果需要则将真实的系统调用地址保存到 pFakeSysCall->ulOrigSysCallAddr 中,并将 pFakeSysCall->ulFakeSysCallAddr 作为假系统调用的地址返回。

这种调用过程中动态获取真实系统调用地址的方法使 TsFltMgr 的Hook框架有较高的兼容性,例如不会使加载顺序晚于TsFltMgr的驱动中的SSDT Hook失效,例如QQ电脑管家本身带的TSKsp.sys驱动。

对于我的测试系统(XP_SP2),TsFltMgr hook的函数有:

// SSDT中:
NtCreateFile、NtCreateKey、NtCreateSection、NtCreateSymbolicLinkObject、NtCreateThread、NtDeleteFile、NtDeleteKey、NtDeleteValueKey、NtDeviceIoControlFile、NtDuplicateObject、NtEnumerateValueKey、NtLoadDriver、NtOpenProcess、NtOpenSection、NtProtectVirtualMemory、NtQueryValueKey、NtRequestWaitReplyPort、NtSetContextThread、NtSetInformationFile、NtSetSystemInformation、NtSetValueKey、NtSuspendThread、NtSystemDebugControl、NtTerminateProcess、NtTerminateThread、NtWriteFile、NtWriteVirtualMemory

// ShadowSSDT中:
NtUserBuildHwndList、NtUserFindWindowEx、NtUserGetForegroundWindow、NtUserMoveWindow、NtUserQueryWindow、NtUserSendInput、NtUserSetParent、NtUserSetWindowLong、NtUserSetWindowPlacement、NtUserSetWindowPos、NtUserShowWindow、NtUserShowWindowAsync、NtUserWindowFromPoint


所有假系统函数都有统一的代码框架,假系统函数的代码框架大致如下:

NTSTATUS __stdcall FakeNt_XXX(xxx)
{
    PFAKE_SYSCALL pFakeSysCall;
    ULONG ulXXX = 0;
    ULONG ulStatus;
    NTSTATUS status;
    ULONGLONG ullTickCount;
    
    pFakeSysCall = g_pFakeSysCall_Nt_XXX;  // 该系统调用对应的 pFakeSysCall 对象
    
    status = STATUS_ACCESS_DENIED;
  

    // 貌似是做性能测试时候需要的,实际版本中 g_bPerformanceTest 为 FALSE
    if ( g_bPerformanceTest ) {
        ullTickCount = KeQueryInterruptTime();
    }


    // 系统调用的调用前处理!
    // +++
    InterlockedIncrement(&pFakeSysCall->ulCountForPreWork);
    ulStatus = PreWork(&ulXXX, pFakeSysCall);
    InterlockedDecrement(&pFakeSysCall->ulCountForPreWork);
    // ---
    
    if ( ulStatus != 0xEEEE0004 && ulStatus != 0xEEEE0005) 
    {    
        OrigSysCall * pOrigSysCall = pFakeSysCall->ulOrigSysCallAddr;

        // 调用原始系统调用!
        if ( pOrigSysCall && NT_SUCCESS(pOrigSysCall(xxx)) ) 
        {
            // 系统调用的调用后处理!
            // +++
            InterlockedIncrement(&pFakeSysCall->ulCountForPostWork),
            ulStatus = PostWork(&ulXXX),
            InterlockedDecrement(&pFakeSysCall->ulCountForPostWork),
            // ---
        }
    }

    // 0xEEEE0004 应该是拒绝调用的意思,0xEEEE0005 应该是允许调用的意思
    if (ulStatus == 0xEEEE0005)
        status = STATUS_SUCCESS;

    // PsGetCurrentProcessId 这个调用的返回值后面并没有用到,可能是多余的
    PsGetCurrentProcessId();

    // 貌似是做性能测试时候需要的
    if ( g_pFakeSysCall_NtTerminateProcess->xxx5 && ullTickCount && g_bPerformanceTest) {
        PerformanceTest(&g_pFakeSysCall_NtTerminateProcess->xxx13, ullTickCount);
    }

    return status;
}


以上就是对TsFltMgr Hook框架的一些分析,祝大家元宵快乐~

[2022冬季班]《安卓高级研修班(网课)》月薪两万班招生中~

收藏
点赞0
打赏
分享
最新回复 (46)
雪    币: 140
活跃值: 活跃值 (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
MixDebug 活跃值 1 2012-2-6 17:57
2
0
广告位出租~  
雪    币: 812
活跃值: 活跃值 (141)
能力值: ( LV9,RANK:165 )
在线值:
发帖
回帖
粉丝
futosky 活跃值 3 2012-2-6 18:13
3
0
膜拜一个先,自己逆向烂啊
雪    币: 756
活跃值: 活跃值 (149)
能力值: ( LV12,RANK:310 )
在线值:
发帖
回帖
粉丝
古河 活跃值 6 2012-2-6 18:55
4
0
漂亮的框架。。。和360的HookPort有什么区别
雪    币: 441
活跃值: 活跃值 (59)
能力值: ( LV12,RANK:600 )
在线值:
发帖
回帖
粉丝
Sysnap 活跃值 14 2012-2-6 19:11
5
0
其实只是分析了一点,没说出整体框架。

还有为啥是
8053dbb6 90            nop
8053dbb7 90            nop
8053dbb8 90            nop
8053dbb9 e962170c77    jmp     TsFltMgr+0x2320 (f75ff320)
前面3个NOP有神马好处
雪    币: 202
活跃值: 活跃值 (30)
能力值: ( LV12,RANK:270 )
在线值:
发帖
回帖
粉丝
QEver 活跃值 5 2012-2-6 19:38
6
0
占位围观!!
雪    币: 69
活跃值: 活跃值 (11)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
JeTus 活跃值 1 2012-2-6 19:42
7
0
前排膜拜大牛分析
雪    币: 141
活跃值: 活跃值 (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
XSJS 活跃值 2012-2-6 19:45
8
0
凑两个双字,猜测。sys现在也到ZMCPB打酱油了?
雪    币: 141
活跃值: 活跃值 (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
XSJS 活跃值 2012-2-6 19:46
9
0
数了下,就差两个人就全员到齐了
雪    币: 55
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
phthegreat 活跃值 2012-2-6 19:55
10
0
字母在LZ的帮助下向数字看齐了
雪    币: 69
活跃值: 活跃值 (11)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
JeTus 活跃值 1 2012-2-6 19:56
11
0
凑俩字节可以放后面吧,猜不到
雪    币: 441
活跃值: 活跃值 (59)
能力值: ( LV12,RANK:600 )
在线值:
发帖
回帖
粉丝
Sysnap 活跃值 14 2012-2-6 19:59
12
0
NO,前面3个NOP是有意图的
雪    币: 107
活跃值: 活跃值 (20)
能力值: ( LV12,RANK:770 )
在线值:
发帖
回帖
粉丝
qiweixue 活跃值 19 2012-2-6 20:25
13
0
还有二个神秘指令,等你挖掘下啊
雪    币: 26
活跃值: 活跃值 (35)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
HitIt 活跃值 2012-2-6 20:27
14
0
3个nop 意味着想要3个妹子 ,  呵呵
雪    币: 69
活跃值: 活跃值 (11)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
JeTus 活跃值 1 2012-2-6 20:37
15
0
如果在这个地方hook的话,后面系统的更新估计也要随着改动了,前面那五个字节不错,貌似MS的各个版本都没变过。
其实也可以考虑前面五个字节的前面 …… 不过我考虑的可能比较局限,还有很多要考虑的……
想了下,还是没想出来那三个NOP的作用,没跟上大牛的步伐啊
雪    币: 69
活跃值: 活跃值 (11)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
JeTus 活跃值 1 2012-2-6 20:40
16
0
三个妹纸
雪    币: 202
活跃值: 活跃值 (30)
能力值: ( LV12,RANK:270 )
在线值:
发帖
回帖
粉丝
QEver 活跃值 5 2012-2-6 20:43
17
0
nop掉mov edi,esp是为了直接使用edi的值?
合理的hook部分就是从 mov     ebx,dword ptr [edi+eax*4]之后到call    ebx之前,但要过滤,最好保留edi值,而前面的部分被hookport占了,所以把mov edi,esp 这句放在过滤后面。

呃,刚受JeTus提醒
8053d7ec 8b3f            mov     edi,dword ptr [edi]
8053d7ee 8b1c87          mov     ebx,dword ptr [edi+eax*4]

这里也可以hook,没太注意。KiFastCallEntry里的hook空间不多了啊~~
雪    币: 537
活跃值: 活跃值 (43)
能力值: ( LV9,RANK:260 )
在线值:
发帖
回帖
粉丝
Fypher 活跃值 4 2012-2-6 20:59
18
0
这个算不算一处神秘指令:
SuperHook proc near

arg_0= dword ptr  8
arg_4= dword ptr  0Ch
arg_8= dword ptr  10h

push    ebp
mov     ebp, esp
push    edi
push    ebx
push    edx
mov     edi, [ebp+arg_0]
mov     eax, [edi]
mov     edx, [edi+4]
mov     ebx, [ebp+arg_4]
mov     ecx, [ebp+arg_8]
mov     edi, [ebp+arg_0]
lock cmpxchg8b qword ptr [edi]
pop     edx
pop     ebx
pop     edi
mov     ebp, esp
pop     ebp
retn    0Ch
SuperHook endp

为了保证原子操作,使用了 lock cmpxchg8b 指令
雪    币: 537
活跃值: 活跃值 (43)
能力值: ( LV9,RANK:260 )
在线值:
发帖
回帖
粉丝
Fypher 活跃值 4 2012-2-6 21:04
19
0
ZMCPB是啥?
雪    币: 537
活跃值: 活跃值 (43)
能力值: ( LV9,RANK:260 )
在线值:
发帖
回帖
粉丝
Fypher 活跃值 4 2012-2-6 21:35
20
0
实际更改内存的函数如下:
SuperHook proc near

arg_0= dword ptr  8
arg_4= dword ptr  0Ch
arg_8= dword ptr  10h

push    ebp
mov     ebp, esp
push    edi
push    ebx
push    edx
mov     edi, [ebp+arg_0]
mov     eax, [edi]
mov     edx, [edi+4]
mov     ebx, [ebp+arg_4]
mov     ecx, [ebp+arg_8]
mov     edi, [ebp+arg_0]
lock cmpxchg8b qword ptr [edi]
pop     edx
pop     ebx
pop     edi
mov     ebp, esp
pop     ebp
retn    0Ch
SuperHook endp


将三个 nop 放在 jmp 前面莫非是为了设计SuperHook函数的调用接口?因为这样在任何地方需要调用 SuperHook 进行 hook 时可以统一这样来调用:

SuperHook(dwHookPoint, 0xe9909090, dwOffSet);

其中 dwOffSet 就是从 dwHookPoint 要跳转到的相对偏移(dwOffSet = new - dwHookPoint - 8)。

不知道这个原因对不对……
雪    币: 350
活跃值: 活跃值 (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Diabloking 活跃值 2012-2-7 02:07
21
0
那三个nop跟360有关吧....我猜
雪    币: 537
活跃值: 活跃值 (43)
能力值: ( LV9,RANK:260 )
在线值:
发帖
回帖
粉丝
Fypher 活跃值 4 2012-2-7 09:05
22
0
也有可能是给360多腾点地方,万一360突然要用7字节jmp呢
雪    币: 83
活跃值: 活跃值 (27)
能力值: ( LV13,RANK:220 )
在线值:
发帖
回帖
粉丝
instruder 活跃值 4 2012-2-7 09:12
23
0
额 多贴点 nt api的处理过程函数啊 看看有没有啥xx没
雪    币: 105
活跃值: 活跃值 (259)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Fido 活跃值 2012-2-7 09:45
24
0
膜拜....坐等解释...
雪    币: 1121
活跃值: 活跃值 (874)
能力值: (RANK:400 )
在线值:
发帖
回帖
粉丝
莫灰灰 活跃值 9 2012-2-7 09:54
25
0
伟哥,亲。很久么见,想屎你啦。
游客
登录 | 注册 方可回帖
返回