502 第 49 章 IA-32 指令
第49章 IA-32指令
本章将学习有关IA-32指令(或称x86指令)的内容。各位刚开始会觉得指令比较复杂,但若
理解了指令的解析方法与原理,就能够轻松地将指令转换为反汇编代码。掌握了这些内容后,各
位的逆向分析技术水平将提高到一个新的层次。
49.1 IA-32 指令
简言之,指令是指CPU能够识读的机器语言(Machine Language)。IA-32指令指IA-32(Intel
Architecture 32位)系列CPU使用的指令。
图49-1 IA-32指令
如图49-1所示,粗线框中的每一行都是1条指令(E8 CC270000、E9 A4FEFFFF、8BFF、55
等都是IA-32指令)。编程人员使用程序语言(C/C++、JAVA、Python等)编写程序,而CPU则使
用机器语言,编程人员编写的程序源代码需要编译/链接后转换为CPU可以识读的机器语言。
提示 关于 IA-32 的详细说明请参考以下链接。
维基百科:http://zh.wikipedia.org/wiki/IA-32(中文版)
http://en.wikipedia.org/wiki/IA-32(英文版)
http://ko.wikipedia.org/wiki/IA-32(韩文版)
IA-32 用户手册:http://www.intel.com/products/processor/manuals/
49.2 常用术语
下面整理一下逆向分析常用术语。讲解IA-32指令的过程中将用到下列术语,准确理解并运
用这些术语将有助于与他人进行顺畅的交流与沟通。
表49-1 常用术语
术 语 说 明 Machine Language 机器语言,CPU可以解析的二进制代码(Binary,0与1)
Instruction 一条机器指令(由OpCode与operand等组成)
49.2 常用术语 503
1
2
3
4
5
19
6
7
8
9
10
11
12
13
14
15
16
17
18
(续)
术 语 说 明 OpCode 操作码(Operation Code),指令内的实际指令
Assembly 汇编编程语言
Assemble 将汇编代码转换为机器语言(OpCode)(类似于C/C++的Compile)
Assembler 执行Assemble作业的程序(如:MASM、TASM、FASM等)
Disassemble 将机器语言转换为汇编语言(也用Unassemble一词)
Disassembler 执行Disassemble作业的程序(一般内嵌在调试器中-OllyDbg、IDA Pro等)
Disassembly 经过Disassemble生成的汇编语言(变量名、函数名等被替换为地址,可读性差)
*Compile 将C/C++等编写的源代码转换为机器代码(生成Obj文件)
*Link 将Obj文件链接为可执行文件(生成Exe/Dll文件 )
使用C/C++语言(或汇编语言)创建出PE文件后,源代码就被转换成了机器码。一名合格的
逆向分析人员必须能够解析这些机器码,并理解其工作原理。但机器码是用二进制(0与1)表示
的,我们很难读懂它。因此一般要把机器码转换为16进制代码,转换后可读性提高,但我们识读
16进制代码时仍然会感到吃力。所以,最后借助调试器内嵌的反汇编器将机器码转换为反汇编代
码,识读这些反汇编代码就容易多了。
49.2.1 反汇编器
图49-2显示的是我们熟悉的OllyDbg调试器的用户界面。OllyDbg调试器内嵌有IA-32反汇编
器(Disassembler)。
图49-2 OllyDbg用户界面
图49-2中,1行代码就是1条指令。(A)区域中是16进制表示的IA-32指令,(B)区域中是与之对
应的反汇编代码,(C)区域是指令在内存(或文件)中的实际形式。
504 第 49 章 IA-32 指令
提示 以地址 401000 处的指令为例,(A)区中的“68 84B34000”是 IA-32 指令,(B)区中
的“PUSH 0040B384”为反汇编代码。
反汇编代码大致由助记符(Mnemonic)与操作数(Operand)组成,助记符表明
指令功能,操作数指示操作对象。“PUSH 0040B384”指令中,PUSH为助记符,0040B384
为操作数。
内嵌在调试器中的反汇编器解析(C)区中的十六进制机器码,将它们切分为(A)区中的一条条
指令,然后将每条指令转换为(B)区中相应的反汇编代码。从易读性来看,(C)中代码低于(A)区代
码,(A)区代码又低于(B)区代码。逆向分析人员一般会阅读(B)区中的反汇编代码,并进行相应分
析。学习IA-32指令后就能分析(A)区中的代码了。
49.2.2 反编译器
近来,大量PE文件都是使用C/C++/VB/Delphi语言编写的。反编译器(Decompiler)与反汇
编器在概念上类似,但是反汇编器用来将机器代码转换为反汇编代码,而反编译器则用来将机器
代码反编译为类似于源代码的代码(C/C++/VB+/Delphi)(反编译时需选用相应语言的反编译
器)。当然,反编译出的代码与源代码还是有一定差距的,但随着技术的不断发展,这种差距会
越来越小。
49.2.3 反编译简介
本节我们将在IDA Pro分析工具中借助Hex-Rays Decompiler插件将C语言程序(机器代码)反
编译为类C语言的代码,并比较它与程序源码的不同。先看程序的C语言源代码,如图49-3所示。
图49-3 C语言源代码
49.2 常用术语 505
1
2
3
4
5
19
6
7
8
9
10
11
12
13
14
15
16
17
18
get_folder_count(LPCSTR szPath)是个非常简单的函数,用来计算参数给定路径(szPath)中
文件夹的个数。上述源代码经过编译后生成PE文件,在IDA Pro分析工具中使用Hex-Rays
Decompiler插件将生成的PE文件反编译(Decompile)为类C语言代码,如图49-4所示。
图49-4 反编译后的C语言代码
各位一定大吃一惊。从图49-4中可以看到,反编译得到的代码与程序的源代码非常相似,仅
函数名称(sub_401000)、变量名称(v1、v2、v3、v8)不同而已。程序的代码较长且较复杂时,
反编译得到的代码可读性可能下降,但是借助反编译代码我们能够快速把握程序的代码结构,从
这个意义来说,反编译仍然是个非常棒的功能。
拥有这种强大功能的IDA Pro分析工具与Hex-Rays Decompiler插件都是付费的商业软件,且
价格昂贵,一般人难以承受,大部分都由公司购买使用。使用OllyDbg调试器打开上面的程序文
件,可以看到反汇编后的代码,如图49-5所示。
从图49-5中可以看到,反汇编代码看上去比较复杂,不如反编译后的代码更容易阅读,由此
可见反编译器多么有用。
提示 请注意,反编译器也不是万能的。若使用保护器等工具故意打乱程序代码,或在
程序运行中使用一些操作技术,就不能反编译,或者使反编译后的代码更加复杂。所
以,学习高级逆向分析技术时一定要掌握反汇编代码和指令。
506 第 49 章 IA-32 指令
图49-5 反汇编代码
49.3 IA-32 指令格式 提示
现在我们学习中级逆向分析技术。如果你仍是逆向分析技术的初学者,不太理解
本部分内容,没关系,阅读后直接跳过即可。以后自己的技术水平提高了,需要了解
指令相关知识时再学习即可。以下内容是我在“Intel® 64 and IA-32 Architectures
Software Developer’ s Manuals”基础上整理而来的。更多详细说明请参考相关用户手册。
正文中使用的有关 IA-32 指令的图片均出自 Intel 的用户手册。
下面学习有关IA-32指令格式的知识。
如图49-6所示,IA-32指令由6部分组成,其中操作码项是必需的,其他项目都是可选的。接
下来对指令的各组成部分予以说明。
49.3 IA-32 指令格式 507
1
2
3
4
5
19
6
7
8
9
10
11
12
13
14
15
16
17
18
图49-6 IA-32指令格式
49.3.1 指令前缀
指令前缀(Instruction Prefixes)是一个可选项目,后面出现特定操作码时将补充说明其含义。
下面举个简单的例子。
66:81FE 4746 CMP SI,4647 66:C703 33D2 MOV WORD PTR DS:[EBX],0D233 F3:A4 REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI] 以上指令最前面的黑体部分为前缀
前缀项大小为1个字节(后面讲解“指令解析方法”(借助操作码映射解析)时会详细说明
Prefix 66的含义)。
49.3.2 操作码
Opcode(Operation Code,操作码)是必不可少的部分,用来表示实际的指令。
66:81FE 4746 CMP SI,4647 66:C703 33D2 MOV WORD PTR DS:[EBX],0D233 F3:A4 REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI] 8BF1 MOV ESI,ECX E8 04400000 CALL 00405040 56 PUSH ESI E9 4A020000 JMP 00401366 33C0 XOR EAX,EAX 0F95C1 SETNE CL C3 RETN CC INT3 以上指令黑体部分为操作码
操作码长度为1~3个字节,我们在常见的应用程序调试中遇到的操作码大部分都是1个字节,
有时也会遇到2个字节的操作码。3个字节长度的操作码主要用在MMX(MultiMedia eXtension,
多媒体扩展)相关指令中,一般很少有机会接触到。操作码通常都带有操作数(Operand),操作
数种类有寄存器、内存地址、常量(Contanst)。指令中出现的ModR/M与SIB选项辅助操作码确
定操作数。
操作码种类很多,解析时一般需要查看Intel用户手册的操作码映射。后面讲解“指令解析方
法”时会做一些解析操作码的练习。
49.3.3 ModR/M
ModR/M是个可选项,主要用来辅助说明操作码的操作数(操作数的个数、种类[寄存器、地
址、常量])。
508 第 49 章 IA-32 指令
66:81FE 4746 CMP SI,4647 66:C703 33D2 MOV WORD PTR DS:[EBX],0D233 8BF1 MOV ESI,ECX 33C0 XOR EAX,EAX 0F95C1 SETNE CL 以上指令黑粗体部分为ModR/M
ModR/M项拥有1个字节(8位)长度,分为3部分,各部分含义如图49-7所示。
图49-7 ModR/M
简单的ModR/M计算练习 Ex1. ModR/M = FE FE的2进制表示 = 11111110 ModR/M拆分 = 11|111|110(Mod:11, Reg:111, R/M:110) Ex2. ModR/M = 03 03的2进制表示 = 00000011 ModR/M拆分= 00|000|011(Mod:00, Reg:000, R/M:011)
49.3.4 SIB SIB(Scale-Index-Base)也是一个可选项,用来辅助说明ModR/M。操作码的操作数为内存
地址时,需要与ModR/M项一起使用。
898424 50020000 MOV [ESP+250], EAX C68424 5C010000 00 MOV [ESP+15C], 00 8D8428 B1354000 LEA EAX, [EAX+EBP+4035B1] 8B0C01 MOV ECX, [EAX+ECX] 以上指令黑体部分为SIB
SIB项也拥有1个字节(8位)长度,分为3部分,各部分含义如图49-8所示。
图49-8 SIB
*简单的SIB计算练习 Ex1. SIB = 24 24的2进制表示 = 00100100 SIB拆分 = 00|100|100(Scale:00, Index:100, Base:100) Ex2. SIB = 01 01的2进制表示 = 00000001 SIB拆分 = 00|000|001(Scale:00, Index:000, Base:001)
49.3.5 位移
位移(Displacement)也是可选项,操作码的操作数为内存地址时,用来表示位移操作。
81B8 00004000 50450000 CMP DWORD PTR DS:[EAX+400000],4550 C705 68CF4000 01000100 MOV DWORD PTR DS:[40CF68],10001 833D 60CF4000 00 CMP DWORD PTR DS:[40CF60],0 814E 0C 00800000 OR DWORD PTR DS:[ESI+C],8000 以上指令黑粗体部分为位移
49.4 指令解析手册 509
1
2
3
4
5
19
6
7
8
9
10
11
12
13
14
15
16
17
18
位移的长度为1、2、4字节。
49.3.6 立即数
立即数(Immediate)也是一个可选项,操作码的操作数为常量时,该常量就被称为立即数。
81B8 00004000 50450000 CMP DWORD PTR DS:[EAX+400000],4550 C705 68CF4000 01000100 MOV DWORD PTR DS:[40CF68],10001 833D 60CF4000 00 CMP DWORD PTR DS:[40CF60],0 814E 0C 00800000 OR DWORD PTR DS:[ESI+C],8000 以上指令黑粗体部分为立即数
立即数的长度为1、2、4字节。
49.4 指令解析手册
首先制作“指令解析手册”,然后借助该手册练习指令解析。
49.4.1 下载 IA-32 用户手册
Intel公司提供的用户手册对IA-32进行了详细说明。代码逆向分析中会经常参考该用户手册,
所以请从下面地址下载。
http://www.intel.com/products/processor/manuals/
进入页面下载这个文件。
Intel® 64 and IA-32 Architectures Software Developer’s Manuals Volume 2A.pdf
Intel® 64 and IA-32 Architectures Software Developer’s Manuals Volume 2B.pdf
49.4.2 打印指令解析手册
下载Intel用户手册后,其中有些表格是解析指令时需要参考的,请将下面列出的表格打印
出来。
Vol. 2A Chapter 2 Instruction Format Table 2-2. 32-Bit Addressing Forms with the ModR/M Byte Table 2-3. 32-Bit Addressing Forms with the SIB Byte Vol. 2B Appendix A Opcode map A.2 Key to Abbreviations A.2.1 Codes for Addressing Method A.2.2 Codes for Operand Type Table A-1. Superscripts Utilized in Opcode Tables A.3 One, Two, Three-byte Opcode Maps Table A-2. One-byte Opcode Map Table A-3. Two-byte Opcode Map A.4 Opcode Extensions for One-byte and Two-byte Opcodes Table A-6. Opcode Extentions for One- and Two Opcodes by Group Number
仅打印粗斜体部分即可
打印上表后,下面通过练习来学习使用操作码映射、ModR/M表、SIB表等表格解析指令。
510 第 49 章 IA-32 指令
提示 上面打印的资料用得多了、泛黄的时候,各位也就成了解析 IA-32 指令的高手。
我几年间一直使用一部自制的指令手册,有人问有关指令解析的问题时,我就会翻阅
它并给出解答。翻阅得多了,就有了感觉,哪些内容在哪页一清二楚,所以查起来非
常快。我尊敬的一位前辈也有这样的参考资料,当时看上去都用旧了。从某种意义上
说,这种使用痕迹就像一把测量逆向分析人员“年轮”的尺子。希望各位也打印一份
这样的资料,需要的时候随时翻阅查看(参考图 49-9)。
图49-9 OpCode用户手册
49.5 指令解析练习
我们从本节开始学习IA-32指令解析方法。
49.5.1 操作码映射
首先解析1条长度为1个字节的操作码指令。
41 INC ECX
查看前面打印出的操作码手册(或者Intel Manual Vol.2B)中的“Table A-2. One-byte Opcode Map”。
解析指令时,最先要查看表格的名称是否为“Table A-2. One-byte Opcode Map:(00H-F7H)*”。
然后将要查找的操作码41拆分为4与1,再在操作码映射中将它们分别作为表格的行与列进行查
找。从查找的结果看,操作码41对应的指令为INC,操作数为ECX(同一方格中的REX.B是64位
系统专用操作数,忽略即可)。所以指令41最终被解析为INC ECX指令,使用图49-6中的IA-32指
令格式表示如下:
[Prefixes][Opcode][ModR/M][SIB][Displacement][Immediate]
提示 图 49-10 中 INC 指令的上标为 i64,REX 指令的上标为 o64,它们的含义在操作码
用户手册的“Table A-1. Superscripts Utilized in Opcode Tables”中给出了讲解。根据表
格中的说明,i64 表示不在 64 位模式中使用,o64 表示只在 64 位模式中使用。所以操
作码 40~47 在 32 位模式中作为 INC 指令使用,在 64 位模式(o64)中用作 REX 前缀。
由于这里解析的是 IA-32 指令,所以应该解析为 INC 指令。操作数亦是如此,在 32 位
模式中要选择 ECX,在 64 位模式中要选择 REX.B。
49.5 指令解析练习 511
1
2
3
4
5
19
6
7
8
9
10
11
12
13
14
15
16
17
18
图49-10 Opcode 41 in Table A-2
49.5.2 操作数
下面继续通过练习来学习操作数的形态结构。
68 A0B44000 PUSH 0040B4A0
使用前面的方法在Table A-2操作码映射中查找指令的第一个字节68,如图所示。
从图49-11中可以看到,操作码68对应于PUSH Iz,为带有1个操作数的PUSH指令。
提示 Table A-2 One-byte Opcode Map 内容繁多,在 Intel 用户手册中占了不少分量,图
49-11 是其中一部分。
图49-11 Opcode 68 in Table A-2
512 第 49 章 IA-32 指令
Iz用来表示操作数的类型,把握其含义即可准确解析整条指令。大写字母I指寻址方法
(Addressing Method),小写字母z指操作数类型(Operand Type),A.2.1 Codes for Addressing Method
与A.2.2 Codes for Operand Type中分别对它们进行了说明。
常用寻址方法整理如表49-2所示。
表49-2 部分Vol.2B A.2.1. Codes for Addressing Method(出处:Intel官方用户手册)
E A ModR/M byte follows the opcode and specifies the operand. The operand is either a general-purpose register or a memory address.
G The reg field of the ModR/M byte selects a general register (for example, AX (000)).
I Immediate data: the operand value is encoded in subsequent bytes of the instruction.
J The instruction contains a relative offset to be added to the instruction pointer register (for example, JMP (0E9), LOOP).
M The ModR/M byte may refer only to memory (for example, BOUND, LES, LDS, LSS, LFS, LGS, CMPXCHG8B).
X Memory addressed by the DS:rSI register pair (for example, MOVS, CMPS, OUTS, or LODS).
Y Memory addressed by the ES:rDI register pair (for example, MOVS, CMPS, INS, STOS, or SCAS).
指示寻址方法的大写字母I代表Immediate(立即数)。若想知道立即数的大小,还要参考操作
数类型。
常用操作数类型整理如表49-3所示。
表49-3 部分Vol.2B A.2.2. Codes for Operand Type(出处:Intel官方用户手册)
b Byte, regardless of operand-size attribute.
d Doubleword, regardless of operand-size attribute.
v Word, doubleword or quadword (in 64-bit mode), depending on operand-size attribute.
z Word for 16-bit operand-size or doubleword for 32 or 64-bit operand-size.
指示操作数类型的小写字母z在32位模式下表示的大小为DWORD(32位,4个字节)。综合
以上信息,操作码68对应的PUSH Iz指令中,Iz(操作数格式)表示大小为4个字节(32位)的立
即数,所以继续读取68之后的4个字节(0040B440),整条指令最终解析为PUSH 0040B4A0,用
IA-32指令格式表示如下:
[Prefixes][Opcode][ModR/M][SIB][Displacement][Immediate]
以上只是解析指令的“热身”练习,下面正式学习指令解析方法。
49.5.3 ModR/M
首先学习包含ModR/M的指令解析方法。
89C1 MOV ECX,EAX
先在操作码映射中查找Opcode 89,如图49-12所示。
从操作码映射中可以看到,Opcode 89对应MOV Ev,Gv指令,带有2个操作数,第一个操作数
的格式为Ev,第二个操作数格式为Gv。
49.5 指令解析练习 513
1
2
3
4
5
19
6
7
8
9
10
11
12
13
14
15
16
17
18
图49-12 Opcode 89 in Table A-2
由表49-3可知,操作数类型中的小写字母v表示操作数的大小为32位(4个字节)。上面指令
中2个操作数的大小均为4个字节。接下来需要把握大写字母E与G代表的含义,这样才能准确解
析整条指令。根据表49-2中的说明,大写字母E是寄存器或内存地址形式的操作数,大写字母G
只能是寄存器形式的操作数。操作数形式为E或G时,操作码后面一定存在ModR/M选项。
提示 了解操作码后就能掌握操作码映射中的指令格式(Mnemonic、操作数个数、操作
数大小)。若操作数的寻址方法为 E 或 G,则分析操作码后面的 ModR/M 即可准确解析
操作数。
所以,整条指令89C1中,紧跟在Opcode 89后面的1个字节C1即为ModR/M选项。ModR/M表
格是指操作码手册中的Table 2-2. 32-Bit Addressing Forms with the ModR/M Byte,如图49-13所示。
图49-13 Table 2-2. 32-bit ModR/M Byte
514 第 49 章 IA-32 指令
图49-13中的ModR/M表看上去比操作码映射复杂得多,但熟悉原理后就能很容易地掌握其使
用方法。ModR/M长度为1个字节,在图49-13ModR/M表中的[ModR/M]区域内,从00开始到FF结
束。前面讲解ModR/M时提到过,它按比特位分为3个部分,分别为Mod、Reg、R/M(参考ModR/M
说明)。下面以ModR/M C1为例讲解。
Ex) ModR/M = C1 C1的二进制形式为 = 11000001 拆分ModR/M = 11|000|001 (Mod:11, Reg:000, R/M:001)
ModR/M拆分后的各值在图49-13中的Mod、REG、R/M部分表示出来(二进制)。请各位在
图49-13中查找C1,分别确认Mod、Reg、R/M的值。操作数的寻址方法为E时,操作数的形式在
图49-13左侧的[E]区域(图49-13表的左侧部分)中显示。
提示 寻址方法“E”代表内存地址或寄存器。从图 49-13 中可以看到,ModR/M 值为
00~BF 时,显示在[E]区域中的操作数为内存地址;ModR/M 值为 C0~FF 时,出现在[E]
区域中的操作数为寄存器。
操作数的寻址方法为G时,操作数的形式出现在图49-13中的[G]区域(图49-13的表上端
部分)。
提示 寻址方法“G”仅有寄存器形式,表 49-2 中已经说明。从图 49-13 可以看到,根
据 ModR/M 的 Reg 值(000~111),从 EAX 变化到 EDI(操作数大小为 32 位时)。
再回来解析指令89C1,Opcode 89对应MOV Ev,Gv指令,且带有ModR/M值。ModR/M为C1,
从图49-13中可以得知,Ev=ECX,Gv=EAX。所以,指令89C1最终解析为MOV ECX,EAX。
提示 大写字母 E 与 G 表示寻址方法,在图 49-13 中分别出现在[E]区域与[G]区域。小写
字母 v 表示操作数类型,32 位计算中操作数的大小也为 32 位。根据这些信息在图 49-13
中查找 ModR/M 值 C1,可知 Ev、Gv 分别为 ECX、EAX。
用IA-32指令格式表示如下:
[Prefixes][Opcode][ModR/M][SIB][Displacement][Immediate]
49.5.4 Group Group指令语句将操作码与ModR/M组合起来,使操作码最多可以表示出8种形式的指令
(Mnemonic)。灵活使用Group指令虽然会使解析变得有些复杂,却能较好地扩展操作码映射。
83C3 12 ADD EBX,12
首先在操作码映射中查找83对应的指令形式。
从图49-14可知,Opcode 83对应的指令为Grp 1 Ev,Ib,带有2个操作数,形式分别为Ev、Ib。
由前面的讲解可知,Ev代表4个字节的寄存器(或者内存地址),Ib代表1个字节的立即数(根据
表49-3可知,操作数类型的b代表字节大小)。
49.5 指令解析练习 515
1
2
3
4
5
19
6
7
8
9
10
11
12
13
14
15
16
17
18
图49-14 Opcode 83 in Table A-2
提示 到现在为止,解析上述语句时仍未出现指令(Mnemonic),只是表示成了 Immediate
Grp 1 的形式,即在多种形式的 Group 中它是 Immediate Grp 1。1 条操作码中包含多种
指令,根据后面的 ModR/M 可以最终确定相应指令。前面图中,Immediate Group 1
的第二个操作数是立即数。该 Group 中的指令用来计算(ADD、SUB、XOR 等)这类
立即数。
操作数中含有Ev符号,所以紧跟在后面的1个字节(C3)为ModR/M。操作码为Group指令语
句时,后面必须紧跟ModR/M。综合目前获取的各种信息,指令83C312解析如下:
83C312 - Grp1 Ev, Ib
到现在还是无法确切知道指令(Mnemonic)与操作数的内容。分析ModR/M“C3”后即可
准确解析上述指令(解析与顺序无关,但从左到右解析起来会更简便)。
Ex) ModR/M = C3 C3的二进制形式为 = 11000011 拆分ModR/M = 11|000|011 (Mod:11, Reg:000, R/M:011)
首先参考Group指令表查看对应指令(Mnemonic)。Group指令表是指操作码用户手册中的
Table A-6. Opcode Extentions for One- and Two Opcodes by Group Number,如图49-15所示。
图49-15 Opcode 83 in Table A-6
图49-15中操作码为83,所以只看Group 1项目即可。并且,由于ModR/M C3的Reg值为000(二
进制),所以对应的指令(Mnemonic)为ADD。综合以上分析,指令83C312解析如下:
83C312 - ADD Ev, Ib
接下来,先确定第一个操作数。在ModR/M表中查找C3值。
由图49-16可知,ModR/M“C3”的Ev对应的值为EBX。
516 第 49 章 IA-32 指令
图49-16 ModR/M“C3”in Table 2-2
提示 从图 49-14 可知,第一个操作数的格式为 Ev。在图 49-13、图 49-16 的[E]区域中
查找寻址方法符号 E 对应的值,有 EBX、BX、BL 这 3 种,再加上操作数类型符号是
小写字母 v,所以最终选择 4 个字节的 EBX 寄存器(该处的 E 只能为通用寄存器,不
可能为 MM3 与 XMM3 寄存器)。也不可以选择[G]区域中的 EAX 值,[G]区域要在寻
址方法符号为 G 时使用。ModR/M 表比较复杂,必须准确理解其使用方法。
至此,指令83C312解析如下:
83C312 - ADD EBX, Ib
第二个操作数的符号为Ib,表示1个字节大小的立即数,直接读取ModR/M后面的1个字节(12)
即可。综上所述,指令83C312最终解析为如下形式:
83C312 - ADD EBX, 12
用IA-32指令格式表示如下:
[Prefixes][Opcode][ModR/M][SIB][Displacement][Immediate]
49.5.5 前缀
本小节介绍含Prefix(前缀)的指令解析方法。有些前缀(66,67)对整条指令的解析有着重
要影响,所以必须掌握。
66:81FE 3412 CMP SI,1234
首先在Table A-2 Opcode Map中查找“66”。
由图49-17可知,“66”表示操作数大小(前缀)。更准确地说,它表示的是Operand-Size Override
Prefix,Prefix 66的含义为“将32位大小的操作数识别为16位大小(或者将16位大小的操作数识别
为32位大小)”。紧接在Prefix 66后面的1个字节81为操作码,在操作码映射中查找“81”,如图
49-18所示。
49.5 指令解析练习 517
1
2
3
4
5
19
6
7
8
9
10
11
12
13
14
15
16
17
18
图49-17 Prefix 66 in Table A-2
图49-18 Opcode 81 in Table A-2
Opcode 81为Group指令,带有2个操作数(Ev,Iz)。符号Ev一般表示32位的寄存器(或内存
地址),而符号Iv表示32位立即数(参考操作码用户手册A.2.1 & A.2.2)。但因前面有Operand-Size
Override Prefix 66,故操作数的大小分别由32位变为16位。操作数格式中出现符号E,则表示后面
跟有ModR/M字节(FE)。综合以上分析,指令6681FE3412解析如下(请注意:Prefix 66使操作
数大小变为16位):
6681FE 3412 - Grp1 Ev, Iz (Operand Size = 16 bit)
要想得到准确指令与操作数,还要分析ModR/M值FE代表的含义。
Ex) ModR/M = FE FE的二进制表示 = 11111110 拆分ModR/M = 11|111|110 (Mod:11, Reg:111, R/M:110)
接着在Table A-6中查看Group指令, 确定其代表的具体指令,如图49-19所示。
图49-19 Opcode 81 in Table A-6
由图49-19中的Group表可知,Opcode 81(Group 1)中,ModR/M的REG(值为111)对应的
实际指令为CMP。
6681FE 3412 - CMP Ev, Iz (Operand Size = 16 bit)
接下来开始确定第一个操作数。在ModR/M表格中查找“FE”,如图49-20所示。
518 第 49 章 IA-32 指令
图49-20 ModR/M FE in Table 2-2
从图49-20中可以看到,ModR/M“FE”的Ev符号对应值为ESI,但是受Prefix 66的限制,要
选择16位的SI。
6681FE 3412 - CMP SI, Iz (Operand Size = 16 bit)
第二个操作数的符号为Iz,原指4个字节(32位)的立即数值,但受Prefix 66的影响,其变为
2个字节(16位)大小,即获取ModR/M后面的2个字节(1234)。所以整条指令最终解析如下:
6681FE 3412 - CMP SI, 1234
用IA-32指令格式表示如下:
[Prefixes][Opcode][ModR/M][SIB][Displacement][Immediate]
49.5.6 双字节操作码
本小节学习操作码为双字节时的指令解析方法。单字节操作码不够用时,可以将其扩展为双
字节。双字节操作码中第一个字节恒为0F,故其在操作码映射中的查找方式与单字节操作码是一
样的。
0F85 FA1F0000 JNZ XXXXXXXX
先在单字节操作码映射中查找指令的第一个字节(0F),如图49-21所示。
图49-21 Opcode 0F in Table A-2
由上表可知,“0F”为双字节操作码的Escape符号,指示继续在Table A-3中查找双字节操作
码。双字节操作码映射(即Table A-3)在Intel用户手册中的分量是单字节的两倍,如图49-22所示。
图49-22 Opcode 0F85 in Table A-3
49.5 指令解析练习 519
1
2
3
4
5
19
6
7
8
9
10
11
12
13
14
15
16
17
18
从图49-22的表格标题可以看到,第一个字节为“0F”。查找第二个字节85,可以看到它对应
的指令为JNE(JNZ)。
提示 Jcc 为 Conditional Jump(条件跳转)指令,一般这种条件跳转指令之前都有比较
语句(CMP、TEST),并根据比较的结果决定是否跳转。Jcc 指令有多种形式,示例中
的 0F85 被解析为 JNE(Jump Not Equal)或 JNZ(Jump Not Zero)指令(两条指令含
义相同)。Jcc 指令(0F80~0F8F)的操作数在图 49-22 中显示为“Long-displacement”。
操作数说明中,一般 Long 表示 4 个字节(32 位),Short 表示 1 个字节(8 位)。所以,
Jcc 指令的操作数为 4 字节大小的 Displacement(移位值)。
由 于 JNE 指 令 的 操 作 数 为 4 个 字 节 大 小 的 移 位 值 , 继 续 读 取 操 作 码 后 面 的 4 个 字 节
(00001FFA),整条指令解析如下:
0F85 FA1F0000 - JNE XXXXXXXX
用IA-32指令格式表示如下:
[Prefixes][Opcode][ModR/M][SIB][Displacement][Immediate]
提示 以上指令中的移位值 00001FFA 为相对位移,加上当前的 EIP 才能准确算出跳转地
址。比如,上述指令的地址为 401000,执行指令后,EIP 值为 401006(增加 6 个字节
(指令长度)),那么实际跳转的地址为 403000(JNE 403000),它是 EIP 值(401006)
与移位值(1FFA)相加的结果。调试中会经常遇到“相对位移”这一术语,希望各位
理解其含义。
49.5.7 移位值&立即数
若指令中同时存在移位值&立即数,该如何解析呢?下面学习这种指令的解析方法。
C705 00CF4000 01000100 MOV DWORD PTR DS:[40CF00], 10001
首先在操作码映射中查找“C7”,如图49-33所示。
图49-23 Opcode C7 in Table A-2
Opcode C7对应Group 11 MOV指令,带有2个操作数(Ev,Iz)。所以上述指令解析如下:
C705 00CF4000 01000100 - Grp11 Ev, Iz
出现Group指令或操作数形式中有E、G时,操作码之后必跟着ModR/M选项,上述指令中
ModR/M的值为05。
Ex. ModR/M = 05
520 第 49 章 IA-32 指令
05的二进制形式 = 00000101 拆分ModR/M = 00|000|101 (Mod:00, Reg:000, R/M:101)
接着在Group指令表(Table A-6)中查找其对应的实际指令(Mnemonic),如图49-24所示。
图49-24 Opcode C7 in Table A-6
Group 11中ModR/M的Reg值(000)对应的指令为MOV,且在Group 11中仅有一个MOV指令
(故图49-23中出现了“Group11 - MOV”的标识)。
C705 00CF4000 01000100 - MOV Ev, Iz
接下来确定第一个操作数(Ev),在ModR/M表(Table 2-2)中查找“05”,如图49-25所示。
图49-25 ModR/M 05 in Table 2-2
第一个操作数(Ev)为“disp32”,代表32位大小的移位值。ModR/M在00~BF范围内时,Ev
形式的操作数表示内存地址(ModR/M在C0~FF范围内时,Ev形式的操作数表示寄存器)。所以,
ModR/M之后的4个字节(0040CF00)为移位值,表示内存地址(表示地址时一定要用上[]中括号)。
C705 00CF4000 01000100 - MOV [0040CF00], Iz
最后,第二个操作数Iz为4个字节大小的立即数,从移位值之后读取4个字节(00010001)
即可。
C705 00CF4000 01000100 - MOV [0040CF00], 10001
上述指令表示向40CF00地址中放入10001值,使用IA-32指令格式表示如下:
[Prefixes][Opcode][ModR/M][SIB][Displacement][Immediate]
49.5.8 SIB 操作数指向内存地址时,SIB(Scale、Index、Base)用来辅助寻址。指令中含有SIB时,其
49.5 指令解析练习 521
1
2
3
4
5
19
6
7
8
9
10
11
12
13
14
15
16
17
18
本身会变得较为复杂。换言之,如果掌握了含有SIB的指令解析方法,你就达到了大师级别。
8B0C01 MOV ECX, [EAX+ECX]
首先在操作码映射中查找“8B”,如图49-26所示。
图49-26 Opcode 8B in Table A-2
由图49-26可知,Opcode 8B对应的指令为MOV Gv,Ev,带有2个操作数,第一个操作数为Gv
形式,是一个4字节的寄存器;第二个操作数为Ev形式,是一个4字节的寄存器或内存地址。操作
数的寻址方法为G、E时,操作码后会跟着ModR/M选项。解析出该ModR/M即可准确解析操作数。
上述指令中ModR/M为0C,在ModR/M表中查找,如图49-27所示。
第一个操作数Gv对应的值为ECX,第二个操作数Ev对应的值为[--][--]。符号[--][--]表示需要
SIB字节来辅助表示准确地址。综合以上分析,上述指令解析如下:
8B0C01 - MOV ECX, [--][--]
第二个操作数的符号[--][--]指向内存地址,要准确解析它需要用到SIB字节,用更直观的方
式表示如下:
[--][--] = [(Reg. A) + (Reg. B)] * 寄存器A、B二者可缺其一!
重写上述指令如下:
8B0C01 - MOV ECX, [(Reg. A) + (Reg. B)]
图49-27 ModR/M 0C in Table 2-2
这种表示方法更加直观。接下来在SIB表中查找Reg.A与Reg.B,如图49-28所示。
前面的指令中,SIB的值为01,SIB表就是操作码用户手册中的Table 2-3。
522 第 49 章 IA-32 指令
图49-28 Table 2-3. 32-bit SIB Byte
查看SIB表的方法与查看ModR/M表的方法类似,先找到SIB值,然后在表顶部的[Reg.A]区域
获取Reg.A寄存器,然后在表左侧的[Reg.B]区域获取Reg.B寄存器。图49-28中与SIB 01对应的
Reg.A为ECX,Reg.B为EAX。所以指令最终解析如下:
8B0C01 - MOV ECX, [ECX+EAX]
用IA-32指令格式表示如下:
[Prefixes][Opcode][ModR/M][SIB][Displacement][Immediate]
操作数为(复杂形式的)内存地址时,SIB就像这样用来辅助寻址。接下来解析一条带有更
复杂的SIB选项的指令。
8D8428 B1354000 LEA EAX, [EAX+EBP+4035B1]
如图49-29所示,Opcode 8D对应的指令为LEA Gv,M。
图49-29 Opcode 8D in Table A-2
LEA指令是“Load Effective Address”的缩写,是“取有效地址指令”。第一个操作数为Gv
形式,是4个字节大小的寄存器;第二个操作数为M形式,仅表示内存地址(参考表49-2)。
8D8428 B1354000 - LEA Gv, M
49.5 指令解析练习 523
1
2
3
4
5
19
6
7
8
9
10
11
12
13
14
15
16
17
18
由于操作数的寻址方法为G、M,所以操作码后面跟着ModR/M字节,分析后面的ModR/M即
可得到准确的操作数值。上述指令中ModR/M值为84,在Table 2-2中查找它,如图49-30所示。
第一个操作数Gv形式为EAX,第二个操作数M形式为[--][--]+disp32。
提示 各位现在已经非常熟悉 ModR/M 表格了吧?在表格上端求 G 形式的值,在表格左
侧求 E 或 M 形式的值。
综合以上分析,上述指令解析如下:
8D8428 B1354000 - LEA EAX, [--][--]+disp32
第二个操作数中的[--][--]+disp32指的是内存地址,要想准确解析,需要使用后面的SIB字节
与32位的移位值。使用更直观的方式表示[--][--]+disp32如下:
[--][--]+disp32 = [(Reg. A) + (Reg. B) + disp32] * 寄存器A、B两者可缺其一!
图49-30 ModR/M 84 in Table 2-2
用更直观的方式解析上述指令。
8D8428 B1354000 - LEA EAX, [(Reg. A)+(Reg. B) + disp32]
接下来查看SIB表以获取Reg.A与Reg.B对应的值。因SIB为28,故Reg.A为EAX,Reg.B为EBP
(参考图49-31)。
图49-31 SIB 28 in Table 2-3
上述指令中disp32的值为SIB后的004035B1。综合以上所有分析,整条指令最终解析如下:
8D8428 B1354000 - LEA EAX, [EAX+EBP+4035B1]
524 第 49 章 IA-32 指令
用IA-32指令格式表示如下:
[Prefixes][Opcode][ModR/M][SIB][Displacement][Immediate]
49.6 指令解析课外练习 若想完全掌握指令解析方法,需要大量练习。反复看前面的练习示例,熟悉解析方法后,在
OllyDbg调试器中打开并运行notepad.exe程序,如图49-32所示。
图49-32 通过OllyDbg做指令解析练习
参考前面打印出的操作码用户手册,将区域[1]中的指令逐条解析为反汇编代码(请先隐藏
OllyDbg调试器中的反汇编窗口)。指令解析的成功率达到99%以上后,再看区域[2]中的机器代码,
将它们解析为反汇编代码。通过这些训练,各位会迅速成长为IA-32指令解析高手。
49.7 小结
本章主要讲解IA-32指令的解析方法,刚接触它的读者朋友可能会觉得有些难度。但若完全
掌握了指令解析方法,逆向技术水平就会达到中级以上。
我主要使用IA-32指令来编写查杀恶意代码的函数。检测变形病毒(Polymorphic Virus)时,
必须探测出Polymorphic引擎中产生的指令类型,这时就需要分析人员具有丰富的指令知识。此外,
了解指令结构也有助于提高代码调试水平。掌握IA-32指令相关知识对于编写“打补丁”代码、
分析漏洞Shell代码都非常有帮助。当然,掌握IA-32指令解析方法对于提高各位的逆向分析技术
水平也有相当大的帮助。
提示 以上内容仅涉及 Intel 用户手册中的极少部分。若想深入学习有关 IA-32 指令的知
识,请各位认真研读 Intel® 64 and IA-32 Architectures Software Developer’ s Manuals(与
指令解析相关的部分为 Vol. 2A-2.1、Vol.2B-Appendix A)。