介绍:
SSDT SHADOW HOOK可用于安全软件窗口保护、安全输入、截屏保护等。例如:挂钩NtUserFindWindowEx、NtUserGetForegroundWindow、NtUserBuildHwndList、NtUserQueryWindow、NtUserWindowFromPoint、NtUserSetParent、NtUserPostMessage、NtUserMessageCall、NtUserSetWindowLong、NtUserShowWindow、NtUserDestroyWindow、NtUserCallHwndParamLock用于窗口保护,挂钩了NtUserSendInput、NtUserGetAsyncKeyState、NtUserOpenDesktop、NtUserTranslateMessage用于安全输入,挂钩NtGdiBitBlt、NtGdiStretchBlt用于截屏保护。
窗口保护
恶意程序通过获取安全软件的窗口句柄,然后通过关闭、隐藏、禁用等手段破坏其正常工作,需要挂钩这些函数来防止恶意程序的破坏。R3和R0函数对应关系如表1所示。
表1 R3-R0函数对于关系
R3 | R0 | 作用 |
FindWindow | NtUserFindWindowEx | 查找窗口获取句柄 |
GetForegroundWindow | NtUserGetForegroundWindow | 得到当前顶层窗口 |
EnumWindows | NtUserBuildHwndList | 枚举所有顶层窗口 |
GetWindowThreadProcessId | NtUserQueryWindow | 获取句柄对应的进程PID |
WindowFromPoint | NtUserWindowFromPoint | 获取所在位置的窗口句柄 |
SetParent | NtUserSetParent | 改变某个子窗口的父窗 |
PostMessage | NtUserPostMessage | 发送消息 |
SendMessage | NtUserMessageCall | 发送消息 |
SetWindowLong | NtUserSetWindowLong | 改变窗口属性 |
ShowWindow | NtUserShowWindow | 改变窗口显示状态 |
DestroyWindow | NtUserDestroyWindow | 销毁窗口 |
EnableWindow | NtUserCallHwndParamLock | 禁用、启用窗口 |
安全输入
挂钩NtUserSendInput、NtUserGetAsyncKeyState、NtUserOpenDesktop、NtUserTranslateMessage分别用于防止模拟按键、获取键盘按键状态、打开安全桌面、将虚假按键还原成真实的按键。
a) NtUserSendInput
恶意程序可以通过调用SendInput来模拟按键干扰正常输入,可以挂钩NtUserSendInput防止恶意操作。当用户正在输入密码等隐私信息的时候,禁止其他程序调用SendInput模拟键盘和鼠标操作。
b) NtUserGetAsyncKeyState
恶意程序可能不停的调用NtUserGetAsyncKeyState来获取键盘的按键状态从而记录键盘的输入信息,可以挂钩NtUserGetAsyncKeyState用来禁止此类键盘记录行为。当用户正在输入密码等隐私信息的时候,禁止其他程序调用NtUserGetAsyncKeyState,但是不会阻止当前受保护的进程调用(否则会影响正常的密码输入行为)。
c) NtUserOpenDesktop
在Windows操作系统中,消息钩子(通过SetWindowsHookEx设置)只会当前的桌面上的窗口有效,所以,可以建立一个安全桌面,用于运行需要严密保护的进程,这样,非本桌面上运行的程序无法通过消息钩子的方式来获取需要保护进程窗口的信息,达到了保护目标进程窗口的目的(360保险箱和金山密保都有一个叫安全桌面的功能,正是这样实现的)。
首先调用真实的NtUserOpenDesktop函数,然后获取返回句柄的桌面名字,如果此桌面名字跟创建的安全桌面名字一样,则关闭此桌面句柄,并返回一个NULL值,否则返回真实的句柄。达到保护安全桌面的目的,真正做到安全桌面不可渗透。
d) NtUserTranslateMessage
在输入密码的时候,用户程序一般调用TranslateMessage将消息转化为具体的按键信息,利用此特点,可以构建一个DirectInput安全输入通道,即尽可能少的通过Windows系统的键盘按键传输通道(此通道是极度危险的,恶意程序可以在任意位置添加HOOK截获按键信息)。
对NtUserTranslateMessage的挂钩用于修正虚拟键盘输入的虚拟按键,首先需要判断是否正在输入密码,虚拟键盘是否正在运行,是否需要修正按键,这三个参数都是运行于R3的控制程序传递进来给R0驱动的。然后判断消息是否是键盘按键的消息,如果是,则进一步判断此消息是否对应虚假按键,如果是,则修正为真实的按键。虚假的按键和真实的按键也是R3传递给R0驱动的。整个输入通道完全自己构建,不通过Windows系统提供的任何通道,所有的类型的HOOK都无法在此期间截获虚拟键盘输入的密码(很多安全软件提供的虚拟键盘的思路大致如此吧,没有去仔细调研)。
截屏保护
很多截屏类的键盘记录程序。当用户在虚拟键盘上按下一个键时,恶意程序就截一次屏幕,这样可以清楚地看到用户输入的密码信息。挂钩两个函数NtGdiBitBlt、NtGdiStretchBlt可以用于防截屏。
卡巴斯基反病毒软件率先推出截屏保护,即当虚拟键盘运行的时候,阻止程序进行截屏操作,在一定程度上可以阻止此类键盘记录工具的工作。但是卡巴斯基有一个很大的缺陷,它只能阻止全屏的截图,不能阻止部分屏幕的截图。
简单实现:
首先定位到SSDT SHADOW的地址
使用的硬编码,根据相同版本下与SSDT地址存在的偏移获取的SSDT SHADOW的地址
windbg下
kd> ?KeServiceDescriptorTable-KeServiceDescriptorTableShadow
Evaluate expression: 64 = 00000040
我只装了XP的虚拟机
switch (dwVersion)
{
case VERSION_2K:
KeServiceDescriptorTableShadow=(DWORD)KeServiceDescriptorTable+0xE0;
break;
case VERSION_2K3:
break;
case VERSION_XP:
KeServiceDescriptorTableShadow=(DWORD)KeServiceDescriptorTable-0x40;
break;
case VERSION_VISTA:
break;
case VERSION_WIN7:
break;
}
方法其实好多,参考 http://bbs.pediy.com/showthread.php?t=56955
然后就是HOOK了
typedef BOOL (__stdcall *PNtUserDestroyWindow)(HWND hwnd);
//定义原来和当前的函数地址
PNtUserDestroyWindow Old_NtUserDestroyWindow,Cur_NtUserDestroyWindow;
BOOL __stdcall MyNtUserDestroyWindow(HWND hwnd)
{
if (hwnd==hProctecHwnd)
{
KdPrint(("被保护窗口hwnd=%d \n",hwnd));
return FALSE;//如果是被保护窗口的句柄直接返回错误
}
else
return Old_NtUserDestroyWindow(hwnd);//调用原来的函数
}
//SSDT SHADOW HOOK
VOID HookNtUserDestroyWindow()
{
DWORD SSDTShadowBaseAddr=GetSSDTShadowAddr()+0x10;//表基址所在地址
DWORD TableCount=SSDTShadowBaseAddr+0x8;//函数数量所在地址
DWORD dwCount=*((PDWORD)TableCount);
PDWORD Fun_Addr=(PDWORD)(*((PDWORD)SSDTShadowBaseAddr));
PDWORD NtUserDestroyWindowAddr=Fun_Addr+355;
//保存原函数地址,SSDT HOOK是根据ZW函数地址硬编码得出的索引得到的函数地址
Old_NtUserDestroyWindow=(PNtUserDestroyWindow)(*NtUserDestroyWindowAddr);
KdPrint(("ssdt shadow addr:0x%X",SSDTShadowBaseAddr));
KdPrint(("数量是:%d",dwCount));
KdPrint(("原函数地址:%X\n",*NtUserDestroyWindowAddr));
KdPrint(("新函数地址:%X\n",MyNtUserDestroyWindow));
__asm //去掉页面保护
{
cli
mov eax,cr0
and eax,not 10000h //and eax,0FFFEFFFFh
mov cr0,eax
}
*NtUserDestroyWindowAddr=MyNtUserDestroyWindow; //指向HOOK地址
__asm
{
mov eax, cr0
or eax, 10000h
mov cr0, eax
sti
}
KdPrint(("hook ok"));
}
VOID UNHOOKNtUserDestroyWindow() //还原HOOK
{
DWORD SSDTShadowBaseAddr=GetSSDTShadowAddr()+0x10;
PDWORD Fun_Addr=(PDWORD)(*((PDWORD)SSDTShadowBaseAddr));
PDWORD NtUserDestroyWindowAddr=Fun_Addr+355;
__asm //去掉页面保护
{
cli
mov eax,cr0
and eax,not 10000h //and eax,0FFFEFFFFh
mov cr0,eax
}
*NtUserDestroyWindowAddr=Old_NtUserDestroyWindow;
__asm
{
mov eax, cr0
or eax, 10000h
mov cr0, eax
sti
}
KdPrint(("unhook..."));
}
HOOK了一个NtUserDestroyWindow,让某个窗口点击右上角的叉号关闭不了
在实现驱动与应用程序通信,控制台接收汉字输入的时候竟然卡了一会。。。