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

Windows平台下C/C++自修改代码实现异常问题求助

Windows下自修改程序的问题排查与解决

我有一些疑问:是否可以在Windows系统中编写自修改程序?我编写了一段使用_asm{}的C++代码,但无法正常运行,不清楚原因,希望得到帮助。

原代码

#include <iostream>

using namespace std;

void print() //FUNCTION TO MODIFY
{
    cout << "Hello, World!\n";
}

void Crypt(int adr)
{
    _asm {

        mov eax, adr

        xor_loop:
        xor byte ptr [eax], 0x24 
            inc eax
            cmp eax, 0xC3 //compare ret opcode
            jne xor_loop
    }

}

int main()
{
    print();

    void *ptr = print;  //address example : 003812E4

    cout << "Address : " << ptr << "\n";

    int mem;

    _asm {
        lea eax, print
        mov mem, eax
        call eax
    }

    cout << "EAX : " << mem << "\n";

    Crypt(mem);
    
    return 0;
}

伪代码

print function:
   mov eax, 0x80
   add eax, 0x02
   call eax
   ret

modify:
   lea eax, print
   xor byte ptr [eax], 0x24 //xor for modify opcode
   inc eax       //increase eax 1 byte
   cmp eax, 0xC3 //compare ret opcode
   jne modify

---------------------------------------------
modify print function example:
   mul eax
   mov eax, edx
   jnz ebx
   ret

问题分析与解决

Windows下完全可以编写自修改程序,你的代码主要存在以下几个关键问题:

1. 内存权限限制

Windows默认将代码段(.text)设置为只读+执行权限,直接写入会触发访问违规(Access Violation)。必须先调用VirtualProtect函数修改目标内存页的权限为可读可写可执行(PAGE_EXECUTE_READWRITE),修改完成后可按需恢复原权限。

2. 循环终止条件错误

你汇编循环里的cmp eax, 0xC3是把寄存器EAX的地址值和0xC3比较,而不是检查当前地址指向的字节是否是ret指令的0xC3 opcode。正确写法应该读取[eax]的内容进行判断:

xor_loop:
xor byte ptr [eax], 0x24 
inc eax
cmp byte ptr [eax], 0xC3 ; 读取当前eax指向的字节,判断是否为ret指令
jne xor_loop

另外,这种基于ret指令的循环可能会越界,建议提前计算函数的大致长度,或者使用明确的结束标记,避免意外修改其他内存区域。

3. 地址存储类型不兼容

用int adr存储函数地址在32位系统可行,但64位系统下指针是8字节,int仅4字节会导致地址截断,引发错误。应该用uintptr_t(来自<cstdint>头文件)或void*来存储地址,确保兼容32/64位环境。

4. 编译器优化干扰

编译器可能对print函数进行内联优化,导致你获取的地址并非实际函数代码的起始地址,或者代码被重排。可以给函数添加__declspec(noinline)属性禁止内联,确保函数有独立的代码段。


修改后的示例代码

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

using namespace std;

__declspec(noinline) void print() // 禁止内联,保证函数地址稳定
{
    cout << "Hello, World!\n";
}

void Crypt(uintptr_t adr)
{
    DWORD oldProtect;
    // 修改目标内存页权限为可读可写可执行
    VirtualProtect(reinterpret_cast<void*>(adr), 0x100, PAGE_EXECUTE_READWRITE, &oldProtect);

    __asm {
        mov eax, adr

        xor_loop:
        xor byte ptr [eax], 0x24 
        inc eax
        cmp byte ptr [eax], 0xC3 ; 检查当前字节是否为ret指令的0xC3
        jne xor_loop
    }

    // 恢复原内存权限(可选,建议恢复以保证安全性)
    VirtualProtect(reinterpret_cast<void*>(adr), 0x100, oldProtect, &oldProtect);
}

int main()
{
    print();

    uintptr_t mem = reinterpret_cast<uintptr_t>(print);
    cout << "Address : " << reinterpret_cast<void*>(mem) << "\n";

    // 调用原函数
    __asm {
        lea eax, print
        call eax
    }

    cout << "EAX : " << reinterpret_cast<void*>(mem) << "\n";

    Crypt(mem);
    
    // 注:修改后的print函数指令已被篡改,直接调用可能触发非法指令异常,需确保修改后的指令序列合法
    // print();
    
    return 0;
}

额外提示

自修改后的指令必须是合法的x86/x64指令,否则执行时会触发非法指令异常。比如你伪代码中修改后的指令序列,要确保每一条指令都是完整且合法的,避免出现截断的指令。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 06:55:06