如何在C++中动态获取内存中指令的操作数(x64进程)
解析x64指令长度与操作数的实操方法
要解决读取外部x64进程指令、确定操作数数量(比如MOV的双操作数)及指令长度的问题,核心是拆解x64的变长指令结构——x86/x64指令长度从1到15字节不等,必须逐段解析才能确定总长度和操作数。
一、先搞懂x64指令的核心结构
x64指令由以下按顺序排列的可选/必选部分组成:
- 前缀字节(最多4个):含REX前缀(
0x40-0x4F,扩展寄存器/操作数大小)、段前缀、操作数/地址大小前缀 - 主操作码(1-3字节):MOV的基础操作码比如
0x8B(内存/寄存器→寄存器)、0x89(寄存器→内存/寄存器)、0xA1(内存→EAX/RAX)等 - ModR/M字节(可选):多数双操作数指令需要它来指定操作数的寻址模式(寄存器/内存)
- SIB字节(可选):当ModR/M的R/M字段指定复杂内存寻址时出现,用于细化寻址方式
- 位移量(0/1/2/4/8字节):内存寻址时的偏移值,长度由ModR/M的Mod字段决定
- 立即数(0/1/2/4/8字节):指令中直接给出的数值,长度由操作码和REX前缀决定
二、MOV指令的操作数判定
MOV在x64中几乎都是双操作数(源→目的),少数看似单操作数的变种实际隐含了操作数:
- 比如
0xA1 m32:表面是单操作数,但实际是MOV EAX, m32,隐含EAX作为目的操作数,本质还是双操作数 - 通过操作码+ModR/M确认:ModR/M的Reg字段对应一个操作数,R/M字段对应另一个(寄存器或内存地址),以此确定双操作数结构
三、确定指令长度的分步流程
不能一次性读取固定字节数,必须按需逐字节解析:
- 读取前缀字节
逐个读取字节,判断是否为前缀(REX、段前缀、操作数/地址大小前缀),最多读4个,直到遇到非前缀的操作码字节。 - 读取完整操作码
操作码可能是1-3字节:- 单字节:如
0x8B、0x89 - 双字节:以
0x0F开头(如MOVZX的0x0F 0xB7) - 三字节:以
0x0F 0x38或0x0F 0x3A开头
读取到完整操作码后,判断是否需要ModR/M字节。
- 单字节:如
- 解析ModR/M与SIB字节
若操作码要求ModR/M(多数MOV变种都需要),读取1字节的ModR/M:- 若ModR/M的R/M字段为
0x04(32位)或0x05(64位),则需额外读取1字节的SIB。
- 若ModR/M的R/M字段为
- 读取位移量
根据ModR/M的Mod字段确定位移长度:- Mod=00(无位移,仅当R/M指向内存):无需读取
- Mod=01:1字节位移
- Mod=10:8字节位移(x64地址模式下)
- Mod=11:寄存器模式,无位移
读取对应长度的位移字节。
- 读取立即数
根据操作码和REX前缀确定立即数长度:- 比如带REX.W前缀的
0x48 0xB8(MOV RAX, imm64),需要读取8字节立即数。
- 比如带REX.W前缀的
四、实操建议
- 优先针对MOV的常见变种做适配:
MOV r64, r64:0x48 0x89+ ModR/M(1字节),总长度3字节MOV r64, m64:0x48 0x8B+ ModR/M + 可选SIB + 位移,长度3-10字节MOV m64, r64:0x48 0x89+ ModR/M + 可选SIB + 位移,长度3-10字节MOV r64, imm64:0x48 0xB8+ 8字节立即数,总长度10字节
- 参考Intel指令集手册建立操作码与结构的映射,避免硬编码遗漏变种
- 读取外部进程内存时,每次读取后要校验地址有效性,防止访问违规
内容的提问来源于stack exchange,提问作者user24684540
相关产品推荐
相关产品推荐

