Study
리버싱 핵심원리_21_Windows 메시지 후킹 본문
21. Windows 메시지 후킹
21.1 훅
- 훅(Hook) : 원하는 정보를 엿보거나 가로채는 도구
- 후킹(Hooking) : 실제로 정보를 엿보고 조작하는 행위
21.2 메시지 훅
Windows 운영체제는 GUI를 제공하고, 이는 Event Driven 방식으로 동작한다.
키보드를 입력하고, 마우스를 이용해 버튼 선택, 창 크기 변경, 창 이동 등의 클릭하는 등의 행위는 모두 이벤트이다.
이런 이벤트가 발생할 때 OS는 미리 정의된 메시지를 해당 응용 프로그램으로 통보한다.
응용 프로그램은 해당 메시지를 분석하여 필요한 작업을 진행하는 것이다.
[그림 21.1]
① 키보드 입력 이벤트가 발생하면 WM_KEYDOWN 메시지가 [OS message queue]에 추가된다.
② OS는 어느 응용 프로그램에서 이벤트가 발생했는지 파악해서 [OS message queue]에서 메시지를 꺼내어 해당 응용 프로그램의 [application queue]에 추가한다.
③ 응용 프로그램은 자신의 message queue를 모니터링하고 있다가 WM_KEYDOWN 메시지가 추가된 걸 확인하고 해당 event handler를 호출한다.
여기서 위 그림과 같이 키보드 메시지 훅이 설치되었다면 OS 메시지 큐와 응용 프로그램 메시지 큐 사이에 설치된 훅 체인에 있는 키보드 메시지 훅들이 응용 프로그램보다 먼저 해당 메시지를 볼 수 있다.
키보드 메시지 훅 함수 내에서는 메시지를 단순히 엿보는 기능뿐만 아니라 메시지 자체의 변경도 가능하며 또한 메시지를 가로채서 아래로 내려보내지 않게 할 수 있다.
※ 참고!
같은 종류의 훅이라도 여러 개를 동시에 설치할 수 있다. 이러한 훅은 설치 순서대로 호출되기 때문에 훅체인이라고 말한다.
21.3 SetWindowsHookEx()
메시지 훅은 SetWindowsHookEx() API를 사용해서 간단히 구현할 수 있다.
idHook
: 설치하고자 하는 훅의 타입을 지정한다.
lpfn
: 훅 프로시저의 주소.
hMod
: 훅 프로시저를 가진 모듈의 핸들(주소)이다.
dwThreadId
: 후킹 할 대상의 thread ID이다. 훅 프로시저가 감시할 스레드의 ID되 이 값이 0이면 시스템의 모든 스레드에서 발생하는 메시지가 훅 프로시저로 전달된다.
SetWindowsHookEx를 이용하여 훅을 설치해 놓으면 어떤 프로세스에서 메시지가 발생했을 때, 운영체제가 해당 dll 파일을 해당 프로세스에 강제로 인젝션 하고 등록된 hook procedure를 호출한다.
※ 여기서 잠깐!
훅을 설치할 때, 대상 프로세스와 dll 간에 시스템이 같아야 한다. 즉, 32bit 프로세스에 64bit dll을 삽입할 수 없고 그 반대도 마찬가지이다.
21.4 키보드 메시지 후킹 실습
21.4.1 실습 예제 HookMain.exe
실습 환경은 VMWare에 설치한 windows 7으로 했다. 원래 시스템은 8인데 알 수 없는 오류가 나면서 자꾸 멈췄다.
cmd를 열어 위와 같이 파일을 실행시킨 후 메모장을 열어 키 입력을 해보자. 그러면 메모장은 사용자의 키 입력을 받지 못할 것이다.
Process Explorer를 열어 notepad에 사용 중인 dll을 확인하자.
▲
맨 아래에 KeyHook.dll이 인젝션 되어 있음을 확인할 수 있다.
Process Explorer의 [Find] → [Find Handle or DLL..] 메뉴를 선택한 후 KeyHook.dll을 검색해보면
▲
위와 같이 KeyHook.dll을 로드한 프로세스 목록을 볼 수 있다.
만약 키보드 훅을 종료하고 싶다면 아까의 cmd 창에서 q를 눌러 함수를 종료한다.
그리고 다시 [Find Handle or DLL..]에 KeyHook.dll을 검색하면 목록에 어떤 프로세스도 나오지 않는다.
21.4.2 소스코드 분석
[코드21.1]
▲
KeyHook.dll을 로드한 후 dll로부터 HOOKSTART와 HOOKSTOP 함수의 주소를 얻는다.
그 후 HookStart로 후킹 시작. (q를 누르면 HookStop 호출)
[코드21.2]
▲
HookStart 함수가 호출되면 SetWindowsHookEx()에 의해 키보드 훅 체인에 KeyboardProc()가 추가된다.
KeyboardProc() 함수의 내용을 먼저 보면, 키보드 입력이 발생했을 때 현재 프로세스 이름과 "notepad.exe" 문자열과 비교하여 만약 같다면 1을 리턴해서 KeyboardProc() 함수를 종료시킨다.
이것은 메시지를 가로채서 없애버리는 동작인 것이다.
그 외의 경우에는 return CallNextHookEx(g_hHook, nCode, wParam, lParam); 명령을 실행하면 메시지는 다른 응용 프로그램 혹은 훅 체인의 또 다른 훅 함수로 전달된다.
이것이 바로 dwThreadId 옵션을 0으로 했음에도 다른 프로세스는 키 입력을 정상적으로 받을 수 있는 이유이다.
메시지를 받아서 없애거나 다음 훅으로 넘기는 게 아니라 CallNextHookEx를 통해 응용프로그램으로 넘긴다.
※ 참고!
MSDN에 정의된 KeyboardProc()의 정의이다.
wParam
: 사용자가 누른 키보드의 virtual key code를 의미한다. 키보드의 하드웨어적인 의미로써 영문 'A'와 'a' 그리고 한글 'ㅁ'은 모두 같은 virtual key code를 가진다.
lParam
: 각 비트별로 다양한 의미를 가지고 있다.
repeat count, scan code, exttended-key flag, context code, previous key-state flag,,transition-state flag
ToAscii() 함수를 이용하여 실제 눌린 키보드의 ASCII 값을 구할 수 있다.
이렇게 키보드 훅이 설치된 상황에서 어떤 프로세스에서 키 입력 이벤트가 발생하면 OS는 해당 프로세스에게 강제로 KeyHook.dll을 인젝션 한다. 이제 KeyHook.dll을 로딩한 프로세스에서 키보드 이벤트가 발생하면 KeyHook.KeyboardProc()이 먼저 호출된다.
이 과정을 그림으로 보면 다음과 같다.
(주목할 점! 키 입력 이벤트가 발생한 프로세스에게 OS가 KeyHook.dll을 강제로 로딩시켜준다!)
21.5 디버깅 실습
21.5.1 HookMain.exe 디버깅
Ollydbg로 HookMain.exe를 열어보자. 처음 나오는 코드는 전형적인 VC++의 Stub 코드이다.
우리가 관심 있는 핵심 코드는 이 부분이 아니니 문자열을 이용해 코드를 찾아간다.
▲
cmd에서 HookMain.exe를 실행시켰을 때 나왔던 문자열이다. 더블클릭하여 찾아간다.
▲
찾던 문자열이 속한 함수의 흐름을 보며 함수 시작점을 찾는다. (그냥 스크롤을 끝까지 올려도 된다)
맨 처음 호출하는 함수가 LoadLibraryA 함수이다. 조금 더 내려가면 GetProcAddress를 이용하여 HookStart와 HookStop 주소를 찾는다.
여기가 바로 HookMain.exe의 main() 함수이다. BP를 걸고 한 줄씩 트레이싱 해보자.
40104B의 CALL EBX 명령어는 HookStart를 호출하는 함수이다. 함수를 따라 안으로 들어가 보면
▲
100010E0(인젝션 된 dll 영역) 주소가 나타난다. 조금 밑으로 내려가면 SetWindowsHookExW() 함수 호출이 보인다.
그 바로 위에 함수를 호출하기 전 필요한 파라미터를 스택에 저장하는 것을 볼 수 있다.
dwThreadId
: 0(시스템에서 발생되는 모든 이벤트를 훅 프로시저로 전달)
hMod
: 10000000(KeyHook.dll 시작 주소)
lpfn
: 10001020(훅 프로시저의 주소)
idHook
: 2(WH_KEYBOARD 옵션)
21.5.2 Notepad.exe 프로세스 내의 KeyHook.dll 디버깅
notepad.exe 파일을 Ollydbg로 열거나 실행 중인 파일을 Ollydbg에 Attach 한다. 그 후 F9를 눌러 프로그램이 완전하게 동작하도록 만들어준다.
notepad를 사용 가능한 상태로 만들었다면 [Alt] + [O]를 이용하여 Debugging options 창을 띄운다.
[Event] 탭에 들어가 [Break on new module (DLL)]을 체크해준다.
이는 새로 인젝션 된 dll이 있을 때 해당 dll 시작 지점에서 Break Point가 작동하도록 돕는 옵션이다.
이 상태에서 HookMain.exe를 실행하고 메모장에 아무 키나 입력해본다.
※ 여기서 잠깐!
① 이때 cmd 창으로 HookMain.exe를 실행시키는데, cmd를 관리자 권한으로 실행시켜야만 제대로 작동한다. 그렇지 않으면 아무 일도 일어나지 않고 메모장에 키 입력이 너무 잘 된다.
② Ollydbg 1.0을 이용하면 다음과 같이 추가된 dll 목록과 함께 디버깅이 멈춘다.
▲
그러나 KeyHook.dll을 찾을 수가 없는 문제점이 있다.
그러면 Ollydbg 2.0을 이용해보자. HookMain.exe를 실행시킨 후 메모장에 아무 키나 입력해보면
▲
위와 같이 Executable modules 창은 뜨지 않지만 KeyHook.dll의 진입점으로 들어간다.
▲
10001020 주소로 가서 BP를 건다. 이곳은 KeyboardProc() 코드이다.
그 후 메모장에 키 입력을 하면 이곳에서 디버깅이 멈추는 것을 확인할 수 있다.
※ 참고!
최초 HookMain.exe 실행 후 키 입력하면 메모장이 윈도우 맨 뒤로 숨어버리며 더 이상 클릭이 듣지 않는다.
이때 실행했던 HookMain.exe를 잠시 종료했다가 다시 킨 후 10001020 주소에 다시 BP를 걸면 메모장에 키 입력 때마다 프로시저에서 디버깅이 멈추는 것을 볼 수 있다.
예상 못 한 오류가 많은 실습 단원이다.
21.6 Comment
실습하며 여러 번 책대로 되지 않았기 때문에 실습을 포기할 뻔했으나 붙잡고 이렇게 저렇게 해보니 답은 있었다.
포기하지 말고 다양한 방법으로 시도해보시길!
'Reversing > 리버싱 핵심원리' 카테고리의 다른 글
리버싱 핵심원리_20_인라인 패치 실습 (0) | 2019.02.21 |
---|---|
리버싱 핵심원리_18_UPack PE 헤더 상세 분석 (0) | 2019.02.18 |
리버싱 핵심원리_17_실행 파일에서 .reloc 섹션 제거하기 (0) | 2019.02.10 |
리버싱 핵심원리_16_Base Relocation Table (0) | 2019.02.10 |
리버싱 핵심원리_15_UPX 실행 압축된 notepad 디버깅 (0) | 2019.02.05 |