汇编与逆向
Bamboo Lv3

汇编

寄存器 (14个)

通用寄存器:AX,BX,CX,DX

其中为了保证8位CPU和16位CPU的兼容性,这四个寄存器都可以分成两个独立的8位寄存器使用

AX = AH(高8位) + AL(低8位) 其余同理。

段寄存器:CS(代码段使用的寄存器),DS(数据段使用的寄存器),SS(堆栈段使用的寄存器),ES(附加段使用的寄存器)

指针寄存器:SP(堆栈内的偏移值),BP(寻址,默认在堆栈段寻找),SI(寻址,默认在数据段寻找),DI(寻址,默认在数据段寻找),IP(存储要执行的下一条指令的偏移地址)

标志寄存器:PSW

其中,BX、BP、SI、DI可用来寻址

其中标志寄存器涉及到的通用标志位是:

零标志位(ZF):结果为0,则ZF =1 ;结果为1,则ZF=0

奇偶标志位(PF):1的个数为偶数,则PF =1;个数为奇数,则PF =0

符号标志位(SF):结果为负,则SF =1;结果为正,则SF=0

进位标志位(CF):无符号数运算的进位值或借位值

溢出标志位(OF):有符号数运算,溢出为1,无溢出为0

OF= 最高位进位(符号位)异或 次高位进位(数值最高位)

方向标志位(DF) : 在串处理指令中控制每次操作后si、di的递减。

寻址方式

大多数汇编语言指令都需要处理操作数。操作数地址提供了要处理的数据存储的位置。有些指令不需要操作数,而另一些指令则需要一个,两个或三个操作数。当一条指令需要两个操作数时,第一个操作数通常是目的地,它在寄存器或存储器位置中包含数据,第二个操作数是源。源包含要传递的数据(立即寻址)或数据的地址(在寄存器或存储器中)。通常,操作后源数据保持不变。

  1. [idata]用一个常量来表示地址,可用于直接定位一个内存单元
  2. [bx]用一个变量来表示内存地址,可用于间接定位一个内存单元
  3. [bx + idata] 用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接定位一个内存单元
  4. [bx + si]用两个变量表示地址
  5. [bx + si + idata]用两个变量和一个常量表示地址

image-20250526224250636

例如:

1
2
3
4
5
mov ax,[bx + 200]
mov ax,[si + 123]
mov ax,[di + 234]
mov ax,[bx + si]
mov ax,[bx + si +200] --> mov ax,200[bx][si] ;把200看成基址,然后形式类似于二维数组

mov指令可能具有以下形式:

1
2
3
4
5
MOV  寄存器, 寄存器
MOV 寄存器, 立即数
MOV 寄存器, 内存
MOV 内存, 立即数
MOV 内存, 寄存器

物理地址与逻辑地址的转换:

物理地址 = 段地址 × 16 +偏移地址

偏移地址–> 段地址:偏移地址

变量

指令 目的 储存空间
db 定义字节 分配1个字节
dw 定义字 分配2个字节
dd 定义双字 分配4个字节

在数据段中使用如下:

1
2
3
4
5
data segment
db 1
dw 1
dd 1
data ends

dup 是一个操作符,和db、dw、dd等数据定义伪指令配合使用的,用来进行数据的重复

1
2
3
db 3 dup ('abc','ABC')
;定义了18个字节-->'abcABCabcABCabcABC'
;相当于 db 'abcABCabcABCabcABC'

基本语法

汇编程序可以分成三个段:数据段、代码段、堆栈段。

注释以分号;开头

其中汇编语句的格式为:

1
[label] mnemonic [operands] [;comment]

堆栈段

push ax : 将ax内容送入栈中,sp=sp-2 指向偏移地址(进栈)

pop ax : 将ss:sp指向的内存单元处数据送入ax寄存器中,sp=sp+2指向当前栈顶下面的单元(出栈)

当栈满的时候再使用push指令进栈以及当栈空的时候再使用pop指令出栈都会发生栈顶超界。

pushf:将标志寄存器的值压栈

popf:从栈中弹出数据,送入标志寄存器中

初始化代码:(栈就是高地址往低地址生长的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
assume cs:code,ds:data,ss:stack

data segment
...
data ends

stack segment
dw 0,0,0,0...,0 ;在内存分配16个字的空间
stack ends

code segment
start:
mov ax,data
mov ds,ax ;设置数据段段地址
mov ax,stack
mov ss,ax ;设置堆栈段段地址
mov sp,20h ;栈初始化,空栈时栈底和栈顶指针sp都是20h(因为分配了16个字,所以是32个字节)
...
code ends
end

逻辑指令

and指令 : 按位进行与运算。(同时为1才能为1)

1
2
3
4
mov al,01100011B
and al,00111011B ;作用:可用来给特定的位置清0,将相应位设为0,其余位不变

执行后:al = 00101011

or指令 : 按位进行或运算。(同时为0才能为0)

1
2
3
4
mov al,01100011B
or al,00111011B ;作用:可以给特定位置设1,将相应位置设为1,其余位设0

执行后: al = 01111011B

not指令 : 按位进行非运算。(全部倒转,反码操作)

1
2
3
4
mov al,01100011B
not al

执行后:al = 10011100B

xor指令 : 按位进行异或运算。(异为1,同为0)

特点:

  • 与自己异或为全0
  • 与自己相反的异或为全1
  • 所有值与0异或保持不变
  • 所有值与1异或则取反

作用:

  • 数据部分取反
  • 同时清除寄存器及CF
1
2
3
xor ax,ax  
;执行之后 ax=0,CF=0 (清零操作)
;相当于 mov ax,0 但是速度比mov快

算术指令

inc 指令 : 将操作数加1.(相当于c语言的num++)

1
2
3
inc bx		;32位寄存器  自增1
inc dl ;16位寄存器 自增1
inc [count] ;变量count 自增1

dec 指令 : 将操作数减1.(相当于c语言的num–)

1
dec [value]

add/sub 指令 : 简单的加/减法指令,用于对字节、字和双字大小的二进制数据进行简单的加/减。

1
add/sub destination,source

add/sub 指令可以发生在:

  • 寄存器 to 寄存器
  • 内存 to 寄存器
  • 寄存器 to 内存
  • 寄存器 to 常量数据
  • 内存 to 常量数据

adc 指令 : 带进位的加法指令,利用CF位上记录的进位值。

1
2
3
4
5
6
格式:
adc 操作对象1,操作对象2

功能:
adc ax,bx
实际上实现的功能是:(ax) = (ax) + (bx) + CF

sbb 指令 : 带错位减法指令,利用CF位上记录的借位值。

1
2
3
4
5
6
格式:
sbb 操作对象1,操作对象2

功能:
sbb ax,bx
实际上实现的功能是:(ax) = (ax) - (bx) - CF

mul 指令 : 乘法指令,对应位数(同为8位或者16位)

两个字节相乘(8位):

被乘数在AL寄存器中,乘数在存储器或者另一个寄存器中。

结果放在AX中,高8位存储在AH中,低8位存储在AL中。

两个单字相乘(16位):

被乘数在AX寄存器中,乘数在内存或其他存储器中。

高阶(最左侧)部分存储在DX中,而低阶(最右侧)存储在AX中。

例子:计算100*10

1
2
3
4
mov al,100
mov bl,10
mul bl
结果:(ax) = 1000(03E8h)

例子:计算100*10000

1
2
3
4
5
mov ax,100
mov bx,10000
mul bx
结果:(ax)=4240H,(dx)= 000FH
(F4240H = 1000000)

div 指令 : 除法指令。

当除数为1个字节时:

除数在寄存器或内存单元中,被除数在AX中。

结果中的商放在AL中,余数放AH中。

当除数为1个单字时:

除数在寄存器或者内存单元中,被除数在DX和AX中。

结果中的商放在AX中,余数放在DX中。

shl 指令 : 逻辑左移指令,将数据向左移位,最后移出的一位写入CF,最低位用0补充。

sal 指令 : 算术左移指令,本质与shl相同。

shr 指令 :逻辑右移指令,将数据向右移位,最后移出的一位写入CF,最高位用0补充。

sar 指令 :算术右移指令,将数据向右移位,最后移出的一位写入CF,最高位用最高位相同的数字补充。

串传送指令

movsb(以字节为单位传送)

将ds:si指向的内存单元送入es:di中

DF=0则si=si+1 ;DF=1则si =si -1

movsw(以字为单位传送)

将ds:si指向的内存单元送入es:di中

DF=0则si=si+2 ;DF=1则si =si -2

rep是根据cx的值重复执行后面的串传送指令

1
2
3
4
rep movsb
;相当于
s:movsb
loop s

cld 指令:将标志寄存器DF置0

std 指令:将标志寄存器DF置1

例子:用串传送指令,将data段的第一个字符串复制到它后面的空间中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
assume ds:data,cs:code

data segment
db 'Welcome to masm!'
db 16 dup (0)
data ends

code segment
start:
mov ax,data
mov ds,ax
mov si,0 ;ds:si指向data:0
mov es,ax
mov di,16 ;es:di指向data:16
mov cx,16 ;rep16次
cld ;DF=0,正向传送
rep movsb
code ends
end

条件

汇编语言中的条件执行是通过几个循环和分支指令来完成的,这些指令可以更改程序中的控制流。

CMP 指令 : 比较两个操作数,从另一个操作数中减去一个操作数,以比较操作数是否相等。它不会干扰目标或源操作数。它与条件跳转指令一起用于决策。

1
2
3
4
CMP DX,00 ;将DX与0进行比较
JE L7 ;如果相等,则跳转到L7

L7:...

通常用于比较计数器值是否达到需要循环的次数:

1
2
3
inc cx
CMP cx,10 ;比较计数器是否达到10
JLE LP1 ;如果小于等于10则跳转到LP1

无条件跳转 : JMP 指令

1
2
3
4
5
6
7
8
MOV  AX, 00    ; 将AX初始化为0
MOV BX, 00 ; 将BX初始化为0
MOV CX, 01 ; 初始化CX为1
L20:
ADD AX, 01 ; 增量AX
ADD BX, AX ; 将AX添加到BX
SHL CX, 1 ; 向左移动CX,这反过来使CX的值翻倍
JMP L20 ; 重复的语句

(机器码:EB)jmp short 标号,对IP的修改范围为-128~127

1
2
3
4
5
6
7
8
9
10
assume cs:codesg
codesg segment
start:
mov ax,0
jmp short s
add ax,1
s:inc ax
codesg ends
end start
;在这个程序中其实ax只执行了一次ax+1操作

(机器码:E9)jmp near ptr 标号 ,修改范围为-32769~32767

(机器码:EA)jmp far ptr 标号,(CS)=标号所在段的段地址,(IP)=标号所在段中的偏移地址

条件跳转

指令 描述 检测的相关标志位
je / jz (equal) 等于则转移 ZF = 1
jne / jnz 不等于则转移 ZF = 0
jb (below) 低于则转移 CF = 1
jnb 不低于则转移 CF = 0
ja (above) 高于则转移 CF = 0,ZF = 0
jna 不高于则转移 CF = 1 或 ZF = 1

子程序

image-20250526233558707

call s : 将当前IP或CS和IP压入栈中(压入栈中保留时IP已经指向下一条指令,所以保留的地址指向CALL的下一条指令,RET返回该地址可以继续执行),然后转移

call far ptr s : CS寄存器先进栈,IP寄存器后进栈

ret 指令 :返回指令,将栈顶一个字节弹出栈送进IP寄存器

retf 指令 : 先弹出的送入IP寄存器,后弹出的送入CS寄存器

img

img

相关程序

大小写字母转换

在ASCII编码中,61h表示“a”,41h表示“A”,相差20h。

“A” : 41H = 01000001B

“a” : 61H = 01100001B

就是第五位不同,所以可以考虑使用xor操作将第5位清零或置一以实现转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
assume cs:code,ds:data

data segment
db 'BaSiC' ;位置在0,转大写
db 'MinIX' ;位置在5,转小写
data ends

code segment
start:
mov ax,data
mov ds,ax ;设置数据段段地址

mov bx,0 ;指针初始化
mov cx,5 ;循环次数
s1:mov al,0[bx] ;读第一个字符
and al,11011111B ;将第5位置0,转为大写字母
mov 0[bx],al ;写回
mov al ,5[bx] ;读一个字符
or al 00100000B ;将第五位置1,转为小写字母
mov 5[bx],al ;写回
inc bx ;指向下一个字符
loop s1 ;第一次循环

mov ax ,4c00h
int 21h
code ends
end

字符串或内存块内容的批量复制

例子:用si和di实现将字符串‘welcome to masm!’复制到他后面的数据区中。

分析:原来数据位置在data:0开始,有16个字节,所以后面的偏移地址为16.用ds:si指向要复制的源始字符串,用ds:di指向复制的目的空间,使用循环进行复制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
assume cs:code,ds:data

data segment
db 'welcome to masm!'
db '................'
data ends

code segment
start:
mov ax,data
mov ds,ax ;数据段的段地址
mov si,0
mov di,16

mov cx,8
s:mov ax,[si]
mov [di],ax
add si,2
add di,2
loop s

mov 4c00h
int 21h
code ends
end

例子:内存块数据拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
assume cs:code

code segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
start:
mov ax,0
mov ds,ax ;段地址
mov bx,0 ;偏移地址

mov cx,8 ;循环次数
s:mov ax,[bx] ;读取偏移地址的内容
mov cs:[bx],ax ;写入内存
add bx,2
loop s

mov ax,4c00h
int 21h
code ends
end start

求一个或多个数的N次方

例子:计算2的12次方(但是这个add只局限于2的次方)

1
2
3
4
5
6
7
8
9
10
11
12
assume cs:code

code segment
mov ax,2
mov cx,11
s:add ax,ax
loop s

mov ax,4c00h
int 21h
code ends
end

例子:计算4的10次方(可以用于单个数的N次方)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
assume cs:code

code segment
start:
mov ax,4
mov cx,9
s:mov bx,4
mul bx
loop s

mov ax,4c00h
int 21h
code ends
end

环境安装

本地环境为Windows,使用的工具有DOSBoxMASM5,8086汇编语言需要在DOS环境下进行,因此需要在电脑上安装一个DOS虚拟机。虚拟机软件选用DOSBox-x 0.83.19,该软件为绿色软件,将压缩包解压后运行文件夹中的DosBox-x程序即可。

DOS环境搭好后需要安装宏汇编开发包MASM5,具体方法为:

\1) 在本机硬盘上创建一个DOS目录,如D:\DOS

\2) 将MASM5.RAR复制到该目录并解压 ;

这里注意的是,masm的路径应该直接为D:\DOS:\MSAM5,里面包含masm.exelink.exedebug.exeexe2bin.exe,分别用于汇编asm程序、连接、调用。

\3) 编辑DOSBOX-X目录下的 dosbox-x.conf 文件:

​ 在文件末尾[autoexec]段落(功能是开机自动运行)下添加一行:

1
mount c d:\dos

​ 该行将你的DOS目录映射为DOS虚拟机的C盘。

​ 然后在上面若干行处将

1
set path  = Z:\;Z:\SYSTEM;Z:\BIN;Z:\DOS;Z:\4DOS;Z:\DEBUG;Z:\TEXTUTIL

​ 这一行修改为

1
set path  = Z:\;Z:\SYSTEM;Z:\BIN;Z:\DOS;Z:\4DOS;Z:\DEBUG;Z:\TEXTUTIL;C:\MASM5

​ 这样即可将宏汇编MASM5的功能加入搜索路径,输入命令即可使用。

\4) 存盘退出,启动虚拟机即可

可以在DOS盘下建立一个exp目录,将实验程序源代码都存在目录里。源代码的编写用任何文本编辑软件编写均可。

至此汇编语言开发环境搭建完毕。

单代码段程序的编写

编写汇编程序

在记事本中写入汇编程序,并修改后缀名为.asm,存放在建立的D:\DOS:\exp

汇编文件

点开解压之后的dosbox文件夹,双击exe打开,cd到存放文件的地方,输入masm回车,在之后的语句输入上述已编写的asm文件名(不需要后缀)然后连续敲回车,显示0 Warning/Severe Errors表明汇编成功,生成一个后缀为.obj文件(也可以检查一下masm是否可用)

还可以直接输入 masm [文件名]

以下是我输入的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
assume cs:codesg
codesg segment
mov ax, 2000H
mov ss,ax
mov sp, 0
add sp, 10
pop ax
pop bx
push ax
push bx
pop ax
pop bx

mov ax, 4c00H
int 21H

codesg ends
end

image-20250317210239105

连接文件

在汇编完成的语句后输入link,之后再输入文件名,连续敲回车,显示LINK:warning L4021:no stack segment表明连接成功,可以看到后缀为.exe的文件

同样也可以直接输入 link [文件名]

image-20250317210338428

调试文件

输入debug [文件名].exe就可以进入调试,在短横线后输入命令即可。

以下为常用命令:

r:查看、改变寄存器的内容

查看就输入r,修改就输入r [需要修改的寄存器名字] ,然后输入修改的数值即可完成。

image-20250317211332992

d:查看内存中的内容

直接输入d,可以查看预设地址内存处的128个字节的内容

image-20250317211304283

也可以查看指定段的,输入d [后]:[前]

image-20250317211548029

e:修改内存单元中的内容

可以写入数据、指令,内存中,数据和指令都是机器码,没什么区别

写入数据(需要指明地址和要写入的地址)

1
e  [地址] [数据](数据如果有多个,中间要加空格)

image-20250317212701926

逐个询问修改数据

1
e [地址]

输入这个地址就会告诉你这个地址的内容,修改就在后面写上需要修改的数据,如果想对下一个字节进行修改,敲空格即可跳到下一个地址

image-20250317213420282

u:将内存中的机器指令翻译成汇编指令

image-20250317213938675

a:以汇编的指令格式在内存中写入机器指令

1
2
3
a [写入的地址]
[内容]
...(按回车退出)

t:单步运行

image-20250317214523035

注意观察每次输入t指令后,ax,bx的值变化。可以观察出t命令是使用一次执行一次汇编指令。

q:退出debug

Powered by Hexo & Theme Keep
Total words 28.5k Unique Visitor Page View