欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

三 开始和结束

三 开始和结束

三 开始和结束
    EB:imm8 --- near jmp
    Where commands start and end .why 2 + 2 = 3
    虽然这部分基于一些非常简单的事实,但并不是每一个程序员都能看到这些事实背后的本质.所以我们最好不要太过于轻视它.
    用OllyDbg 加载上次编写好的“Nop”程序.看着左列的指令地址,起始的指令地址是多少,在我的程序里是:
  00401000>  90  nop
    再看看右边的寄存器窗口,EIP的值又是什么,在我的程序里是
        EIP  00401000
    EIP寄存器指向内存中某个字节的地址,这个地址将被认为是某条指令的开始地址.当内存中的字节未被处理器加载并解码,还不知道这条指令到何处结束.
    现在我们按F8执行,可以看到
  00401001  90  nop
  EIP:    00401001
    EIP指向了内存中下一个字节并把它当作是指令.这里的下一个字节是指紧接上一条指令的结束地址的那个字节.如果你认为我没有写“EIP指向下一条指令”是因为我糟糕的英语,那你就错了.(尽管你并不是错在我糟糕的英语^_^(原作者是俄罗斯人)).
    如果一切正常的话,这些字节将被解码然后被当作下一条指令执行.事实上这些字节可能仅仅是一些垃圾,而处理器则不能正确的对其解码而产生异常.
    在程序的当前代码行(高亮显示的那一行)输入OpCode
   FFFF
    OllyDbg不会把它当作是一条指令,你将在助记符那一栏看到 “???” .同时EIP指向我们刚刚输入的FFFFh.所以如果不管这里的垃圾(对于处理器来说是“非法指令”)的话,处理器将解码并执行它.我们按下F8键,可以看到提示的错误信息(在OllyDbg下方的状态栏),处理器产生了异常.相应的,OllyDbg中断并询问如何继续.
    我们将FFFFh 替换为 9090h,然后输入不同的助记符,比如长指令或者使用lea指令,单步执行并仔细观察EIP 和指令长度的变化.你将看到处理器识别出指令的尺寸,且EIP现在指向紧接着刚输入的指令即nop.
    处理器是如何做到这个的呢?
    事实上处理器是通过解码来正确识别指令的.通过分析指令格式可以知道组成指令的域以及域的大小,不同的位指示了某个大小的域被使用.
    例如:短跳转指令EB: imm8 为两个字节,EB 是CODE域,当EIP指向的内容是EB 09,处理器通过解码第一个字节将得知EB 的指令长度是2个字节.
    所以问题的一个初步回答是:
        1. 开始:处理器认为当前EIP指向的内存单元中的第一个字节就是指令的开始.
        2. 结束:处理器通过对OpCode进行解码(大多数情况是根据[ code ]域),从而得知哪里是结束
        3. 如果指令不是“控制类型“的,那么下一条指令的开始将紧接当前指令的末尾.
    注意:除非被处理器加载,处理器是没有办法确定这些内容是否是真正的合法指令.
    现在回顾一下第三点,什么是“控制类型“指令?一般来说它们是ret,call,jmp,jcc,int等.它们之间的共同点是什么呢?不要回答我:”它们都跳转到某个地址“.或者更加糟糕的回答:”它们跳转到另外某条指令开始的地方“.
    我们在程序的当前行输入EB 00,并按F8执行.它将跳到哪里?我们看到处理器停在下一条指令开始的地方.我们再输入EB FE,运行,但是并没有跳转到任何地方,就像所有的事情都停止响应一样.就算是普通的指令执行后,也会步向下一条指令,而现在却既不跳转也不步进到下一条指令.
    真正的底层程序员应该理解指令的本质,而不单单从指令的字面意义上去理解它.真正的底层程序员不会说cmp指令比较操作数;他会说cmd指令是用第一个操作数减去第二个操作数,由此来设置相应的标志位.同时,我们关心的只是标志位,并不关心键操作后的结果,所以不需要把减操作的结果储存到第一个操作数中.这才是cmp指令的本质.
    如果知道了它是如何工作的,你就可以用cmp指令来做任何它擅长的事情,而不仅仅是比较.其实并没有什么cmp指令,有的只是简单的逻辑的,数学和数据转换\写入\读取的OpCode.
    让我们回到“控制指令”.它们真正做了什么样的操作呢?答案是:它们改变EIP寄存器.
    我们知道EIP指向的内存数据被处理器当作是指令的开始.
    通过这些控制指令我们又可以将什么值赋给EIP呢?
    答案是:如果你非常了解OpCode和这些指令的算法,那么可以是任何32位的值.所以我们可以强迫处理器把内存中任意某个字节当作指令的开始.
    输入OpCode  
        04 AC
    你将看到
        00401008  04 AC  ADD AL,0AC
    我们也知道ACh是lodsb的OpCode,地址00401008是OpCode 04 AC的起始地址,但是被地址00401009指向的指令是什么呢?如果我们使EIP指向00401009,那么处理器将不会把ACh当作是04:imm8的操作数,而是把它看做一个OpCode  ACh->lodsb?
    现在如果我们需要实现一个算法
  IF  ZF = 0
    Add  al,ACh
  ELSE
    Lodsb
    请试着用助记符写下这个算法.
    请看下面的汇编代码:
  Jne  $+1
  Add  al,ACh
    这就是我们所需要的.请不要试着运行它,除非你清楚的知道[esi]指向的地方是什么内容.
  如果ZF = 1,EIP将指向指令add  al,ACh的第二个字节,因为我们知道第二个字节的值是ACh,而操作码是ACh ->lodsb,我们实现这个算法仅用了4个字节!!!
  00401005  75 01      JNZ      SHORT
  00401007  04 AC      ADD      AL,0AC
    我们一起来看看各个字节都表示什么意思.
    75:imm 是75 01 的域格式,75是JNZ的OpCode,imm在这里是01,会被加到EIP里面去,整个7501表示:如果这条指令被执行了,则EIP会指向下一条指令的第二个字节.04 AC的域格式:04:imm,04->OpCode,AC->imm.两条指令都有两个域,格式为:,[ code ] [ imm ].
    如果ZF = 0,7501这条指令就会把EIP中下一条指令的起始地址 + 1,使得EIP指向下一条指令的第二个字节,处理器将认为AC所在的地址才是下一条指令的开始,这时AC将被认为是一个新的OpCode.
    否则EIP将指向04 AC所在的地址,开始的字节是04h,处理器将认出格式域:04:imm8(add  al,imm8),这时AC将被当成操作数,而不是操作码.
    现在我们看看谁是最快最聪明的程序员.
    1. 实现一个4字节的算法:
        IF    ZF = 1
    Inc  eax
        ELSE
    Mov  al,40h
    2. 另一个测试(5字节):
        IF    PF = 1
    Set all bits in  ax to 0
        ELSE
    Set all  bits in  eax to 0
    3. 再一个测试(5字节):
        IF    PF = 1
    Set all bits in  eax to 1
        ELSE
    Set all  bits in  ax to 1
    (Try it yourself)
    (1 提示:  je  $ + 1
    Mov  al,40h
                74h,01h,B0h,40h  )
    (2 提示:  jpe  $ + 3
    Xor  eax,eax
                74h,01h,66h,31h,C0h)
原文第三帖

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功