本节我们看看X86指令集以及X86的硬件体系架构。在汇编语言中最常见的指令就是mov,他将数据从一个地方转移到指定位置,该指令能将数据转移到特定位置的内存或是给定寄存器。mov指令的格式为(mov 目的,源头),源头指的是要被挪到的数据,目的是数据被挪动的目的地, 我们看几个具体例子: mov eax, ebx (把寄存器ebx中的数据拷贝到eax寄存器) mov eax, 0x42 (把数值0x42赋值给eax寄存器) mov eax, [0x4037c4](把地址为0x4037c4的4字节数据拷贝到eax寄存器] mov eax, [ebx] (先从寄存器ebx中获取数值,然后找到该数值对应的内存地址,接着再把地址所在处4字节数据赋值给寄存器eax) mov eax, [ebx + esi*4] (取出ebx中的数值,取出esi寄存器中的数值,将后者乘以4后加上前者,所得结果作为内存地址,并把给定地址的4字节数据拷贝到eax寄存器)
另一个跟mov指令很像的是指令lea,lea eax, [ebx+8],其作用为把ebx寄存器的值加上8后所得结果放入eax,这里需要注意区别,利润mov eax, [ebx+8]是把ebx的值加上8,所得结果作为内存地址,然后将地址所在处的4字节数据转移到eax寄存器,因此指令lea eax, [ebx+8]等价于mov eax, ebx+8,这是一个容易混淆之处,我们看下图会更清楚一些:
左边是寄存器的数值,右边是对应内存地址的数值。对于指令lea ebx, [eax5+5],它会计算0x00000005+5=5,然后把结果5放到寄存器ebx。lea指令主要用于加快计算速度,要不然我们完成给定计算需要使用很多条指令,例如先使用mul 5将eax里面的数值乘以5,然后add eax, 5,将5加到eax,然后mov ebx, eax,将eax中存储的数值挪动到ebx,因此不使用lea指令的话,我们需要使用4到5条指令才能达到相同效果。
x86指令集中有不少专门用于进行数值运算。从加减乘除到各种二进制操作,指令add用于执行加法操作,它的格式为:add destination, value 也就是把value对应的数值家到destination对应的数值上,后者可以是内存,也可以是特定寄存器。同理sub指令用于进行减法操作,特别要注意的是,该指令会修改两个重要的标志位寄存器,分别为ZF和CF,如果做减法后所得结果为0,ZF寄存器就好设置为1,如果sub destination ,value这条指令执行时,destination对应的数值小于value,那么CF寄存器就会设置为1,我们看几条运算指令的例子: sub eax, 0x10 把eax寄存器里面的数值减去0x10 add eax, ebx 把ebx寄存器里面的数值添加到eax寄存器 inc edx,将edx寄存器里面的数值增加1 dec ecx 将ecx寄存器里面的数值减少1
乘除法指令分别为mul, div,这两条指令的特点是指对应一个操作数,例如mul 5是指将eax寄存器中的数值乘以5,注意eax没有出现在指令中,对mul指令而言,它默认就是讲eax的数值乘以给定数值。所以在执行mul指令前,我们必须预先存储好eax指令中的数值。mul指令执行后所得结果会被当成一个64位数值分别存储在两个寄存器中,他们分别为edx和eax,edx存储高32位,eax存储低32位,假设执行mul指令后,所得结果为5,000,000,000,该数值早已超过32位,因此必须使用两个32位的寄存器共同存储,其存储结果如下:
div指令使用与mul一样,只是结果的存储正好相反。做完除法后所得结果为64位,那么除法的结果存储在eax,除法所得余数存储在edx,因此在执行mul或div指令时,需要预先存储好两个寄存器的数值,要不然指令执行后原来存储的数值会被冲刷掉。在高级语言编程中我们计算余数时,例如x = x%b,那么底层就好使用div指令,然后把寄存器edx中的数值转移到x变量对应的地址。
除了运算指令外,二进制操作指令也经常用到,例如or, and 和xor等。这些指令的用法与add,sub类似,xor指令在汇编中经常使用,它常用于清零,例如xor eax, eax就是把eax寄存器的数值设置为0,当然我们也可以使用mov eax, 0来清零,但是前者转换为机器码时只有2字节,后者需要5字节。
指令shr,shl用于进行挪位操作,例如shr destination, count把destination对应的数值向右移动count位,shl destination, count 把destination的值向左边挪动count位,挪动后空出来的比特位用0来补齐。例如 shr 1000, 1,执行后数值变为0100。指令ror和rol也用来挪动二进制比特位,于shr,shl不同的是,他们不是用0弥补而是把被挪走的比特位放到前面,例如ror 10101, 1执行后所得结果为11010.挪位指令通常对应对应高级语言的>>和<<操作符。在进行反汇编分析是时,如果我们看到一系列shr, shl, ror,rol等指令,这意味着代码极有可能在进行加解密操作。
最后一条操作符指令就是NOP,它什么都不做,这条指令经常用于进行缓冲区溢出攻击,这条指令的作用在后续章节中会详细讲解。