如何在执行前解密附加PE程序的汇编指令?含间接寻址疑问
逐指令解密加密.text段的实现方案(含间接寻址处理)
可以实现逐指令解密加密的.text段,你当前用VEH+PAGE_NOACCESS触发异常的思路是可行的,核心是在异常处理中完成「解密指令→单步执行→重新加密」的闭环,间接寻址的情况不需要提前解析目标,靠异常机制就能自动处理。
核心实现逻辑
- 触发异常:将附加程序的.text段设置为
PAGE_NOACCESS,CPU试图执行该区域指令时会抛出EXCEPTION_ACCESS_VIOLATION异常。 - 解密指令:在VEH中识别异常地址属于.text段后,用反汇编工具(比如Capstone)获取当前指令的长度,解密对应字节。
- 单步执行:开启CPU的单步标志(
EFlags |= 0x100),让CPU执行完当前解密后的指令后抛出EXCEPTION_SINGLE_STEP异常。 - 重新加密:在单步异常处理中,将刚执行完的指令重新加密,并恢复.text段的
PAGE_NOACCESS权限,等待下一次异常触发。
间接寻址的处理方法
像48 FF 25 5A 08 00 00 JMP QWORD PTR [RIP + 0x85A]这类间接寻址指令,不需要提前解析目标地址:
- 当VEH处理这条跳转指令的ACCESS_VIOLATION异常时,会解密该跳转指令并允许单步执行。
- CPU执行完跳转指令后,会计算出目标地址并试图访问该地址。
- 如果目标地址在加密的.text段内,会再次触发ACCESS_VIOLATION异常,VEH重复「解密→执行→加密」的流程,自动处理目标地址的指令。
全程不需要手动解析间接寻址的目标,异常机制会自动捕获所有对加密.text段的访问请求。
修改后的代码示例
依赖准备
需要引入Capstone反汇编库(用于准确获取指令长度),你可以编译官方源码或使用预编译版本。
VEH异常处理函数(含解密/加密逻辑)
#include <capstone/capstone.h> #include <memory.h> #include <windows.h> // 全局变量:保存附加程序.text段的关键信息 PBYTE g_encryptedTextBase; SIZE_T g_textSize; BYTE* g_originalEncryptedData; csh g_csHandle; PVOID g_lastDecryptedAddr; SIZE_T g_lastDecryptedLen; // 示例解密函数(替换为你的实际解密逻辑) void DecryptInstruction(BYTE* addr, SIZE_T len) { const BYTE key = 0xAA; // 替换为你的解密密钥 for (SIZE_T i = 0; i < len; ++i) { addr[i] ^= key; } } // 重新加密指令(恢复为原始加密状态) void ReEncryptInstruction(BYTE* addr, SIZE_T len) { memcpy(addr, g_originalEncryptedData + (addr - g_encryptedTextBase), len); } LONG NTAPI VehHandler(PEXCEPTION_POINTERS pExceptInfo) { DWORD exceptionCode = pExceptInfo->ExceptionRecord->ExceptionCode; PVOID exceptionAddr = pExceptInfo->ExceptionRecord->ExceptionAddress; // 仅处理附加程序.text段内的异常 if (exceptionAddr >= g_encryptedTextBase && exceptionAddr < (PBYTE)g_encryptedTextBase + g_textSize) { if (exceptionCode == EXCEPTION_ACCESS_VIOLATION) { // 反汇编获取当前指令长度 cs_insn* insn = NULL; size_t disasmCount = cs_disasm(g_csHandle, (const uint8_t*)exceptionAddr, 15, (uint64_t)exceptionAddr, 1, &insn); if (disasmCount == 0) { return EXCEPTION_CONTINUE_SEARCH; // 反汇编失败,交给系统处理 } g_lastDecryptedAddr = exceptionAddr; g_lastDecryptedLen = insn[0].size; // 解密当前指令 DecryptInstruction((BYTE*)exceptionAddr, g_lastDecryptedLen); // 设置内存权限为可执行可读 DWORD oldProtect; VirtualProtect(exceptionAddr, g_lastDecryptedLen, PAGE_EXECUTE_READ, &oldProtect); // 开启单步执行 pExceptInfo->ContextRecord->EFlags |= 0x100; cs_free(insn, disasmCount); return EXCEPTION_CONTINUE_EXECUTION; } else if (exceptionCode == EXCEPTION_SINGLE_STEP) { // 重新加密刚执行完的指令 ReEncryptInstruction((BYTE*)g_lastDecryptedAddr, g_lastDecryptedLen); // 恢复内存权限为PAGE_NOACCESS DWORD oldProtect; VirtualProtect(g_lastDecryptedAddr, g_lastDecryptedLen, PAGE_NOACCESS, &oldProtect); // 关闭单步执行 pExceptInfo->ContextRecord->EFlags &= ~0x100; return EXCEPTION_CONTINUE_EXECUTION; } } // 其他异常交给系统处理 return EXCEPTION_CONTINUE_SEARCH; }
主程序代码
int main(int argc, char** argv) { // 初始化Capstone(64位模式,32位程序改为CS_MODE_32) if (cs_open(CS_ARCH_X86, CS_MODE_64, &g_csHandle) != CS_ERR_OK) { return 1; } cs_option(g_csHandle, CS_OPT_DETAIL, CS_OPT_ON); // 注册VEH AddVectoredExceptionHandler(1, &VehHandler); // 假设你已完成附加程序的加载、导入表修复、重定位 // 替换为你实际的.text段地址和大小 g_encryptedTextBase = (PBYTE)retAddr; g_textSize = textsize; // 保存加密后的原始数据,用于后续重新加密 g_originalEncryptedData = (BYTE*)malloc(g_textSize); if (!g_originalEncryptedData) { cs_close(&g_csHandle); return 1; } memcpy(g_originalEncryptedData, g_encryptedTextBase, g_textSize); // 设置.text段为PAGE_NOACCESS DWORD oldProtect; VirtualProtect(g_encryptedTextBase, g_textSize, PAGE_NOACCESS, &oldProtect); // 调用附加程序的入口点 ((void(*)())g_encryptedTextBase)(); // 资源清理 cs_close(&g_csHandle); free(g_originalEncryptedData); return 0; }
注意事项
- 指令长度准确性:必须用可靠的反汇编库(如Capstone)获取指令长度,手动解析容易出错,尤其是复杂的x86/x64指令。
- 内存权限管理:每次解密后仅设置必要的权限(如
PAGE_EXECUTE_READ),执行完立即恢复PAGE_NOACCESS,避免内存被非法访问。 - 跨页指令处理:极少数情况下指令会跨内存页,此时需要同时解密两个页的对应区域,确保指令完整。
- 架构适配:如果附加程序是32位,需要将Capstone的模式改为
CS_MODE_32,并调整上下文记录中的寄存器(如Eip而非Rip)。
内容的提问来源于stack exchange,提问作者getunstuckaq
相关产品推荐
相关产品推荐

