记录外壳的活动
记录外壳活动有很多好处比如当需要监控用户的行为回溯系统崩溃前的过程实现这一功能的关键工具相当简单它就是COM接口IShellExecuteHook编写一个实现了这一接口的COM对象后再在系统中注册就可以容易地控制并影响Windows外壳的运行Windows 和Windows 都支持IShellExecuteHook外壳扩展而在Windows 和Windows NT 上则必须安装活动桌面扩展后才支持(也就是说必须安装IE )
一个实现了IShellExecuteHook接口的COM对象可以截获所有对ShellExecute和ShellExecuteEx函数的调用ShellExecute和ShellExecuteEx函数主要用于执行应用程序它们可以接收一个文件名并能自动获得同文件名相关的可执行文件名此外它们还支持系统安全认证如果在NT上设定了用户的可执行权限ShellExecute和ShellExecuteEx函数将会在创建新的进程前检查权限(CreateProcess和WinExec函数则没有这项功能)函数调用的流程如下
()获得将要运行的可执行文件名
()根据程序名检查用户执行权限
()激活全部已注册的IshellExecuteHook扩展
()当所有扩展和权限都同意执行创建新的进程并返回
Windows外壳大量调用ShellExecute和ShellExecuteEx函数来执行几乎是所有的资源管理器的操作比如双击目录浏览文件夹内容打印编辑文档查看文件属性选择文档的上下文相关菜单等等此外开始菜单的运行对话框和DOS方式下的Startexe也使用ShellExecuteEx函数来执行程序简单地说几乎用户的所有外壳操作都可以被扩展截获包括其他应用程序对ShellExecute和ShellExecteEx的调用
编写外壳活动记录器
首先需要创建一个进程内COM对象选菜单命令New | ActiveX Library然后点击菜单New|Com Object创建COM对象框架按图填充对话框的内容然后点击OK按钮Delphi就会自动生成框架文件并保存生成的文件
IShellExecuteHook的接口定义在shlobjpas单元中添加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 = {FADDBEBAEBE};
下面就是用来截获并记录外壳操作的实现部分一旦外壳扩展被注册后每次ShellExecute 和ShellExecuteEx函数运行时都会调用COM对象的Execute函数我们的核心代码就是通过Execute方法实现的方法定义如下
function TTShellExecuteHookExecute(
var ShellExecuteInfo: TShellExecuteInfo): HResult;
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 TTShellExecuteHookExecute(
var ShellExecuteInfo: TShellExecuteInfo): HResult;
var
FileName: String;
begin
Result := S_FALSE;
with ShellExecuteInfo do
begin
FileName := UpperCase(ExtractFileName(lpFile));
if Pos(NOTEPAD FileName) = then
begin
Result := S_OK;
hInstApp := ;
MessageBox(Wnd 不允许记事本运行! 错误 MB_OK or MB_ICONERROR);
end;
end;
end;
进一步我们甚至可以利用这点实现一个自定义的安全认证机制根据用户要求限制运行的程序有兴趣的朋友可以试验一下一定很有意思
有一点要注意的是在Execute方法下不能调用ShellExecute和ShellExecuteEx函数外部程序如果是这样的话我们的Execute方法又会被新的ShellExecute调用这样系统就会进入死循环如果我们确实想在Execute方法中调用外部程序的话可以使用CreateProcess或WinExec函数来替代这两个函数不会被ShellExecuteHook截获
对于外壳动作记录器来说只要在Execute方法中记录程序信息到日志文件中就可以了代码非常简单因为所有需要的信息都在TShellExecuteInfo记录中包含了这里只记录运行的动作文件名和时间需要记录其他信息的话大家可自行修改代码示意如下
function TTShellExecuteHookExecute(
var ShellExecuteInfo: TShellExecuteInfo): HResult;
var
FileStream: TFileStream;
a:TStringList;
S:string;
begin
Result := S_FALSE;
with ShellExecuteInfo do
begin
FileStream:=TFileStreamCreate(c:\shellexecutehooktxtfmopenwrite);
S:=string(lpVerb)+:+string(lpFile)+DateTimeToStr(Now)+##;
FileStreamSeek(FileStreamSizesoFromBeginning);
FileStreamWrite(PChar(S)^Length(S));
FileStreamFree;
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 TShellExComObjectFactoryUpdateRegistry(Register: Boolean);
const
hellExecuteHooksKey=
SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellExecuteHooks;
var
Handle: HKey;
Status Disposition: Integer;
ClassID: String;
begin
ClassID := GUIDToString(Class_TShellExecuteHook);
if Register then
begin
Status := RegCreateKeyEx(HKEY_LOCAL_MACHINE PChar(
ShellExecuteHooksKey) REG_OPTION_NON_VOLATILE
KEY_READ or KEY_WRITE nil Handle @Disposition);
if Status = then
begin
Status := RegSetValueEx(Handle PChar(ClassID) REG_SZ
PChar(Description) Length(Description) + );
RegCloseKey(Handle);
end;
end else
begin
Status := RegOpenKeyEx(HKEY_LOCAL_MACHINE PChar(ShellExecuteHooksKey)
KEY_READ or KEY_WRITE Handle);
if Status = then
begin
Status := RegDeleteValue(Handle PChar(ClassID));
RegCloseKey(Handle);
end;
end;
if Status <> then raise EOleErrorCreate(sCreateRegKeyError);
inherited UpdateRegistry(Register);
end;
initialization
TShellExComObjectFactoryCreate(
ComServer TTShellExecuteHook Class_TShellExecuteHookTShellExecuteHook
ShellExecute hook sample ciMultiInstance tmApartment);
end
如果系统中有多个ShellExecuteHook的话外壳会按照ShellExecuteHook的安装顺序进行调用如果要想使某个外壳扩展优先运行可先删除其他扩展然后添加优先扩展原来的扩展依次放在后面不过这样做也可能意义不大因为别人也会这么干最后程序运行的结果
记住ShellExecuteHook并不是一个完善的用于监视系统运行的扩展它只能监视ShellExecute和ShellExecuteEx的运行它不能保证记录系统所有的行为特别是很多情况下外壳并不使用ShellExecute来进行一些常用的操作比如我们在资源管理器中选择一个文件然后调用右键菜单的属性命令后记录器没有记录这个动作但如果直接调用ShellExecute(如下示)的话ShellExecuteHook却会正确执行
ShellExecute(nil properties footxtnilnilSW_SHOW);
这说明外壳并不使用ShellExecute函数显示属性对话框总之一定要谨慎使用这项技术确保它确实符合工作的需求