Detours Hooking Framework -2. Hooking API

프로그래밍 2011.07.12 16:44 Posted by 아일레프

지난 글에서 Detours로 Function을 후킹하는 방법을 살펴보았다. 이 포스트에는 API를 Hooking하는 법을 알아본다. 물론, 매우 간단하고 쉽다.

 

API Hooking

API를 후킹하는 방법은 지난 글에서 말했던 방식과 완전히 동일하다. 단, 내부에서 Detours가 Hooking하는 방식은 다르다.


#include <Windows.h>
#include <stdio.h>
#include <detours.h>

int main(int, char **){
    ::MessageBoxA(NULL, "messageBox", "Hello", MB_OK);
}

위 코드에서 MessageBoxA를 후킹해 MessageBox 창의 caption을 "Hooked"로 바꿔보자. 코드는 다음과 같을 것이다.


#include <Windows.h>
#include <stdio.h>
#include <detours.h>

int (WINAPI *pfTrueMessageBox)(
    __in_opt HWND hWnd,
    __in_opt LPCSTR lpText,
    __in_opt LPCSTR lpCaption,
    __in UINT uType) = MessageBoxA;

int WINAPI HookingMessageBox(
    __in_opt HWND hWnd,
    __in_opt LPCSTR lpText,
    __in_opt LPCSTR lpCaption,
    __in UINT uType){
        
        return pfTrueMessageBox(hWnd, lpText, "Hooked", uType);
}

int main(int, char **){

    DWORD error;
    ::DetourTransactionBegin();
    ::DetourUpdateThread(::GetCurrentThread());
    ::DetourAttach((PVOID*)&pfTrueMessageBox, HookingMessageBox);
    error = DetourTransactionCommit();

    if(error != NO_ERROR){
        printf("fail to attach\n");
    }

    ::MessageBoxA(NULL, "messageBox", "Hello", MB_OK);

}

이 프로그램을 실행시키면 다음 결과를 확인할 수 있다.

Caption이 바뀌었음을 확인 할 수 있다. 자, 이제 내부에서 어떤 일이 벌어졌는지 확인해보자.

먼저 Hooking되기 이전의 MessageBoxA 함수를 호출하는 명령은 다음과 같다.

::MessageBoxA(NULL, "messageBox", "Hello", MB_OK);
00EC165C  mov         esi,esp 
00EC165E  push        0 
00EC1660  push        offset string "Hello" (0EC7854h) 
00EC1665  push        offset string "messageBox" (0EC7844h) 
00EC166A  push        0 
00EC166C  call        dword ptr [__imp__MessageBoxA@16 (0ECC404h)] 
00EC1672  cmp         esi,esp 
00EC1674  call        @ILT+445(__RTC_CheckEsp) (0EC11C2h) 

명령어를 통해 알 수 있듯이 __imp__MessageBoxA의 심볼 값 - 0xECC404h주소의 value 를 call하라는 명령어 이다. 그리고 0xECC404h주소에는 0x7670fd1e 값이 들어있다.

 

그리고 해당 주소에는 MessageBoxA 함수의 코드가 위치해 있다.

7670FD1E  mov         edi,edi 
7670FD20  push        ebp 
7670FD21  mov         ebp,esp 
7670FD23  push        0 
7670FD25  push        dword ptr [ebp+14h] 
7670FD28  push        dword ptr [ebp+10h] 
7670FD2B  push        dword ptr [ebp+0Ch] 
7670FD2E  push        dword ptr [ebp+8] 
7670FD31  call        7670FCD6 

또한 MessageBoxA의 function Pointer를 저장한 pfTrueMessageBox 의 값은 0x7670FD1E이다.

코드를 진행해 Detours로 Attach한 뒤의 Assembly 명령어를 살펴보면 다음과 같은 변화가 있다는 것을 알 수 있다.

7670FD1E  jmp         HookingMessageBox (0EC12BCh) 
7670FD23  push        0 
7670FD25  push        dword ptr [ebp+14h] 
7670FD28  push        dword ptr [ebp+10h] 
7670FD2B  push        dword ptr [ebp+0Ch] 
7670FD2E  push        dword ptr [ebp+8] 
7670FD31  call        7670FCD6 

먼저 위와 같이 0x7670FD1E주소의 명령어가 변경되었다. 그리고, pfTrueMessageBox의 값이 0x6fff0060으로 변경되었다.

그리고 0x6fff0060의 위치에는 다음 명령어가 존재함을 확인 할 수 있다.

6FFF0060  mov         edi,edi 
6FFF0062  push        ebp 
6FFF0063  mov         ebp,esp 
6FFF0065  jmp         7670FD23

6FFF0060 - 6FFF0063은 Detour로 Attach하기 전 0x7670FD1E - 0x7670FD21의 명령어였다.  

정리해보자. 이전 포스트에서 Detours는 단순히 함수 심볼 값의 명령어를 변경할 뿐이었다. 하지만 이번에는 새로운 명령어가 기존의 위치에 추가되고, 기존의 명령어가 다른 위치로 이동됨으로써 Hooking 동작이 이루어졌다. 물론 다른 방식으로 이 API후킹이 이루어질 수도 있다. 0xECC404h 주소의 값에 HookingMessageBox 주소(0x0EC12BCh)를 적어 놓는 것도 하나의 방법일 것이다. 하지만 Detour는 기존의 코드를 변경하는 방식을 택했다는 것을 확인해주기 바란다. 만약 Detours를 통하지 않고 직접 API를 후킹하고 싶다면 www.reversecore.com/63을 확인하면 된다. 정말 자세히 잘 설명 되어있다. 다음에는LoadLibrary, GetProcAddress를 통해 얻어진 API를 후킹하는 법에 대해 살펴볼 것이다.

 

 

** 2011. 07. 12 추가 내용

LoadLibrary, GetProcAddress로 얻어진 함수를 후킹하는 방법을 새 글로 다루려고 했는데 사실 이것은 이 포스트의 내용과 다르지 않기에 여기에 추가한다.

위에서 MessageBoxA함수를 호출할 수 있는 것은 User32.dll이 동적 링크 되었기 때문이다. 그런데 User32.dll을 동적 링크하라는 명령이 보다시피 프로그램내에서 존재하지 않고 있다. 이는 User32.dll이 암시적으로 동적로딩되었기 때문이다. (PE Loader가 프로그램이 시작할 때 이 일을 한다.) 하지만 때때로 프로그래머는 명시적으로 dll을 로딩하고 해당 dll의 exported된 함수를 사용하고 싶을 때가 있는데 이때 사용하는 명령어가 LoadLibrary, GetProcAddress이다. 위 프로그램과 같은 프로그램을 만들되, 명시적 Dynamic Link를 사용해보자. 프로그램은 다음과 같을 것이다.


typedef int (WINAPI *PFMessageBox)(
    __in_opt HWND hWnd,
    __in_opt LPCSTR lpText,
    __in_opt LPCSTR lpCaption,
    __in UINT uType);

PFMessageBox pfTrueMessageBox;
int main(int, char **){
    HMODULE user32Module = LoadLibrary(TEXT("user32.dll"));

PFMessageBox messageBox = (PFMessageBox)::GetProcAddress(user32Module,
"MessageBoxA"); messageBox(NULL, "messageBox", "Hello", MB_OK); }


위 프로그램을 Detours로 후킹하면 다음과 같다.


#include <Windows.h>
#include <stdio.h>
#include <detours.h>

typedef int (WINAPI *PFMessageBox)(
    __in_opt HWND hWnd,
    __in_opt LPCSTR lpText,
    __in_opt LPCSTR lpCaption,
    __in UINT uType);

PFMessageBox pfTrueMessageBox;

int WINAPI HookingMessageBox(
    __in_opt HWND hWnd,
    __in_opt LPCSTR lpText,
    __in_opt LPCSTR lpCaption,
    __in UINT uType){
        
        return pfTrueMessageBox(hWnd, lpText, "Hooked", uType);
}

int main(int, char **){
    HMODULE user32Module = LoadLibrary(TEXT("user32.dll"));
    pfTrueMessageBox = (PFMessageBox)::GetProcAddress(user32Module, "MessageBoxA");
    DWORD error;
    ::DetourTransactionBegin();
    ::DetourUpdateThread(::GetCurrentThread());
    ::DetourAttach((PVOID*)&pfTrueMessageBox, HookingMessageBox);
    error = DetourTransactionCommit();

    if(error != NO_ERROR){
        printf("fail to attach\n");
    }
    
    PFMessageBox messageBox = (PFMessageBox)::GetProcAddress(user32Module, "MessageBoxA");
    messageBox(NULL, "messageBox", "Hello", MB_OK);

}


pfTrueMessageBox와 messageBox 함수 포인터가 가리키는 주소가 다르다는 것에 주의해야 한다. pfTrueMessageBox에는 기존의 MessageBoxA를 호출하는 명령 있으며 messageBox에는 HookingMessageBox를 호출하는 명령이 있다. 그리고 이 일을 하기 위해 Detours가 하는 일은 암시적 동적 링크된 API를 후킹하는 방식과 완전히 동일하다.

신고