汇编学习

汇编语言

vscode反汇编
-exec disassemble /m
-exec disassemble /m main
-exec info registers //寄存器信息
字节长度

1byte=8bits

1word=2bytes=16bits

1dword=4bytes=32bits

逻辑运算
运算 and or xor
0 0 0 0 0
0 1 0 1 1
1 0 0 1 1
1 1 1 1 0
计算机加法运算

以2+3为例

X:0010 Y:0011

1.首先进行xor运算得到0001(此操作将相加后无需进位的数保留下来)。

2.然后进行and运算得到0010,并左移一位 0010 << 1 ==0100 (此操作为进位操作) 。

3.最后判断2操作后得到的数是否为0(判断是否仍需进位)。

4.继续将1和2步骤得到的数进行上述操作,直到得到0为止 。

32位通用寄存器
寄存器 作用
EAX 累加器
ECX 计数
EDX I/O指针
EBX DS段的数据指针
ESP 堆栈指针
EBP SS段的数据指针
ESI 字符串操作的源指针:SS段的数据指针
EDI 字符串操作的目标指针:ES段的数据指针
通用寄存器使用

MOV /ADD/SUB

MOV EAX,12345678 ;将12345678放入EAX中
ADD EAX,1 ;EAX中的值加1
MOV ECX,2 ;将2放入ECX中
ADD EAX,ECX ;将EAX和ECX中的值相加
SUB EAX,3 ;EAX中的值减3

LEA 寻址 LEA EAX,DWORD PTR DS:[0x12345678]

内存

1.每个内存单元的宽度为8。

2.[0x0000000] [0xFFFFFFFF]称为地址。

3.地址的作用:当我们想从内存中读取数据或者想向内存中写入数据,首先应该找到读、写的位置。

内存的读写
寻址公式 例子
[立即数] DWORD PTR DS:[0x12345678]
[reg] reg代表任意寄存器 DWORD PTR DS:[ECX]
[reg+立即数] DWORD PTR DS:[ECX+4]
[reg+reg*{1,2,4,8}] mov dword ptr ds[0x0000FFFF],0x0000FFFF
[reg+reg*{1,2,4,8}+立即数] DWORD PTR DS:[ECX+ECX*4+2]
堆栈
PUSH ;进栈
POP ;出栈

先进栈后出栈,栈底地址不变。

栈有两种基本操作:入栈和出栈。入栈就是将一个新的元素放到栈顶,出栈就是从栈顶取出一个元素。栈顶的元素总是最后入栈,需要出栈时,又最先从栈中取出。栈的这种操作规则被称为:LIFO(Last In First Out)。

最基本的两个指令是PUSH(入栈)和POP(出栈)。8086CPU的入栈和出栈操作都是以字为单位进行的。

image-20231126213242303

8086CPU中,有两个寄存器,段寄存器SS和寄存器SP,栈顶地址存在SS中,偏移地址存在SP中。任意时刻SS:SP指向栈顶元素。push指令和pop指令执行时,CPU从SS和SP中得到栈顶地址。

image-20231126213759123

POP指令与PUSh恰好相反。

栈顶超界问题

image-20231126214108797image-20231126214121306

我们在编程的时候要自己操心栈顶超界的问题,要根据可能用到的最大栈空间,来安排栈的大小,防止入栈的数据太多而导致的超界;执行出栈操作的时候也要注意,以防栈空的时候继续出栈而导致的超界。

push和pop

push和pop可以寄存器和内存之间传送数据

push 寄存器 ;寄存器中数据入栈
pop 寄存器 ;寄存器中数据出栈
push 段寄存器
pop 段寄存器
push 内存单元 ;将一个内存单元处的字入栈
pop 内存单元 ;出栈,用一个内存字单元接收出栈数据
mov ax,1000H
mov ds,ax ;内存单元的段地址放入ds中
push [0] ;将1000:0处的字压入栈中
pop [2] ;出栈,出栈数据送入1000:2处

汇编语言

宽度:能存储的二进制数的位数。

逻辑运算:2-3

2:0010

-3:补码形式 1011->1100->1101

0010 xor 1101 = 1111

0010 and 1101 = 0000 << 0000

结果为1111 为-1的补码形式。

八进制计算2-5

image-20231130194222483

这与存储器的宽度有关,可以看到在八进制结果中得到17·····75,这是因为在十六进制表达中,在这个64位存储器中,以二进制计算后的结果转为16进制就是上述F····FD(-3的补码形式),转为八进制就是上述结果。

寄存器和入栈出栈

运行代码

image-20231130183632167

运行前寄存器情况

image-20231130183714936

栈情况

image-20231130183838985

PUSH EBP:将EBP中的栈底地址压入栈,栈顶地址-4。

image-20231130184205498

MOV EBP,ESP:将栈顶地址放入EBP中,此时栈顶栈底地址相同

image-20231130184931860

SUB ESP,40:ESP中值减-0x40

image-20231130185151089

PUSH EBX:将ebx中存的值入栈且栈顶地址-4

image-20231130185522263

PUSH ESI:将ESI中存的值入栈且栈顶地址-4

image-20231130185636388

PUSH EDI:将EDI中存的值入栈且栈顶地址-4

image-20231130185716311

LEA EDI,DWORD PTR SS:[EBP-40]:EBP的地址值-0x40,存入EDI中

image-20231130190009019

MOV ECX,10:0x10存入ECX中

image-20231130190354965

MOV EAX,0xCCCCCCCC:

image-20231130190420213

REP STOS DWORD PTR ES:[EDI]:覆盖从edi开始的内存为eax,edi减小4。循环ecx次,每循环一次ecx的值减1。

image-20231130190756807

MOV EAX,DWORD PTR SS:[EBP+8]:ebp的地址+0x8中的值存入eax

image-20231130191249490

ADD EAX,DWORD PTR SS:[EBP+C]:ebp+0xc内的值加eax中的值,存入eax。

image-20231130191411105

POP EDI:栈顶地址中值取出,存入edi

image-20231130191513577

POP ESI:

image-20231130191540290

POP EBX:

image-20231130191602695

MOV ESP,EBP:

image-20231130191639709

POP EBP:

image-20231130191722494

RETN:retn表示pop eip,然后跳转到eip中的地址指向的指令准备开始执行

image-20231130191844204

image-20231130192056661

通用寄存器

寄存器 编号(二进制) 编号(十进制)
32位 16位 8位
EAX AX AL 000 0
ECX CX CL 001 1
EDX DX DL 010 2
EBX BX BL 011 3
ESP SP AH 100 4
EBP BP CH 101 5
ESI SI DH 110 6
EDI DI BH 111 7

32位寄存器的前半部分为16位寄存器,前四个16位寄存器又可以分成8个8位寄存器。

验证如下

image-20231204192310192

image-20231204192547165

汇编指令

(r代表通用寄存器,m代表内存,imm代表立即数)

1.mov 目标操作数,源操作数

作用:拷贝源操作数到目标操作数

(1)源操作数可以是立即数、通用寄存器、段寄存器或内存单元

(2)目标操作数可以是通用寄存器、段寄存器、内存单元

(3)操作数宽度必须一样

(4)源操作数和目标操作数不能同时为内存单元

MOV r8/m8,r8 #可以将8位通用寄存器中的值存入8位通用寄存器或者8位内存中
MOV r16/m16,r16
MOV r32/m32,r32
MOV r8,r8/m8 #可以将8位通用寄存器或者8位内存中的值存入8位寄存器中
MOV r16,r16/m16
MOV r32,r32/m32
MOV r8,imm8 #将一个8位的立即数存入8位通用寄存器中
MOV r16,imm16
MOV r32,imm32

2.add 目标操作数,源操作数

作用:将目标操作数与源操作数相加,结果存入目标操作数

(1)源操作数可以是立即数、通用寄存器、段寄存器或内存单元

(2)目标操作数可以是通用寄存器、段寄存器、内存单元

(3)操作数宽度可以不一样

(4)源操作数和目标操作数不能同时为内存单元

ADD r8/m8,imm8
ADD r16/m16,imm16
ADD r32/m32,imm32
ADD r16/m16,imm8 #源操作数与目标操作数可以不一致!!
ADD r32/m32,imm8
ADD r8/m8,r8
ADD r16/m16,r16
ADD r32/m32,r32
ADD r8,r8/m8
ADD r16,r16/m16
ADD r32,r32/m32

3.sub

作用:将目标操作数减源操作数,结果存入目标操作数

(1)源操作数可以是立即数、通用寄存器、段寄存器或内存单元

(2)目标操作数可以是通用寄存器、段寄存器、内存单元

(3)操作数宽度可以不一样

(4)源操作数和目标操作数不能同时为内存单元

SUB r8/m8,imm8 #8,16,32位都可以
SUB r8/m32,imm32 #源操作数与目标操作数宽度可以不一致
SUB r32/m32,r32
SUB r32,r32/m32

4.and/or/xor

AND r8/m8, imm8
AND r16/m16,imm16
AND r32/m32,imm32
AND r16/m16, imm8
AND r32/m32, imm8
AND r8/m8, r8
AND r16/m16, r16
AND r32/m32, r32
AND r8, r8/m8
AND r16, r16/m16
AND r32, r32/m32

5.not

NOT r8/m8
NOT r16/m16
NOT r32/m32

6.push 入栈

PUSH r32
PUSH r16
PUSH m16
PUSH m32
PUSH imm8/imm16/imm32
#举例:
push eax
push ax
push word ptr ss:[esp] #会从低位开始往高位取16位,即四个八进制,剩下的就不取了
push dword ptr ds:[edx]
push 0x12/0x1234/0x12345678/0x1 #这些数都会被当成32位立即数

7.pop 出栈

POP r32
POP r16
POP m16
POP m32
#举例:
pop esi
pop si
pop word ptr ss:[esp] #注意一定不能写成sp,虽然sp16位寄存器,但是ss:[]格式不允许16位,只允许32位寄存器,至于取多少内存宽度,取决的前面的word还是dword还是byte
pop dword ptr ss:[eax]

内存

mov dword ptr ds:[0x0012FF34],0x12345678
  • dword表示要使用的内存大小为4个字节,即要读/写多少;还可以是byte和word等
  • ptr就表示代表后面是一个指针,即后面要跟内存编号(固定写法)
  • DS是段寄存器,32位汇编中段寄存器和内存的属性有关系:如果后面地址直接用数表示,则用DS;如果后面写ESP或者EBP,就写SS;如果EDI的话就用ES。所以32位汇编中段寄存器就是后面内存属性的一个标识。
  • [0x0012FF34]表示后面的立即数存入内存的(起始)地址,如果前面是dword,表示从这个内存地址对应的一字节内存块开始存储(或读取),一共要存储(或读取)4字节内存,即4个内存块(因为一个地址对应一字节大小内存,那么一个编号可能不够存),即从[0x0012ff34]这个单元块开始存(或读),再往高地址存,依次往下,直到占用了4字节的内存,即从[0x0012ff34]到[0x0012ff37]都是用来存这个指令后面的数值的
  • 0x12345678就是存入内存的数值,称为立即数。如果立即数的宽度比前面指定的内存宽度大,则从低位开始取,取到指定的宽度,高位多余的部分就被丢弃,比如要写入dword,但是立即数为0x123456789,则最终写入的内存的值为0x23456789,一共4字节

标志寄存器

一、标志寄存器总览

​ EFLAGS寄存器,主要用于反映处理器的状态和ALU运算结果的某些特征及控制指令的执行。通俗来讲:标志寄存器与CPU中的其它寄存器不一样:它是按位起作用的,每一位都有专门的含义,记录特定信息

image-20240101142040330

二、标志寄存器常用标志

1.进位标志CF(Carry Flag)

​ 运算最高位产生了进位或者错位,其值为1,否则为0。

;最高位取决于运算数据宽度
add eax,0x1 ;计算32位
add al,0xff ;计算8位
2.奇偶标志PF(Parity Flag)

​ 运算结果的低八位中,(二进制)1的个数为偶数置1,为奇数置0。

​ 只判断运算,mov操作不会改变PF。

3.辅助进位标志AF(Auxiliary Carry Flag)

​ 无论数据宽度,看低四位是否发生进位或借位。如果发生进位或借位则值为1,否则为0。

4.零标志ZF(Zero Flag)

​ 用来反映运算结果是否为0,为0则值为1,否则为0。

mov eax0 ;操作并不会使ZF值变为1。
5.符号标志SF(Sign Flag)

​ 符号标志SF用来反映运算结果的符号位,它与运算结果的最高位相同

​ 注意如果SF位为1,只能说明最高位为1,不能说明这就一定是一个负数,因为有符号数和无符号数是程序员在写的时候自己决定 的,所以当程序员认为结果为无符号数时,那么最高位是1也不是负数,除非认为是有符号数,才能根据SF位为1判断是结果是负数

6.溢出标志OF(Overflow Flag)

​ 用来反映有符号数加减所得结果是否溢出,如果运算结果超出当前运算位数所能表示的范围,则称为溢出,OF值为1,否则为0。(主要给有符号数运算使用)

​ 无符号数相加超过数据宽度则溢出(一般看CF)

​ 正+正=正 ,如果为负则有溢出

​ 负+负=负 ,结果为正则有溢出

​ 正+负 永远不会有溢出

7.方向标志DF(Direction Flag)

​ 当该位置1时(DF=1),存储器地址自动减少(减多少一般看指令给的宽度是byte,word还是dword,或者视情况而定),串操作指令为自动减量指令,即从高位到低位处理字符串;当该位置0时(DF=0),存储器地址自动增加,串操作指令为自动增量指令。

其他汇编指令

1.ADC:带进位加法

​ 相加运算后加上CF的值。

ADC R/M,R/M/IMM #两边不能同时为内存,且宽度要一样
#举例:
ADC AL,CL
ADC BYTE PTR DS:[12FFC4],2
ADC BYTE PTR DS:[12FFC4],AL
2.SBB:带借位减法

​ 相减运算后减去CF的值。

SBB R/M,R/M/IMM #两边不能同时为内存,宽度要一样
#举例:
SBB AL,CL
SBB BYTE PTR DS:[12FFC4],2
SBB BYTE PTR DS:[12FFC4],AL #宽度一致
3.XCHG:交换数据
XCHG R/M,R/M #后面不能是立即数!两边不能同时为内存,宽度要一样
#举例:
XCHG AL,CL
XCHG DWORD PTR DS:[12FFC4],EAX
XCHG BYTE PTR DS:[12FFC4],AL #宽度一致
4.MOVS:内存和内存之间移动数据

​ 将后面ESI中存到内存地址编号中的值(按照给定的数据宽度),存到前面的EDI中的内存地址编号中。

​ DF为0时,ESI和EDI中的地址编号根据数据宽度进行+1/+2/+4

​ DF为1时,ESI和EDI中的地址编号根据数据宽度进行 -1/ -2/ -4

MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI] #简写为 MOVSB
MOVS WORD PTR ES:[EDI],WORD PTR DS:[ESI] #简写为 MOVSW
MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI] #简写为 MOVSD
5.STOS:将AL/AX/EAX的值存到[EDI]指定的内存单元(根据数据宽度)

​ DF的值对EDI的影响同MOVS

STOS BYTE PTR ES:[EDI] #简写为 STOSB
STOS WORD PTR ES:[EDI] #简写为 STOSW
STOS DWORD PTR ES:[EDI] #简写为 STOSD
6.REP:按计数寄存器(ECX)中指定的次数重复执行,执行一次ECX-1

​ 对象为MOVS和STOS

JCC

一、jmp

​ 作用:修改EIP的值,执行时只有EIP变化。

​ EIP:CPU读取指令的地址。

jmp 立即数/寄存器
二、call

​ 作用:修改EIP的值,将call下一跳指令的地址压入栈中。ESP会发生变化,此时栈顶地址也叫返回地址。

call 地址/寄存器
三、retn

​ 作用:修改EIP的值,将栈顶地址中的值(返回地址)出栈,并赋给EIP,之后跳转到EIP中的地址,也就是call下面的操作。(通常与call一起使用)

四、cmp

​ 作用:只改变标志寄存器,不修改操作数。

​ 原理:进行一次SUB操作,但是并不修改寄存器中的值。根据SUB的结果来改变标志寄存器的值。当两个数相等时,ZF置1。

格式:

CMP EAX,ECX
CMP AX,WORD PTR DS:[405000] #可以将内存中的值与寄存器作比较,但是数据宽度必须一致
CMP EAX,DWORD PTR DS:[405000]

​ cmp执行后通过标志寄存器的值判断两个数的大小。

无符号数:

CMP结果 ZF CF
目的操作数<源操作数 0 1
目的操作数>源操作数 0 0
目的操作数=源操作数 1 0

有符号数:

CMP结果 标志位
目的操作数<源操作数 SF≠OF
目的操作数>源操作数 SF=OF
目的操作数=源操作数 ZF=1

ZF位与SF位比较重要

五、test

​ 作用:将两个数进行and操作,只改变标志寄存器的值。

格式:

TEST R/M,R/M/IMM #同样前面不能同时为操作数

常见用法:一般用来判断某寄存器值是否为0(一般看ZF标志寄存器判断)

六、jcc指令
1、je、jz

​ je:jmp if equal

​ jz:jmp if zero

有条件跳转,如果相等则跳转,一般看ZF标志位。

2、jne,jnz(与je、jz相反)

​ jne:jmp if not equal

​ jnz:jmp if not zero

3、js、jns

​ jmp if(not) sign

​ 如果SF位为1(0)则跳转。

4、jp、jpe

​ jmp if parity (even)

​ PF=1跳转

5、jnp、jpo

​ jmp if not parity;jmp when parity flag is odd

​ PF=0跳转

6、jo、jno

​ jmp if (not) overflow

​ OF=1(0)则跳转

7、jb、jnae

​ jmp if below;jmp if above or equal

​ jb指令前面的cmp等比较指令中前面的数跟后面的数比较,小于则跳转(无符号数),即CF=1跳转。

8、jnb、jae

​ jmp if not below;jmp if above or equal

​ 大于等于则跳转(无符号数),CF=0跳转

9、jbe、jna

​ jmp if below or equal;jmp if not above

​ 小于等于则跳转(无符号数),CF=1或ZF=1跳转。

10、jnbe、ja

​ jmp if not below or equal;jmp if above

​ 大于则跳转(无符号数),CF=0且ZF=0跳转。

11、jl、jnge

​ jmp if less;jmp if not greater or equal

​ 小于则跳转(有符号数),SF≠OF跳转。

12、jnl、jge

​ jmp if not less;jmp if greater or equal

​ 大于等于则跳转(有符号数),SF=OF跳转。

13、jle、jng

​ jmp if less or equal;jmp if not greater

​ 小于等于则跳转(有符号数),SF≠OF或ZF=1。

14、jnle、jg

​ jmp if not less or equal;jmp if greater

​ 大于则跳转(有符号数),SF=OF且ZF=0。

堆栈图

OllyDBG F2设置断点,F9运行到断点处,F8单步运行,CALL用F7执行。

画堆栈图

image-20240306183617383

(初始堆栈)↑

image-20240304194415328

image-20240304194439996

image-20240304194725349

  • push 2push 1操作一般是将函数要用到的参数赋值并压入栈中,后面函数可能要使用;
  • call函数表示调用此函数,让CPU跳转到后面跟的地址,开始执行函数,先会经过jmp指令,起一个跳板的作用,两个都会改变eip的值,让CPU跳转执行,而且最要注意的是,call函数跳转前会先将函数的返回地址压入栈中;
  • push ebp是将函数调用前的源栈底储存后面,后面要恢复堆栈

image-20240304194629908

image-20240304194651962

image-20240304200130749

  • mov ebp,espsub esp,40为了开辟缓冲区,用来存储函数中要用到的数据

  • push ebxpush esipush edi为了保留现场,这些寄存器中可能存着函数调用前的信息,函数执行时可能会使用这些寄存器,所以将数据先存入内存

  • lea edi, dword ptr [ebp-40]将缓冲区开始地址放入edi

  • mov ecx,10mov eax,ccccccccrep stos dword ptr es:[edi]将缓冲区用cccccccc填满

  • CCCCCCCC是四个十六进制数,CC其实就是int3的硬编码,int3是断点,填充这么多CCCCCCCC有什么好处呢?防止缓冲区溢出:因为在写代码时,这块新分配出来的内存(缓冲区),就是给程序用的,当有数据要存储时,就会存到这块内存中,其他不用的地方全是CCCCCCCC,即int3,那么当程序一旦超过缓冲区时,就会自动停下来

  • 其次,这块新分配出来的内存,上一个函数执行时可能使用过,留下垃圾数据(对本次函数没有意义的数据),那么将缓冲区填充的还有一个好处就是将缓冲区的垃圾信息覆盖,所以最好给参数赋初始值

image-20240306185331596

  • mov eax, dword ptr [ebp+8]add eax, dword ptr [ebp+C]这两条指令就是函数真正的功能,将两个函数的参数相加,将结果存到eax中返回(如果结果大于0xffffffff可以存到内存中)
  • pop edipop esipop ebx用来恢复现场,将原来的寄存器中值恢复
  • retn等价于pop eip,而此时栈顶中的值就是函数的返回地址,所以此指令执行后让CPU回到这函数的下面一行指令地址
  • add esp,8是为了将堆栈恢复原状

本文作者:yee-l

本文链接:https://www.cnblogs.com/yee-l/p/18232888

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   yee-l  阅读(18)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起