DLL注入工具:利用 ZwCreateThreadEx 注入 DLL

1 ZwCreateThreadEx 和 CreateRemoteThread

1.1 CreateRemoteThread函数

创建在另一个进程的虚拟地址空间中运行的线程,并可选择指定扩展属性。
由于该函数没有在ntdll.dll库中申明所以需要通过GetProcAddress获取函数的导出地址

函数原型
HANDLE CreateRemoteThread(
HANDLE hProcess, // handle to process
LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD
SIZE_T dwStackSize, // initial stack size
LPTHREAD_START_ROUTINE lpStartAddress, // thread function
LPVOID lpParameter, // thread argument
DWORD dwCreationFlags, // creation option
LPDWORD lpThreadId // thread identifier
);
参数说明:
hProcess
[输入] 进程句柄
lpThreadAttributes
[输入] 线程安全描述字,指向SECURITY_ATTRIBUTES结构的指针
dwStackSize
[输入] 线程栈大小,以字节表示
lpStartAddress
[输入] 一个LPTHREAD_START_ROUTINE类型的指针,指向在远程进程中执行的函数地址
lpParameter
[输入] 传入参数
dwCreationFlags
[输入] 创建线程的其它标志

lpThreadId
[输出] 线程身份标志,如果为NULL,则不返回

返回值
成功返回新线程句柄,失败返回NULL,并且可调用GetLastError获得错误值。

1.2 ZwCreateThreadEx

ZwCreateThreadEx 是利用 ntdll 导出的未文档化函数

1.3 ZwCreateThreadEx与CreateRemoteThread的区别:

  • CreateRemoteThread是由ZwCreateThreadEx调用而来的,而在底层的调用中发现CreateRemoteThread 中的CreateSuspened 默认为1,导致线程创建完成后一直无法挂起恢复运行
  • 传统的 CreateRemoteThreadEx 注入方式极容易被查杀,不便于测试环境。

1.4 ZwCreateThreadEx 函数在 32 和 64 上的定义不相同,参见:

  • x86-64:
typedef DWORD(WINAPI* typedef _ZwCreateThreadEx)(
        PHANDLE ThreadHandle,
        ACCESS_MASK DesiredAccess,
        LPVOID ObjectAttributes,
        HANDLE ProcessHandle,
        LPTHREAD_START_ROUTINE lpStartAddress,
        LPVOID lpParameter,
        ULONG CreateThreadFlags,
        SIZE_T ZeroBits,
        SIZE_T StackSize,
        SIZE_T MaximumStackSize,
        LPVOID pUnkown
        );
  • x86-32:
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
        PHANDLE ThreadHandle,
        ACCESS_MASK DesiredAccess,
        LPVOID ObjectAttributes,
        HANDLE ProcessHandle,
        LPTHREAD_START_ROUTINE lpStartAddress,
        LPVOID lpParameter,
        BOOL CreateSuspended,
        DWORD dwStackSize,
        DWORD dw1,
        DWORD dw2,
        LPVOID pUnkown
        );

其中,lpStartAddress 支持提供内存地址用于执行 Payload 函数,lpParameter 传入结构体指针,用于在回调中使用。

2 遍历目标进程的 PID

我们常常使用进程名作为特征,遍历进程的 PID 以及句柄,然后执行注入过程,
这里我们使用 CreateToolhelp32Snapshot 获取进程快照,再通过Process32First和Process32Next遍历目标进程的 PID

2.1 Process32First 得到父进程ID

这里,我们重点讲一下Process32First,Process32First返回进程快照中第一个进程的信息,每个进程都存着一个PROCESSENTRY32信息,里面的数据结构如下:

typedef struct tagPROCESSENTRY32W
{
    DWORD   dwSize;                 // 结构大小 
    DWORD   cntUsage;               // 此进程的引用计数 必须为 0
    DWORD   th32ProcessID;          // this process
    ULONG_PTR th32DefaultHeapID;    // 进程默认堆ID 必须为0
    DWORD   th32ModuleID;           // associated exe  进程模块ID 必须为0
    DWORD   cntThreads;             // 进程线程数
    DWORD   th32ParentProcessID;    // 进程父ID
    LONG    pcPriClassBase;         // 此进程创建的任何线程的基本优先级。
    DWORD   dwFlags;                // 必须为0
    WCHAR   szExeFile[MAX_PATH];    // Path  进程可执行文件名称
} PROCESSENTRY32W;
typedef PROCESSENTRY32W *  PPROCESSENTRY32W;
typedef PROCESSENTRY32W *  LPPROCESSENTRY32W;

2.2 通过Module32First()获取第一个模块信息

这里,我们重点讲一下Module32First,Module32First返回进程快照中第一个模块的信息,每个模块都存着一个MODULEENTRY32信息,里面的数据结构如下:

typedef struct tagMODULEENTRY32W
{
    DWORD   dwSize;				// 结构大小 
    DWORD   th32ModuleID;       // This module
    DWORD   th32ProcessID;      // owning process
    DWORD   GlblcntUsage;       // Global usage count on the module
    DWORD   ProccntUsage;       // Module usage count in th32ProcessID's context
    BYTE  * modBaseAddr;        // Base address of module in th32ProcessID's context
    DWORD   modBaseSize;        // Size in bytes of module starting at modBaseAddr
    HMODULE hModule;            // The hModule of this module in th32ProcessID's context
    WCHAR   szModule[MAX_MODULE_NAME32 + 1];
    WCHAR   szExePath[MAX_PATH];
} MODULEENTRY32W;
typedef MODULEENTRY32W *  PMODULEENTRY32W;
typedef MODULEENTRY32W *  LPMODULEENTRY32W;

2.3 处理崩溃的进程

需要注意的是我们可能遇到内存中已经崩溃的进程,这种时候会导致注入器不断尝试注入过程,但始终失败。PROCESSENTRY32W 结构体中的 cntThreads 包含进程的线程计数信息,崩溃的进程没有主线程,我们可以加上这个条件来过滤。

2.4 获取父进程ID信息代码如下:

std::vector<DWORD> pids;	// 容器对象用于存储 PID
// 创建进程快照
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap == INVALID_HANDLE_VALUE)
{
	printf("Create snapshot error:%d\n", GetLastError());
}
else 
{
	PROCESSENTRY32W pe = { 0 };
	pe.dwSize = sizeof(PROCESSENTRY32W);
	
	// 使用 Process32FirstW 和 Process32NextW 的组合来遍历快照信息
	if (Process32FirstW(hSnap, &pe)) {
	    do {
	        // wcscmp 比较进程名称是否相同,
	        // pe.cntThreads >= 1 可以过滤已经崩溃的进程
	        if (!wcscmp(pe.szExeFile, exepth) && pe.cntThreads >= 1) {
	            pids.push_back(pe.th32ProcessID);	// 压入 PID 信息
	        }
	    } while (Process32NextW(hSnap, &pe));
	}
	// 关闭句柄
	CloseHandle(hSnap);
}

2.5 提升至管理员权限

现在我们有了 PID 有了 注入执行函数,但是进程可能需要提升至管理员权限并开启 DEBUG 权限才能注入DLL,我们使用 AdjustTokenPrivileges 函数实现获取进程 DEBUG 权限:

BOOL EnableDebugPrivilege(BOOL bEnablePrivilege)   // to enable or disable privilege
{
    HANDLE hProcess = NULL;
    TOKEN_PRIVILEGES tp{};
    LUID luid;
    hProcess = GetCurrentProcess();
    HANDLE hToken = NULL;
    OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &hToken);
 
    if (!LookupPrivilegeValueW(
        NULL,           // lookup privilege on local system
        SE_DEBUG_NAME,  // privilege to lookup 
        &luid))        	// receives LUID of privilege
    {
        printf("LookupPrivilegeValue error: %u\n", GetLastError());
        CloseHandle(hToken);
        return FALSE;
    }
 
    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    if (bEnablePrivilege)
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    else
        tp.Privileges[0].Attributes = 0;
 
    // Enable the privilege or disable all privileges.
    if (!AdjustTokenPrivileges(
        hToken,
        FALSE,
        &tp,
        sizeof(TOKEN_PRIVILEGES),
        (PTOKEN_PRIVILEGES)NULL,
        (PDWORD)NULL))
    {
        printf("AdjustTokenPrivileges error: %u\n", GetLastError());
        CloseHandle(hToken);
        return FALSE;
    }
 
    if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
    {
        printf("The token does not have the specified privilege. \n");
        CloseHandle(hToken);
        return FALSE;
    }
    CloseHandle(hToken);
    return TRUE;
}

3 完整注入器代码:

此注入器支持参数:

DLLInjectTool.exe <要注入的目标进程名(含后缀)> <要注入完整 DLL 路径>
#include <iostream>
#include <windows.h>
#include <vector>
#include <tlhelp32.h>
#include <shlwapi.h>
 
#pragma comment(lib, "Shlwapi.lib")
#pragma comment(lib, "advapi32.lib")
BOOL ProcessHasLoadDll(
    DWORD pid, 
    const TCHAR* dll
);
 
BOOL ZwCreateThreadExInjectDll(
    DWORD dwProcessId,
    const wchar_t* pszDllFileName
);
 
BOOL EnableDebugPrivilege(
    BOOL bEnablePrivilege
);
 
int wmain(int argc, wchar_t* argv[])
{
    SetConsoleTitleW(L"DLLInjectTool v.1.0");
    printf("DLLInjectTool v.1.0 通用 DLL 注入工具;@Rio-CTH\n");
    if (argc != 3)
    {
        std::cout << "错误:参数不合法!" << std::endl;
        std::cin.get();
        return -1;
    }
    const size_t exepthlen =
        wcslen(argv[1]) * sizeof(wchar_t);
    const size_t dllpthlen = 
        wcslen(argv[2]) * sizeof(wchar_t);
 
    if (exepthlen < 5 || dllpthlen < 1)
    {
        std::cout << "错误:文件路径错误!" << std::endl;
        std::cin.get();
        return -1;
    }
    wchar_t* exepth = new wchar_t[exepthlen];
    wchar_t* dllpth = new wchar_t[dllpthlen];
    //wchar_t pszPath[MAX_PATH] = { 0 };
    wcscpy_s(exepth, exepthlen, argv[1]);
    wcscpy_s(dllpth, dllpthlen, argv[2]);
    //wcscpy_s(pszPath, dllpthlen, argv[2]);
    //BOOL exeextflag = PathFileExistsW(exepth);
    if (!wcsstr(exepth, L".exe"))
    {
        std::cout << "错误:目标进程名必须包含.exe后缀!" << std::endl;
        std::cin.get();
        return -1;
    }
 
    BOOL dllextflag = PathFileExistsW(dllpth);
    if (FALSE == dllextflag)
    {
        std::cout << "错误:文件不存在或者无法访问!" << std::endl;
        std::cin.get();
        return -1;
    }
 
    if (PathGetDriveNumberW(dllpth) == -1)
    {
        TCHAR szPath[_MAX_PATH] = { 0 };
        TCHAR szDrive[_MAX_DRIVE] = { 0 };
        TCHAR szDir[_MAX_DIR] = { 0 };
        TCHAR szFname[_MAX_FNAME] = { 0 };
        TCHAR szExt[_MAX_EXT] = { 0 };
        //TCHAR DataBinPath[MAX_PATH] = { 0 };
        TCHAR CurBinPath[MAX_PATH] = { 0 };
        GetModuleFileNameW(NULL, szPath, sizeof(szPath) / sizeof(TCHAR));
        ZeroMemory(CurBinPath, sizeof(CurBinPath));
        _wsplitpath_s(szPath, szDrive, szDir, szFname, szExt);
        wsprintf(CurBinPath, L"%s%s", szDrive, szDir);
        //wcscpy_s(DataBinPath, CurBinPath);
        wcscat_s(CurBinPath, dllpth);
        //wprintf(L"[WorkstationDirectory]: %s\n", DataBinPath);
        dllextflag = PathFileExistsW(CurBinPath);
        if (FALSE == dllextflag)
        {
            std::cout << "错误:文件不存在或者无法访问!" << std::endl;
            std::cin.get();
            return -1;
        }
        dllpth = CurBinPath;
    }
 
    EnableDebugPrivilege(TRUE);
 
    std::vector<DWORD> pids;
    HANDLE hp = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32W pe = { 0 };
    pe.dwSize = sizeof(PROCESSENTRY32W);
    if (Process32FirstW(hp, &pe)) {
        do {
            if (!wcscmp(pe.szExeFile, exepth) && pe.cntThreads >= 1) {
                pids.push_back(pe.th32ProcessID);
            }
        } while (Process32NextW(hp, &pe));
    }
    CloseHandle(hp);
 
    for (int i = 0; i < pids.size(); i++) {
        if (ProcessHasLoadDll(pids[i], dllpth) != NULL)
        {
            std::cout << "警告:PID 为 " << pids[i] << " 的进程已经包含目标 DLL。" << std::endl;
            continue;
        }
 
        if (ZwCreateThreadExInjectDll(pids[i], dllpth) == FALSE)
        {
            std::cout << "错误:注入 PID 为 " << pids[i] << " 的进程时失败。" << std::endl;
            std::cout << "原因:" << GetLastError() << std::endl;
        }
    }
 
    if (pids.size() == 0)
    {
        std::wcout << "错误:未找到目标进程:" << exepth << std::endl;
        std::cin.get();
        EnableDebugPrivilege(FALSE);
        return -1;
    }
    // 销毁过程
    pids.clear();
    pids.shrink_to_fit();
    std::cout << "操作已经完成。" << std::endl;
    std::cin.get();
    EnableDebugPrivilege(FALSE);
    return 0;
}
 
BOOL EnableDebugPrivilege(
    BOOL bEnablePrivilege   // to enable or disable privilege
)
{
    HANDLE hProcess = NULL;
    TOKEN_PRIVILEGES tp{};
    LUID luid;
    hProcess = GetCurrentProcess();
    HANDLE hToken = NULL;
    OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &hToken);
 
    if (!LookupPrivilegeValueW(
        NULL,            // lookup privilege on local system
        SE_DEBUG_NAME,   // privilege to lookup 
        &luid))        // receives LUID of privilege
    {
        printf("LookupPrivilegeValue error: %u\n", GetLastError());
        CloseHandle(hToken);
        return FALSE;
    }
 
    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    if (bEnablePrivilege)
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    else
        tp.Privileges[0].Attributes = 0;
 
    // Enable the privilege or disable all privileges.
 
    if (!AdjustTokenPrivileges(
        hToken,
        FALSE,
        &tp,
        sizeof(TOKEN_PRIVILEGES),
        (PTOKEN_PRIVILEGES)NULL,
        (PDWORD)NULL))
    {
        printf("AdjustTokenPrivileges error: %u\n", GetLastError());
        CloseHandle(hToken);
        return FALSE;
    }
 
    if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
 
    {
        printf("The token does not have the specified privilege. \n");
        CloseHandle(hToken);
        return FALSE;
    }
    CloseHandle(hToken);
    return TRUE;
}
 
 
BOOL ProcessHasLoadDll(DWORD pid, const TCHAR* dll) {
    /*
    * 参数为TH32CS_SNAPMODULE 或 TH32CS_SNAPMODULE32时,如果函数失败并返回ERROR_BAD_LENGTH,则重试该函数直至成功
    * 进程创建未初始化完成时,CreateToolhelp32Snapshot会返回error 299,但其它情况下不会
    */
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);
    while (INVALID_HANDLE_VALUE == hSnapshot) {
        DWORD dwError = GetLastError();
        if (dwError == ERROR_BAD_LENGTH) {
            hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);
            continue;
        }
        else {
            //if (dwError == ERROR_PARTIAL_COPY) {
                //printf("CreateToolhelp32Snapshot failed: %d\ncurrentProcessId: %d \t targetProcessId:\n",
                    //dwError, GetCurrentProcessId(), pid);
            //}
            //else {
                printf("CreateToolhelp32Snapshot failed: %d\ncurrentProcessId: %d \t targetProcessId:%d\n",
                    dwError, GetCurrentProcessId(), pid);
            //}
            return FALSE;
        }
    }
    MODULEENTRY32W mi{};
    mi.dwSize = sizeof(MODULEENTRY32W); //第一次使用必须初始化成员
    BOOL bRet = Module32FirstW(hSnapshot, &mi);
    while (bRet) {
        // mi.szModule是短路径
        if (wcsstr(dll, mi.szModule) || wcsstr(mi.szModule, dll)) {
            //TCHAR* dllWithFullPath = new TCHAR[MAX_MODULE_NAME32 + 1];
            //lstrcpyW(dllWithFullPath, mi.szModule);
            if (hSnapshot != NULL) CloseHandle(hSnapshot);
            return TRUE;
        }
        mi.dwSize = sizeof(MODULEENTRY32W);
        bRet = Module32NextW(hSnapshot, &mi);
    }
    if(hSnapshot != NULL) CloseHandle(hSnapshot);
    return FALSE;
}
 
BOOL ZwCreateThreadExInjectDll(DWORD dwProcessId, const wchar_t* pszDllFileName)
{
    size_t pathSize = (wcslen(pszDllFileName) + 1) * sizeof(wchar_t);
    // 1.打开目标进程
    HANDLE hProcess = OpenProcess(
        PROCESS_ALL_ACCESS, // 打开权限
        FALSE, // 是否继承
        dwProcessId); // 进程PID
    if (NULL == hProcess)
    {
        printf("错误:打开目标进程失败!\n");
        return FALSE;
    }
    // 2.在目标进程中申请空间
    LPVOID lpPathAddr = VirtualAllocEx(
        hProcess, // 目标进程句柄
        0, // 指定申请地址
        pathSize, // 申请空间大小
        MEM_RESERVE | MEM_COMMIT, // 内存的状态
        PAGE_READWRITE); // 内存属性
    if (NULL == lpPathAddr)
    {
        printf("错误:在目标进程中申请空间失败!\n");
        CloseHandle(hProcess);
        return FALSE;
    }
    // 3.在目标进程中写入Dll路径
    if (FALSE == WriteProcessMemory(
        hProcess, // 目标进程句柄
        lpPathAddr, // 目标进程地址
        pszDllFileName, // 写入的缓冲区
        pathSize, // 缓冲区大小
        NULL)) // 实际写入大小
    {
        printf("错误:目标进程中写入Dll路径失败!\n");
        CloseHandle(hProcess);
        return FALSE;
    }
 
    //4.加载ntdll.dll
    HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
    if (NULL == hNtdll)
    {
        printf("错误:加载ntdll.dll失败!\n");
        CloseHandle(hProcess);
        return FALSE;
    }
 
    // 5.获取LoadLibraryA的函数地址
    // FARPROC可以自适应32位与64位
    FARPROC pFuncProcAddr = GetProcAddress(GetModuleHandle(L"Kernel32.dll"),
        "LoadLibraryW");
    if (NULL == pFuncProcAddr)
    {
        printf("错误:获取LoadLibrary函数地址失败!\n");
        CloseHandle(hProcess);
        return FALSE;
    }
 
    // 6.获取ZwCreateThreadEx函数地址,该函数在32位与64位下原型不同
    // _WIN64用来判断编译环境 ,_WIN32用来判断是否是Windows系统
#ifdef _WIN64
    typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
        PHANDLE ThreadHandle,
        ACCESS_MASK DesiredAccess,
        LPVOID ObjectAttributes,
        HANDLE ProcessHandle,
        LPTHREAD_START_ROUTINE lpStartAddress,
        LPVOID lpParameter,
        ULONG CreateThreadFlags,
        SIZE_T ZeroBits,
        SIZE_T StackSize,
        SIZE_T MaximumStackSize,
        LPVOID pUnkown
        );
#else
    typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
        PHANDLE ThreadHandle,
        ACCESS_MASK DesiredAccess,
        LPVOID ObjectAttributes,
        HANDLE ProcessHandle,
        LPTHREAD_START_ROUTINE lpStartAddress,
        LPVOID lpParameter,
        BOOL CreateSuspended,
        DWORD dwStackSize,
        DWORD dw1,
        DWORD dw2,
        LPVOID pUnkown
        );
#endif 
    typedef_ZwCreateThreadEx ZwCreateThreadEx =
        (typedef_ZwCreateThreadEx)GetProcAddress(hNtdll, "ZwCreateThreadEx");
    if (NULL == ZwCreateThreadEx)
    {
        printf("错误:获取ZwCreateThreadEx函数地址失败!\n");
        CloseHandle(hProcess);
        return FALSE;
    }
    //7.在目标进程中创建远线程
    HANDLE hRemoteThread = NULL;
    DWORD lpExitCode = 0;
    DWORD dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL,
        hProcess,
        (LPTHREAD_START_ROUTINE)pFuncProcAddr, lpPathAddr, 0, 0, 0, 0, NULL);
    if (NULL == hRemoteThread)
    {
        printf("错误:目标进程中创建线程失败!\n");
        CloseHandle(hProcess);
        return FALSE;
    }
 
    // 8.等待线程结束
    WaitForSingleObject(hRemoteThread, -1);
    GetExitCodeThread(hRemoteThread, &lpExitCode);
    if (lpExitCode == 0) 
    {
        printf("错误:目标进程中注入 DLL 失败,请检查提供的 DLL 是否有效!\n");
        CloseHandle(hProcess);
        return FALSE;
    }
    // 9.清理环境
    VirtualFreeEx(hProcess, lpPathAddr, 0, MEM_RELEASE);
    CloseHandle(hRemoteThread);
    CloseHandle(hProcess);
    FreeLibrary(hNtdll);
    return TRUE;
}
  • 10
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值