[c++] IAT 호출 vs GetProcAddress 호출

wangki·2025년 7월 2일
0

cpp

목록 보기
5/7

IAT 호출 vs GetProcAddress 호출

#include <iostream>
#include <windows.h>

typedef int (*MessageBoxWFn)(HWND, LPCWSTR, LPCWSTR, UINT);

int main()
{
    MessageBoxW(NULL, L"asdf", L"test", MB_OK);

    HMODULE module = LoadLibraryW(L"User32.dll");
    if (module == NULL) {
        return 0;
    }

    MessageBoxWFn message_box_fn = (MessageBoxWFn)GetProcAddress(module, "MessageBoxW");

    if (message_box_fn == NULL) {
        FreeLibrary(module);
        return 0;
    }

    message_box_fn(NULL, L"test", L"test", MB_OK);

    FreeLibrary(module);
    return 1;
}

break point를 설정 후 디스어셈블리로 어셈블리어를 보면

    MessageBoxW(NULL, L"asdf", L"test", MB_OK);
00007FF753F8183C  xor         r9d,r9d  
00007FF753F8183F  lea         r8,[string L"test" (07FF753F89BB0h)]  
00007FF753F81846  lea         rdx,[string L"asdf" (07FF753F89BC0h)]  
00007FF753F8184D  xor         ecx,ecx  
00007FF753F8184F  call        qword ptr [__imp_MessageBoxW (07FF753F90158h)]  
00007FF753F81855  nop  

위 처럼 나온다.
분석을 해보자면 ecx는 NULL, rdxr8에는 문자열이 저장된 주솟 값, 그리고 r9d도 0으로 만드는 코드이다.

00007FF753F8184F  call        qword ptr [__imp_MessageBoxW (07FF753F90158h)]  

위 어셈블리어의 의미는 __imp_MessageBoxW가 가르키는 주솟 값으로 이동 후 8바이트를 읽어서 호출해라라는 의미이다.
즉, 실제 MessageBoxW의 정의에 연결된 주소값을 콜하는 코드이다.
iat에서 읽어온다고 보면된다.

그러면 GetProcAddress를 통해서 불러올 때 차이가 뭔지 보겠다.

    message_box_fn(NULL, L"test", L"test", MB_OK);
00007FF753F8189D  xor         r9d,r9d  
00007FF753F818A0  lea         r8,[string L"test" (07FF753F89BB0h)]  
00007FF753F818A7  lea         rdx,[string L"test" (07FF753F89BB0h)]  
00007FF753F818AE  xor         ecx,ecx  
00007FF753F818B0  call        qword ptr [message_box_fn]  
00007FF753F818B3  nop  

message_box_fn이 가르키는 주솟 값의 8바이트를 읽어서 콜을 해준다.
즉, iat에서 참조해서 가져오지 않는다.

MessageBoxWmessage_box_fn의 주솟값을 찍으면 같을 수 있다.
그러나 iat hook을 한 경우 두 주솟값이 다르게 된다. 왜냐하면 GetProcAddressUser32.dllexport table을 참조하기 때문이다.

windbg로 살펴보기

// ConsoleApplication14
#include <iostream>
#include <windows.h>

typedef void (*DoSomethingFn)();

int main()
{
    /*std::string temp;
    std::cin >> temp;*/

    MessageBoxW(NULL, L"asdf", L"test", MB_OK);

    HMODULE module = LoadLibraryW(L"test.dll");

    if (module == NULL) {
        return 0;
    }

    DoSomethingFn do_something_fn = (DoSomethingFn)GetProcAddress(module, "DoSomething");

    if (do_something_fn == NULL) {
        FreeLibrary(module);
        return 0;
    }

    
    do_something_fn();

    printf("IAT Call MessageBoxW: %p\n", MessageBoxW);
    //printf("GetProcAddress call message_box_fn: %p\n", message_box_fn);

    FreeLibrary(module);
    return 1;
}
// test.dll
// dllmain.cpp : DLL 애플리케이션의 진입점을 정의합니다.
#include "pch.h"
#include <windows.h>
#include <stdio.h>


typedef int (*MessageBoxWFn)(HWND, LPCWSTR, LPCWSTR, UINT);


extern "C" __declspec(dllexport) void DoSomething() {
    HMODULE module = LoadLibraryW(L"User32.dll");
    if (module == NULL) {
        return;
    }

    MessageBoxWFn message_box_fn = (MessageBoxWFn)GetProcAddress(module, "MessageBoxW");

    if (message_box_fn == NULL) {
        FreeLibrary(module);
        return;
    }

    message_box_fn(NULL, L"test", L"test", MB_OK);

    printf("message_box_fn address: %p\n", message_box_fn);

    FreeLibrary(module);
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH: {
        MessageBoxW(NULL, L"hello! this is test dll", L"test", MB_OK);
    }
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

한 번이라도 MessageBoxW를 호출해 주어야 User32.dll이 디스크립터로 만들어진다.

test.dll로 빌드하여 ConsoleApplication14의 실행 파일이 존재하는 디렉토리에 넣어주었다.

windbg를 통해서 ConsoleApplication14를 실행해 주었다.

!dh user32 -e

user32.dll의 export table을 볼 수 있는 명령어이다.

MessageBoxW의 주솟값은 00007FFA0A1B91F0이다.

!dh ConsoleApplication14 -i

iat를 보는 방법이다.

MeesageBoxW의 주솟값은 00007FFA0A1B91F0export table의 주솟값과 같은 것을 확인할 수 있다.

만약에 iat hook을 통해서 ConsoleApplication14User32.dllMessageBoxW를 후킹했다고 가정해본다.

iat를 참조해서 부르는 경우에는 정상적으로 후킹이 되겠지만, GetProcAddress를 통해서 export table에서 참조하는 경우는 원형 함수가 호출 될 것이다.
따라서 iat hook이 정상적으로 동작하지 않을 것이다.

결론

실무를 하면서 iat hook이 작동하지 않는 케이스가 여러 가지 있었는데 이번에 위와 같은 케이스를 발견했고 스터디하게 되었다. 위와 같은 케이스는 detour로 처리를 하면 된다.

0개의 댓글