使用远程线程注入的三个函数CreateRemoteThread、NtCreateThreadEx、RtlCreateUserThread

本文介绍了三种远程线程注入技术:CreateRemoteThread、RtlCreateUserThread 和 NtCreateThreadEx 的使用方法,包括参数解释、注意事项及跨版本兼容性解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

远程线程注入的三个函数


  • CreateRemoteThread
          创建一个在其它的进程的虚拟地址空间中运行的线程。
          使用CreateRemoteThreadEx 函数来创建一个在其它的进程的虚拟地址空间中运行的线程并可选的指定一个特定的扩展属性。
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
);

参数

      hProcess
      指定即将在其内部创建一个线程的目标进程,该句柄必须拥有PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_WRITE, 和 PROCESS_VM_READ 访问权限。
      lpThreadAttributes
      SECURITY_ATTRIBUTES 结构体指针,指定新创建的线程的安全属性,以及函数返回的子线程的句柄是否能够被子进程继承。如果该参数为NULL,得到一个默认的安全属性,且句柄不可以被继承。线程的默认的安全描述符的ACL 来自于创建者的主令牌。
            Windows XP:一个线程的默认ACL 来自于创建者的主或者模仿令牌。Windows XP SP2 和 Windows Server 2003 开始改变。
      dwStackSize
      栈的初始大小,字节对齐。详细请参考https://msdn.microsoft.com/en-us/library/windows/desktop/ms686774(v=vs.85).aspx
      lpStartAddress
      远程线程的起始地址。
      lpParameter
      传递给远程线程的指针
      dwCreateFlags
      控制线程创建的标志

含义
0线程创建之后立马执行
CREATE_SUSPENDED暂停的线程,需要调用ResumeThread 恢复其执行
STACK_SIZE_PARAM_IS_A_RESERVATIONdwStackSize 参数指定了栈的初始预留的大小,如果没有指定这个参数的话,dwStackSize 参数指定的是提交的大小

      lpThreadId
      用于接收线程ID。可为NULL

返回值

      成功的话,返回值是新创建的线程的句柄。失败返回NULL。
      即使lpStartAddress 指向的内存位置非法,CreateRemoteThread 也可能调用成功。当新线程开始执行的时候,如果起始地址非法,触发异常并导致线程退出。非法的起始地址导致的线程终止会导致线程所在的进程错误退出。这比较像CreateProcess 的异步属性,即使创建的进程会有DLL 丢失的情况,该创建操作也会成功。

注意

      终端服务将每个会话隔离起来了,因此,如果目标进程和调用者进程在不同的会话中,CreateRemoteThread 调用将失败。
      当我们不为新创建的线程提供安全描述符的时候,函数返回的线程句柄拥有对于新创建的线程的所有的访问权限,如果我们提供了,之后对于该线程句柄的操作将会通过ACL 的检查,如果检查失败的话,请求该操作的进程将无法以期待的方式访问该线程。
      如果创建线程的时候没有指定暂停标志,新创建的线程在CreateRemoteThread 函数返回之前就已经开始执行了。
      ExitProcess、ExitThread、CreateThread、CreateRemoteThread 函数和正在开始执行的进程,它们在同一个进程内的操作都被同步了。在一个地址空间中,上面列举出的事件中,只有一个在发生。这意味着,下面的规则必须被遵守。

  1. 在进程开始以及DLL 初始化函数中,新线程可以被创建,但是直到进程的DLL 初始化完成,它们都得不到执行。
  2. 一个进程中只能有一个线程在进行DLL 初始化或者派遣例程。
  3. 直到所有的线程完成了它们的DLL 初始化或者派遣例程之后,ExitProcess 函数才会退出。

  • RtlCreateUserThread
typedef NTSTATUS (WINAPI *LPFUN_RtlCreateUserThread)(
    HANDLE,                 // ProcessHandle
    PSECURITY_DESCRIPTOR,   // SecurityDescriptor (OPTIONAL)
    BOOLEAN,                // CreateSuspended
    ULONG,                  // StackZeroBits
    SIZE_T,                 // StackReserved
    SIZE_T,                 // StackCommit
    PVOID,                  // StartAddress
    PVOID,                  // StartParameter (OPTIONAL)
    PHANDLE,                // ThreadHandle
    PCLIENT_ID              // ClientID
    );

       这个函数可以实现跨会话创建线程。唯一的问题就是:当我们在Vista 之前的操作系统调用此函数所创建的线程并没有通知给csrss 进程,它没有完整的初始化,在调用一些网络或者其它的系统函数的时候他可能返回失败,而通过CreateRemoteThread 创建的线程没有任何问题,在Vista+的操作系统上调用此函数也没有问题。因此我们需要判断系统版本,当目标系统为Vista-的时候,我们应该通过RtlCreateUserThread创建一个挂起的线程,然后调用CsrClientCallServer通知csrss 这个线程的创建,然后csrss 做相应的记录及初始化工作之后,我们再恢复线程的执行。最后,我们新创建的线程必须自己调用ExitThread退出,否则也会产生崩溃的情况。

  • NtCreateThreadEx
typedef DWORD64 (WINAPI *PFNTCREATETHREADEX)  
    (   
    PHANDLE                 ThreadHandle,     
    ACCESS_MASK             DesiredAccess,    
    LPVOID                  ObjectAttributes,     
    HANDLE                  ProcessHandle,    
    LPTHREAD_START_ROUTINE  lpStartAddress,   
    LPVOID                  lpParameter,      
    BOOL                    CreateSuspended,      
    DWORD64                   dwStackSize,      
    DWORD64                   dw1,   
    DWORD64                   dw2,   
    LPVOID                  Unknown   
    ); 

       在Windows 2000 和 XP SP 0,1 上我们使用CreateRemoteThread,在Vista+ 上我们使用NtCreateThreadEx。
在XP SP2 上,我们可以按照RtlCreateUserThread + CsrClientCallServer 的方式创建远程线程。

  • 代码示例
// 下面这个函数就是调用NtCreadThreadEx 创建远程线程
HANDLE MyCreateRemoteThread(HANDLE hProcess, LPTHREAD_START_ROUTINE pThreadProc, LPVOID pRemoteBuf)  
{  
    HANDLE      hThread = NULL;  
    FARPROC     pFunc = NULL;  
    // 只有Vista 之后操作系统可用此函数
    if(IsVistaOrLater())
    {
        pFunc = GetProcAddress(GetModuleHandle(_T("ntdll.dll")), "NtCreateThreadEx");  
        if( pFunc == NULL )  
        {
            ShowMessage(_T("无法得到NtCreateThreadEx 函数地址"));
            ErrorShow();
            return FALSE;  
        }  
        ((PFNTCREATETHREADEX)pFunc)(
            &hThread, 
            0x1FFFFF,   // 内核调试的结果就是这个参数
            NULL,  
            hProcess,  
            pThreadProc,  
            pRemoteBuf,  
            FALSE,  
            NULL,  
            NULL,  
            NULL,  
            NULL);  
        if( hThread == NULL )  
        {  
            ErrorShow();
            return FALSE;  
        }
        return hThread;  
    }
    else
    {
        ShowMessage(_T("版本错误\r\n无NtCreateThreadEx 函数"));
        return FALSE;
    }
} 

// 使用CreateRemoteThread 创建远程线程的操作比较简单直接调用即可,毕竟是文档化的函数
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf,0, NULL);  

// RtlCreateUserThread 的使用

#define STATUS_SUCCESS                      ((NTSTATUS) 0x00000000) 
#define STATUS_INFO_LENGTH_MISMATCH        ((NTSTATUS) 0xC0000004) 
#define STATUS_IO_DEVICE_ERROR              ((NTSTATUS) 0xC0000185) 
struct THREAD_INFO
{
    HANDLE hThread;
    CLIENT_ID id;
};

struct CSRSS_MESSAGE 
{
    ULONG Unknown1;
    ULONG Opcode;
    ULONG Status;
    ULONG Unknown2;
};

struct PORT_MESSAGE
{
    ULONG u1;
    ULONG u2;

    union
    {
        CLIENT_ID ClientId;
        float DoNotUseThisField;
    };

    ULONG MessageId;

    union
    {
        ULONG ClientViewSize;
        ULONG CallbackId;
    };
};

struct CSR_API_MESSAGE
{
    PORT_MESSAGE  PortMessage;
    CSRSS_MESSAGE CsrssMessage;
    THREAD_INFO   ThreadInfo;
};
typedef CLIENT_ID *PCLIENT_ID; 

typedef PVOID PUSER_THREAD_START_ROUTINE; 


typedef NTSTATUS (NTAPI *pfnInitSubSystem) (
    IN OUT CSR_API_MESSAGE* ApiMessage,
    IN OUT struct CSR_CAPTURE_BUFFER* CaptureBuffer,
    IN ULONG ApiNumber,
    IN ULONG DataLength );
LPTHREAD_START_ROUTINE AllocWritePath(HANDLE hTargetProcHandle, LPCWSTR dllPath, LPVOID *lpExecParam) {

    LPVOID lpDllAddr = NULL;
    LPVOID lpWriteVal = NULL;
    LPVOID loadLibAddr = NULL;

#ifdef _WIN64
    if( lpDllAddr = VirtualAllocEx(hTargetProcHandle, NULL, wcslen(dllPath)*sizeof(WCHAR) + sizeof(WCHAR), MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE))
    {
        if (WriteProcessMemory(hTargetProcHandle, lpDllAddr, dllPath, wcslen(dllPath)*sizeof(WCHAR) + sizeof(WCHAR), NULL) == 0) {
            ErrorShow();
            return NULL;
        }
        *lpExecParam = (LPVOID *)lpDllAddr;
        loadLibAddr = GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "LoadLibraryW");
    }

#else
    BYTE StaticShellCode[31] = { 0xE8,0,0,0,0,  // call (5字节)           4
        0x5D,           // pop ebp              5
        0x8B,0xC5,  // mov eax,ebp          7
        0x83,0xC0,0x1A, // add eax,1a       10
        0x50,       // push eax             11
        0xB8,0,0,0,0,   // mov eax,LoadLibraryW 16
        0xFF,0xD0,  // call eax             18
        0x6A,0,     // push 0               20
        0xB8,0,0,0,0,   // mov eax,ExitThread   25
        0xFF,0xD0,  // call eax             27
        0xC3,   // ret 4                28
        0};
    BYTE*   ShellCode = new BYTE[wcslen(dllPath)*sizeof(WCHAR)+sizeof(WCHAR) + 31];
    CopyMemory(ShellCode,StaticShellCode,31);
    CopyMemory(&ShellCode[31],dllPath,wcslen(dllPath)*sizeof(WCHAR) + sizeof(WCHAR));
    PVOID lpCunc = GetProcAddress(GetModuleHandle("kernel32.dll"),"LoadLibraryW");
    CopyMemory(&ShellCode[13],&lpCunc,sizeof(PVOID));
    lpCunc = GetProcAddress(GetModuleHandle("kernel32.dll"),"ExitThread");
    CopyMemory(&ShellCode[22],&lpCunc,sizeof(PVOID));

    LPVOID  pRemoteCode = NULL;
    if(pRemoteCode = VirtualAllocEx(hTargetProcHandle, NULL, wcslen(dllPath)*sizeof(WCHAR) + sizeof(WCHAR)+31, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE))
    {
        if (WriteProcessMemory(hTargetProcHandle, pRemoteCode, ShellCode, wcslen(dllPath)*sizeof(WCHAR) + sizeof(WCHAR) + 31, NULL) == 0) {
            ErrorShow();
            return NULL;
        }
        *lpExecParam = 0;
        loadLibAddr = (LPVOID)pRemoteCode;
    }
#endif
    return (LPTHREAD_START_ROUTINE) loadLibAddr;
}

BOOL MyNtCreateThreadEx(CONST WCHAR* szDllPath,DWORD ProcessId)
{
    BOOL bRet = FALSE;
    OSVERSIONINFO osver;
    HANDLE      ProcessHandle = NULL;
    LPVOID      lpStartExecAddr = NULL;
    LPVOID      lpExecParam = NULL;
    osver.dwOSVersionInfoSize = sizeof(osver);
    if (GetVersionEx(&osver)) { 
        if (osver.dwMajorVersion == 5) {
            ProcessHandle = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_CREATE_THREAD, 0, ProcessId );
        }
        if (osver.dwMajorVersion == 6 && osver.dwMinorVersion == 0) {
            printf("\t[+] Detected Windows Vista\n");
            ProcessHandle = NULL;
        }
        if (osver.dwMajorVersion == 6 && osver.dwMinorVersion == 1) {
            ProcessHandle = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, ProcessId );
        }
        if (osver.dwMajorVersion == 6 && osver.dwMinorVersion == 2) {
            ProcessHandle = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, ProcessId );
        }
    } else {
        printf("\n[!] Could not detect OS version\n");
    }
    if(ProcessHandle != NULL)
    {
        if(lpStartExecAddr = AllocWritePath(ProcessHandle, szDllPath, &lpExecParam))
        {
            printf("%p\r\n",lpStartExecAddr);
            LPVOID rtlCreateUserAddr = NULL;
            HANDLE  hRemoteThread = NULL;
            CLIENT_ID cid;

            if( rtlCreateUserAddr = GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "RtlCreateUserThread") ) {

                LPFUN_RtlCreateUserThread funRtlCreateUserThread = (LPFUN_RtlCreateUserThread)rtlCreateUserAddr;
                // hRemoteThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)lpStartExecAddr,NULL,0,NULL);                                        成功
                // hRemoteThread = CreateRemoteThread(ProcessHandle, NULL, 0, (LPTHREAD_START_ROUTINE)lpStartExecAddr, lpExecParam,0, NULL);        成功
                // call RtlCreateUserThread to create suspended thread, then CsrClientCallServer(enable win32 subsystem support) and then resume thread
                // 之前失败?----XP需要通过CsrClientCallServer enable win32 subsystem support.
#ifndef _WIN64
                // create suspended thread
                funRtlCreateUserThread(         
                    ProcessHandle,          // ProcessHandle
                    NULL,               // SecurityDescriptor (OPTIONAL)
                    1,              // CreateSuspended
                    0,                  // StackZeroBits
                    0,                  // StackReserved
                    0,                  // StackCommit
                    (PVOID) lpStartExecAddr,// StartAddress
                    (PVOID) lpExecParam,// StartParameter (OPTIONAL)
                    &hRemoteThread,     // ThreadHandle
                    &cid                // ClientID
                    );
                if (hRemoteThread == NULL) {
                    ErrorShow();
                }
                else
                {
                    CSR_API_MESSAGE csrmsg = { { 0 }, { 0 }, { hRemoteThread, cid } };
                    // enable win32 subsystem support
                    pfnInitSubSystem CsrClientCallServer = (pfnInitSubSystem)GetProcAddress(GetModuleHandle(_T("ntdll.dll")),"CsrClientCallServer");
                    // Resume
                    NTSTATUS status = CsrClientCallServer( &csrmsg, NULL, 0x10001u, sizeof( THREAD_INFO ) );
                    if (status == STATUS_SUCCESS) 
                    {
                        ResumeThread(hRemoteThread);
                        bRet = TRUE;
                    }
                }
#else
                funRtlCreateUserThread(         
                    ProcessHandle,          // ProcessHandle
                    NULL,               // SecurityDescriptor (OPTIONAL)
                    0,              // CreateSuspended
                    0,                  // StackZeroBits
                    0,                  // StackReserved
                    0,                  // StackCommit
                    (PVOID) lpStartExecAddr,// StartAddress
                    (PVOID) lpExecParam,// StartParameter (OPTIONAL)
                    &hRemoteThread,     // ThreadHandle
                    &cid                // ClientID
                    );
                if(hRemoteThread == NULL || hRemoteThread == INVALID_HANDLE_VALUE)
                {
                    ErrorShow();
                }
                else
                {
                    ShowMessage(_T("%d"),GetThreadId(hRemoteThread));
                    //ResumeThread(hRemoteThread);
                    bRet = TRUE;
                }
#endif

            }
        }
    }
    return bRet;
}


// 当我们注入提权的进程的时候可能需要提升自己的权限,其中DEBUG 权限是可以满足我们要求的权限
BOOL EnableDebugPrivilegeX86(BOOL bEnable = TRUE)   //它是CRemThreadInjector类的静态成员
{   //附给本进程特权,以便访问系统进程
    BOOL bOk = FALSE;
    HANDLE hToken;
    //打开一个进程的访问令牌
    if(::OpenProcessToken(::GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hToken))
    {   //取得特权名称为"SetDebugPrivilege"的LUID
        LUID uID;
        ::LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&uID);
        //调整特权级别
        TOKEN_PRIVILEGES tp;
        tp.PrivilegeCount = 1;
        tp.Privileges[0].Luid = uID;
        tp.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : 0;
        ::AdjustTokenPrivileges(hToken,FALSE,&tp,sizeof(tp),NULL,NULL);
        bOk = (::GetLastError() == ERROR_SUCCESS);
        //关闭访问令牌句柄
        ::CloseHandle(hToken);
    }
    return bOk;
}
BOOL EnableDebugPrivilegeX64()
{
    DWORD dwRetVal;
    RtlAdjustPrivilege=(pfnRtlAdjustPrivilege64)GetProcAddress((HMODULE)(GetModuleHandle(_T("ntdll.dll"))),"RtlAdjustPrivilege");
    if(RtlAdjustPrivilege == NULL)
    {
        return FALSE;
    }
    /**
    .常量 SE_BACKUP_PRIVILEGE, "17", 公开
    .常量 SE_RESTORE_PRIVILEGE, "18", 公开
    .常量 SE_SHUTDOWN_PRIVILEGE, "19", 公开
    .常量 SE_DEBUG_PRIVILEGE, "20", 公开
     */
    return RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE,1,0,&dwRetVal);
}
BOOL EnableDebugPrivilegeX86X64()
{
#ifdef _WIN64
    return EnableDebugPrivilegeX64();
#else
    return EnableDebugPrivilegeX86();
#endif
}
### 关于内核远程线程注入的技术细节 #### Windows 平台下的内核远程线程注入 在 Windows 系统中,内核远程线程注入是一种高级技术,通常用于动态链接库 (DLL) 的加载或其他恶意行为。以下是其实现的关键技术和细节: 1. **API 函数的作用** - `VirtualAllocEx` 和 `CreateRemoteThread` 是 NT 内核模式下常用的 API 函数[^1]。这些函数允许分配目标进程的内存空间,并在其上下文中创建一个新的线程。 - 在更深层次上,`CreateRemoteThread` 调用了未公开的内核函数 `RtlCreateUserThread`,而后者实际上是基于 `NtCreateThreadEx` 或 `ZwCreateThreadEx` 实现的[^2]。 2. **具体实现流程** - 首先,使用 `OpenProcess` 打开目标进程句柄。 - 接着,利用 `VirtualAllocEx` 分配一段可写、可执行的目标进程地址空间。 - 将要执行的代码(通常是 DLL 加载路径)写入到上述分配的空间中,通过 `WriteProcessMemory` 完成数据传输。 - 使用 `CreateRemoteThread` 创建一个新线程,其入口点指向 `LoadLibrary`,参数则设置为目标 DLL 的路径字符串指针。 - 当线程运行时,它会在目标进程中调用 `LoadLibrary` 来加载指定的 DLL 文件。 3. **内核层面的具体机制** - 在内核级别,`CreateRemoteThread` 最终依赖于 `ZwCreateThreadEx` 来完成实际操作[^3]。这意味着任何对远程线程的操作都可以追溯至这一核心功能。 - 此外,为了绕过某些安全限制(如 Session 0 隔离),可能还需要额外处理权限提升或令牌伪造等问题。 4. **注意事项** - 上述方法仅适用于 Windows 操作系统环境中的用户模式程序交互;对于其他平台或者更高层次的安全防护措施,则需采用不同的策略和技术手段加以应对。 --- #### Linux 平台下的内核远程线程注入 Linux 下不存在像 Windows 中那样显式的远程线程注入接口,但由于其开源特性和灵活的设计理念,仍然可以通过一些间接途径达成类似效果: 1. **ptrace 工具的应用** - Ptrace 是一种强大的调试工具,能够监控甚至修改另一个正在运行的过程状态信息。借助 ptrace 可以向目标进程中注入自定义指令序列从而达到控制目的。 ```c long ret = ptrace(PTRACE_ATTACH, pid, NULL, NULL); // Attach to target process if(ret != 0){ perror("PTRACE_ATTACH failed"); exit(EXIT_FAILURE); } wait(NULL); // Wait until the process stops struct user_regs_struct regs; ptrace(PTRACE_GETREGS, pid, NULL, &regs); // Get current register values void *addr = mmap(0, sizeof(shellcode), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); // Allocate memory for shellcode memcpy(addr, shellcode, sizeof(shellcode)); // Copy shellcode into allocated space regs.rip = (unsigned long) addr; // Set instruction pointer to our code location ptrace(PTRACE_SETREGS, pid, NULL, &regs); // Apply changes back to registers ptrace(PTRACE_CONT, pid, NULL, NULL); // Resume execution of modified program flow ``` 2. **共享对象预加载技巧** - 利用 LD_PRELOAD 环境变量可以强制让某个特定应用程序优先加载我们自己编写的 so 库文件而不是标准库版本。这种方法虽然简单有效但也容易被检测发现所以并不推荐作为隐蔽攻击手法之一。 3. **模块化扩展支持** - 如果拥有超级管理员特权的话还可以考虑编写 kernel module 插件形式来进行更加深入级别的干预动作比如挂钩 syscalls 表项等等不过这已经超出了常规意义上的“线程注入”的范畴了属于完全改变操作系统行为特征的行为应该谨慎对待以免造成不可预料后果影响整个系统的稳定性可靠性等方面表现情况恶化加剧风险程度提高很多倍数以上不建议轻易尝试除非必要情况下才去实施此类方案设计思路规划布局安排部署落实到位才行哦! --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值