5.메시지 훅킹과 최상단 윈도우

본장에서는 메시지 훅킹을 하는 방법에 대해서 설명합니다. Part1의 2장에서 간단하게 메시지 훅킹에 대해서 설명을 하였습니다. 본장에서는 메시지훅킹의 모든 방법을 설명합니다. 또한 시스템에서 동작되고 있는 모든윈도우에서 메시지를 훅킹하고 처리하는 방법에 대해서 설명합니다. 메시지 훅킹을 알게 되면 다른 프로그램의 윈도우 내용을 얻을수 있으며, 컨트롤 까지 가능하게 됩니다. 시스템에서 구동되는 전윈도우에서 메시지를 가로채거나,마우스,키보드 이벤트를 가로채고자 할경우 또는 메뉴에 설정된 항목들이 선택되거나, 윈도우내용이 변경되었을때등의 특이 기능을 구현하고자 할경우 본 장에서 설명한 방법을 이용합니다. 윈도우가 언제나 최상단으로 위치하고자 하는 기능은 매우 간단합니다. 본장의 ExSpy 예제에서 이방법을 알려줍니다.

대상자: 고급

Chapter 등급: 선택

공부방법:

본항목은 프로그램에 특별한 기능을 추가하고자 할경우 필요한 항목입니다. 일반 어플리케이션을 작성하고자 할경우에는 크게 이용되지 않습니다.

1. 메시지 훅킹

Paint Shop에서 화면을 캡처 항목을 선택하여 화면을 캡처하게 되면 해당 윈도우는 아이콘화 되고, 우측 마우스 버튼을 클릭하거나 또는 F11키를 누르게 되면 화면을 캡처할수 있는 기능을 있습니다.. 아래한글에서 영한사전을 가동시키고 전윈도우에서 영어 텍스트를 선택하게되면 해당 단어의 뜻이 영한사전에 출력되는 것도 보신적이 있을것입니다. Visaul C++에서 Spy++ 프로그램을 구동시키면 해당윈도우에서 발생하는 모든 메시지를 가로채어 Spy++ 프로그램에서 내용을 출력해줍니다. 이런 모든 기능들은 메시지 훅킹을 이용합니다.

메시지훅킹이란 윈도우 시스템이나 또는 어플리케이션 프로그램에서 발생되는 모든 메시지를 가로채는 것을 의미합니다. 메시지 훅킹을 하고자 할경우에는 SetWindowsHookEx 함수와 UnhookWindowsHookEx를 사용합니다. 이두함수에 대해서는 Part1의 2장의 10항목에서 자세히 설명하였습니다. 이함수를 이용하여 훅킹할수 있는 메시지 들은 다음과 같습니다.

①마우스와 키보드에서 발생하는 메시지를 레코딩 하고 Paly할수 있습니다. 이때 사용하는 메시지 플래그는 WH_JOURNALRECORD와 WH_JOURNALPLAYBACK입니다.

②키보드에서 발생하는 메시지를 가로채고 이메시지를 삭제하거나 수정을 할수 있습니다. 이때는 메시지 플래그를 WH_KEYBOARD를 사용합니다.

③마우스에서 발생하는 메시지를 가로채고 이메시지를 삭제하거나 수정을 할수 있습니다. 이때는 메시지 플래그를 WH_MOUSE를 사용합니다.

④GetMessage와 PeekMessage에서 호출하였을때 얻을수 있는 모든 메시지를 가로채거나 수정할수 있습니다. 이때는 메시지 플래그를 WH_GETMESSAGE를 사용합니다.

⑤대화상자,메시지박스,스크롤바, 또는 메뉴등에서 발생하는 모든 메시지를 가로채거나 수정할수 있습니다. 이때는 메시지 플래그를 WH_MSGFILTER를 사용합니다.

⑥시스템 메뉴가 설정된 윈도우에서 시스템에서 보내는 COMMAND를 가로채거나 수정을 할수 있습니다. 이때는 메시지 플래그를 WH_SYSMSGFILTER를 사용합니다.

⑦SendMessage  함수에 의해서 매시지가 보낼때 이때 메시지를 가로채거나 수정을 할수 있습니다. 이때는 메시지 플래그를 WH_CALLWNDPROC를 사용합니다.

⑧시스템에서 특정 엑션이 발생하였을경우( 특정 윈도우가 종료되거나,아이콘화 되거나,시스템에서 구동된 윈도우의 정렬 순서가 바뀌거나) 메시지를 가로채거나 수정을 할수 있습니다. 이때는 메시지 플래그를 WH_CBT를 사용합니다. CBT는 Computer-based training의 약자입니다.

⑨디버깅 메시지를 가로챌수 있습니다. 이때는 WH_DEBUG를 사용합니다.

⑩쉘에서 발생되는 메시지를 가로챌수 있습니다. 이때는 WH_SHELL를 사용합니다.

위의 내용을 보신다면, 모든 윈도우에서 모든 기능을 가로챌수 있다는 결론이 나옵니다. 결국 시스템에서 구동된 모든 프로그램을 내마음대로 컨트롤 할수 있다는 것입니다. 이렇기 때문에 메시지 훅킹이 매우 강력하다는 것을 느끼게 될것입니다. 강력한만큼 이 프로그램을 작성하기가 매우 어렵다고 느낍니다. 그러나 몇가지 팁만 알고 있다면 그렇게 어려운게 아닙니다. (필자도 이몇가지 팁을 알려고 몇일을 고생했는지 모릅니다만.. 모든것들이 알고나면 별거 아니라는 느낌이 들더군요)

<1>메시지 훅킹의 기본 방법

메시지 훅킹의 기본방법은 Part1의 2장의 그림 1-2-26입니다. SetWindowsHookEx함수를 이용하여 첫번째 인자에는 훅킹 플러그,두번째 인자에는 훅킹을 수행하고자하는 프로시저함수포인터와 세번째인자로 훅킹하고자 하는 어플리케이션 인스턴스를 설정하고 ,4번째 인자로 어플리케이션 스레드 ID를 설정하면 훅킹을 할수가 있습니다.

SetWindowsHookEx의 함수는 다음과 같습니다.

HHOOK SetWindowsHookEx(  int idHook,  //훅킹 설정플래그

HOOKPROC lpfn,     // 훅킹을 수행하고자 하는 프로시저 함수

HINSTANCE hMod,    // 훅킹 하고자 하는 애플리케이션 인스턴스

DWORD dwThreadId   // 애플리케이션 인스턴스 스레드 ID

);

SetWindowsHookEx함수를 이용하여 훅킹을 설정하게 되면 해당 메시지가 발생되었을때 lpfn함수가 수행되게 됩니다. 이렇게 수행되면 lpfn함수에서 메시지를 가로챌수가 있습니다. SetWindowsHookEx함수가 성공적으로 수행되면 HHOOK형의 훅킹 핸들을 리턴하게 됩니다.

메시지를 가로채어서 필요한 내용을 수행한후에 메시지 훅킹을 종료하고자 한다면 SetWindowsHookEx함수에 의해서 설정된 훅킹 핸들러를 인자로 BOOL UnhookWindowsHookEx 함수를 수행시키면 됩니다. UnhookWindowsHookEx함수는 다음과 같습니다.

BOOL UnhookWindowsHookEx(

HHOOK hhk   // 해제하고자 하는 훅킹 핸들러

);

다음은 마우스 메시지를 가로채로 이것을 수행하는 예입니다.

//메시지 훅킹 설정

HHOOK hHook =SetWindowsHookEx(WH_JOURNALRECORD,

HookMouseProc,GetModuleHandle(NULL), 0);

//메시지 훅킹 설정

LRESULT CALLBACK HookMouseProc(int nCode, WPARAM wParam, LPARAM lParam)

{

LRESULT lResult = 1;

static POINT point;

if (nCode == HC_ACTION)

{

//메시지 훅킹 처리 함수

}

return CallNextHookEx(hHook, nCode, wParam, lParam);;

}

//메시지 훅킹 해제

UnhookWindowsHookEx(hHook);

SetWindowsHookEx함수를 사용하여 훅킹을 하게 되면 메시지가 발생되었을때 HookMouseProc 함수가 수행됩니다. 이함수안에서 메시지에 대한 처리내용을 수행하면 됩니다. 이렇게 수행을 한후에 훅킹을 해제 하고자 한다면 UnhookWindowsHookEx함수를 사용합니다.

<2>메시지 훅킹 스코프

메시지 훅킹은 단순하게 SetWindowsHookEx 사용하면 됩니다. 전항목에서 설명한 방법대로 윈도우 훅킹을 하게 되면 되는 부분도 있으나 (WH_JOURNALPLAYBACK, WH_JOURNALRECORD) 그러나 대부분의 메시지 훅킹은 수행을 할수 없게 됩니다. 이렇게 되는 이유는 메시지 훅킹의 스코프가 다르기 때문입니다. 먼저 훅킹 플러그와 스코프 리스트를 보시기 바랍니다.

(표1)은 훅킹 플러그와 스코프 리스트입니다.

(그림 파일)

(표1)은 훅킹 플러그와 스코프 리스트

표1에서 보듯이 메시지를 가로챌때 WH_JOURNALPLAYBACK, WH_JOURNALRECORD 와 WH_SYSMSGFILTER 는 Sytem에서 메시지를 가로챕니다. 이 3개는 어플리케이션 프로그램에서 SetWindowsHookEx함수를 이용하여 메시지를 가로챌수 있으나 Thread에서 메시지를 가로채는 함수들은 MFC로 작성한 어플리케이션 프로그램에서는 메시지를 가로챌수가 없습니다.(실행 에러가 발생될것입니다.) Thread에서 메시지를 가로챈다는 의미는 특정 윈도우 어플리케이션 프로그램에서 메시지를 가로챈다는 의미입니다.한개의 윈도우에서 스레드를 가로채고자 할때는 어플리케이션 프로그램 내부에서 가능하다고 도큐먼트에 나와 있으나, 실제적으로 작성해 보면 에러가 납니다.이이유는 어플리케이션 내부에서도 SetWindowsHookEx함수를 사용하고 있기 때문에 충돌이 있지 않을까 하는 생각입니다. (정확한 이야기가 아니라 필자의 생각입니다.) MFC를 이용하여 어플리케이션을 작성하면 내부적으로 메시지를 가로챌경우 SetWindowsHookEx함수를 이용합니다.(MFC 소스를 분석해보시면 알수 있을것입니다) 즉 어플리케이션 내부에 하나의 훅킹함수가 설정되고 이훅킹함수에서 메시지를 처리하고 있습니다. 이때 또하나의 훅킹 함수를 설정하고 이곳에서 또다른 형태의 내용을 수정한다고 하였을경우 문제가 발생될것이라는 것이 필자의 생각인것입니다. 또한가지는 훅킹 함수는 현재 프로그램 내부에 존재하는 것이 아니라 훅킹하는 프로그램 내부로 들어간다는 것입니다. 그림 2-5-1은 메시지 훅킹이 되었을때의 내용을 그림화 한것입니다.

(그림 파일)

(그림 2-5-1)메시지 훅킹시 내용

그림 2-5-1의 ⓐ처럼 SetWindowsHookEx함수를 이용하여 훅킹를 설정하고 이때 훅킹 프로시저를 HookMouseProc 로 설정하였다면 이 프로시저는   ⓐ프로그램 내부에 있는 것이 아니라 훅킹하고자 하는 프로그램 즉 ⓑ항목의 영역에 있습니다. 이럴경우 ⓐ프로그램 안에 있는 데이터를 사용할수가 없습니다. 예를 들어서  ⓐ프로그램 내부에  전역변수로 int data;라고 선언하고 data에 값을 설정하였다 하더라도 HookMouseProc에서는 data에 설정된 값을 확인 할수가 없습니다. 이이유는 HookMouseProc가 수행되는 영역이 ⓐ프로그램이 아니기 때문입니다. 일반적으로 훅킹한 내용을 처리하면 이내용의 결과를 어플리케이션에서 알아둡니다. 즉 ⓐ에서 훅킹 함수를 설정하여 이 훅킹함수가 처리한 결과를 ⓐ프로그램이 알아야 한다는 것입니다. 이렇게 하기 위해서는  ⓐ프로그램안에 존재하는 클래스나 또는 변수들에 값을 설정하거나 하여야 합니다. 그러나 훅킹 프로시저는 ⓐ프로그램 영역에 없기 때문에 이것이 불가능하게 됩니다. systemp메시지만 훅킹하는 3개의 훅킹 플래그 WH_JOURNALPLAYBACK, WH_JOURNALRECORD 와 WH_SYSMSGFILTER  는 ⓐ프로그램에 존재하는 여러 변수들을 핸들링할수가 있습니다. system을 훅킹할때는 훅킹 함수가  ⓐ프로그램 내부에 존재하기 때문입니다. 그러나 Thread일경우에는 ⓐ영역에 존재하지 않습니다.

<3>다른 프로그램에서 메시지를 훅킹하려면

2-5-1과 같은 형태로 훅킹이 되기 때문에 다른 프로그램에서 메시지를 가로채고자 한다면 이때에는 DLL를 이용하여야 합니다. DLL를 만들고 이 DLL 이 훅킹하고자 하는 프로그램 영역으로 들어가고 이 DLL이 훅킹을 처리한 결과를 메인 프로그램에 알려주는 방법을 이용합니다. 이렇게 훅킹을 위한 DLL 를 만들고자 할경우에는 다음에서 말하는 2가지 방법을 꼭 사용하여야 합니다.

①DLL를 정규 DLL 로 만들고 함수에 __declspec(dllexport) 를 사용하지 않고 EXPORT를 시켜야 한다.

②메인 프로그램에 결과를 전달하고자 하는 변수는 DLL의 공유 변수를 사용하거나 또는 메모리 맵핑을 이용하여야 한다.

DLL에서 __declspec(dllexport) 을 사용하지 않고 EXPORT함수를 만들어야 하는 방법은 Part 2 1장 3항목에서 설명하였습니다. DEF파일에서 EXPORT 항목에 함수를 설정하고 @ 와 함께 순서 번호를 설정하여야 한다는 의미입니다. ②항목의 내용은 Part 2 1장 3항목에서 설명한 내용처럼 공유 데이터를 만들고 이 공유 데이터를 통해서 메인 프로그램과 통신을 해야 한다는 의미입니다. 이방법외에 Part1 의 10장에서 설명한 메모리 맵핑을 이용하여 메인프로그램과 통신을 하는 방법에 의해서 결과들을 메인 프로그램에 보내는 것입니다.

위에서 설명한 2가지 방법을 이용하게 되면 한개의 프로그램에서 메시지 훅킹을 할뿐만 아니라 모든 윈도우에서 메시지를 훅킹할수가 있습니다.

<4>메시지훅킹 후에 메시지 처리

메시지를 훅킹하여 필요한 내용을 처리하고 현재 메시지를 소멸 시키지 않고 다시 다른 훅킹 프로시저에게 넘겨주고자 할경우가 있습니다. 이럴경우에는 CallNextHookEx 함수를 사용합니다. 이함수는 다음과 같습니다.

LRESULT CallNextHookEx(

HHOOK hhk,      // 현재 훅킹 핸들

int nCode,      // 상태 코드

WPARAM wParam,  // 현재 훅킹 프로시저에 설정된 wParam

LPARAM lParam   // 현재 훅킹 프로시저에 설정된 lParam

);

훅킹 프로시저에서 CallNextHookEx 함수를 사용하지 않으면 훅킹된 메시지가 다른 어플리케이션에 전달되지 않고 소멸됩니다.(system에서만 메시지훅킹을 하는 3개의 함수제외) 예를 들어서 마우스 이벤트를 훅킹하고 CallNextHookEx함수를 수행시키지 않았다면 , 마우스 이벤트는 어플리케이션 프로그램에서 발생되지 않습니다.예를 들어서  A라는 프로그램에서 마우스 이벤트를 훅킹하고 CallNextHookEx함수를 수행하지 않으면 A라는 프로그램에서는 메뉴를 선택하기 위해서 마우스를 사용하고자 하여도 아무런 응답이 없게 됩니다.

다음은 마우스 메시지를 훅킹하는 함수에서 해당 내용을 훅킹하고 다시 다음 훅킹 프로시저로 내용을 넘기는 예입니다.

LRESULT CALLBACK MouseFunc (int nCode, WPARAM wParam, LPARAM lParam )

{

LPMOUSEHOOKSTRUCT MouseHookParam;

CString message;

if ( nCode >= 0 ) {

//이곳에서 마우스 이벤트를 훅킹하고

}

//다음 훅킹 프로시저에 내용을 넘긴다.

return( CallNextHookEx(hHook, nCode, wParam, lParam));

}

2. 메시지 훅킹 필터 함수

SetWindowsHookEx의 두번째 인자에는 훅킹을 처리하는 함수를 설정해주어야 합니다. 이것을 훅킹 필터 함수라고 합니다. 이함수의 기본형은 다음과 같이 되어 있습니다.

LRESULT CALLBACK FilterFunc(

int nCode,

WPARAM wParam,

LPARAM lParam

)

nCode에는 현재 메시지의 상태를 설정하는 플래그이며 여러 정보들은 wParam과 lParam에 훅킹 플래그에 따라서 다르게 선언됩니다. 표1에서 보듯이 12개의 훅킹 플래그가 잇있습니다. 이훅킹플래그에 따라서 wParam과 lParam에 설정되는 내용이 다르다는 것입니다. 메시지가 발생되었을때 wParam과 lParam에 여러 형태로 값이 설정되는 것과 같은 의입니다. 본항목에서는 훅킹 플래그에 따라서 wParam과 lParam그리고 nCode에 어떤 값이 설정되는 가를 알아보겠습니다.

<1>WH_CALLWNDPROC

WH_CALLWNDPROC 플래그를 설정하게 되면 다른 애플리케이션 프로그램에서 SendMessage 함수가 수행되면 SendMessage에서 보낸 값을 훅킹할수가 있습니다.

이때 훅킹 필터 함수를 설정하게 되면 lParam에 CWPSTRUCT 구조체의 형태로 정보를 보내줍니다. CWPSTRUCT구조체는 다음과 같습니다.

typedef struct tagCWPSTRUCT{

LPARAM lParam;

WPARAM wParam;

DWORD	message;

HWND hwnd;

}

CWPSTRUCT,*PCWPSTRUCT,NEAR *NPCWPSTRUCT,FAR *LPCWSPRUCT;

SendMessage 함수에서 설정된 lParam과 wParam그리고 message가 CWPSTRUCT의 lParam,wParam,message에 설정되며 hwnd는 SendMessage함수를 수행한 윈도우 핸들입니다.  훅킹 필터함수의 nCode가 0보다 클경우는 현재 SendMessage함수가 수행되고 이때 정확하게 값을 훅킹했다는 의미가 되면 이값이 0보다 작을 경우에는 훅킹이 성공하지 않았다는 의미입니다. 다음은 WH_CALLWNDPROC 플래그를 설정하고 메시지 훅킹을 설정하였을대 훅킹 필터 함수의 예입니다.

LRESULT CALLBACK CallWndProcFunc (int nCode, WPARAM wParam, LPARAM lParam )

{

PCWPSTRUCT  pParamStruct;

CString message;

if ( nCode >= 0 ) {

pParamStruct = (PCWPSTRUCT) lParam;

//pParamStruct->message 에는 메시지 값이 설정

//pParamStruct->hwnd 는 윈도우 핸들

//pParamStruct->lParam에는 lParam 정보

//pParamStruct->wParam에는 wParam정보

//이내용을가지고 훅킹 내용을 설정한다.

}

//다음 훅킹 프로시저를 수행시킨다.

return CallNextHookEx(hHook, nCode, wParam, lParam);

}

<2>WH_CBT

WH_CBT는 훅킹 상태코드 즉 훅킹필터 함수의 nCode에 따라서 여러개로 정보가 나뉘어져서 수행됩니다.

(1)HCBT_ACTIVATE

훅킹된 윈도우 상태가 활성화 되고 있다는 의미입니다.  wParam에는 활성화 되는 윈도우 핸들이 설정되며 lParam는 CBTACTIVATESTRUCT 구조체 형태로 값이 설정됩니다. CBTACTIVATESTRUCT 는 다음과 같습니다.

typedef struct tagCBTACTIVATESTRUCT

{      BOOL fMouse;

HWND hWndActive;

} CBTACTIVATESTRUCT;

fMouse는 마우스를 클릭하여 해당 윈도우가 활성화 되었을경우 fMouse값이 TRUE로 설정되면 그렇지 않을 경우 FASLE 로 설정됩니다. hWndActive는 현재 활성화 되어 있는 윈도우 핸들입니다.

(2)HCBT_CREATEWND

현재 새로운 윈도우가 만들어질때 발생됩니다. wParam에는 새로만들어지는 윈도우 핸들이 lParam에는 CBT_CREATEWND구조체의 정보가 설정됩니다. CBT_CREATEWND는 다음과 같습니다.

typedef struct tagCBT_CREATEWND { // cbtcw

LPCREATESTRUCT lpcs;

HWND           hwndInsertAfter;

} CBT_CREATEWND;

lpcs는 CREATESTRUCT 구조체 포인터이며 hwndInsertAfter은 새로만들어지는 윈도우가 설정되는 윈도우 핸들입니다.

(3)HCBT_DESTROYWND

윈도우가 사라질때 발생됩니다. wParam에는 소멸되는 윈도우 핸들이며 lParam에는 0값이 설정됩니다.

(4)HCBT_MINMAX

윈도우가 아이콘화 되거나 또는 화면크기로 확대될때 발생됩니다. wParam에는 MAX나 MINIMIZED된 윈도우 핸들이며 lParam에는 현재 확대되었는지 또는 축소되었는지의 플래그가 설정됩니다. 이 플러그는 일반적으로 확대 축소할때 사용하였던 SW_ 계열의 플러그가 설정됩니다. 만일 확대가 되었다면 SW_MAXMIZED 가 lParam에 설정됩니다.

(5)HCBT_MOVESIZE

윈도우가 이동되거나 크기가 바뀔경우 이때 발생됩니다. wParam에는 크기가 변하거나 이동된 윈도우의 핸들이 설정되며 lParam에는 LPRECT구조체형태로 현재 이동된 크기의 값이 설정됩니다.

(6)HCBT_SYSCOMMAND

시스템 메시지가 발생되었을때 설정됩니다. wParam에는 SC_ 계열의 시스템 이벤트 코드가 설정되면 lParam의 HIWORD에는  마우스 x좌표가 LOWORD에는 마우스의 y좌표가 설정됩니다. 표2는 SC_ 계열의 시스템 이벤트 코드 리스트입니다.

(그림 파일)

(표2) SC_계열 시스탬 이벤트 코드

(7)HCBT_CLICKSKIPPED

마우스 이벤트가 어플리케이션 프로그램의 메시지 Input 큐에서 사라지거나 설정될때 발생됩니다. wParam에는 WM_LBUTTONDOWN 과 같은 마우스 메시지가 설정되며 lParam에는 MOUSEHOOKSTRUCT구조체의 정보로 설정됩니다. MOUSEHOOKSTRUCT 구조체는 다음과 같습니다.

typedef struct tagMOUSEHOOKSTRUCT {

POINT pt;//마우스 윈도우 포지션

HWND  hwnd; //메시지가 발생된 윈도우 핸들

UINT  wHitTestCode; //히트 테스트 코드

DWORD dwExtraInfo; //그외 필요한 정보는 이곳에 설정된다.

} MOUSEHOOKSTRUCT;

(8)HCBT_SETFOCUS

윈도우에 포커스가 설정되었을때 발생됩니다. wParam에는 윈도우 핸들이 lParam에는 포커스가 사라지는 윈도우 핸들이 설정됩니다.

다음은 WH_CBT를 이용하여 여러 메시지를 훅킹하는 예입니다.

LRESULT CALLBACK CbtFunc (int nCode, WPARAM wParam, LPARAM lParam )

{

CString message;

CBTACTIVATESTRUCT *Active;

LPMOUSEHOOKSTRUCT MouseHookParam;

LPCBT_CREATEWND   CreateWndParam;

LPRECT	Rect;

long lpProc;

char szType[80];

if ( nCode >= 0 ) {

switch ( nCode ) {

case HCBT_ACTIVATE:

Active = (CBTACTIVATESTRUCT *) lParam;

message.Format("CBT ACTIVATE Wnd:%d Wnd:%d is active. Mouse Flag:%d",

wParam,Active->hWndActive,Active->fMouse);

break;

case HCBT_CLICKSKIPPED:

MouseHookParam = (LPMOUSEHOOKSTRUCT) lParam;

message.Format("CBT CLICKIPPED Point:%d %d %d",

MouseHookParam->pt.x,MouseHookParam->pt.y,wParam);

break;

case HCBT_CREATEWND:

CreateWndParam = (LPCBT_CREATEWND) lParam;

lpProc = GetWindowLong((HWND)wParam, GWL_WNDPROC);

message.Format("CBT CREATEWND Wnd:%d  Proc:%ld",wParam,	lpProc);

break;

case HCBT_DESTROYWND:

message.Format("CBT DESTROYWND Wnd:%d",wParam);

break;

case HCBT_KEYSKIPPED:

message.Format("CBT KEYSKIPPED Key:%d", wParam);

break;

case HCBT_MINMAX:

switch ( LOWORD(lParam) ) {

case SW_HIDE:

strcpy(szType, "SW_HIDE");

break;

case SW_NORMAL:

strcpy(szType, "SW_NORMAL");

break;

case SW_SHOWMINIMIZED:

strcpy(szType, "SW_SHOWMINIMIZED");

break;

case SW_MAXIMIZE:

strcpy(szType, "SW_MAXIMIZE");

break;

case SW_SHOWNOACTIVATE:

strcpy(szType, "SW_SHOWNOACTIVATE");

break;

case SW_SHOW:

strcpy(szType, "SW_SHOW");

break;

case SW_MINIMIZE:

strcpy(szType, "SW_MINIMIZE");

break;

case SW_SHOWMINNOACTIVE:

strcpy(szType, "SW_SHOWMINNOACTIVE");

break;

case SW_SHOWNA:

strcpy(szType, "SW_SHOWNA");

break;

case SW_RESTORE:

strcpy(szType, "SW_RESTORE");

break;

default:

strcpy(szType,"Unknown Message");

}

message.Format("CBT MINMAX Wnd:%d %s",

wParam,(LPSTR)szType);

break;

case HCBT_MOVESIZE:

Rect = (LPRECT) lParam;

message.Format("CBT MOVESIZE Wnd:%d to %d %d %d %d",

wParam,Rect->left,Rect->top,Rect->right,Rect->bottom);

break;

case HCBT_SETFOCUS:

message.Format("CBT SETFOCUS Wnd:%d 포커스가 설정 Wnd:%d",

wParam,LOWORD(lParam));

break;

case HCBT_SYSCOMMAND:

switch ( wParam ) {

case SC_SIZE:

strcpy(szType,"SC_SIZE");

break;

case SC_MOVE:

strcpy(szType,"SC_MOVE");

break;

case SC_MINIMIZE:

strcpy(szType,"SC_MINIMIZE");

break;

case SC_MAXIMIZE:

strcpy(szType,"SC_MAXIMIZE");

break;

case SC_NEXTWINDOW:

strcpy(szType,"SC_NEXTWINDOW");

break;

case SC_PREVWINDOW:

strcpy(szType,"SC_PREVWINDOW");

break;

case SC_CLOSE:

strcpy(szType,"SC_CLOSE");

break;

case SC_VSCROLL:

strcpy(szType,"SC_VSCROLL");

break;

case SC_HSCROLL:

strcpy(szType,"SC_HSCROLL");

break;

case SC_MOUSEMENU:

strcpy(szType,"SC_MOUSEMENU");

break;

case SC_KEYMENU:

strcpy(szType,"SC_KEYMENU");

break;

case SC_ARRANGE:

strcpy(szType,"SC_ARRANGE");

break;

case SC_RESTORE:

strcpy(szType,"SC_RESTORE");

break;

case SC_TASKLIST:

strcpy(szType,"SC_TASKLIST");

break;

case SC_SCREENSAVE:

strcpy(szType,"SC_SCREENSAVE");

break;

case SC_HOTKEY:

strcpy(szType,"SC_HOTKEY");

break;

default:

strcpy(szType,"Unknown Message");

}

message.Format("CBT SYSCOMMAND %s",(LPSTR)szType);

break;

}

}

return( CallNextHookEx(hHook[1], nCode,wParam, lParam));

}

<3>WH_MOUSE

WH_MOSUE를 이용하면 마우스 이벤트를 얻을수 있습니다. wParm에는 마우스 이벤트가 설정됩니다. 예를 들어 좌측마우스 버튼이 클릭되었다면  wParam에는 WM_LBUTTONDOWN메시지가 발생됩니다.  lParam에는 MOUSEHOOKSTRUCT 구조체 형태로 정보가 설정됩니다. MOUSEHOOKSTRUCT는 위의  HCBT_CLICKSKIPPED 설명을 참조하시기 바랍니다. nCode에는 두개의 값이 설정됩니다. HC_ACTION은 현재 메시지가 발생된후에 메시지가 수행되어 삭제된 상태이며 HC_NOREMOVE 아직 메시지가 존재하는 상태입니다.

다음은 메시지 훅킹 필터함수의 예입니다.

LRESULT CALLBACK MouseFunc (int nCode, WPARAM wParam, LPARAM lParam )

{

LPMOUSEHOOKSTRUCT MouseHookParam;

CString message;

if ( nCode >= 0 ) {

MouseHookParam = (MOUSEHOOKSTRUCT *) lParam;

message.Format("Mouse Wnd:%d Point:%d %d	wParam=%d",MouseHookParam->hwnd,

MouseHookParam->pt.x,MouseHookParam->pt.y,wParam);

}

return( CallNextHookEx(hHook[5], nCode, wParam, lParam));

}

<4>WH_KEYBOARD

WH_KEYBOARD 플래스글 사용하면 키보드 메시지가 발생되었을때 이벤트가 설정됩니다. wParam에는 가상키코드 값이 설정되며 lParam에는 스캔 코드값이 설정됩니다. nCode에는 현재 메시지가 큐에서 사라지게되면 HC_ACTION이 그렇지 않다면 HC_NOREMOVE 가 설정됩니다.

3. 메시지 훅킹 예제 만들기

본항목에서는 모든 윈도우에서 마우스 메시지를 가로채는 예제와 모든 메시지를 가로채는 예제를 작성해 보겠습니다. 본 항목에서 작성하는 예제는 총 4개입니다. 마우스 메시지를 가로채는 ExHookMouse DLL프로그램과 ExHookMouse 예제를 이용하여 마우스 메시지를 가로채는 ExMouseHook 프로그램, 그리고 모든 메시지를 가로채는 MgHookDll DLL프로그램과 이를 이용하는 ExSpy프로그램을 제작합니다.

<1>마우스 메시지 가로채는 예제 ExHookMouse

ExHookMosue는 마우스 메시지를 가로채서 현재 마우스 메시지 정보를 설정하는 예제입니다. ExHookMouse 프로젝트를 만들대 정규 DLL형태로 만듭니다.

ExHookMouse는 공유 데이터를 사용합니다. 본장 앞에서 설명하였듯이 공유 데이터를 설정하지 않으면 DLL과 메인과의 통신이 되지 않기 때문입니다. 본예제에서는 공유 데이터를 DLL를 로드한 윈도우 핸들로 설정하였습니다.

//공유 데이터 설정

#pragma data_seg("SHARDATA")

//메인 윈도우 핸들

static HWND	hwndMain = NULL;

//DLL 인스턴스 핸들

static HINSTANCE	hInstance;

#pragma data_seg()

위와 같이 설정하면 hwndMain과 hInstance는 공유데이터로 설정됩니다. 위와 같이 설정한후에 def파일에도 공유 데이터항목을 설정해야 합니다. def파일에 다음과 같이 기록합니다.

SECTIONS

SHARDATA   Read Write Shared

현재 DLL에서 훅킹할때는 SetWindowsHookEx를 수행합니다. 이렇게 수행되면 훅킹 핸들이 넘어옵니다. 이 훅킹 핸들을 전역 변수로 다음과 같이 선언합니다.

HHOOK hHook;

위와 같이 한후에 4개의 함수를 선언합니다. 이 4개의 함수는 다음과 같습니다.

//훅킹 초기화

int FAR PASCAL InitHooksDll(HWND hMain);

//훅킹 시작하는 함수

int FAR PASCAL InstallHook();

//훅킹 종료 함수

int FAR PASCAL UnInstallHook();

//훅킹 필터 함수

LRESULT CALLBACK MouseFunc (int nCode, WPARAM wParam, LPARAM lParam );

InitHooksDll함수는 훅킹을 초기화 하는 함수입니다. 이함수에서는 공유데이터멤버인 메인윈도우 핸들과 인스턴스에 값을 설정합니다. 다음은 InitHookDll의 내용입니다.

int CALLBACK InitHooksDll(HWND hMain)

{

hwndMain = hMain;

hInstance=theApp.m_hInstance;

return 1;

}

InitHooks 함수에서는 hMain으로 넘겨준 윈도우 핸들 포인터 값을 hwndMain에 설정하고 현재 DLL의 인스턴스를 hInstance에 설정한것입니다.

InstallHook 함수는 훅킹 함수를 수행하는 함수입니다. 이함수의 내용은 다음과 같습니다.

int CALLBACK InstallHook()

{

hHook=SetWindowsHookEx(WH_MOUSE,

(HOOKPROC)MouseFunc,hInstance,0);

return 1;

}

InstallHook함수는  WH_MOSUE플러그를 설정한 SetWindowsHookEx함수를 실행하고 이렇게 실행된 결과를 hHook에 설정한것입니다. SetwindowsHookEx에서 MouseFunc 로 필터 함수를 설정하였습니다. 이필터함수에서는 메시지를 받으면 메인윈도우 화면에 메시지를 출력하도록 하였습니다. 이함수는 다음과 같습니다.

LRESULT CALLBACK MouseFunc (int nCode, WPARAM wParam, LPARAM lParam )

{

HDC	hDC;

LPMOUSEHOOKSTRUCT MouseHookParam;

CString message,msg;

if ( nCode >= 0 ) {

if ( nCode == HC_NOREMOVE )

msg= "큐에 데이터 있음";

else

msg="데이터 삭제됨     ";

MouseHookParam = (MOUSEHOOKSTRUCT *) lParam;

message.Format("MOUSE  Wnd:%d Point:%d %d wParam=%d

%s",MouseHookParam->hwnd,

MouseHookParam->pt.x,MouseHookParam->pt.y,wParam,msg);

hDC = GetDC(hwndMain);

TextOut(hDC, 10, 10,message,message.GetLength());

ReleaseDC(hwndMain, hDC);

}

return( CallNextHookEx(hHook, nCode, wParam, lParam));

}

DLL를 로드한 메인 윈도우 화면에 출력하기 위해서 GetDC를 사용하였습니다. 이때 hwndMain이 필요하게 됩니다. 이 hwndMain를 공유 데이터로 설정한 이유가 바로 여기에 있습니다. 위의 MouseFunc는 현재 프로그램에서 구동되는 것이아니라 메시지를 훅킹하는 윈도우에서 공유됩니다. SetWindowsHookEx함수에서 스레드를 0으로 설정하였기 때문에 모든 윈도우에서 발생되는 메시지를 받기 때문에 어느부분에도 공유되지 않고 쉽게 말하면 공중에 떠있는 상태로 구동이 됩니다. 만일 hwndMain를 그냥 전역 변수로 설정하게 되면 DLL를 로드한 프로그램의 영역에 hwndMain은 설정되고 DLL 은 이값을 참조할수가 없게 됩니다. 전역변수로만 설정하고 GetDC를 하게 되면 hwndMain값을 얻을수가 없기 때문에 메인프로그램의 윈도우 핸들을 얻을수가 없습니다. 따라서 hwndMaind를 공유 데이터로 설정한것입니다.

UnInstallHook 함수는 hHook에 설정된 훅킹을 해제 하는 함수입니다. 이함수는 다음과 같습니다.

int CALLBACK UnInstallHook()

{

UnhookWindowsHookEx(hHook);

return 1;

}

위와 같이 함수 4개를 선언하고 코딩하고 난후 4개의 함수중에 MouseFunc를 제외한 3개의 함수는 DLL 를 로드한 프로그램에서 호출해야 합니다. 이렇게 호출할 함수형은 일반적으로 __declspec(dllexport) 함수를 사용하게 되나 이함수를 사용하지 않습니다. 이부분도 같은 의미입니다. __declspec(dllexport) 사용하여 EXPORT를 시키게 되면 함수가 호출된 프로그램 영역에서 구동됩니다. 이렇게 될경우 공유 데이터로 설정한 값이 참조가 안됩니다. 따라서 def파일을 수정하여 EXPORT를 하여야 합니다. 다음은 def파일에서 EXPORT항목에 함수를 기록한 내용입니다.

EXPORTS

InitHooksDll	@1

InstallHook	@2

UnInstallHook	@3

위와 같이 하면 마우스 이벤트를 얻어서 메인 윈도우 화면에 출력하는 DLL제작이 끝나게 됩니다.

<2>마우스 메시지 가로채는 예제 ExMouseHook

ExMouseHook 예제는 앞에서 만든 ExHookMouse.dll를 이용하여 현재 마우스 이벤트를 화면에 출력하는 예제 입니다. 이예제는 대화상자 형태로 만들었습니다. 이예제의 전체 구성도는 그림 2-5-2와 같습니다.

(그림 파일)

(그림 2-5-2)ExMouseHook 전체 구성도

2-5-2의  ⓐ항목은 ExMouseHook의 실행화면입니다. ⓐ항목에서 보듯이 현재 모든 윈도우에서 발생하는 마우스 메시지와 윈도우 핸들 그리고 마우스 위치가  ⓐ화면의 대화상자에 출력이 됩니다. 이렇게 하기 위해서 그림 2-5-2의  ①항목처럼 OnInitDialog함수에서 ExHookMouse.dll에 있는 InitHooksDll함수와 InstallHook함수를 호출합니다. 이렇게 호출되면 바로 마우스 메시지가 훅킹되고 그렇게 되면 ⓐ윈도우의 상단부에 훅킹된 결과가 출력이 됩니다. 대화상자가 종료되면 그림 2-5-2의 ③항목처럼 UnInstallHook함수를 호출하여 대화상자를 종료 합니다.

본 예제와 ExHookMosue예제에서 DLL 를 만들고 링크하고 헤더를 포함하는 부분은 생략합니다. 이부분은 Part2의 1장에서 이미 설명한 부분입니다. 자세한 내용은 1장과 제공되는 CD의 소스를 참조하시기 바랍니다.

<3>모든 메시지 가로채기 예제 MgHookDll

MgHookDll는 WH_CALLWNDPROC, WH_CBT, WH_DEBUT, WH_GETMESSAGE, WH_KEYBOARD, WH_MOSUE 플래그를 설정하여 발생되는 모든 메시지의 결과를 보내는 dll입니다. 즉 거히 모든 메시지를 가로채는 예제까 됩니다. 본예제 또한 정규 DLL 로제작하였습니다. 총 6개의 메시지를 가로채기위해서 HHOOK 핸들 변수를 전역 변수로 6개의 배열로 다음과 같이 설정하였습니다.

HHOOK hHook[6];

본예제는 윈도우 핸들을 이용한것이 아니라 메모리 맵핑을 이용하였습니다. 메모리 맴핑에 대해서는 PART 1의 10장에서 설명하였습니다. 자세한 내용은 PART1 의 10장을 참조하시기 바랍니다.

메모리 매핑을 하기위해서는 기본적으로 두개의 변수가 필요합니다. 첫번째는 매모리 맵핑 핸들이며 두번째는 매모리 맵핑과 연결된 변수입니다. 이것을 공유 데이터로 설정하였습니다.

#pragma data_seg("SHARDATA")

static HWND	hwndMain = NULL;

static HINSTANCE	hInstance;

static HANDLE m_hMapping;//메모리 맵핑 핸들

static LPVOID m_pViewOf;//메모리 파일에 연결된 변수

#pragma data_seg()

이제 DLL외부에서 호출할 3개의 함수를 선언합니다. 이함수는 다음과 같습니다.

// 초기화 함수

int FAR PASCAL InitHooksDll(HWND hMain);

//훅킹 시작함수

int FAR PASCAL InstallHook(int num);

//훅킹 해제 함수

int FAR PASCAL UnInstallHook(int num);

InitHooksDll 은 앞에서 작성한 예제처럼 인스턴스와 메인윈도우 핸들값을 저장하는 함수입니다. 이함수는 다음과 같습니다.

int CALLBACK InitHooksDll(HWND hMain)

{

hwndMain = hMain;

hInstance=theApp.m_hInstance;

return 1;

}

InstallHook 함수는 훅킹을 시작하는 함수입니다. 이때 int num이라는 인자가 있습니다. 이인자는 훅킹하고자 하는 플래그 번호입니다. 번호값과 훅킹 플래그는 표3과 같습니다.

(그림 파일)

(표3) int num의 값과 훅킹 플래그

표3에 의거하여 SetWindowsHook함수를 가동시킨 InstallHook함수는 다음과 같습니다.

int CALLBACK InstallHook(int num)

{

switch(num)

{

case 0:

hHook[num]=SetWindowsHookEx(WH_CALLWNDPROC,

(HOOKPROC)CallWndProcFunc,hInstance, 0);

break;

case 1:

hHook[num]=SetWindowsHookEx(WH_CBT,

(HOOKPROC)CbtFunc,hInstance, 0);

break;

case 2:

hHook[num]=SetWindowsHookEx(WH_DEBUG,

(HOOKPROC)DebugFunc,hInstance, 0);

break;

case 3:

hHook[num]=SetWindowsHookEx(WH_GETMESSAGE,

(HOOKPROC)GetMessageFunc,hInstance, 0);

break;

case 4:

hHook[num]=SetWindowsHookEx(WH_KEYBOARD,

(HOOKPROC)KeyboardFunc,hInstance, 0);

break;

case 5:

hHook[num]=SetWindowsHookEx(WH_MOUSE,

(HOOKPROC)MouseFunc,hInstance, 0);

break;

}

return 1;

}

UnInstallHook함수는 int num의 값에 의해서 해당 훅킹 핸들의 해제하는 것입니다. 이 내용은 다음과 같습니다.

int CALLBACK UnInstallHook(int num)

{

UnhookWindowsHookEx(hHook[num]);

return 1;

}

InstallHook함수를 보시면 SetWindowsHookEx함수에서 등록한 필터 함수들이 다릅니다. 총 6개의 필터함수가 설정됩니다. 이함수중에서 WH_CALLWNDPROC 플래그에 의해서 수행되는 CallWndProcFunc 함수는 다음과 같습니다.

LRESULT CALLBACK CallWndProcFunc (int nCode, WPARAM wParam, LPARAM lParam )

{

PCWPSTRUCT  pParamStruct;

CString message;

if ( nCode >= 0 ) {

pParamStruct = (PCWPSTRUCT) lParam;

message.Format("CALLWNDPROC Wnd:%d Message:%d",

pParamStruct->hwnd,pParamStruct->message);

①	//메모리 맵핑 파일을 오픈한다.

m_hMapping =OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE,

FALSE, "ExMessageData");

//m_pViewOf에 연결

m_pViewOf = MapViewOfFile(m_hMapping, FILE_MAP_READ |

FILE_MAP_WRITE, 0, 0, 0);

strcpy((char *)m_pViewOf,message);

CloseHandle(m_hMapping);

}

return CallNextHookEx(hHook[0], nCode, wParam, lParam);

}

CallWndProcFunc 에서 중요하게 볼것이 ①항목입니다. 이벤트 메시지가 발생되어 이값을 message에 문자열로 정보를 설정한다음 "ExMessageData"라는 메모리 파일을 만들고 이파일에 m_pViewOf 를 연결시킨다음 이 변수에 값을 설정한것입니다. 즉 메모리 맵핑을 이용하여 데이터를 정보를 얻겠다는 의미입니다. "ExMessageData" 라는 메모리 맵핑파일을 오픈하였기 때문에 DLL를 로드하는 프로램에서는 □ExMessageData" 라는 메모리 맵핑 파일을 만들어야 합니다. 다음 항목에서 설명하는 ExSpy에서는 이렇게 메모리 맵핑파일을 만듭니다.

나머지 5개의 필터 함수또한 □ExMessageData"라는 메모리 맵핑 파일에 데이터를 기록합니다. 이 5개의 함수는 다음과 같습니다.

(프로그램 소스)

LRESULT CALLBACK CbtFunc (int nCode, WPARAM wParam, LPARAM lParam )

{

CString message;

CBTACTIVATESTRUCT *Active;

LPMOUSEHOOKSTRUCT MouseHookParam;

LPCBT_CREATEWND   CreateWndParam;

LPRECT	Rect;

long lpProc;

char szType[80];

if ( nCode >= 0 ) {

switch ( nCode ) {

case HCBT_ACTIVATE:

Active = (CBTACTIVATESTRUCT *) lParam;

message.Format("CBT ACTIVATE Wnd:%d Wnd:%d is active. Mouse Flag:%d",

wParam,Active->hWndActive,Active->fMouse);

break;

case HCBT_CLICKSKIPPED:

MouseHookParam = (LPMOUSEHOOKSTRUCT) lParam;

message.Format("CBT CLICKIPPED Point:%d %d %d",

MouseHookParam->pt.x,MouseHookParam->pt.y,wParam);

break;

case HCBT_CREATEWND:

CreateWndParam = (LPCBT_CREATEWND) lParam;

lpProc = GetWindowLong((HWND)wParam, GWL_WNDPROC);

message.Format("CBT CREATEWND Wnd:%d  Proc:%ld",wParam, lpProc);

break;

case HCBT_DESTROYWND:

message.Format("CBT DESTROYWND Wnd:%d",wParam);

break;

case HCBT_KEYSKIPPED:

message.Format("CBT KEYSKIPPED Key:%d", wParam);

break;

case HCBT_MINMAX:

switch ( LOWORD(lParam) ) {

case SW_HIDE:

strcpy(szType, "SW_HIDE");

break;

case SW_NORMAL:

strcpy(szType, "SW_NORMAL");

break;

case SW_SHOWMINIMIZED:

strcpy(szType, "SW_SHOWMINIMIZED");

break;

case SW_MAXIMIZE:

strcpy(szType, "SW_MAXIMIZE");

break;

case SW_SHOWNOACTIVATE:

strcpy(szType, "SW_SHOWNOACTIVATE");

break;

case SW_SHOW:

strcpy(szType, "SW_SHOW");

break;

case SW_MINIMIZE:

strcpy(szType, "SW_MINIMIZE");

break;

case SW_SHOWMINNOACTIVE:

strcpy(szType, "SW_SHOWMINNOACTIVE");

break;

case SW_SHOWNA:

strcpy(szType, "SW_SHOWNA");

break;

case SW_RESTORE:

strcpy(szType, "SW_RESTORE");

break;

default:

strcpy(szType,"Unknown Message");

}

message.Format("CBT MINMAX Wnd:%d %s",

wParam,(LPSTR)szType);

break;

case HCBT_MOVESIZE:

Rect = (LPRECT) lParam;

message.Format("CBT MOVESIZE Wnd:%d to %d %d %d %d",

wParam,Rect->left,Rect->top,Rect->right,Rect->bottom);

break;

case HCBT_SETFOCUS:

message.Format("CBT SETFOCUS Wnd:%d 포커스가 설정 Wnd:%d",

wParam,LOWORD(lParam));

break;

case HCBT_SYSCOMMAND:

switch ( wParam ) {

case SC_SIZE:

strcpy(szType,"SC_SIZE");

break;

case SC_MOVE:

strcpy(szType,"SC_MOVE");

break;

case SC_MINIMIZE:

strcpy(szType,"SC_MINIMIZE");

break;

case SC_MAXIMIZE:

strcpy(szType,"SC_MAXIMIZE");

break;

case SC_NEXTWINDOW:

strcpy(szType,"SC_NEXTWINDOW");

break;

case SC_PREVWINDOW:

strcpy(szType,"SC_PREVWINDOW");

break;

case SC_CLOSE:

strcpy(szType,"SC_CLOSE");

break;

case SC_VSCROLL:

strcpy(szType,"SC_VSCROLL");

break;

case SC_HSCROLL:

strcpy(szType,"SC_HSCROLL");

break;

case SC_MOUSEMENU:

strcpy(szType,"SC_MOUSEMENU");

break;

case SC_KEYMENU:

strcpy(szType,"SC_KEYMENU");

break;

case SC_ARRANGE:

strcpy(szType,"SC_ARRANGE");

break;

case SC_RESTORE:

strcpy(szType,"SC_RESTORE");

break;

case SC_TASKLIST:

strcpy(szType,"SC_TASKLIST");

break;

case SC_SCREENSAVE:

strcpy(szType,"SC_SCREENSAVE");

break;

case SC_HOTKEY:

strcpy(szType,"SC_HOTKEY");

break;

default:

strcpy(szType,"Unknown Message");

}

message.Format("CBT SYSCOMMAND %s",(LPSTR)szType);

break;

}

//메모리 맵핑 파일을 오픈한다.

m_hMapping =OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, "ExMessageData");

//m_pViewOf에 연결

m_pViewOf = MapViewOfFile(m_hMapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);

strcpy((char *)m_pViewOf,message);

CloseHandle(m_hMapping);

}

return( CallNextHookEx(hHook[1], nCode,wParam, lParam));

}

LRESULT CALLBACK DebugFunc (int nCode, WPARAM wParam, LPARAM lParam )

{

PDEBUGHOOKINFO pDebugHook;

CString message;

if ( nCode >= 0 ) {

pDebugHook = (PDEBUGHOOKINFO) lParam;

message.Format("디버깅 메시지");

//메모리 맵핑 파일을 오픈한다.

m_hMapping =OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, "ExMessageData");

//m_pViewOf에 연결

m_pViewOf = MapViewOfFile(m_hMapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);

strcpy((char *)m_pViewOf,message);

CloseHandle(m_hMapping);

}

return( CallNextHookEx(hHook[2], nCode, wParam, lParam));

}

LRESULT CALLBACK GetMessageFunc (int nCode, WPARAM wParam, LPARAM lParam )

{

MSG *lpMsg;

CString message;

if ( nCode >= 0 ) {

lpMsg = (MSG *) lParam;

message.Format("GETMESSAGE   Wnd:%d Time:%d  Point:%d %d Msg:%d",

lpMsg->hwnd, lpMsg->time,

lpMsg->pt.x, lpMsg->pt.y,lpMsg->message);

//메모리 맵핑 파일을 오픈한다.

m_hMapping =OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, "ExMessageData");

//m_pViewOf에 연결

m_pViewOf = MapViewOfFile(m_hMapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);

strcpy((char *)m_pViewOf,message);

CloseHandle(m_hMapping);

}

return CallNextHookEx(hHook[3], nCode, wParam, lParam);

}

LRESULT CALLBACK KeyboardFunc (int nCode, WPARAM wParam, LPARAM lParam )

{

CString message;

if ( nCode >= 0 ) {

message.Format("KEYBOARD Key:%d",wParam);

//메모리 맵핑 파일을 오픈한다.

m_hMapping =OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, "ExMessageData");

//m_pViewOf에 연결

m_pViewOf = MapViewOfFile(m_hMapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);

strcpy((char *)m_pViewOf,message);

CloseHandle(m_hMapping);

}

return( CallNextHookEx(hHook[4], nCode, wParam, lParam));

}

LRESULT CALLBACK MouseFunc (int nCode, WPARAM wParam, LPARAM lParam )

{

LPMOUSEHOOKSTRUCT MouseHookParam;

CString message;

if ( nCode >= 0 ) {

MouseHookParam = (MOUSEHOOKSTRUCT *) lParam;

message.Format("Mouse Wnd:%d Point:%d %d wParam=%d",MouseHookParam->hwnd,

MouseHookParam->pt.x,MouseHookParam->pt.y,wParam);

//메모리 맵핑 파일을 오픈한다.

m_hMapping =OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, "ExMessageData");

//m_pViewOf에 연결

m_pViewOf = MapViewOfFile(m_hMapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);

strcpy((char *)m_pViewOf,message);

CloseHandle(m_hMapping);

}

return( CallNextHookEx(hHook[5], nCode, wParam, lParam));

}

(프로그램 소스끝)

본에제에서도 def 파일를 이용하여 외부로 호출되는 프로그램을  EXPORT하여야 합니다. 본 에제의 def파일 내용은 다음과 같습니다.

; MgHookDll.def : Declares the module parameters for the DLL.

LIBRARY      "MgHookDll"

DESCRIPTION  'MgHookDll Windows Dynamic Link Library'

HEAPSIZE  1024

SECTIONS

SHARDATA   Read Write Shared

EXPORTS

InitHooksDll	@1

InstallHook	@2

UnInstallHook	@3

<4>모든 메시지 가로채기 예제 ExSpy

ExSpy 는 spy++ 프로그램같이 윈도우에서 발생하는 모든 메시지를 훅킹하는 예제입니다. ExSpy예제는 대화상자 형태로 제작하였습니다.이예제의 전체 구조는 그림 2-5-3 과 같습니다.

(그림 파일)

(그림 2-5-3)ExSpy 전체 구조

2-5-3의 ⓐ 항목은 ExSpy프로그램을 출력되는 화면입니다. ExSpy는 6개의 메시지 훅킹 설정 체크버튼과 한개의 최상위 윈도우 설정 체크버튼 그리고 메시지 결과가 출력되는 리스트박스가 있습니다. 6개의 메시지 훅킹 설정버튼중에 메시지를 가로채고 싶은 항목이 있으면 해당 버튼을 클릭하면 해당내용 메시지가 그림 2-5-3의 ②항목에 출력이 됩니다.

(1)최상위 윈도우 만들기

현재 윈도우가 언제나 최상위로 위치하고자 할때는 매우 간단하게 할수 있습니다. SetWindowPos함수를 이용하여 다음과 같이 설정하면 최상위 윈도우가 됩니다.

SetWindowPos(&wndTopMost,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);

최상위 윈도상태를 해제하고자 한다면 SetWindowPos함수를 이용하여 다음과 같이 하면 됩니다.

SetWindowPos(&wndNoTopMost,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);

이 두개의 함수는 어떤 설명과 이유가 없는 팁으로 생각하시기 바랍니다. 그냥 □음이렇게 하면 되는구나□ 라고 무조건 최상위 윈도우를 만들때는 앞에서 설명한 방법을 사용하시면 됩니다. 그림 2-5-3의 ③항목을 선택하였을때 최상위 윈도우로 설정하거나 해제하는 함수는 다음과 같습니다.

void CExSpyDlg::OnTopwin()

{

UpdateData(TRUE);

if(m_bTopWin)

SetWindowPos(&wndTopMost,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);

else

SetWindowPos(&wndNoTopMost,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);

}

m_bTopWin 는 그림 2-5-3의 ③항목과 DDX_Text로 연결된 BOOL변수입니다.

UpdateData(TRUE)를 하게 되면 체크가 되어 있을경우 m_bTopWind에는 TRUE가 그렇지 않을경우에는 m_bTopWin에는 FALSE 가 설정됩니다. 이에 따라 최상위 윈도우를 설정하거나 또는 해제 한것입니다.

(2)매모리 맵핑 파일 만들기

MgHookDll 에서는 메모리 맵 파일을 이용하여 정보를 전송했습니다. 따라서 MgHookDll 를 로드하는 ExSpy 프로그램에서는 메모리 맵핑파일을 만듭니다. 이렇게 메모리 맵핑파일을 만든다음 DLL의 InitHooksDll 함수를 호출하고 타이머를 가동시킨 OnInitDialog 함수는 다음과 같습니다.

BOOL CExSpyDlg::OnInitDialog()

{

CDialog::OnInitDialog();

// Set the icon for this dialog.  The framework does this automatically

//  when the application's main window is not a dialog

SetIcon(m_hIcon, TRUE);	// Set big icon

SetIcon(m_hIcon, FALSE);	// Set small icon

//메모리 파일을 만든다.

m_hMapping=CreateFileMapping((HANDLE) 0xFFFFFFFF, NULL,

PAGE_READWRITE, 0,10240, "ExMessageData");

//메로리 파일에 데이터 포인터를 m_pViewOf으로 설정

m_pViewOf = MapViewOfFile(m_hMapping,

FILE_MAP_READ|FILE_MAP_WRITE,

0, 0, 0);

//Dll초기화 함수 호출

InitHooksDll(this->m_hWnd);

//타이머 가동

SetTimer(1,10,NULL);

return TRUE;  // return TRUE  unless you set the focus to a control

}

메모리 맵핑 파일을 만들고 InitHooksDll함수를 호출한다음 SetTimer함수를 가동하였습니다. SetTimer함수를 가동한 이유는 0.1초마다 메모리 파일에 기록된 데이터의 변경이 있으면 이데이터를 리스트박스에 설정하기 위해서입니다. SetTiemer함수에의해서 수행되는 OnTimer함수는 다음과 같습니다.

void CExSpyDlg::OnTimer(UINT nIDEvent)

{

//리스트 박스에 있는 데이터수를 얻고

int num=m_listBox.GetCount();

//400개가 넘으면 데이터를 모두 삭제한다.

if(num>400)

m_listBox.ResetContent();

//메모리 파일에 있는 데이터를 data에 받고

CString data;

data=(LPCSTR)m_pViewOf;

//data 와 바로 전에 받았던 m_strMsg데이터과 같지않으면

//새로운 메시지이므로 리스트박스에 기록한다.

if(data!=m_strMsg)

{

m_strMsg=data;

num=m_listBox.AddString(data);

m_listBox.SetCurSel(num);

}

CDialog::OnTimer(nIDEvent);

}

위와 같이하면 메모리 맵핑 파일에 새롭게 데이터가 기록되면 이내용이 바로 리스트박스에 기록되게 됩니다.

(3)매시지 훅킹 설정

그림 2-5-3의 6개의 훅킹 체크 버튼을 클릭했을때는 체크되어있을경우 InstallHook 함수에 해당 번호를 해제되어 있을경우 UnInstallHook에 해제 번호를 설정합니다. 이 번호는 표3과 같은 값으로 설정합니다. 이내용은 다음과 같습니다.

//WH_CALLWNDPROC

void CExSpyDlg::OnCheck1()

{

UpdateData(TRUE);

if(m_bHook1)

InstallHook(0);

else

UnInstallHook(0);

}

//WH_CBT

void CExSpyDlg::OnCheck2()

{

UpdateData(TRUE);

if(m_bHook2)

InstallHook(1);

else

UnInstallHook(1);

}

//WH_DEBUG

void CExSpyDlg::OnCheck3()

{

UpdateData(TRUE);

if(m_bHook3)

InstallHook(2);

else

UnInstallHook(2);

}

//WH_GETMESSAGE

void CExSpyDlg::OnCheck4()

{

UpdateData(TRUE);

if(m_bHook4)

InstallHook(3);

else

UnInstallHook(3);

}

//WH_KEYBOARD

void CExSpyDlg::OnCheck5()

{

UpdateData(TRUE);

if(m_bHook5)

InstallHook(4);

else

UnInstallHook(4);

}

//WH_MOUSE

void CExSpyDlg::OnCheck6()

{

UpdateData(TRUE);

if(m_bHook6)

InstallHook(5);

else

UnInstallHook(5);

}

m_bHook1 ~ m_bHook6은 그림 2-5-3의 ①항목의 6개 체크버튼과 DDX_Text로 연결된 BOOL 변수입니다. 이값에 의해서 현재 체크상태인가 체크되지 않은 상태인가를 확인하고 InstallHook함수와 UnInstallHook함수를 실행한것입니다.

(4)매시지 훅킹 해제

윈도우가 종료 될때는 메시지 훅킹을 종료 합니다. 이내용은 다음과 같습니다.

void CExSpyDlg::OnDestroy()

{

if(m_bHook1)

UnInstallHook(0);

if(m_bHook2)

UnInstallHook(1);

if(m_bHook3)

UnInstallHook(2);

if(m_bHook4)

UnInstallHook(3);

if(m_bHook5)

UnInstallHook(4);

if(m_bHook6)

UnInstallHook(5);

CDialog::OnDestroy();

CloseHandle(m_hMapping);//메모리 맵핑 해제

}

m_bHook1 ~m_bHook6까지를 체크하여 해당값이 1일경우에는 현재 메시지 훅킹이 가동중이므로 훅킹을 해제합니다. 이렇게 한후 설정하였던 메모리 맵핑을 해제합니다.

지금까지 ExSpy의 프로그램의 전반적인 제작 방법을 설명하였습니다. 본항목은 고급자에 해당하는 항목이므로 기본적인 방법은 생략하였습니다. 나머지부분은 제공되는 CD의 예제를 참조하시기 바랍니다.