Delphi深度探索之外壳执行操作记录器 |
| 作者: 哈巴狗的小窝 来源:希赛网 http://www.csai.cn 2006年01月20日 |
|
记录外壳的活动 记录外壳活动有很多好处,比如当需要监控用户的行为,回溯系统崩溃前的过程。实现这一功能的关键工具相当简单,它就是COM接口IShellExecuteHook。编写一个实现了这一接口的COM对象后,再在系统中注册,就可以容易地控制并影响Windows外壳的运行。Windows 98和Windows 2000都支持IShellExecuteHook外壳扩展,而在Windows 95和Windows NT 4.0上则必须安装活动桌面扩展后才支持(也就是说必须安装IE 4.01)。 一个实现了IShellExecuteHook接口的COM对象可以截获所有对ShellExecute和ShellExecuteEx函数的调用。ShellExecute和ShellExecuteEx函数主要用于执行应用程序,它们可以接收一个文件名并能自动获得同文件名相关的可执行文件名。此外,它们还支持系统安全认证。如果在NT上设定了用户的可执行权限,ShellExecute和ShellExecuteEx函数将会在创建新的进程前检查权限(CreateProcess和WinExec函数则没有这项功能)。函数调用的流程如下: (1)获得将要运行的可执行文件名。 (2)根据程序名检查用户执行权限。 (3)激活全部已注册的IshellExecuteHook扩展。 (4)当所有扩展和权限都同意执行,创建新的进程并返回。 Windows外壳大量调用ShellExecute和ShellExecuteEx函数来执行几乎是所有的资源管理器的操作,比如双击目录、浏览文件夹内容、打印编辑文档、查看文件属性、选择文档的上下文相关菜单等等。此外,开始菜单的运行对话框和DOS方式下的Start.exe也使用ShellExecuteEx函数来执行程序。简单地说几乎用户的所有外壳操作都可以被扩展截获,包括其他应用程序对ShellExecute和ShellExecteEx的调用。
首先需要创建一个进程内COM对象,选菜单命令New | ActiveX Library,然后点击菜单New|Com Object,创建COM对象框架,按图2.14填充对话框的内容,然后点击OK按钮。Delphi就会自动生成框架文件,并保存生成的文件。 IShellExecuteHook的接口定义在shlobj.pas单元中,添加shlobj到单元uses部分,然后添加IShellExecuteHooko方法原型到COM对象声明部分,声明部分代码如下: unit ShellExecuteHookObj; interface uses Windows, ActiveX, ComObj, ShlObj, ShellAPI; type TTShellExecuteHook = class (TComObject, IShellExecuteHook) protected function Execute(var ShellExecuteInfo: TShellExecuteInfo): HResult; stdcall; end; const Class_TShellExecuteHook: TGUID = ''''{935FA400-243D-11D3-B06E-857B2AE2BE64}''''; 下面就是用来截获并记录外壳操作的实现部分,一旦外壳扩展被注册后,每次ShellExecute 和ShellExecuteEx函数运行时都会调用COM对象的Execute函数。我们的核心代码就是通过Execute方法实现的。方法定义如下: function TTShellExecuteHook.Execute( Execute方法会从外壳获得一个类型为TshellExecuteInfo的参数,参数定义如下: _SHELLEXECUTEINFOA = record cbSize: DWORD; fMask: ULONG; Wnd: HWND; lpVerb: PAnsiChar; lpFile: PAnsiChar; lpParameters: PAnsiChar; lpDirectory: PAnsiChar; nShow: Integer; hInstApp: HINST; { Optional fields } lpIDList: Pointer; lpClass: PAnsiChar; hkeyClass: HKEY; dwHotKey: DWORD; hIcon: THandle; hProcess: THandle; end; 这个记录结构中的lpFile包含了要运行的文件名,而lpVerb则表明执行的动作,动作由一些标准的字符串代表,比如,open(打开)、print(打印)、edit(编辑)、explore(浏览)、properties(属性)、find(查找)和其他上下文菜单的命令名。 有时,lpFile并不包含可执行文件名,这是因为ShellExecute接到的运行参数是一个文档名。比如当我们在资源管理器中双击文本文件时,Windows用文本文件名作为参数调用ShellExecute函数,而ShellExecute函数则获得同文本文件相关联的可执行文件名,然后执行。 TShellExecuteInfo结构中还记录了要运行程序的很多信息,然而这里我们只能在Execute方法中修改nCmdShow参数,nCmdShow参数定义了窗口在运行后的显示状态,包括最大化、最小化、正常等选项,对于其他参数的修改都会被外壳忽略。除此之外,在Execute方法中可以根据情况允许外壳继续缺省的任务或通知外壳取消执行,这可以通过Execute函数的返回值来实现。 如果Execute的返回值为S_FALSE,外壳就继续缺省的任务,如果返回S_OK,则外壳认为扩展已经成功,就不再继续执行了。另外如果返回一个错误代码或系统无法识别的值,则外壳会弹出错误信息。这给了我们一个控制程序运行的机会,比如可以限制任何对记事本的调用,代码如下: function TTShellExecuteHook.Execute( var FileName: String; begin Result := S_FALSE; with ShellExecuteInfo do begin FileName := UpperCase(ExtractFileName(lpFile)); if Pos(''''NOTEPAD'''', FileName) = 1 then begin Result := S_OK; hInstApp := 32; MessageBox(Wnd, ''''不允许记事本运行!'''', ''''错误'''', MB_OK or MB_ICONERROR); end; end; end; 进一步,我们甚至可以利用这点实现一个自定义的安全认证机制,根据用户要求限制运行的程序。有兴趣的朋友可以试验一下,一定很有意思。 有一点要注意的是,在Execute方法下不能调用ShellExecute和ShellExecuteEx函数外部程序,如果是这样的话,我们的Execute方法又会被新的ShellExecute调用,这样系统就会进入死循环。如果我们确实想在Execute方法中调用外部程序的话,可以使用CreateProcess或WinExec函数来替代。这两个函数不会被ShellExecuteHook截获。 对于外壳动作记录器来说,只要在Execute方法中记录程序信息到日志文件中就可以了,代码非常简单,因为所有需要的信息都在TShellExecuteInfo记录中包含了,这里只记录运行的动作、文件名和时间,需要记录其他信息的话,大家可自行修改,代码示意如下: function TTShellExecuteHook.Execute( var FileStream: TFileStream; a:TStringList; S:string; begin Result := S_FALSE; with ShellExecuteInfo do begin FileStream:=TFileStream.Create(''''c:\shellexecutehook.txt'''',fmopenwrite); S:=string(lpVerb)+'''':''''+string(lpFile)+DateTimeToStr(Now)+#13#10; FileStream.Seek(FileStream.Size,soFromBeginning); FileStream.Write(PChar(S)^,Length(S)); FileStream.Free; end; end; 注册ShellExecuteHook 要想使COM对象被外壳加载,需要在注册表中注册一些信息。在下面这个子键中添加COM类的GUID及描述字符串后就可以了(描述字符串可以不赋值,但不妨给一个以便于识别)。 HKEY_LOCAL_MACHINE SOFTWARE Microsoft Windows CurrentVersion Explorer ShellExecuteHooks {CLSID}= ''''描述字符串'''' 修改注册表可以通过重载COM的类工厂的UpdateRegistry方法来实现。代码示意如下: implementation uses ComServ, SysUtils; resourcestring sCreateRegKeyError = ''''创建注册表项失败''''; type TShellExComObjectFactory = class(TComObjectFactory) public procedure UpdateRegistry(Register: Boolean); override; end; { TShellExComObjectFactory } procedure TShellExComObjectFactory.UpdateRegistry(Register: Boolean); const hellExecuteHooksKey='''' var Handle: HKey; Status, Disposition: Integer; ClassID: String; begin ClassID := GUIDToString(Class_TShellExecuteHook); if Register then begin Status := RegCreateKeyEx(HKEY_LOCAL_MACHINE, PChar( if Status = 0 then begin Status := RegSetValueEx(Handle, PChar(ClassID), 0, REG_SZ, PChar(Description), Length(Description) + 1); RegCloseKey(Handle); end; end else begin Status := RegOpenKeyEx(HKEY_LOCAL_MACHINE, PChar(ShellExecuteHooksKey), 0, KEY_READ or KEY_WRITE, Handle); if Status = 0 then begin Status := RegDeleteValue(Handle, PChar(ClassID)); RegCloseKey(Handle); end; end; if Status <> 0 then raise EOleError.Create(sCreateRegKeyError); inherited UpdateRegistry(Register); end; initialization TShellExComObjectFactory.Create( end.
记住ShellExecuteHook并不是一个完善的用于监视系统运行的扩展。它只能监视ShellExecute和ShellExecuteEx的运行,它不能保证记录系统所有的行为。特别是很多情况下外壳并不使用ShellExecute来进行一些常用的操作,比如我们在资源管理器中选择一个文件,然后调用右键菜单的属性命令后,记录器没有记录这个动作,但如果直接调用ShellExecute(如下示)的话,ShellExecuteHook却会正确执行。 ShellExecute(nil, ''''properties'''', ''''foo.txt'''',nil,nil,SW_SHOW); 这说明外壳并不使用ShellExecute函数显示属性对话框。总之一定要谨慎使用这项技术,确保它确实符合工作的需求。 |
Delphi深度探索之自动完成外壳扩展 |
| 作者: 哈巴狗的小窝 来源:希赛网 http://www.csai.cn 2006年01月20日 |
|
自动完成简介
大家一定都非常熟悉IE浏览器的地址输入编辑框,它提供了自动完成的功能。自动完成(Auto Complete)功能简化了编辑框的输入功能,它可以根据已经输入的部分字符串进行预测和匹配。图2.7的例子,演示了自动完成可以依据输入的“pro”,给出Pascal的保留字中与其相匹配的字符串列表"procedure"、"program"、"property"。 在利用自动完成功能前,必须要知道自动完成并不是任意版本的Windows都支持,自动完成功能实际上是由Internet Explorer 4.0引入并必须有4.71版及以上的Shell32.dll的支持。很多早期的系统,包括很多安装了IE 4的系统并不支持这项特性,所以使用自动完成功能时必须确保要安装的系统支持这项功能,如果不支持就需要安装IE和集成外壳。 自动完成功能是通过一组COM接口来实现的。这些接口使我们可以连接自动完成到编辑框,添加编辑框可以枚举的字符串列表并配置其显示属性。 1. 自动完成的用途 使用自动完成功能可以增强程序的易用性,它可以节省用户反复输入一些常用字符串的时间,并减少输入错误。它还可以用于部分字符匹配的查找系统,可以用它实现对数据库内容的快速查找。另外,它还支持向浏览器历史、最近运行程序和外壳目录输入等功能中添加自定义的完成列表。 2. 配置自动完成 需要知道的是在Delphi里并没有提供自动完成功能相关COM接口的pascal声明单元。由于一般这类接口在微软都是以C语言的头文件形式提供接口,Delphi不可能及时提供最新的相应的Pascal包装单元。所以为了使用这项功能,本文中额外提供了一个shlintf单元翻译了相应的头文件的声明。下表总结了Shlintf的内容:
自动完成功能最基本的一个接口就是IAutoComplete接口,它的声明如下: type Init方法用来把IAutoComple接口以及自动完成列表同编辑框相连接。参数pwszRegKeyPath和pwszQuickComplete使得IAutoComplete接口可以按预定义的格式扩展部分输入的字符串,快捷键为CTRL+ENTER。比如设定pwszQuickComplete为”http://www.%s.cn/”后,当用户输入"csai" 到编辑框,并按下快捷键CTRL+ENTER,编辑框中的文本就会更新为http://www.csai.cn/。 Enable方法是根据fEnable标识来切换自动完成功能的开关状态的。缺省时是激活的。 下图显示了IAutoComplete接口是如何自动完成字符串的,当用户在编辑框中输入字符时,一种可能是最能匹配已经输入字符串的候选字符会被反白显示。
回过头再来看看Init方法的punkACL参数,它是一个必须的参数而且必须指向一个提供字符串的接口。注意不是我们熟悉的Delphi中提供的TStrings对象,而是一个IEnumString字符串接口。IEnumString接口负责生成一个用于自动完成的候选字符串列表。除了IEnumString接口外,punkACL参数还可以是IACList和IACList2等其他接口。 IACList接口简单地说就是可以对候选字符串分类来改善自动完成的效率,本文中将不加介绍。 要注意的是Microsoft并没有停止对自动完成功能的改进,在Internet Explorer 5.0中,Microsoft 还提供了IAutoComplete2接口。IAutoComplete2接口进行了进一步的扩展,它使得我们可以设定一系列的选项来设定自动完成的可视化表达以及操作。下面是 IAutoComplete2接口的声明: type IAutoComplete2接口引入了两种新的方法,这两种方法使用同样的标识组合作为参数,下表列出了可用的标识及其意义。
就像下图所显示的那样,IAutoComplete2同IAutoComplete的显示方式有一点不同,就是因为IAutoComplete2支持不同的显示模式,这些模式可以通过SetOptions 方法来设定。
3. 实现自动完成 自动完成功能是通过COM对象来实现的,下面代码用As操作符来获得IAutoComplete2接口: FAutoComplete := 获得接口后我们就可以实现自动完成功能了,下面代码是一个简单的例子: unit Main; interface uses Windows, Messages, SysUtils, Classes, Graphics, OleCheck(FAutoComplete.SetOptions(ACO_AUTOSUGGESTor ACO_UPDOWNKEYDROPSLIST)); OleCheck(FAutoComplete.Init(CompletionEdit.Handle, FStrings, nil, nil)); 上面的例程首先获得IAutoComplete2接口,如果系统不支持IAutoComplete2接口,as操作符将引发EinvalidCastError异常。接下来的一行代码会创建一个TEnumString类的实例并取得IUnknown接口,这个接口将作为FAutoComplete的Init方法的punkACL参数,TEnumString类是从TInterfacedObject类继承的,实现在StrTools单元中,实现它的目的是使TStrings类同IEnumString接口兼容。然后,设定显示选项使自动完成支持下拉列表和自动建议。最后初始化FAutoComplete并同编辑框相连接,被连接的编辑框就支持自动完成了。 4. 标准自动完成列表 除了支持标准的编辑框的自动完成,Microsoft还允许通过下表中的一组COM对象来存取标准自动完成列表。
下面的代码演示了如何使用自动完成功能来设定C:\下的外壳内容为候选字符串列表: var OleCheck(FAutoComplete.SetOptions(ACO_AUTOSUGGEST or ACO_UPDOWNKEYDROPSLIST)); OleCheck(FAutoComplete.Init(CompletionEdit.Handle, FStrings, nil, nil)); 5. 改进自动完成
总的来说,自动完成功能是一项非常棒的功能,它可以简化用户同经常输入的文本交互的过程。用好自动完成功能,会使得你的程序显得很专业。 |
Delphi开发嵌入式IE浏览器监控程序 |
| 作者:佚名 来源:希赛网 http://www.csai.cn 2006年02月05日 |
|
引言 随着网络的发展和普及,对网络的监控日益成为网络安全的必然要求,也是管理和维护网络的一个有效手段。当前已经有多种应用比较广泛的网络监控和过滤程序,如美萍网管、we-blocker等,它们都可以实现基于网络URL和IP地址的监控和过滤功能。 本文将对如何开发一个嵌入到IE浏览器中的监控程序进行介绍,该程序可以根据网络URL和IP地址进行监控和过滤,并且记录系统日志,从而使你对IE(当然是使用它的用户)的所有操作一览无余。相信通过阅读本文,会加深你对网络监控程序的理解,以及提高你的COM编程能力和数据库编程能力。 本文在Delphi 6开发环境下实现,数据库使用桌面数据库Access 2000。 基础知识 1.COM编程 COM(Component Object Model,组建对象模型)是微软公司(Microsoft Company)提出的二进制通信规范,用于软件组件之间的跨越多个进程、机器、硬件和操作系统的互操作,它是一项通过接口(Interface)透明的传递封装数据的技术,并且独立于语言和操作平台。一个COM对象是实现一个或者若干个接口的对象,即COM对象借助接口输出它所提供的服务。 2.IE浏览器的体系结构 IE浏览器的体系结构如图1所示。其中MSHTML是位于SHDOCVW和HTML页面之间的COM对象,SHDOCVW对象用于处理页面的显示,而MSHTML用于处理页面的语法分析,并且将页面中的标记转换为元素。MSHTML是一个COM服务器,允许COM客户端程序访问它对外提供的服务。 实现IE浏览器嵌入式编程的基本方法是建立COM对象,从而以COM客户端程序的形式和MSHTML COM服务器建立连接,然后通过接口回调实现需要的控制功能。也即首先建立COM对象,实现系统规定的若干接口,然后注册COM对象,并且将COM对象的有关信息写入到IE浏览器扩展功能指定的注册表位置。
嵌入式IE浏览器监控程序的实现 嵌入式IE浏览器监控程序的实现主要包括监控程序的实现和维护程序的实现两部分,根据程序开发过程,可以将其分为四个步骤,下面分别对各个实现步骤进行详细介绍。 1.实现COM对象 开发COM对象的任务就是建立一个能够与IE浏览器的MSHTML COM服务器建立连接并且通过接口回调实现控制功能的COM对象,具体实现过程如下: (1)运行Delphi,依次点击File|New|Other,切换到ActiveX页面,选择ActiveX Library,点击OK按钮,即建立一个新的工程。 (2) 再次点击File|New|Other,切换到ActiveX页面,选择COM Object,点击OK按钮,即出现如图2所示的COM Object Wizard界面,去掉Included Type Library复选框,在Class Name中输入COM对象的名称IEMonitor,然后点击OK按钮,即新建了一个名称为TIEMonitor的COM对象(该COM对象的CLSID由系统自动生成)(见图2)。
(3)将工程和单元文件依次保存为DIEMonitor.dpr和UIEMonitor.pas。切换到UIEMonitor.pas单元文件,编辑TIEMonitor的定义形式如下:
TIEMonitor = class(TComObject, IDispatch, IObjectWithSite) 其中主要是对IObjectWithSite接口的SetSite()方法和IDispatch接口的Invoke()方法的实现:首先通过SetSite()方法获得IE的WebBrowser接口,然后寻找连接点,并且通过调用Advise()方法建立COM自身与连接点的连接;当连接建立成功后,IE在事件引发后,会调用连接到自身的IDispatch接口对象的Invoke方法,不同的事件对应不同的DispID编码,因此可以通过在程序中判断DispID对不同的事件做出相应的处理。系统主要对BeforeNavigate2、DownLoadComplete和OnQuit三个事件进行处理,它们对应的DispID分别为250、104和253。在Invoke()方法中,根据DispID分别调用DoBeforeNavigate2()、DoDownLoadComplete()和DoOnQuit()函数实现相关处理和控制功能,具体实现详见程序源代码。 (4)编译工程,生成DIEMonitor.dll文件。 2.注册/卸载COM对象 注册COM对象包括注册COM对象,以及将COM对象的有关信息写入到IE浏览器扩展功能指定的注册表位置,下面分别对其注册方法进行介绍。卸载方法分别和注册方法相对应。 (1)注册/卸载COM对象。一种方法是在Delphi开发环境下运行Run|Register ActiveX Server/Run|Unregister ActiveX Server自动完成COM对象的注册/卸载;另一种方法是通过调用MS Windows操作系统提供的regsvr32.exe命令进行注册。 (2) 注册/卸载COM对象的有关信息到IE浏览器扩展功能指定的注册表位置。将COM对象的CLSID写入到IE浏览器扩展功能指定的注册表位置后,当IE浏览器启动时,就通过该CLSID查找COM对象有关信息(如文件路径),从而实现了COM对象嵌入式功能扩展。为了实现这个目的,只需要在注册表的[HKEY_LOCAL_MACHINE\Software\Microsoft\ Windows\CurrentVersion\Explorer\Browser Helper Objects]项下,建立一个名称为COM对象的CLSID的注册表项目即可。 3.开发监控模块 监控模块主要完成对IE浏览器的监控功能,在Invoke()函数中触发BeforeNavigator2事件时调用执行,主要包括ChkUrl()、WriteSite()、WriteLog()和GetIP()四个功能函数,分别完成检查URL(及IP地址)、记录URL和IP地址、记录系统日志、根据URL取得IP地址的功能,分别简单介绍如下,具体实现详见程序源代码。 (1)ChkUrl()函数:它是监控模块的核心函数,也是模块的入口函数。ChkUrl()函数首先取得用户请求的URL,并且调用GetIP()函数取得其对应的IP地址,然后到系统中查找该URL及IP是否已经存在,如果存在则根据系统保存的结果禁止/允许用户访问,否则就根据系统的当前工作状态(禁止/允许)动态处理,同时调用WriteSite()函数将该URL和IP地址保存到系统中。最后调用WriteLog()函数记录系统日志。 (2)WriteSite()函数:完成将URL和IP地址保存到系统中的功能,同时记录系统当前日期和时间。 (3)WriteLog()函数:将有关信息形成系统日志并且保存下来,主要有网络URL和IP地址、用户名称、访问日期和时间、处理结果(禁止/允许访问)等信息。 (4)GetIP()函数:提供根据URL取得对应IP地址的功能,通过调用TNMHTTP组件的相关功能实现:首先将URL赋值给TNMHTTP组件的Host属性,然后调用ResolveRemoteHost()方法,就可以从其RemoteIP属性中获得对应的IP地址。 4.开发维护程序 维护程序主要实现系统参数维护和日志管理两方面功能,在启动时需要管理员登录。 (1)系统参数维护:启用/停止系统监控功能等,程序界面如图3所示。
(2)系统日志管理:日志的查询、打印等功能(见图4)。
总结和系统进一步完善计划 本文详细介绍了嵌入式IE浏览器监控程序的开发方法和实现过程,该系统以COM对象的形式嵌入到IE浏览器中,可以实现对IE浏览器(即用户)的监控功能。 |