Reversing

[Reversing] Code Injection + Trampoline(Inline) Hooking x86

미친해커 2022. 4. 25. 13:46
반응형

트램펄린(인라인) 후킹이란?

트램펄린 후킹이란 이름처럼 점프(뛴다)라는 이름에 걸맞은 후킹 방법이다.

후킹 할 API 함수의 상위 5 Byte를 jmp 0x00000000 형태로 패치하여 해당 함수가 호출되었을 때 EIP(PC) 레지스터가 다른 함수의 주소로 변경되어 해당 함수로 점프하여 제어권을 가로채는 기술이다.

 

필자가 트램펄린 후킹을 공부했을 때(2019년도)에는 트램펄린 후킹 기술에 대해 검색을 하여도 x86 기반의 설명밖에 찾지 못하였다. 또 x86과 x64 간의 트램폴린 후킹 기술의 구현 방법이 미세하게 다르다. 하지만 x64 시스템에서의 트램펄린 후킹 기술은 많이 알려지지 않은 것으로 보이며 찾을 수도 없었다. 그렇기에 필자는 독자적으로 방법을 구현하여(아닐 수도 있다) 사용하고 있었다. 그렇기 때문에 트램펄린 후킹에 대한 포스팅은 x86과 x64 2개로 나누기로 했다.

 

위에서 설명한 방법은 x86과 x64 시스템에서 모두 사용가능한 방법이며 x64 시스템에서는 약간의 제약이 걸린다.

코드 인젝션과 트램펄린 후킹의 조합

코드 인젝션은 Fileless 형식의 임의의 코드 주입 공격이다. 그리고 트램펄린 후킹은 프로세스의 타겟 함수의 상위 5 바이트를 조작하여 특정 API의 제어권을 가로채는 기법이다.

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

예제 코드

 

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>
#include "../Library/Rlibloaderapi.h"

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

PVOID CodeInjection(HANDLE hProcess, PVOID lpBuffer, SIZE_T nSize)
{
    // Allocate memory in target process
    PVOID BufferAddress = VirtualAllocEx(hProcess, NULL, nSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

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

    SIZE_T NumberOfBytesWritten;

    // Write the lpBuffer in allocated memory
    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)
{
    /*
    An error occurs in the following ways:
    return OriMessageBox(hWnd, "Hooked..!", lpCaption. uType);
    The compiled "Hooked..!" in this code points to a section of the process with a pointer, 
    so another process Occur EXCEPTION_ACCESS_VIOLATION.
    Therefore, you must store all the strings in the stack.
    */
    // All strings should be used as follows
    char Text[] = "Hooked..!";

    // Top 5 Bytes of MessageBoxA in user32.dll
    BYTE OriginCode[5] = { 
        0x8B, 0xFF, // mov edi, edi
        0x55,       // push ebp
        0x8B, 0xEC  // mov ebp, esp
    };
    
    // Variables to back up hook code
    BYTE HookCode[5];
    
    // 0x11223344 is code patched to original MessageBoxA address after Code Injection
    volatile int (WINAPI *OriMessageBox)(HWND, LPCSTR, LPCSTR, UINT) = 0x11223344;

    for (int i = 0; i < 5; i++)
    {
        // Backup hook code
        HookCode[i] = *(BYTE *)((ULONG_PTR)OriMessageBox + i);
        
        // Overwrite Top 5 Bytes of MessageBoxA to original code.
        // Trampoline(Inline) Hook code to Original code
        *(BYTE *)((ULONG_PTR)OriMessageBox + i) = OriginCode[i];
    }

    // call after Manipulate lpText parameter and save return value
    int ret = OriMessageBox(hWnd, Text, lpCaption, uType);

    // Overwrite with hook code again when function ends
    for (int i = 0; i < 5; i++)
        *(BYTE *)((ULONG_PTR)OriMessageBox + i) = HookCode[i];

    return ret;
}

BOOL Trampoline_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 address of target api
    PVOID ProcAddress = GetRemoteProcAddress(hProcess, GetRemoteModuleHandleA(hProcess, lpModuleName), lpProcName);

    if (ProcAddress == NULL)
    {
        VirtualFreeEx(hProcess, NewApiAddress, 0, MEM_DECOMMIT);
        VirtualFreeEx(hProcess, NewApiAddress, 0, MEM_RELEASE);
        return FALSE;
    }

    DWORD OldProtect;

    // Change memory protection option of Top 5 byte of target api (PAGE_EXECUTE_READ to PAGE_EXECUTE_READWRITE)
    if (VirtualProtectEx(hProcess, ProcAddress, 5, PAGE_EXECUTE_READWRITE, &OldProtect) == FALSE)
    {
        VirtualFreeEx(hProcess, NewApiAddress, 0, MEM_DECOMMIT);
        VirtualFreeEx(hProcess, NewApiAddress, 0, MEM_RELEASE);
        return FALSE;
    }

    // Assembly Code
    BYTE JmpCode[5] = { 
        0xE9, 0x00, 0x00, 0x00, 0x00 // jmp 0x00000000 (4Byte Relative jump)
    };

    // Calculate relative jump address
    // Relative Address = Target Address - Hook Address - 5 (5Byte is assembly length)
    // jmp NewApiAddress
    *(DWORD *)(JmpCode + 1) = (ULONG_PTR)NewApiAddress - (ULONG_PTR)ProcAddress - 5; 

    // Code patch 0x11223344 in new api
    if (CodePatch(hProcess, (ULONG_PTR)NewApiAddress + SearchCodePatchOffset(NewMessageBoxA, NewApiSize), &ProcAddress, 4) == FALSE)
    {
        VirtualFreeEx(hProcess, NewApiAddress, 0, MEM_DECOMMIT);
        VirtualFreeEx(hProcess, NewApiAddress, 0, MEM_RELEASE);
        return FALSE;
    }

    // Top 5 bytes code patch of target process
    if (CodePatch(hProcess, ProcAddress, JmpCode, 5) == FALSE)
    {
        VirtualFreeEx(hProcess, NewApiAddress, 0, MEM_DECOMMIT);
        VirtualFreeEx(hProcess, NewApiAddress, 0, MEM_RELEASE);
        return FALSE;
    }

    // Trampline(Inline) 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 (Trampoline_Hook(hProcess, "user32.dll", "MessageBoxA", NewMessageBoxA, (ULONG_PTR)Trampoline_Hook - (ULONG_PTR)NewMessageBoxA))
        printf("Trampoline Hooking Success\n");
    else
        printf("Trampoline Hooking Failed\n");
}

main.exe
0.06MB

타겟 프로그램

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

더보기
#include <windows.h>

#define SHOW_MESSAGEBOX 1

int (WINAPI *lpMessageBoxA)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);

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);
    lpMessageBoxA = GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");

    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)
            lpMessageBoxA(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.05MB

결과 확인

 

반응형