滴水三期:day05.1-转移指令

本文详细介绍了x86指令系统中JMP、CALL、RET、CMP、TEST以及JCC指令的用法和原理。JMP用于无条件跳转,CALL在跳转的同时保存返回地址,RET则从堆栈取出返回地址继续执行。CMP用于比较操作数,改变标志寄存器,TEST则进行与运算并更新标志寄存器。JCC指令如JE、JNE等根据标志寄存器的特定位进行有条件跳转。文章还探讨了无符号和有符号数比较的区别,并提供了相关作业和记忆技巧。

一、JMP

  • jmp指令的作用:修改EIP的值,且jmp执行时只有eip发生变化

    EIP是什么:EIP中的地址值就是CPU要读取指令的地址,但是jmp准确说没有跳转的作用,只是去修改EIP中的值,只有CPU才根据eip中的值去跳转,而且不是所有的指令都可以改EIP中的值,比如mov指令没法对EIP做修改

  • 格式:

    jmp short 立即数   #简写:jmp 立即数
    jmp 寄存器
    

    如果转移的目标地址的范围在JMP指令所处地址的-128~+127字节范围之内,则会自动加上short,如果超过此范围再用short会报错

二、call

  • 作用:修改EIP中的值并且将call的下一跳指令的地址压入堆栈中!所以栈顶地址也会发生变化,即ESP会发生变化,此地址也称返回地址

  • 格式:

    CALL 地址A/寄存器
    
  • 计算机是怎么知道下一跳指令的地址的:根据硬编码知识,不同的指令用编码表示有不同的字节,见下图。那么比如现在call指令的地址为0040116E,则在call执行之前,计算机就通过call的地址+call指令的硬编码长度5,把下一跳的地址算出来,并且将返回地址入栈

    屏幕截图 2021-11-28 152314

三、RETN

  • 作用:修改EIP中的值,将栈顶地址中存的值—即返回地址出栈,并赋给EIP。因为要出栈,所以esp的值会+4,即栈顶指针下移

    类似于指令

    pop eip     #将现在栈顶中的值出栈赋给eip,CPU根据eip中的返回地址值跳转到原来call函数的下面
    
  • 一般RET和CALL指令时成对出现的

四、CMP

  • 作用:只改变标志寄存器,不会修改任何一个操作数

  • 原理:执行从目的操作数中减去源操作数的隐含减法操作(SUB),但是不对任何一个操作数做修改!只是根据相减的结果来改变零标志位的,当两个操作数相等的时候,零标志位置1

  • 格式:

    CMP EAX,ECX
    CMP AX,WORD PTR DS:[405000]   #可以将内存中的值与寄存器作比较,但是数据宽度必须一致
    CMP EAX,DWORD PTR DS:[405000]
    
  • 通过CMP执行后的标志寄存器来判断两个操作数的大小

    • 如果比较的是两个无符号数,则零标志位ZF和进位标志位CF表示的两个操作数之间的关系

      CMP结果ZFCF
      目的操作数 < 源操作数01
      目的操作数 > 源操作数00
      目的操作数 = 源操作数10
    • 如果比较的是两个有符号数,则符号标志位、零标志位和溢出标志位表示的两个操作数之间的关系

      CMP结果标志位
      目的操作数 < 源操作数SF ≠ OF
      目的操作数 > 源操作数SF=OF
      目的操作数 = 源操作数ZF=1

    一般ZF位和SF位比较重要

五、TEST

  • 作用:该指令在一定程序上和CMP指令类似,两个数值进行与AND操作结果不保存但是会改变相应标志位

  • 格式:

    TEST  R/M,R/M/IMM   #同样前面不能同时为操作数
    
  • 常见用法:一般用来判断某寄存器值是否为0(一般看ZF标志寄存器判断)

    test eax,eax        #用来判断eax寄存器中值是否为0,如果为0,则最后ZF标志寄存器置为1
    

    因为实际上就是做运算:eax and eax,如果eax中的值为0x00000000,则and运算结果还是全0,所以zf置为1;但是如果eax中的值某一位不为0,则结果就不为0

六、JCC指令

1.JE、JZ

  • JE:jmp equal;JZ:jmp zero(je和jz作用差不多)

  • 作用:也是用来跳转的,但是是有条件的跳转,不像jmp是无条件的跳转

  • 如何跳转:根据标志寄存器ZF位来判断是否跳转,如果执行此指令时的ZF零标志位为1,则跳转;否则不跳转继续执行后面的指令。简单来说就是结果相等就跳转

    虽然一般je或者jz指令上面都会有比较相关的语句,因为cmp和je一般都和c语言的ifelse语句有关系,所以其实就是做一个比较,然后决定是否跳转到哪里执行

  • 格式:

    cmp eax,ecx      #cmp会改变标志寄存器,当eax与ecx值一样,则ZF位为1
    je 0x0019aaef    #看此时标志寄存器ZF,如果为1则跳转到0x0019aaef这个地址执行;否则不跳转,继续往下执行
    

2.JNE、JNZ

  • JNE:jmp if not equal;JNZ:jmp if not zero
  • 作用:有条件的跳转,根据标志寄存器ZF位来决定是否跳转,如果ZF标志位为0,则跳转;否则不跳转。简单来说就是结果不相等就跳转

3.JS

  • JS:jmp if sign

  • 作用:如果标志寄存器SF位为1,则跳转;否则不跳。即结果为负数则跳转

4.JNS

  • JNS:jmp if not sign
  • 作用:如果SF位为0,则跳转;否则不跳。即结果为非负则跳转

5.JP、JPE

  • JP:jmp if parity;JPE:jpm if parity even

  • 作用:结果低8位中1的个数为偶数则跳转,即PF=1跳转

6.JNP、JPO

  • JNP:jmp if not parity;JPO:jump when parity flag is odd

  • 作用:结果低8位中1的个数为奇数则跳转,即PF=0跳转

7.JO

  • JO:jmp if overflow
  • 作用:结果溢出了则跳转,即OF=1跳转

8.JNO

  • 作用:结果没有溢出则跳转,即OF=0跳转

9.JB、JNAE

  • JB:jmp if below;JNAE:jmp if not above or equal
  • 作用:JB指令前面的cmp等比较指令中前面的数跟后面的数比较小于则跳转 (无符号数),即CF=1跳转

10.JNB、 JAE

  • JNB:jmp if not below;JAE:jmp if above or equal
  • 作用:大于等于则跳转 (无符号数),即CF=0跳转

11.JBE、JNA

  • JBE:jmp if below or equal;JNA:jmp if not above

  • 作用:小于等于则跳转(无符号数),即CF=1或者ZF=1跳转

    为什么JNB没有ZF=1条件?因为大于等于时,只需要根据CF就可以判断;但是JBE,如果小于出现借位的情况,此时CF=1,但是如果等于,则CF=0但是ZF=1,所以JBE,CF=1成立时跳转,CF=0时也不要着急,再看ZF,如果=1也可以跳转

12.JNBE、 JA

  • JNBE:jmp if not below or equal;JA:jmp if not above

  • 作用:大于则跳转(无符号数),即CF=0并且ZF=0跳转

    因为大于就表示小于等于,所以要CF=0和ZF=0同时满足


13.JL、JNGE

  • JL:jmp if less;JNGE:jmp if not greater or equal

  • 作用:小于则跳转有符号数),即SF ≠ OF跳转(注意这里的小于是指我们平时所说的有符号的数的大小,比如3小于5,-3小于-1,-3小于5等,就是我们平时写代码时if判断里面写的数,不是看计算机中存的二进制数大小哦!!!

    为什么SF ≠ OF?

    1. 如果比较的是两个正数,cmp比较会隐含一个sub操作,因为被减数小于减数,那么最后结果必定在左半球(即80到ff),则SF=1。再判断OF位:因为计算机会把减法当做加一个负数来处理,所以此时相当于一个正数加一个负数,则OF位永远为0,此时SF≠OF
    2. 如果比较的是两个负数,因为被减数要小于减数(90000000-a0000000),它们相减结果一定在左半球(即80到ff),所以SF为1;但是同样负数减负数相当于一个负数加一个正数,则OF永远为0
    3. 如果比较的是一个负数一个正数,因为要满足小于,所以只能是负数减正数,即cmp 负数,正数,那么相当于两个负数相加,则OF位可能溢出也可能不溢出:当溢出时,OF=1,此时结果应该在右半球(0-7f),则SF=0;当没有溢出时,OF=0,此时结果应该在右半球(80-ff),则SF=1

    综上所述,有符号数的小于跳转条件其实就是SF≠OF

    image-20211128183504549

14.JNL、JGE

  • JNL:jmp if not less;JGE:jmp if greater or equal

  • 作用:大于等于则跳转 (有符号数),即SF=OF跳转

    为什么SF=OF?

    1. 如果比较的是两个正数,cmp比较会隐含做一个sub操作,因为被减数大于等于减数,所以结果一定在右半球(0-7f),SF=0。而同理,正数减正数计算机会处理为正数加负数,永远不会溢出,则OF=0
    2. 如果比较的是两个负数,被减数大于等于减数(左半球任意取两个数),结果总是在右半球(0-7f),SF=0,且永远不会溢出,则OF=0
    3. 如果比较的是一个正数一个负数,被减数大于等于减数,则一定是正数减负数,相当于两个正数相加,则可能溢出可能不溢出:如果溢出,OF=1,结果一定在左半球,则SF=1;若干不溢出,OF=0,即如果一定在右半球,则SF=0

    综上所述:SF=OF等价于大于等于(有符号数)

15.JLE、 JNG

  • JLE:jmp if less or equal;JNG:jmp if not greater
  • 作用:小于等于则跳转(有符号数),即SF≠OF或者ZF=1(在小于的基础上加一个等于即可)

16.JNLE、JG

  • JNLE:jmp if not less or equal;JG:jmp if greater

  • 作用:大于则跳转(有符号数),即SF=OF并且ZF=0

七、有无符号的区别

  • 假如现在:

    eax=0xffff0000;ecx=0x7ffffffff
    cmp eax,ecx     #此时执行完后,当成无符号:CF=0,ZF=0,SF=0;当成有符号:OF=1
    
    如果当成无符号的数,那么JA 0x00401139应该跳转,
    ①使用JA的文字条件判断:大于则跳转,eax确实比ecx大,所以跳转
    ②使用JA的条件判断:CF=0 and ZF=0,因为eax-ecx结果不等于0,且最高位没有发生借位,所以跳转
    
    如果当成有符号的数,那么JG 0x00401129不跳转,
    ①使用JG文字条件判断:大于则跳转(有符号数),因为当成有符号数,所以ecx表示正数,eax表示负数。而eax-ecx是负数减正数,不满足大于的情况,所以不跳转
    ②使用JG的条件判断:SF=OF and ZF=0,因为发生了溢出,所以OF=1,且结果不等于0,即ZF=0,所以不跳转
    
    image-20211128190440229

八、作业

  1. CALL执行时堆栈有什么变化?EIP有变化吗?

    • 有变化,堆栈的栈顶地址-4,并且将call指令下一条指令的地址压栈;EIP的值为call函数后面跟的地址

      image-20211128204317268
  2. RET执行时堆栈有什么变化?EIP有变化吗?

    • 有变化,栈顶中的值出栈,将值赋给EIP,堆栈的栈顶地址+4

      image-20211128204757554
  3. 使用汇编指令修改标志寄存器中的某个位的值,实现JCC的十六种跳转

    要通过汇编指令的执行去影响标志位,能用CMP和TEST实现的优先考虑.

    • cmp影响cf位

      mov eax,0x77777777
      mov ecx,0x88888888
      
    • cmp影响pf位

      mov eax,0x1
      cmp eax,eax  #结果为0
      
    • cmp影响af位

      mov eax,0x2222
      mov ecx,0x3333
      cmp eax,ecx
      
    • TEST影响zf位

      mov eax,0
      TEST eax,eax
      
    • CMP影响SF位

      mov eax,0xffffffff
      mov ecx,0x81111111
      cmp eax,ecx
      
    • cmp影响OF位

      mov eax,0xc0000000
      mov ecx,0x50000000
      cmp eax,ecx
      

    • JE跳转(ZF=1)

      mov eax,0
      test eax,eax
      je 0x4010cf
      
    • JNE跳转(ZF=0)

      mov eax,0x1
      mov ecx,0x2
      cmp eax,ecx
      jne 0x4010e2
      
    • JS跳转(SF=1)

      mov eax,0xffffffff
      mov ecx,0x11111111
      cmp eax,ecx
      js 0x4010f1
      
    • JNS跳转(SF=0)

      mov eax,0x2
      mov ecx,0x1
      cmp eax,ecx
      jns 0x401109
      
    • JP跳转(PF=1)

      mov eax,0x4
      mov ecx,0x1
      cmp eax,ecx
      jp 0x40111e
      
    • JNP跳转(PF=0)

      mov eax,0x4
      mov ecx,0x2
      cmp eax,ecx
      jnp 0x40112f
      
    • JO跳转(OF=1)

      mov eax,0xaaaaaaaa
      mov ecx,0x55555555
      cmp eax,ecx
      jo 0x401143
      
    • JNO跳转(OF=0)

      mov eax,0xffffffff
      mov ecx,0x11111111
      cmp eax,ecx
      jno 0x401154
      
    • JB跳转(CF=1)

      mov eax,0x10000000
      mov ecx,0x20000000
      cmp eax,ecx
      jb 0x40116B
      
    • JNB跳转(CF=0)

      mov eax,0
      test eax,eax
      jnb 0x40118a
      
    • JBE跳转(CF=1 or ZF = 1)

      mov eax,0x11111111
      mov ecx,0x22222222
      cmp eax,ecx
      jbe 0x4011b3
      
      mov eax,0
      test eax,eax
      jbe 0x4011c2
      
    • JA跳转(CF=0 and ZF=0)

      mov eax,0x20000000
      mov ecx,0x10000000
      cmp eax,ecx
      JA 0x4011D3
      
    • JL跳转(OF≠SF)

      mov eax,0xaaaaaaaa
      mov ecx,0x11111111
      cmp eax,ecx
      jl 0x4011ea
      

      记忆的话直接记住jmp if less,有符号数前操作数小于后操作数即可成立,两个正数就是小的在前,大的在后;两个负数就是小的在前(按照左半球的负数的排列,其实二进制值小的,表示的负数也是小的,以为ff总是最大的那个负整数-1,比它小的负数二进制表示出来也比它小);一个负数一个正数,则负数在前,正数在后即可。OF≠SF也是这么推出来的

    • JGE跳转(OF=SF)

      mov eax,0x11111111
      mov ecx,0xaaaaaaaa
      cmp eax,ecx
      jnl 0x4011f9
      

      OF=SF怎么想到的:因为无非就三种情况,正数减正数因为前大后小,则OF=0且SF=0;负数减负数因为前大后小,则OF=0且SF=0;正数减负数(因为前大后小),则溢出的时候,此时SF也=1,不溢出的时候,此时SF也等于0。所有情况中OF都等于SF

    • JLE跳转(OF≠SF or ZF=1)

      mov eax,0xaaaaaaaa
      mov ecx,0x11111111
      cmp eax,ecx
      jle 0x40120a
      
      mov eax,0
      test eax,eax
      jle 0x40121b
      
    • JG跳转(OF=SF and ZF=0)

      mov eax,0x11111111
      mov eax,0xaaaaaaaa
      cmp eax,ecx
      jg 0x40122a
      

九、记忆技巧

虽然可以推出来这些有条件的跳转指令(JCC)的跳转条件是什么,但是如果逆向的时候还要现推就很费时间

  • 先根据这些JCC的指令的缩写退出它的英文全称,就可以知道它满足什么条件才会跳转,比如JNL就是jmp if not less,表示如果上面一条cmp指令的前操作数不小于后操作数,即前操作数大于等于后操作数,则跳转。这个其实是推c语言if等判断语句,就是可以推出来c语言究竟在比较什么,但是如果我们想直接改变这些JCC指令的跳转与否,本质上是修改标志寄存器的某些关键位,通过直接修改这条JCC指令相关的标志寄存器某位来达到是否让它跳转的效果
  • 所以此时我们就要记忆每一个JCC指令是哪些标志寄存器位影响的:前面几个JCC指令都好记,比较复杂的是后面四个有符号的JCC指令
    • JL和JGE,一个小于,一个大于等于,刚好加起来是全集,所以这两个一起记:JL的跳转条件是OF≠SF,那么JGE的跳转条件就是OF=SF
    • 接着因为JL记住了,所以JLE就记住了:加一个或等于0的条件即可,所以为OF≠SF or ZF=1
    • 接着因为JGE记住了,所以JG就记住了:加一个不等于0的条件即可,所以为OF=SF and ZF=0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值