Operating System/이해하면 인생이 바뀌는 Windows API hook

IAT hook

Tony Lim 2024. 12. 2. 17:44

IAT = Import Address Table

DLL 에서 가져다 쓴 함수들에대한 정보들을 가지고 있다고함

묵시적인 로딩인 경우에만 남아있는다

 

PE (portable excutable) 파일 구조

https://github.com/corkami/pics/blob/master/binary/pe101/pe101.pdf

 

RVA (Relative Virutal Address)

dll 이 가상메모리에 로딩이 되고 어떤 함수가 어디에 써있느냐를 나타낼떄 RVA를 통해 나타낸다.

 


IAT hook 기술 개요

  • 실행 파일이 가지고 있는 IAT 영역 정보를 직접 조작하는 방식으로 피호출 API함수를 변경하는 기법
  • C 언어 기반 응용 프로그램의 주요 함수들은 별도의 C Runtime Library(DLL) 로 구현되어 있으며 프로그램에서 호출 시 관련 정보가 IAT에 기술되어 있다.
  • IAT에 기술된 정보는 특정 라이브러리(DLL)에 대한 묵시적 로딩 방식 적용시에 생성되는 것이다.

 

묵시적 로딩을 통해 메모리에 dll들이 다 로딩이 되면 IAT에 해당 function으로 갈 수 있는 주소가 저장된다.

실제 런타임에 puts 를 호출하기 위해서 IAT에서 메모리 주소를 찾고 DLL로 가서 해당 함수를 실행하고 다시 testFunc로 돌아오게 된다.

IAT에 담긴 주소를 다르게 담게 하여서 전혀 다른 DLL 을 호출하게 만들 수 있다.

 

PIMAGE_IMPORT_DESCRIPTOR getImportTable()
{
    LPVOID baseAddress = GetModuleHandle(NULL);
    PIMAGE_DOS_HEADER dosHeaders = (PIMAGE_DOS_HEADER)baseAddress;
    PIMAGE_NT_HEADERS ntHeader =
        (PIMAGE_NT_HEADERS)((DWORD_PTR)baseAddress + dosHeaders->e_lfanew);
    IMAGE_DATA_DIRECTORY importTableDesc =
        ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];

    PIMAGE_IMPORT_DESCRIPTOR importTable = NULL;
    importTable = (PIMAGE_IMPORT_DESCRIPTOR)
        ((DWORD_PTR)baseAddress + importTableDesc.VirtualAddress);

    return importTable;
}

null을 인자로 주면 현재 process 에서 진행중인 exe (pe)를 불러온다. 이후 pe 구조대로 내부 정보들을 읽어온다

optional header를 기준으로 타고들어가서 import table (iat) 주소를 알아낸다.

int hookIAT(const char *pszfuncName, LPVOID fphookFunc,
    PIMAGE_IMPORT_DESCRIPTOR importTable)
{

    LPVOID pBaseAddress = GetModuleHandle(NULL);
    while (importTable->Name != NULL)
    {
        PIMAGE_THUNK_DATA impNameTable = (PIMAGE_THUNK_DATA)
            ((DWORD_PTR)pBaseAddress + importTable->OriginalFirstThunk);
        PIMAGE_THUNK_DATA impAddrTable = (PIMAGE_THUNK_DATA)
            ((DWORD_PTR)pBaseAddress + importTable->FirstThunk);
        while (impNameTable->u1.AddressOfData != NULL)
        {
            PIMAGE_IMPORT_BY_NAME importByName =
                (PIMAGE_IMPORT_BY_NAME)
                ((DWORD_PTR)pBaseAddress + impNameTable->u1.AddressOfData);
            cout << importByName->Name << endl;

            if (strcmp(importByName->Name, pszfuncName) == 0)
            {
                DWORD oldProtect;
                ::VirtualProtect(&impAddrTable->u1.Function,
                    sizeof(DWORD_PTR), PAGE_READWRITE, &oldProtect);
                impAddrTable->u1.Function = (DWORD_PTR)fphookFunc;
            }

            impNameTable++;
            impAddrTable++;
        }

        importTable++;
    }
    return 0;
}

import table에서 내가 원하는 함수가 나올 때까지 계속 while을 돌게 된다.

원하는 함수이름을 찾으면 impAddrtTable -> u1.Function 을 통해 주소를 fphooFunc로 바꿔치기한다.

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

using namespace std;

int MyMessageBox(
    HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
    MessageBoxW(NULL, L"MyMessageBox", L"*** Hooked ***", MB_OK);
    return 0;
}

PIMAGE_IMPORT_DESCRIPTOR getImportTable()
{
    LPVOID baseAddress = GetModuleHandle(NULL);
    PIMAGE_DOS_HEADER dosHeaders = (PIMAGE_DOS_HEADER)baseAddress;
    PIMAGE_NT_HEADERS ntHeader =
        (PIMAGE_NT_HEADERS)((DWORD_PTR)baseAddress + dosHeaders->e_lfanew);
    IMAGE_DATA_DIRECTORY importTableDesc =
        ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];

    PIMAGE_IMPORT_DESCRIPTOR importTable = NULL;
    importTable = (PIMAGE_IMPORT_DESCRIPTOR)
        ((DWORD_PTR)baseAddress + importTableDesc.VirtualAddress);

    return importTable;
}

int hookIAT(const char *pszfuncName, LPVOID fphookFunc,
    PIMAGE_IMPORT_DESCRIPTOR importTable)
{

    LPVOID pBaseAddress = GetModuleHandle(NULL);
    while (importTable->Name != NULL)
    {
        PIMAGE_THUNK_DATA impNameTable = (PIMAGE_THUNK_DATA)
            ((DWORD_PTR)pBaseAddress + importTable->OriginalFirstThunk);
        PIMAGE_THUNK_DATA impAddrTable = (PIMAGE_THUNK_DATA)
            ((DWORD_PTR)pBaseAddress + importTable->FirstThunk);
        while (impNameTable->u1.AddressOfData != NULL)
        {
            PIMAGE_IMPORT_BY_NAME importByName =
                (PIMAGE_IMPORT_BY_NAME)
                ((DWORD_PTR)pBaseAddress + impNameTable->u1.AddressOfData);
            cout << importByName->Name << endl;

            if (strcmp(importByName->Name, pszfuncName) == 0)
            {
                DWORD oldProtect;
                ::VirtualProtect(&impAddrTable->u1.Function,
                    sizeof(DWORD_PTR), PAGE_READWRITE, &oldProtect);
                impAddrTable->u1.Function = (DWORD_PTR)fphookFunc;
            }

            impNameTable++;
            impAddrTable++;
        }

        importTable++;
    }
    return 0;
}

int main(void)
{
    MessageBoxA(NULL, "Before IAT hook", "Hook test", MB_OK);

    PIMAGE_IMPORT_DESCRIPTOR importTable = getImportTable();
    hookIAT("MessageBoxA", (LPVOID)MyMessageBox, importTable );

    MessageBoxA(NULL, "After IAT hook", "Hook test", MB_OK);
    return 0;
}

MyMessageBox에서는 MessageBoxW 를 사용했는데 이는 stackoverflow와 손 쉬운 main return을 위해서 인것 같다.

https://www.inflearn.com/community/questions/1451438/iat-hook-%EA%B8%B0%EC%88%A0-%EA%B0%9C%EC%9A%94-%EC%97%90%EC%84%9C-%EC%A7%88%EB%AC%B8%EC%9D%B4%EC%9E%88%EC%8A%B5%EB%8B%88%EB%8B%A4#337622


IAT hook의 한계

  • 비교적 안정적인 기법이나 묵시적 로딩 방식을 채택하고 있는 경우에 대해서만 적용이 가능하다.
  • LoadLibrary 함수를 이용하는 Dynamic 방식을 채택한 실행 코드에 대해서 사실상 무의미하다.

 

 

 

 

 

 

'Operating System > 이해하면 인생이 바뀌는 Windows API hook' 카테고리의 다른 글

DLL injection  (0) 2024.12.13
Inline hook  (0) 2024.12.02
사전지식 - 두 번째  (0) 2024.11.27
사전 지식 - 첫 번째  (0) 2024.11.22