寄存器基础
寄存器是CPU内部的存储设备
32位通用寄存器
寄存器 | 主要用途 | 编号 | 存储数据范围 |
---|---|---|---|
EAX | 累加器 | 0 | 0-0xFFFFFFFF |
ECX | 计数 | 1 | 0-0xFFFFFFFF |
EDX | I/O指针 | 2 | 0-0xFFFFFFFF |
EBX | DS段的数据指针 | 3 | 0-0xFFFFFFFF |
ESP | 堆栈指针 | 4 | 0-0xFFFFFFFF |
EBP | SS段的数据指针 | 5 | 0-0xFFFFFFFF |
ESI | 字符串操作的源指针;SS段的数据指针 | 6 | 0-0xFFFFFFFF |
EDI | 字符串操作的目标指针;ES段的数据指针 | 7 | 0-0xFFFFFFFF |
解释:32位因此是8个F
ollydbg快捷键
按键 | 功能 |
---|---|
F3 | 打开文件 |
F7 | 单步执行,进入函数 |
F8 | 单步执行,不进入函数 |
16位通用寄存器
16位通用寄存器 |
---|
AX |
CX |
DX |
BX |
SP |
BP |
SI |
DI |
其实16位寄存器就是把32位砍一半
8位通用寄存器
8位通用寄存器 |
---|
AH |
CH |
DH |
BH |
AL |
CL |
DL |
BL |
其实就是把16位的从中间砍一半,一半是H,一半是L
汇编指令基础
1.MOV(赋值)
- mov eax,0x1
- 操作码 目标操作数 源操作数
- 将0x1值存入eax寄存器
目标操作数及源操作数可以使用如下内容 - r:通用寄存器 - m:内存 - imm:立即数
有如下规则: - 操作数宽度必须一样 - 大多数汇编指令中源操作数和目标操作数不能同时是内存单元
2.ADD(加法)
- add eax,0x1
- 给eax累加0x1
3.SUB(减法)
- sub eax,0x2
- 给eax值减去0x2
4.AND(与运算)
- AND eax,0x2
- 让eax和0x2做与运算并赋值给eax
5.OR(或运算)
- OR eax,0x2
- 让eax和0x2做或运算并赋值给eax
6.XOR(异或运算)
- XOR eax,0x2
- 让eax和0x2做异或运算并赋值给eax
7.NOT(非运算)
- NOT eax
- 对eax取反并赋值给eax
8.LEA(取地址编号)
LEA EAX,DWORD PTR DS:[0X12FFC4]
9.ADC(带进位加法)
两边不能同时为内存,数据宽度要一样 如果此时CF标志位为1则加法时会将CF值累加进去 mov al,0x01 mov cl,0x01 adc al,cl 在cf为1时结果为3,cf为0时结果为2
10.SBB(带借位减法)
跟adc一样,如果c为1多减1
11.XCHG(数据交换)
不能同时为内存,数据宽度要相同 值互换,因此不能有立即数
12.MOVS(移动数据)
内存到内存 movs dword ptr ds:[edi],dword ptr ds:[esi] 上面的代码等价于 movsd(movsw,movsd),移动之后esi和edi的值会各加4或者减4
13.STOS(将AL/AX/EAX值存储到[edi]指定的内存单元)
STOS BYTE ptr es:[edi] 存储完后edi会按照d位决定加减,按指定的数据宽度决定加/减多数
14.REP(按照ECX中指定的次数重复执行命令字符串指令)
mov ecx,0x10; rep movsd; 重复执行16次movsd
15.jmp
jmp 0x4183EF 修改eip(决定了cpu下一次执行的汇编代码地址)为0x4183Ef的代码。
16.call
call 0x4183EF 修改eip为0x4183EF,并且在栈里压入返回地址(一般就是call指令下一行指令的地址)。
17.ret
相当于pop eip
18.jle
小于等于跳转
内存
计算机中常用计量单位
- BYTE(字节) = 8bit
- WORD (字)= 16bit
- DWORD(双字)= 32bit
19.movsx
先符号扩展,再传送
mov AL 0xFF MOVSX CX,AL
因为0xFF符号位为1,所以扩展按照符号位扩展全1然后传送,因此 CX=0xFFFF
20.movzx
先0扩展再传送
内存的寻址
1.内存的性质
- 内存通过编号寻址,每一个地址代表一个内存块,内存是以字节为单位的。
- 因此32位操作系统最大寻址空间是4GB
2.内存地址的表示方法
[0x12345678] 通过方括号包裹一个立即数的方式表示一块内存的地址
3.写内存
mov dword ptr ds:[0x12345678],0x12345678
解释如下: - dword指定写入多少个字节,可以单位为byte,word,dword - ptr代表后面这是一个指针,一个地址,不是一个普通的值 - ds表示段寄存器 - [0x12345678]表示内存的地址标号 - 0x12345678表示要写入的数据,此处八个16进制位,正好是一个dword
4.从内存中读
mov EAX,dword ptr ds:[0x12345678] 把内存地址位0x12345678的块读一个dword长度存入EAX寄存器
注: - 实验时可以使用ollydbg中写入esp寄存器当中所存的内存地址
5.OD查看数据命令
db 0x12FFDC
d(查看数据 )b(byte)
6.内存寻址方式
- [立即数]
- [REG] (REG表示寄存器)
- [reg(+/*-)立即数]
- [REG+REG*(1,2,4,8)]
- [REG+REG*(1,2,4,8)+立即数]
堆栈
实现一个堆栈
压入数据
mov ebx,0x12ffe0(制定栈底) mov edx,0x12ffe0(指定栈顶) mov dword ptr ds:[edx-4],aaaaaaaa(存入一个数据) sub edx,4(栈顶减4)
注: intel一般栈底是高地址,栈底是低地址。 一般用ebp存栈底,esp存栈顶
取值
mov esi,dword ptr ds:[ebx-4]
堆栈操作指令
压栈
push 0x12345678 相当于栈顶提升,然后存入一个数据
push是可以直接push内存的 push可以32,16位数据,不能是8位
出栈
pop eax 相当于栈顶值存入eax,然后栈顶降低
额外指令
pushad 把八个reg内容全部存栈里 popad 把寄存器内容从栈里恢复回去
标志寄存器
CF(carry flag)进位标志(无符号运算)
用来判断最高位是否会进位,例如: mov al,0xFF add al 此时cf变成1
PF(parity flag)奇偶标志位
判断运算结果中最低有效字节(一般就是最后八位)中1的个数,1的个数为偶数则为1,例如: mov al,3 al表示为2进制为00000011 此时p位为1
AF(Auxiliary Carry flag)辅助进位标志位
数据宽度一半的位有没有进位,如: mov eax 0x0000FFFF add eax,1 此时AF为1
ZF(zero flag)零标志位
反应运算结果是否为0,如: mov al,0x02 sub al,0x2 或者xor eax,eax
SF(sign flag)符号标志位
计算结果的最高位
OF(overflow flag)溢出标志位(有符号运算)
- 正数和负数相加永远不会溢出
- 正数+正数=负数 溢出
- 负数+负数=正数 溢出
DF(direction flag)方向标志位
决定movs后esi和edi加的方向,如果是1就减,如果是0就加
C语言基础补充
裸函数
void __declspec(naked) Function(){ }
编译器不会替你生成一堆乱七八糟的东西 裸函数当中可以直接写汇编代码,如下:
void __declspec(naked) Function(){ __asm{ ret } }
使用裸函数实现一个加法求和
#include "stdafx.h" int __declspec(naked) plus(int x,int y,int z){ __asm{ push ebp mov ebp,esp sub esp,0x40 push ebx push esi push edi mov ecx,0x10 mov eax,0xcccccccc lea edi,dword ptr ds:[ebp-0x40] rep stosd //stosd:将eax的内容复制到edi的内存空间,复制四个字节,并将edi加4个字节 mov eax,dword ptr ds:[ebp+0x8] add eax,dword ptr ds:[ebp+0xC] add eax,dword ptr ds:[ebp+0x10] pop edi pop esi pop ebx mov esp,ebp pop ebp ret } } int main(int argc, char* argv[]) { printf("%d",plus(1,2,3)); return 0; }
调用约定
调用约定 | 参数压栈顺序 | 平衡堆栈 |
---|---|---|
__cdecl | (从右至左入栈) | 调用者清理栈(外平栈) |
__stdcall | (从右至左入栈) | 自身清理栈(内平栈) |
__fastcall | (ECX/EDX传前两个,剩下的从右至左入栈) | 自身清理栈(内平栈) |
如需指定,在函数声明时函数名前加调用约定名即可,c/c++默认用的__cdecl。
数据类型
- 基本类型
- 构造类型
- 指针类型
- 空类型
字节对齐
#pragma pack(n) 结构体。。。 #pragma pack()
上面n的单位是字节。
typedef
为现有类型定义新的名字
typedef unsigned char word
typedef struct student{ int x; char arr[10]; }stu;