天天看點

無DLL,直接将整個EXE注入其他程序注入代碼的方式比較注入EXE的方法效果

注入代碼的方式比較

注入shellcode

優點:

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

缺點:

1. 要寫位置無關代碼,這意味着不能直接使用全局變量、其他編譯單元的函數(包括CRT的

memcpy

)、API等。如果要使用則要由源程序配置設定空間、計算API在目标程序的位址,并傳到目标程序的shellcode。或者shellcode自己計算

LoadLibrary

GetProcAddress

的位址也行

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, , remoteStartup, remoteCtx, , NULL);
        if (remoteThread == NULL)
            throw runtime_error("Failed to create remote thread");
        // 等待注入結束并取RemoteStartup傳回值
        WaitForSingleObject(remoteThread, INFINITE);
        DWORD exitCode;
        GetExitCodeThread(remoteThread, &exitCode);
        if (exitCode != )
        {
            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 ;
    if (!ResolveImportTable(ctx))
        return ;
    return ctx->callback();
}

bool RelocateModuleBase(InjectionContext* ctx)
{
    // offset = 0,不需要重定位
    if (ctx->offset == )
        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 == 
        || ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size == )
        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 != )
    {
        auto relocationData = PWORD((uintptr_t)relocation + sizeof(IMAGE_BASE_RELOCATION));
        int nRelocationData = (relocation->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
        for (int i = ; i < nRelocationData; i++)
        {
            if (relocationData[i] >>  == IMAGE_REL_BASED_HIGHLOW)
            {
                auto address = (uint32_t*)((uintptr_t)ctx->imageBase + relocation->VirtualAddress + (relocationData[i] & x0FFF));
                *address += (uint32_t)ctx->offset;
            }
#ifdef _WIN64
            if (relocationData[i] >>  == IMAGE_REL_BASED_DIR64)
            {
                auto address = (uint64_t*)((uintptr_t)ctx->imageBase + relocation->VirtualAddress + (relocationData[i] & x0FFF));
                *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 == 
        || ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size == )
        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 ;
    }
    LPVOID remoteImageBase = InjectExe(process, RemoteMain);
    if (remoteImageBase == NULL)
        return ;
    cout << "remoteImageBase = 0x" << remoteImageBase << endl;
    return ;
}

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

    // 測試malloc(),注意不能使用靜态連結的CRT
    free(malloc());

    // 擷取目前程序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 ;
}
           

效果

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

無DLL,直接将整個EXE注入其他程式注入代碼的方式比較注入EXE的方法效果