16汇编第十讲完结Call变为函数以及指令的最后讲解

2018-01-05 18:01:10 浏览数 (1)

16汇编完结Call变为函数以及指令的最后讲解

学了10天的16位汇编,这一讲就结束了,这里总结一下昨天的LOOP指令的缺陷,因为lOOP指令的缺陷,所以我们都改为下面的汇编代码使用了,自己去写,其中条件是你自己写的

请看汇编代码:

代码语言:javascript复制
do while 的汇编代码
 WHILE:
       mov ax,ax
       cmp ax, 10
       jl WHILE
代码语言:javascript复制
while 的汇编代码

 WHILE:
    cmp ax, 10
     jge WHILE_END
    mov ax,ax
     jmp WHILE
WHILE_END:

一丶Call指令(子程序)变为函数调用(重要,这个以后逆向会天天看到所以一定掌握)

首先我们明白一点,昨天我们写的只是一个单独的子程序(什么保存栈地址,开辟局部变量空间,....都没有写,只是单独的平栈),今天我们就写一个Call变为函数调用的例子,一步一步的看为什么这么做,对以后的逆向很有帮助

1.首先我们明白Call调用的几种方式

段内:   一个段中去调用,或者跳转

段间:   代码不在同一个段中,从一个段跳跃到另一个段

Call指令分为4中类型 (类似于JMP)

Call  label(标号) ;                                                       段内调用丶直接寻址

Call r16/m16 (16位寄存器,或者2字节内存)                段内调用丶间接选址

Call  far ptr label;                 段间调用丶直接寻址

Call  far ptr mem;                                                        段间调用丶间接寻址

昨天我们写的简单的子程序例子:

代码语言:javascript复制
mov ax,1
push ax
mov bx,2
push bx
Call   标号
add sp,4....
标号:
    mov bp,sp   mov ax,[bp  2]   mov bx,[bp  4]   add ax,bx
    ret

我们压入了两个参数,一个Ax,一个Bx,在Call的时候,会把下一行的地址压入到栈中,也就是 add sp,4的所在的地址

我们画一下栈

可以看出,这个就是当前的栈结构了, 我们执行代码的时候,首先 bp 和 sp是平等的了

现在 bp 2  能寻得bx, bp 4 能寻得 ax

然后ret  注意,ret只是把sp 2了,也就是弹栈弹出了返回地址,并且给IP了,现在IP就会跳转到下一条指令执行的位置

也就是(add sp,4)  现在要注意了,bx 和 ax 都还在栈中,我们没办法让栈恢复所以在外面用 add sp,4 让sp的位置(现在的位置 2了,已经在bx的位置了)变为栈底了

所以这个就是C语言的 C调用约定

如果我们想StdCall  (std 调用约定,标准的调用约定) 就要用retn 这个指令了,不能是ret了,

写成 retn  4  代表先把返回地址返回出去,然后再让sp 4 个字节,相当于在函数内部就平栈了,这样外部就不用写

add sp,4了,不用自己平栈了

 2.由Call 变为函数一步一步来

现在基于上面的原理我们知道如何平衡一个栈了,但是你有没有发现,为什么我们一开始要把sp 给Bp

也就是让 BP和SP一样的位置

这个是有原因的

首先C语言调用函数的时候会进行几步操作

1.保存栈底

2.申请局部变量空间

3.保存环境

4.恢复环境

5.释放局部变量空间

6.恢复栈底

7.弹栈返回

好看上面的我们可能有点不明白,下面我把整体的栈图放出来

整体的栈是这样的,这里为什么要一开始把bp和sp相等,是有原因的,我们不妨这样想,如果我们申请局部变量空间的

时候,是不是参数的偏移也要改动,这样每次都要自己计算偏移,相当麻烦,所以只能这样,

我们以后找参数就  bp xxx (因为bp一开始就在栈底的值这一栏)  这样就能寻找到参数,你开辟多大的局部变量看空间都和我没关系,所以以后我只需要 bp-xxx 就是找到的局部变量

现在看下汇编代码的模版吧

代码语言:javascript复制
MY_ADD:  ;stdcall    
    push bp
    mov bp, sp    ;1. 保存栈底
    
    sub sp, 10    ;2. 申请局部变量空间这里随你便,申请的时候先抬栈,让sp高一点,但是不会影响bp
    push bx       ;3.保存环境 保存环境的意思就是外面的寄存器的值要保存一下,这样恢复寄存器的值
                 
    mov word ptr [bp-4], 1        ;每次我只需要 bp -xxx 就知道寻找局部变量
    mov word ptr  [bp-2], 2
    mov ax, [bp 6]  ;参数1         ;每次我bp  xxx 就知道我是找参数,所以不会冲突,打死参数的地址不会变
    add ax, [bp 8]  ;参数2
    
                 ;xxxxxxx
    pop bx          ;4. 恢复环境      弹栈的时候寄存器信息先回复
    mov sp, bp     ;5. 释放局部变量空间  而且恢复变量控件的时候也很容易,直接把bp当前的位置给sp即可,释放空间了
    pop bp        ;恢复以前的栈底的值
    retf         ;6. 返回   ,retf下面详细讲

在这里主要是掌握bp所在的位置即可,就能明白为什么这样写了,不信的话自己写个程序,看下反汇编,大体的就是这个套路,这里讲解的是为什么这样做,不是和市面的汇编视频一样,你看到 bp -xxx 就知道他在访问局部变量就行

其实这个是错误的,我们要知其然,并知其所以然

看下栈图,掌握bp所在的位置

只要掌握bp所在的位置即可,上面的代码即可明白

 3.Call指令的retf段间转移

这个我们首先要明白,在Call的时候会把Call下边一条指令的地址保存到栈中,出栈的时候要给IP,让其更改跳转,

跳转到Call下一条指令执行的位置的地方

但是现在我们是段间Call,也就是不在一个段中,这个时候栈不光会保存返回地址,还会保存当前CS段寄存器的地址

这样返回的时候  CS:IP返回,但是现在有一个问题,就是我们自己根本就平不了栈,我们把IP拿出来了,给IP,CS段寄存器根本没办法改,这样我们必须同时修改CS:IP的值才能回到以前的地方,但是现在没办法了,因为你改IP回跳,改CS会跳,必须同时改,弄不了,所以弄一个retf的指令去帮我们去做

注意retf 你也需要平栈,比如我们压入了两个参数,就要 retf 4, retf会默认把栈顶4个字节的数据取出来分别给 ip和CS段寄存器,但是剩下4个字节都是我们的参数,比如自己去释放,让SP的加4到栈底才可以

二丶中断指令

1.什么是中断指令

中断,是有一种改变程序执行顺序的方法

中断具有很多的中断类型

中断的指令有3条

  1.INT i8(i8代表一个八位的立即数)

  2.IRET  IRET 和Call差不对,Call的ret返回的时候会把栈顶的元素弹出两个字节,这两个字节是返回地址,所以可以回到正确的地方执行指令,但是IRET明显比ret保存的东西多,其中ret我们可以手工的pop和jmp去执行,IRET也可以自己去做,但是你要完整的模拟才可以,一般还是调用IRET即可

  3.INTO

2.中断指令的自我理解

  其实中断指令就是调用硬件提供的API(也称为系统调用)我们前边用过很多次了

比如显示一个字符串

代码语言:javascript复制
mov ah,09h
int 21h

其中参数是09,int 21h代表执行,还有很多

介绍下指令

  INT I8:  中断的调用指令: 产生I8号中断,就是调用int代表我要调用了,其中指令是什么使我们给的,是一个八位立即数比如 09

  IRET:  中断的返回指令,理解为返回,可以进行下一条指令的执行

  INTO:  不常用,不讲解.

3.21h中断,到底是个啥玩意

我们每次都调用21h什么的,但是不知道他是个啥玩意

他是DOS提供给用户的,用于调用系统功能的中断(简单理解就是DOS提供的API,让用户调用),他有近百个功能让公户选择使用.包括设备管理,目录管理,和文件管理

ROM-BIOS(主板的BIOS)也是这种形式,这就是为什么程序一开机显示器就会显示字符,别忘了这个时候系统还没有启动,还没有操作系统一说

看下图:

现在操作系统也还是这样调用,操作系统的API很多,底层就依赖于这256个中断,只不过操作系统可能处理的方式更多,比如根据AH的值,调用一个函数指针,也就是一个函数的地址,这个函数地址里面又有很多封装,慢慢的操作系统API就越来越多.这些都是我们不关心的

具体的中断,可以看下第一课所有的课堂资料中的指令字典,寻找一下中断号自己去调用

比如判断按键的汇编例子

代码语言:javascript复制
getkey:    mov ah,01h    ;功能号:ah←01h
    int 21h              ;功能调用
    cmp al,’Y’           ;处理出口参数al
    je yeskey            ;是“Y”
    cmp al,’N’
    je nokey             ;是“N”
    jne getkey
    ...
yeskey:    ...
nokey:    ...

三丶最后的指令详解

LOCK指令  封锁总线,不让总线接受指令

这个常用与多线程的时候操作,多线程的操作中的同步对象的 自加锁就是这样实现的

比如:

代码语言:javascript复制
lock add word ptr[bp],3

当我们吧3给内存的时候,其余的程序也可能再往这里写数据,所以同步一下,这样就完成这一条指令,才可以进行下一条指令的操作

自减锁就是 把add 变为sub,交换锁就是 把指令变为 xchg lock只能同时处理一条指令,这是为防止我们把系统总线都锁死,这样操作系统就会崩溃,信息到不了,不过这个不是我们关心的

解锁是CPU自己解锁的,没有解锁指令

HLT 暂停指令

这个就比较简单的,我们电脑都有休眠功能,就是用的这个指令,让CPU功耗降低,不执行,以前是无限循环NOP指令

但是NOP也是一个指令也会有功耗,所以现在改为HLT指令了,执行了这个指令HLT不进行任何操作,当我们发送了一条指令过去之后,就会脱离暂停状态

就好比电脑挂机了,屏幕黑了,就是进入HLT了,我们点击鼠标或者键盘,发送了一条指令,接着就唤醒了

交权指令

ESC 6位立即数,reg/mem

我们都知道,以前算浮点数的时候都是CPU一个去做的,现在有了浮点处理器,也就是协处理器,专门算浮点的一个CPU

我们的CPU计算浮点数的时候,要把权力交给浮点处理器,这时候就称为交权,在这个时候CPU要等待浮点处理器返回的结果,期间一直等待

FADD FDIV 是浮点计算指令,就是我们计算指令前面加F就会计算浮点数了

浮点数有7个寄存器

ST -> st7 按照标号来的

浮点处理器的st不能和通用寄存器一样去使用,它是吧ST寄存器压入栈中,让前两个栈中的数据相加返回的

关于浮点处理,后面再说,这个不是16为汇编中使用的

WAIT 等待指令

这个就简单了,CPU交权后,就使用这个指令去等待浮点处理器返回结果

总结:

  16位汇编在我的博客上都精简了,但是你想搞明白就要多花点时间,细细品味汇编的内容,掌握汇编

过几天开始32位汇编的讲解,如果觉得好,请评论一下,关注一下,加个粉丝.支持一下,谢谢

对于Call变为函数的哪里,一定要掌握,不懂的可以去看下C语言的栈内存结构,或者看下它的汇编代码,一定搞明白

这个以后逆向的时候天天看.

学习资料: 链接:http://pan.baidu.com/s/1nuPNUFf 密码:x44c

0 人点赞