2012-05-31

Keyboard hook


글로벌 후킹에 관심이 생겨 자료를 검색하다 보니 좋은 내용을 발견했다.
테스트 해보니 nprotect나 공인인증서에서는 키보드 보안이 user32.dll을 후킹하는 것을 차단하는 것 같다.
그런 보안이 없는 환경에서.. 믿을 만한 PC가 아니라면 중요한 정보는 입력하지 않는게 좋겠다.


후킹 과정 요약.
user32.dll의 SetWindowsHookEx로 후킹
SetWindowsHookEx로 후킹할 때 필요한 콜백함수에 대한 대리자 선언(keyboardHookProc)
후킹한 키 값을 저장할 구조체 선언(keyboardHookStruct)
후킹할 키 값을 List에 등록(HookedKeys)
키보드에 대한 이벤트 추가(KeyDown)
훅 프로시져 구성(hookProc)



출처 및 참고자료:
StormySpike http://www.codeproject.com/Articles/19004/A-Simple-C-Global-Low-Level-Keyboard-Hook
http://www.jiniya.net/wp/microsoftware


// 주석추가만 있고, 원본 수정 및 변경 없음

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices; // dllimport를 하기 위해 InteropServices네임스페이스 추가
using System.Windows.Forms;

namespace Utilities {
///








/// A class that manages a global low level keyboard hook
///

class globalKeyboardHook {
#region Constant, Structure and Delegate Definitions
///








/// defines the callback type for the hook
///

        // 콜백함수에 사용될 대리자 정의
public delegate int keyboardHookProc(int code, int wParam, ref keyboardHookStruct lParam);

        // 후킹한 키보드의 데이터를 저장할 구조체
public struct keyboardHookStruct {
public int vkCode;  //  virtual-key Code. (range 1~254) dword(4바이트) 이므로 int를 대신 씀
public int scanCode;
public int flags; // bit별로 숫자키패드에서 입력된 값인지, ALT키가 눌린 상태인지, keyboard의 press , release상태 등의 정보가 담긴 플래그
public int time; //
public int dwExtraInfo; // MSDN에서는 UintPtr 형을 쓰라고 함..
}

        ///


        ///  키보드 스트럭쳐에 대한 설명.
        ///  vkCode와 scanCode의 차이점
        ///  vkCode는 code값이 사람이 인식하기 쉬운 알파벳 순서
        ///  scanCode는 code값이 키보드에 위치적인 순서
        ///
        ///     vkCode | scanCode
        ///  a    65   |    30
        ///  b    66   |    48
        ///  c    67   |    46
        ///  d    68   |    32
        ///  --------------------------
        ///  q    81   |    16
        ///  w    87   |    17
        ///  e    69   |    18
        ///  r    82   |    19
        ///
        /// flags- 8bit의 추가 정보 flag
        /// 예) 일반키에 대해서는 down이벤트와 press이벤트일 때 0, up이벤트 일때 128
        /// test에 대한 플래그 코드, 시스템키라던지 확장키인지 플래그에 나타남
        /// 0 bit, 확장키(숫자패드, 펑션키일 때 1)
        /// 1~3 bit, reserved
        /// 4 bit, 이벤트에 inject되었으면 1
        /// 5 bit, context code, Alt키가 눌렸으면 1
        /// 6 bit, reserved
        /// 7 bit, 0= press, 1=release
        ///
        /// time은 타임스탬프, GetMessageTime API에 의해 키가 down, press, up상태마다 타임스탬프가 기록된다.
        /// 예) 163072875, 163077453
        ///
        /// dwExtraInfo
        /// Additional information associated with the message
        ///



const int WH_KEYBOARD_LL = 13; // 후킹할 Handle 상수
const int WM_KEYDOWN = 0x100;  // 후킹할 메시지 상수
const int WM_KEYUP = 0x101;
const int WM_SYSKEYDOWN = 0x104;
const int WM_SYSKEYUP = 0x105;
#endregion

#region Instance Variables
///








/// The collections of keys to watch for
///

        // 모니터링할 키의 List
public List HookedKeys = new List(); //유저 어플리케이션에서 A키와 B키를 후킹할 대상으로 List.Add했다.(ghk.HookedKeys.Add("Keys.A");)
///








/// Handle to the hook, need this to unhook and call the next hook
///

        // hook handle 정의
IntPtr hhook = IntPtr.Zero;
#endregion

#region Events
///








/// Occurs when one of the hooked keys is pressed
///

        public event KeyEventHandler KeyDown;  // KeyDown 이벤트, 사용자 어플리케이션에서 이벤트핸들러를 추가해서 사용. (예: gkh.KeyUp += new KeyEventHandler(gkh_KeyUp);)
///








/// Occurs when one of the hooked keys is released
///

public event KeyEventHandler KeyUp;   // KeyUp 이벤트,
#endregion

#region Constructors and Destructors
///








/// Initializes a new instance of the class and installs the keyboard hook.
///

public globalKeyboardHook() {
hook(); // 후킹을 시작할 함수
}

///








/// Releases unmanaged resources and performs other cleanup operations before the
/// is reclaimed by garbage collection and uninstalls the keyboard hook.
///

~globalKeyboardHook() {
unhook(); // 후킹을 일시정지할 함수
}
#endregion

#region Public Methods
///








/// Installs the global hook
///

public void hook() {
IntPtr hInstance = LoadLibrary("User32"); //User32.dll이 hInstance
            /// HHOOK SetWindowsHookEx(int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId);
            /// idHook, WH_KEYBOARD_LL이 후킹할 대상
            /// lpfn, hookProc은 콜백함수
            /// hMod, hInstance("user32.dll") 후킹 프로시져가 존재하는 모듈의 핸들
            /// dwThreadId, 후킹할 쓰레드의 ID, 0은 시스템 전역을 뜻함.
hhook = SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, hInstance, 0);
}

///








/// Uninstalls the global hook
///

        // 후킹 일시정지
public void unhook() {
UnhookWindowsHookEx(hhook);
}

///







        /// wParam        /// 키보드 입력 이벤트를 일으킨 가상 키 코드라고 한다...시스템키, 확장키, Down , up 에 따라 값이 변함.        /// lParam        /// 키보드 입력 이벤트에 대한 추가 정보라고 한다...        /// 75819508이 출력되는데 이 정보에 대한 주소        /// 비교) keyboardHookStruct = (KeyBoardHookStruct) Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct))        /// -> lparam의 주소를 참조해서 keyboardHookStructure 객체를 생성한 소스도 있다.        /// lparam의 비트별 정보는 아래를 참조        /// 0~15 bit, 반복횟수        /// 16~23 bit, scanCode        /// 24 bit, is Extended key        /// 25~28 bit, reserved        /// 29 bit, Alt key        /// 30 bit, 이전 키이벤트의 상태가 눌린 상태면 1        /// 31 bit, 지금 상태가 눌린 상태면 1 /// The callback for the keyboard hook
///

///
The hook code, if it isn't >= 0, the function shouldn't do anyting
///
The event type
///
The keyhook event information
///
public int hookProc(int code, int wParam, ref keyboardHookStruct lParam) {
if (code >= 0) {
Keys key = (Keys)lParam.vkCode; // LParam에 후킹한 vkCode. key는 후킹된 키보드 값이다.
if (HookedKeys.Contains(key)) { // HookedKeys는 후킹할 키의 리스트였음. 따라서 이 리스트에 지금 눌린 key값이 있으면 아래 KeyDown, KeyUp 이벤트가 실행된다.
KeyEventArgs kea = new KeyEventArgs(key);
if ((wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) && (KeyDown != null)) {
KeyDown(this, kea) ; // KeyDown(object sender, KeyEventArgs e)
} else if ((wParam == WM_KEYUP || wParam == WM_SYSKEYUP) && (KeyUp != null)) {
KeyUp(this, kea);
}
if (kea.Handled) //이벤트에 대한 Handled가 true인 경우, 후킹된 메시지는 유저 어플리케이션에서 처리하고 폐기됨. false일 경우 처리하고 그 후에 user32.dll에서 처리하도록 함.
return 1;    //Handled 값이 true여서 해당 키에 대해 유저 어플리케이션에서만 처리되고, 1을 return, user32.dll은 해당 KeyEvent를 못받음.
}
}

            return CallNextHookEx(hhook, code, wParam, ref lParam);  // 다른 후킹 프로시져에게 제어권을 넘겨주는 역할, return 0로 해도 실행은 됨.
}
#endregion

#region DLL imports
        // SetWindowsHookEx, UnhookWindowsHookEx, CallNextHookEx에 대한 DllImport.
///








/// Sets the windows hook, do the desired event, one of hInstance or threadId must be non-null
///

///
The id of the event you want to hook
///
The callback.
///
The handle you want to attach the event to, can be null
///
The thread you want to attach the event to, can be null
/// a handle to the desired hook
[DllImport("user32.dll")]
static extern IntPtr SetWindowsHookEx(int idHook, keyboardHookProc callback, IntPtr hInstance, uint threadId);

///








/// Unhooks the windows hook.
///

///
The hook handle that was returned from SetWindowsHookEx
/// True if successful, false otherwise
[DllImport("user32.dll")]
static extern bool UnhookWindowsHookEx(IntPtr hInstance);

///








/// Calls the next hook.
///

///
The hook id
///
The hook code
///
The wparam.
///
The lparam.
///
[DllImport("user32.dll")]
static extern int CallNextHookEx(IntPtr idHook, int nCode, int wParam, ref keyboardHookStruct lParam);

///








/// Loads the library.
///

///
Name of the library
/// A handle to the library
[DllImport("kernel32.dll")]
static extern IntPtr LoadLibrary(string lpFileName);
#endregion
}
}



사용 땐,
// 위 소스의 네임스페이스 사용
using utility;

// globalKeyboardHook 생성
globalKeyboardHook gkh = new globalKeyboardHook();


// HookedKeys 리스트에 모니터링할 키 추가
gkh.HookedKeys.Add(Keys.A);
gkh.HookedKeys.Add(Keys.B);
// 이벤트 추가

gkh.KeyDown += new KeyEventHandler(gkh_KeyDown);
gkh.KeyUp += new KeyEventHandler(gkh_KeyUp);
// 이벤트 처리



void gkh_KeyUp(object sender, KeyEventArgs e) {
lstLog.Items.Add("Up\t" + e.KeyCode.ToString());
e.Handled = true;
}

void gkh_KeyDown(object sender, KeyEventArgs e) {
lstLog.Items.Add("Down\t" + e.KeyCode.ToString());
e.Handled = true;
}