实用代码:【源码下载】全局键盘钩子的VB实现
相信很多人都知道用vb不能做出全局的钩子,导致这个不能的原因有这么两个:
1、vb无法制作可自由导出函数的dll文件
2、vb对多线程支持非常不好,且运行库中众多的函数都不能在多线程环境中运行
对于前者,已经有现成的解决方案。只要拦截vb编译过程,然后改一下连接程序的参数就可以攻克这个问题。而后者而言,现在为止没有什么完全的解决方法,唯有针对特定问题的答案。
接下来放送一下我弄这个全局钩子的全过程:
首先解决制作可以导出函数的dll的问题。这个问题我是用我自己以前写的一个叫BuildControl的半拉子插件解决的。之所叫半拉子插件,是因为我只完成了导出所有公共函数的功能而已……不过,对于眼前这个问题是绝对够用了。接着,我就开始写入钩子函数之类(并编译成dll,准备第1次测试。
点击hook后,没有什么反应……光这样是看不出来我们的dll是否已经被进入了目标程序的进程空间di,于是我拿出ollydbg开始调试。按了hook后,每按一次键盘就看到目标程序模块列表中一闪而过我们的dll……看来,dll是已经注入了,而问题可能出在其他地方。我在dll的入口处设置了一个断点,再次按了一下键盘。光标停在了dllmain的入口处,再一步步跟踪后终于发现问题所在:
call ds:FindWindowW
mov edi, ds:__vbaSetSystemError
mov esi, eax
call edi ; __vbaSetSystemError
这个__vbaSetSystemError函数就是症结所在了。__vbaSetSystemError函数的作用是调用api函数GetLastError获得api调用的错误代码,以供Err对象使用。编译器把__vbaSetSystemError插入到了每个api函数的调用后面。所以,虽然我在一开始就使用类型库导入api函数,并在整个dll里都使用Unicode版的api而没有使用vb库中的函数,想避免发生线程相关的问题,但是,没想到还是逃不过这个编译器预设的陷阱。
现在,问题变为如何使__vbaSetSystemError函数无效。整个程序里有那么多的api调用,不可能搜索程序替换掉每个__vbaSetSystemError函数,那么只能dll文件的函数输入表(IAT)入手,将其函数地址改为另外一个地方。因为调用一次api就必定会调用一次__vbaSetSystemError,所以我们只有1个api的调用机会。可能这里有些同志会想到,用copymemory或writeprocessmemory,但是我们并不知道__vbaSetSystemError函数在导入函数表里的具体地址,而且,导入函数表所在的内存位置一般都是只读的,需要用VirtualProtect函数来设定其为可写后才能将内容写入。这么多的任务是绝对不可能用一个api就能搞定的。经过一番思索,唯一能胜任的就只有CallWindowProc函数了。用CallWindowProc函数可以调用其lpPrevWndFunc参数所指向地址的代码,用这个函数来执行一段能完成上述任务的汇编代码那就万事ok了。
汇编代码主要做这么几个事情:
1、找到__vbaSetSystemError函数在当前dll中的引入地址位置
2、得到VirtualProtect函数的地址
3、调用VirtualProtect函数设定读写权限
4、改写地址
得到函数的引入地址是比较简单的,只要看看pe文件结构相关的材料就可以。但是要得到VirtualProtect函数的地址就麻烦了,因为需要先知道其所在库文件Kernel32.dll的内存首地址。因为不同的系统kernel32的装入位置都不尽相同,而根据所有能找得找的定位kernel32地址的方法都有所缺憾,并不能保证一定正确,所以,只能另想办法……
通过类型库使用的api函数与直接在程序中用delcare语句声明的api函数区别在于,前者在编译时就把这些api函数作为导入函数直接编译进程序,而后者则还要通过vb运行库中的DllFunctionCall函数来调用。既然如此,如果我们在程序中使用了通过类型库引入的VirtualProtect函数,那么运行时在dll的输入函数表里就会有这个函数的正确内存地址了。想到这里我就在程序中添加了一个哑函数:
Private Sub DummyFuncionLib()
Call VirtualProtect(0, 0, 0, 0)
End Sub
这个函数是永远也不会被调用的,他的存在只是为了让输入函数表里有VirtualProtect。
写汇编代码,并将其编译成二进制代码,有了二进制代码,接下去就是把它表示到vb中了。我选择用一个有几十个Currency类型成员的结构来存放这堆数字,而不是用数组,因为天知道用数组又会出现什么问题呢。
ok,现在我们有了应该有的所有东西。最后在dll入口点函数dllmain的DLL_PROCESS_ATTACH分支里写上调用补丁的代码,然后整个世界就安静了……~~
p.s. 我所用的方法可能不是最好的,可能在实际使用过程中又会有这样那样的奇怪问题,但是,我想,这曾经成功过的经历就足以激励我们继续前进了。VB的魅力从未消去……
相关链接:
1、编译控制:BuildControl http://210.33.90.250/inc/vbsrc/buildcontrol.rar
2、程序源码:KBHook http://210.33.90.250/inc/vbsrc/kbhook.rar