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

x64环境下DLL注入中__declspec(naked)的替代方案咨询

x64平台DLL注入中替代__declspec(naked)避免栈副作用的方案

针对x64平台下无法使用__declspec(naked)和内联汇编的限制,在DLL注入的代码洞(code cave)场景中,有以下几种可靠的替代方案:

1. 纯MASM汇编实现裸函数

这是最贴合x86下__declspec(naked)效果的方案,直接用MASM编写完全由你控制栈和寄存器的函数,编译器不会插入任何额外栈帧代码。

创建单独的.asm文件,写入以下代码:

.code
; 声明裸函数,无编译器自动生成的栈帧
codecave proc
    ; 保存所有非易失寄存器(x64调用约定要求)
    push rax
    push rbx
    push rcx
    push rdx
    push rsi
    push rdi
    push rbp
    push r8
    push r9
    push r10
    push r11
    push r12
    push r13
    push r14
    push r15

    ; 你的业务逻辑
    mov qword ptr [rbx + 4], 0Dh
    mov qword ptr [0102AE98h], rbx

    ; 恢复寄存器
    pop r15
    pop r14
    pop r13
    pop r12
    pop r11
    pop r10
    pop r9
    pop r8
    pop rbp
    pop rdi
    pop rsi
    pop rdx
    pop rcx
    pop rbx
    pop rax

    ; 跳回原执行地址
    jmp ret_address
codecave endp
end

将该文件添加到C++项目中,配置项目链接时包含汇编输出,最终生成的函数完全符合裸函数的需求,没有栈帧副作用。

2. C++配合编译器属性+手动寄存器管理

如果不想单独编写汇编文件,可以用C++代码配合编译器选项,尽可能消除栈帧代码,同时手动管理寄存器的保存与恢复:

#include <cstdint>

// 声明原返回地址(需根据注入场景动态赋值)
extern "C" void* ret_address;

__declspec(noinline) void codecave() {
    ; 手动保存x64调用约定中的非易失寄存器
    uint64_t rbx_val = rbx;
    uint64_t rsi_val = rsi;
    uint64_t rdi_val = rdi;
    uint64_t rbp_val = rbp;
    uint64_t r12_val = r12;
    uint64_t r13_val = r13;
    uint64_t r14_val = r14;
    uint64_t r15_val = r15;

    ; 业务逻辑,用volatile防止编译器优化内存操作
    *(volatile uint64_t*)(rbx + 4) = 0xD;
    *(volatile uint64_t*)(0x102AE98) = rbx;

    ; 恢复寄存器
    rbx = rbx_val;
    rsi = rsi_val;
    rdi = rdi_val;
    rbp = rbp_val;
    r12 = r12_val;
    r13 = r13_val;
    r14 = r14_val;
    r15 = r15_val;

    ; 跳回原地址,用函数指针实现(避免内联汇编)
    reinterpret_cast<void(*)()>(ret_address)();
}

注意事项:

  • 开启O2优化级别,让编译器尽可能消除冗余栈操作
  • 使用__declspec(noinline)防止函数被内联,避免额外代码插入
  • 用volatile修饰内存指针,确保写入操作不被编译器优化

3. 手动构造机器码执行

如果需要完全脱离汇编工具,可以直接在C++中构造对应逻辑的x64机器码,将内存设置为可执行后跳转执行:

#include <cstdint>
#include <windows.h>

// 声明原返回地址
void* ret_address = nullptr;

void run_codecave() {
    ; 对应x64汇编逻辑的机器码(最后6字节为jmp指令占位,需动态替换地址)
    unsigned char codecave_code[] = {
        0x50, 0x53, 0x51, 0x52, 0x56, 0x57, 0x55,
        0x41,0x50, 0x41,0x51, 0x41,0x52, 0x41,0x53,
        0x41,0x54, 0x41,0x55, 0x41,0x56, 0x41,0x57,
        0x48,0xC7,0x43,0x04,0x0D,0x00,0x00,0x00,
        0x48,0x89,0x1D,0x98,0xAE,0x02,0x10,
        0x41,0x5F, 0x41,0x5E, 0x41,0x5D, 0x41,0x5C,
        0x41,0x5B, 0x41,0x5A, 0x41,0x59, 0x41,0x58,
        0x5D, 0x5F, 0x5E, 0x5A, 0x59, 0x5B, 0x58,
        0xFF,0x25,0x00,0x00,0x00,0x00 // jmp [rip+0],后续替换地址
    };

    ; 替换jmp指令的目标地址
    *reinterpret_cast<void**>(codecave_code + sizeof(codecave_code) - 4) = ret_address;

    ; 将内存区域设置为可执行
    DWORD old_protect;
    VirtualProtect(codecave_code, sizeof(codecave_code), PAGE_EXECUTE_READWRITE, &old_protect);

    ; 执行机器码
    reinterpret_cast<void(*)()>(codecave_code)();

    ; 恢复内存保护属性(可选)
    VirtualProtect(codecave_code, sizeof(codecave_code), old_protect, &old_protect);
}

这个方案适合小片段逻辑,缺点是需要手动转换汇编到机器码,维护成本较高。


内容的提问来源于stack exchange,提问作者LisnX

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 08:08:11