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