What were ShellExecute hooks designed for? - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20080910-00/?p=20933
Raymond Chen 2008年09月10日
ShellExecute 钩子是为什么设计的?
简要
ShellExecute钩子设计用于扩展可执行字符串集合,如使IE能通过"运行"对话框打开网址。滥用此钩子进行安全检查或审计,不仅违背设计初衷,也可能因其他钩子或直接调用CreateProcess而失效。
正文
Windows 95引入了(Windows Vista移除了)ShellExecute钩子的概念。这些是实现了IShellExecuteHook
接口的对象。该接口只有一个方法:IShellExecuteHook::Execute
,它接收一个SHELLEXECUTEINFO
结构体,并返回S_OK
表示项目已执行,返回S_FALSE
允许继续处理,以及返回一个错误代码以停止处理。
Shell执行钩子的预期用途是允许你扩展可以执行的字符串集合。例如,Internet Explorer 1使用了一个Shell执行钩子,这样你就可以在"运行"对话框中输入http://www.microsoft.com/
并调用Web浏览器。这是必要的,因为最初的Windows 95版本对URL没有特殊了解。如果没有钩子,输入URL到"运行"对话框将会产生一个错误。
让我们再次看看那些返回值。最简单的返回值是S_FALSE
,这意味着你没有识别该项目并希望继续正常处理。返回S_OK
意味着你识别了该项目并成功执行了它。返回一个错误代码意味着你识别了该项目,但在尝试执行时发生了错误。返回错误会停止进一步处理,这在这种情况下很重要,因为你确实想要停止处理:你识别了用户尝试做的事情,但你无法做到。如果你返回了S_FALSE
,那么Shell(和其他Shell执行钩子)会尝试执行该项目。由于这是只有你识别的东西,它们会失败,并且用户会得到某种错误,如“文件未找到”,而不是准确描述为什么你无法执行该项目的错误。
这就是理论,但我已经看到一些应用程序将Shell执行钩子用于完全不同的目的。它们注定要失败。一个应用程序安装了一个Shell执行钩子以实现某种奇怪的“安全”概念。当他们的钩子被调用时,它们会检查用户是否“被允许”运行程序;如果允许,它们返回S_FALSE
,如果被阻止,它们返回E_ACCESSDENIED
。另一个应用程序使用他们的钩子来记录用户运行的所有程序,大概是为了让日志可以用于审计目的。记录操作后,它们返回S_FALSE
以允许正常执行继续。
这两种方法都是对Shell执行钩子模型的滥用,因为它们并没有扩展Shell可以执行的项目集合。它们只是使用钩子模型将一些代码潜入执行路径。但这也是问题所在。
首先,它们只拦截通过ShellExecuteEx
函数进行的操作。如果有人直接调用CreateProcess
,那么Shell执行钩子就不会运行,那么刚刚绕过了你的“安全”和“审计”系统。
其次,这些钩子都假设它们是系统中唯一的Shell执行钩子。(记住,可以有多个。)假设系统中安装了三个钩子,由Internet Explorer安装的用于处理URL的钩子,"审计"钩子和"安全"钩子,假设Shell决定按这个顺序调用它们。(钩子执行的顺序未指定,因为如果你按预期使用它们,顺序并不重要。)如果用户在"运行"对话框中输入一个URL,URL钩子启动Web浏览器并返回S_OK
表示,“我处理了这个。”你的“审计”钩子从未看到过URL,你的“安全”钩子从未有机会拒绝操作。位于“安全”钩子前面的钩子不仅可以绕过安全,而且还能逃避“审计”钩子。
“审计”和“安全”就此泡汤。
侧边栏
由于Shell执行钩子实际上成为ShellExecute
函数的一部分,它们无意中受到ShellExecute
必须遵循的所有应用程序兼容性约束的限制。欢迎来到操作系统。你的世界刚刚变得更加复杂。
没有“应用程序兼容性约束列表”(哦,如果有,生活会变得多么简单);基本上,ShellExecute
过去做的任何事情都是事实上的兼容性约束。一些程序如果在ShellExecute
内部创建工作线程就会崩溃;其他程序如果在ShellExecute
内部调用PeekMessage
就会崩溃。一些程序调用ShellExecute
然后立即退出,这意味着你的Shell执行钩子最好在工作完成之前(或至少在工作已经交给另一个进程完成之前)不要返回。一些程序在异常小的堆栈上调用ShellExecute
,所以你需要注意你的堆栈使用。即使文档中指出程序不应该在DllMain
内部调用Shell函数,但这并不能阻止人们尝试。你的Shell执行钩子在被调用到这种“不可能”的条件下,最好以一种理智的方式表现。