0x00
开设日常学习和笔记分享的篇章,帮助大家来学习汇编语言。为什么要学习汇编语言呢?是由于在红蓝对抗中,常常因为一些AV/EDR的存在导致我们的工具被查杀。所以才需要我们来对抗AV也就是免杀技术。所以要学习免杀技术就要从基础开始。后续可能还会分享一些C++、PE文件结构等等笔记。另外也可能会推出逆向相关的知识。
0x01
上期实验:
assume cs:codesg
codesg segment
mov ax,1000h
mov ds,ax
mov ax,2
add ax,ax ;由于没有学习乘法指令,这里先简单使用加法指令代替
mov ds:50,ax
mov ax,4c00h
int 21h
codesg ends
end
四、[bx]和loop指令
[bx]和内存单元的描述
[0]代表内存单元,偏移地址是0,比如在debug指令中:
mov ax,[0] 将一个内存单元的内容送入ax,这个内存单元的长度为2字节,存放一个字节,偏移地址为0,段地址在ds中。
mov al,[0] 将一个内存单元的内容送去al,这个内存单元的长度为1字节,存放一个字,偏移地址为0,段地址在ds中。
要完整的描述一个内存单元需要两种信息:
- 内存单元的地址
- 内存单元的长度(类型)
描述性符号()
我们用描述性符号()表示一个寄存器或一个内存单元中的内容,比如(ax)表示ax中的内容,(20000H)表示内存20000H单元的内容,()中的内存单元地址为物理地址
((ds)*16+(bx))表示:ds中的内容为ADR1,bx中的内容为ADR2,内存ADR1*16+ADR2单元的内容,也可以理解为ds中ADR1作为段地址,bx中的ADR2作为偏移地址,内存ADR1:ADR2单元的内容
注意:()中的元素可以有三种类型:寄存器名、段寄存器名、内存单元的物理地址,比如(ax)、(bx)、(20000H)、((ds)*16+(bx))是正确的用法,(2000:0)、((ds):1000H)是错误的用法
(x)的应用
- ax中的内容为0010H,(ax)=0010H
- 2000:1000 处的内容为0010H,(2100H)=0010H
- 对于mov ax,[2] 的功能可以描述为 (ax)=((ds)*16+2)
- 对于mov [2],ax 可描述为 ((ds)*16+2)=(ax)
- 对于 add ax,2 可描述为 (ax)=(ax) + 2
- 对于add ax,bx 可描述为 (ax)=(ax) + (bx)
- 对于push ax 可描述为 (sp)=(sp) - 2, ((ss)*16+(sp))=(ax)
- 对于pop ax 可描述为 (ax)= ((ss)*16+(sp)),(sp)=(sp)+2
(x)所表示的数据有两种类型:字节和字,是哪种类型由寄存器名或具体的运算决定,比如(al)、(bl)得到字节型数据,(ax)、(bx)得到字型数据
约定符号idata表示常量
我们用idata表示常量,比如 mov ax,[idata] 表示 mov ax,[1] 、 mov ax,[2]、 mov ax,[3] 等;比如 mov ax ,idata 表示 mov ax,1、 mov ax,2、 mov ax,3等,他们都是非法指令
[bx]指令
看一下 mov ax,[bx] 指令的作用:bx中存放的数据作为一个偏移地址EA,段地址SA默认在ds中,将SA:EA处的数据送入ax中,即 (ax) = ((ds)*16+(bx)) = (ax)
而对于mov [bx],ax 来说,bx中存放的数据作为一个偏移地址EA,段地址SA默认在ds中,将ax中的数据送入内存SA:EA处,即 ((ds)*16+(bx))=(ax)
首先前三条指令执行后ds=2000H,bx=1000H
mov ax,2000H
mov ds,ax
mov bx,1000H
然后第四条指令把内存2000:1000处的字型数据送入ax中,执行后ax=00beH
mov ax,[bx]
接着第五六条指令执行前bx=1000H,执行后bx=10002H
inc bx
inc bx ; inc想到于自增操作
然后第七条指令 mov [bx],ax ,执行前ds=2000H,bx=1002H,ax 将ax中的数据送入内存2000:1002处,执行后2000:1002单元处内容为BE,2000:1003单元处内容为00
接下来第八九条指令执行前bx=1002H,执行后bx=1004H
inc bx
inc bx
接下来第十条指令mov [bx],ax
把ax中的数据送入内存2000:1004处,执行后2000:1004单元的内容为BE,2000:1005单元的内容为00
接下来第十一条指令inc bx
执行前bx=1004H,执行后bx=1005H
接下来第十二条指令mov [bx],al
把al中数据送入内存2000:1005处,执行后2000:1005单元内容为BE
接下来第十三条指令inc bx
执行前bx=1005H,执行后bx=1006H
第十四条指令mov[bx],al
将al中数据送入2000:1006处,执行后2000:1006单元的内容为BE
Loop指令
CPU执行loop操作的时候要两部操作:
- (CX)=(CX)-1
- 判断cx中的值
cx中的值影响着loop指令的执行结果,我们常用loop指令来实现循环功能,cx中记录循环次数
实验一:编程计算2的12次方
assume cs:codesg
codesg segment
mov ax,2
mov cx,11 ;表示循环11次
S:
add ax,ax
loop s
mov ax,4c00h
int 21h
codesg ends
end
- 汇编语言中标号代表一个地址,标号s表示了一个地址,地址处由一条指令 add ax,ax
- cpu在执行loop s 需要两步操作:
- (cx)=(cx)-1
- 判断cx中的值,不为0则转至s所标识的地址处执行,如果为0就吓一跳指令
- 以下三条指令,执行loop s时首先(cx)-1,然后(cx)不为0,则向前转至s处执行,可以利用cx控制循环的执行次数
在debug中跟踪用loop指令实现的循环程序
实验二:计算ffff:000f单元中的数乘3,结果存在dx中
- ffff:0006单元中是一个字节型数据,范围在0~255,和3相乘结果不会大于65535,可以在dx存放下
- 用循环累加法,将内存赋值给ax,用dx进行累加,先设(dx)=0,然后做三次(dx)=(dx)+(ax)
- ffff:0006是一个字节单元,ax是16位寄存器,数据长度不一样,设ffff:0006单元中数据为xxh,若要ax中值与ffff:0006中先等,ax中应为00xxh,所以实现ffff:0006单元向ax赋值,应令(ah)=0,(al)=ffff6h
assume cs:code
code segment
mov ax,0ffffh
mov ds,ax
mov bx,6
mov al,[bx]
mov ah,0
mov dx,0
mov cx,3
s:
add dx,ax
loop s
mov ax,4c00h
int 21h
code ends
end
大于9ffffh的十六进制数据A000H、A001H等在书写时以字母开头,而汇编源程序不能以字母开头,所以A000H写成0A000h
debug和masm对指令的不同处理
mov ax,[0] 表示将ds:0 处的数据送入ax中,但是在汇编源程序中被当作指令 mov ax,0 处理
将内存2000:0、 2000:1、 2000:2、 2000:3 单元中的数据送入al,bl,cl,dl中
在debug中实现:
mov ax,2000
mov ds,ax
mov al,[0]
mov bl,[1]
mov cl,[2]
mov dl,[3]
汇编源程序实现:
assume cs:code
code segment
mov ax,2000h
mov ds,ax
mov al,[0]
mov bl,[1]
mov cl,[2]
mov dl,[3]
mov ax,4c00h
int 21h
code ends
end
loop和[bx]的联合应用
考虑一个问题:计算 ffff:0~ffff:b 单元中的数据和,结果存在dx中
1.运算后结果是否会超出dx能存储的范围
- ffff:0~ffff:b 存储字节型数据,范围在0~55之间,12个这样的数据相加不会大于65535,可以放下
2.能否将 ffff:0~ffff:b 中的数据累加到dx中?
- ffff:0~ffff:b 中的数据是8位的,不能直接加到16位寄存器dx中
3.能否将 ffff:0~ffff:b 中的数据累加到dl中,并设置 (dh)=0 ,从而实现累加到dx中?
- dl是八位寄存器,能容纳的数据范围在0~255之间,ffff:0~ffff:b 也是8位数据,如果仅向dl中累加12个8位数据,很可能造成进位丢失
4.到底怎么进行累加?
- 从上面得知由两个问题:类型的匹配和结果的不越界,具体来说就是在做加法的时候有两种方法
- (dx)=(dx)+内存中的8位数据
- (dl)=(dl)+内存中的8位数据
- 第一种方法中的问题是两个运算对象的类型不匹配,第二种方法中的问题是结果有可能越界
- 解决的方法就是拿一个16位的寄存器当作中介,将内存单元的8位数据赋值到一个16位的寄存器ax中,再将ax中的数据加到dx中,从而使两个运算对象的类型哦哦并且结果不会越界。
assume cs:code
code segment
mov ax,0fffh
mov ds,ax
mov dx,0
mov al,ds[0]
mov ah,0
add dx,ax
mov al,ds[1]
mov ah,0
add dx,ax
mov al,ds[2]
mov ah,0
add dx,ax
mov al,ds[3]
mov ah,0
add dx,ax
mov al,ds[4]
mov ah,0
add dx,ax
mov al,ds[5]
mov ah,0
add dx,ax
mov al,ds[6]
mov ah,0
add dx,ax
mov al,ds[7]
mov ah,0
add dx,ax
mov al,ds[8]
mov ah,0
add dx,ax
mov al,ds[9]
mov ah,0
add dx,ax
mov al,ds[0ah]
mov ah,0
add dx,ax
mov al,ds[0bh]
mov ah,0
add dx,ax
mov ax,4c00h
int 21h
code ends
end
使用loop循环实现
assume cs:code
code segment
mov ax,0ffffh
mov ds,ax
mov bx,0
mov dx,0
mov cx,12
s:
mov al,[bx]
mov ah,0
add dx,ax
inc bx
loop s
mov ax,4c00h
int 21h
code ends
end
段前缀
指令 mov ax,[bx] ,内存单元的偏移地址由bx给出,而段地址默认在ds,我们可以在访问内存单元的指令中显示的给出内存的单元的段地址所在的段寄存器,比如:
- mov ax,ds:[bx] 将一个内存单元的内容送入ax,这个内存单元的长度为2字节,存放一个字,偏移地址在bx中,段地址在ds中
- 这些出现在访问内存单元的指令中的ds ,在汇编语言中称为段前缀
一段安全的空间
在8086CPU中向随意一段内存空间写入内容很危险,因为可能存放系统数据或代码,比如
assume cs:code
code segment
mov ax,0
mov ds,ax
mov ds:[26h],ax
mov ax,4c00h
int 21h
code ends
end
将程序编译链接成exe文件,用debug加载,跟踪运行
其中 mov ds:[26h],ax 被masm翻译为机器码 a3 26 00 ,而debug将这个机器码解释为mov [0026],ax,可见二者同义
0:0026处存放着重要的系统数据,该命令将其改写,会在纯dos方式下引起死机。
我们在操作系统的环境中工作,操作系统管理所有的资源,我们向内存空间写入数据需要使用操作系统分配的空间,不应直接用地址任意指定内存单元;但是我们尽量直接对硬件编程
运行在CPU实模式的DOS没有能力对硬件系统进行全面、严格的管理,所以在纯DOS方式(实模式可以不理会DOS,直接用汇编语操作真实的硬件;但是在Windows 2000、unix这些运行于CPU保护模式下的操作系统中,硬件操作系统利用CPU保护模式严格管理,不理会操作系统之间操作真实的硬件是不可能的
总结
我们需要直接向一段内存写入内容,这段内存不应存放系统或其他程序的代码或数据,DOS方式下,一般情况 0:200~0:2ff 空间为空,可以使用
段前缀的使用
考虑一个问题:将内存 ffff:0~ffff:b 单元的数据复制到 0:200~0:20b 单元中
1.两个单元描述的是同一段内存空间
2.复制的过程应用循环实现,简要描述如下:
- 初始化,x=0
- 循环12次,将 ffff:x 单元的数据送入 0020:x (需要一个寄存器中转), x=x+1
3.在循环中,原始单元和目标单元的偏移地址x是变量,用bx来存放
4.将 0:200~0:20b 用 0020:0~0020:b miaoshu ,就是为了使目标单元的偏移地址二号原始地址的偏移地址从同一数值0开始
程序如下:
assume cs:code
code segment
mov bx,0
mov cx,12
s:
mov ax,0ffffh
mov ds,ax
mov dl,[bx]
mov ax,0020h
mov ds,ax
mov [bx],dl
inc bx
loop s
mov ax,4c00h
int 21h
code ends
end
因原始单元 ffff:x 和目标单元 0020:x 相距大于64KB,在不同的64Kb段李,每次循环要设置两次ds,效率不高,我们可以用两个段寄存器分别存放原始单元和目标单元的段地址,可以省略循环中需要重复做12次的设置ds的程序段,改进后如下:
assume cs:codesg
codesg segment
mov ax,0ffffh
mov ds,ax
mov dx,0
mov cx,12
mov ax,0020h
mov es,ax
s:
mov [dx],es:[dx]
inc dx
loop s
mov 4c00h
int 21h
codesg ends
end
使用es 存放目标空间的段地址,用ds存放原始空间的段地址,在访问内存空间的指令 mov es:[bx],al 中,显式的用段前缀 es: 给出单元的段地址,这样就不用在 循环中重复设置ds
熟悉[bx]和loop的使用
作业一:计算123*236。
作业二: 编程,向内存 0:200~0:23F 依次传送数据 0~63(3fh),程序中只能使用9条命令,包括 mov ax,4c00h和int21h。
0x02
往期笔记:
往期实战:
红队视角下的域森林突破:一场由Shiro反序列化引发的跨域控攻防对抗
域控接管后的“核爆效应”:基于DCSync与黄金票据的1600台主机权限收割
原文始发于微信公众号(伍六七安全):汇编语言Day04
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论