Visualbasic 6.0 SetWindowsHookEx를 이용한 시스템 후킹

Posted by 겨울에
2011. 2. 4. 02:08 scrap/ Visual Basic
 [6.0] SetWindowsHookEx를 이용한 시스템 후킹 - 1. 키보드
 
 Windows에서는 마우스나 키보드같은 GUI 입력을 이용하여 대화 상자와 대화할 수 있고,
 마우스나 키보드 같은 입력을 컨트롤하는 API도 지원합니다. 대표적으로 아래와 같은 API가 있습니다.
 
       저수준
      - mouse_event (마우스 입력을 발생시켜주는 API)
      - keybd_event (키보드 입력을 발생시켜주는 API)
      - SendInput (마우스나 키보드 입력을 보내주는 API)
 
       고수준
      - BlockInput (마우스나 키보드 입력을 차단하는 API)
      - SetCursorPos (커서의 좌표를 설정하는 API)
      - SendKeys (마우스나 키보드 입력을 지시하는 VB 내장함수)
        (VB의 SendKeys는 WH_JOURNALPLAYBACK 훅을 이용해서 입력을 만든다.)
 
 그렇다면, SetWindowsHookEx는 무엇을 하는 API일까요? 이 API는 윈도우를 만든 MS가 프로그래머에게 준
 고귀한 선물 이라고 할 수 있는데, 시스템을 전역적으로 후킹할 수 있는 API라고 보시면 됩니다.
 우선 이 API는 아래와 같은 후킹 방법을 제공합니다.
 
 WH_CALLWNDPROC
 WH_CBT
 WH_DEBUG
 WH_FOREGROUNDIDLE
 WH_GETMESSAGE
 WH_KEYBOARD
 WH_KEYBOARD_LL
 WH_MOUSE
 WH_MOUSE_LL
 WH_JOURNALRECORD
 WH_JOURNALPLAYBACK
 WH_MSGFILTER
 WH_SHELL
 ...
 
 여기서 눈여겨 볼것은 WH_KEYBOARD 와 WH_KEYBOARD_LL 입니다.
 이번 강좌에서는 WH_KEYBOARD_LL 후크를 이용해서 시스템의 키 입력을 가장 먼저
 가로채서 '키 입력을 무시하거나..', '키 눌림을 감지하는 키로거 등...' 활용할 수 있는 기초적인
 방법을 소개해드리겠습니다. (이 방법은 Win 2000/XP 이상에서만 사용 가능합니다.)
 
 SetWindowsHookEx의 선언문은 아래와 같습니다.
 Public Declare Function SetWindowsHookEx Lib "user32.dll" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, ByVal hmod As Long, ByVal dwThreadId As Long) As Long
 
 - idHook: 후크의 종류 (WH_XXX)
 - lpfn: 후킹 프로시저(함수)의 주소값(AddressOf XXX)
 - hmod: App.hInstance 의 값
 - dwThreadId: App.ThreadID 의 값. (후크의 종류에 따라 이것은 넣지 않을수도 있다.)
 - 반환값: 후킹의 핸들 (HHOOK) 32-bit 정수 (0 이면 실패)
 
 이런 후킹을 해제하는 함수는 아래와 같습니다.
 
- UnhookWindowsHookEx
Public Declare Function UnhookWindowsHookEx Lib "user32.dll" (ByVal hHook As Long) As Long
  - hHook: SetWindowsHookEx에서 반환했던 후크의 핸들
  - 반환값이 0이면 실패이다.
 
 후크의 종류에 따라 lpfn에 들어가는 함수의 주소값의 함수 선언은 달라질 수 있습니다.
 WH_KEYBOARD_LL에 대응하는 후크 프로시져는 아래와 같습니다.
 
LRESULT CALLBACK LowLevelKeyboardProc(      
    int nCode,
    WPARAM wParam,
    LPARAM lParam
);
 
 이를 비주얼 베이직 함수 형태로 고치면..
 
 Public Function LowLevelKeyboardProc(ByVal nCode As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
     ....
 End Function
 
 형태가 됩니다. (p.s: 이런 함수는 주소값을 구해야하므로 폼이나 클래스 모듈이 아닌 '모듈'에 선언해야합니다.)
 
 MSDN에 의하면
 
-----------------------------------------//
Parameters
nCode
[in] Specifies a code the hook procedure uses to determine how to process the message. If nCode is less than zero, the hook procedure must pass the message to the :Track('ctl00_rs1_mainContentContainer_ctl00|ctl00_rs1_mainContentContainer_ctl05',this);" href="http://msdn2.microsoft.com/en-us/library/ms644974(VS.85).aspx">CallNextHookEx function without further processing and should return the value returned by CallNextHookEx. This parameter can be one of the following values.
HC_ACTION
The wParam and lParam parameters contain information about a keyboard message.
wParam
[in] Specifies the identifier of the keyboard message. This parameter can be one of the following messages: :Track('ctl00_rs1_mainContentContainer_ctl00|ctl00_rs1_mainContentContainer_ctl06',this);" href="http://msdn2.microsoft.com/en-us/library/ms646280(VS.85).aspx">WM_KEYDOWN, :Track('ctl00_rs1_mainContentContainer_ctl00|ctl00_rs1_mainContentContainer_ctl07',this);" href="http://msdn2.microsoft.com/en-us/library/ms646281(VS.85).aspx">WM_KEYUP, :Track('ctl00_rs1_mainContentContainer_ctl00|ctl00_rs1_mainContentContainer_ctl08',this);" href="http://msdn2.microsoft.com/en-us/library/ms646286(VS.85).aspx">WM_SYSKEYDOWN, or :Track('ctl00_rs1_mainContentContainer_ctl00|ctl00_rs1_mainContentContainer_ctl09',this);" href="http://msdn2.microsoft.com/en-us/library/ms646287(VS.85).aspx">WM_SYSKEYUP.
lParam
[in] Pointer to a :Track('ctl00_rs1_mainContentContainer_ctl00|ctl00_rs1_mainContentContainer_ctl10',this);" href="http://msdn2.microsoft.com/en-us/library/ms644967(VS.85).aspx">KBDLLHOOKSTRUCT structure.
-----------------------------------------//
 
 이렇게 나와있습니다. 'nCode는 메시지를 처리할 방법' 을 말한다는것 같고, wParam는 이 프로시저가
 어떠한 키 눌림조작에 의해 실행되었는지를 나타냅니다. (4개중에 하나),
 lParam는 'KBDLLHOOKSTRUCT' 구조체의 포인터(메모리 주소값)가 대입된다고 합니다.
 
 - 참고로 KBDLLHOOKSTRUCT는 아래와 같은 구조체입니다.
Public Type KBDLLHOOKSTRUCT
    vkCode As Long           ' VK_XXX 키 코드
    scanCode As Long         ' 하드웨어 스캔 코드
    flags As Long
    time As Long
    pdwExtraInfo As Long
End Type
 
 그리고, 해당 키 입력이 유효하지 않게 하려면(다른 훅이 키보드 입력을 받지 못하게 방지하는 기능도 합니다)
 후크 프로시저에서 0이 아닌 값을 반환하면 되고,
 유효하게 하려면 CallNextHookEx를 호출해 그 반환값을 돌려줘야한다고 MSDN에 써있습니다.
 (CallNextHookEx는 아래와 같은 api입니다)
Public Declare Function CallNextHookEx Lib "user32.dll" (ByVal hHook As Long, ByVal ncode As Long, ByVal wParam As Long, ByRef lParam As Any) As Long
 
 그럼 이것을 참고로 하여 A 키와 B 키를 서로 바꿔주는 프로그램을 한번 코딩해봅시다.
 아래에 나오는 코드는 모두 모듈에 넣어주시면 됩니다.
 
Public Declare Sub keybd_event Lib "user32.dll" (ByVal bVk As Byte, ByVal bScan As Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)
Public Declare Function SetWindowsHookEx Lib "user32.dll" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, ByVal hmod As Long, ByVal dwThreadId As Long) As Long
Public Declare Function UnhookWindowsHookEx Lib "user32.dll" (ByVal hHook As Long) As Long
Public Declare Function CallNextHookEx Lib "user32.dll" (ByVal hHook As Long, ByVal nCode As Long, ByVal wParam As Long, ByRef lParam As Any) As Long
Public Const WH_KEYBOARD_LL As Long = 13
Public Const WM_KEYDOWN As Long = &H100
Public Const WM_KEYUP As Long = &H101
Public Type KBDLLHOOKSTRUCT
    vkCode As Long           ' VK_XXX 키 코드
    scanCode As Long         ' 하드웨어 스캔 코드
    flags As Long
    time As Long
    dwExtraInfo As Long      ' 부가 정보 (keybd_event의 4번째 인자)
End Type
Private hHook As Long
Sub Main()
    ' 후킹 프로시저 설치!
    MsgBox "후킹 프로시저를 설치합니다."
    hHook = SetWindowsHookEx(WH_KEYBOARD_LL, AddressOf LowLevelKeyboardProc, App.hInstance, 0)
    If hHook = 0 Then
        MsgBox "후킹 프로시저를 설치할 수 없었습니다."
        End
    End If
    MsgBox "후킹 프로시저가 동작중입니다. [확인]을 누르면 제거됩니다."
    If UnhookWindowsHookEx(hHook) = 0 Then
        MsgBox "후킹 프로시저를 제거할 수 없었습니다."
        End
    End If
    MsgBox "후킹 프로시저가 제거되었습니다."
End Sub
Public Function LowLevelKeyboardProc(ByVal nCode As Long, ByVal wParam As Long, lParam As KBDLLHOOKSTRUCT) As Long
    If nCode < 0 Then GoTo Finalize
    If lParam.dwExtraInfo = 33 Then GoTo Finalize ' 33은 그냥 정한 숫자
    If wParam = WM_KEYDOWN Or wParam = WM_KEYUP Then
        If lParam.vkCode = vbKeyA Then
            keybd_event vbKeyB, 0, IIf(wParam = WM_KEYUP, 2, 0), 33
            LowLevelKeyboardProc = 1
            Exit Function
        End If
        If lParam.vkCode = vbKeyB Then
            keybd_event vbKeyA, 0, IIf(wParam = WM_KEYUP, 2, 0), 33
            LowLevelKeyboardProc = 1
            Exit Function
        End If
    End If
Finalize:
    LowLevelKeyboardProc = CallNextHookEx(hHook, nCode, wParam, lParam)
End Function
 
이 코드에 대한 설명은 다음에 하겠습니다.