TsFltMgr.sys系统蓝屏的原因分析(小心QQ电脑管家)

所属分类: 电脑基础 / 故障排除 阅读数: 97
收藏 0 赞 0 分享

同事一WindowsXP系统,正常执行,关闭后,第二天无法启动,详细症状为:

(1)安全模式以及带网络功能的安全模式都能够进入;
(2)正常模式,还没出现WindowXP滚动栏就開始重新启动;
(3)进安全模式,禁用自己主动重新启动后,再正常启动,出现蓝屏,报TsFltMgr.sys内存错误!

经过互联网查询,和不断摸索,最后发现居然是可恶的QQ软件管家惹的祸,进安全模式果断卸载QQ软件管家后,再重新启动,系统全然正常了。

以下转载了一篇分析QQ电脑管家的文章,请參考:

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 </p> <p>// 调用 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 // 更改本次调用相应的内核函数地址!</p> <p>// 恢复现场
popad
popfd</p> <p>// 运行 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;</p> <p> if ( ulSyscallId >= 0x400 )
return ulSyscallAddr;</p> <p> 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
}</p> <p> 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</p> <p>// 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;
</p> <p> // 貌似是做性能測试时候须要的,实际版本号中 g_bPerformanceTest 为 FALSE
if ( g_bPerformanceTest ) {
ullTickCount = KeQueryInterruptTime();
}</p> <p>
// 系统调用的调用前处理!
// +++
InterlockedIncrement(&pFakeSysCall->ulCountForPreWork);
ulStatus = PreWork(&ulXXX, pFakeSysCall);
InterlockedDecrement(&pFakeSysCall->ulCountForPreWork);
// ---

if ( ulStatus != 0xEEEE0004 && ulStatus != 0xEEEE0005)
{
OrigSysCall * pOrigSysCall = pFakeSysCall->ulOrigSysCallAddr;</p> <p> // 调用原始系统调用!
if ( pOrigSysCall && NT_SUCCESS(pOrigSysCall(xxx)) )
{
// 系统调用的调用后处理!
// +++
InterlockedIncrement(&pFakeSysCall->ulCountForPostWork),
ulStatus = PostWork(&ulXXX),
InterlockedDecrement(&pFakeSysCall->ulCountForPostWork),
// ---
}
}</p> <p> // 0xEEEE0004 应该是拒绝调用的意思,0xEEEE0005 应该是同意调用的意思
if (ulStatus == 0xEEEE0005)
status = STATUS_SUCCESS;</p> <p> // PsGetCurrentProcessId 这个调用的返回值后面并没实用到,可能是多余的
PsGetCurrentProcessId();</p> <p> // 貌似是做性能測试时候须要的
if ( g_pFakeSysCall_NtTerminateProcess->xxx5 && ullTickCount && g_bPerformanceTest) {
PerformanceTest(&g_pFakeSysCall_NtTerminateProcess->xxx13, ullTickCount);
}</p> <p> return status;
}

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

更多精彩内容其他人还在看

分辨率调不了怎么办 电脑屏幕分辨率调不了的原因及解决方法

分辨率又称解析度,一般理解为屏幕显示像素的多少。电脑分辨率调不了怎么办?电脑新手朋友们经常会遇到此问题,下面与大家分享下3种电脑分辨率的调整方法,遇到类似情况的朋友可以参考下,希望对大家有所帮助
收藏 0 赞 0 分享

驱动更新失败问题怎么解决?完美解决驱动更新失败问题

在电脑上无论是安装驱动还是安装应用软件都有出现失败的时候,本章帮大家解决驱动程序更新的时候出现失败的现象
收藏 0 赞 0 分享

电容坏了(爆浆)是否影响电脑的正常运行

爆浆之后电脑还能正常开机和运行,但电脑的稳定性肯定不如以前了,在恶劣的环境下运行时,就会出现莫名奇妙的死机或者重启
收藏 0 赞 0 分享

笔记本看视频或玩游戏卡住黑屏是什么原因怎么解决

HP CQ40笔记本电脑,看个视频,一会儿就电脑卡住,然后几秒钟后直接黑屏,玩游戏或者看视屏就这个样子,用鲁大师测试温度:一般就60多度 也没有很高,原因及解决方法如下,有类似情况的朋友可以参考下
收藏 0 赞 0 分享

电脑本地连接没有了网络连接的本地连接不见的解决方法

电脑本地连接没有了,想必有很多朋友都遇到过这种情况吧,究竟是什么原因呢?由于所造成的原因各不相同,下面与大家分享下本地连接没有了的原因与解决办法,遇到类似情况的朋友可以参考下本文
收藏 0 赞 0 分享

电脑桌面无法显示音量控制窗口怎么回事?问题分析与解决方法介绍

Windows无法在任务栏上显示音量控制。请使用‘控制面板’中的‘添加/删除程序’来安装”,请问要怎么装
收藏 0 赞 0 分享

玩游戏自动跳回桌面故障分析及解决

电脑在正常运行时总会有一个程序莫名其妙的闪现,时间很短,在进程中似乎没什么异常,通常玩大型3D游戏时如果电源功率不够的话就会有跳回桌面的现象,电源不稳定也是这种现象产生
收藏 0 赞 0 分享

三星平板电脑连接充电器后仍未供电的解决方法

当平板电脑完全没有电后,连接充电器并尝试启动平板电脑,但却无法开机,这是怎么回事呢
收藏 0 赞 0 分享

U盘插到电脑上不显示图标的解决方法(以金士顿8GU盘为例)

有时候U盘在插上电脑时候,显示正常介入了,但是在我的电脑了却不显示图标,这就让人纠结了,遇到这样的情况,我们该怎么解决呢?下面与大家分享下具体的解决过程
收藏 0 赞 0 分享

IT故障排查工作中的六条不变法则适用于大多数而非全部情况

高强度故障排查工作往往会遵循一些通用且不成文的实践规则,在本文将结合自身经历总结出六条不变法则,希望能为大家的实际工作带来助益,只适用于大多数而非全部情况
收藏 0 赞 0 分享
查看更多