解决注入器在DLL的入口函数中创建线程时卡死的问题

文章目录

  • 问题描述
  • 验证过程
    • 创建动态库项目
    • 动态库中启动线程
      • 错误的用法
      • 正确的用法
  • 附:注入器代码
    • inject_tool.h
    • inject_tool.cpp
  • 更多

问题描述

根据网上的教程,写了一个注入器的实现:
解决注入器在DLL的入口函数中创建线程时卡死的问题_第1张图片
但是我发现注入后无法卸载,经过一系列的排除法验证后,最终确定了原因。
先上代码,动态库DllMain中使用线程的正确姿势:

#include 
std::atomic_bool g_run_falg_ = true;

DWORD WINAPI ThreadProc(LPVOID lpThreadParameter) {
     
    while (g_run_falg_) {
     
        OutputDebugStringW(L"injector thread run");
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }

    OutputDebugStringW(L"injector thread exit");

    return 0;
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     ) {
     
    switch (ul_reason_for_call) {
     
    case DLL_PROCESS_ATTACH: {
     
        OutputDebugStringW(L"injector DLL_PROCESS_ATTACH");

        DWORD dwThreadId;
        // 使用windows API来创建线程,std::thread 会卡死,原因不明。
        HANDLE hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, &dwThreadId);
        std::this_thread::sleep_for(std::chrono::seconds(1));
        CloseHandle(hThread);

        break;
    }

    case DLL_THREAD_ATTACH:
        OutputDebugStringW(L"injector DLL_THREAD_ATTACH");
        break;

    case DLL_THREAD_DETACH:
        OutputDebugStringW(L"injector DLL_THREAD_DETACH");
        break;

    case DLL_PROCESS_DETACH: {
     
        OutputDebugStringW(L"injector DLL_PROCESS_DETACH");
        g_run_falg_ = false; 
        // 最坏的情况是,1秒后线程退出。需要等等线程完全释放后退出,否则微信崩溃
        std::this_thread::sleep_for(std::chrono::seconds(1));
        break;
    }

    }

    return TRUE;
}

验证过程

创建动态库项目

  1. 使用VS 2017,点击新建->动态链接库(DLL)
    解决注入器在DLL的入口函数中创建线程时卡死的问题_第2张图片
  2. 在dllmain.cpp中,加入日志,以便使用 DugView工具(下载,提取码:m194)查看动态库运行情况
BOOL APIENTRY DllMain(HMODULE hModule,
                      DWORD  ul_reason_for_call,
                      LPVOID lpReserved
                     ) {
     
    switch (ul_reason_for_call) {
     
    // 注意,默认的case不带break,会一直往下执行。其次break需要用{}包起来,否则作用域就是switch了
    case DLL_PROCESS_ATTACH: {
     
        OutputDebugStringW(L"injector DLL_PROCESS_ATTACH");
        break;
    }

    case DLL_THREAD_ATTACH: {
     
        OutputDebugStringW(L"injector DLL_THREAD_ATTACH");
        break;
    }

    case DLL_THREAD_DETACH: {
     
        OutputDebugStringW(L"injector DLL_THREAD_DETACH");
        break;
    }

    case DLL_PROCESS_DETACH: {
     
        OutputDebugStringW(L"injector DLL_PROCESS_DETACH");
        break;
    }

    }

    return TRUE;
}
  1. 使用od加载微信。然后点击注入,查看OD(附加后点击e,可查看加载的库)中动态库是否已成功加载。如下图,没有问题,已成功加载。
    解决注入器在DLL的入口函数中创建线程时卡死的问题_第3张图片

  2. 使用 DugView工具(下载,提取码:m194)查看Log输入,如下
    解决注入器在DLL的入口函数中创建线程时卡死的问题_第4张图片

  3. 步骤4中,我点击了卸载,故输出了卸载的2个log,刷新一下OD,发现该动态库已经不在了。

动态库中启动线程

错误的用法

通过上面的验证,我们初步明白了注入和卸载的执行顺序。有时候,我们可能会在DLL中启动Socket监听,故需要新启动一个线程去做这件事情,我使用了标准线程库std::thread,代码如下:

BOOL APIENTRY DllMain(HMODULE hModule,
                      DWORD  ul_reason_for_call,
                      LPVOID lpReserved
                     ) {
     
    switch (ul_reason_for_call) {
     
    case DLL_PROCESS_ATTACH: {
     
        OutputDebugStringW(L"injector DLL_PROCESS_ATTACH");

		// 这里创建线程,模拟Socket监听线程
        std::thread t([]() {
     
            while (g_run_falg_) {
     
                std::this_thread::sleep_for(std::chrono::seconds(1));
                OutputDebugStringW(L"injector thread run");
            }

            OutputDebugStringW(L"injector thread exit");
        });
        t.detach();

        break;
    }
    // ...
}

此时,我通过注入器再注入后,发现无法再卸载,Log也只有DLL_PROCESS_ATTACH,DLL_THREAD_ATTACH没有执行,说明被卡住了。后经过搜索验证,更换了std::thread后解决,具体见:正确的用法。

正确的用法

动态库DllMain入口中,线程正确启动和退出的姿势如下:

#include 
std::atomic_bool g_run_falg_ = true;

DWORD WINAPI ThreadProc(LPVOID lpThreadParameter) {
     
    while (g_run_falg_) {
     
        OutputDebugStringW(L"injector thread run");
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }

    OutputDebugStringW(L"injector thread exit");

    return 0;
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     ) {
     
    switch (ul_reason_for_call) {
     
    case DLL_PROCESS_ATTACH: {
     
        OutputDebugStringW(L"injector DLL_PROCESS_ATTACH");

        /*std::thread t([]() {
            while (g_run_falg_) {
                std::this_thread::sleep_for(std::chrono::seconds(1));
                OutputDebugStringW(L"injector thread run");
            }

            OutputDebugStringW(L"injector thread exit");
        });
        t.detach();*/

        DWORD dwThreadId;
        // 使用windows API来创建线程,std::thread 会卡死,原因不明。
        HANDLE hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, &dwThreadId);
        std::this_thread::sleep_for(std::chrono::seconds(1));
        CloseHandle(hThread);

        break;
    }

    case DLL_THREAD_ATTACH:
        OutputDebugStringW(L"injector DLL_THREAD_ATTACH");
        break;

    case DLL_THREAD_DETACH:
        OutputDebugStringW(L"injector DLL_THREAD_DETACH");
        break;

    case DLL_PROCESS_DETACH: {
     
        OutputDebugStringW(L"injector DLL_PROCESS_DETACH");
        g_run_falg_ = false; 
        // 最坏的情况是,1秒后线程退出。需要等等线程完全释放后退出,否则微信崩溃
        std::this_thread::sleep_for(std::chrono::seconds(1));
        break;
    }

    }

    return TRUE;
}

附:注入器代码

inject_tool.h

#ifndef _INJECT_TOOLS_4B2B684A_9CA6_4A9E_B7C0_9EC427D54FD7_
#define _INJECT_TOOLS_4B2B684A_9CA6_4A9E_B7C0_9EC427D54FD7_

#include 

namespace core {
     
    /** @fn ProcessNameFindPID
    * @brief 通过进程名获取进程ID
    * @param ProcessName:进程名字,带.exe
    * @return
    */
    DWORD ProcessNameFindPID(const char* processName) noexcept;

    /** @fn getFileName
      * @brief 从路径中截取文件名
      * @param filePath:完整文件路径名
      * @return
      */
    std::wstring getFileName(const std::wstring& filePath);

    /** @fn StartProcess
      * @brief 启动进程
      * @param filePath:进程完整路径
      *        firstWindowName:判断成功的窗口名,微信为WeChatLoginWndForPC
      * @return
      */
    void StartProcess(std::wstring filePath, std::wstring firstWindowName = L"WeChatLoginWndForPC");

    /** @fn InjectDll
      * @brief 注入dll,对应的动态库入口DllMain会执行一次DLL_PROCESS_ATTACH,请在这里挂载Hook等初始化动作。
      * @param exeName:进程名
      *		   targetDllFilePath:要注入动态库的路径
      * @return
      */
    void InjectDll(const std::string& exeName, const std::wstring& targetDllFilePath);

    /** @fn UnloadDll
      * @brief 卸载DLL,对应的动态库入口DllMain会执行一次DLL_PROCESS_DETACH,请在这里释放Hook、线程等动作。
      *        PS:如果有残留线程,则将无法卸载DLL!微信关闭后,也将无法正确退出,从而变成僵尸进程。
      * @param exeName:要卸载动态库的目标进程
      *        targetDllFilename:要卸载动态库的名字
      * @return
      */
    void UnloadDll(const std::string& exeName, const std::wstring& targetDllFilePath);

    /** @fn CheckIsInject
      * @brief 检测DLL是否已经注入
      * @param dwProcessid:进程ID
      *        targetDllFilePath:文件路径名
      * @return
      */
    bool CheckIsInject(DWORD processid, const std::wstring& targetDllFilePath);

}

#endif//_INJECT_TOOLS_4B2B684A_9CA6_4A9E_B7C0_9EC427D54FD7_

inject_tool.cpp

#include "stdafx.h"
#include "inject_tools.h"
#include 
#include 
#include 
#include 
#include 

#pragma comment(lib,"advapi32")

namespace core {
     

    DWORD ProcessNameFindPID(const char* ProcessName) noexcept {
     
        PROCESSENTRY32 pe32 = {
      0 };
        pe32.dwSize = sizeof(PROCESSENTRY32);
        HANDLE hProcess = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);

        if (Process32First(hProcess, &pe32) == TRUE) {
     
            do {
     
                USES_CONVERSION;

                if (strcmp(ProcessName, W2A(pe32.szExeFile)) == 0) {
     
                    return pe32.th32ProcessID;
                }
            } while (Process32Next(hProcess, &pe32));
        }

        return 0;
    }

    void StartProcess(std::wstring filePath, std::wstring firstWindowName) {
     
        if (!nbase::FilePathIsExist(filePath, false)) {
     
            throw std::exception("exe is not exist.");
        }

        STARTUPINFO si;
        PROCESS_INFORMATION pi;
        ZeroMemory(&si, sizeof(si));
        si.cb = sizeof(si);
        ZeroMemory(&pi, sizeof(pi));

        si.dwFlags = STARTF_USESHOWWINDOW; // 指定wShowWindow成员有效
        si.wShowWindow = TRUE;             // 此成员设为TRUE的话则显示新建进程的主窗口,为FALSE的话则不显示

        if (!CreateProcess(filePath.c_str(), NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
     
            throw std::exception("create wechat process error, regedit may be changed!");
        }

        if (!firstWindowName.empty()) {
     
            HWND  hwnd = nullptr;
            int i = 0;

            while (nullptr == hwnd) {
     
                hwnd = FindWindow(firstWindowName.c_str(), NULL);
                ++i;

                if (i >= 3)
                    break;

                Sleep(500);
            }

            if (nullptr == hwnd) {
     
                throw std::exception("not find the window,please check it");
            }
        }
    }

    void InjectDll(const std::string& exeName, const std::wstring& targetDllFilePath) {
     
        //获取微信Pid
        DWORD dwPid = ProcessNameFindPID(exeName.c_str());

        if (dwPid == 0) {
     
            throw std::exception("not find process.");
        }

        std::wstring fileName = getFileName(targetDllFilePath);

        //检测dll是否已经注入
        if (!CheckIsInject(dwPid, targetDllFilePath)) {
     
            //打开进程
            HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);

            if (hProcess == nullptr) {
     
                throw std::exception(nbase::UTF16ToUTF8(L"进程打开失败").c_str());
            }

            // 在微信进程中申请内存
            // 1参数进程句柄,2分配空间位置为空就随机,3一个路径地址,4权限,5可读可写
            LPVOID pAddress = VirtualAllocEx(hProcess, NULL, MAX_PATH, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

            if (pAddress == nullptr) {
     
                throw std::exception(nbase::UTF16ToUTF8(L"内存分配失败").c_str());
            }

            //写入dll路径到微信进程
            const auto path = nbase::UTF16ToUTF8(targetDllFilePath);

            if (WriteProcessMemory(hProcess, pAddress, path.c_str(), path.length(), NULL) == 0) {
     
                throw std::exception(nbase::UTF16ToUTF8(L"路径写入失败").c_str());
            }

            //获取LoadLibraryA函数地址
            FARPROC pLoadLibraryAddress = GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA");

            if (pLoadLibraryAddress == NULL) {
     
                throw std::exception(nbase::UTF16ToUTF8(L"获取LoadLibraryA函数地址失败").c_str());
            }

            // 注入成功后开线程操作
            // CreateRemoteThread第四个参数需要拿自己的地址用 GetModuleHandle 获取Kernel32基址
            HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pLoadLibraryAddress, pAddress, 0, NULL);

            if (hRemoteThread == NULL) {
     
                throw std::exception(nbase::UTF16ToUTF8(L"远程线程注入失败").c_str());
            }

            CloseHandle(hRemoteThread);
            CloseHandle(hProcess);
        }
    }

    std::wstring getFileName(const std::wstring& filePath) {
     
        const auto index = filePath.find_last_of(L"\\");

        if (index == std::wstring::npos) {
     
            throw std::exception("invalid filePath");
        }

        std::wstring fileName = filePath.substr(index + 1, filePath.length() - index - 1);
        return std::move(fileName);
    }

    bool CheckIsInject(DWORD processid, const std::wstring& targetDllFilePath) {
     
        HANDLE hModuleSnap = INVALID_HANDLE_VALUE;
        //初始化模块信息结构体
        MODULEENTRY32 me32 = {
      sizeof(MODULEENTRY32) };
        //创建模块快照 1 快照类型 2 进程ID
        hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, processid);

        //如果句柄无效就返回false
        if (hModuleSnap == INVALID_HANDLE_VALUE) {
     
            throw std::exception(nbase::UTF16ToUTF8(L"创建模块快照失败").c_str());
        }

        //通过模块快照句柄获取第一个模块的信息
        if (!Module32First(hModuleSnap, &me32)) {
     
            //获取失败则关闭句柄
            CloseHandle(hModuleSnap);

            throw std::exception(nbase::UTF16ToUTF8(L"获取第一个模块的信息失败").c_str());
        }

        std::wstring fileName = getFileName(targetDllFilePath);

        do {
     
            if (std::wstring(me32.szModule) == fileName) {
     
                return true;
            }

        } while (Module32Next(hModuleSnap, &me32));

        return false;
    }

    void UnloadDll(const std::string& exeName, const std::wstring& targetDllFilePath) {
     
        //获取微信Pid
        DWORD dwPid = ProcessNameFindPID(exeName.c_str());

        if (dwPid == 0) {
     
            throw std::exception(nbase::UTF16ToUTF8(L"找不到进程").c_str());
        }

        std::wstring fileName = getFileName(targetDllFilePath);

        //遍历模块
        HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPid);
        MODULEENTRY32 ME32 = {
      0 };
        ME32.dwSize = sizeof(MODULEENTRY32);
        BOOL isNext = Module32First(hSnap, &ME32);
        BOOL flag = FALSE;

        while (isNext) {
     
            USES_CONVERSION;

            if (strcmp(W2A(ME32.szModule), nbase::UTF16ToUTF8(fileName).c_str()) == 0) {
     
                flag = TRUE;
                break;
            }

            isNext = Module32Next(hSnap, &ME32);
        }

        CloseHandle(hSnap);

        if (flag == FALSE) {
     
            throw std::exception(nbase::UTF16ToUTF8(L"找不到目标模块").c_str());
        }

        //打开目标进程
        HANDLE hPro = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
        //获取FreeLibrary函数地址
        FARPROC pFun = GetProcAddress(GetModuleHandleA("kernel32.dll"), "FreeLibrary");

        //创建远程线程执行FreeLibrary
        HANDLE hThread = CreateRemoteThread(hPro, NULL, 0, (LPTHREAD_START_ROUTINE)pFun, ME32.modBaseAddr, 0, NULL);

        if (!hThread) {
     
            throw std::exception(nbase::UTF16ToUTF8(L"创建远程线程失败").c_str());
        }

        if (WaitForSingleObject(hThread, 2 * 1000) == WAIT_TIMEOUT) {
     
            CloseHandle(hThread);
            CloseHandle(hPro);
            throw std::exception(nbase::UTF16ToUTF8(L"远程线程执行超时,动态库或许有残留线程没退出!").c_str());
        }

        CloseHandle(hThread);
        CloseHandle(hPro);
    }

}

更多

相关代码我已经放在了 https://github.com/xmcy0011/study-trace

你可能感兴趣的:(C++葵花宝典,c++,windows,多线程)