이전 글

Detours Hooking Framework 1 - Hooking Function

Detours Hooking Framework 2 - Hooking API

Detours Hooking Framework 3-1 Hooking class member function

 

잠시 복습

지난 글에서 class member function을 호출했을 때 컴파일 된 assembly에 대해 살펴보았다. 나머지는 전부 제쳐두고 다음을 기억하면 됐다.


    int result1 = a.TestFunction(10);
008E1612  push        0Ah  
008E1614  lea         ecx,[a]  
008E1617  call        TestClass::TestFunction (8E111Dh)  

class의 instance의 주소를 ecx에 넣고 해당 함수를 콜 한다는 규칙이었다. 자, 이것을 기억해두고 class member function을 후킹해보자.

 

 

Hooking class member function


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

class TestClass{
private :
    int mInt;

public :
    TestClass() : mInt(0){}
    TestClass(int initValue) : mInt(initValue){}
    int TestFunction(int iValue);
};

int TestClass::TestFunction(int iValue){
    return mInt + iValue;
}

int HookingFunction(int value){
    std::cout<<"Hooked" <<std::endl;
    //여기서­ 진짜 멤버 함수를 호출해야한다.
    return 0;
}
int main(int argc, char** argv){
    TestClass a(10),b(20);

    int result1 = a.TestFunction(10);

    return 0;
}


위 코드에서 TestClass의 TestFunction함수를 후킹해보자. Detours를 사용하기 위해 TestClass::TestFunction과 HookingFunction의 address를 넘겨야 한다. 이때 main()함수의 코드는 다음과 같게 된다.


int HookingFunction(int value){
    std::cout<<"Hooked" <<std::endl;
    //여기서 진짜 멤버 함수를 호출해야 한다.
    return 0;
}

PVOID functionAddress ;

int main(int argc, char** argv){
    TestClass a(10),b(20);
    functionAddress = (PVOID)(&(PVOID&)TestClass::TestFunction);

    DWORD error;
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    DetourAttach((PVOID*)&functionAddress, HookingFunction);
    
    error = DetourTransactionCommit();

    int result1 = a.TestFunction(10);

    return 0;
}

위 작업으로 a.TestFunction(10)을 실행했을 때 HookingFunction이 호출되게 된다. 기존 TestClass::TestFunction이 존재하던 Address에 jmp HookingFunction이란 코드가 대치되었기 때문이다. 그런데 HookingFunction코드 내에서 어떻게 TestClass::TestFunction을 실행할 수 있을까?

 

TestClass::TestFunction 을 실행하기 위한 조건은 다음과 같았다. 먼저 TestClass의 instance, a의 주소를 ecx에 넘겨 주어야 하고 TestClass::TestFunction의 address를 call해야 한다. 그런데 문제가 있다. a의 주소를 HookingFunction 내에서 알아낼 방법이 없다는 것이다. a의 주소는 ecx로 넘어 오기 때문에 알 수 있지 않냐고 생각할 수 있겠지만 ecx를 일반함수 HookingFunction에서 보존한다는 보장이 없기 때문이다. 만약 ecx값을 보존한다면 다음과 같이 코딩하면 되겠지만 이는 실패한다.


int HookingFunction(int value){
    std::cout<<"Hooked" <<std::endl;
    int result;
    __asm push value;
    __asm call [functionAddress];
    __asm mov [result], eax;
    return result;
}


그럼 어떻게 해야할까? 답은 의외로 간단하다. 일반 함수로 Hooking하는 것이 아니라 클래스의 멤버함수로 Hooking하면 된다. 다음과 같이 말이다.


class HookingClass{
public:
    int HookingFunction(int value){
        std::cout<<"Hooked" <<std::endl;
        int result;
        __asm push value;
        __asm mov ecx, [this];
        __asm call [functionAddress];
        __asm mov [result], eax;
        return result;
    }
};


int main(int argc, char** argv){
    TestClass a(10),b(20);
    functionAddress = (PVOID)(&(PVOID&)TestClass::TestFunction);

    DWORD error;
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    DetourAttach((PVOID*)&functionAddress, (PVOID)(&(PVOID&)HookingClass::HookingFunction));
    
    error = DetourTransactionCommit();

    int result1 = a.TestFunction(10);

    return 0;
}


위 코드를 실행시키면 HookingClass::HookingFunction(int value)내에서 TestClass::TestFunction을 실행할 수 있음을 알 수 있다. __asm mov ecx, [this];  에서 [this]가 곧 instance a의 주소이다. 이때 __asm으로 어셈블리 코드를 직접 사용하는 것이 싫으면 다음과 같이 하면 된다.


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

class TestClass{
private :
    int mInt;

public :
    TestClass() : mInt(0){}
    TestClass(int initValue) : mInt(initValue){}
    int TestFunction(int iValue);
};

int TestClass::TestFunction(int iValue){
    return mInt + iValue;
}

class HookingClass{
public:
    int HookingFunction(int value);
    static int (HookingClass::*Real_Target)(int);
};

int HookingClass::HookingFunction(int value)
{
    std::cout<<"Hooked" <<std::endl;
    return (this->*Real_Target)(value);
}
int (HookingClass::* HookingClass::Real_Target)(int) = (int (HookingClass::*)(int))&TestClass::TestFunction;

int main(int argc, char** argv){
    TestClass a(10),b(20);

    DWORD error;
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    DetourAttach(&(PVOID&)HookingClass::Real_Target, (PVOID)(&(PVOID&)HookingClass::HookingFunction));
    
    error = DetourTransactionCommit();

    int result1 = a.TestFunction(10);

    return 0;
}


굉장히 아름다운 이 방법은 detours의 sample에 포함된 소스코드를 참조한 것이다. 붉은 색으로 된 코드를 보고 그 안에 담긴 의미를 음미해보라. 다음 마지막이 될 다음 포스트에는 virtual function을 후킹하는 법에 대하 알아보도록 한다.

 

신고

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를 후킹하는 방식과 완전히 동일하다.

신고

Detours Hooking Framework - 1. hooking function

프로그래밍 2011.06.16 00:37 Posted by 아일레프

최근 Hooking을 해야 할 일이 생겼다. 그런데 이 Hooking할 함수가 dll의 일반 export함수도 아니고, 클래스 멤버의 함수도 아니고, COM객체의 virtual 함수라 이리저리 많은 삽질을 해야했다. 그러다 Detours란 Hooking Framework를 만나게 되었는데 와, 이거 정말 훌륭하더라. 물론 Virtual 함수를 Hooking하는 것은 이리저리 많은 삽질을 거쳐야 했지만, 프로그래밍이란게 한번 알고 나면 너무 간단해 허무한 것이다. 고로 지금 허무하다.

 

설치.

Detours Hooking Framework 이 사이트에서 Detours Express 2.1을 다운로드 받고 설치하자. default 경로는 C:\Program Files (x86)\Microsoft Research\Detours Express 2.1 이다. 해당 폴더에 Command Prompt로 이동해 nmake all 명령을 실행하자. 본인은 nmake all 명령이 실패했는데 나의 문제는 Visual Studio의 Command Prompt를 이용하고 set DETOURS_TARGET_PROCESSOR =x86 을 설정함으로써 해결할 수 있었다.

 

Function Hooking

이제 Detours를 사용해보자. Visual Studio에 DetoursTest를 만들고 C/C++, Linker 환경을 설정하자. C/C++의 Additional Include Directories에 Detours Express의 Include 디렉토리를 추가하고, Linker의 Additional Library Directories에 Detours Express의 lib 디렉토리를 추가한다. 그리고 Linker의 Additional Dependencies에 detoured.lib, detours.lib를 추가한다. 그후 main.cpp란 파일을 추가 한 후 다음 코드를 넣자.


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

void TestFunction(){
    printf("TestFunction Called\n");
}

void HookingFunction(){
    printf("before - TestFunction call");
    TestFunction();
    printf("after - TestFunction call");
}

int main(int, char**){
    TestFunction();
    getchar();
}

main에서 TestFunction을 call했으므로 당연히 CMD 창에 "TestFunction Called" 문장이 나타날 것이다. 이제 TestFunction을 Hooking해 TestFunction()을 호출하면 HookingFunction()이 호출되게 할 것이다. 그리고 이 일은 굉장히 간단하다. 프로그램을 다음과 같이 바꿔보자.


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

void TestFunction(){
    printf("TestFunction Called\n");
}
void (*pfTrueTestFunction)() = TestFunction;
void HookingFunction(){
    printf("before - TestFunction call\n");
    TestFunction();
    //pfTrueTestFunction();
    printf("after - TestFunction call\n");
}

int main(int, char**){
    DWORD error;
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    DetourAttach((PVOID*)&pfTrueTestFunction, HookingFunction);
    error = DetourTransactionCommit();

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

프로그램을 실행하면 어떤 일이 벌어질까? before - TestFunction Call 문장이 반복되어 출력될 것이다. 원하는 동작을 원한다면 HookingFunction의 TestFunction()을 주석처리하고 pfTrueTestFunction function pointer로 기존 메소드를 출력하면 된다.

어떻게 이 일이 가능하게 되었을까? DetourAttach 함수 콜에 Breakpoint를 설정하고 TestFunction과 pfTrueTestFunction의 값을 확인해보자.

TestFunction 주소 값과 pfTrueTestFunction 값이 다르지만 pfTrueTestFunction 주소 값의 Assembly 명령어를 확인하면 Jmp 명령어로 TestFunction으로 점프하는 코드가 있음을 확인 할 수 있다. 그리고 그 명령어 위에 'TestFunction:'이 있는데 이것이 함수 symbol값이다. 

이제 DetourAttach명령과 DetourTransactionCommit()까지 프로그램을 진행시켜보자. Watch창으로 TestFunction과 pfTrueTestFunction을 다시 확인해보자. 재미있게도 pfTrueTestFunction의 주소 값이 바뀌었다.

pfTrueTestFunction의 값이 변경되었음을 알 수 있다. 0x010c0060메모리 주소를 보면 TestFunction의 메모리로 점프하는 코드가 있다.

 

더 재미있는 것은 0x010e12bc의 메모리주소의 값이 변경되었다는 사실에 있다.

TestFunction: 심볼 값에 Jmp TestFunction 명령이 있었는데 Jmp HookingFunction으로 변경되었다. 이것이 DetourAttach명령과 DetourTransactionCommit이 하는 일이다. 이것으로 후킹이 완료되었다. 

 

마치며

Detours로 특정 함수 심볼 값을 변경해 후킹을 해보았다. 이런 방식으로 기존의 exe파일이나 dll파일을 후킹할 수도 있다. detours의 bin 폴더 내에는 setdll이란 명령어가 있는데 이를 이용하면 된다. 이 setdll명령은 기존의 exe파일이나 dll파일에 외부의 dll을 로드하게하는 명령어이다. 예를 들어
setdll /d:dllInject.dll notepad.exe
명령을 실행시키면 이후에 notepad.exe를 실행했을 때 프로그램 내부에서 dllInject.dll이 로드되게 된다. 즉 LoadLibrary("dllInject.dll")이 실행되게 되는 것이다. 이 dll이 로드되면 dll내부의 DllMain함수가 실행되게 되고, 만약 DllMain함수 내에서 DetourAttach 등으로 Hooking동작을 하는 코드가 있다면 notepad.exe내에서 호출하는 함수, API들을 Hooking할 수 있다.
 

 2로 계속됩니다.

** 2001. 07. 01 수정.
setdll /d:dllInject.dll executable.exe를 실행시키면 dllInject.dll이 executable.exe에 Inject된다고 위에 설명했었다. 이것은 사실이다. 그런데 난 멋도 모르고 어떻게 추측했었냐면, .text의 EntryPoint에 LoadLibrary("dllInject.dll") 코드를 명시적으로 실행시켜 dllInject.dll의 DllMain 함수가 실행되게 할 줄 알았다. 하지만 사실은 이와 달라 정정한다. Dll Loading의 방식에는 2가지가 있는데 그 중 하나는 명시적 로딩, 즉 코드 내에서 LoadLibrary("dllInject.dll")이라고 명시적으로 코딩을 하는 방법이고, 또 하나는 암시적 로딩으로써 프로그램이 시작하는 순간에 같이 로딩되는 방법이 있다. 암시적 로딩을 위해 PE 파일은 어떤 DLL파일을 로드 해야 하는지 명시해 PE Loader에게 알려야 한다. Detours 는 PE파일의 이 부분을 변경한다. 즉, dllInject.dll이 필요하다고 PE Loader에게 알리는 것이다. 자세한 부분은 www.reversecore.com/23에 잘 설명되어 있다. 

신고


 

티스토리 툴바