無DLL,直接將整個EXE注入其他進程

注入代碼的方式比較

注入shellcode

優點:
1. 簡單,只需要EXE的一部分。代碼可以用C\C++或彙編寫

缺點:
1. 要寫位置無關代碼,這意味着不能直接使用全局變量、其他編譯單元的函數(包括CRT的memcpy)、API等。如果要使用則要由源進程分配空間、計算API在目標進程的地址,並傳到目標進程的shellcode。或者shellcode自己計算LoadLibraryGetProcAddress的地址也行
2. 沒有符號文件,難以調試

注入DLL

建議沒什麼特殊需要都優先選擇注入DLL

優點:
1. 代碼沒什麼限制
2. 調試方便,VS可以直接在源代碼下斷點,附加到目標進程,注入DLL後可以正常調試

缺點:
1. 程序要帶上一個DLL。其實我覺得不算缺點,大不了把DLL放到EXE資源裏,要注入時釋放到一個臨時目錄
2. 容易被檢測到注入,不是幹壞事的話也可以忽略這點

注入EXE

優點:
1. 只需要EXE,少一個DLL文件。有些遊戲補丁和修改器是這麼幹的
2. 跟shellcode比起來,因爲自帶重定位表、IAT等東西,可以不寫位置無關代碼,只要修復了重定位和IAT就可以正常運行大部分代碼。而且操作系統在載入源進程時已經做了IAT修復,假設kernel32.dll模塊在每個進程加載地址一樣,就可以在目標進程修復IAT之前直接調用部分API

缺點:
1. 沒有符號文件,難以調試
2. 不能依賴於全局變量的初始狀態,這意味着不能使用靜態鏈接的CRT,因爲CRT的很多函數依賴於全局變量的初始值(比如malloc

注入EXE的方法

前置知識,這裏面提到的不再細講:
1. 注入DLL,這裏用到遠線程注入
2. 加載PE文件,用到PE文件的結構和修復重定位、IAT

完整源碼:InjectExe

1. 把整個EXE和需要的變量寫入目標進程

typedef int(* RemoteCallbackType)();

// 需要傳到目標進程的變量
struct InjectionContext
{
    LPVOID imageBase; // 目標進程中EXE的地址
    uintptr_t offset; // 目標進程中EXE的地址 - 源進程中EXE的地址,用來做重定位
    RemoteCallbackType callback; // 注入完畢後在目標進程調用的回調
};

// 注入EXE到process,然後在目標進程調用callback
// callback必須返回0,否則視爲注入失敗
// 如果注入成功則返回EXE在目標進程的地址,否則返回NULL
LPVOID InjectExe(HANDLE process, RemoteCallbackType callback)
{
    auto dosHeader = (PIMAGE_DOS_HEADER)GetModuleHandle(NULL);
    auto ntHeader = PIMAGE_NT_HEADERS((uintptr_t)dosHeader + dosHeader->e_lfanew);
    auto imageBase = (LPVOID)dosHeader;
    SIZE_T imageSize = ntHeader->OptionalHeader.SizeOfImage;

    LPVOID remoteImageBase = NULL;
    LPVOID remoteCtx = NULL;
    HANDLE remoteThread = NULL;
    try
    {
        // 優先在當前的imageBase分配地址,這樣就不用重定位,分配失敗則選擇其他地址
        remoteImageBase = VirtualAllocEx(process, imageBase, imageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        if (remoteImageBase == NULL)
        {
            remoteImageBase = VirtualAllocEx(process, NULL, imageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
            if (remoteImageBase == NULL)
                throw runtime_error("Failed to allocate remoteImageBase");
        }
        // 把源進程EXE寫到目標進程
        if (!WriteProcessMemory(process, remoteImageBase, imageBase, imageSize, NULL))
            throw runtime_error("Failed to write remoteImageBase");

        // 準備要傳到目標進程的變量
        uintptr_t offset = (uintptr_t)remoteImageBase - (uintptr_t)imageBase;
        InjectionContext ctx;
        ctx.imageBase = remoteImageBase;
        ctx.offset = offset;
        // 注意目標進程中的callback地址 = 源進程中地址 + offset
        ctx.callback = RemoteCallbackType((uintptr_t)callback + offset);

        // 把ctx寫到目標進程
        remoteCtx = VirtualAllocEx(process, NULL, sizeof(ctx), MEM_COMMIT, PAGE_READWRITE);
        if (remoteCtx == NULL)
            throw runtime_error("Failed to allocate remoteCtx");
        if (!WriteProcessMemory(process, remoteCtx, &ctx, sizeof(ctx), NULL))
            throw runtime_error("Failed to write remoteCtx");

        // 在目標進程調用RemoteStartup函數,參數爲ctx,這個函數後面介紹
        // 注意目標進程中的RemoteStartup地址 = 源進程中地址 + offset
        LPTHREAD_START_ROUTINE remoteStartup = LPTHREAD_START_ROUTINE((uintptr_t)RemoteStartup + offset);
        remoteThread = CreateRemoteThread(process, NULL, 0, remoteStartup, remoteCtx, 0, NULL);
        if (remoteThread == NULL)
            throw runtime_error("Failed to create remote thread");
        // 等待注入結束並取RemoteStartup返回值
        WaitForSingleObject(remoteThread, INFINITE);
        DWORD exitCode;
        GetExitCodeThread(remoteThread, &exitCode);
        if (exitCode != 0)
        {
            SetLastError(exitCode);
            throw runtime_error("RemoteStartup failed");
        }
    }
    catch (runtime_error& e)
    {
        cerr << e.what() << ": 0x" << hex << GetLastError() << oct << endl;
        CloseHandle(remoteThread);
        VirtualFreeEx(process, remoteCtx, sizeof(InjectionContext), MEM_DECOMMIT);
        VirtualFreeEx(process, remoteImageBase, imageSize, MEM_DECOMMIT);
        return NULL;
    }
    CloseHandle(remoteThread);
    VirtualFreeEx(process, remoteCtx, sizeof(InjectionContext), MEM_DECOMMIT);
    return remoteImageBase;
}

2. 修復重定位和IAT

RemoteStartup函數很簡單,就是修復重定位和IAT,然後調用callback。修復重定位和IAT的代碼我是從mmLoader修改的

DWORD WINAPI RemoteStartup(InjectionContext* ctx)
{
    if (!RelocateModuleBase(ctx))
        return 1;
    if (!ResolveImportTable(ctx))
        return 2;
    return ctx->callback();
}

bool RelocateModuleBase(InjectionContext* ctx)
{
    // offset = 0,不需要重定位
    if (ctx->offset == 0)
        return true;

    auto dosHeader = (PIMAGE_DOS_HEADER)ctx->imageBase;
    auto ntHeader = PIMAGE_NT_HEADERS((uintptr_t)dosHeader + dosHeader->e_lfanew);
    if (ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress == 0
        || ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size == 0)
        return true;

    auto relocation = PIMAGE_BASE_RELOCATION((uintptr_t)ctx->imageBase +
        ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
    if (relocation == NULL) // 無效的重定位表
        return false;
    while (relocation->VirtualAddress + relocation->SizeOfBlock != 0)
    {
        auto relocationData = PWORD((uintptr_t)relocation + sizeof(IMAGE_BASE_RELOCATION));
        int nRelocationData = (relocation->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
        for (int i = 0; i < nRelocationData; i++)
        {
            if (relocationData[i] >> 12 == IMAGE_REL_BASED_HIGHLOW)
            {
                auto address = (uint32_t*)((uintptr_t)ctx->imageBase + relocation->VirtualAddress + (relocationData[i] & 0x0FFF));
                *address += (uint32_t)ctx->offset;
            }
#ifdef _WIN64
            if (relocationData[i] >> 12 == IMAGE_REL_BASED_DIR64)
            {
                auto address = (uint64_t*)((uintptr_t)ctx->imageBase + relocation->VirtualAddress + (relocationData[i] & 0x0FFF));
                *address += ctx->offset;
            }
#endif
        }
        relocation = PIMAGE_BASE_RELOCATION((uintptr_t)relocation + relocation->SizeOfBlock);
    }
    return true;
}

bool ResolveImportTable(InjectionContext* ctx)
{
    auto dosHeader = (PIMAGE_DOS_HEADER)ctx->imageBase;
    auto ntHeader = PIMAGE_NT_HEADERS((uintptr_t)dosHeader + dosHeader->e_lfanew);
    if (ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress == 0
        || ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size == 0)
        return true;

    auto importTable = PIMAGE_IMPORT_DESCRIPTOR((uintptr_t)ctx->imageBase +
        ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
    for (; importTable->Name != NULL; importTable++)
    {
        // 加載DLL
        auto dllName = PCHAR((uintptr_t)ctx->imageBase + importTable->Name);
        // 假設kernel32.dll在源進程和目標進程被加載到同一個地址,修復IAT之前就可以直接使用LoadLibrary等API了
        HMODULE module = LoadLibraryA(dllName);
        if (module == NULL)
            return false;

        PIMAGE_THUNK_DATA originalThunk;
        if (importTable->OriginalFirstThunk)
            originalThunk = PIMAGE_THUNK_DATA((uintptr_t)ctx->imageBase + importTable->OriginalFirstThunk);
        else
            originalThunk = PIMAGE_THUNK_DATA((uintptr_t)ctx->imageBase + importTable->FirstThunk);
        auto iatThunk = PIMAGE_THUNK_DATA((uintptr_t)ctx->imageBase + importTable->FirstThunk);
        for (; originalThunk->u1.AddressOfData != NULL; originalThunk++, iatThunk++)
        {
            FARPROC function;
            if (IMAGE_SNAP_BY_ORDINAL(originalThunk->u1.Ordinal))
                function = GetProcAddress(module, (LPCSTR)IMAGE_ORDINAL(originalThunk->u1.Ordinal));
            else
            {
                auto nameInfo = PIMAGE_IMPORT_BY_NAME((uintptr_t)ctx->imageBase + originalThunk->u1.AddressOfData);
                function = GetProcAddress(module, nameInfo->Name);
            }

            iatThunk->u1.Function = (uintptr_t)function;
        }
    }
    return true;
}

3. 使用方法

int main()
{
    // 注入到記事本
    HWND hwnd = FindWindow(_T("Notepad"), NULL);
    DWORD pid;
    GetWindowThreadProcessId(hwnd, &pid);
    HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
    if (process == NULL)
    {
        cerr << "Failed to open process: 0x" << hex << GetLastError() << oct << endl;
        return 1;
    }
    LPVOID remoteImageBase = InjectExe(process, RemoteMain);
    if (remoteImageBase == NULL)
        return 1;
    cout << "remoteImageBase = 0x" << remoteImageBase << endl;
    return 0;
}

// 這個函數在目標進程調用
int RemoteMain()
{
    // 測試使用API
    MessageBox(NULL, _T("RemoteMain()"), _T("InjectExe"), MB_OK);

    // 測試malloc(),注意不能使用靜態鏈接的CRT
    free(malloc(1));

    // 獲取當前進程EXE路徑
    array<WCHAR, MAX_PATH> processPath;
    GetModuleFileNameW(GetModuleHandle(NULL), &processPath.front(), MAX_PATH);
    wstringstream stream;
    stream << L"Hello world!\nI'm called from " << &processPath.front();
    MessageBoxW(NULL, stream.str().c_str(), L"InjectExe", MB_OK);

    // 也可以在這裏添加hook,就不演示了
    return 0;
}

效果

拿記事本測試的效果,我這裏記事本是64位程序,所以編譯時也要用64位配置:

效果

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章