admin管理员组

文章数量:1631139

一. 远程线程注入原理

前面几篇文章介绍了《Windows注入与拦截(2) – 使用注册表方式完成DLL注入》,《Windows注入与拦截(3) – 使用钩子方式完成DLL注入》这2种注入方式。“注册表注入方式”由于不能精确指定需要注入的进程,而且只能注入到GUI程序中,灵活性较差;“钩子注入方式”虽然能够精确指定注入的线程,但只能针对特定类型的消息进行Hook,从而间接的实现注入,对于类似windows服务类的程序就束手无策了。

本文介绍的“远程线程的注入方式”是在实际中使用最为广泛的一种注入方式,它即可以精确指定需要注入的进程,又可以注入到非GUI程序中。

远程线程注入方式使用的关键API为CreateRemoteThread,原型如下:

HANDLE WINAPI CreateRemoteThread(
  _In_  HANDLE                 hProcess,
  _In_  LPSECURITY_ATTRIBUTES  lpThreadAttributes,
  _In_  SIZE_T                 dwStackSize,
  _In_  LPTHREAD_START_ROUTINE lpStartAddress,
  _In_  LPVOID                 lpParameter,
  _In_  DWORD                  dwCreationFlags,
  _Out_ LPDWORD                lpThreadId
);

CreateRemoteThread的参数和我们平时创建本地线程使用的CreateThread的参数类似,新增了hProcess句柄参数用于指定在哪个进程创建远程线程,通过OpenProcess可以获取到进程句柄,但需要注意权限问题。

通过远程线程方式实现DLL注入主要是在lpStartAddresslpParameter这2个参数上面做文章。
lpStartAddress参数和CreateThread函数的一样,都是线程的处理过程函数,函数原型如下:

DWORD WINAPI ThreadProc(LPVOID lpParameter);

我们知道加载DLL使用的API是LoadLibraryALoadLibraryW,原型如下:

HMODULE WINAPI LoadLibrary(LPCTSTR lpFileName);

对比LoadLibrary和线程处理函数(LPTHREAD_START_ROUTINE)的原型,我们发现两者的函数的原型基本相同。虽然不是完全相同,但都是接收一个指针参数,而且都是返回一个值,并且调用约定也都是WINAPI

我们完全可以利用下它们之间的相似性,把线程处理函数的地址设为LoadLibraryALoadLibraryW,类似下面这样:

HANDLE hThread = CreateRemoteThread(hProcessRemote, 
    NULL, 
    0, 
    LoadLibraryA, 
    "C:\\InjectDll.dll", 
    0, 
    NULL
);

CreateRemoteThread创建的新线程在远程进程地址空间中被创建的时候,就会立即调用LoadLibraryA函数,并传入DLL路径的地址作为其参数。 (~~ 这一句话才是关键 ~~)

二. 需要注意的问题

按照上面的介绍的方法很容易就能实现远程线程注入,实际上也的确是很容易实现,只是还有3个地方需要注意:LoadLibrary函数地址、DLL路径字符串地址、取消注入。

2.1 LoadLibrary函数地址问题

我们不能向上面的代码那样直接把LoadLibraryALoadLibraryW作为第4个参数传给CreateRemoteThread函数。这其中涉及模块的导入段等问题,如果在调用CreateRemoteThread时直接引用LoadLibraryA,则该引用会被解析为我们被注入DLL的导入段中的LoadLibraryA转换函数的地址,如果把这个转换函数的地址作为远程线程的起始地址传入,其结果很可能是访问违规。

为了强制代码略过转换函数并直接调用LoadLibraryA函数,我们必须通过GetProcAddress来得到LoadLibraryA的确切地址。如:

HMODULE hKernel32 = GetModuleHandle(TEXT("kernel32.dll"));
LPVOID pLoadLibraryAAddr = (LPVOID)GetProcAddress(hKernel32, "LoadLibraryA");

2.2 DLL路径字符串地址问题

DLL路径字符串"C:\\InjectDll.dll"的内存地址位于调用进程的地址空间中,并不位于被注入的进程的地址空间中。所以当LoadLibraryA用这个地址去被注入进程的地址空间中访问的时候,DLL路径的字符串并不在那里,这很有可能导致远程线程访问违规。

如果对进程的地址空间不了解,可以参考:Windows内存体系系列文章。

为了解决这个问题,我们需要把DLL的路径字符串存储到被注入进程的地址空间中。Windows提供的VirtualAllocEx函数可以实现在其他进程的地址空间中分配内存块。实现过程大致如下:

hTargeProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID); // dwProcessID为被注入目标进程的进程ID
if (!hTargeProcess) {
    return;
}

SIZE_T dllPathSize = strlen(pszDllPath); // pszDllPath存储了DLL的路径
pVM4DllPath = VirtualAllocEx(hTargeProcess, NULL, dllPathSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!pVM4DllPath) {
    return;
}

if (!WriteProcessMemory(hTargeProcess, pVM4DllPath, pszDllPath, dllPathSize, NULL)) {
    return;
}

2.3 取消注入问题

取消注入就是将DLL从目标进程卸载,我们知道,卸载DLL所用的API是FreeLibrary,但我们不能直接调用这个函数,因为直接调用的话是在我们的进程中卸载DLL,而不是目标进程中卸载,很显然这样达不到卸载的目的。我们需要和加载DLL时一样,将FreeLibrary的地址作为第4个参数传给CreateRemoteThread函数,但同样需要通过GetProcAddress来得到FreeLibrary的确切地址:

PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "FreeLibrary");
if(pfnThreadRtn) {
    hThread = CreateRemoteThread(hProcess, NULL, 0, pfnThreadRtn, me.modBaseAddr, 0, NULL);
}

三. 实例

InjectDllByRemoteThreadEjectDllByRemoteThread两个函数使用远程线程的方式分别实现了注入和取消注入的功能。

3.1 InjectDllByRemoteThread

BOOL InjectDllByRemoteThread(DWORD dwProcessID, const char* pszDllPath) {
    BOOL bRet = FALSE;
    const DWORD dwThreadSize = 50 * 1024;
    HANDLE hTargeProcess = NULL;
    HANDLE hRemoteThread = NULL;
    PVOID pVM4LoadLibrary = NULL;
    PVOID pVM4DllPath = NULL;

    __try {
        if (!EnablePrivilege(SE_DEBUG_NAME, TRUE)) {
            __leave;
        }

        hTargeProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID);
        if (!hTargeProcess) {
            __leave;
        }

        SIZE_T dllPathSize = strlen(pszDllPath);
        pVM4DllPath = VirtualAllocEx(hTargeProcess, NULL, dllPathSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        if (!pVM4DllPath) {
            __leave;
        }

        if (!WriteProcessMemory(hTargeProcess, pVM4DllPath, pszDllPath, dllPathSize, NULL)) {
            __leave;
        }

        HMODULE hKernel32 = GetModuleHandle(TEXT("kernel32.dll"));
        LPVOID pLoadLibraryAAddr = (LPVOID)GetProcAddress(hKernel32, "LoadLibraryA");

        hRemoteThread = CreateRemoteThread(hTargeProcess, NULL, 0, (DWORD(WINAPI *)(LPVOID))pLoadLibraryAAddr, pVM4DllPath, 0, NULL);
        if (!hRemoteThread) {
            __leave;
        }

        WaitForSingleObject(hRemoteThread, INFINITE);

        DWORD dwExitCode = 0;
        BOOL B = GetExitCodeThread(hRemoteThread, &dwExitCode);

        bRet = TRUE;
    }
    __finally {
        if (hTargeProcess && pVM4DllPath) {
            VirtualFreeEx(hTargeProcess, pVM4DllPath, dwThreadSize, MEM_RELEASE);
        }

        if (hRemoteThread) {
            CloseHandle(hRemoteThread);
        }

        if (hTargeProcess) {
            CloseHandle(hTargeProcess);
        }
    }

    return bRet;
}

3.2 EjectDllByRemoteThread

BOOL EjectDllByRemoteThread(DWORD dwProcessID, LPCWSTR pszDllPath) {
    BOOL bOk = FALSE;
    HANDLE hTHSnapshot = NULL;
    HANDLE hProcess = NULL, hThread = NULL;

    __try {
        hTHSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessID);
        if(hTHSnapshot == INVALID_HANDLE_VALUE) { 
            __leave; 
        }

        MODULEENTRY32W me = {sizeof(me)};
        BOOL bFound = FALSE;
        BOOL bMoreMods = Module32FirstW(hTHSnapshot, &me);
        for(; bMoreMods; bMoreMods = Module32NextW(hTHSnapshot, &me)) {
            bFound = (_wcsicmp(me.szModule, pszDllPath) == 0) ||
                (_wcsicmp(me.szExePath, pszDllPath) == 0);
            if(bFound) break;
        }

        if(!bFound) { 
            __leave; 
        }

        hProcess = OpenProcess(
            PROCESS_QUERY_INFORMATION |
            PROCESS_CREATE_THREAD |
            PROCESS_VM_OPERATION,  // For CreateRemoteThread
            FALSE, dwProcessID);
        if(hProcess == NULL) {
            __leave; 
        }

        PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)
            GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "FreeLibrary");
        if(pfnThreadRtn == NULL) {
            __leave; 
        }

        hThread = CreateRemoteThread(hProcess, NULL, 0, pfnThreadRtn, me.modBaseAddr, 0, NULL);
        if(hThread == NULL) {
            __leave; 
        }

        WaitForSingleObject(hThread, INFINITE);

        bOk = TRUE;
    }
    __finally {
        if(hTHSnapshot != NULL) {
            CloseHandle(hTHSnapshot);
        }
        if(hThread != NULL) {
            CloseHandle(hThread);
        }
        if(hProcess != NULL) {
            CloseHandle(hProcess);
        }
    }

    return(bOk);
}

3.3 DllMain

使用远程线程的方式进行DLL注入,我们一般在DllMain的DLL_PROCESS_ATTACH条件分支开始业务逻辑(通常会另外创建一个子线程,将业务逻辑放到子线程中处理),DLL_PROCESS_DETACH条件分支出结束业务逻辑。

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  fdwReason, LPVOID lpReserved) {
    HANDLE hThread = NULL;

    switch(fdwReason) {
        case DLL_PROCESS_ATTACH:
        {
            g_hDllModule = hModule;
            // 使用注册表方式和CreateRemoteThread方式注入时,一般在此处创建线程
            //

            hThread = (HANDLE)_beginthreadex(NULL, 0, PluginProc, NULL, 0, NULL);
            if (hThread) {
                CloseHandle(hThread); // 关闭句柄,防止句柄泄漏
            }
            break;
        }
        case DLL_THREAD_ATTACH:
        {
            break;
        }
        case DLL_THREAD_DETACH:
        {
            break;
        }
        case DLL_PROCESS_DETACH:
        {
            // 结束业务逻辑
            // ......
            break;
        }
    }
    return TRUE;
}

完整的示例代码下载地址: https://download.csdn/download/china_jeffery/10323108

本文标签: 线程方式Windowsdll