逆向工程——汇编基础(二)

[TOC]

参考文档:《简明 x86 汇编语言教程》作者:司徒彦南
##使用寄存器
对x86基本寄存器的认识,对于一个汇编语言编程人员来说是不可或缺的。

###汇编语言中的整数常量表示
####十进制整数
这是汇编器默认的数制。直接用我们熟悉的表示方式表示即可。例如,1234表示十进制的1234。不过,如果你指定了使用其他数制,或者有凡事都进行完整定义的习惯,也可以写成[十进制数]d[十进制数]D在行式。
####十六进制数
这是汇编程序中最常用的数制。十六进制数表示为0[十六进制数]h0[十六进制数]H,其中,如果十六进制数的第一位是数字,则开头的0可以省略,例如7fffh0ffffh
####二进制数
这也是一种常用的数制。二进制表示为[二进制数]b[二进制数]B。一般程序中用二进制数表示掩码等数据非常的直观,但需要些很长的数据(4位二进制数相当于一位十六进制数)。例如,1010110b。
####八进制数
八进制数现在已经不是很常用了,一个典型的例子是Unix的文件属性。八进制数的形式是[八进制数]q[八进制数]Q[八进制数]o[八进制数]O。例如,777Q。
调试器默认使用十六进制表示整数

###简单指令
下面介绍一些指令,在这之前,我们约定:

  1. reg32,32-bit寄存器,如EAX、EBX等。
  2. reg16,16-bit寄存器,如AX,BX等。
  3. reg8?,8-bit寄存器,如AL,BH等。
  4. imm32,32-bit立即数,可以理解为常数。
  5. imm16,16-bit立即数。
  6. imm8? 8-bit立即数。

####MOV指令
mov,要move的缩写,它可以将数据发送到寄存器中。

1
2
3
4
5
6
7
mov reg32,(reg32 | imm8 | imm16 | imm32)
mov reg16,(reg16 | imm8 | imm16)
mov reg8,(reg8 | imm8)

例如:
mov eax,ebx ;ebx内容送入eax
mov ecx,ebx ;edx内容送入ecx

mov eax,010h表示在EAX寄存器中载入00000010h

####xchg指令

1
2
3
4
5
6
xchg reg32,reg32
xchg reg16,reg16
xchg reg8,reg8

例如:
xchg ebx,ecx,表示ebx与ecx的数值被交换。

####递增(减)指令

1
2
inc reg(8,16,32)
dec reg(8,16,32)

####add指令
将寄存器的数值与另一寄存器或立即数的值相加,并存回此寄存器

1
2
3
4
5
add reg32,reg32/imm(8,16,32)
add reg16,reg16/imm(8,16)
add reg8,reg8/imm(8)

减法SUB同ADD

####lea指令
目标地址传送指令:将一个近地址指针写入到指定的寄存器。

1
2
3
4
lea reg16,mem16

例如:
lea ax,buf ;将存储器buf所指的地址传送给ax

其中,reg16必须是一个16位的通用寄存器,mem16必须是一个存储器。执行完这个指令后,就讲mem16所指的16位偏移地址传送到了reg16中。
MOV指令传送的是地址所指的内容,而LEA只是传地址

####rep、stos指令
REP指令的作用是重复上面的指令,ECX的值是重复次数。
STOS指令是将EAX中的值拷贝到一个目的地址中。

1
2
3
4
lea edi,[ebp-0C0h]
mov ecx,30h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
  1. REP指令可以是任何字符串指令(CMPS、LODS、MOVS、SCAS、STOS)的前缀。
    REP能够引发其后的字符串指令被重复,只要ECX的值不为0,重复就会继续。每一次字符串指令执行后,ECX的值都会减小。
  2. STOS(store into string)意思是把EAX的内容拷贝到一个目的地址。
    用法:stos dst,dst是一个目的地址,例如stos dword ptr es:[edi]dword ptr(强制转换成dword格式)前缀是告诉stos,一次拷贝双字(4个字节)到目的地址。

####逻辑运算
逻辑运算指令qnrt包括AND, OR, XOR, TEST, NOT,逻辑运算的结果会影响到CF, PF, AF, ZF, OF标志位。
关于如何影响及哪些操作影响哪些标志位,请参考[Win32 汇编 - 逻辑运算指令: AND、OR、XOR、NOT、TEST][1]
[1]: http://blog.csdn.net/betabin/article/details/7306347 “Win32 汇编 - 逻辑运算指令: AND、OR、XOR、NOT、TEST”

####cmp指令
cmp(compare)指令比较两个操作数的大小,进行的运算是第一个操作数减去第二个操作数,但结果不会影响两个操作数的值,只会影响flag的CF、ZF、OF、AF、PF。
对各个flag的影响如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
若执行指令后
ZF=1 这个简单,则说明两个数相等,因为zero为1说明结果为0
当无符号时:

CF=1 则说明了有进位或借位,cmp是进行的减操作,故可以看出为借位,所以,此时oprd1<oprd2
CF=0 则说明了无借位,但此时要注意ZF是否为0,若为0,则说明结果不为0,故此时oprd1>oprd2
当有符号时:
若SF=0,OF=0 则说明了此时的值为正数,没有溢出,可以直观的看出,oprd1>oprd2
若SF=1,OF=0 则说明了此时的值为负数,没有溢出,则为oprd1<oprd2
若SF=0,OF=1 则说明了此时的值为正数,有溢出,可以看出oprd1<oprd2
若SF=1,OF=1则说明了此时的值为负数,有溢出,可以看出oprd1>oprd2
最后两个可以作出这种判断的原因是,溢出的本质问题:
两数同为正,相加,值为负,则说明溢出
两数同为负,相加,值为正,则说明溢出
故有,正正得负则溢出,负负得正则溢出

参考来自:[[汇编cmp比较指令详解]][1]
[1]: http://laokaddk.blog.51cto.com/368606/284280/ “汇编cmp比较指令详解”
####跳转指令
跳转指令分三类:

  1. 无条件跳转:JMP
  2. 根据CX、ECX寄存器的值跳转:JCXZ(CX为0则跳转)、JECXZ(ECX为0则跳转)
  3. 根据EFLAGS寄存器的标志位跳转,列表如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    JE   ;等于则跳转
    JNE ;不等于则跳转

    JZ ;为 0 则跳转
    JNZ ;不为 0 则跳转

    JS ;为负则跳转
    JNS ;不为负则跳转

    JC ;进位则跳转
    JNC ;不进位则跳转

    JO ;溢出则跳转
    JNO ;不溢出则跳转

    JA ;无符号大于则跳转
    JNA ;无符号不大于则跳转
    JAE ;无符号大于等于则跳转
    JNAE ;无符号不大于等于则跳转

    JG ;有符号大于则跳转
    JNG ;有符号不大于则跳转
    JGE ;有符号大于等于则跳转
    JNGE ;有符号不大于等于则跳转

    JB ;无符号小于则跳转
    JNB ;无符号不小于则跳转
    JBE ;无符号小于等于则跳转
    JNBE ;无符号不小于等于则跳转

    JL ;有符号小于则跳转
    JNL ;有符号不小于则跳转
    JLE ;有符号小于等于则跳转
    JNLE ;有符号不小于等于则跳转

    JP ;奇偶位置位则跳转
    JNP ;奇偶位清除则跳转
    JPE ;奇偶位相等则跳转
    JPO ;奇偶位不等则跳转

###实模式与保护模式
为何要了解Intel 80386的保护模式和分段机制?首先,我们知道Intel 80386只有在进入保护模式后,才能充分发挥其强大的功能,提供更好的保护机制和更大的寻址空间,否则仅仅是一个快速的8086而已。没有一定的保护机 制,任何一个应用软件都可以任意访问所有的计算机资源,这样也就无从谈起操作系统设计了。且Intel 80386的分段机制一直存在,无法屏蔽或避免。其次,在我们的bootloader设计中,涉及到了从实模式到保护模式的处理,我们的操作系统功能(比 如分页机制)是建立在Intel 80386的保护模式上来设计的。如果我们不了解保护模式和分段机制,则我们面向Intel 80386体系结构的操作系统设计实际上是建立在一个空中楼阁之上。
####模式种类
从80386开始,cpu有三种工作方式:实模式,保护模式和虚拟8086模式。只有在刚刚启动的时候是real-mode,等到linux操作系统运行起来以后就运行在保护模式。
实模式只能访问地址在1M以下的内存称为常规内存,我们把地址在1M 以上的内存称为扩展内存。
在保护模式下,全部32条地址线有效,可寻址高达4G字节的物理地址空间;扩充的存储器分段管理机制和可选的存储器分页管理机制,不仅为存储器共享和保护提供了硬件支持,而且为实现虚拟存储器提供了硬件支持;支持多任务,能够快速地进行任务切换和保护任务环境;4个特权级和完善的特权检查机制,既能实现资源共享又能保证代码和数据的安全和保密及任务的隔离;支持虚拟8086方式,便于执行8086程序。
虚拟8086模式是运行在保护模式中的实模式,为了在32位保护模式下执行纯16位程序。它不是一个真正的CPU模式,还属于保护模式。
####模式区别
保护模式同实模式的根本区别是进程内存受保护与否 。可寻址空间的区别只是这一原因的果。
【实模式】将整个物理内存看成分段的区域,程序代码和数据位于不同区域,系统程序和用户程序没有区别对待,而且每一个指针都是指向”实在”的物理地址。这样一来,用户程序的一个指针如果指向了系统程序区域或其他用户程序区域,并改变了值,那么对于这个被修改的系统程序或用户程序,其后果就很可能是灾难性的。为了克服这种低劣的内存管理方式,处理器厂商开发出保护模式。
【保护模式】物理内存地址不能直接被程序访问,程序内部的地址(虚拟地址)要由操作系统转化为物理地址去访问,程序对此一无所知。 至此,进程(这时我们可以称程序为进程了)有了严格的边界,任何其他进程根本没有办法访问不属于自己的物理内存区域,甚至在自己的虚拟地址范围内也不是可以任意访问的,因为有一些虚拟区域已经被放进一些公共系统运行库。这些区域也不能随便修改,若修改就会有: SIGSEGV(linux 段错误);非法内存访问对话框(windows 对话框)。
【补充】保护模式下,有两个段表:GDT(Global Descriptor Table)和LDT(Local Descriptor Table),每一张段表可以包含8192 (2^13)个描述符[1],因而最多可以同时存在2 * 2^13 = 2^14个段。虽然保护模式下可以有这么多段,逻辑地址空间看起来很大,但实际上段并不能扩展物理地址空间,很大程度上各个段的地址空间是相互重叠的。目 前所谓的64TB(2^(14+32)=2^46)逻辑地址空间是一个理论值,没有实际意义。在32位保护模式下,真正的物理空间仍然只有2^32字节那 么大。注:在ucore lab中只用到了GDT,没有用LDT。
####64位奔腾4处理器工作模式
事实上,现在的64位奔腾4处理器,拥有三种基本模式和一种扩展模式,

  1. 基本模式:
    保护模式:纯32位保护执行环境。
    实模式:纯16位无保护执行环境。
    系统管理模式:当SMI引脚为有效进入系统管理模式,首先保存当前的CPU上下文。它有独立的地址空间,用来执行电源管理或系统安全方面的指令。
  2. 扩展模式:
    IA-32e模式,64位操作系统运行在该模式。
    该模式有两种子模式:
    1)兼容模式:该模式下,64位操作系统运行在32位兼容环境,能正常运行16,32位应用程序就像基本的保护模式一样,访问32位地址空间,但不能运行纯16位实模式程序(就是不能运行虚拟86模式程序了)。
    2)64位模式:在该模式下,处理器完全执行64位指令,使用64位地址空间和64操作数,运行16,32位程序必须切换到兼容模式。
    IA-32e子模式的切换完全基于代码段寄存器。这样一来,运行在IA-32e模式中(64位)的OS完全可以无缝的运行所有16,32,64为应用程序,通过设置32位后的CS。

##练习
把寄存器全部设置成0的状态,然后执行下面的代码:

1
2
3
4
mov eax,0a1234h			;将十六进制数0a1234h送入eax
mov bx,ax ;将ax内容送入bx
mov ah,bl ;将bl内容送入ah
mov al,bh ;将bh内容送入al

思考此时EAX的内容是多少?