Intel CPU相关内容学习目录:
Intel32位指令编码学习笔记
保护模式学习笔记之基础知识
保护模式学习笔记之段机制
保护模式学习笔记之分页机制
IA-32e模式学习笔记
CPU可以处理的数据只有0,1数据,C/C++编译以后生成的可执行文件保存在磁盘中是都是以二进制的形式保存的。如下图是用Winhex打开一个可执行文件的内容,由于二进制难以查看,所以Winhex以16进制的形式展示出来。
那也就是说,在没有源码情况下,得到一个可执行文件的时候,只能通过这些指令编码来识别程序的行为。但这样的指令编码是难以读懂的,所以需要对这些指令编码进行转换。如下图就是OD将左边的指令编码转成右边的可读的汇编语言:
而要知道如何将二进制保存的指令编码转换成可读的汇编语言,就需要使用intel提供的白皮书。在白皮书的第35页给出了下图,该图给出了intel的指令编码的格式,可以看出一条指令编码是由6个部分构成的。
其中各个部分的含义如下:
由此可知,每一条指令编码的长度最长为15个字节,最短为1个字节。而指令的长度,很大程度上又是由操作码,也就是Opcode来决定的。所以,对操作码的解析是最重要的,白皮书中对操作码的说明在1475页中的TableA-2。
在这两张表中,最左列一列数字代表的是操作码的高4位,第一行的数字代表的是操作码的低4位,这样就可以找到不同的操作码所对应的汇编指令从而解析出指令。
前缀指令一共有4个类别,分别有不同的作用。
这一类的前缀指令只有一个,那就是0x66。该前缀指令的作用是修改操作数的数据宽度,如下图是不包含该前缀指令的指令编码,其功能就是将32位的ebx寄存器中的内容入栈。
而当在该指令前面加入前缀指令0x66的时候,此时这条指令就会变成将16位的bx入栈,也就是说该前缀指令的作用就是将32位操作数的数据宽度改变为16位的。
这一类指令也只有一个,那就是0x67。该前缀指令的作用是可以改变地址计算时候的宽度。如下下面这条指令,在没有前缀指令的时候,计算地址时候是用32位寄存器ecx计算。
而当该条指令编码加上0x67前缀指令的时候,地址的计算就变成使用16位的bx和di
该类指令,主要是用来改变指令操作时候的数据段。通过使用不同的前缀指令,可以指定指令运行时候使用不同的数据段,具体数值如下:
当没有使用这些前缀指令的时候,指令的运行过程中所使用的段都是默认的段,比如下面这两条指令编码所使用的段就是ss段
而通过这一类的前缀指令就可以修改默认的段
这类指令一共有三种:
其中的LOCK指令的作用是在多核CPU情况下,保证只有一个CPU可以访问指定的指令。
根据上面的TableA-2可以得知,高位为0x4,0x5,0x7,0x9,0xB,0xC,0xE的时候,代表的就是定长指令,因为这类指令里面所写的内容并没有像Eb,Gv这样的内容。对于表中操作码上的i64,o64,d64则在1473页的Table A-1中有说明
i64: 在64位的情况下无效
o64:只在64位的情况下有效
d64:在64位的模式下,默认的操作数的宽度也是64位
而对于需要使用寄存器作为操作数的操作码来说,寄存器的名字说明了其宽度(64位,32位,16位,8位。这部分的说明在1469页的A.2.3中
当一个操作码需要一个特定的寄存器作为操作数时,该寄存器将通过名称(例如,AX、CL或ESI)进行标识。该名称表示寄存器是64、32、16还是8位宽。
当寄存器宽度取决于操作数大小属性时,使用eXX或rXX的寄存器标识符。当可能使用16或32位大小时,则可使用EXX;当可能使用16、32或64位大小时,将使用RXX。例如:eAX表示当操作数大小属性为16时使用AX寄存器,当操作数大小属性为32时使用EAX寄存器。RAX可以表示AX、EAX或RAX。
而对于这些寄存器的操作,随着操作码低4位数值的增加,按照EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI(32位),AL,CL,DL,BL,AH,CH,DH,BH的顺序增加。所以对于0x50-0x57的操作码来说,对应的指令为push eax, push ecx, push edx, push ebx,push esp, push ebp, push esi, push edi。对于0x58-0x5F则是pop eax, pop ecx, pop edx, pop ebx, pop esp, pop ebp, pop esi, pop edi。同理,对于0x40-0x47就是inc eax到inc edi,对于0x48-0x4F就是dec eax到dec edi。而对于0x90-0x97,其中0x90比较特殊,为nop指令,其他的依旧按照顺序为xchg eax, ecx到xchg eax, edi。
对于非寄存器的操作数来说,操作数是由形式为Zz的两个字符代码标识。第一个字符大写字母指定寻址方法;第二个字符小写字母指定操作数的类型。
其中寻址方式在1467页的A.2.1中给出了说明
A:直接地址:指令没有ModR/M字节,操作数的地址用指令编码。不能应用基本寄存器、索引寄存器或缩放因子(例如,farJMP(EA))
J:该指令包含要添加到指令指针寄存器中的相对偏移量(例如,JMP(0E9)、LOOP)
I:立即数:操作数值被编码在指令的后续字节中
而第二部分的操作数类型则在随后的A.2.2中给出了说明
b:1字节,与操作数大小的属性无关
d:4字节,与操作数大小的属性无关
p:32位、48位或80位的指针,具体取决于操作数大小的属性
v:2,4或8字节,取决于操作数大小的属性
由上内容可以得知:
0xB0-0xB7对应的操作就是将1字节立即数赋值到寄存器中,比如0xB0 0x12对应的是mov ax, 0x12。而对于0xB8-0xBF则是按照寄存器的顺序将4字节的内容赋值到寄存器中,比如0xB878563412对应的是 mov eax, 0x12345678,而0xBF 0x12345678则是 mov edi, 0x12345678。
0x70-0x7F,该类指令为JCC跳转指令+一个字节的偏移地址,偏移地址的计算公式是(目标地址-指令地址-2)。偏移地址以补码的形式保存,所以当偏移地址的大小在0x00-0x7F的时候,指令会向高地址跳转,而如果大小在0x80到0xFF的时候,指令就会向低地址跳转,如下图所示
对于操作码高位为0xE的这一类指令中,只有0xEA比较特殊,它的指令格式是JMP Ap,将会读取6个字节长度的数据作为直接地址,代表的是JMP CS:Id,其中的高2位赋值给CS,低4位赋值给了EIP。
还有高位为0xC的4个定长指令分别是0xC3,0xC2,0xCB,0xCA,所代表函数可以按照上面的方法得到。
另外还有一类定长指令是当操作码等于0x0F的时候,由TableA-2中可以知道,此时的操作码至少为2位
而对下一个操作码的解析需要查看1478页的TableA-3才可以得知。
在这一类指令中,最常见的就是高位为0x8的这一组指令,它由JCC以及后面的4个字节作为偏移作为指令,偏移地址依然等于目的地址-指令地址-6,同样采用补码形式,也就是说0x00000000-0x7FFFFFFF会往高地址跳转,而0x80000000-0xFFFFFFFF则会向低地址跳转。
由此可以得出结论,在Intel指令集中,有一部分指令只需要通过操作码就可以得知完整的指令长度以及指令的作用,此类指令便是定长指令。
这一类指令是当查表发现是Eb这样的指令的时候,就说明此时的指令是变长指令,指令需要通过ModR/M进一步解析。比如0x00,此时对应的指令是ADD Eb, Gb,那么接下去的一个字节就是ModR/M字段,用它的值才能决定指令的操作。
根据上面A.2.1可以知道E和G的含义是:
E:一个ModR/M字节遵循操作码并指定操作数。该操作数可以是通用寄存器或存储器地址。如果是内存地址,则根据段寄存器和以下任意值计算该地址:基本寄存器、索引寄存器、缩放因子、位移。也就是说它可以是寄存器或者内存
G:ModR/M字节的reg字段选择一个通用寄存器(例如,AX(000))
那也就是说E可以是寄存器也可以是内存,而G只能是通用寄存器。而E和G代表的内容是什么,就需要对ModR/M进行解析。一字节的ModR/M分成了三个部分,各部分的名称以及占用的比特位如下:
其中的R/M和Mod字段,决定了E的内容,而Reg/Opcode则决定了G的内容。在32位下,它们具体是如何决定的,就需要查看白皮书中第41页得Table2-2
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2022-1-8 12:38
被1900编辑
,原因: