前几天有位网友问我用VB实现SHELL扩展的问题,这个问题比较有意思,虽然VB较少使用了,但是用VB开发COM组件还是比较方便的(前几天用EVC开发COM组件,相比起来,用VB还是比较幸福的),所以便进行了深入的研究。
Shell扩展有多种,而我们目前所关注的就是实现“IShellExecuteHookW ”接口的扩展,这个接口功能很强劲,只要是window加载相应程序,必须要过这一关,这样你就可以在程序执行之前预先获知要运行的进程名称,并且你可以决定该程序是否执行(瑞星杀毒软件就实现了这样一个组件,在程序运行之前,进行截获并杀毒)。
网上有位朋友用C#实现了该功能,链接如下:http://blog.csdn.net/startsoft/archive/2002/12/30/13417.aspx,写的很详细,不过我没有用C#做一遍,是否有效不好说。
从内容上看实现该功能应该比较容易,然而上帝是公平的,用VB虽然编写COM组件比较容易,但却在实现过程中,为VB设下一个又一个难关,下面我一一道来!
1、 接口函数为过程,不是函数,不能返回值
这是VB默认生成的接口函数,如果强制修改为Function,则编译无法通过。
Public Sub IShellExecuteHookW_Execute(pei As olelib.SHELLEXECUTEINFO)
End Sub
而恰恰是,实现该功能必须要返回值,实际发现,该过程虽然没有返回值,但是对调用者来说,返回的是S_OK,这样,所有的程序都无法启动,因为只有返回值为S_FALSE时,才允许可执行文件运行。
不过,如果过程中显示路径信息的话,是可以正确显示的(也就是说可以截获程序运行信息)。
2、 另辟蹊径,用黑客技术实现函数返回
查了n多资料,发现有如下实现方法:
‘-------类中的代码------------
Implements IShellExecuteHookW
Private m_pOldIShellExecuteHookW As Long
Private Sub Class_Initialize()
Dim pShellExecuteHookW As IShellExecuteHookW
Set pShellExecuteHookW = Me
'把“IShellExecuteHookW_Execute”接口函数替换为“Execute”
m_pOldIShellExecuteHookW = SwapVtableEntry(ObjPtr(pShellExecuteHookW), 4, AddressOf Execute)
End Sub
Public Sub IShellExecuteHookW_Execute(pei As olelib.SHELLEXECUTEINFO)
'空接口,实际并不执行,因为已转入Execute 中执行
End Sub
‘---------模块中的代码---------------
Public Function Execute(pei As olelib.SHELLEXECUTEINFO) As HRESULTS
‘新接口,如果接口被调用,则执行该函数体内的代码
End Sub
Public Function SwapVtableEntry(pObj As Long, EntryNumber As Integer, ByVal lpfn As Long) As Long
Dim lOldAddr As Long
Dim lpVtableHead As Long
Dim lpfnAddr As Long
Dim lOldProtect As Long
CopyMemory lpVtableHead, ByVal pObj, 4
lpfnAddr = lpVtableHead + (EntryNumber - 1) * 4
CopyMemory lOldAddr, ByVal lpfnAddr, 4
Call VirtualProtect(lpfnAddr, 4, PAGE_EXECUTE_READWRITE, lOldProtect)
CopyMemory ByVal lpfnAddr, lpfn, 4
Call VirtualProtect(lpfnAddr, 4, lOldProtect, lOldProtect)
SwapVtableEntry = lOldAddr
End Function
从以上代码可以看出,在COM组件被初始化时,把原接口函数的地址换成新接口地址,使我们自定义的接口函数取代原函数。
注意上面的代码,在模块中接口“Execute”已经是函数形式,可以返回值了
至于SwapVtableEntry函数第二个参数为什么是4,我也不清楚,我看过其他相关例程,什么数字的都有,不过一般都是4,我实际测试过,如果不是4,有种情况是原类中的接口和模块中的“Execute”会先后执行的(有的甚至会执行几次)。
这个时候,编译加载,你发现是可以通过不同的返回值,来决定刚打开的程序是否运行的,不过命运之神偏偏又捉弄我们,Execute函数的参数值有问题,无法正确的显示程序信息。
3、柳暗花明
刚开始我以为是SwapVtableEntry第二个参数在搞怪,从0测试到11,都不行,反而把Windows搞死好几次。
后来把pei As olelib.SHELLEXECUTEINFO参数定义为lPei as long 型,通过内存拷贝,进行类型赋值也不行。
实际发现pei. cbSize参数为结构体的大小,固定为60,所以我把该参数的前后64个字节全看了个遍,也没有发现有60的,实在没有办法了,我又仔细看了看用C#实现的代码:
public class ExtenShell : IShellExecuteHook
{
private int S_OK=0;
private int S_FALSE=1;
public int Execute(SHELLEXECUTEINFO sei)
{
try
{
MessageBox.Show(null, "[ Verb ]: " + sei.lpVerb + "/n[ File ]: " + sei.lpFile + "/n[ Parameters ]:" + sei.lpParameters + "/n[ Directory ]:" + sei.lpDirectory , "ShellExtensionHook",MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch(Exception e)
{
Console.Error.WriteLine("Unknown exception : " + e.ToString());
}
return S_FALSE;
//如果返回值为S_OK则SHELL将停止对Shell对象的以后的所有动作。
}
}
用C#实现很简单,直接实现public int Execute(SHELLEXECUTEINFO sei)接口就可以了,看着看着,突然,灵光一现,接口Execute为类中的函数,而在VB中新的接口函数放在模块中,普通函数和类中函数是有区别的,那就是类中的函数的第一个参数为隐含参数,也就是this指针,一般指针的长度为4个字节,在VB中也就是long型,好,重新把VB模块中函数声明如下:
Public Function Execute(this As Long, pei As olelib.SHELLEXECUTEINFO) As HRESULTS
End Function
注意,新添加了this As Long参数,好,编译测试,OK,成功!!!
完整代码如下:
1、类中代码:
'*************************************************************************
'**模块名:CShellHook
'**说明:YFsoft版权所有2007-2008(C)
'**创建人:叶帆
'**日期:2007-08-2313:20:11
'**修改人:
'**日期:
'**描述:叶帆工作室http://blog.csdn.net/yefanqiu
'**版本:V1.0.0
'*************************************************************************
OptionExplicit
ImplementsIShellExecuteHookW
Privatem_pOldIShellExecuteHookWAsLong
'*************************************************************************
'**函数名:Class_Initialize
'**输入:无
'**输出:无
'**功能描述:类初始化
'**全局变量:
'**调用模块:
'**作者:叶帆
'**日期:2007-08-2313:20:09
'**修改人:
'**日期:
'**版本:V1.0.0
'*************************************************************************
PrivateSubClass_Initialize()
DimpShellExecuteHookWAsIShellExecuteHookW
SetpShellExecuteHookW=Me
'把“IShellExecuteHookW_Execute”接口函数替换为“Execute”
m_pOldIShellExecuteHookW=SwapVtableEntry(ObjPtr(pShellExecuteHookW),4,AddressOfExecute)
EndSub
'*************************************************************************
'**函数名:IShellExecuteHookW_Execute
'**输入:pei(olelib.SHELLEXECUTEINFO)-
'**输出:无
'**功能描述:接口函数(为空)
'**全局变量:
'**调用模块:
'**作者:叶帆
'**日期:2007-08-2313:20:24
'**修改人:
'**日期:
'**版本:V1.0.0
'*************************************************************************
PublicSubIShellExecuteHookW_Execute(peiAsolelib.SHELLEXECUTEINFO)
'已转入Execute中执行
EndSub
'*************************************************************************
'**函数名:Class_Terminate
'**输入:无
'**输出:无
'**功能描述:类销毁
'**全局变量:
'**调用模块:
'**作者:叶帆
'**日期:2007-08-2313:20:19
'**修改人:
'**日期:
'**版本:V1.0.0
'*************************************************************************
PrivateSubClass_Terminate()
DimpShellExecuteHookWAsIShellExecuteHookW
SetpShellExecuteHookW=Me
m_pOldIShellExecuteHookW=SwapVtableEntry(ObjPtr(pShellExecuteHookW),4,m_pOldIShellExecuteHookW)
EndSub
2、模块中的代码
'*************************************************************************
'**模块名:ShellHook
'**说明:YFsoft版权所有2007-2008(C)
'**创建人:叶帆
'**日期:2007-08-2313:23:52
'**修改人:
'**日期:
'**描述:叶帆工作室http://blog.csdn.net/yefanqiu
'**版本:V1.0.0
'*************************************************************************
OptionExplicit
PublicConstE_NOTIMPL=&H80004001
PublicConstPAGE_EXECUTE_READWRITE=&H40&
PublicConstS_FALSE=1
PublicConstS_OK=0
PublicDeclareSubCopyMemoryLib"kernel32"Alias"RtlMoveMemory"(pDestAsAny,pSourceAsAny,ByValByteLenAsLong)
PublicDeclareFunctionVirtualProtectLib"kernel32"(ByVallpAddressAsLong,ByValdwSizeAsLong,ByValflNewProtectAsLong,ByReflpflOldProtectAsLong)AsLong
PrivateDeclareFunctionlstrlenALib"kernel32"(ByVallpStringAsLong)AsLong
PrivateDeclareFunctionlstrlenWLib"kernel32"(ByVallpStringAsLong)AsLong
PrivateDeclareFunctionlstrcpyALib"kernel32"(ByVallpString1AsLong,ByVallpString2AsLong)AsLong
PrivateDeclareFunctionlstrcpyWLib"kernel32"(ByVallpString1AsLong,ByVallpString2AsLong)AsLong
'*************************************************************************
'**函数名:Execute
'**输入:this(Long)-类的this指针
'**:pei(olelib.SHELLEXECUTEINFO)-参数
'**输出:(HRESULTS)-
'**功能描述:被替换的接口函数
'**全局变量:
'**调用模块:
'**作者:叶帆
'**日期:2007-08-2313:23:56
'**修改人:
'**日期:
'**版本:V1.0.0
'*************************************************************************
PublicFunctionExecute(thisAsLong,peiAsolelib.SHELLEXECUTEINFO)AsHRESULTS
DimstrInfoAsString
strInfo=strInfo+"cbSize"+str(pei.cbSize)+vbCrLf
strInfo=strInfo+"fMask"+str(pei.fMask)+vbCrLf
strInfo=strInfo+"hInstApp"+str(pei.hInstApp)+vbCrLf
strInfo=strInfo+"hwnd"+str(pei.hwnd)+vbCrLf
strInfo=strInfo+"lpDirectory"+StrFromPtr(pei.lpDirectory,True)+vbCrLf
strInfo=strInfo+"lpFile"+StrFromPtr(pei.lpFile,True)+vbCrLf
strInfo=strInfo+"lpParameters"+StrFromPtr(pei.lpParameters,True)+vbCrLf
strInfo=strInfo+"lpVerb"+StrFromPtr(pei.lpVerb,True)+vbCrLf
strInfo=strInfo+"nShow"+str(pei.nShow)+vbCrLf
MsgBoxstrInfo
IfMsgBox("允许'"+StrFromPtr(pei.lpFile,True)+"'程序执行吗?",vbQuestion+vbOKCancel,"程序运行监控")=vbOKThen
Execute=S_FALSE
Else
Execute=S_OK
EndIf
EndFunction
'*************************************************************************
'**函数名:SwapVtableEntry
'**输入:pObj(Long)-类对象初始地址
'**:EntryNumber(Integer)-入口函数索引
'**:ByVallpfn(Long)-新函数
'**输出:(Long)-原函数地址
'**功能描述:更换接口函数
'**全局变量:
'**调用模块:
'**作者:叶帆
'**日期:2007-08-2313:24:26
'**修改人:
'**日期:
'**版本:V1.0.0
'*************************************************************************
PublicFunctionSwapVtableEntry(pObjAsLong,EntryNumberAsInteger,ByVallpfnAsLong)AsLong
DimlOldAddrAsLong
DimlpVtableHeadAsLong
DimlpfnAddrAsLong
DimlOldProtectAsLong
CopyMemorylpVtableHead,ByValpObj,4
lpfnAddr=lpVtableHead+(EntryNumber-1)*4
CopyMemorylOldAddr,ByVallpfnAddr,4
CallVirtualProtect(lpfnAddr,4,PAGE_EXECUTE_READWRITE,lOldProtect)
CopyMemoryByVallpfnAddr,lpfn,4
CallVirtualProtect(lpfnAddr,4,lOldProtect,lOldProtect)
SwapVtableEntry=lOldAddr
EndFunction
'*************************************************************************
'**函数名:StrFromPtr
'**输入:ByVallpString(Long)-字符串指针
'**:OptionalfUnicode(Boolean=False)-字符格式
'**输出:(String)-字符串
'**功能描述:转换字符串
'**全局变量:
'**调用模块:
'**作者:叶帆
'**日期:2007-08-2313:24:28
'**修改人:
'**日期:
'**版本:V1.0.0
'*************************************************************************
PublicFunctionStrFromPtr(ByVallpStringAsLong,OptionalfUnicodeAsBoolean=False)AsString
OnErrorResumeNext
IffUnicodeThen
StrFromPtr=String(lstrlenW(lpString),Chr(0))
lstrcpyWStrPtr(StrFromPtr),ByVallpString
Else
StrFromPtr=String(lstrlenA(lpString),Chr(0))
lstrcpyAByValStrFromPtr,ByVallpString
EndIf
EndFunction
注:要实现在程序运行截获,必须在注册表添加如下项(如下图),字符串为COM的GUID,VB中生成的COM的GUID,你可以在注册表中搜索获取,也可以用专门的工具直接查看(我用的工具是,RegCtrls.exe),当然也可以新建VB工程引用你的COM组件,保存后,用文本编辑器打开工程文件,查看相应GUID信息。

程序在浏览器被双击运行后,会提前弹出如下对话框(此外程序中调用的进程,也会显示该对话框),这时候该程序运不运行就你说了算了。别说和Vista中安全机制还真有些像,需要用户确认下才能运行:)

分享到:
相关推荐
总之,VB通过Shell函数和`WScript.Shell`对象提供了一种灵活的方式来调用外部exe,并能等待其返回,从而实现更复杂的交互操作。掌握这种技术对于任何VB开发者来说都是非常有价值的,无论你是新手还是经验丰富的...
在VB6(Visual Basic 6)编程环境中,获取文件扩展名对应的图标是一项常见的任务,尤其在设计用户界面或实现文件管理功能时。本教程将详细解释如何根据文件扩展名来获取并显示相应的图标。 首先,我们需要理解文件...
在VB6中,我们有时需要调用操作系统命令并获取这些命令的输出结果,这通常涉及到“Shell”功能。本文将详细介绍如何在VB6中通过全API方式来实现这一目标。 首先,要理解什么是“Shell”。在计算机术语中,Shell通常...
总结来说,VBShell和Run Shell VB是VB编程中用于交互操作系统的强大工具,它们极大地扩展了VB程序的功能,使开发者能灵活地控制和整合系统资源。然而,这也要求开发者具备良好的安全意识,以确保程序的稳定性和安全...
接口在VB6.0中用作一种设计工具,帮助我们保持代码的模块化和可扩展性。下面我们将深入探讨类接口的相关知识点: 1. **接口定义**:在VB6.0中,接口是通过`Interface`关键字来声明的。它包含一组纯虚方法(没有实现...
为了实现这个需求,我们需要掌握如何让VB等待`Shell`调用的程序运行结束。 首先,让我们深入理解`Shell`函数的基本用法。`Shell`函数的语法如下: ```vb Public Declare Function Shell Lib "kernel32" Alias ...
VB 中,常以Shell指令来执行外部程式,然而它在Create该外部process 後,立刻 就会回到vb 的下一行程式,无法做到等待该Process结束时,才执行下一行指令, 或是说,无法得知该Process是否已结束,甚者,该...
VB Shell外壳程序是一种基于Visual Basic(VB)编程语言构建的外壳扩展,它允许开发者自定义Windows操作系统的右键菜单、文件关联等行为。这个实例是一个实际的VB Shell外壳程序,包含源代码,对于想要深入学习VB...
在VB6.0中,我们通常会使用MSComm控件(Microsoft Communications Control)来处理串口相关的操作,如打开、关闭串口,设置波特率、数据位、停止位以及奇偶校验等参数,以及发送和接收数据。 首先,了解SHELL函数的...
在本文中,我们将深入探讨VB中Shell函数的使用,并结合大学生可能需要的七类网站,来阐述如何利用Shell函数实现这些功能。这对于初学者来说是一个很好的实践案例,可以帮助他们更好地理解和应用VB中的这一核心概念。...
**VB.NET获取进程命令参数详解** 在VB.NET编程中,获取进程及其命令行参数是一项重要的功能,这通常涉及到操作系统层面的交互。VB.NET提供了一种简单的方法来访问这些信息,主要通过`System.Diagnostics.Process`类...
综上所述,通过VB编程可以实现获取指定扩展名关联的程序图标,并进一步处理这些图标,如显示在用户界面中。在实际应用中,可能还需要处理错误和异常,以及适应不同环境下的兼容性问题。希望提供的这些信息能对你有所...
VB调用显示Windows控制面板各个模块,也就是vb操作控制面板,把Windows控制面板中的内容全部显示在本程序的窗口中,像键盘设置、区域设置、网络、显示、多媒体等各个模块的显示。本例中主要是通过rundll32.exe shell...
VB简单获取文件扩展名,用法很简单:选择要获得扩展名的文件,点击操作按钮,最后就显示文件的扩展名了,简要说一下原理:从文件名的长度到文件名的第一个字符作循环,如果当前的字符是".",设置变量Ext的值为i,...
在VB中,获取硬盘序列号主要通过Windows API(应用程序接口)来实现,因为VB自身并不直接提供获取硬件信息的内置函数。以下是一个简单的示例,展示如何使用API函数: 1. 首先,我们需要引入API函数`CreateObject`,...
在VB.NET编程环境中,处理文件名以及其扩展名是一项常见的需求。例如,在某些情况下,开发者可能需要去除文件名中的扩展名部分,以便进行更进一步的处理或者为了方便地操作文件。下面将详细介绍如何通过自定义函数来...
在VB中获取机器码,我们可以利用Windows API(应用程序接口)调用来实现。API函数提供了与操作系统进行交互的能力,例如`WMI`(Windows Management Instrumentation)服务就提供了获取硬件信息的方法。下面将详细...
在IT领域,特别是编程与硬件交互方面,使用Visual Basic(简称VB)来实现USB接口设备的数据传送是一项重要的技能。本文将深入探讨如何利用VB来控制和通信与USB设备,特别是针对C8051F320这类微控制器。通过分析标题...
首先,我们需要了解Windows的上下文菜单(CtxMenu),它是通过ShellExtensibility接口来实现扩展的。这个接口包含了一系列方法,如`QueryContextMenu`、`InvokeCommand`和`GetCommandString`,它们分别用于创建菜单...