代码段(.text)默认只读且不可写,强行修改会触发段错误;现代系统启用w^x保护,需用mmap/virtualalloc分配rwx内存并同步指令缓存才能安全执行自修改代码。

不能直接在标准 C++ 运行时动态修改代码段(.text)的指令,除非你主动绕过内存保护机制并承担严重风险。代码段默认是只读且不可执行写入的,强行写入会触发 Segmentation fault 或 EXC_BAD_ACCESS。
为什么对 .text 段写入会崩溃
现代操作系统(Linux/macOS/Windows)默认启用 W^X(Write XOR Execute)保护:一个内存页要么可写,要么可执行,不能同时具备两者。而 .text 段在加载时被映射为 PROT_READ | PROT_EXEC,没有 PROT_WRITE 权限。
- 尝试用
memcpy或指针解引用向函数地址写入字节 → 立即触发 SIGSEGV -
mprotect()或VirtualProtect()可临时改权限,但需对齐到页边界(通常 4KB),且必须确保目标地址确实在可修改范围内(不是共享库或 PIE 基址随机化后的不可控区域) - 即使成功改写,CPU 指令缓存(ICache)可能未同步,导致新指令不生效 —— 需要显式执行
__builtin___clear_cache()(GCC/Clang)或FlushInstructionCache()(Windows)
如何安全定位并修改一段可写的可执行内存
不要碰原生 .text,而是自己申请一块「RWX 内存」,把机器码拷进去再跳转执行。这是 SMC 的实际可行路径:
- 用
mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)(Linux/macOS)或VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)(Windows)分配一页 RWX 内存 - 把加密后的指令字节(如 x86-64 的
\x48\x89\xc3)用memcpy写入该内存 - 调用前必须清指令缓存:
__builtin___clear_cache(start_addr, end_addr)(Clang/GCC ≥ 4.8) - 将该地址强制转为函数指针并调用,例如:
auto fn = reinterpret_cast<void>(page_addr); fn();</void>
malloc / new 分配的内存为什么不能直接当代码用
堆内存(malloc、new 返回的地址)默认只有 PROT_READ | PROT_WRITE,缺少 PROT_EXEC。直接跳转过去会触发 SIGILL(非法指令)。
- 即使你用
mprotect()给堆内存加执行权限,glibc 的 malloc 实现(如 ptmalloc)会将相邻 chunk 的元数据(size 字段、fd/bk 指针)混在用户数据旁 —— 写坏它们会导致后续free崩溃 - ASLR 和堆随机化让地址不可预测,不利于硬编码跳转或 patch
- 堆页通常未对齐到 CPU 缓存行或页边界,
mprotect失败或影响其他合法内存
加密/解密指令字节本身要注意什么
SMC 不是简单异或;它必须保证解密后指令语义正确、长度固定、无跨指令边界修改:
- 避免修改指令中间字节:x86-64 是变长指令集,改错一个字节可能导致后续所有指令解析偏移
- 推荐按完整指令对齐加密:先用反汇编器(如
capstone)解析出指令边界,再对每条指令整体加密/解密 - 不能加密函数内跳转目标地址(如
call rel32中的偏移量),否则解密后跳转会错乱 —— 这些需运行时重定位或改为间接跳转 - 函数返回前必须重新加密,否则内存 dump 工具(如
gcore)可直接抓取明文指令
真正能落地的 SMC,本质是「手动管理 RWX 内存 + 指令级精准加解密 + ICache 同步」,和标准 C++ 动态内存管理(new/malloc)无关。任何试图绕开页权限、忽略缓存、或在堆上写代码的做法,都会在某个优化等级、系统版本或负载条件下突然失效。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











