用消息拦截技术制作系统日志

发表于:2007-07-14来源:作者:点击数: 标签:
康帕斯(中国)国际信息服务有限公司 马文骞 能够完整记录电脑使用情况的日志文件在 Windows系统 安全 管理方面的作用是不可低估的。本文介绍了利用消息拦截技术制作日志文件的方法,其中的关键函数是一个未公开的 API系统调用。 一、利用钩子(Hook)拦截系
康帕斯(中国)国际信息服务有限公司 马文骞  

能够完整记录电脑使用情况的日志文件在 Windows系统安全管理方面的作用是不可低估的。本文介绍了利用消息拦截技术制作日志文件的方法,其中的关键函数是一个未公开的 API系统调用。


一、利用钩子(Hook)拦截系统消息

日志文件对于一个大企业内部网络的维护与管理是至关重要的。另外还有许多其它场合也离不开日志的使用,例如:多人共享一台电脑,或在家庭中要记录儿童使用电脑的细节,等等。

日志程序若想完整记录电脑运行期间有哪些软件启动过、各使用了多长时间、以及使用浏览器访问互联网的情况等,必须对系统级消息进行拦截。RegisterShellHook是一个未公开的 API系统函数,它可以帮助日志程序在整个 Windows系统范围内感知到其它窗体的创建、激活或关闭等消息,而且不要求这些窗体与日志程序有父子关系,哪怕是 Windows最高级别的窗体也可以。RegisterShellHook 调用方法为:

Public Declare Function RegisterShellHook Lib "Shell32" Alias "#181" _
(ByVal hwnd As Long, ByVal nAction As Long) As Long

其中参数hwnd为日志程序的句柄,参数 nAction为所要进行操作的代码。具体的调用细节参见下面的例子及其注释。


二、将日志程序隐藏起来

把日志程序的Visible属性设为False当然是必要的一步。然后是 ShowInTaskbar属性也设为 False,以便其在 Windows的任务栏中不出现。最后,为了在 CTRL+ALT+DEL 所弹出的列表中隐藏日志程序,需要调用RegisterServiceProcess函数:

Public Declare Function RegisterServiceProcess Lib "kernel32" _
(ByVal dwProcessID As Long, ByVal dwType As Long) As Long

其中参数dwType是操作代码,值“1”表示从CTRL+ALT+DEL列表中去除,值“0”表示在列表中恢复;参数 dwProcessID是要在列表中去除或恢复的进程标识,可以用GetCurrentProcessId() API 函数得到日志程序的进程标识,也可以用更简便的方法,即把 dwProcessID参数置为空值,其含义是用当前程序的进程标识作为参数(见下例)。

另外,为了让日志程序在 Windows每次启动时都能自动运行,需要修改注册表,即在注册表的下述位置新建一个以日志程序的路径及名称为值的“串值”:

\HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run

此外,产生的日志文件也应妥为隐藏,最好用 Winsock控件随时向服务器传送。
为了简洁,下面的例子仅将日志文件放在了根目录,并且略去了用TCP/IP传送文件的代码。


三、一个完整的例子

下面的代码虽然短小,却是一个完整的能自我隐藏的日志程序(用 VB6.0实现,在 Win98下测试通过)。

' 窗体部分的代码(Form1.frm)
Option Explicit
Private Sub Form_Load()
Dim tmp As Long
' 将日志程序的名称从 CTRL+ALT+DEL 列表中清除
tmp = RegisterServiceProcess(ByVal 0&, 1)
Timer1.Interval = 60000 ' 定时器的作用是每隔一分钟将日志存盘
' 定义一个新的系统级的消息类型
Msg_ID = RegisterWindowMessage("SHELLHOOK")
Call RegisterShellHook(hwnd, 1) ' 调用未公开的函数(进行注册)
' 实施拦截:在存储了原入口地址的同时,将新地址指向自定义的函数WindowProc
Original = SetWindowLong(hwnd, GWL_WNDPROC, AddressOf WindowProc)
End Sub
Private Sub Form_Unload(Cancel As Integer)
Dim tmp As Long
Call RegisterShellHook(hwnd, 0) ' 调用未公开的函数(取消注册)
tmp = SetWindowLong(hwnd, GWL_WNDPROC, Original) ' 将入口地址还原
End Sub
Private Sub Timer1_Timer()
If Len(Text1.Text) > 0 Then
Open "C:\SystemLog.Sys" For Append As #1 ' 以“添加”方式打开日志
Print #1, Text1.Text ' 日志自动存盘
Text1.Text = ""
Close #1
End If
End Sub
' 模块部分的代码(模块1.bas)
Public Declare Function RegisterShellHook Lib "Shell32" Alias "#181" _
(ByVal hwnd As Long, ByVal nAction As Long) As Long
Public Declare Function RegisterWindowMessage Lib "user32" Alias _
"RegisterWindowMessageA" (ByVal lpString As String) As Long
Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
(ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Public Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" _
(ByVal hwnd As Long, ByVal lpString As String, ByVal clearcase/" target="_blank" >cch As Long) As Long
Public Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" _
(ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal Msg As Long, ByVal _
wParam As Long, ByVal lParam As Long) As Long
Public Declare Function RegisterServiceProcess Lib "kernel32" _
(ByVal dwProcessID As Long, ByVal dwType As Long) As Long
Const HSHELL_WINDOWCREATED = 1 ' 系统级的窗体被创建
Const HSHELL_WINDOWDESTROYED = 2 ' 系统级的窗体即将被关闭
'Const HSHELL_ACTIVATESHELLWINDOW = 3 ' SHELL 的主窗体将被激活(本例未用)
Const HSHELL_WINDOWACTIVATED = 4 ' 系统级的窗体被激活
'Const HSHELL_GETMINRECT = 5 ' 窗体被最大化或最小化(本例未用)
'Const HSHELL_REDRAW = 6 ' Windows 任务栏被刷新(本例未用)
'Const HSHELL_TASKMAN = 7 ' 任务列表的内容被选中(本例未用)
'Const HSHELL_LANGUAGE = 8 ' 中英文切换或输入法切换(本例未用)
Public Const GWL_WNDPROC = -4 ' 该索引用来创建窗口类的子类
Public Msg_ID As Long, Original As Long
Public Function WindowProc(ByVal hwnd As Long, ByVal uMsg As Long, ByVal _
wParam As Long, ByVal lParam As Long) As Long ' 回调函数
Dim tmp1 As String, tmp2 As String, i As Long
If uMsg = Msg_ID Then
tmp1 = String(200, "*")
i = GetWindowText(lParam, tmp1, 200) ' 取窗体的标题
If i > 0 Then tmp1 = Left(tmp1, i) Else tmp1 = "未命名"
tmp1 = tmp1 + " " + Str(Date) + " " + Str(Time) + vbCrLf ' 加入日期
' 下面对窗体句柄值进行格式化的目的是为了日志文件在视觉上更美观
tmp2 = Format(lParam, "000000")
If Right(Form1.Text1, 2) <> vbCrLf Then tmp2 = vbCrLf + tmp2
Select Case wParam
Case HSHELL_WINDOWCREATED
Form1.Text1 = Form1.Text1 + tmp2 + " 创建:" + tmp1
Case HSHELL_WINDOWDESTROYED
Form1.Text1 = Form1.Text1 + tmp2 + " 关闭:" + tmp1
Case HSHELL_WINDOWACTIVATED
Form1.Text1 = Form1.Text1 + tmp2 + " 激活:" + tmp1
' 为了程序简洁,本例仅处理“创建”、“激活”和“关闭”这三个消息,
' 其实就生成日志文件的目的,上述三个消息已基本够用。
' Case ...
' ...
End Select
Else
' 使用已被存储下来的原入口地址
WindowProc = CallWindowProc(Original, hwnd, uMsg, wParam, lParam)
End If
End Function

下面列出的即为上述日志程序所产生的日志文件(长约十分钟的片段)。从中可以看出在该时间段内的电脑使用情况:曾拨号上网、浏览过“计算机世界”、收过邮件、访问过注册表等。左列的数字是相应窗体的句柄。

002624 激活:Project1 - Microsoft Visual Basic [设计]
002624 关闭:Microsoft Visual Basic [设计]
001692 创建:正在连接到 95963
001692 激活:正在连接到 95963
003512 关闭:Hotmail - 通行全球的免费 Web 电子邮件 - Microsoft Internet Explorer
001880 创建:未命名 01-6-6 16:01:25
001880 激活:未命名 01-6-6 16:01:25
001880 激活:计算机世界网-应用与方案-首页 - Microsoft Internet Explorer
001880 激活:计算机世界网-应用与方案-应用编程 - Microsoft Internet Explorer
003488 创建:Microsoft Internet Explorer 01-6-6 16:07:40
003488 激活:Microsoft Internet Explorer 01-6-6 16:07:41
003488 关闭:计算机世界网-用屏幕取词技术实现动态标注 - Microsoft Internet Explorer
001880 激活:计算机世界网-e海航标-首页 - Microsoft Internet Explorer
001880 关闭:计算机世界网-e海航标-首页 - Microsoft Internet Explorer
001132 激活:浏览 - C:\
001132 关闭:浏览 - C:\
002772 创建:Outlook Express 01-6-6 16:10:41
002772 激活:Outlook Express 01-6-6 16:10:41
002772 激活:收件箱 - Outlook Express
002772 关闭:收件箱 - Outlook Express
003920 关闭:浏览 - 我的电脑
000640 创建:注册表编辑器
000640 激活:注册表编辑器
000640 关闭:注册表编辑器
003756 创建:未命名 01-6-6 16:11:30
003756 关闭:未命名 01-6-6 16:11:30
001328 创建:网络监视器
001328 激活:网络监视器
001328 激活:网络监视器 - 0 连接到 \\CD_PROGRAM
001328 关闭:网络监视器 - 0 连接到 \\CD_PROGRAM
002700 关闭:连接到 95963
001804 关闭:未命名

原文转自:http://www.ltesting.net