출처: http://www.jiniya.net/lecture/techbox/unicode1.html

이렇게 해서 다른 언어OS에서 한글이 깨진다면~~
C:\windows\fonts 폴더에 한글 폰트를 넣어보세요~~


Visual C++ 6.0 으로 프로젝트를 생성하면 기본적으로 비유니코드 버전으로 프로젝트가 생성된다.
이 프로젝트에 유니코드 빌드 환경을 추가하는 방법은 다음과 같다.

1. 유니코드 빌드 환경 추가하기
[Build] - [Configurations] 메뉴항목을 선택한다.


위 이미지와 같은 화면에서 Win32 Debug 또는 Win32 Release 항목을 선택한 후 오른쪽의 Add... 버튼을 선택한다.


Configuration 항목의 값을 아래 그림에서와 같이 Debug_Unicode 로 입력한다.


Release_Unicode 환경도 위 과정과 같이 추가한다.

2. 유니코드 빌드 환경 설정
[Project] - [Settings] 메뉴항목을 선택하여 C/C++ 탭으로 이동한다.


Preprocessor definitions 항목의 _MBCS 를 지우고 _UNICODE 를 입력한다.

Link 탭으로 이동한 후 Category 콤보박스에서 Output 을 선택한다.
Entry-point symbol 항목값으로 wWinMainCRTStartup 를 입력한다.


 

AND

출처: http://kukuta.tistory.com/25

/**
  예전에 한번 보긴 했었지만, 거의 C style의 type casting만을 사용하다 보니 내 머리 속에서 점점 잊혀져 가고 있다.
  C style의 type casting이 사용하기도 쉽고, 자유도가 높긴 하지만 여러가지 오류의 소지도 있고, 코드도 지저분하게 만든느 경향이 있으므로 앞으로는 C++ style의 type casting을 사용 하기로 결심. 이렇게 다시 한번 정리 한다.
*/

* static_cast
 static_cast는 C에서의 type casting의 제한된 버젼이라고 생각하면 된다. 다만 C와의 차이점은 상속 트리 내에서의 포인터 형 변환만이 가능하다는 것이다.
 즉, 상속 트리 내에서 부모나 자식간으로의 포인터 casting은 가능하지만 전혀 관계가 없는 type으로 casting 하고자 하면 컴파일 타임에 오류가 난다.

class A {
};

class B : public A {
};

class C {
};

A* a = new A;

// B는 A의 자식이므로 casting이 가능하다. 또한 그 반대로도 가능하다.
B* b = static_cast<B*>(a);

// 컴파일 오류
C* c = static_cast<C*>(a);



* const_cast

 const_cast는 서로 다른 형간의 변환을 수행하지는 못하지만 대신 const가 아니었던것을 const로 만들거나, const였던것을 non-const로 만들 수 있다. 하지만 일반적으로 non-const type을 const로 바꾸는 경우는 거의 없으며 그리 제한적인 변경이 아니므로 자동적인 type casting이 일어난다. 하지만 const를 non-const type으로 변경 하는 것은 명시적으로 지정 해 주어야만 한다.

class A {
};

class B : public A{
};

int main() {

   A* a = new A;
   // 컴파일 오류 invalid const_cast from type `A*' to type `B*'
   B* b = const_cast<B*>(a);

    const int ci = 10;
    int* pi = const_cast<int*>(&ci);
    *pi = 1000;

    std::cout << pi << " " << *pi << std::endl;
    std::cout << &ci << " " <<  ci << std::endl;
}

※ 신기한 것은 같은 주소 공간에 서로 다른 값을 가지고 있다는 것이다. 이것을 어떻게 설명 해야 하는 것인고..

* reinterpret_cast

C style type casting과 똑같은 위력을 발휘한다. type, 상속 관계에 관계 없이 어떤 내장형, 어떤 포인터형이든지 다 casting이 가능하다.
※ 각종 예외상황들은 해당 컴파일러에 따라 다르게 처리된다.

* dynamic_cast
다른 casting과 dynamic_cast의 차이점은 다른 여타 cast 연산자들은 컴파일 타임에 컴파일러에 의해 평가되며, 정상적인 컴파일 또는 오류로 결과가 나타난다. 하지만 dynamic_cast는 casting의 가능 여부를 런타임시에 평가하며 내장 테이터 형을 제외한 포인터나 참조에만 이용 할 수 있다.

dynamic_cast는 static_cast와 달리 두 포인터가 동일한 상속 트리에 있는지 확인하지 않으며 포인터가 참조하는 개체의 실제 형을 확인하고 변환이 가능한지를 확인한다. 만일 가능하다면 다중 상속을 처리하기 위한 오프셋 계산까지 마친 새로운 포인터를 리턴하고, 불가능 하다면 NULL 포인터를 리턴한다.

보다 자세한 설명을 위해 아래의 예를 살펴 보도록 하자 :

class Parent
{
public :
    virtual void polymorphic() = 0;
};

class DeriveA : public Parent
{
public :
    virtual void polymorphic() {
    }
};

class DeriveB : public Parent
{
public :
    virtual void polymorphic() {
    }
};

int main(int argc, char** argv)
{
    Parent* p =  new DeriveA;
    DeriveB* db = dynamic_cast<DeriveB*>(p);
    if(NULL == db)
    {
        std::cout << "It is not a instance of DeriveB" << std::endl;
    }
    else
    {
        std::cout << "It is a instance of DeriveB" << std::endl;
    }

    DeriveA* da = dynamic_cast<DeriveA*>(p);
    if(NULL == da)
    {
        std::cout << "It is not a instance of DeriveA" << std::endl;
    }
    else
    {
        std::cout << "It is a instance of DeriveA" << std::endl;
    }

    return 0;
}

※ dynamic_cast를 사용하기 위해서는 컴파일러의 RTTI(real time type infomation) 옵션을 켜놓아야 한다.

위의 코드를 컴파일 하여 결과를 살펴 보면 :

It is not a instance of DeriveB
It is a instance of DeriveA

와 같이 나올 것이다.

요약을 하자면,  DeriveA의 인스턴스는 다형성을 이용하기 위해 부모 클래스의 포인터를 이용하고 있고, dynamic_cast는 런타임시에 어떤 자식 클래스의 인스턴스인지 부모 클래스의 포인터를 통해서 알아 낼 수 있는 것이다.

다이나믹 캐스트에 대한 보다 자세한 설명은 아래 링크 참조 :
http://www.cprogramming.com/reference/typecasting/dynamiccast.html

AND

출처: http://blog.daum.net/bwangel/2


novtable 애트리뷰트의 의미는 순수 가상함수만 있는 추상 클래스에서는 가상 함수 테이블이 포함될 필요가 없다는 이야기. 결국 메모리 공간을 가질 필요가 없고 생성자나 소멸자의 역활 또한 막는다.
AND

출처: http://synch3d.com/wiki/moin/moin.cgi/C_2b_2b_20_bf_ac_bb_ea_c0_da_20_bf_c0_b9_f6_b7_ce_b5_f9

C++ 연산자 오버로딩 부분 (출처 : winapi.co.kr)

연산자 오버로딩

연산자 오버로딩은 이미 존재하는 연산자의 기능을 조금 바꾸는 것이지 아예 새로운 연산자를 만드는 것은 아니다. 원래 C++ 언어가 제공하는 기존 연산자만 오버로딩의 대상이며 C++이 제공하지 않는 연산자를 임의로 만들 수는 없다.
class Time 
{ 
     .... 
    const Time operator +(const Time &T) const { 
          .... 
     } 
}; 
... 
C=A.operator +(B);            // C=A+B; 와 같다. 

클래스의 연산자 함수를 정의하는 방법은 다음 두가지가 있다.
① 클래스의 멤버 함수로 작성한다.
② 전역 함수로 작성한다.

리턴 타입
연산의 결과로 어떤 타입을 리턴할 것인가는 연산자별로 다르다.
(정수+실수의 리턴?, []연산자의 리턴은 멤버중 하나..등등)

const Complex operator +(const Complex &T) const { 
     Complex R(real+T.real, image+T.image); 
     return R; 
} 
const Complex operator +(const Complex &T) const { 
     return Complex(real+T.real, image+T.image); 
} 
이 코드는 앞서 만든 코드보다 훨씬 더 짧고 간략해 보일 뿐만 아니라 컴파일러의 리턴값 최적화(
[BadWikiTag]Retrun Value Optimization) 기능의 도움도 받을 수 있어 훨씬 더 유리하다. 제대로 만든 컴파일러는 호출원의 대입되는 좌변에 대해 곧바로 생성자를 호출하며 불필요한 임시 객체를 만들지 않음으로써 훨씬 더 작고 빠른 코드를 생성한다.

A+B => A.operator +(B) or operator +(A,B)

    멤버 연산자 함수로 만드는 것이 더 깔끔하다. 다만 불가피하게 전역으로만 만들어야 하는 경우도 있고 =, ( ), [ ], -> 연산자들은 반드시 멤버 연산자 함수로만 만들어야 한다.
enum origin { EAST, WEST, SOUTH, NORTH }; 
origin &operator++(origin &o) 
{ 
     if (o == NORTH) { 
          o = EAST; 
     } else { 
          o=origin(o+1); 
     } 
     return o; 
} 

객체와 기본형의 연산

class Time 
{ 
     // friend const Time operator +(int s, const Time &T); // int+T friend 생략 가능하다.. 하지만 여전히 Time 클래스와 연결되어있으므로 friend로 묶여 있어도 무방. 
     const Time operator +(int s) const;                 // T+int 
     .... 
}; 
const Time Time::operator +(int s) const { // 멤버 
     .... 
} 
 
const Time operator +(int s, const Time &T) // friend전역 
{ 
     return T+s; 
} 

이미 존재하는 연산자 중에도 오버로딩의 대상이 아닌 것들이 있다. 다음 연산자들은 기능을 변경할 수 없다. 즉, 오버로딩의 대상이 아니다.

.(구조체 멤버 연산자)
::(범위 연산자)
?:(삼항 조건 연산자)
.*( 멤버 포인터 연산자)
sizeof
typeid
static_cast
dynamic_cast
const_cast
reinterpret_cast
new
delete

&&, || 논리 연산자의 경우 쇼트 서키트 기능이 동작하도록 설계되어 있지만 오버로딩되면 쇼트 서키트는 더 이상 동작하지 않는다. 문법적으로는 허용된다 하더라도 그 효과를 예측하기 어려우므로 가급적이면 이 연산자들은 오버로딩하지 말아야 한다. 사실 이 연산자들이 오버로딩되어야 하는 경우도 거의 없는 편이다.

A.operator +(B)
operator +(A,B)
둘다 있다면 어떤 함수가 실행될까?
dev-c++기준 컴파일 오류
ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for the second:

    const A operator+(const A&, const A A::operator+(B) const
int operator +(int a, int b)
이 함수는 정수형의 덧셈 연산을 완전히 새로 정의하는데 정수형의 덧셈은 언어의 가장 기본적인 동작이고 CPU의 원자적인 연산이기 때문에 이 동작이 바뀌게 되면 파급효과가 너무 엄청날 것이다. 그래서 컴파일러는 기본형에 대한 연산자 오버로딩은 거부하며 "최소한 하나의 피연산자는 클래스 타입이어야 한다"는 에러 메시지를 출력한다.

관계연산자

class Time 
{ 
        ... 
        bool operator ==(const Time &T) const { 
                return (hour == T.hour && min == T.min && sec == T.sec); 
        } 
        bool operator !=(const Time &T) const { 
                return !(*this == T); 
        } 
        int operator >(const Time &T) const { 
                return (hour*3600+min*60+sec > T.hour*3600+T.min*60+T.sec); 
        } 
        bool operator >=(const Time &T) const { 
                return (*this == T || *this > T); 
        } 
        bool operator <(const Time &T) const { 
                return !(*this >= T); 
        } 
        bool operator <=(const Time &T) const { 
                return !(*this > T); 
        } 
} 

증감연산자

++++A가 실행되어 A를 한 번 더 증가시키기 위해 이 연산자의 리턴 타입이 레퍼런스여야 한다. A++은 반대로 값으로 리턴.
class Time 
{ 
// time++ 
     Time &operator ++() {  
          sec++; 
          min += sec/60; 
          sec %= 60; 
          hour += min/60; 
          min %= 60; 
          return *this; 
     } 
 
// ++time 
     const Time operator ++(int dummy) { 
          Time R = *this; 
          ++*this; 
          return R; 
     } 
} 

대입 연산자

리턴형은 자신의 레퍼런스
class Person 
{ 
     .... 
    Person &operator =(const Person &Other) { 
        if (this != &Other) { 
           delete [] Name; 
           Name=new char[strlen(Other.Name)+1]; 
           strcpy(Name,Other.Name); 
           Age=Other.Age; 
        } 
        return *this; 
    } 
}; 

Person A=B; // 복사 생성자 호출

Person A,B;
A = B; // 대입 연산자 호출

Person Boy; // 여기서 디폴트 생성자.
Person Young=Boy; // 여기서 복사 생성자.. 하지만 Boy는 디폴트 생성자로 만들어져 있는데 deep 카피 시도시 문제가 생김.. 반드시 복사 생성자에서는 인자로 넘어오는 타입에 대한 NULL확인 필요.

복사 생성자
: 초기화될 때 동적할당일 경우 NULL체크를 한다.
대입 연산자
: 사용하던 메모리를 해제하고 NULL체크후 대입받는 객체에 맞게 다시 할당한다.

생성자, 소멸자, 복사생성자, 대입연산자 이중 하나라도 빠지거나 생략되면 제대로 동작하지 않는다. 추가로 상속될 경우를 대비해 소멸자는 virtual 선언한다.

복합 대입 연산자

operator + 연산자를 오버로딩 했다고 해서 operator += 까지 같이 정의되는 것이 아니다.
... 
Time &operator +=(int s) { 
... 

<< 연산자

C++ 표준이 출력 스트림으로 사용한다.
ostream& operator<<(const char *);
ostream& operator<<(char);
ostream& operator<<(short);
ostream& operator<<(int);
ostream& operator<<(long);
ostream& operator<<(float);
ostream& operator<<(double);
....
class Time 
{ 
     friend ostream &operator <<(ostream &c, const Time &T); 
     friend ostream &operator <<(ostream &c, const Time *pT); 
     ... 
}; 
ostream &operator <<(ostream &c, const Time &T) 
{ 
     c << T.hour << "시" << T.min << "분" << T.sec << "초"; 
     return c; 
} 
ostream &operator <<(ostream &c, const Time *pT) 
{ 
     c << *pT; 
     return c; 
} 

[] 연산자

기존의 배열에서 쓰이는 [] 연산자와는 구별이 된다.
DArray a[5]; 
a[4][0] = 10; // [4]는 a배열의 4번째 항목을 반환하고 클래스 내부에 정의된 [0] 항목을 반환한다. 
 
const DArray constA; 
printf("%d", constA[0]); // operator[]이 아닌 const ~ operator[] (~) const을 호출한다. 반드시 반환과 내용에 const가 있어야 컴파일 허용된다. 
class DArray 
{ 
     .... 
     ELETYPE &operator [](int idx) { // 기본 [] 연산자 
          return ar[idx]; 
     } 
     const ELETYPE &operator [](int idx) const { // const 형 인스턴스에 쓰이는  
 
[] 연산자 
     return ar[idx]; 
         } 
}; 

멤버 참조 연산자

.연산자는 오버로딩 불가하나 ->은 가능.
이 연산자의 리턴 타입은 클래스나 구조체의 포인터로 고정되어 있다. 보통 클래스에 포함된 다른 클래스 객체나 구조체의 번지를 리턴하여 포함된 객체의 멤버를 읽는 용도로 사용된다. 이 연산자를 오버로딩하면 포함 객체의 멤버를 마치 자신의 멤버처럼 액세스할 수 있다.

-> 연산자는 보통 스마트 포인터라 불리는 포인터를 흉내내는 클래스를 만들기 위해 사용되며 포인터의 유효성 점검이나 사용 카운트 유지 기능을 구현한다. 어떤 객체를 래핑하는 클래스를 만들 때 래핑한 객체가 래핑된 객체인 것처럼 동작해야 하므로 -> 연산자로 래핑된 객체의 멤버를 바로 액세스할 수 있어야 하는 것이다.

class Book 
{ 
private: 
        Author Writer; 
        ... 
public: 
        Author *operator->() {  
                return &Writer;  
        } 
} 

() 연산자

클래스는 정보와 동작을 동시에 가질 수 있으므로 세상의 모든 사물을 다 흉내낼 수 있다. 포인터를 래핑할 수도 있고 함수를 래핑할 수도 있는데 함수를 그대로 흉내내는 클래스를 정의하고 싶을 때 이 연산자를 재정의한다. () 연산자를 정의하는 클래스를 함수 객체(Functor)라고 하는데 C++ 표준 라이브러리에서 일반화된 알고리즘의 동작에 변화를 주기 위해 흔히 사용된다.
class ScoreManager 
{ 
private: 
     // 성적을 저장하는 여러 가지 멤버 변수들 
     int ar[3][5][10][4]; 
public: 
     ScoreManager() { memset(ar,0,sizeof(ar)); } 
     int &operator()(int Grade,int Class,int StNum,const char *Subj) { 
          return ar[Grade][Class][StNum][0]; 
     } 
     // [] 연산자와 같이 const형을 지원하기 위해서 
     const int &operator()(int Grade,int Class,int StNum,const char *Subj) const { 
          return ar[Grade][Class][StNum][0]; 
     } 
}; 
 
void main() 
{ 
     ScoreManager SM; 
 
     printf("1학년 2반 3번 학생의 국어 성적 = %d\n",SM(1,2,3,"국어")); 
     SM(2,3,4,"산수")=99; 
} 

new, delete 연산자

객체를 힙에 할당하는 new 연산자는 두 가지 동작을 하는데 운영체제의 힙 관리 함수를 호출하여 요청한만큼 메모리를 할당하고 이 할당된 메모리에 대해 객체의 생성자를 호출하여 초기화한다. new가 생성자를 호출하는 것은 언어의 고유한 기능이므로 사용자가 생성자 호출을 금지한다거나 할 수 없지만 객체를 위한 메모리를 할당하는 방식은 원하는대로 변경할 수 있다.
#include <stdio.h> 
#include <stdlib.h> 
 
class AA 
{ 
public: 
        AA() 
        { 
                printf("생성자 호출\n"); 
        } 
        ~AA() 
        { 
                printf("소멸자 호출\n"); 
        } 
}; 
 
void *operator new(size_t t) 
{ 
        printf("사용자용 new\n"); 
        return malloc(t); 
} 
 
 
void operator delete(void *p) 
{ 
        printf("사용자용 delete\n"); 
        free(p); 
} 
 
 
int main() 
{ 
        AA *a = new AA(); 
        delete a; 
        system("PAUSE"); 
        return 0; 
} 
 
// 출력 결과 
/* 
사용자용 new 
생성자 호출 
소멸자 호출 
사용자용 delete 
계속하려면 아무 키나 누르십시오 . . . 
*/ 
AND

출처: http://kin.naver.com/detail/detail.php?d1id=1&dir_id=10104&eid=Yyu+UAy6QQtcJthWPuhYwSbTjtn1vgbk&qb=7Jew7IKw7J6QIG9wZXJhdG9y&enc=utf8&pid=fAMLzloi5U4sstUs4iwsss--143245&sid=SiOHr8puI0oAACuvcrw


어떤 코드를 분석하는 중에


특정 클래스 안에 아래와 같은 코드가 있었습니다.


operator  HWND() const { return hWnd; }


hWnd란 이 클래스의 내부 멤버로 WinMain에서 핸들을 받아오는것이고요


위의 연산자 오버로딩 함수는 그 클래스의 멤버함수입니다.


저걸 어떻게 해석하면 좋을까요?



어떻게 생각하면 연산자 오버로딩이 아니라


인자가 없는 HWND라는 함수가 반환형을 operator 형으로 갖는것 같기도 하고



연산자 오버로딩으로 HWND를 재정의(??) 한 것 같기도 하고...



그렇지만 연산자가 반환형이 될 수는 없겠지요?


operator는 어디까지나 연산자 오버로딩에 대한 키워드지 데이터 형이 아니니까요.



그럼 연산자 오버로딩이란 소린데 위의 구문에서 보면


반환형도 지정되 있지 않고(형을 지정하지 않으면 기본 int이니


int로 처리될지도 모르겠지만...)


뒤에 연산자를 지정하지도 않았습니다. 설마 HWND() 전체가 연산자인건 아니겠고요.


연산자 오버로딩에 대해 잘 아시는 분은 꼭 알려주시기 바랍니다.

신고

의견 쓰기

질문자 채택된 경우, 추가 답변 등록이 불가합니다.

질문자 선택

re: 연산자 오버로딩에서 operator가 반환형이 될수도 있나요?

pastel1024

답변채택률 85.0%

2006.07.19 20:20

질문자 인사 아하... 그럼 형변환연산을 오버로딩한 것이군요? 이야... 신기하네요. 형변환 연산도 오버로딩이 된다니... 감사합니다. ^^

형변환 연산자입니다.
어떤 데이터형을 다른 데이터형으로 변환시켜주는 방법을 정의해 줍니다.

예로 double이 변형되어 int로 저장되는 것도 형변환방법에 따라
변경되어야 제대로 변경이 되죠. 물론 이정도는 컴파일러에서 기본으로
되는 것이지만요

예로 서로 다른 두 종류의 클래스
classA A;
classB B;
가 있을 때

A = B;

또는

void func(classA in);
...
func(classB);
와 같이 알아서 형변환이 되어서 사용되어야 하는 경우에 정의해 주면
편리하게 사용할 수 있습니다.

아래는 실제 코드로 만든 예제입니다.
int값을 데이터로 가지는 Point라는 클래스와
double값을 데이터로 가지는 Position이라는 두 클래스가 있을때

Position => Point로 의 형변환을 정의해 준 것입니다.

----------------------------------------------------------------------

#include
using namespace std;

class Point{
public:
 int x; int y;
 Point() {};
 Point(int in_x, int in_y) : x(in_x), y(in_y) {};
};

class Position{
public:
double x;
 double y;
 Position(double in_x, double in_y) : x(in_x), y(in_y) {};
 operator Point() const { return Point((int)x, (int)y); };
};

int main(){
 Position A(3.7, 12.8);
 Point B;
 B = A;
 cout << B.x << endl;
 cout << B.y << endl;

 return 0;
}
AND

출처: http://blog.daum.net/ssailer/6326875

Printf format

C에서의 표준입출력 고급옵션

 

printf("출력변환문자",출력값~)

 

1) 출력변환문자 옵션    %[-][0][전체자리폭].[정밀도][변환문자]

 

   [%] 변환하기 위해 필요하다.

   [-]   문자가 왼쪽에 채워지고 오른쪽에 공백.

            (일반: 오른쪽에 채워지고 왼쪽에 공백)

   [0]  오른쪽에 문자를 쓰고 왼쪽에는 0 채워진다.

 

   [전체자리폭] 출력하는 문자나 정수 등의 전체 자릿수

   [정밀도]        소수점 이후의 자리수

   [변환문자]

      %c         : char

      %hu/uh: unsigned short int

      %lu/ul   : unsigned long int  

      %d         : int 10진수

      %u         : unsigned int   

      %o         : unsigned int 8진수

      %x/X    : unsigned int 16진수

      %ld        : long 10진수

      %lo        : long 8진수

      %lx        : long 16진수

      %lu        : unsigned long 10진수

      %f         : float

      %lf        : double

      %s         : 문자열

      %e/E    : 지수형(소문자,대문자)

      %p        : 포인터

      %%      : %부호

AND

출처: http://www.codeproject.com/KB/system/keyboard.aspx?display=Print

Introduction

Simulation of a keyboard input is a well known concept for those who are all familiar with Visual Basic. SendKeys() in Visual Basic does all the things, if you want to do anything without keyboard. But what is in SendKeys() function? What does it do? Can we do such a thing with Visual C++? This article is the answer. I think this will be useful for beginners who are all just trying to do something different with VC++. Let us get into the steps…

Function Syntax

void keybd_event(BYTE bVirtualKey, BYTE bScanCode, 
                         DWORD dwFlags, DWORD dwExtraInfo);
  • bVirtualKey //Virtual Keycode of keys. E.g., VK_RETURN, VK_TAB…
  • bScanCode //Scan Code value of keys. E.g., 0xb8 for “Left Alt” key.
  • dwFlags //Flag that is set for key state. E.g., KEYEVENTF_KEYUP.
  • dwExtraInfo //32-bit extra information about keystroke.

Function Details:

  • bVirtualKey

    Virtual keycode that has to be send as key input. The following are the available predefined virtual key codes:

    VK_NUMPAD7 0x67 VK_BACK 0x08
    VK_NUMPAD8 0x68 VK_TAB 0x09
    VK_NUMPAD9 0x69 VK_RETURN 0x0D
    VK_MULTIPLY 0x6A VK_SHIFT 0x10
    VK_ADD 0x6B VK_CONTROL 0x11
    VK_SEPARATOR 0x6C VK_MENU 0x12
    VK_SUBTRACT 0x6D VK_PAUSE 0x13
    VK_DECIMAL 0x6E VK_CAPITAL 0x14
    VK_DIVIDE 0x6F VK_ESCAPE 0x1B
    VK_F1 0x70 VK_SPACE 0x20
    VK_F2 0x71 VK_END 0x23
    VK_F3 0x72 VK_HOME 0x24
    VK_F4 0x73 VK_LEFT 0x25
    VK_F5 0x74 VK_UP 0x26
    VK_F6 0x75 VK_RIGHT 0x27
    VK_F7 0x76 VK_DOWN 0x28
    VK_F8 0x77 VK_PRINT 0x2A
    VK_F9 0x78 VK_SNAPSHOT 0x2C
    VK_F10 0x79 VK_INSERT 0x2D
    VK_F11 0x7A VK_DELETE 0x2E
    VK_F12 0x7B VK_LWIN 0x5B
    VK_NUMLOCK 0x90 VK_RWIN 0x5C
    VK_SCROLL 0x91 VK_NUMPAD0 0x60
    VK_LSHIFT 0xA0 VK_NUMPAD1 0x61
    VK_RSHIFT 0xA1 VK_NUMPAD2 0x62
    VK_LCONTROL 0xA2 VK_NUMPAD3 0x63
    VK_RCONTROL 0xA3 VK_NUMPAD4 0x64
    VK_LMENU 0xA4 VK_NUMPAD5 0x65
    VK_RMENU 0xA5 VK_NUMPAD6 0x66

    Character key can be converted into virtual key using VkKeyScan(TCHAR ch) function.

  • bScanCode

    Scan code is the hardware key code for the key (make and break codes). The following are the available scan codes (break code will be used in this parameter).

  • dwFlags

    A set of flag bits that specify various aspects of function operation. An application can use any combination of the following predefined constant values to set the flags.

    Value Meaning
    KEYEVENTF_EXTENDEDKEY If specified, the scan code was preceded by a prefix byte having the value 0xE0 (224).
    KEYEVENTF_KEYUP If specified, the key is being released. If not specified, the key is being depressed.
  • dwExtraInfo

    32-bit extra information along with the keyboard input.

Example Code

// Simulating a Alt+Tab keystroke
keybd_event(VK_MENU,0xb8,0 , 0); //Alt Press
keybd_event(VK_TAB,0x8f,0 , 0); // Tab Press
keybd_event(VK_TAB,0x8f, KEYEVENTF_KEYUP,0); // Tab Release
keybd_event(VK_MENU,0xb8,KEYEVENTF_KEYUP,0); // Alt Release

// Simulating a Ctrl+A keystroke
keybd_event(VK_CONTROL,0x9d,0 , 0); // Ctrl Press
keybd_event(VkKeyScan(‘A’),0x9e,0 , 0); // ‘A’ Press
keybd_event(VkKeyScan(‘A’),0x9e, KEYEVENTF_KEYUP,0); // ‘A’ Release
keybd_event(VK_CONTROL,0x9d,KEYEVENTF_KEYUP,0); // Ctrl Release

Conclusion

This article may not be that much detailed. None of the articles can satisfy one's expectations. But, each article should be a seed for your technical growth. Thus, I believe that this would be a seed. Thank you all.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Naren Neelamegam


E-Mail : loveablevirus@yahoo.com
Web : http://www.narenn.com
Occupation: Software Developer (Senior)
Location: India India

Discussions and Feedback

Comment 31 messages have been posted for this article. Visit http://www.codeproject.com/KB/system/keyboard.aspx to post and view comments on this article, or click here to get a print view with messages.

PermaLink | Privacy | Terms of Use
Last Updated: 4 Jun 2004
Editor: Smitha Vijayan

AND

출처: http://blog.naver.com/egeroo?Redirect=Log&logNo=60002934064

-----------------------------------------------------------------
Hook.
-----------------------------------------------------------------
윈도우의 시스템 메세지 핸들링 메커니즘안에서 애플리케이션이
서브루틴을 설치함으로써 타겟 윈도우 프로시져에 도착하기 전에
메세지를 감시할수 있다.


-> 후킹은 시스템을 전반적으로 느리게 만드므로 필요시 사용후
곧바로 지워야 한다.

Hook Chanins

여러가지 후킹 타입을 지원하는데, 여러 메세지 핸들링 메카니즘에
대한것들로 예를 들어 애플리케이션이 WH_MOUSE 후킹을 쓰면
마우스 메세지 후킹을 할 수 있다.
시스템은 각 후킹 타입에 대해 따로 분리된 후크 체인을 갖고 있다.
후크체인은 특별히 애플리케이션에서 후크 프로시져로 불리는
콜백 함수들의 포인터들의 리스트이다.
만일 특별한 타입의 후크에 연관된 메세지가 발생하면 시스템은
그 메세지를 후크체인에 있는 각 후크 프로시져들에게 통보한다.
후크 프로시져의 호출은 수반한 후크의 타입에 의존된다. 몇 종류의 후크에
대한 후크 프로시져는 오직 메세지를 감시한다. 다른것들은 메세지를
수정할수 있고 체인에서의 진행을 멈추게 할수있으며 다음 후크프로시져나
목적 윈도우로가는것을 막을수도 있다.

Hook Procedures
각 후크종류의 이점을 얻기 위해서 개발자는 후크 프로시져를 제공받으며
SetWindowsHookEx 함수를 이용해 원하는 후크 체인에 설치할 수 있다.
후크 프로시져는 반드시 다음 문법을 따라야 한다.

LRESULT CALLBACK HookProc{ int nCode, WPARAM wParam, LPARAM lParam );

HookProc은 애플리케이션 마다 정의한 후크 이름으로 바꿀수 있다.

nCode파라미터는 후크 프로시져가 행해야 할 행동에 대한 코드이다.
후크 코드 값은 후크 타입에 의존하는데, 각 타입은 자신의 후크코드 집합을
갖게 된다. wParam과 lParam인자의 값은 후크 코드에 의존되지만 보통
send나 post를 통해 만들어진 메세지들에 대한 정보를 갖고 있다.

SetWindowsHookEx함수는 항상 후크 체인의 시작점에 후크 프로시져를 설치한다.
어떤 후크에 대해 감시하는 이벤트가 발생했을때 시스템은 그 후크에 관련된
후크 체인의 처음 프로시져를 호출하게 된다. 체인안의 각 후크 프로시져는
이 이벤트를 다음 프로시져를 넘겨줄것인가를 결정할 수 있다.
후크 프로시져는 다음 프로시져로 이 이벤트를 넘길수있는데 이때
CallNextHookEx함수를 호출함으로 할 수 있다.

어떤 후크 타입에서의 후크 프로시져는 오직 메세지를 감시할수 있음을 주의해라
시스템이 각 후크 프로시져에게 메세지를 넘길때,
각 후크 프로시져를 CallNextHookEx를 호출

후크프로시져는 시스템안 모든 스레드를 감시하는 전역적으로 쓰일수 있으며
특정 스레드를 감시할수 있으며 단지 하나의 스레드를 위한 메세지를 감시
할수 있다. 전역적인 후크( global hook )프로시져는 어떤 애플리케이션의
문맥(context)중에서 호출될수있다. 그래서 프로시져는 반드시 분리된 DLL
모듈안에 있어야 한다. 한 스레드 후크 프로시져는 오직 연관된 스레드의
문맥안에 호출된다. 만일 애플리케이션이 그 자신의 스레드들의 하나에
대해서 후크 프로시져를 설치한다면, 후크 프로시져는 애플리케이션의 여분 코드
로써 같은 모듈안에 있거나 DLL 안에 둘중 하나에 있을수 있다. 만일
애플리케이션이 다른 애플리케이션의 스레드를 위해서 후크프로시져를
설치하려면, 그 프로시져는 반드시 DLL안에 있어야 한다. 자세한 내용은
DLL 부분을 참조 하라.

전역적 후크는 오직 디버깅을 목적으로 써라. 아니라면 사용하지 말아라.
전역적 후크는 시스템의 성능을 저하시키며, 다른 애플리케이션이 같은
타입의 전역 후크를 사용한다면 충돌을 일으킬수 있다.

Hook Types
각 후크 타입들은 애플리케이션이 시스템 메세지 핸들링 메커니즘에서
감시할수 있는 것들이다.

WH_CALLWNDPROC and WH_CALLWNDPROCRET Hooks
WH_CALLWNDPROC과 WH_CALLWNDPRORET 후크는 SendMessage함수를 이용해
윈도우로 보낸 메세지를 감시할수 있게 한다. 시스템은 WH_CALLWNDPROC후크
프로시져를 목적지 윈도우 프로시져로 전달되기 전에 호출한다. 그리고
윈도우 프로시져가 메세지를 처리한 후 시스템은 WH_CALLWNDPROCRET 후크
프로시져를 호출한다.

WH_CALLWNDPROCRET 후크는 CWPRETSTRUCT구조체의 주소를 건내주는데
이는 메세지를 처리하려는 윈도우로 부터 반환된 값과 그 메세지와
연관된 메세지 인자들도 갖고 있다. 서브클래싱된 윈도우는 메세지 집합
처리안에서 작동하지 않는다.
CallWndProc과 CallWndRetProc함수를 참조

WH_CBT Hook
시스템은 WH_CBT후크 프로시져를 윈도우가 활성화,생성,파괴,최소화,
최대화,이동,크기변경을 하기 전에 호출한다. 즉 시스템명령이 완성되기
전, 시스템 메세지 큐에서 마우스나 키보드 이벤트를 지우기 전 ,
인풋포커스를 세팅하기 전, 시스템 메세지큐를 동기화하기 전.
후크 프로시져에서 반환하는 값은 시스템이 그들 명령들을 허락하거나
금지하는가를 결정하게 된다. WH_CBT후크는 컴퓨터 기반 트레이닝
(Computer-Based Traning) 애플리케이션에서 주요 쓰인다.
CBTProc 함수를 참조


WH_DEBUG Hook
시스템은 어떤 다른 후크와 연관되어 후크 프로시져가 호출되기 전에
WH_DEBUG 후크 프로시져를 호출한다. 개발자는 시스템이 다른 타입의
후크와 연관된 후크 프로시져를 호출할것 인지를 결정할 수 있다.
DebugProc 함수를 참조

WH_FOREGROUNDIDLE Hook
이 후크는 개발자가 백그라운드 스레드가 아이들 상태 시간에 있을때
낮은 수행권한으로 후크 프로시져가 수행되게 할 수 있다.
ForegroundIdleProc 함수를 참조

WH_GETMESSAGE Hook
이 후크는 애플리케이션이 GetMessage나 PeekMessage를 통해 돌려지는
메세지를 감시할 수 있게 해준다. 개발자는 WH_GETMESSAGE 후크를
씀으로써 마우스나 키보드 입력이나 메세지큐로 들어온 다른
메세지들을 감시할수 있다.
GetMsgProc 함수를 참조

WH_JOURNALPLAYBACK Hook
이 후크는 애플리케이션이 시스템 메세지 큐로 메세지를 추가시킬수
있게 한다. 개발자는 이 후크를 이용해 마우스나 키보드 이벤트같은것이
이전에 레코드된것(녹화한것)을 플레이시킬수 있다. 일반적인 마우스나
키보드 입력은 WH_JOURNALPLAYBACK 후크가 설치된 동안에서는 불가능하다.
WH_JOURNALPLAYBACK 후크는 전역적인 후크이다.  어떤 특정  스레드에
대한 후크를 쓸수 없다.
WH_JOURNALPLAYBACK 후크는 타임아웃 값을 반환한다. 이값은 시스템이
플레이백 후크로 부터 현재 메세지가 처리되기 전까지 얼마나 많은
밀리초를 기다렸는가를 나타내는다.
JournalPlaybackProc 함수를 참조

WH_JOURNALRECORD Hook
이 후크는 개발자가 입력된 이벤트들을 녹화(레코드)하거나 감시할수
있게 한다. 일반적으로 개발자는 이 후크를 이용해 일련의 마우스나
키보드 이벤트를 녹화한 후에 WH_JOURNALPLAYBACK 후크를 통해
플레이할수 있다.  이 후크는 전역 후크로 - 특정 스레드에
대한 후크로 사용할수 없다.
JournalRecordProc 함수를 참조

WH_KEYBOARD Hook
이 후크는 애플리케이션이 GetMessage나 PeekMeesage로 얻어진
WM_KEYDOWN과 WM_KEYUP메세지들을 감시할수 있게 한다. 개발자는
WH_KEYBOARD릴 이용해 메세지큐로 보내진(posted) 키보드
입력을 감시 할수 있다.
KeyboardProc 함수를 참조

WH_KEYBOARD_LL Hook [ 윈NT 4.0 서비스팩 3에 의해 갱신됨 ]
이 후크는 개발자가 스레드 입력 큐안으로 들어온(posted) 키보드
입력 이벤트를 감시할수 있다.
LowLevelKeyboardProc 함수를 참조

WH_MOUSE Hook
이 후크는 개발자가 GetMessage나 PeekMessage로 들어온 마우스
메세지를 감시할수 있다. 개발자는 WH_MOUSE를 이용해 메세지 큐로
전달된(Posted) 마우스 입력을 감시할수 있다.
MouseProc함수를 참조

WH_MOUSE_LL Hook [ 윈NT 4.0 서비스팩 3에 의해 갱신됨 ]
이 후크는 개발자가 스레드 입력 큐안으로 전달된(posted)것들중
마우스 입력 이벤트를
감시할수 있게 한다.
LowLevelMouseProc함수를 참조

WH_MSGFILTER and WH_SYSMSGFILTER Hooks

이 후크들은 개발자가 메뉴,스크롤바, 메세지 박스, 다이얼로그
박스에 의해 처리되는 메세지들을 감시할수 있고 언제 다른 윈도우가
사용자에 의해 Alt+Tab이나 Alt+Esc키조합을 누름으로써 활성화 되는지를
탐지 할수 있다. 이 후크는 오직 메뉴,스크롤바,메세지박스,대화상자로
넘겨지는 메세지를 감시할수 있다. 이런 것들은 후크 프로시져를 설치한
애플리케이션에 의해 생성된 것들이어야 한다. WH_SYSMSGFILTER 후크는
모든 애플리케이션에 대해 그런 메세지들을 감시한다.

WH_MSGFILTER와 WH_SYSMSGFILTER후크들은 개발자가 메인 메세지루프안의
필터링을 끝낸 모달루프의 메세지 필터링을 형성할수 있게 한다. 예를 들어
종종 큐로 부터 메세지를 받을때나 메세지를 특별한 처리를 위해 메세지를
발송할때, 애플리케이션은 메인 루프안에서 새로운 메세지가 도착했는지
검사하게 된다. 그러나 모달루프 안에서는 애플리케이션이 메인메세지 루프에서
그 메세지를 필터링(변환,Filter)하기 위한 기회를 주지 않고 시스템이 곧바로
메세지를 회수하고 발송하게 된다. 만일 애플리케이션이 WH_MSGFILTER또는
WH_SYSMSGFILTER 후크 프로시져를 설치한다면, 시스템은 모달 루프 동안 그
프로시져를 호출하게 된다.

애플리케이션은 WH_MSGFILTER 후크를 직접 호출할수 있는데 이는
CallMsgFilter 함수로 가능하다. 이 함수를 사용함으로써, 애플리케이션은
같은 코드로 모달루프안에서 메세지를 메인 루프에서 쓰듯이 같은 코드로
필터링할수 있게 된다. 이렇게 하기 위해서, WH_MSGFILTER 후크 프로시져안에서
필터링 명령들을 GetMessage와 DispathMessage 함수 호출로 캡슐화해야 한다.

while ( GetMessage(&msg, (HWND)NULL,0, 0))
{
      if( !CallMsgFilter(&qmsg, 0 ))
        DispatchMessage(&qmsg);
}

CallMsgFilter의 마지막 인자가 간단히 후크 프로시져를 통해 넘어오는데,
개발자는  어떤 값이든 들여 보낼수 있다. MSGF_MAINLOOP같은 정의된
상수를 씀으로써 이 값은 프로시져가 어디서 호출이 되었는가를
결정할수 있게 사용될수 있다.

자세한 정보는 MessageProc과 SysMsgProc 함수를 참조하라.


WH_SHELL Hook
쉘애플리케이션은 WH_SHELL 후크를 사용함으로써 중요한
통지(notification)을 얻을 수 있다.  쉘 애플리케이션이 활성화
되어있고 최상위 레벨 윈도우가 생성되고 파괴될때 시스템은
WH_SHELL 후크 프로시져를 호출한다.

자세한 정보는 ShellProc함수를 참조하라

 
Using Hooks

Installing and Releasing Hook Procedures
개발자는 SetWindowsHookEx함수를 호출해 특정 타입의 후크
프로시져를 설치할수 있다. 프로시져가 모든 스레드에 연관되었거나
특정 스레드에 연관될수 있으며 프로시져 엔트리 포인터를 포인터와
연관될수 있다.

개발자는 애플리케이션과 분리된 DLL안의 전역 후크 프로시져를
설치 할수 있다. 프로시져가 설치하려는 애플리케이션은 후크
프로시져가 설치되기전에 DLL 모듈을 핸들링 해야 한다. LoadLibrary
함수를 이용해 DLL의 이름을 주면 DLL 모듈의 핸들러를 반환받을수 있다.
개발자가 핸들을 얻게 되면 개발자는 GetProcAddress 함수를 이용해
후크 프로시져의 주소를 반환받을수 있다. 마지막으로 개발자는
SetWindowsHookEx 함수를 이용해 후크 프로시져를 후크 체인에 설치
할수 있다. SetWindowsHookEx는 모듈 핸들을 거쳐 후크 프로시져
엔트리 포인트를 가리키게 되며 스레드 지정자로 0을 쓰면
( thread identifier) 후크 프로시져가 시스템안의 모든 스레드와
연관되었다고 하는것이다.
이 절차는 아래와 같은 예로 보여진다.

HOOKPROC hkprcSysMsg;
static HINSTANCE hinstDLL;
static HHOOK hhookSysMsg;

hinstDLL = LoadLibrary((LPCSTR)"c:\\windows\\sysmsg.dll");
hkprcSysMsg = (HOOKPROC)GetProcAddress(hinstDLL, "SysMessageProc");
hhookSysMsg = SetWindowsHookEx(WH_SYSMSGFILTER, hkprcSysMsg,hinstDLL, 0);

개발자는 후크 체인안의 후크 프로시져를 UnhookWindowsHookEx함수를 호출함으로써
해체시킬수 있다. 이때 해체할 특정 후크 프로시져가 필요하다. 애플리케이션이
더이상 후크 프로시져가 필요없다면 곧바로 후크 프로시져를 해체하라.


You can release a global hook procedure by using UnhookWindowsHookEx,
but this function does not free the DLL containing the hook procedure.
This is because global hook procedures are called in the process
context of every application in the system, causing an implicit call
to the LoadLibrary function for all of those processes. Because a
call to the FreeLibrary function cannot be made for another process,
there is then no way to free the DLL. The system eventually
frees the DLL after all processes explicitly linked to the DLL
have either terminated or called FreeLibrary and all processes
that called the hook procedure have resumed processing outside the DLL.

개발자는 UnhookWindowsHookEx를 통해 전역 후크 프로시져를
해체할수 있다. 그러나 이 함수는 후크 프로시져를 포함한 DLL을
해체하진 않는다.  그런 프로세스들의 모두는 명시적으로 LoadLibrary
함수를 호출하여 사용하여 전역 후크 프로시져가 시스템안에서 매
애플리케이션의 프로세스 문맥안에서 호출되기 때문이다.  
때문에 FreeLibrary 함수는 다른 프로세스에서 사용 불가능하다.
그렇다면 DLL을 해체하는 방법은 없다. 명시적으로 연결된 DLL이
파괴되었는지 또는 FreeLibrary가 호출되었는지 또는 모든 후크
프로시져로 불리는 프로세스가 DLL 밖에서 처리를
재개한 후에 시스템은 간간히(Eventually) DLL을 해체한다.

전역 후크 프로시져를 설치하는 대처적인 방법으로는 DLL안의 설치
함수같은것을 제공하는 것이다. 이 방법을 통해 설치된 애플리케이션은
DLL모듈을 핸들링 할 필요가 없다.
DLL과 함꼐 연결된 애플리케이션은 설치 함수들을 접근할수 있다.
설치 함수는 DLL 모듈 핸들과 다른 SetWindowsHookEx를 호출하에
상세한 정보를 제공할수 있다.
DLL은 또한 전역 후크 프로시져를 해체하는 특정 함수를 포함할수
있다. 애플리케이션은 파괴될때 이 후크 해체 함수를 호출할수 있다.

[출처] hook 설명|작성자 박호진

AND

출처: http://cafe.naver.com/netpee.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=879

. 훅이란
 
 

훅킹(Hooking)이란 이미 작성되어 있는 코드의 특정 지점을 가로채서 동작 방식에 변화를 주는 일체의 기술이다. 훅이란 낚시바늘같은 갈고리 모양을 가지는데 여기서는 코드의 중간 부분을 낚아채는 도구라는 뜻으로 사용된다. 대상 코드의 소스를 수정하지 않고 원하는 동작을 하도록 해야 하므로 기술적으로 어렵기도 하고 운영체제의 통상적인 실행 흐름을 조작해야 하므로 때로는 위험하기도 하다. 훅킹을 하는 방법에는 여러 가지가 있는데 과거 도스 시절에 흔히 사용하던 인터럽터 가로채기 기법이나 바로 앞에서 알아본 서브클래싱도 훅킹 기법의 하나라고 할 수 있다.

이외에도 미리 약속된 레지스트리 위치에 훅 DLL의 이름을 적어 주거나 BHO(Browser Helper Object)나 응용 프로그램 고유의 추가 DLL(Add in)을 등록하는 간단한 방법도 있고 PE 파일의 임포트 함수 테이블을 자신의 함수로 변경하기, CreateRemoteThread 함수로 다른 프로세스의 주소 공간에 DLL을 주입(Injection)하는 방법, 메모리의 표준 함수 주소를 덮어 쓰는 꽤 어려운 방법들도 있다. 이런 고급 훅킹 기술은 이 책의 범위를 벗어나므로 여기서는 소개만 하고 다루지는 않기로 한다. 이 절에서 알아볼 메시지 훅은 윈도우로 전달되는 메시지를 가로채는 기법으로 다양한 훅킹 방법중의 하나이다.

메시지 기반의 윈도우즈에서는 운영체제와 응용 프로그램, 또는 응용 프로그램 사이나 응용 프로그램 내부의 컨트롤끼리도 많은 메시지들을 주고 받는다. 훅(Hook)이란 메시지가 목표 윈도우로 전달되기 전에 메시지를 가로채는 특수한 프로시저이다. 오고 가는 메시지를 감시하기 위한 일종의 덫(Trap)인 셈인데 일단 응용 프로그램이 훅 프로시저를 설치하면 메시지가 윈도우로 보내지기 전에 훅 프로시저를 먼저 거친다. 서브클래스 프로시저와 마찬가지로 훅 프로시저에서는 메시지를 단순히 살펴보기만 할 수도 있고 메시지를 변경하거나 아예 없애버릴 수도 있다.

훅 프로시저가 어떤 메시지를 받을 것인가는 훅의 타입과 범위에 따라 달라진다. 훅 타입은 WH_로 시작되는 매크로 상수로 지정하는데 WH_MOUSE, WH_KEYBOARD 등 여러 가지가 있다. 이름으로 유추할 수 있듯이 WH_MOUSE 훅은 마우스 메시지를 가로채고 WH_KEYBOARD 훅은 키보드 메시지를 가로챈다. 이외에도 여러 가지 훅 타입이 정의되어 있는데 잠시 후 정리해 볼 것이다.

또한 훅은 메시지를 가로챌 범위에 따라 지역 훅(Thread Specific)과 시스템 전역 훅(System Wide)으로 나누어진다. 지역 훅은 특정 스레드에서 발생하는 메시지들만 전달받으며 전역 훅은 시스템의 모든 스레드에서 발생하는 메시지를 가로챌 수 있다. 응용 프로그램 자신의 메시지만 받고 싶을 때는 지역 훅을 사용하며 시스템에서 발생하는 모든 메시지를 받고자 할 때는 전역 훅을 설치해야 한다.

훅 프로시저는 응용 프로그램이 자신의 필요에 따라 언제든지 설치할 수 있기 때문에 하나의 훅 타입에 대해 여러 개의 훅 프로시저가 존재할 수도 있다. 그래서 운영체제는 설치된 훅 프로시저들을 훅 체인(Hook Chain)으로 관리한다. 훅 체인이란 훅 프로시저 함수들의 번지를 담고 있는 일종의 함수 포인터 배열이라고 할 수 있다. 응용 프로그램이 훅 프로시저를 설치하면 운영체제는 훅 체인의 선두에 이 프로시저를 등록한다. 훅 프로시저가 감시하는 메시지가 발생하면 운영체제는 훅 체인의 선두에 등록된 훅 프로시저에게 이 메시지를 전달하고 훅 프로시저는 체인을 따라 다음 훅 프로시저에게 메시지를 연속적으로 전달하며 종국에는 그 메시지를 받을 윈도우에게 전달된다. 물론 중간에서 메시지가 변형되거나 사라질 수도 있다.




훅 프로시저가 설치되어 있으면 시스템은 메시지가 발생할 때마다 훅 프로시저에게 메시지를 전달해 주어야 한다. 훅 체인에 여러 개의 훅 프로시저가 설치되어 있다면 메시지는 훅 체인 내의 모든 훅 프로시저를 거쳐야만 비로소 목표 윈도우로 전달될 수 있다. 그래서 훅은 시스템의 전반적인 속도를 눈에 띄게 느리게 만든다. 짧은 순간에 수백 개의 메시지가 처리되는데 이 메시지들이 훅 프로시저를 한 바퀴 돌아오려면 당연히 시간이 걸릴 수밖에 없다. 훅을 사용하는 디버거나 스파이 등의 툴을 띄워 놓으면 시스템이 느려지는 것을 직접 경험해 본 바 있을 것이다. 그래서 훅은 꼭 필요할 때만 설치해야 하며 사용이 끝난 후에는 곧바로 제거하는 것이 좋다.



. 훅 프로시저

 

 

훅 체인에 등록되어 메시지를 감시하는 함수를 훅 프로시저(Hook Procedure)라고 한다. 훅 타입에 따라 훅 프로시저의 인수나 리턴값의 의미는 달라지지만 원형은 고정되어 있다. 다음은 WH_KEYBOARD 타입의 키보드 훅 프로시저인데 다른 타입의 훅 프로시저도 이름만 다르고 원형은 동일하다.

 

LRESULT CALLBACK KeyboardProc( int code, WPARAM wParam, LPARAM lParam);

 

훅 프로시저는 응용 프로그램이 제공하는 콜백 함수이므로 원형만 제대로 지킨다면 이름은 마음대로 정할 수 있다. 세 개의 인수를 가지는데 첫 번째 인수 code는 훅 프로시저에서 이 메시지를 어떻게 처리할 것인가를 알려주며 이 값이 음수이면 훅 프로시저는 이 메시지를 처리하지 말고 다음 훅 프로시저에게 메시지를 넘겨야 한다. wParam, lParam은 전달된 메시지에 대한 추가 정보들인데 실제 의미는 훅 타입에 따라 달라지므로 각 타입별로 레퍼런스를 참고해야 한다. 훅 프로시저를 설치할 때는 다음 함수를 사용한다.

 

HHOOK SetWindowsHookEx( int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId );

 

첫 번째 인수 idHook은 설치하고자 하는 훅의 타입을 지정하며 WH_로 시작되는 매크로 상수중 하나를 써 주면 된다.  lpfn 은 훅 프로시저의 번지이며 hMod는 훅 프로시저를 가진 인스턴스 핸들이다. dwThreadId는 훅 프로시저가 감시할 스레드의 ID이되 이 값이 0이면 시스템의 모든 스레드에서 발생하는 메시지가 훅 프로시저로 전달된다. 자신의 메시지를 훅킹할 때는 GetCurrentThreadId 함수로 현재 스레드의 ID를 넘겨주면 된다. 시스템의 모든 메시지를 감시하고자 한다거나 다른 프로그램의 메시지를 감시하고자 할 경우 lpfn은 반드시 분리된 DLL에 있어야 하며 이때 hMod는 이 DLL의 핸들이어야 한다. 다음은 지역 훅과 전역 훅을 설치하는 일반적인 방법이다.

 

지역 훅 : SetWindowsHookEx(idHook, lpfn, NULL, GetCurrentThreadId());

전역 훅 : SetWindowsHookEx(idHook, lpfn, hDll, 0);

 

SetWindowsHookEx 함수는 훅 프로시저를 설치한 후 HHOOK 타입의 훅 핸들을 리턴해 주는데 이 핸들은 해제를 위해 전역변수에 잘 보관해 두어야 한다. 만약 에러가 발생했다면 NULL을 리턴한다. 훅 프로시저를 해제하는 함수는 다음과 같다.

 

BOOL UnhookWindowsHookEx( HHOOK hhk );

 

해제하고자 하는 훅 핸들을 넘겨주기만 하면 된다. 훅을 설치한 프로그램은 종료되기 전에 반드시 훅 프로시저를 해제해 주어야 한다. 훅 프로시저가 설치되면 해당 타입의 메시지는 목표 윈도우로 보내지기 전에 훅 프로시저에게 먼저 전달되는데 훅 프로시저는 메시지를 살펴본 후 특별한 이유가 없으면 메시지를 훅 체인의 다음 훅 프로시저에게 전달해 주어야 한다. 이때는 다음 함수를 사용한다.

 

LRESULT CallNextHookEx( HHOOK hhk, int nCode, WPARAM wParam, LPARAM lParam );

 

hhk는 현재 처리하고 있는 훅의 핸들인데 SetWindowsHookEx 함수가 리턴한 값이다. 나머지 세 인수는 운영체제가 훅 프로시저에게 전달해준 인수들이다. 훅 체인에 포함된 훅 프로시저의 목록은 운영체제가 직접 관리하기 때문에 훅을 설치한 응용 프로그램은 다음 훅 프로시저의 번지를 따로 저장할 필요없이 이 함수만 호출해 주면 훅 체인을 따라 모든 훅 프로시저가 순서대로 호출되며 최종적으로 목표 윈도우로 메시지가 전달될 것이다.

훅 프로시저는 전달 받은 메시지를 다음 훅 프로시저에게 꼭 전달해 주어야 할 의무는 없으며 메시지를 아예 없애버리려면 전달하지 않아도 상관없으며 원하는 대로 변경할 수도 있다. 물론 이 때는 메시지를 없애버리거나 변경한 후의 효과에 대해 확실히 책임질 수 있어야 한다. 이 함수는 훅 체인에서 다음 훅 프로시저를 호출하고 훅 프로시저가 리턴하는 값을 다시 리턴해 주는데 현재의 훅 프로시저는 이 리턴값을 또 그대로 리턴해 주어야 한다. 그래서 훅 프로시저의 끝은 보통 return CallNextHookEx(...) 호출문이 온다.

다음은 윈도우즈가 제공하는 훅 타입들이다. 훅 타입에 따라 감시하고 취급할 수 있는 메시지의 종류가 달라진다.

 

 

훅 타입

설명

WH_CALLWNDPROC,

 

WH_CALLWNDPROCRET

SendMessage 함수로 메시지를 보내기 전에

 

WH_CALLWNDPROC 훅 프로시저가 호출되며 윈도우 프로시저가 메시지를 처리한 후에 WH_CALLWNDPROCRET 훅 프로시저가 호출된다. WH_CALLWNDPROCRET 훅은 훅 프로시저에게 CWPRETSTRUCT 구조체를 전달하는데 이 구조체에는 메시지와 메시시를 처리한 리턴값을 담고 있다.

 

WH_CBT

윈도우를 생성, 파괴, 활성화, 최대, 최소, 이동, 크기변경하기 전에, 시스템 명령을 처리하기 전에, 마우스나 키보드 메시지를 메시지 큐에서 제거하기 전에 이 훅 프로시저가 호출된다. 이 훅은 컴퓨터를 이용한 훈련 프로그램(Computer Based Training)에서 주로 사용된다.

 

WH_DEBUG

다른 타입의 훅 프로시저를 호출하기 전에 이 타입의 훅 프로시저를 호출하며 다른 타입의 훅 프로시저 호출을 허가할 것인지를 결정한다.

 

WH_GETMESSAGE

GetMessage나 PeekMessage 함수로 조사되는 메시지를 감시한다.

 

WH_JOURNALRECORD

키보드나 마우스를 통해 입력되는 이벤트를 감시하고 기록한다. 기록된 이벤트는 WH_JOURNALPLAYBACK 훅에서 재생할 수 있다. 이 훅은 전역으로만 설치할 수 있으며 특정 스레드에만 설치할 수는 없다.

 

WH_JOURNALPLAYBACK

시스템 메시지 큐에 메시지를 삽입할 수 있도록 한다. 이 훅에서 WH_JOURNALRECORD 훅에서 기록한 키보드 마우스 입력을 재생할 수 있다. 이 훅이 설치되어 있으면 마우스나 키보드 입력은 금지된다. 이 훅은 전역으로만 설치할 수 있으며 특정 스레드에만 설치할 수는 없다.

 

WH_KEYBOARD

WM_KEYDOWN, WM_KEYUP 등의 키보드 메시지를 감시한다.

 

WH_MOUSE

마우스 메시지를 감시한다.

 

WH_MSGFILTER,

WH_SYSMSGFILTER

메뉴, 스크롤 바, 메시지 박스, 대화상자 등에 의해 처리되는 메시지와 사용자의 Alt+Tab키, Alt+Esc키 입력에 의한 포커스 이동을 감시한다. WH_MSGFILTER훅은 훅 프로시저를 설치한 프로그램에 대해서만 동작하며 WH_SYSMSGFILTER 훅은 모든 프로그램에 대해서 동작한다.

 

WH_SHELL

쉘 프로그램이 활성화되거나 새로운 최상위 윈도우가 만들어지거나 파괴될 때 이 훅 프로시저가 호출된다.

 

WH_FOREGROUNDIDLE

포그라운드 스레드가 한가해질 때 이 훅 프로시저가 호출된다. 아이들 시에 우선 순위가 낮은 작업을 하고 싶을 때 이 훅을 사용한다.

 

WH_KEYBOARD_LL

스레드의 입력큐에 붙여지는 키보드 입력 메시지를 감시한다. WH_KEYBOARD보다 더 저수준의 메시지를 받을 수 있지만 NT 4.0 SP 3 이후에만 사용할 수 있다.

 

WH_MOUSE_LL

스레드의 입력큐에 붙여지는 마우스 입력 메시지를 감시한다.

 

각 훅 타입에 따라 사용되는 구조체나 리턴값, 훅 프로시저의 인수가 다르므로 상세한 정보는 레퍼런스를 참조하기 바란다.




. 키보드 훅

 

 

훅 타입중에 개념적으로 가장 이해하기 쉽고 간단한 키보드 훅 프로시저를 작성해 보자. 다음 예제는 키보드 메시지를 훅킹하여 메시지에 대한 정보를 작업영역에 보여준다.

 

KeyHook

char Mes[]="키보드 테스트 프로그램입니다.";

char Mes2[128];

char Mes3[128];

int Count=0;

HHOOK hKeyHook;

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

{

   if (nCode>=0) {

      InvalidateRect(hWndMain,NULL,TRUE);

      if (wParam == VK_F2) {

          wsprintf(Mes2,"F2 입력이 금지된 키입니다.");

          return 1;

      } else {

          wsprintf(Mes2,"nCode=%d, wParam=%u, lParam=%08x, Count=%d",

             nCode, wParam, lParam,Count++);

      }

   }

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

}

 

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

   HDC hdc;

   PAINTSTRUCT ps;

 

   switch(iMessage) {

   case WM_CREATE:

      hKeyHook=SetWindowsHookEx(WH_KEYBOARD,KeyHookProc,NULL,GetCurrentThreadId());

      return 0;

   case WM_KEYDOWN:

      wsprintf(Mes3,"실제 받은 : %u",wParam);

      InvalidateRect(hWnd,NULL,TRUE);

      return 0;

   case WM_PAINT:

      hdc=BeginPaint(hWnd, &ps);

      TextOut(hdc,100,10,Mes,strlen(Mes));

      TextOut(hdc,100,30,Mes2,strlen(Mes2));

      TextOut(hdc,100,50,Mes3,strlen(Mes3));

      EndPaint(hWnd, &ps);

      return 0;

   case WM_DESTROY:

      UnhookWindowsHookEx(hKeyHook);

      PostQuitMessage(0);

      return 0;

   }

   return(DefWindowProc(hWnd,iMessage,wParam,lParam));

}

 

실행 결과는 다음과 같다.






WM_CRAETE에서 SetWindowsHookEx 함수로 KeyHookProc 훅 프로시저를 WH_KEYBOARD 훅 타입의 훅 체인에 추가하였다. 현재 스레드의 ID를 주었으므로 자기 자신의 키보드 메시지만 감시하는 지역 훅이다. 사용자가 키보드를 누르거나 뗄 때 즉, WM_KEYDOWN, WM_KEYUP 메시지가 발생하면 KeyHookProc 훅 프로시저가 호출될 것이다. WM_DESTROY에서는 설치한 훅 프로시저를 해제하도록 했다.

훅 프로시저로 전달된 nCode 인수가 음수일 경우는 어떤 처리도 해서는 안 되며 곧바로 훅 체인의 다음 훅 프로시저로 메시지를 넘겨주어야 한다. 예제에서는 nCode가 0보다 크거나 같을 때만 메시지를 감시하도록 했다. 훅 프로시저는 전달된 메시지의 내용을 점검한 후 메시지를 변경하거나 없애버릴 수도 있고 아니면 그대로 체인의 다음 함수에게 전달할 수도 있다.

이 예제는 훅 프로시저가 특정 키입력을 거부할 수도 있다는 것을 보여 주기 위해 F2 키가 입력되었을 경우 0이 아닌 값(보통 1 또는 TRUE)을 리턴하여 이 키에 대한 입력 메시지가 다음 훅 프로시저로 전달되지 않도록 했다. 시스템은 훅 프로시저가 0이 아닌 값을 리턴하면 이 메시지를 중간에서 없애버리며 다음 훅 프로시저로 전달되지 않으므로 결국 목표 윈도우는 이 메시지를 받지 못하게 된다. F2가 아닌 경우는 어떤 키가 입력되었는지 wParam과 lParam에 대한 정보를 화면에 출력하도록 했다.

키보드 훅 프로시저는 WM_KEYDOWN, WM_KEYUP 메시지에 대해 호출되므로 키가 눌러지거나 떨어질 때 모두 호출된다. 만약 훅 프로시저내에서 키가 눌러진 것인지 떨어진 것인지를 구분하고 싶다면 lParam의 최상위 비트가 1인지 아닌지를 점검해 보면 된다. if ((lParam & 0x80000000)==0) 조건문이 참이면 WM_KEYDOWN 메시지가 전달된 것이고 거짓이면 WM_KEYUP이 전달된 것이다.

위 예제에서 훅 타입을 WH_MOUSE로 바꾸면 마우스 메시지를 훅킹할 것이다. 훅을 설치, 해제하는 방법은 동일하되 nCode 인수의 의미나 wParam, lParam으로 전달되는 추가 정보의 의미는 달라진다. 지역 훅은 사용 방법이 쉽고 별다른 제약 사항이나 주의 사항이 없어 위험하지도 않은 편이다.



. 전역 훅

 

 

전역 훅은 시스템에서 발생하는 모든 메시지를 가로챌 수 있으므로 강력하고 활용성도 높다. 하지만 보호된 Win32 환경의 특수성으로 인해 프로그래밍하기는 무척 어려운 편인데 전역 훅을 제대로 이해하려면 Win32의 메모리 구조, DLL, IPC, 스레드, 메시지 전달 체계, PE 파일의 구조에 대한 이해가 있어야 한다. 그래서 전역 훅을 제대로 이해하려면 이런 선수 과목을 먼저 공부한 후에 볼 것을 권장한다.

메시지들은 발생한 사건에 대한 정보를 wParam, lParam 인수로 보내는데 이 두 인수로 전달되는 값은 보통 정수나 핸들 등의 단순값이지만 일부 복잡한 메시지(WM_CREATE, WM_DRAWITEM)는 구조체의 포인터를 전달하기도 한다. 전역 훅 프로시저는 시스템의 모든 스레드에서 발생하는 메시지들을 감시하는데 훅 프로시저가 메시지에 대한 정보를 읽으려면 전달된 구조체 포인터로부터 멤버를 읽을 수 있어야 한다.

그러나 Win32 환경에서는 프로세스들의 주소 공간이 분리되어 있기 때문에 일반적인 함수로는 이 문제를 해결할 수 없다. 훅 프로시저가 전달받은 포인터로는 이 메시지를 받을 프로세스의 주소 공간을 액세스할 수 없는 것이다. 그래서 전역 훅 프로시저는 반드시 분리된 DLL에 있어야 한다. DLL은 공유되는 모듈이며 연결된 프로세스의 주소 공간에서 실행되므로 모든 스레드의 메시지를 자유롭게 읽을 수 있다. DLL안에 훅 프로시저를 작성해 놓으면 시스템이 훅 프로시저를 호출하기 전에 이 DLL을 메시지 목표 프로세스의 주소 공간으로 먼저 로드한다.

32비트 환경이 보호된 환경이기 때문에 전역 훅 프로시저가 DLL에 있어야 한다는 것은 어렵지 않게 이해가 될 것이다. 훅 프로시저를 DLL로 작성할 때 곤란한 문제가 하나 더 있는데 바로 공유 데이터의 문제이다. DLL은 어디까지나 메시지를 가로채기 위한 훅 프로시저를 제공할 뿐이며 가로챈 메시지를 실제로 처리하는 작업은 훅 서버가 하는 것이 일반적이다. 훅 DLL은 가로챈 메시지를 훅 서버에게 IPC나 메시지 등의 방법으로 전달해야 하며 그러기 위해서는 DLL이 훅 서버의 핸들을 항상 가지고 있어야 한다. 또한 훅 핸들도 전역으로 유지해야 훅 프로시저의 끝에서 CallNextHookEx 함수를 호출하여 메시지가 원활하게 흘러가도록 할 수 있다.

DLL은 연결된 프로세스의 주소 공간에 맵핑되며 각 DLL 인스턴스별로 고유한 데이터를 가진다. DLL은 코드는 공유하지만 데이터는 공유하지 않도록 되어 있다. 결국 훅 서버의 핸들이나 훅 핸들 등 동작에 꼭 필요한 정보를 각각의 DLL이 따로 가지게 되며 이렇게 되면 훅 DLL과 훅 서버가 지속적인 통신을 할 수 없을 것이다. 각 프로세스에 연결된 DLL마다 훅 서버에 대한 정보나 훅 핸들이 달라져 버리기 때문이다.







훅 프로시저가 어떤 프로세스의 주소 공간에서 실행되고 있든지 가로챈 메시지를 전달할 훅 서버는 항상 일정해야 한다. 그래서 훅 DLL은 각각의 프로세스에 연결되더라도 동작에 필요한 전역 변수는 DLL인스턴스끼리 공유해야 하며 그러기 위해 각 DLL 인스턴스끼리 IPC로 통신하거나 또는 공유섹션이나 파일맵핑, 레지스트리같은 약속된 장소에 전역 변수를 저장해야 한다.

다음은 설치와 해제의 문제에 대해 점검해 보자. 훅을 설치하는 응용 프로그램(훅 서버)은 훅 프로시저를 가진 DLL(훅 드라이버)을 로드한 후 GetProcAddress 함수로 훅 프로시저의 번지를 조사하고 SetWindowsHookEx함수로 훅을 설치할 수 있다. 훅을 해제할 때는 UnhookWindowsHookEx 함수를 호출하는데 이 함수에 의해 훅은 체인에서 제거되지만 각 프로세스의 주소 공간으로 로드되어 버린 DLL들은 명시적으로 제거할 기회를 가지지 못하게 된다.

또한 전역 훅을 설치할 때 SetWindowsHookEx 함수로 훅 DLL의 핸들을 전달해야 하는데 훅 서버가 훅 DLL의 핸들을 알려면 LoadLibrary 함수로 DLL을 실행중에 읽어와야 한다. 이렇게 되면 훅 서버와 훅 DLL이 묵시적으로 연결될 수 없다. 그래서 통상 전역 훅은 DLL 자체에 설치, 해제 함수를 작성하고 훅 서버는 DLL의 이 함수를 호출하여 설치와 해제 작업을 한다.

이론이 점점 복잡해지고 있는데 이쯤에서 실제 예제를 만들어 보고 분석하면서 이론을 정리해 보도록 하자. 다음 예제는 전역 키보드 훅을 설치하여 키보드가 눌러질 때마다 효과음을 낸다. 전역 훅이므로 모든 응용 프로그램으로 전달되는 키보드 메시지를 가로챌 수 있다. 먼저 훅 프로시저를 제공하는 DLL부터 작성해 보자.

 

#include <windows.h>

 

#pragma data_seg(".kbdata")

HINSTANCE hModule=NULL;

HHOOK hKeyHook=NULL;

HWND hWndBeeper=NULL;

#pragma data_seg()

#pragma comment (linker, "/SECTION:.kbdata,RWS")

 

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

{

   if (nCode>=0) {

      SendMessage(hWndBeeper,WM_USER+1,wParam,lParam);

   }

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

}

 

extern "C" __declspec(dllexport) void InstallHook(HWND hWnd)

{

   hWndBeeper=hWnd;

   hKeyHook=SetWindowsHookEx(WH_KEYBOARD,KeyHookProc,hModule,NULL);

}

 

extern "C" __declspec(dllexport) void UninstallHook()

{

   UnhookWindowsHookEx(hKeyHook);

}

 

BOOL WINAPI DllMain(HINSTANCE hInst, DWORD fdwReason, LPVOID lpRes)

{

   switch (fdwReason) {

   case DLL_PROCESS_ATTACH:

      hModule=hInst;

      break;

   case DLL_PROCESS_DETACH:

      break;

   }

   return TRUE;

}

 

세 개의 전역 변수를 가지는데 이 변수들은 .kbdata라는 이름의 섹션에 선언되어 있으며 이 섹션에 공유 속성을 주었다. 섹션이란 동일한 특성을 가지는 연속된 메모리 공간인데 .data 섹션에는 초기화된 데이터들이 저장되며 .bss 섹션에는 초기화되지 않은 데이터들이 저장된다. #pragma data_seg지시자는 이후 선언된 변수가 저장될 섹션을 지정하는데 별도의 섹션을 따로 만들 수 있으며 #pragma comment 명령으로 링커에게 이 섹션의 특성을 지정할 수 있다. 공유 섹션을 만드려면 다음과 같이 한다.

 

#pragma data_seg("섹션명")

변수 선언

#pragma data_seg()

#pragma comment (linker, "/SECTION:섹션명,RWS")

 

섹션 이름은 임의의 문자열로 붙일 수 있되 대소문자는 구분하지 않으며 8문자 이하로 작성해야 한다. data_seg 지시자로 섹션 이름을 주면 이후 선언되는 변수들은 이 섹션에 저장되며 data_seg 지시자만 사용하면 디폴트 섹션으로 돌아간다. 그리고 #pargma comment로 이 섹션에 대해 읽기(R), 쓰기(W), 공유(S) 속성을 주도록 링커 옵션을 주었다. 공유 섹션에 선언되는 데이터는 반드시 초기값을 가져야 한다.

예제에서는 DLL의 인스턴스 핸들, 훅 핸들, 그리고 훅 서버의 윈도우 핸들을 공유 섹션에 선언하고 모두 NULL로 초기화했다. 이 변수들은 개별 프로세스와 연결되는 모든 DLL에 의해 공유되므로 한번 대입된 값은 어떤 프로세스의 주소 공간에서 실행되든 항상 동일한 값으로 참조할 수 있다.

DllMain에서는 DLL이 로드될 때 자신의 핸들을 저장해 두었는데 이 핸들은 전역 훅을 설치할 때 훅을 소유한 모듈이 누구인지를 지정하기 위해 사용된다. 훅 설치 함수인 InstallHook은 인수로 훅 서버의 윈도우 핸들을 전달받아 hWndBeeper 전역 변수에 저장했으며 DLL에 정의된 훅 프로시저 KeyHookProc 함수를 키보드 메시지에 대한 전역 훅으로 설치한다. 훅 서버에서 이 함수를 한번만 호출해 주면 이후 KeyHookProc 함수는 모든 응용 프로그램으로 전달되는 키보드 관련 메시지를 먼저 받을 수 있게 된다. UninstallHook 함수는 훅을 해제한다.

가장 중요한 KeyHookProc 함수는 키보드 메시지를 전달받았을 때 이 메시지를 훅 서버인 hWndBeeper에게 그대로 전달하는데 이때 WM_USER+1 사용자 정의 메시지를 사용했다. 키보드 메시지는 wParam, lParam에 모든 부가 정보를 다 실을 수 있기 때문에 이런 간단한 방법으로 훅 서버에게 정보를 전달할 수 있지만 좀 더 복잡한 메시지라면 WM_COPYDATA나 파일 맵핑 등의 더 복잡한 IPC 방법으로 통신해야 할 것이다. 훅 서버에게 메시지를 전달한 후 CallNextHookEx 함수를 호출하여 체인의 다음 훅 함수를 호출해 주어 결국은 목표 윈도우가 이 메시지를 받을 수 있도록 했다.

KeyHookProc은 키보드 메시지를 훅 서버 대신 받아주는 일만 할 뿐이며 가로챈 메시지를 실제로 사용하는 주체는 훅 서버이다. 결국 이 DLL은 메시지를 받을 프로세서의 주소 공간에 잠입해서 이 프로세서로 전달되는 메시지를 가로채 훅 서버에게 전달해 주는 일만 하는 심부름꾼에 불과하다. 훅 서버는 독립된 프로세스이기 때문에 다른 프로세스의 주소 공간을 들여다 볼 수 없으며 그래서 DLL이라는 간첩을 모든 프로세스의 주소 공간에 침투시키는 방법을 쓴다.

이 예제는 공유 데이터 처리를 위해 공유 섹션을 사용했는데 파일 맵핑으로도 데이터를 공유할 수 있다. 파일 맵핑은 프로세스간에 공유할 수 있는 메모리 영역이므로 이 영역에 전역 변수들을 저장하면 각 DLL 인스턴스끼리 동일한 값을 참조할 수 있다. 예제 소스에 파일 맵핑을 사용하는 코드도 작성되어 있으므로 참고하기 바란다.

다음은 가로챈 키보드 메시지를 처리하는 훅 서버의 코드를 보도록 하자. 훅 서버는 훅 드라이버(DLL)를 통해 시스템에 발생한 모든 메시지를 전달받는데 이 시점에서 어떤 작업이든지 할 수 있다. 이 예제의 경우 훅 DLL로부터 전달된 WM_USER+1 메시지가 작업을 할 시점이다. 자신이 설치해 놓은 훅 프로시저를 통해 다른 프로세스의 키 입력 시점을 정확하게 제공받는 것이다. 다음 훅 서버는 메시지 자체를 건드리지는 않으며 눌러진 키의 종류에 따라 적절한 효과음을 내기만 한다.

 

#include <mmsystem.h>

#include "resource.h"

#include "../KeyBeepDll/KeyBeepDll.h"

TCHAR Mes[]="시스템의 입력을 감시하며 키가 눌러질 때마다 소리를 냅니다.";

TCHAR Mes2[128];

 

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

   HDC hdc;

   PAINTSTRUCT ps;

   static count;

 

   switch(iMessage) {

   case WM_CREATE:

      InstallHook(hWnd);

      return 0;

   case WM_USER+1:

      wsprintf(Mes2,"입력된 :%d, lParam : %x ",wParam,lParam);

      InvalidateRect(hWnd,NULL,TRUE);

      if ((lParam & 0x80000000)==0) {

          if (wParam >= 'A' && wParam <= 'Z') {

             PlaySound(MAKEINTRESOURCE(IDR_CHARACTER), g_hInst, SND_RESOURCE | SND_ASYNC);

          }

          if (wParam == ' ') {

             PlaySound(MAKEINTRESOURCE(IDR_SPACE), g_hInst, SND_RESOURCE | SND_ASYNC);

          }

          if (wParam >= '0' && wParam <= '9') {

             PlaySound(MAKEINTRESOURCE(IDR_NUMBER), g_hInst, SND_RESOURCE | SND_ASYNC);

          }

          if (wParam >= VK_F1 && wParam <= VK_F24) {

             PlaySound(MAKEINTRESOURCE(IDR_FUNCTION), g_hInst, SND_RESOURCE | SND_ASYNC);

          }

          if (wParam >= VK_PRIOR && wParam <= VK_HELP) {

             PlaySound(MAKEINTRESOURCE(IDR_EDIT), g_hInst, SND_RESOURCE | SND_ASYNC);

          }

          if (wParam == VK_BACK || wParam == VK_TAB || wParam == VK_RETURN) {

             PlaySound(MAKEINTRESOURCE(IDR_BACKTAB), g_hInst, SND_RESOURCE | SND_ASYNC);

          }

          if ((wParam >= 186 && wParam <= 191) || (wParam >= 219 && wParam <= 222)) {

             PlaySound(MAKEINTRESOURCE(IDR_PUNC), g_hInst, SND_RESOURCE | SND_ASYNC);

          }

      }

      return 0;

   case WM_PAINT:

      hdc=BeginPaint(hWnd, &ps);

      TextOut(hdc,10,10,Mes,lstrlen(Mes));

      TextOut(hdc,10,30,Mes2,lstrlen(Mes2));

      EndPaint(hWnd, &ps);

      return 0;

   case WM_DESTROY:

      UninstallHook();

      PostQuitMessage(0);

      return 0;

   }

   return(DefWindowProc(hWnd,iMessage,wParam,lParam));

}

 

WM_CREATE에서 DLL의 훅 설치 함수를 호출하여 자신이 시작될 때 전역 훅을 설치했으며 WM_DESTROY에서 훅을 해제하도록 했다. 그래서 훅 서버가 실행중인 동안에는 전역 훅 프로시저가 지속적으로 시스템의 모든 키보드 관련 메시지를 감시하며 이때마다 훅 서버에게 메시지와 관련된 정보를 알려준다.

가로챈 메시지를 처리하는 대부분의 코드는 WM_USER+1에 있는데 화면에 눌러진 키의 정보를 문자열로 출력해 주고 키의 범위에 따라 적절한 효과음을 낸다. 효과음은 짧은 웨이브 파일을 사용자 정의 리소스로 미리 준비해 두었으며 웨이브 파일을 연주할 때는 PlaySound라는 함수를 사용한다. 직접 실행해 보면 이 프로세스가 실행중인 동안에는 메모장이나 워드 프로세서에서 키를 누를 때마다 타이프를 치는 듯한 효과음이 출력될 것이다.

포커스를 누가 가지고 있든간에 사용자가 키를 누르기만 하면 키보드 메시지가 발생하는데 이 메시지를 DLL의 훅 프로시저가 먼저 가로채서 훅 서버에게 전달해 주며 훅 서버는 입력된 키의 종류에 따라 그럴듯한 사운드를 출력하도록 되어 있기 때문이다. 훅 서버나 훅 드라이브가 가로챈 메시지를 조용히 다음 훅 체인으로 보내 주므로 다른 응용 프로그램의 동작은 방해하지 않도록 하였다. 만약 KeyHookProc에서 CallNextHookEx를 호출하지 않는다면 효과음만 나고 실제 키 입력 메시지가 목표 윈도우로 전달되지 않으므로 시스템의 키보드는 먹통이 되어 버릴 것이다.





예제를 직접 실행해 보면 키보드를 두드릴 때마다 찰칵 찰칵 소리가 나서 마치 타자기를 치는 듯한 색다른 기분이 든다. 코드를 짧게 만드느라 편의 기능은 넣지 않았는데 메인 윈도우를 조금 더 예쁘장하게 장식하고 여기에 효과음을 선택할 수 있는 기능이라든가 볼륨 조절 기능 등만 넣어도 깜찍한 악세사리로 쓸만한 프로그램이 된다.


 

 


 

 

마. 훅의 활용


전역 훅 프로시저는 다른 프로세스의 메시지 흐름을 들여다 볼 수 있다는 점에서 활용성이 높다. 특정 윈도우로 입력되는 메시지의 흐름을 살펴보면 이 윈도우가 입력된 메시지에 대해 어떻게 반응할 것인가를 예측할 수 있으며 따라서 윈도우에게 사건이 발생하는 정확한 시점을 알 수 있다. 훅 프로시저는 메시지 흐름을 감시하고 있다가 관심있는 이벤트가 발생했을 때 원하는 어떤 조치를 취할 수 있을 것이다.

다음 예제는 메모장의 키 입력을 감시하는 훅 프로시저를 설치하고 사용자가 babo라는 키를 연속으로 입력하면 이 키 입력을 모두 취소하고 chunjae로 바꿔 버린다. babo라는 연속된 키입력 이벤트에 반응하여 메모장의 동작을 원하는 방식으로 제어할 수 있는 것이다. 훅 DLL은 앞서 작성했던 KeyBeepDll과 동일하므로 따로 살펴볼 필요가 없으며 훅 서버 프로그램이 키 입력을 감시하고 이벤트에 대응하는 방법만 보도록 하자.

 

#include "../HookNotePadDll/HookNotePadDll.h"

TCHAR Mes[]="메모장에서 BABO 입력하면 chaunjae 변경합니다.";

TCHAR szSrc[]="BABO";

TCHAR szDest[]="CHUNJAE";

int idx;

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

   HDC hdc;

   PAINTSTRUCT ps;

   HWND hFGWnd;

   TCHAR szClass[32];

   int i;

 

   switch(iMessage) {

   case WM_CREATE:

      InstallHook(hWnd);

      return 0;

   case WM_USER+1:

      hFGWnd=GetForegroundWindow();

      GetClassName(hFGWnd,szClass,32);

      if (lstrcmpi(szClass,"NotePad")==0 && (lParam & 0x80000000)==0) {

          if (wParam == (WPARAM)szSrc[idx]) {

             idx++;

          } else {

             idx=0;

          }

          if (szSrc[idx]==0) {

             for (i=0;i<lstrlen(szSrc);i++) {

                keybd_event(VK_BACK,0,0,0);

                keybd_event(VK_BACK,0,KEYEVENTF_KEYUP,0);

             }

             for (i=0;i<lstrlen(szDest);i++) {

                keybd_event(szDest[i],0,0,0);

                keybd_event(szDest[i],0,KEYEVENTF_KEYUP,0);

             }

          }

      }

      return 0;

   case WM_PAINT:

      hdc=BeginPaint(hWnd, &ps);

      TextOut(hdc,10,10,Mes,lstrlen(Mes));

      EndPaint(hWnd, &ps);

      return 0;

   case WM_DESTROY:

      UninstallHook();

      PostQuitMessage(0);

      return 0;

   }

   return(DefWindowProc(hWnd,iMessage,wParam,lParam));

}

 

이 프로그램은 메모장의 동작에 대해서만 관여하므로 훅 프로시저로부터 WM_USER+1 메시지를 받았을 때 활성화된 윈도우가 메모장이 맞는지를 먼저 살펴 본다. 활성 윈도우의 윈도우 클래스명이 "NotePad"가 아니라면 메모장이 아니므로 아무런 동작도 하지 않으며 키가 떨어질 때도 역시 아무 동작도 하지 않는다. 오직 메모장에서 키를 누를 때(WM_KEYDOWN)만 동작하도록 했는데 이 조건을 변경하면 모든 프로그램에 대해 동작하도록 바꿀 수도 있다.

메모장에서 키가 눌러졌을 때, 즉 메모장 윈도우가 WM_KEYDOWN 메시지를 받았을 때 wParam이 babo의 연속인지 아닌지를 항상 감시하고 있다가 만약 babo가 연속으로 입력되면 4개의 BS키를 보내 babo를 지우고 chunjae키를 차례대로 누름으로써 babo를 chunjae로 바꾸어 준다. keybd_event 는 마치 사용자가 키보드를 누른 것처럼 키 이벤트를 발생시키는 함수이다.

이 예제는 연속된 키입력만 감시하기 때문에 한글, 대소문자 등은 구분하지 않으며 중간에 BS나 커서 이동키로 이동, 편집한 경우도 연속된 문자열로 인정하지 않는다. IME 상태나 대소문자 구분 등을 판별하고 BS, Del 등의 간단한 편집키를 처리한다면 좀 더 완벽한 동작을 할 수도 있다. 이런 방식으로 다른 프로그램의 키입력을 감시, 변경하면 백그라운드 맞춤법 검사기나 상용구 입력기 등을 만들 수 있을 것이다.

다음 예제는 지역 훅을 사용하여 메시지 박스를 부모 윈도우의 중앙에 출력한다. MessageBox 함수는 자신의 위치를 지정하는 플래그가 없으며 무조건 화면 중앙에 나타나도록 되어 있다. 이 함수는 호출하는 즉시 메시지 박스를 띄우고 확인 버튼을 누를 때까지 리턴하지 않기 때문에 호출원에서 윈도우의 위치를 옮길 수 있는 기회가 없다. 위치를 옮길 때는 MoveWindow나 SetWindowPos 함수를 사용한다는 것은 알고 있지만 이 함수를 호출할 마땅한 시점이 없는 것이다.

메시지 박스가 생성되는 시점, 그러니까 WM_CREATE 메시지를 받을 때 이 윈도우의 위치를 옮겨야 하는데 그 시점이 운영체제 내부에 있기 때문에 응용 프로그램이 자신의 코드를 실행할  기 회가 없는 것이다. 메시지 박스는 일종의 대화상자이고 이 대화상자의 윈도우 프로시저는 운영체제에 내장되어 있어 프로그래밍할 수 있는 대상이 아니다. 메시지 박스가 생성되는 시점을 구하기 위해 훅을 설치하고 윈도우가 생성될 때 보내지는 메시지를 가로채야 한다.

이때 사용하는 훅이 WH_CBT이다. CBT(Computer Based Training) 훅은 초보자들의 컴퓨터 조작 훈련을 위해 제공되는데 윈도우가 생성, 파괴, 이동 및 크기 변경시의 메시지를 감시하도록 한다. CBT 프로그램은 사용자에게 윈도우 조작 방법을 알려주고 실습을 유도하는데 이때 사용자들이 지시대로 윈도우의 생성, 이동, 종료 등을 제대로 하는지 감시하기 위해 WH_CBT 훅을 사용한다. 이 훅의 원래 목적은 사용자 교육용이지만 일반 응용 프로그램도 윈도우 관련 메시지를 가로채기 위해 이 훅을 사용할 수 있다.

이 훅을 사용하면 특정 윈도우가 생성되는 시점을 응용 프로그램이 알 수 있고 이때 원하는 처리, 예를 들어 위치를 옮기거나 크기를 바꾸거나 스타일을 변경할 수 있다. 메시지 박스처럼 사용자가 직접 만든 윈도우가 아닐지라도 말이다. 다음 예제는 WH_CBT훅과 서브클래싱을 사용하여 메시지 박스를 부모 윈도우의 중앙에 오도록 한다.

 

HHOOK hCbtHook;

void MoveToParentCenter(HWND hWnd)

{

   RECT wrt,crt;

   HWND hParent;

 

   hParent=GetParent(hWnd);

   if (IsIconic(hParent)) {

      ShowWindow(hParent,SW_RESTORE);

   }

 

   GetWindowRect(hParent,&wrt);

   GetWindowRect(hWnd,&crt);

   SetWindowPos(hWnd,HWND_NOTOPMOST,wrt.left+(wrt.right-wrt.left)/2-(crt.right-crt.left)/2,

      wrt.top+(wrt.bottom-wrt.top)/2-(crt.bottom-crt.top)/2,0,0,SWP_NOSIZE);

}

 

LRESULT CALLBACK NewWndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

   WNDPROC OldProc;

   OldProc=(WNDPROC)GetProp(hWnd,"OldProp");

 

   switch(iMessage) {

   case WM_CREATE:

      MoveToParentCenter(hWnd);

      break;

   case WM_NCDESTROY:

      SetWindowLong(hWnd,GWL_WNDPROC,(DWORD)OldProc);

      RemoveProp(hWnd,"OldProp");

      break;

   }

   return CallWindowProc(OldProc,hWnd,iMessage,wParam,lParam);

}

 

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

{

   CBT_CREATEWND *pCbt;

   HWND hWnd;

   TCHAR szClassName[32];

   WNDPROC OldProc;

 

   if (nCode == HCBT_CREATEWND) {

      hWnd=(HWND)wParam;

      pCbt=(CBT_CREATEWND *)lParam;

 

      if (HIWORD(pCbt->lpcs->lpszClass)) {

          lstrcpy(szClassName,pCbt->lpcs->lpszClass);

      } else {

          GlobalGetAtomName((ATOM)pCbt->lpcs->lpszClass,szClassName,32);

      }

 

      if (lstrcmpi(szClassName,"#32770")==0 && ((pCbt->lpcs->style & WS_CHILD)==0)) {

          OldProc=(WNDPROC)GetWindowLong(hWnd,GWL_WNDPROC);

          SetProp(hWnd,"OldProp",OldProc);

          SetWindowLong(hWnd,GWL_WNDPROC,(DWORD)NewWndProc);

      }

   }

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

}

 

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

   HDC hdc;

   PAINTSTRUCT ps;

 

   switch(iMessage) {

   case WM_CREATE:

      hCbtHook=SetWindowsHookEx(WH_CBT,CbtHookProc,NULL,GetCurrentThreadId());

      return 0;

   case WM_LBUTTONDOWN:

      MessageBox(hWnd," 메시지 박스는 부모 윈도우의 중앙에 나타납니다","알림",MB_OK);

      return 0;

   case WM_PAINT:

      hdc=BeginPaint(hWnd, &ps);

      EndPaint(hWnd, &ps);

      return 0;

   case WM_DESTROY:

      UnhookWindowsHookEx(hCbtHook);

      PostQuitMessage(0);

      return 0;

   }

   return(DefWindowProc(hWnd,iMessage,wParam,lParam));

}

 

WM_CRAETE에서 WH_CBT 훅을 설치하는데 메시지 박스는 같은 스레드내에서 생성되는 윈도우이므로 지역 훅을 설치하면 된다. WM_PAINT에서는 간단한 안내 메시지를 출력하고 WM_DESTROY에서는 훅을 제거했다. 마우스 왼쪽 버튼을 누르면 MessageBox 함수를 호출하여 메시지 박스를 띄운다. 별다른 처리를 하지 않는다면 메시지 박스는 항상 화면 중앙에 열리게 될 것이다.

CBT 훅 프로시저의 nCode로는 윈도우에 어떤 일이 발생했는지를 알려주는 다음과 같은 값이 전달되며 이때 wParam으로는 윈도우의 핸들이 전달되며 lParam으로는 메시지의 부가 정보가 전달된다. 다음 도표는 CBT 훅의 nCode값과 lParam 인수를 정리한 것인데 더 자세한 정보는 레퍼런스를 참고하기 바란다.

 

코드

설명

lParam

HCBT_ACTIVATE

윈도우 활성화

CBTACTIVATESTRUCT 구조체

HCBT_CREATEWND

윈도우 생성

CBT_CREATEWND 구조체

HCBT_DESTROYWND

윈도우 파괴

0

HCBT_MINMAX

최소 또는 최대화

하위 워드에 현재 상태(SW_*)

HCBT_MOVESIZE

이동 또는 크기 변경

윈도우의 현재 위치값을 가지는 RECT 구조체

HCBT_SYSCOMMAND

시스템 명령 실행

WM_SYSCOMMAND와 동일

 

리턴값으로는 해당 동작의 허가 여부를 리턴하는데 0을 리턴하면 동작을 허가하는 것이고 1을 리턴하면 금지하는 것이다. CBT 훅은 항상 해당 동작이 일어나기 전에 훅 프로시저에게 먼저 전달된다. 예를 들어 윈도우가 생성될 때 HCBT_CREATEWND 코드를 먼저 보낸 후 이 훅 프로시저가 0을 리턴하면 목표 윈도우로 WM_NCCREATE, WM_CREATE 메시지가 전달되며 윈도우가 파괴될 때도 WM_DESTROY 메시지를 보내기 전에 CBT훅의 HCBT_DESTROYWND 코드가 먼저 전달된다.

이 예제는 메시지 박스가 생성될 때 윈도우의 위치를 옮기고자 하므로 nCode가 HCBT_CREATEWND일 때 원하는 코드를 실행해야 한다. 이때 lParam으로는 다음과 같이 정의된 구조체의 포인터가 전달된다.

 

typedef struct {

    LPCREATESTRUCT lpcs;

    HWND hwndInsertAfter;

} CBT_CREATEWND, *LPCBT_CREATEWND;

 

lpcs는 CREATESTRUCT 구조체이며 hwndInsertAfter는 이 윈도우 바로 앞의 Z 순서를 가지는 윈도우 핸들이다. 훅 프로시저에서 이 구조체의 값을 직접 변경하면 윈도우의 위치나 크기, Z 순서를 바꿀 수 있다. lpcs->x, lpcs->y값을 조정하면 메시지 박스가 생성될 위치를 지정할 수 있는데 예제에서는 이 값을 직접 변경하지 않고 서브클래싱만 하고 있다. 왜냐하면 부모의 중앙 좌표를 구해야 하는데 윈도우가 생성되는 이 시점에는 아직 부모가 누구인지를 알 수 없기 때문이다. CBT훅은 동작이 일어나기 직전에 보내지므로 아직 이 윈도우는 만들어지지 않았으며 부모 자식 관계도 설정되어 있지 않다.

그래서 훅 프로시저는 서브클래싱만 해 놓고 서브클래스 프로시저의 WM_CREATE(또는 WM_INITDIALOG)에서 부모의 위치를 참조하여 부모의 중앙 위치로 가도록 했다. 여기서 WM_CREATE 메시지는 부모를 알 수 있는 최초의 시점이며 또한 이 윈도우가 보이기 전이므로 위치를 옮길 수 있는 최적의 위치에 해당된다. 원래 윈도우 프로시저의 번지는 별도의 전역 변수에 저장할 수도 있지만 윈도우 스스로 기억하도록 하기 위해 윈도우 프로퍼티를 사용했다. 이 윈도우는 파괴되기 직전에 윈도우 프로퍼티로부터 원래 윈도우 프로시저를 구해 자신의 서브클래싱을 직접 해제한다. 요약하자면 CBT 훅 프로시저는 윈도우가 생성되는 시점을 가로채서 서브클래싱만 하고 위치를 옮기는 작업은 서브클래스 프로시저가 하고 있는 셈이다.

훅 프로시저는 서브클래싱할 윈도우를 정확하게 선정해야 한다. CBT 훅은 모든 윈도우의 생성, 파괴, 이동 메시지를 받기 때문에 조건 점검을 정밀하게 하지 않으면 대화상자뿐만 아니라 대화상자안의 버튼이나 스태틱같은 컨트롤까지도 위치 이동의 대상이 되어 버리기 때문이다. 예제에서는 윈도우 클래스가 #32770, 즉 대화상자이고 차일드가 아닌지만으로 메시지 박스인지를 점검하고 있다. 윈도우가 좀 더 많은 프로젝트에서는 이보다 더 정밀한 조건 점검을 해야 할 것이다. 그렇지 않으면 원치않은 윈도우까지 위치가 이동되는 부작용이 발생한다.

예제를 실행하고 마우스 왼쪽 버튼을 눌러 보면 메시지 박스가 부모의 중앙에 열리게 될 것이다. 코드를 이해하는 것은 어렵지 않은데 그렇다면 메시지 박스의 위치를 옮기는데 왜 이렇게 복잡한 과정을 거쳐야 하는 것일까? 그 이유는 MessageBox라는 함수가 모달 대화상자를 열고 이 대화상자의 운용 일체를 관장하고 있기 때문이다. 이 함수를 호출하면 대화상자를 생성, 표시, 파괴하는 동작이 모두 이 함수내에서 일어나며 응용 프로그램이 메시지 박스에 대해 조작을 할 수 있는 기회가 없다. 그래서 훅을 설치하고 서브클래싱해서 생성 시점을 가로채는 복잡한 과정을 거쳐야 하는 것이다. 이 방법은 메시지 박스뿐만 아니라 공통 대화상자나 프로퍼티 시트 등의 컨트롤에도 동일하게 적용된다.

AND


초보자를 위한 Kernel based windows rootkit -1부-
1. 문서위치
beist.org/research/public/beginnerwinrootkit/beginnerwinrootkit.pdf

AND