미친해커

[Reversing] Code Injection + IAT Hooking 본문

Reversing

[Reversing] Code Injection + IAT Hooking

미친해커 2022. 4. 22. 05:07
반응형

코드 인젝션과 IAT 후킹의 조합

코드 인젝션은 Fileless 형식의 임의의 코드 주입 공격이다. 그리고 IAT 후킹은 프로세스의 Import Address Table을 조작하여 특정 API의 제어권을 가로채는 기법이다.

이 두개의 기법을 적절히 활용하면 Fileless 형식의 IAT 후킹을 할수 있다.

예제 코드

 

GitHub - jungjin0003/Code-Injection: Code Injection + Hooking sample

Code Injection + Hooking sample. Contribute to jungjin0003/Code-Injection development by creating an account on GitHub.

github.com

Code Injector

소스코드는 아래 더보기에 있다.

더보기
#include <stdio.h>
#include <windows.h>
#include <winternl.h>

#define IFREE(HeapBase) (RtlFreeHeap(_get_heap_handle(), 0, (HeapBase)) || 1)
#define Upper(s1)       (s1 >= 65 && s1 <= 90 ? (char)s1 + 32 : s1)

WINBOOL CodePatch(HANDLE hProcess, LPVOID lpBaseAddress, LPCVOID lpBuffer, SIZE_T nSize)
{
    SIZE_T lpNumberOfBytesWritten = 0;
    return WriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, &lpNumberOfBytesWritten) && nSize == lpNumberOfBytesWritten;
}

LPVOID FindIATAddress(HANDLE hProcess, LPCSTR lpModuleName, LPCSTR lpProcName)
{
    PROCESS_BASIC_INFORMATION pbi = { 0, };
    // NtQueryInformationProcess 함수로 타겟 프로세스의 PEB 주소를 구한다.
    NtQueryInformationProcess(hProcess, ProcessBasicInformation, &pbi, sizeof(PROCESS_BASIC_INFORMATION), NULL);
    
    PVOID BaseAddress;
    // PEB 구조체에서 ImageBase를 읽어온다.
    if (ReadProcessMemory(hProcess, (ULONG_PTR)pbi.PebBaseAddress + 0x10, &BaseAddress, sizeof(ULONG_PTR), NULL) == FALSE)
    {
        return NULL;
    }

    IMAGE_DOS_HEADER *DOS = malloc(sizeof(IMAGE_DOS_HEADER));
    // 타겟 프로세스의 IMAGE_DOS_HEADER를 읽어온다.
    if (ReadProcessMemory(hProcess, BaseAddress, DOS, sizeof(IMAGE_DOS_HEADER), NULL) == FALSE && IFREE(DOS))
    {
        return NULL;
    }

    IMAGE_NT_HEADERS *NT = malloc(sizeof(IMAGE_NT_HEADERS));
    // 타겟 프로세스의 IMAGE_NT_HEADER를 읽어온다.
    if (ReadProcessMemory(hProcess, (ULONG_PTR)BaseAddress + DOS->e_lfanew, NT, sizeof(IMAGE_NT_HEADERS), NULL) == FALSE && IFREE(DOS) && IFREE(NT))
    {
        return NULL;
    }

    free(DOS);

    IMAGE_IMPORT_DESCRIPTOR *IMPORT = malloc(NT->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size);
    PVOID *FirstImport = IMPORT;
    // NT Header에서 IMAGE_IMPORT_DESCRIPTOR를 읽어온다.
    if (ReadProcessMemory(hProcess, (ULONG_PTR)BaseAddress + NT->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress, IMPORT, NT->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size, NULL) == FALSE && IFREE(NT) && IFREE(IMPORT))
    {
        return NULL;
    }

    free(NT);
    // 타겟 DLL의 IMAGE_IMPORT_DESCRIPTOR를 찾는다.
    for (; IMPORT->OriginalFirstThunk != NULL; IMPORT++)
    {
        // 현재 IMAGE_IMPORT_DESCRIPTOR의 DLL 이름과 타겟 DLL이름을 비교
        for (char ch, i = 0; ReadProcessMemory(hProcess, (ULONG_PTR)BaseAddress + IMPORT->Name + i, &ch, 1, NULL); i++)
        {
            if (Upper(ch) != Upper(lpModuleName[i]))
                break;
            else if (ch == NULL && lpModuleName[i] == NULL)
                goto FOUND_MODULE;
        }
        continue;
FOUND_MODULE:
        break;
    }

    if (IMPORT->OriginalFirstThunk == NULL)
        return NULL;

    PVOID IATAddress = NULL;

    IMAGE_THUNK_DATA *THUNK = malloc(sizeof(IMAGE_THUNK_DATA));
    // 타겟 API의 IAT 주소를 구한다.
    for (int i = 0; ReadProcessMemory(hProcess, (ULONG_PTR)BaseAddress + IMPORT->OriginalFirstThunk + i * sizeof(void *), THUNK, sizeof(IMAGE_THUNK_DATA), NULL) && THUNK->u1.AddressOfData != NULL; i++)
    {
        if (THUNK->u1.Ordinal >= 0x80000000)
            continue;
        // Compare api name of current IMPORT->OriginalFirstThunk to target api name
        for (char ch, j = 0; ReadProcessMemory(hProcess, (ULONG_PTR)BaseAddress + THUNK->u1.AddressOfData + j + 2, &ch, 1, NULL); j++)
        {
            if (ch != lpProcName[j])
                break;
            else if (ch == NULL && lpProcName[j] == NULL)
            {
                IATAddress = (ULONG_PTR)BaseAddress + IMPORT->FirstThunk + i * sizeof(void *);
                goto FOUND_IAT;
            }
        }
    }
FOUND_IAT:
    free(THUNK);
    free(FirstImport);
    return IATAddress;
}

PVOID CodeInjection(HANDLE hProcess, PVOID lpBuffer, SIZE_T nSize)
{
    // 타겟 프로세스의 공간에 메모리 할당
    PVOID BufferAddress = VirtualAllocEx(hProcess, NULL, nSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    if (BufferAddress == NULL)
    {
        return NULL;
    }

    SIZE_T NumberOfBytesWritten;
    // 할당한 공간에 lpBuffer의 데이터를 작성
    if (WriteProcessMemory(hProcess, BufferAddress, lpBuffer, nSize, &NumberOfBytesWritten) == FALSE || nSize > NumberOfBytesWritten)
    {
        VirtualFreeEx(hProcess, BufferAddress, 0, MEM_DECOMMIT);
        VirtualFreeEx(hProcess, BufferAddress, 0, MEM_RELEASE);
        return NULL;
    }

    return BufferAddress;
}

DWORD SearchCodePatchOffset(PVOID lpFunctionAddress, SIZE_T FunctionSize)
{
    for (int i = 0; i < FunctionSize - 8; i++)
    {
        // x86
        if (__SIZEOF_POINTER__ == 4 && *(ULONG_PTR *)((ULONG_PTR)lpFunctionAddress + i) == 0x11223344)
            return i;
        // x64
        else if (__SIZEOF_POINTER__ == 8 && *(ULONG_PTR *)((ULONG_PTR)lpFunctionAddress + i) == 0x1122334455667788)
            return i;
    }

    return -1;
}

int WINAPI NewMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
    /*
    다음과 같은 방법으로 호출하면 에러가 발생한다.
    return OriMessageBox(hWnd, "Hooked..!", lpCaption. uType);
    해당 코드가 컴파일 될때 "Hooked..!" 문자열은 포인터이기 때문에 다른 섹션에 저장되고
    해당 섹션에서 포인터로 파라미터를 전달하기 때문에 다른 프로세스에서
    해당 코드가 실행되면 다음과 같은 오류가 발생한다. EXCEPTION_ACCESS_VIOLATION.
    그렇기 때문에 모든 문자열을 스택에 저장되도록 프로그래밍 해야한다.
    */
    // 모든 문자열은 다음과 같은 방식으로 선언해야 한다.
    char Text[] = "Hooked..!";
    // 0x1122334455667788은 코드 인젝션 후에 원본 MessageBoxA 주소로 패치된다. 
    volatile int (WINAPI *OriMessageBox)(HWND, LPCSTR, LPCSTR, UINT) = 0x1122334455667788;
    // x86 시스템에서 사용한다면 다음과 같이 선언한다.
    // volatile int (WINAPI *OriMessageBox)(HWND, LPCSTR, LPCSTR, UINT) = 0x11223344; 

    // lpText 변수를 조작하여 함수 실행
    return OriMessageBox(hWnd, Text, lpCaption, uType);
}

BOOL IAT_Hook(HANDLE hProcess, LPCSTR lpModuleName, LPCSTR lpProcName, PVOID lpNewApiAddress, SIZE_T NewApiSize)
{
    SIZE_T NumberOfBytesRead;
    // Code Injection of NewAPI(HOOK Function)
    PVOID NewApiAddress = CodeInjection(hProcess, lpNewApiAddress, NewApiSize);

    if (NewApiAddress == NULL)
        return FALSE;
    // Find IAT address of target api
    PVOID IATAddress = FindIATAddress(hProcess, lpModuleName, lpProcName);

    if (IATAddress == NULL)
    {
        VirtualFreeEx(hProcess, NewApiAddress, 0, MEM_DECOMMIT);
        VirtualFreeEx(hProcess, NewApiAddress, 0, MEM_RELEASE);
        return FALSE;
    }
    
    PVOID OriAPIAddress = NULL;
    // Read original api address from IAT
    if (ReadProcessMemory(hProcess, IATAddress, &OriAPIAddress, sizeof(PVOID), &NumberOfBytesRead) == FALSE || NumberOfBytesRead != 8)
    {
        VirtualFreeEx(hProcess, NewApiAddress, 0, MEM_DECOMMIT);
        VirtualFreeEx(hProcess, NewApiAddress, 0, MEM_RELEASE);
        return FALSE;
    }
    // Code patch 0xFFFFFFFFFFFFFFFF in new api
    if (CodePatch(hProcess, (ULONG_PTR)NewApiAddress + SearchCodePatchOffset(NewMessageBoxA, NewApiSize), &OriAPIAddress, sizeof(ULONG_PTR)) == FALSE)
    {
        VirtualFreeEx(hProcess, NewApiAddress, 0, MEM_DECOMMIT);
        VirtualFreeEx(hProcess, NewApiAddress, 0, MEM_RELEASE);
        return FALSE;
    }
    // IAT code patch of target process
    if (CodePatch(hProcess, IATAddress, &NewApiAddress, sizeof(ULONG_PTR)) == FALSE)
    {
        VirtualFreeEx(hProcess, NewApiAddress, 0, MEM_DECOMMIT);
        VirtualFreeEx(hProcess, NewApiAddress, 0, MEM_RELEASE);
        return FALSE;
    }
    // IAT Hooking success!!
    return TRUE;
}

int main(int argc, char *argv[])
{
    DWORD PID = 0;
    printf("PID : ");
    scanf("%d", &PID);

    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
    if (IAT_Hook(hProcess, "user32.dll", "MessageBoxA", NewMessageBoxA, (ULONG_PTR)IAT_Hook - (ULONG_PTR)NewMessageBoxA))
    {
        printf("IAT Hooking Success\n");
    }
    else
    {
        printf("IAT Hooking Failed\n");
    }
}

main.exe
0.06MB

타겟 프로그램

소스코드는 아래 더보기에 있다.

더보기
#include <windows.h>

#define SHOW_MESSAGEBOX 1

VOID AddButton(HWND hWnd);
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    RECT wr = { 0, 0, 500, 500};

    AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE);

    WNDCLASSA wc = { 0, };

    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
    wc.hCursor = LoadCursorA(NULL, IDC_ARROW);
    wc.hInstance = hInstance;
    wc.lpszClassName = "WindowsClass";
    wc.lpfnWndProc = WndProc;

    if (RegisterClassA(&wc) == 0)
        return -1;

    HWND hWnd = CreateWindowA("WindowsClass", "Code Injection", WS_OVERLAPPEDWINDOW | WS_VISIBLE, GetSystemMetrics(SM_CXSCREEN) / 2 - 250, GetSystemMetrics(SM_CYSCREEN) / 2 - 250, wr.right - wr.left, wr.bottom - wr.top, NULL, NULL, NULL, NULL);

    MSG msg = { 0, };

    while (GetMessageA(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessageA(&msg);
    }
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
    switch (Msg)
    {
    case WM_COMMAND:
        if (wParam == SHOW_MESSAGEBOX)
            MessageBoxA(hWnd, "Not Hooked..!", "Click Me!", 0);
    case WM_CREATE:
        AddButton(hWnd);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProcA(hWnd, Msg, wParam, lParam);
    }
}

VOID AddButton(HWND hWnd)
{
    CreateWindowA("Button", "Click Me!", WS_VISIBLE | WS_CHILD, 200, 225, 100, 50, hWnd, SHOW_MESSAGEBOX, NULL, NULL);
}

target.exe
0.06MB

결과 확인

반응형
Comments