利用 ZwCreateThreadEx 注入 DLL

传统的 CreateRemoteThreadEx 注入方式极容易被查杀,不便于测试环境。

我们可以采取很多方法来注入 DLL 比如较新的早鸟(EarlyBird)技术,就是利用 APC 配合未公开函数进行劫持注入。

这里我们给出一个利用 ntdll 导出的未文档化函数 ZwCreateThreadEx 实现远程进程注入。

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 传入结构体指针,用于在回调中使用。

我们常常使用进程名作为特征,遍历进程的 PID 以及句柄,然后执行注入过程,

这里我们使用 CreateToolhelp32Snapshot 遍历目标进程的 PID,需要注意的是我们可能遇到内存中已经崩溃的进程,这种时候会导致注入器不断尝试注入过程,但始终失败

PROCESSENTRY32W 结构体中的 cntThreads 包含进程的线程计数信息,崩溃的进程没有主线程,于是,可以加上这个条件来过滤。

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

现在我们有了 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;
}

完整注入器代码:

此注入器支持参数:

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"LoadLibraryTool v.1.0");
    printf("LoadLibraryTool 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;
}

测试结果:

注意事项:

当成功创建远程线程,但在远程函数执行时可能出现目标进程异常崩溃的现象。此时,可以通过系统收集的信息进行分析。如果观察到错误类型为“AppCrash” 且错误原因包含 “StackHash” 关键字,则有可能是触发了 DEP 或者 ASLR 导致的。当然,这并不绝对。此时应该检查远程函数是否关注了不同进程中可能由基址随机化导致的内存访问违例(目标模块需要重定位);然后,再检查注入的代码是否存在潜在的缓冲区溢出或者堆栈损坏,这会导致 DEP。此外,可能还需要检查注入代码中的 api 依赖的模块是否已经在目标进程中初始化,以及是否已经将多级调用的每一层函数的代码均写入到目标进程,并正确重定位(不能只拷贝最外层 Call)。

转载请注明出处:利用 ZwCreateThreadEx 注入 DLLicon-default.png?t=O83Ahttp://t.csdnimg.cn/uuMvK

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

涟幽516

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值