from http://zerray.com/
看win32汇编看到钩子部分,突发奇想,打算写一个改变键盘布局的整人程序
查了查资料,发现底层键盘钩子(WH_KEYBOARD_LL)可以实现。首先是安装和卸载钩子:
InstallHook proc hIns: DWORD
invoke SetWindowsHookEx, WH_KEYBOARD_LL, addr KeyProc, hIns, NULL
mov hHook, eax
ret
InstallHook endp
UninstallHook proc
invoke UnhookWindowsHookEx, hHook
ret
UninstallHook endp
这些与别的钩子一样,在回调函数 KeyProc 中 nCode 值为 HC_ACTION,wParam 为按键消息(WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, WM_SYSKEYUP),lParam 为指向 KBDLLHOOKSTRUCT 结构的指针。
首先,我想捕获按键x,为了知道是否捕获到了,我让程序在有x按下时弹出一个对话框。
KeyProc proc nCode: DWORD, wParam: WPARAM, lParam: LPARAM
.IF nCode == HC_ACTION
.IF (wParam == WM_KEYDOWN)
mov edx, lParam
assume edx: PTR KBDLLHOOKSTRUCT
.IF ([edx].vkCode == VK_X)
invoke MessageBox, NULL, addr szMsg, addr szTit, MB_OK
.ENDIF
.ENDIF
.ENDIF
invoke CallNextHookEx, hHook, nCode, wParam, lParam
ret
KeyProc endp
程序很简单,当有x按下时弹出一个对话框,按一下试试,OK!看来我们已经捕获到按键了。
那现在要怎样才能把x按键改成其它的按键呢?先试试其他程序是不是在我们的钩子函数之后处理按键消息:把 KeyProc 中的 invoke 一句改成 ret。运行程序,按x,呵呵,x键被屏蔽了,看来钩子函数处理按键消息的优先级是很高的。那把这个消息改成别的按键传下去就能起到改变按键的作用了吧?试试看喽!
KeyProc proc nCode: DWORD, wParam: WPARAM, lParam: LPARAM
.IF nCode == HC_ACTION
.IF (wParam == WM_KEYDOWN)
mov edx, lParam
assume edx: PTR KBDLLHOOKSTRUCT
.IF ([edx].vkCode == VK_X)
mov [edx].vkCode, VK_Y
.ENDIF
.ENDIF
.ENDIF
invoke CallNextHookEx, hHook, nCode, wParam, lParam
ret
KeyProc endp
我把改 vkCode 改成了 VK_Y,现在其它程序得到的消息应该是按键y了吧?运行,按x,奇怪,还是x??为什么啊?难道 scanCode 也要改??查了一下 VK_Y 的 scanCode ,也改了,还是不行。难道我们的程序没有修改那段内存的权限?提升程序的权限?太麻烦了,还是想想别的办法吧!既然现在能够屏蔽掉一个按键了,那我们不是可以再用 keybd_event 模拟按键吗?对,就这么干!
我们现在屏蔽x和y按键,然后在x按键时模拟y按键,在y按键时模拟x按键,这样就调换了x和y的键盘位置。
KeyProc proc nCode: DWORD, wParam: WPARAM, lParam: LPARAM
.IF nCode == HC_ACTION
.IF (wParam == WM_KEYDOWN)
mov edx, lParam
assume edx: PTR KBDLLHOOKSTRUCT
.IF ([edx].vkCode == VK_X)
invoke keybd_event, VK_Y, 0, 0, 0
ret
.ELSEIF ([edx].vkCode == VK_Y)
invoke keybd_event, VK_X, 0, 0, 0
ret
.ENDIF
.ENDIF
.ENDIF
invoke CallNextHookEx, hHook, nCode, wParam, lParam
ret
KeyProc endp
这回应该没问题了吧?运行一下试试!哦,NO,又出问题了,按下x或y后,x和y不停的交替出现,消息循环了?是啊,连用 keybd_event 模拟的按键都被底层键盘钩子捕获了!怎么办呢?其实多判断一下 scanCode 就好了,模拟的按键和实际的按键 scanCode 是不同的。好了,现在问题完全解决了!在加上判断 WM_SYSKEYDOWN 的判断,这样就换的更彻底了。下面是完整的程序:
.386
.model flat, stdcall
option casemap: none
include /masm32/include/windows.inc
include /masm32/include/kernel32.inc
include /masm32/include/user32.inc
includelib /masm32/lib/kernel32.lib
includelib /masm32/lib/user32.lib
WinMain proto :DWORD, :DWORD, :DWORD, :DWORD
InstallHook proto :DWORD
UninstallHook proto
HotKeyID1 equ 1
HotKeyID2 equ 2
KBDLLHOOKSTRUCT STRUCT
vkCode DWORD ?
scanCode DWORD ?
flags DWORD ?
time DWORD ?
dwExtraInfo ULONG ?
KBDLLHOOKSTRUCT ENDS
.const
ClassName db 'HookxClass', 0
AppName db 'Hookx', 0
Running db 'i am running...', 0
.data?
inst dd ?
hHook dd ?
cmd dd ?
.code
start:
invoke GetModuleHandle, NULL
mov inst, eax
invoke GetCommandLine
mov cmd, eax
invoke WinMain, inst, NULL, cmd, SW_HIDE ;整人的程序,当然不能把窗口显示出来了!
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize, SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra, NULL
mov wc.cbWndExtra, NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground, COLOR_WINDOW + 1
mov wc.lpszMenuName, NULL
mov wc.lpszClassName, OFFSET ClassName
invoke LoadIcon, NULL, IDI_APPLICATION
mov wc.hIcon, eax
mov wc.hIconSm, eax
invoke LoadCursor, NULL, IDC_ARROW
mov wc.hCursor, eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx, NULL,/
addr ClassName,/
addr AppName,/
WS_OVERLAPPEDWINDOW,/
CW_USEDEFAULT,/
CW_USEDEFAULT,/
CW_USEDEFAULT,/
CW_USEDEFAULT,/
NULL,/
NULL,/
hInst,/
NULL
mov hwnd, eax
invoke ShowWindow, hwnd, CmdShow
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, addr msg, NULL, 0, 0
.BREAK .IF (!eax)
invoke TranslateMessage, addr msg
invoke DispatchMessage, addr msg
.ENDW
mov eax, msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg == WM_CREATE
invoke InstallHook, inst
invoke RegisterHotKey, hWnd, HotKeyID1, MOD_ALT, VK_8 ;注册两个快捷键用来查看程序时候在运行和关闭程序
invoke RegisterHotKey, hWnd, HotKeyID2, MOD_ALT, VK_9
.ELSEIF uMsg == WM_HOTKEY
.IF wParam == HotKeyID1
invoke MessageBox, NULL, addr Running, addr AppName, MB_OK
.ELSEIF wParam == HotKeyID2
invoke UninstallHook
invoke PostMessage, hWnd, WM_DESTROY, NULL, NULL
.ENDIF
.ELSEIF uMsg == WM_DESTROY
invoke UnregisterHotKey, hWnd, HotKeyID2
invoke UnregisterHotKey, hWnd, HotKeyID1
invoke UninstallHook
invoke PostQuitMessage, NULL
.ELSE
invoke DefWindowProc, hWnd, uMsg, wParam, lParam
ret
.ENDIF
xor eax, eax
ret
WndProc endp
KeyProc proc nCode: DWORD, wParam: WPARAM, lParam: LPARAM
.IF nCode == HC_ACTION
.IF (wParam == WM_KEYDOWN) || (wParam == WM_SYSKEYDOWN)
mov edx, lParam
assume edx: PTR KBDLLHOOKSTRUCT
.IF ([edx].vkCode == VK_X) && ([edx].scanCode != 0)
invoke keybd_event, VK_Y, 0, 0, 0
ret
.ELSEIF ([edx].vkCode == VK_Y) && ([edx].scanCode != 0)
invoke keybd_event, VK_X, 0, 0, 0
ret
.ENDIF
.ENDIF
.ENDIF
invoke CallNextHookEx, hHook, nCode, wParam, lParam
ret
KeyProc endp
InstallHook proc hIns: DWORD
invoke SetWindowsHookEx, WH_KEYBOARD_LL, addr KeyProc, hIns, NULL
mov hHook, eax
ret
InstallHook endp
UninstallHook proc
invoke UnhookWindowsHookEx, hHook
ret
UninstallHook endp
end start
现在我可以把这个程序偷偷的放到同学的机器上去运行了 ,咦?我的键盘怎么了?