#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, rdx
와 r8
에는 문자열이 저장된 주솟 값, 그리고 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
에서 참조해서 가져오지 않는다.
MessageBoxW
와 message_box_fn
의 주솟값을 찍으면 같을 수 있다.
그러나 iat hook을 한 경우 두 주솟값이 다르게 된다. 왜냐하면 GetProcAddress
는 User32.dll
의 export table
을 참조하기 때문이다.
// 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
의 주솟값은 00007FFA0A1B91F0
로 export table
의 주솟값과 같은 것을 확인할 수 있다.
만약에 iat hook
을 통해서 ConsoleApplication14
의 User32.dll
의 MessageBoxW
를 후킹했다고 가정해본다.
iat
를 참조해서 부르는 경우에는 정상적으로 후킹이 되겠지만, GetProcAddress
를 통해서 export table
에서 참조하는 경우는 원형 함수가 호출 될 것이다.
따라서 iat hook이 정상적으로 동작하지 않을 것이다.
실무를 하면서 iat hook
이 작동하지 않는 케이스가 여러 가지 있었는데 이번에 위와 같은 케이스를 발견했고 스터디하게 되었다. 위와 같은 케이스는 detour
로 처리를 하면 된다.