QQ电脑管家中的TsFltMgr Hook框架分析

2012-2-6 Nie.Meining Debug

新版的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的地址(SSDTSSDTShadow的地址)

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框架的一些分析,祝大家元宵快乐~

发表评论:

Powered by emlog