Windows 운영체제에서는 키보드나 마우스 같은 입력장치로 메뉴 선택, 버튼 선택, 마우스 이동, 스크롤 등등은 모두 event로 칭한다. 이벤트가 발생하면 OS는 미리 정의된 메시지를 해당 응용프로그램으로 보낸다.
그럼 응용프로그램은 그 메시지를 분석하여 필요한 작업을 진행한다. 이런 메시지를 중간에서 가로채서 엿보거나 조작하는 것이 메시지 훅이다.
- 입력 이벤트 발생
예를 들어 키보드 입력 이벤트가 발생하면 WM_KEYDOWN ("키가 눌렸어요"라는 뜻) 메시지가 OS Message Queue에 추가된다.
- 이벤트 발생 프로세스 파악
OS는 어느 응용 프로그램에서 이벤트가 발생했는지 파악해서 OS Message Queue에서 메시지를 꺼내서 해당 응용 프로그램의 Application Message Queue에 추가한다.
- 이벤트 핸들러 호출
응용프로그램은 자신의 Application message queue를 모니터하다가 새로 들어온 WM_KEYDOWN 메시지를 그 메시지에 맞는 Event Handler를 호출한다.
여기서 만약 2번 과정에서 os queue에서 application queue로 메시지가 전달되는 과정에 Hook이 설치되었다면 우리가 먼저 메시지를 읽거나 가로채거나 조작하거나 메시지를 application queue에 못 가게 만들 수가 있다.
SetWindowsHookEx()라는 API를 사용하면 메시지 훅을 간단하게 구현할 수 있다.
SetWindowsHookEx() API는 이렇게 정의되어 있다.
hook procedure : 운영체제가 호출해주는 콜백함수. Call back이라는 건 어떤 이벤트가 발생하면 이 함수를 호출해줘 라고 지정해놓는 것이다.
메세지 훅을 걸려면 hook procedure는 dll 내부에 존재해야 하고 그 dll의 인스턴스 핸들이 hMod라는데 이거 뭔 말이야 ㅡㅡ
이 그림을 이해해보자.
HookMain.exe는 KeyHook.dll을 최초로 로딩해서 키보드 훅을 설치한다. 이 KeyHook.dll에는 hook procedure가 존재한다고 했다. hook procedure는 이벤트가 발생할 때 호출되는 함수라고 했지. 그게 KeyboardProc()이야.
그 밑에 SetWindowsHookEX()API가 있다. KeyHook.dll이 로드되면 이 api를 이용해서 keyboardProc이라는 키보드 훅이 설치되는 것이다.
자 만약 다른 응용프로그램인 notepad나 explore나 iexplore 같은 곳에서 키 입력이라는 이벤트가 발생했다고 치자.
그럼 OS는 프로세스에 메모리 공간에 keyhook.dll 파일을 강제 로딩해
SetWindowsHookEX가 keyboardProc 호출해
KeyboardProc 설치돼
hookmain은 이렇게 실행시키면 된다.
hookmain을 실행시키고 notepad를 실행시키면, 아무런 키보드가 입력되지 않을 것이다.
이 친구의 소스코들들 함 보자.
심플하다.
KeyHook.dll을 로딩하다가 HookStart() 함수가 호출되면 후킹을 시작하는 거고 키 입력에 q가 들어오면 HookStop() 함수가 호출되어 후킹을 종료한다.
자 그럼 여기서 로딩하는 KeyHook.dll에 들어있는 후킹을 담당하는 KeyHook.cpp를 열어보자.원리를 설명해보자.
HookStart() 함수가 호출된다.
SetWindowsHookEx()가 실행된다.
KeyboardProc()가 키보드 훅 체인에 추가된다.
이 원리가 헷갈릴 땐 위에 설명을 다시 보고 오면 된다.
개념 | 실제 |
---|---|
후킹 프로그램 | HookMain |
dll 로딩함 | KeyHook.dll |
API가 키훅을 걸어줌 | SetWindowsHookEx() |
dll에는 hook procedure라는 콜백함수가 존재 | KeyboardProc()이 호출된다. |
이제 어떤 프로세스(메모장)에서 키 입력 이벤트가 발생하면 OS는 해당 프로세스에게 강제로 KeyHook.dll을 인젝션해준다. 이제 dll이 로드된 프로세스에서 키보드 이벤트 발생하면 KeyboardProc()이 먼저 호출되는거지.
이 hook procedure을 좀 더 자세히 봐볼까.
현재 프로세스의 이름을 따온다. 그 이름이"notepad.exe" 문자열과 같다면 return1 한다. return1은 함수를 종료시키는 것이다. 즉 메시지를 가로채서 없애버리는 것이다. 그래서 키보드의 메시지가 notepad.exe의 message queue에 전달되지 않는다.
그 밑에 있는 return CallNextHookEx(~~)는 뭐냐면 메모장에 발생한 이벤트가 아니니까 다른 응용 프로그램의 훅체인 또는 훅 함수로 보내버리는 것이다.
자 x96dbg로 HookMain.exe를 열어보자.지금 보이시는 장면은 전형적인 Stub Code라고 합니다. 그냥 눈에 익혀두세요.
문자열에 우리가 아는 press q to quit이 보이니까 그리로 가자.
가서 스크롤 좀만 올리면 바로 메인 함수의 시작이 보인다.
401006에서는 KeyHook.dll을 호출한다. LoadLibrary() 그 후 40104B에서 KeyHook.HookStart() 함수가 호출된다.
-> call ebx가 왜 함수 호출이냐고? 몰라. ebx에 HookStart()주소가 들어있나보지.
한번 step into로 들어가볼까
여기가 KeyHook.dll의 HookStart()함수다.
100010EF에선 Call SetWindowsHookExW() 명령어가 있다.
그 위의 push가 두개있다.
얘 파라미터가 두개 필요하다.
첫번째 파라미터는 idHook이었다.
이 값이 2다. 2는 WH_KEYBOARD(2)를 의미하는 듯.
두번째는 lpfn이다. 즉 콜백되는 hook procedure함수가 뭐냐는 거다. 그게 push 10001020 명령어니까 10001020이 hook procedure의 주소다.
디버거로 notepad를 열고 실행해준다.
교재에선 Break on new module(DLL)을 설정하라 했지만 비슷한거라도 일단 체크해뒀다. 새로운 DLL이 로드되면 디버깅을 멈추겠지.
이 옵션이 DLL이 인젝션 된 순간부터 디버깅하려고 할 때 유용하다고 한다.
그 다음 HookMain.exe를 실행해준다.
그리고서 노트패드에 키보드를 입력하면
이렇게 디버거에 KeyHook.dll이 로드되어서 거기서 멈췄다고 알려준다.
그리고 교재에선 여기서 마지막으로 인젝션된 이후에 콜백된 HookProcedure 함수인 KeyboardProc 주소인 10001020에 BP 걸어서 가보라는데 여긴 어떻게 하는건지 모르겠다.