DLL注入——使用远程线程

1.简介

从根本上来说,DLL注入技术要求目标进程中的一个线程调用LoadLibrary来载入我们想要的DLL。由于我们不能轻易地控制别人进程中的线程,因此这种方法要求我们在目标进程中创建一个新的线程,Windows提供了创建远程线程的函数。

HANDLE 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
);
  • hProcess:用于创建线程的进程的句柄。
  • lpThreadAttributes:指向SECURITY_ATTRIBUTES结构的指针,该结构确定返回的句柄是否可以被子进程继承。
  • dwStackSize:堆栈的初始大小,以字节为单位。如果此参数为零,则新线程使用默认大小。
  • lpStartAddress:指向线程要执行的应用程序定义函数的指针。
  • lpParameter:指向要传递给线程的变量的指针。
  • dwCreationFlags:控制线程创建的标志。
  • lpThreadId:指向接收线程标识符的变量的指针。如果该参数为NULL,则不返回线程标识符。

2.步骤

  • 用VirtualAllocEx函数在远程进程的地址空间中分配一块内存。
  • 用WriteProcessMemory函数把DLL的路径名复制到第一步分配的内存中。
  • 用GetProcAddress函数来得到LoadLibraryW或LoadLibraryA函数的实际地址(在Kernel32.dll中)
  • 用CreateRemoteThread函数在远程进程中创建一个线程,让新线程调用正确的LoadLibrary函数并在参数中传入第一步分配的内存地址。这时,DLL已经被注入到远程进程的地址空间中,DLL的DllMain函数会收到DLL_PROCESS_ATTACH通知并且执行我们想要执行的代码。
  • VirtualFreeEx来释放第1步分配的内存。
  • 用GetProcAddress函数来得到FreeLibrary函数的实际地址(在Kernel32.dll中)
  • 用CreateRemoteThread函数在远程进程中创建一个线程,让新线程调用FreeLibrary函数并在参数中传入远程DLL的HMODULE。

步骤1、2、3原理简介:

字符串“D://remoteDll.dll”,位于调用进程的空间地址中,我们把这个地址传给新创建的远程线程,远程线程再把它传给LoadLibrary,当LoadLibrary去访问这个内存地址的时候,DLL的路径字符串并不在那里,远程进程的线程就很可能引发访问违规。为了解决这个问题,我们需要把DLL的字符串存放到远程进程的地址空间中去,所以会使用VirtualAllocEx和WriteProcessMemory方法。

3.示例

3.1先做一个动态库remoteDll.dll

此库的功能是,一旦被注入到进程的地址空间中,就报告该进程正在使用的所有DLL,并将打印信息写入到本地文件D://out.txt中。

const char* filepath = "D://out.txt";

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    {
        FILE* fp = freopen(filepath, "w", stdout);
        
        PBYTE pb = NULL;
        MEMORY_BASIC_INFORMATION mbi;
        while (VirtualQuery(pb, &mbi, sizeof(mbi)) == sizeof(mbi))
        {
            int nlen;
            char szModName[MAX_PATH];
            if (mbi.State == MEM_FREE)
                mbi.AllocationBase = mbi.BaseAddress;
            if ((mbi.AllocationBase == hModule) || (mbi.AllocationBase != mbi.BaseAddress) || (mbi.AllocationBase == NULL))
                nlen = 0;
            else
            {
                nlen = GetModuleFileNameA((HINSTANCE)mbi.AllocationBase, szModName, _countof(szModName));
            }
            if (nlen > 0)
            {
                char szBuf[MAX_PATH] = { 0 };
                wsprintfA(szBuf, "\n%p-%s", mbi.AllocationBase, szModName);
                printf("%s\n", szBuf);
            }

            pb += mbi.RegionSize;
        } 

        fclose(fp);
    }
    break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

3.2写一个测试程序

新建一个带界面的程序

改了一下窗口界面,这里加了一个菜单项注入。

点击注入出现弹窗,如下图所示。

输入进程ID,点击确定即可,下面是注入的回调函数。

// “注入”框的消息处理程序。
INT_PTR CALLBACK Inject(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
    {
        return (INT_PTR)TRUE;
    }
    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK)
        {
            //获取ID值
            DWORD processID = GetDlgItemInt(hDlg, IDC_EDIT1, NULL, FALSE);
            if (processID == 0)
            {
                processID = GetCurrentProcessId();
            }

            if (InjectLib(processID, TEXT("D://remoteDll.dll")))
            {
                printf("inject ok");
                EjectLib(processID, TEXT("D://remoteDll.dll"));
            }
            else
            {
                printf("inject failed");
            }

            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        else if (LOWORD(wParam) == IDCANCEL)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}

InjectLib函数如下:

BOOL WINAPI InjectLib(DWORD processID, PCWSTR pszLibFile)
{
    BOOL bOk = FALSE;
    HANDLE hProcess = NULL, hThread = NULL;
    PWSTR pszLibFileRemote = NULL;

    __try
    {
        hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD |
            PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, processID);

        if (hProcess == NULL)
            __leave;

        int cch = 1 + lstrlenW(pszLibFile);
        int cb = cch * sizeof(wchar_t);

        pszLibFileRemote = (PWSTR)VirtualAllocEx(hProcess, NULL, cb, MEM_COMMIT, PAGE_READWRITE);
        if (pszLibFileRemote == NULL)
            __leave;

        if (!WriteProcessMemory(hProcess, pszLibFileRemote, (PVOID)pszLibFile, cb, NULL))
            __leave;

        FARPROC pfnThreadRtn = GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
        if (pfnThreadRtn == NULL)
            __leave;

        hThread = CreateRemoteThread(hProcess, NULL, 0, (PTHREAD_START_ROUTINE)pfnThreadRtn, pszLibFileRemote, 0, NULL);
        if (hThread == NULL)
            __leave;

        WaitForSingleObject(hThread, INFINITE);

        bOk = TRUE;
    }
    __finally
    {
        if (pszLibFileRemote != NULL)
            VirtualFreeEx(hProcess, pszLibFileRemote, 0, MEM_RELEASE);

        if (hThread != NULL)
            CloseHandle(hThread);

        if (hProcess != NULL)
            CloseHandle(hProcess);
    }

    return bOk;
}

EjectLib函数如下:

BOOL WINAPI EjectLib(DWORD processID, PCWSTR pszLibFile)
{
    BOOL bOk = FALSE;
    HANDLE hthSnapshot = NULL;
    HANDLE hProcess = NULL, hThread = NULL;
    PWSTR pszLibFileRemote = NULL;

    __try
    {
        //获取指定进程的快照,以及这些进程使用的堆、模块和线程。
        hthSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, processID);
        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, pszLibFile) == 0) || (_wcsicmp(me.szExePath, pszLibFile));
            if (bFound)
                break;
        }

        if (!bFound)
            __leave;

        hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD |
            PROCESS_VM_OPERATION, FALSE, processID);

        if (hProcess == NULL)
            __leave;

        FARPROC pfnThreadRtn = GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "FreeLibrary");
        if (pfnThreadRtn == NULL)
            __leave;

        //me.modBaseAddr 在所属进程的上下文中模块的基地址。
        hThread = CreateRemoteThread(hProcess, NULL, 0, (PTHREAD_START_ROUTINE)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执行程序

打开一个记事本程序,找到PID,如下图所示,PID = 20752。

运行3.2中的程序,输入这个PID,点击确定注入成功。

3.4运行结果

注入之后,在本地文件out.txt中,可以看见正在该记事本程序正在使用的DLL。

猜你喜欢

转载自blog.csdn.net/wzz953200463/article/details/126591449