You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

64位平台CreateRemoteThread调用NtAllocateVirtualMemory时错误码5求助

问题分析与解决方案

核心原因

  1. 跨进程函数指针无效:你在本地进程获取的NtAllocateVirtualMemory地址是本地ntdll.dll的函数入口,但64位Windows默认开启ASLR(地址空间布局随机化),目标进程的ntdll.dll加载基址和本地进程不同,导致这个函数指针在目标进程中指向无效内存,调用时触发内存访问错误,线程退出码被系统映射为ERROR_ACCESS_DENIED(0x5)。

  2. Shellcode截取逻辑不可靠:用0xCCCC作为shellcode结束标记仅在VS调试模式下有效,Release模式或其他编译环境下不会有这个填充字节,会导致shellcode截取长度错误,进而引发执行异常。

修复步骤

1. 正确获取目标进程的NtAllocateVirtualMemory地址

不要直接传递本地的函数指针,而是在目标进程中通过GetModuleHandleA("ntdll.dll")和GetProcAddress动态获取正确的函数地址:

ULONG_PTR WINAPI inject_begin(InjectParam* inject_param) {
    inject_param->age += 1;
    PVOID memory_address = NULL;
    SIZE_T size = 10;

    // 在目标进程内动态获取函数地址
    HMODULE h_ntdll = GetModuleHandleA("ntdll.dll");
    NTALLOCATEVIRTUALMEMORY pNtAllocate = (NTALLOCATEVIRTUALMEMORY)GetProcAddress(h_ntdll, "NtAllocateVirtualMemory");
    
    if (pNtAllocate) {
        pNtAllocate((HANDLE)-1, &memory_address, 0, &size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    }
    return 0;
}

2. 替换可靠的Shellcode截取方式

放弃用0xCCCC判断结束,通过定义结束标记函数,用函数地址差计算shellcode长度:

// shellcode入口函数
ULONG_PTR WINAPI inject_begin(InjectParam* inject_param) {
    // ... 原有逻辑
}

// 结束标记函数,必须紧跟inject_begin定义
void inject_end() {}

// 在remote_map_load_dll中计算长度
DWORD shell_code_size = (DWORD)((BYTE*)inject_end - (BYTE*)inject_begin);

这种方式不受编译模式影响,能准确获取shellcode实际长度。

3. 确保权限配置正确(可选)

如果目标进程是UAC提升的进程,当前进程需要以管理员权限运行。同时可以完善权限调整函数的错误检查,确保SE_DEBUG_NAME权限成功启用:

void adjust_token_privileges() {
    HANDLE h_token = NULL;
    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &h_token))
    {
        printf("OpenProcessToken failed: %d\n", GetLastError());
        return;
    }
    TOKEN_PRIVILEGES priv = { 0 };
    priv.PrivilegeCount = 1;
    priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

    if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &priv.Privileges[0].Luid))
    {
        DWORD dwErr = 0;
        if (!AdjustTokenPrivileges(h_token, FALSE, &priv, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) {
            dwErr = GetLastError();
            printf("AdjustTokenPrivileges failed: %d\n", dwErr);
        } else {
            dwErr = GetLastError();
            if (dwErr == ERROR_NOT_ALL_ASSIGNED) {
                printf("SE_DEBUG_NAME privilege not assigned\n");
            }
        }
    } else {
        printf("LookupPrivilegeValue failed: %d\n", GetLastError());
    }
    CloseHandle(h_token);
}

修复后核心代码片段

//shell code 
ULONG_PTR WINAPI inject_begin(InjectParam* inject_param) {
    inject_param->age += 1;//age=2
    PVOID memory_address = NULL;
    SIZE_T size = 10;
    
    // 目标进程内动态获取函数地址
    HMODULE h_ntdll = GetModuleHandleA("ntdll.dll");
    NTALLOCATEVIRTUALMEMORY pNtAllocate = (NTALLOCATEVIRTUALMEMORY)GetProcAddress(h_ntdll, "NtAllocateVirtualMemory");
    if (pNtAllocate) {
        pNtAllocate((HANDLE)-1, &memory_address, 0, &size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    }
    return 0;
}

// 结束标记函数,用于计算shellcode长度
void inject_end() {}

void remote_map_load_dll(HANDLE target_process)
{
    InjectParam inject_param;
    RtlZeroMemory(&inject_param, sizeof(inject_param));
    inject_param.age = 1;
    
    // 准确计算shellcode长度
    DWORD shell_code_size = (DWORD)((BYTE*)inject_end - (BYTE*)inject_begin);
    printf("shellcode length:%d\n", shell_code_size);
    PVOID shell_code_buffer = malloc(shell_code_size);
    RtlCopyMemory(shell_code_buffer, inject_begin, shell_code_size);
    
    // 不再需要传递本地的NtAllocateVirtualMemory指针
    PBYTE shellcode_address = (PBYTE)VirtualAllocEx(target_process, 0, shell_code_size + sizeof(inject_param), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    // ... 后续写入、创建线程逻辑保持不变
}

内容的提问来源于stack exchange,提问作者Yulin.Li

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 20:04:55