拦截关机/重启/注销,需要HOOK什么内核函数,我们来看看PowerTool是怎么实现的:
我现在的系统是32位WIN7 旗舰版 [版本 6.1.7600]
打开PowerTool,选择“禁止进程创建等配置”,选中,查看Shadow SSDT钩子,发现NtUserCallNoParam被挂钩,如下图:
我们跟踪下PowerTool 的NtUserCallNoParam 的替代函数
lkd> u 8e97ed10 l50
8e97ed10 8bff mov edi,edi
8e97ed12 55 push ebp
8e97ed13 8bec mov ebp,esp
8e97ed15 83ec08 sub esp,8
8e97ed18 8b4508 mov eax,dword ptr [ebp+8]
8e97ed1b 50 push eax
8e97ed1c ff15d8cd988e call dword ptr ds:[8E98CDD8h]
8e97ed22 8945fc mov dword ptr [ebp-4],eax
8e97ed25 837d0810 cmp dword ptr [ebp+8],10h
8e97ed29 7507 jne 8e97ed32
8e97ed2b c745fc00000000 mov dword ptr [ebp-4],0
8e97ed32 8b45fc mov eax,dword ptr [ebp-4]
8e97ed35 8be5 mov esp,ebp
8e97ed37 5d pop ebp
8e97ed38 c20400 ret 4
关键代码:
8e97ed25 837d0810 cmp dword ptr [ebp+8],10h
8e97ed29 7507 jne 8e97ed32
得出结论:
32位WIN7系统下NtUserCallNoParam参数一是10h时是关机/重启/注销。
那么在WINXP SP3 上是怎么样呢?我们看下,如下图:
与WIN7不同,WINXP是HOOK NtUserCallOneParam
继续跟踪
lkd> u A01A1B80 l50
a01a1b80 8bff mov edi,edi
a01a1b82 55 push ebp
a01a1b83 8bec mov ebp,esp
a01a1b85 83ec08 sub esp,8
a01a1b88 8b450c mov eax,dword ptr [ebp+0Ch]
a01a1b8b 50 push eax
a01a1b8c 8b4d08 mov ecx,dword ptr [ebp+8]
a01a1b8f 51 push ecx
a01a1b90 ff1534fd1aa0 call dword ptr ds:[0A01AFD34h]
a01a1b96 8945fc mov dword ptr [ebp-4],eax
a01a1b99 837d0c34 cmp dword ptr [ebp+0Ch],34h
a01a1b9d 7507 jne a01a1ba6
a01a1b9f c745fc00000000 mov dword ptr [ebp-4],0
a01a1ba6 8b45fc mov eax,dword ptr [ebp-4]
a01a1ba9 8be5 mov esp,ebp
a01a1bab 5d pop ebp
a01a1bac c20800 ret 8
关键代码:
a01a1b99 837d0c34 cmp dword ptr [ebp+0Ch],34h
a01a1b9d 7507 jne a01a1ba6
XP下NtUserCallOneParam下参数二 是 34h时是是关机/重启/注销
核心代码如下:
InitHook proc
;HOOK SSSDT 必须在GUI线程上下文操作才有效,DispatchControl 操作会加载win32k.sys,加载win32k.sys后,系统导出的KeServiceDescriptorTable表会失效
;这时HOOK SSDK 直接用找到的SSSDT基址即可,SSSDT表在第二个数组。
;取SSDT表基地址
invoke GetServiceDescriptorTableShadowAddress
mov SSDT,eax
;取SSSDT表基地址(win32.sys)
add eax,size SYSTEM_SERVICE_TABLE
mov SSSDT,eax
invoke DbgPrint,$CTA0("SSSDT BASE: %8X\n"),eax
;取系统版本
invoke GetWindowsVersion
mov WINDOWS_VERSION,eax
;只确定WINXP和WIN7的SSSDT序号是正确的,其他系统请修改。
.if eax==WINDOWS_VERSION_2K
mov NtUserCallNoParam_callnumber,142h
mov NtUserCallOneParam_callnumber,143h
mov Rcode,38h
ret
.elseif eax==WINDOWS_VERSION_XP || eax==WINDOWS_VERSION_2K3
mov NtUserCallNoParam_callnumber,142h
mov NtUserCallOneParam_callnumber,143h
mov Rcode,34h
ret
.else
mov NtUserCallNoParam_callnumber,14dh
mov NtUserCallOneParam_callnumber,14eh
mov Rcode,10h
ret
.endif
ret
InitHook endp
;***************************************************************************
; 根据给出的服务序号和替代服务地址修改ssdt表,返回值为原服务地址
;***************************************************************************
EditSSDT proc uses ebx ecx lpbase:dword ,dwIndex:dword,lpNowProc:dword
invoke MmIsAddressValid,lpNowProc ;检查地址是否有效
.if al== FALSE
;invoke DbgPrint, $CTA0("无效地址:%8X\n"),lpNowProc
mov eax,FALSE
ret
.endif
mov ecx, dwIndex
mov eax, lpbase ;SYSTEM_SERVICE_TABLE
.if ecx>[eax+8] ;判断服务序号是否超出服务总数
mov eax,FALSE
ret
.endif
mov eax,[eax] ;SSDT的指针
mov ebx,[eax+ecx*4] ;原地址
.if lpNowProc==ebx ;原地址和要修改的地址相同,代表已经修改,直接返回假
mov eax,FALSE
ret
.endif
push ebx ;入栈,保存原来的ServerBase
lea ecx,[eax+ecx*4] ;取序号地址 也就是 mov ecx,eax+ecx*4
push ecx ;保护ecx, MmIsAddressValid函数会修改ECX的值
invoke MmIsAddressValid,[ecx] ;检查地址是否有效
pop ecx
.if al== FALSE
pop eax ;恢复堆栈
;invoke DbgPrint, $CTA0("无效地址:%8X\n"),ecx
mov eax,FALSE
ret
.endif
;去掉写保护
push eax
cli
mov eax, cr0
and eax, not 10000h
mov cr0, eax
pop eax
;修改 SSDT
push lpNowProc
pop dword ptr[ecx]
;恢复写保护
push eax
mov eax, cr0
or eax, 10000h
mov cr0, eax
sti
pop eax
pop eax ;出栈,返回值为原服务地址
ret
EditSSDT endp
new_NtUserCallNoParam proc Routine:dword , Param:dword
;在win7下NtUserCallNoParam的第一个参数Routine = 10h的时候 是关机/重启/注销
mov eax,Rcode
.IF Routine==eax
mov eax ,0
ret
.else
push Param
push Routine
call old_NtUserCallNoParam
ret
.endif
new_NtUserCallNoParam endp
new_NtUserCallOneParam proc Param:dword , Routine:dword
;在win2k下 NtUserCallOneParam的第二个参数Routine = 0x38的时候 是关机/重启/注销
;在winxp/win2k3下NtUserCallOneParam的第二个参数Routine = 0x34的时候 是关机/重启/注销
mov eax,Rcode
.IF Routine==eax
mov eax ,0
ret
.else
push Routine
push Param
call old_NtUserCallOneParam
ret
.endif
new_NtUserCallOneParam endp
代码目录里附带了一个MASM写的驱动加载工具,测试时请注意:
先注册,再运行,然后I/O控制,控制码800是安装HOOK,801是卸载HOOK
注意,驱动停止前先要用控制码801卸载HOOK,不然蓝屏
编译环境:
RadASM
blog:
http://nohacks.cn
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)