第041课 80x86寻址方式与函数

来源:互联网 发布:迪优美特网络机顶盒x7 编辑:程序博客网 时间:2024/06/06 15:43
 第041课   80x86寻址方式与函数
​1. 指令1 -- 组成
                                                  操作码     操作数
指令由操作码和操作数两部分组成
操作码说明计算机要执行哪种操作,如传送、运算、移位、跳转等操作,它是指令中不可缺少的组成部分
操作数是指令执行的参与者,即各种操作的对象
有些指令不需要操作数,通常的指令都是有一个或两个操作数,也有个别指令由3个甚至4个操作数

​1. 指令2 -- 操作码和操作数
各种指令的操作码:
用一个唯一的助记符表示(指令功能的英文缩写)
对应着机器指令的一个二进制码
指令中的操作数:
可以是一个具体的数值
可以是存放数据的寄存器
或指明数据在主存位置的存储器地址
                                             MOV          AX,                12
                                           操作码   目的操作数      源操作数
一般左手: 目的操作数
一般右手: 源操作数

mov AX, 12          // 一个机器码
mov BX, 12          //另一个机器码

​1. 指令3 -- 指令的助记符格式
                               操作码  操作数1, 操作数2 ; 注释
操作数2, 称为源操作数src,它表示参与指令操作的一个对象
操作数1,  称为目的操作数dest,它不仅可以作为指令操作的一个对象,还可以用来存放指令操作的结果
分号后的内容是对指令的解释
例如:
                         mov dest, src ;  dest  ---> src
 mov指令的功能时将源操作数src传送至目的操作数dest
例如:
                         mov AL, 05     ;    AL  <--- 05
                         mov BL, AX     ;    BX  <--- AX    

​1. 指令4 -- MOV指令动画原理

​1. 指令5 -- 指令操作数的表达
r8      :   任意一个8位通用寄存器
              AH  AL  BH  BL  CH  CL  DH  DL
r16    :   任意一个16位通用寄存器
              AX   BX  CX  DX  SI   DI   BP  SP
reg    :   代表r8或r16(32位环境下代表r8或r16或r32,64位同)
seg    :   段寄存器 CS/DS/ES/SS
m8    :   一个8位寄存器操作数单元(所有主存寻址方式)
m16  :   一个16位寄存器操作数单元(所有主存寻址方式)
mem :   代表m8或m16(32位环境下代表m8或m16或m32,64位同)
i8       :   一个8位立即数(C中一个整型常量)
i16     :   一个16位立即数
imm   :  代表i8或i16(32位环境下代表i8或i16或i32,64位同)
dest   :  目的操作数
src      :  源操作数




鲁鹏飞 2014/9/28 22:22:31

1. 指令6 -- 汇编语言的多种表达方式

同一寻址方式可以写成不同的形式:
MOV AX, [BX][SI]
             ; 等同于 MOV AX, [BX + SI]
MOV AX, 12[SI]
            ;  等同于 MOV AX, [SI + 12]               ----> 推荐写法
MOV AX, 12[BX][SI]
            ;  等同于MOV AX, 12[BX + SI]
            ;  等同于MOV AX, [BX + SI + 12]        ----> 推荐写法

​2. 8086的寻址方式1 -- 学习方法

从8086的机器代码格式入手, 论述:
立即数寻址方式
寄存器寻址方式
存储器寻址方式

进而熟悉8086汇编语言指令格式,尤其是其中操作数的表达方法;为展开8086指令系统做好准备

​2. 8086的寻址方式2 -- 立即数寻址

指令中的操作数之间存放在机器代码中,紧跟在操作码之后(操作数作为指令的一部分存放在操作码之后的主存单元中)
这种操作数被称为立即数imm
它可以是8位数值i8(00~FF)
也可以是16位数值i16(0000~FFFF)
立即数寻址方式常用来给寄存器赋值
 MOV AL, 34h      ; 34为立即数寻址,在代码段里面
 opcode: B0 34
10100    B0
10101    34
  CS:1000
  IP   : 100
CS*10h + IP = 10100处读字节运行



---------------------------------------------------------------------------------------------

指令字节缓冲器
送去cpu执行时,是一个字节一个字节执行的
cpu直接交互  <----> 一级缓存  <---> 二级缓存 <---> 三级缓存
代码不是一个数,没有必要根据大、小尾方式反着读
Intel精妙之处,google查阅opcode hack
通过修改有效的几个字节完成复杂的功能
人为制造截断,截断后指令完成了新功能,看似是错误代码,实际是被精心构造的代码
内存hook, 指令引擎
返回指令长度,inlinehook
MOV AL, B0 ;这种情况,指令截断时,将B0解释成MOV AL, XX解释了
总结可重复利用的规则
不断挖掘新规则,可利用的规则越多,故意向这些规则倾斜

处理器 无法区分指令和数据
如果10100执行的是图片数据,将IP指向其,CPU都会尝试执行

MOV AX, 1234
先移动34到内部暂存器,在移动12到内部暂存器
再整合,做加法运算

只能区分一条指令内,哪是代码哪是数据

让小孩分辨男人、女人,要告诉他哪是男哪是女
计算机也一样,先告诉它B0 34是一条指令,计算机才能识别出B0是代码,34是数据

CS和IP搭配,从代码段取机器码,执行
SS和SP搭配,从堆栈取数据
----------------------------------------------------------------------------------------------------------

​2. 8086的寻址方式3 -- 寄存器寻址

操作数存放在CPU的内部寄存器reg中,可以是:
8位寄存器r8:
AH、AL、BH、BL、CH、CL、DH、DL
16位寄存器r16:
AX、BX、CX、DX、SI、DI、BP、SP
4个段寄存器seg:
CS、DS、SS、ES

MOV BX, AX(以源操作数为准说明寻址方式)
MOV后, AX里的内容不变,和一般的移动理解不同
MOV ds, ax
10100 8E   MOV DS,
10101 D8  AX

MOV DS, CX    8ED9          
MOV DS, BX    8EDB
MOV DS, DX    8EDA
D9,DB,DA表示源操作数代表哪个寄存器

​2. 8086的寻址方式4 -- 存储器寻址方式

指令中给出了操作数的主存地址信息(偏移地址,称之为有效地址EA),
而段地址在默认的或用段超越前缀指定的段寄存器中

8086设计了多种寄存器寻址方式
直接寻址方式
寄存器间接寻址方式
寄存器相对寻址方式
基址变址寻址方式
相对基址变址寻址方式

​2. 8086的寻址方式5 -- 直接寻址

有效地址在指令中直接给出
默认的段地址在DS段寄存器,可使用段超越前缀改变
MOV AX, [2000H]
; AX <-- DS:[2000H]
; 指令OPCode: A10020
MOV AX, ES: [2000H]
; AX <-- ES:[2000H]
; 指令OPCode: A10020

​2. 8086的寻址方式6 -- 寄存器间接寻址

有效地址存放在基址寄存器BX或变址寄存器SI、DI中
默认的段地址在DS段寄存器,可使用段超越前缀改变
MOV AX, [SI]  ; AX <--- DS:[SI]

MOV AX, [BX]
10100 8B
10101 07

用途:
call eax;                //虚函数调用方式
3环 --> 0环          //系统服务调度表,大数组, 某个函数调用 

鲁鹏飞 2014/9/28 22:22:42

2. 8086的寻址方式7 -- 寄存器相对寻址(加一偏移)

有效地址是寄存器内容与有符号8位或16位位移量之后,寄存器可以是BX、BP或SI、DI
有效地址 = BX/BP/SI/DI + 8/16位位移量
段地址对应BX/SI/DI寄存器默认是DS,对应BP寄存器默认是SS,可用短超越前缀改变
例如:
MOV AX, [DI + 06]   ; AX <---DS:[DI+06]
MOV AX, [BP + 06]  ; AX <---SS:[BP+06]
用于寻址结构体成员,BX/BP/SI/DI指向首结构体首地址,偏移为跳过的成员大小

MOV AX, 1000[SI]                 ;SI内为1000
10100 8B                                 ; MOV AX, 
10101 84                                 ; [SI]       
10102 00                                 ; 1000的00
10103 10                                 ; 1000的10

计算SI+1000时会用到ALU( 逻辑算术单元)

​2. 8086的寻址方式8 -- 基址变址寻址(俩寄存器)

有效地址由基址寄存器(BX或BP)的内容加上变址寄存器(SI或DI)的内容构成:
有效地址 = BX/BP + SI/DI
段地址对应BX基址寄存器默认是DS,对应BP基址寄存器默认是SS,可用段超越前缀改变
例子:
MOV AX, [BX + SI]        ;  AX  <-- DS:[BX + SI]
MOV AX, [BP + DI]        ;  AX <-- SS:[BP + DI]
MOV AX, DS:[BP + DI]  ;  AX <-- DS:[BP + DI]

MOV AL, [BX][SI]             ; BX: 1500, SI: 500
10100 8B
10101 00
-------------------------------------------------------------
反汇编引擎,不用写商业代码
某一指令的所有操作数都解析出来,就可以了
其他指令也就会了
--------------------------------------------------------------

​2. 8086的寻址方式9 -- 相对基址变址寻址(俩寄存器加一偏移量)

有效地址是基址寄存器(BX/BP)、变址寄存器(SI/DI)与一个8位或16位位移量之和:
有效地址 = BX/BP + SI/DI + 8/16位位移量
段地址对应BX基址寄存器默认是DS, 对应BP基址寄存器默认是SS, 可用段超越前缀改变
例子:
MOV AX, [BS + SI + 06H]   ; AX  <--- DS:[BX + SI + 06H]

MOV AL, 1000[BX][SI]
10100 8B
10101 80
10102 00
           10

以上例子中,所有8B机器码, 代表mov al或mov ax或mov eax

3. 栈1 --- 介绍

堆栈是一个"后进先出LIFO"(或说"先进后出FILO")的主存区域,位于堆栈段中,SS段寄存器记录其段地址
堆栈只有一个出口,即当前栈顶; 用堆栈指针寄存器SP指定,栈顶是地址较小的一端(低端),栈底不变


​push ebp              //保存旧栈底

mov  ebp, esp      //打开栈帧
sub   esp,0c          //开辟空间,抬高栈顶(由高到低增长)

函数分析示例:
Add(nNum1, nNum2, nNum3)  //3个数相加求和
0x002000E4  0000 0003  <---  Local_3
0x002000E8  0000 0002  <---  Local_2
0x002000EC  0000 0001  <---  Local_1
0x002000F0      旧EBP       <---  EBP = 0x002000F0 (mov ebp,esp)
0x002000F4   ?????????    <---  返回地址
0x002000F8   00001111   <---  Parm_1
0x002000FC   00002222   <---  Parm_2
0x00200100   00003333   <---  Parm_3


栈帧: 从EBP到ESP之间, 属于函数内部的东西

​push ebp              

mov  ebp, esp
debug版有,release没有了

栈只保存这三种数据: 函数参数、局部变量、返回地址

栈溢出,到栈里面运行
stdcall,是这种模式的调用

push ebx          //保存要使用的寄存器的原值
push esi
push edi

堆栈开辟一大片空间,填充成0xcc(即int 3断点),相当于下一大片int3断点。形成错误着陆区

lea edi, [ebp-0C0]
mov ecx,30
mov eax,CCCCCCCC
rep stos dword ptr [edi]

mov eax, dword prt [ebp + 8]              //第一个参数
add  eax, dword prt [ebp + 0C]           //第二个参数
add  eax, dword prt [ebp + 10]           //第三个参数


pop edi                                                   //恢复寄存器值
pop esi
pop ebx

恢复sub开辟的栈空间:
1.可以add esp, 0xC0

2.由于将以前的esp保存到了ebp中,所有直接用ebp恢复esp也行
mov esp,ebp       

pop ebp                                                   //恢复旧ebp       

汇编默认规定,将返回值保存在eax里面
dword ptr : 双字类型的指针,ptr 即 pointer缩写              

​debug模式下,各个参数之间添加0xCCCCCCCC,方便出错时调试

00000003
CCCCCCCC
00000002
CCCCCCCC
00000001
CCCCCCCC
原EBP
返回地址

鲁鹏飞 2014/9/28 22:22:52

交换两个数的值的另一种方法:
push eax
push ecx
pop eax
pop ecx

堆栈平衡
人为构造不平衡
push eax
push ecx
push 123123
pop ecx
pop ecx

内核中,有个专门的线程,在系统空闲时,将栈内垃圾值置0


实验一:
将Add函数的默认开辟栈空间大小由0xE4, 改为0xA1


int Add(int nNumA, int nNumB, int nNumC)
{
   int nNA = 1;
   int nNB = 2;
   int nNC = 3;
   return nNumA + nNumB + nNumC + nNA + nNB + nNC;
}
int _tmain(int argc, _TCHAR* argv[])
{
   int nNum = Add(0x1111, 0x2222, 0x3333);
   return 0;}  


步骤1:
查找main函数,在C运行时库里找,enter进入



步骤2:

找到main函数,enter进入



步骤3:

找到Add函数,enter进入



步骤4:

记录sub指令地址, 0x013A2363



步骤4:

查看Add模块基址0x0139000



步骤5:

计算相对虚地址(Relative Virtual Addresses): 0x013A2363 - 0x0139000 = 0x12363
LoadPE计算文件偏移0x1763



步骤6:

010edit打开Add.exe, cltr+g,1763


找到数据81 EC E4,将E4修改成A1



步骤7:

OD重新打开后,开辟栈空间大小即0x0A1




实验二:

OD内写一个两个数的加法函数,并调用它




实验三:

修改一个程序,使其在运行前弹出对话框
0 0