无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 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位配置:

无DLL,直接将整个EXE注入其他进程_第1张图片

你可能感兴趣的:(Windows黑科技,注入代码,注入EXE,隐藏DLL,PE文件,隐藏模块)