二进制安全-汇编基础
进制的定义
二进制:由两个符号组成,分别是0 、1
八进制:由八个符号组成,分别是0、1、2、3、4、5、6、7
十进制:由十个符号组成,分别是0、1、2、3、4、5、6、7、8、9
十六进制:由十六个符号组成,分别是0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F
度量单位:
1byte 字节 = 8 bit 比特 char
WORD = 2 BYTE = 16 bit short int
DWORD = 4 BYTE = 32 bit
QWORD = 8 BYTE = 64 bit
1kb = 1024 byte = 8192 bit
1mb = 1024 kb
1gb = 1024 mb
1tb = 1024 gb
有符号 | 无符号 | |
---|---|---|
byte | -128-127 | 0-255 |
word | -32768-32767 | 0-65535 |
DWORD | ||
qword |
16位汇编:实模式,16位处理器内的内部,最多可以处理存储的长度为16位。
32位汇编:保护模式,32位处理器内的内部,最多可以处理存储的长度为32位。
64位汇编:保护模式,64位处理器的内部,最多可以处理存储的长度位64位。
位数 | 通用寄存器 | 扩展 |
---|---|---|
16位通用寄存器 | AX、BX、 CX、 DX、 SI、 DI、 BP、 SP | R8W、R9W、R10W、R11W、R12W、 R13W、R14W、R15W |
32位通用寄存器 | EAX、EBX、ECX、EDX、ESI、EDI、EBP、ESP | R8D、R9D、R10D、R11D、R12D、 R13D、R14D、R15D |
64位通用寄存器 | RAX、RBX、RCX、RDX、RSI、RDI、RBP、RSP | R8、R9、R10、R11、R12、 R13、R14、R15 |
32位 常用寄存器
8个通用寄存器
8个通用寄存器:
EAX 是”累加器”(accumulator),操作数和结果数据累加器,返回值运算结果一般都存储在这里
EBX 是”基地址”(base)寄存器, 在内存寻址时存放基地址。
ECX 是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器。
EDX 是(destination) 用于存储部分乘法结果和部分除法被除数
edi 目标索引寄存器(destination index): 字符串操作的目标指针,ES段的数据指针
esi 源索引寄存器(source index):字符串操作的源指针,SS段的数据指针
ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向
系统栈最上面一个栈帧的栈顶。
EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向
系统栈最上面一个栈帧的底部
其中一部分还可以拆开处理
EIP:指令寄存器(Extended Instruction Pointer),其内存放着一个指针,该指针永远指向下
一条等待执行的指令地址
可以说如果控制了 EIP 寄存器的内容,就控制了进程——我们让 EIP 指向哪里,CPU 就会
去执行哪里的指令
XMM寄存器:(浮点寄存器)
EFLAGS寄存:包含了独立的二进制位,用于控制CPU操作,或是反应一些CPU操作的结果。有些指令可以测试和控制这些单独的处理器标识位。
EFLAGS寄存器的状态标志(0、2、4、6、7以及11位)指示算术指令(如ADD, SUB, MUL以及DIV指令)的结果,这些状态标志的作用如下:
内部数据类型
整数
- BYTE 8位
- SBYTE 8位 有符号
- WORD 16位 符号
- SWORD 16位 有符号
- DWORD 32位 符号
- SDWORD 32位 有符号
- FWORD 48位 保护模式的远指针
- QWORD 64位 整数
- TBYTE 80位 整数
实数
- REAL4 32位 短实数
- REAL8 64位 长实数
- REAL10 80位 扩展实数
伪指令
- db 8位整数 =char 可以保持ascll码
- dw 16位整数
- dd 32位整数
大端序和小端序
首先还是先看下基本概念:
1、大端模式:高字节保存在内存的低地址
2、小端模式:高字节保存在内存的高地址
mov arr,01234567h
存储
大端序:01 23 45 67
小端序:67 45 23 01
指令集
算数运算
加
格式:ADD OPRD1,OPRD2
功能:两数相加
加法指令运算的结果对CF、SF、OF、PF、ZF、AF都会有影响
不允许OPRD1与OPRD2同时为存储器
————————————————————————————————
带进位加法指令ADC
格式:ADC OPRD1,OPRD2
功能:OPRD1 = OPRD1 + OPRD2 + CF
减
减法指令SUB
格式:SUB OPRD1,OPRD2
功能:两个操作数的相减,即从OPRD1减去OPRD2,其结果放在OPRD1中,指令的类型及标识位的影响与ADD指令相同,注意立即数不能用于目的操作数,两个存储器操作数之间不能直接相减,操作数可为8位或16位的无符号数或符号数
————————————————————————————————
带错位减法指令SBB
格式:SBB OPRD1,OPRD2
功能:进行两个操作数的相减再减去CF进位标志位,即从OPRD1 = OPRD1 - OPRD2 - CF,其结果放在OPRD1中
乘
无符号数指令MUL
格式:MUL OPRD
两个相乘数,要么都是8位,要么都是16位。 8位乘法,16位乘法。
如果是8位,一个数字默认存放在al中,另外一个数字存放在其他8位寄存器中或者字节型内存单元中。
mul 8位寄存器 ;结果存放在ax中
如果是16位,一个数字默认存放在ax中,另外一个数字存放在其他16位寄存器中或者字型内存单元中。
mul 16位寄存器 ;结果存放在dx, ax中
8位乘法,得到一个16位数, 结果存放在ax中
16位乘法,得到一个32位数, 低16位存放在ax中,高16位存放在dx中————————————————————————————————
带符号数指令IMUL
功能:乘法操作
OPRD为通用寄存器或存储器操作数
本指令会影响标志位CF及OF
除
无符号数除法指令DIV
格式:DIV OPRD
功能:实现两个无符号二进制数除法运算
div指令是除法指令。100001/100,100001是被除数,100是除数。一般格式为:div reg或div 内存单元,reg和内存单元存放的是除数,除数可分为8位和16为2种。
被除数:默认放在AX或DX和AX,如果除数为8位,被除数则为16位,默认在AX中存放;如果除数 为16位,被除数则为32位,在DX和AX中存放,DX存放高16位,AX存放低16位。
————————————————————————————————
带符号数除法指令IDIV
格式:IDIV OPRD
功能:实现两个带符号数的二进制除法运算
比如16bit 的被除数,分别存在2个8bit寄存器AH:AL,商放在AL,余数在AH
比如32bit 的被除数,分别存在16个8bit寄存器DX:AX,商放在AX,余数在DX
比如64bit 的被除数,分别存在32个8bit寄存器EDX:EAX,商放在RAX,余数在EDX
比如128bit 的被除数,分别存在64个8bit寄存器RDX:RAX,商放在RAX,余数在RDX
自增
加1指令INC(INCrement by 1)
格式:INC OPRD
自减
减一指令DEC(Decrement by 1)
格式:DEC OPRD
LOOP
循环控制指令LOOP
格式:loop 标号
功能:相当于
dec cx
Jnz
即先对cx减1,然后判cx是否为0,不为0,转后面给出标号所指的入口,为0,顺序执行
MOV指令
数据传送指令 MOV
格式:MOV OPRD1,OPRD2
功能:将一个源操作数送到目的操作数中,即OPRD1<–OPRD2
说明:
OPRD1为目的操作数,可以说寄存器、存储器、累加器
OPRD2为源操作数,可以数寄存器、存储器、累加器和立即数。
MOVS(move string)
movs指令是汇编少有的两边都可以是memory的指令,MOVS在开发中通常极有可能是一串字符串的复制
字符串传送指令MOVS
格式:MOVS OPRD1,OPRD2
功能:OPRD1<—OPRD2
说明: 其中OPRD2为源串符号地址,OPRD1为目的串符号地址
LEA
有效地址传送指令
格式:LEA OPRD1,OPRD2
功能:将源操作数给出的有效地址传送到指定的寄存器中
OPRD1必须是寄存器
XCHG
数据交换指令
格式:XCHG OPRD1,OPRD2,其中OPRD1为目的操作数,OPRD2为源操作数
功能:将两个操作数相互交换位置,该指令把源操作数OPRD2与目的操作数OPRD1交换
TEST
格式:TEST OPRD1,OPRD2
功能:其中OPRD1、OPRD2的含义同AND指令一样,也是对两个操作数进行按位的‘与‘运算
不同之处:是不讲’与‘的结果送目的操作数,即本指令对两个操作数的内容均不进行修改,仅数载逻辑与操作后,对标志位重新置位
CALL指令
过程调用指令
格式:CALL OPRD
功能:过程调用指令
相当于:
push eip
amp OPRD
RETN指令
返回指令,相当于:
pop eip
jmp eip
常用的JCC指令
JMP:无条件跳转
JZ/JE:ZF = 1(jump When Zero和jump When Equal) 等于0或相等
JNZ(jump no Zero)与JNE(jump no Equals ) ZF=0 不等于0或者不相等
比较两个有符号数,高低用greater和less表示:
JG 前>后 Jump if greater
JL 前<后 Jump if less
JL=JNGE(jump if less,or not greater equal)
JGB和JLE是用于比较带符号数的转移指专令:
JGE 转移条件(Jump if greater or equal):JGE al, bl ;al里的带符号内容大于或等于bl时跳转。
JLE 转移条件 (Jump if less or equal):JLE al, bl ;al里的带符号内容小于或等于属bl时跳转。
比较两个无符号数,高低用below或者above表示:
JBE/JNA(jump if below or equal,or not above)比较结果为<=时转移
JBE/JNA:CF = 1/ZF = 1 低于等于或者不高于跳转
JNBE/JA(jump if not below or equal,or above)
JNBE/JA:CF = 0 / ZF = 0 不低于等于/高于跳转
JL/JNGE:SF != OF 小于/不大于等于跳转
JNL/JGE:SF = OF 不小于/大于等于跳转
栈操作指令
PUSH:压栈指令,32位汇编首先ESP-4,留出一个空间,然后把要压入栈中的内容压入
POP:出栈指令,32位汇编首先将栈顶的数据弹出给指定的目标,然后ESP+4,清掉空间
在函数栈帧中,一般包含以下几类重要信息。
(1)局部变量:为函数局部变量开辟的内存空间。
(2)栈帧状态值:保存前栈帧的顶部和底部(实际上只保存前栈帧的底部,前栈帧的顶部
可以通过堆栈平衡计算得到),用于在本帧被弹出后恢复出上一个栈帧。
(3)函数返回地址:保存当前函数调用前的“断点”信息,也就是函数调用前的指令位置,
以便在函数返回时能够恢复到函数被调用前的代码区中继续执行指令。
函数调用
函数调用大致包括以下几个步骤。
(1)参数入栈:将参数从右向左依次压入系统栈中。
(2)返回地址入栈:将当前代码区调用指令的下一条指令地址压入栈中,供函数返回时继 续执行。
(3)代码区跳转:处理器从当前代码区跳转到被调用函数的入口处。
(4)栈帧调整:具体包括。
保存当前栈帧状态值,已备后面恢复本栈帧时使用(EBP 入栈);
将当前栈帧切换到新栈帧(将 ESP 值装入 EBP,更新栈帧底部);
给新栈帧分配空间(把 ESP 减去所需空间的大小,抬高栈顶);
对于__stdcall 调用约定,函数调用时用到的指令序列大致如下
1 | ;func(a,b,c) |
函数返回的步骤如下
三步:
(1)保存返回值:通常将函数的返回值保存在寄存器 EAX 中。
(2)弹出当前栈帧,恢复上一个栈帧。
1 | 具体包括: |
(3)跳转:按照函数返回地址跳回母函数中继续执行。
理解图示:
汇编练习,弹个框框
基础知识—-汇编代码结构
1 | .586 代表指令集 |
1 | .586 |
格式化输入输出(printf scanf)
1 | .586 |