今天在看罗云彬的Windows环境下32位汇编语言程序设计时,在408页看到有句话“……所以远程钩子的钩子函数必须位于一个的动态链接库中,而且必须是共享数据段的动态链接库”,前面那段没有问题,我们来研究下后面那一部分。
他书上在后面的那键盘例子里面也许真需要这样的技术,但,这不是一般情况,这里共享数据段只是为了跨进程共享数据来用。
我们先来看看那例子,有几个有趣的地方。
/
Main.asm
1 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 2 ; Sample code for < Win32ASM Programming 2nd Edition> 3 ; by 罗云彬, http://asm.yeah.net 4 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 5 ; Main.asm 6 ; 键盘钩子演示程序的主程序,调用 dll 装载键盘钩子 7 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 8 ; 使用 nmake 或下列命令进行编译和链接: 9 ; ml /c /coff Main.asm 10 ; rc Main.rc 11 ; Link /subsystem:windows Main.obj Main.res 12 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 13 .386 14 .model flat, stdcall 15 option casemap :none 16 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 17 ; Include 文件定义 18 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 19 include windows.inc 20 include user32.inc 21 includelib user32.lib 22 include kernel32.inc 23 includelib kernel32.lib 24 include Hookdll.inc 25 includelib Hookdll.lib 26 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 27 ; Equ 等值定义 28 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 29 ICO_MAIN equ 1000 30 DLG_MAIN equ 1000 31 IDC_TEXT equ 1001 32 WM_HOOK equ WM_USER + 100h 33 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 34 ; 代码段 35 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 36 37 .code 38 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 39 _ProcDlgMain proc uses ebx edi esi hWnd,wMsg,wParam,lParam 40 local @dwTemp 41 42 mov eax,wMsg 43 ;******************************************************************** 44 .if eax == WM_CLOSE 45 invoke UninstallHook 46 invoke EndDialog,hWnd,NULL 47 ;******************************************************************** 48 .elseif eax == WM_INITDIALOG 49 invoke InstallHook,hWnd,WM_HOOK 50 .if ! eax 51 invoke EndDialog,hWnd,NULL 52 .endif 53 ;******************************************************************** 54 .elseif eax == WM_HOOK 55 mov eax,wParam 56 .if al == 0dh 57 mov eax,0a0dh 58 .endif 59 mov @dwTemp,eax 60 invoke SendDlgItemMessage,hWnd,IDC_TEXT,EM_REPLACESEL,0,addr @dwTemp 61 .else 62 mov eax,FALSE 63 ret 64 .endif 65 mov eax,TRUE 66 ret 67 68 _ProcDlgMain endp 69 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 70 start: 71 invoke GetModuleHandle,NULL 72 invoke DialogBoxParam,eax,DLG_MAIN,NULL,offset _ProcDlgMain,NULL 73 invoke ExitProcess,NULL 74 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 75 end start
/
Hookdll.asm
1 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 2 ; Sample code for < Win32ASM Programming 2nd Edition> 3 ; by 罗云彬, http://asm.yeah.net 4 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 5 ; Hookdll.asm 6 ; 键盘钩子使用的 dll 程序 7 ; 用来方置钩子过程 8 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 9 ; 使用 nmake 或下列命令进行编译和链接: 10 ; ml /c /coff Hookdll.asm 11 ; Link /subsystem:windows /section:.bss,S /Def:Hookdll.def /Dll Hookdll.obj 12 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 13 .386 14 .model flat, stdcall 15 option casemap :none 16 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 17 ; Include 文件定义 18 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 19 include windows.inc 20 include user32.inc 21 includelib user32.lib 22 include kernel32.inc 23 includelib kernel32.lib 24 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 25 .data 26 hInstance dd ? 27 28 .data? 29 hWnd dd ? 30 hHook dd ? 31 dwMessage dd ? 32 szAscii db 4 dup (?) 33 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 34 .code 35 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 36 ; dll 的入口函数 37 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 38 DllEntry proc _hInstance,_dwReason,_dwReserved 39 40 push _hInstance 41 pop hInstance 42 mov eax,TRUE 43 ret 44 45 DllEntry Endp 46 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 47 ; 键盘钩子回调函数 48 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 49 HookProc proc _dwCode,_wParam,_lParam 50 local @szKeyState[256]:byte 51 52 invoke CallNextHookEx,hHook,_dwCode,_wParam,_lParam 53 invoke GetKeyboardState,addr @szKeyState 54 invoke GetKeyState,VK_SHIFT 55 mov @szKeyState + VK_SHIFT,al 56 mov ecx,_lParam 57 shr ecx,16 58 invoke ToAscii,_wParam,ecx,addr @szKeyState,addr szAscii,0 59 mov byte ptr szAscii [eax],0 60 invoke SendMessage,hWnd,dwMessage,dword ptr szAscii,NULL 61 xor eax,eax 62 ret 63 64 HookProc endp 65 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 66 ; 安装钩子 67 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 68 InstallHook proc _hWnd,_dwMessage 69 70 push _hWnd 71 pop hWnd 72 push _dwMessage 73 pop dwMessage 74 invoke SetWindowsHookEx,WH_KEYBOARD,addr HookProc,hInstance,NULL 75 mov hHook,eax 76 ret 77 78 InstallHook endp 79 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 80 ; 卸载钩子 81 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 82 UninstallHook proc 83 84 invoke UnhookWindowsHookEx,hHook 85 ret 86 87 UninstallHook endp 88 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 89 End DllEntry
/
MakeFile
1 NAME = Main 2 DLL = Hookdll 3 4 ML_FLAG = /c /coff 5 LINK_FLAG = /subsystem:windows 6 DLL_LINK_FLAG = /subsystem:windows /section:.bss,S 7 8 $(DLL).dll $(NAME).exe: 9 10 $(DLL).dll: $(DLL).obj $(DLL).def 11 Link $(DLL_LINK_FLAG) /Def:$(DLL).def /Dll $(DLL).obj 12 $(NAME).exe: $(NAME).obj $(NAME).res 13 Link $(LINK_FLAG) $(NAME).obj $(NAME).res 14 15 .asm.obj: 16 ml $(ML_FLAG) $< 17 .rc.res: 18 rc $< 19 20 clean: 21 del *.obj 22 del *.res 23 del *.exp 24 del *.lib
注意一下,在makefile里面,将bss区段设置成为了共享区段, /section:.bss,S 未初始化的数据段(.data?)默认放在这里
///
初初我看到他这源码的时候给我的感觉就是怎么这么乱。乱用,滥用。刚开始时我还一度以为这不能正常工作,后来发现是我误解彬哥的意图了,即使这不是个好的例子,但它还是能正常运行的。
重点在Hookdll.asm里面,他下的钩子是全局,最后一个线程ID如果是0,就是全局钩子。
1 invoke SetWindowsHookEx,WH_KEYBOARD,addr HookProc,hInstance,NULL
然后,他在回调函数里面调用SendMessage来将它自己定义的一个消息回传到hwnd主窗口,
1 invoke SendMessage,hWnd,dwMessage,dword ptr szAscii,NULL
不过由于hWnd位于共享数据段内,所以当打开多个这个程序时,暂且叫做A1,A2,SendMessage发送的数据其实是发到之后打开的那个程序A2回调函数里面。虽然在SendMessage里面它的LParam里面的参数是地址,但由于这地址位于共享数据段里面,所以该程序能正常获取数据,但A1就不能再获取数据了。
好,现在又引发了个有趣的问题了,当按一下键,那窗口显示两个字符(开了两个那个程序),这是因为hook了两次,回调函数调用了CallNextHookEx,分别调用了两个Hook回调函数,相当于在hook链表里面加入了两个,由于操作的变量都存放在共享数据段里面,所以,一切的结果都反映在最后打开的那个程序里。
图示为先后打开两个程序.
这里我们看到了一个知识点,SetWindowsHookEx不是像其他的内核对象一样简单地增加计数,而是真真切切地把Hook函数增加到Hook链表里面,即使那Hook是同一个。调用一次SetWindowsHookEx就增加一个,这我后来测试也成立。
我们来修改一下,让他只hook自己的线程,然后在MakeFile里面把共享数据段给取消掉,验证下是否仍然可以正常运行
将InstallHook函数里面的SetWIndowsHookEx换成下面两条函数
1 invoke GetentThreadId 2 invoke SetWindowsHookEx,WH_KEYBOARD,addr HookProc,hInstance,eax
结果发现,生成的那个程序,暂且叫做A吧,成功执行了,不过也跟预料的情况一样,只记录当前进程的按键,但当打开多个程序时,每个进程都可以记录,却记录的不重复。
跟预料中的一样,“必须是共享数据段的动态链接库”这是不正确的,也许作者只是笔误,他想描述的是它后面的例子应该把数据段放到共享区段里面,检查的时候却不料将其放到前面跟例子不搭边的内容上去了。
//
再来看个有趣的问题,跟原先的一样,不修改源代码,只将共享区段给去掉。
当开了两个该程序时,暂且称为A1,A2,按下一个键时,会发现,窗口反馈出两个相同的键。
这又是为什么呢,取消了共享数据段了啦,理应每个HookDll里的变量都是位于不同的进程空间里的,不受影响才对的。
仔细想想整个流程...
呵呵,聪明的你想到了?
来,我来说说我的看法吧。
因为我们执行的是相同的程序,程序的变量szAscii等放的地址空间是完全一样的,而下的Hook是全局的,所以在每个程序里面其实都有发送自定义的WH_HOOK消息(只是其他的程序没有处理而已),Hook链表里面有两个回调函数都用SendMessage发送了信息,所以累计发送了两次,接收到的是两个字符。
是这样吗?
没错?
唔,感觉好像是对的。
但是,别忘了,我们是取消了共享数据段的。
还是把SendMessage贴上来看得仔细点吧,
invoke SendMessage,hWnd,dwMessage,dword ptr szAscii,NULL
hWnd是个窗口句柄,这是两个窗口喔,hWnd由消息循环获得,两个窗口,理应是不同的值吧?
有点怀疑是不是hWnd指向的结果是同一个,经过调试,发现打开两个该程序,传递的hWnd的值竟然是一样的。
而当我改了下HookDll.asm后,重新生成了个,现在有两个不同的dll了,测试的结果却是hWnd是不一样的,一个为NULL,另外一个为真实的值。
实际上,只要dll是不一样,即使代码没改(不如生成的时间不一样),传递的hWnd的值都是不一样的,这样很符合我们的思维。