2.9内存复制
2.9内存复制
本节必须掌握的知识点:
掌握MOVS指令、STOS指令、REP指令的格式、功能
在上一节中介绍了部分常用指令,本节继续介绍几个常用指令,看到标题大家肯定能猜到接下来介绍的常用指令肯定和内存有关系。到目前为止我们知道与内存有关系的指令,MOV指令,它是移动指令,可以从内存到寄存器,从寄存器到寄存器,从寄存器到内存,并不能从内存到内存,接下来介绍的这些指令它们可以做到从内存到内存的。
2.9.1【MOVS指令】
MOVS指令串传送指令,用于传送字符串。
知道了MOVS指令的用途,那它主要做什么工作呢?在程序运行过程中,经常要复制大片内存或者初始化某段内存时,MOV指令在32位汇编中一次最多只能操作4个字节,且不能做到直接从内存复制到内存,必须借用寄存器才能将一块内存的数据复制到另一块内存中,而MOVS指令就可以直接做到从一块内存复制到另一块内存,使程序员效率高了些。
MOVS的格式如下:
1、MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI] 简写MOVSB
2、MOVS WORD PTR ES:[EDI],WORD PTR DS:[ESI] 简写MOVSW
3、MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI] 简写MOVSD
【注意:MOVS指令只能使用EDI、ESI这两个通用寄存器,不能使用其他寄存器。当我们要把内存中的数据从一个地方复制到一个地方,我们就使用这些特定的串指令,EDI、ESI分别存储需要复制和被复制的两块内存单元地址,这里被复制的内存单元地址为[ESI],复制到的内存单元地址为[EDI]。MOVS一次可以复制一个字节、两个字节和四个字节。】
说了这么多知识点,我们来点干货,借用DTDebug.exe软件,动手做实验。
第一步:打开DTDebug.exe软件,将要调试的软件拖进DTDebug.exe中。【本节调试的软件为GeePlayer.exe】注:可以调试任意其他软件,步骤相同。
第二步:看图2-9-1所示,ESI存储的数据为0x00000000,EDI存储的数据为0x00000000,需要用到两个已经申请的内存地址,需要从堆栈窗口中找,且记住他们存储的数据。用内存地址0x0018F854它存储的数据为0x00EB2BA2及内存地址0x0018F860它存储的数据为0x00000000。接下来我们修改ESI和EDI的数据,分别将两个内存地址写到ESI和EDI中。ESI的数据修改为0x0018F854,将EDI的数据修改为0x0018F860,如图2-9-2所示。
第三步:输入汇编指令,MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]或MOVSB。按一个字节复制,如图2-9-3所示。
第四步:按F8执行,观察ESI和EDI存储的数据有什么变化及内存地址0x0018F854和内存地址0x0018F860它们存储的数据有什么变化,如图2-9-4所示。
我们观察图2-9-4,当按F8执行完后,看到了寄存器窗口中ESI存储的数据发生了变化,从0x0018F854变成了0x0018F855,EDI存储的数据从0x0018F860变成了0x0018F861。堆栈窗口中内存地址0x0018F854的数据没有发生变化,而内存地址0x0018F860的数据发生了变化由数据0x00000000变成了数据0x000000A2。由于我们是按1个字节复制的,所以ESI和EDI存储的数据会自加1,数据也是复制一个字节。
我们用同样的步骤对MOVSW操作观察有什么变化。
第一步:输入指令MOVSW,修改ESI、EDI的数据,ESI存储的数据为0x0018F854,ESI存储的数据为0x0018F85C,内存地址0x0018F854存储的数据为0x00EB2BA2,内存地址0x0018F85C存储的数据为0x00000000如图2-9-5所示。
第二步:按F8执行,并观察各数据的变化,如图2-9-6所示。
我们观察图2-9-6,当按F8执行完后,看到了寄存器窗口中ESI存储的数据发生了变化,从0x0018F854变成了0x0018F856,EDI存储的数据,从0x0018F85C变成了0x0018F85E。堆栈窗口中内存地址0x0018F854的数据没有发生变化,而内存地址0x0018F85C的数据发生了变化,由数据0x00000000变成了数据0x00002BA2。由于我们是按2个字节复制的,所以ESI和EDI存储的数据会自加2,数据也是复制2个字节。
对MOVSB和MOVSW分别做了实验,为了锻炼大家的动手能力,这里将不再对MOVSD做实验,希望大家能总结MOVSD的工作原理。
总结:根据对MOVSB和MOVSW的实验我们得出,MOVS指令会根据操作1字节、2字节、4字节相应的改变ESI和EDI存储的数据变化,会自动增加1、2、4。相应的内存地址存储的数据也会复制1个字节、2字节、4字节。
看到这大家有没有想过,MOVS执行后只会自增,如何才能让它自减哪?有一个寄存器可以帮忙,就是标志寄存器——EFLAGS,它的每一位代表不同的含义,后面会详细介绍它每一位代表什么。这里介绍它的第10位——DF位,也就是方向位。如果是0的时候,我们使用MOVS指令执行完后,ESI和EDI会增加相应的宽度,如果是1,表示MOVS指令执行完后,EDI和ESI会减少相应的宽度。如图2-9-7:DF位。
我们只需要双击DF位对应的数据0,那么DF位就变成了1,当然这是我们自己调试程序时用到的方法。如图2-9-8手动修改DF位。
我们实验一下,修改了DF位通过执行MOVS指令是否真的自减相应的宽度哪?
第一步:输入指令MOVSB,使用当前ESI和EDI存储的数据,如图2-9-9所示。
第二步:按F8执行,观察各数据变化,如图2-9-10所示。
F8执行完后,发现ESI和EDI存储的数据都自减1,而内存地址里对应的数据自动复制了1个字节。
如果我们批量的处理MOVS指令,相应的ESI和EDI 会增加或减少相应字节宽度,相应的内存地址也会相应的改变。自己动手观察下面的例题。
例:
主要观察ESI和EDI存储的数据变化及相关内存地址的变化。
当DF位为0时,输入以下指令:
MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
MOVS WORD PTR ES:[EDI],WORD PTR DS:[ESI]
MOVS WORD PTR ES:[EDI],WORD PTR DS:[ESI]
MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]
MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]
当DF位为1时,输入以下指令:
MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
MOVS WORD PTR ES:[EDI],WORD PTR DS:[ESI]
MOVS WORD PTR ES:[EDI],WORD PTR DS:[ESI]
MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]
MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]
ESI、EDI 能自增自减,这样有什么好处呢?好处就是假如我们需要复制大片内存,只需要重复编写MOVSD就可以了,接下来介绍STOS指令。
【STOS指令】
STOS指令:这个指令通常用于将某一块内存设置为特定的值。它是将AI/AX/EAX寄存器里面的值复制到[EDI]中,执行完后EDI增加或减少相应的宽度的字节。[EDI]表示把EDI中的数据当成是一个内存地址,可以说它是一个指针,指向某一块内存。它的格式如下:
[if !supportLists]1、[endif]STOS BYTE PTR ES:[EDI]简写为STOSB
[if !supportLists]2、[endif]STOS WORD PTR ES:[EDI]简写为STOSW
[if !supportLists]3、[endif]STOS DWORD PTR ES:[EDI]简写为STOSD
我们同样借助DTDebug.exe软件做实验。
第一步:打开软件,将要调试的软件拖进DTDebug.exe中,如图2-9-11所示。
第二步:输入以下指令,此时DF位为0,如图2-9-12所示。
MOV EAX,0x11223344
MOV EDI,0x0018FCEC
STOS BYTE PTR ES:[EDI] //EDI的值+1
STOS WORD PTR ES:[EDI //EDI的值+2
STOS DWORD PTR ES:[EDI] //EDI的值+4
第三步:单步按F8观察,如图2-9-12、2-9-13、2-9-14、2-9-15、2-9-16所示。
当DF位为1时,输入一下指令。
MOV EAX,0x55667788
MOV EDI,0x0018FCEC
STOS BYTE PTR ES:[EDI] //EDI的值-1
STOS WORD PTR ES:[EDI //EDI的值-2
STOS DWORD PTR ES:[EDI] //EDI的值-4
通过以上实验,我们总结:
当DF位为0时,STOS指令会把AL/AX/EAX存器存储的数据,存储到[EDI]指定的内存地址相对应的数据中去。相应的EDI存储的数据会增加相应的宽度1、2、4,直至AL/AX/EAX存器存储的数据变为0;
当DF位为1时,STOS指令会把AL/AX/EAX寄存器存储的数据,存储到[EDI]指定的内存地址相对应的数据中去。相应的EDI存储的数据会减少相应的宽度1、2、4,直至AL/AX/EAX存器存储的数据变为0。
我们通常用STOS来进行初始化,我们将需要初始化的值放在AI/AX/EAX寄存器中,将需要初始化的内存地址储存到EDI中。
我们介绍了2个指令一个MOVS指令、一个STOS指令,这两个指令都是为了对大量连续内存进行操作的,但是它们本身并不能重复操作,难道我们工作中要手动一行一行的输入这些指令吗?答案是否定的,所以就有了REP指令。什么是REP指令哪?我们接下来介绍REP指令。
【REP指令】
REP指令:按计数寄存器(ECX)中指定的次数重复执行字符串指令。
我们同样借助DTDebug.exe软件做实验。
第一步:打开软件,将要调试的软件拖进DTDebug.exe中。
第二步:输入以下指令,并修改ESI和EDI存储的数据。ESI改为0x0018FDE0,EDI改为0x0018FE00如图2-9-17所示:
MOV ECX,10
REP MOVSD
第三步:按F8执行并观察数据变化,如图2-9-18、2-9-19、2-9-20所示:
接下来仔细观察REP MOVS指令的变化,由于我们复制的次数较多,按F8单步跳过(一次执行完),按F7是单步步入执行(每使用一次F7,代表程序单步执行一次),这样我们可以看到使用rep指令时,每执行一次, ECX减1,如图2-9-19(按F7)、2-9-20(按F7)、2-9-21(按F8)。
如图2-9-19第一次按F7执行,ECX当前值为F,看到ESI存储的数据变成了0x0018FDE4,EDI存储的数据变成了0x0018FE04,内存地址0x0018FDE0存储的数据没有变化,而内存地址0x0018FE00存储的数据变成了00EB2BA2。
如图2-9-20第而次按F7执行,ECX当前值为E,看到ESI存储的数据变成了0x0018FDE8,EDI存储的数据变成了0x0018FE08,内存地址0x0018FDE4存储的数据没有变化,而内存地址0x0018FE04存储的数据变成了0x11111111。
如图2-9-21按F8执行完,ECX当前值为0,看到ESI存储的数据变成了0x0018FE20,EDI存储的数据变成了0x0018FE40,相对应的内存地址的数据发生了变化。
根据实验我们得出了,当前DF位为0,REP MOVSD指令每按一次F7,ECX存储的数据减少1,对应的ESI和EDI的数据会增加4,而相对应的内存地址存储的数据复制到将要被存储的内存地址中去,且每次内存地址都会自动往下移动4个字节;
当前DF位为1,REP MOVSD指令每按一次F7,ECX存储的数据减少1,对应的ESI和EDI的数据会减4,而相对应的内存地址存储的数据复制到将要被存储的内存地址中去,且每次内存地址都会自动往上移动4个字节。
下一节为介绍堆栈相关指令。
练习:
1、使用 MOVS 指令从一个地址向另一个地址复制0x10字节的数据,分别对DF位为0时、DF位为1时做实验,并能自己总结出MOVS的工作原理
2、向某块内存中填充0x44个 0xCC,分别对DF位为0时、DF位为1时做实验。