윈도우 환경에서 Python을 사용하여 키보드 후킹을 하는 방법 관련하여 좀더 쓰기 편하게 코드 정리를 하였다. 특수키를 확인하는 방법도 추가되었다.



소스


KeyRogue.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# -*- coding: utf-8 -*-
#!/usr/bin/python
 
import sys
from ctypes import *
from ctypes.wintypes import MSG
from ctypes.wintypes import DWORD
 
class KeyRogue:
    user32 = windll.user32
    kernel32 = windll.kernel32
 
    WH_KEYBOARD_LL = 13
    WM_KEYDOWN = 0x0100
    WM_SYSKEYDOWN = 0x0104
     
    VK_CONTROL = 0x11
    VK_MENU = 0x12
    VK_SHIFT = 0x10
     
    hooked = None
    hookProc = None
    pointer = None
     
    @staticmethod
    def installHookProc(hookProc):
        if KeyRogue.hooked is not None:
            print("already installed. uninstalling..")
            KeyRogue.uninstallHookProc()
        KeyRogue.hookProc = hookProc
        KeyRogue.pointer = KeyRogue.getFPTR(KeyRogue.hookProcInternal)
         
        KeyRogue.hooked = KeyRogue.user32.SetWindowsHookExA(
            KeyRogue.WH_KEYBOARD_LL,
            KeyRogue.pointer,
            KeyRogue.kernel32.GetModuleHandleW(None),
            0
        )
        if not KeyRogue.hooked:
            return False
        return True
 
    @staticmethod
    def uninstallHookProc():
        if KeyRogue.hooked is None:
            return
        KeyRogue.user32.UnhookWindowsHookEx(KeyRogue.hooked)
        KeyRogue.hooked = None
 
    @staticmethod
    def bytes(integer):
        return divmod(integer, 0x10000)
             
    @staticmethod
    def getFPTR(fn):
        CMPFUNC = CFUNCTYPE(c_int, c_int, c_int, POINTER(c_void_p))
        return CMPFUNC(fn)
 
    @staticmethod
    def startKeyLog():
        msg = MSG()
        KeyRogue.user32.GetMessageA(byref(msg), 0, 0, 0)
 
    @staticmethod
    def getKeyCode(lParam):
        high, low = KeyRogue.bytes(lParam[0])
        return low
 
    @staticmethod
    def getKeyInfo(lParam):
        keyCode = KeyRogue.getKeyCode(lParam)
        return (keyCode, chr(keyCode))
 
    @staticmethod
    def checkModifier(lParam, vKey):
        if KeyRogue.user32.GetAsyncKeyState(vKey) & 0x8000:
            return True
        return False
 
    @staticmethod
    def hookProcInternal(nCode, wParam, lParam):
        if wParam != KeyRogue.WM_KEYDOWN and wParam != KeyRogue.WM_SYSKEYDOWN:
            return KeyRogue.user32.CallNextHookEx(KeyRogue.hooked, nCode, wParam, lParam)
        if KeyRogue.hookProc:
            KeyRogue.hookProc(nCode, wParam, lParam)
        return KeyRogue.user32.CallNextHookEx(KeyRogue.hooked, nCode, wParam, lParam)
 
    @staticmethod
    def start(hookProc):
        if KeyRogue.installHookProc(hookProc):
            KeyRogue.startKeyLog()
        else:
            print("install failed")
 
    @staticmethod
    def stop():
        KeyRogue.uninstallHookProc()
client.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from KeyRogue import *
 
def hookProc(nCode, wParam, lParam):
    key = KeyRogue.getKeyInfo(lParam)
    print("key code = %d" % key[0])
    print("key char = %c" % key[1])
    if KeyRogue.checkModifier(lParam, KeyRogue.VK_CONTROL):
        print("CTRL pressed")
        if (key[1] == 'Q'):
            print("exit key pressed, call stop()")
            KeyRogue.stop()
            sys.exit(-1)
    if KeyRogue.checkModifier(lParam, KeyRogue.VK_MENU):
        print("ALT pressed")
    if KeyRogue.checkModifier(lParam, KeyRogue.VK_SHIFT):
        print("SHIFT pressed")
 
KeyRogue.start(hookProc)
  • Hook 설치 성공시 .start() 함수는 리턴되지 않는다.(심지어 stop() 함수를 사용하여 언인스톨되었을 경우에도) 따라서 GUI가 있는 프로그램에 키보드 후킹 기능을 추가하려면 별도의 쓰레드에서 실행되도록 처리가 필요하다.


참고사항

<테스트 환경>
- OS : Windows 10
- Python 버전 : 3.6.5(32/64bit), 3.7.5(32bit)


,