汇编五 - 汇编语言补充
知识点
- 学习环境为什么要选择VC6?
- 为了更好地学习细节,因为越是高版本的开发环境,在编译的时候替我们添加的额外代码越多,不利于我们学习
- VC6比较小,安装方便
- 凡是搞底层的人开发环境一定是VC6
- UltraEdit
- 用于查看二进制文件的一款PC软件。
- 熟练背会二进制与16进制之间的对应关系。
- 数据宽度
- 数学上的数字,是没有大小限制的,可以无限的大。但在计算机中,由于受硬件的制约,数据都是有长度限制的(我们称之为数据宽度),超过最多宽度的数据会被丢弃。
- 计算机中常见的数据宽度
- 位:bit,一个二进制位
- 字节(byte):8个bit,至少
- 字(word):16个bit
- 双字(doubleword):32个bit
- 原码、反码、补码
- 正数:三码合一
- 负数:
- 反码:符号位不变,其余所有二进制码取反。
- 补码:符号位不变,其余所有二进制码取反,然后再加1.
- 补码的补码等于源码。
- 计算机在内存中以补码的方式存储。
- 16位存储的方式
- 无符号数存储的范围:0~FF
- 有符号数存储的范围:
- 正数:0~7F
- 负数:FF~80
- 一个圆形被分割成2半,左边负数,右边正数。(注意,负数都是补码的形式存储的,FF就是-1)
- 计算机只会做位运算(与、或、非、异或、左移、右移动),不会四则运算
- 加:(计算机使用位运算实现加法)
- 2个数先异或,结果为X
- 判断是否有进位,用与判断,将2个数与的结果有值为Y,则说明有进位,没有值则没有进位
- 没有进位,这异或的结果X就是加法的值。
- 有进位
- 将Y左移一位,然后与X异或,得结果为Z
- 再次执行2,进行判断,直到没有进位,得到最终结果。
- 减:
- 本质还是加法运算
- 4-5 => 4+ (-5).
- -5的补码为FB
- 乘:本质是加法
- 除:本质是减法
- 如下图:

- 加:(计算机使用位运算实现加法)
- 学汇编的目的是为了了解程序运行的原理。
- 当学习一种新语法,不太理解时
- 直接转化成汇编,就能看到本质。
- 配置汇编的学习环境
- 下载DTDEBUG
- 百度搜索下载
DTDebug(VT-O)专业版V1.0.025
- 百度搜索下载
- 配置路径。
- 下载之后解压,打开文件夹,然后打开DTDebug文件夹
- 双击DTDebug.exe
- 然后,选择工具栏->options->appearance->Directories
- 设置udd路径,找到DTDebug文件加下的udd文件即可。
- 设置plugin路径,找到DTDebug文件加下的plugin文件即可。
- 然后关闭重新打开DTDebug即可。
- 窗口简介
- 工具栏打开View->CPU,然后展示一个窗口,这个窗口有4个区域
- 第一个区域:通常用于写汇编代码的区域,叫反汇编窗口
- 第二个区域:register(FPU),寄存器窗口。
- 第三个区域:左下角,为内存窗口
- 第四个区域:右下角,堆栈窗口
-
常用快捷键:
F8 :单步步过,单步执行 F7 : 单步步入,不跳跃函数单步执行。 F2: 设置断点 - 内存窗口的使用
- 这个内存窗口就是虚拟内存的4G空间。
-
可以输入命令查看内存的数据
db:一个字节一个字节的查看 db 18ff9c dw:2个字节2个字节的查看 dw 18ff9c dd: 4个字节4个字节的查看
- 寄存器窗口
- EFL为标志寄存器,以及内部存储的值
- EFL上面的:
C/P/A/Z/S/T/D/O为该寄存器的有效标志位, - 比如D为DF标志位,方向标志位。在数据串操作时确定操作的方向,EFL的第10位。
- 堆栈窗口
- 这里展示的就是一个应用进程启动后,操作系统分配的4G虚拟内存空间的堆栈段。
- 下载DTDEBUG
寄存器
- 寄存器有多大?
- 32位CPU: 8位、16位、32位,3种
- 64位CPU: 8位、16位、32位,64位,4种。
- 当前计算机的cup是多少位?
- 05年以后买的电脑都是64位的。
- 为何要学32位的汇编?
- 因为64位就是从32位拓展过来的,并没有整体变化。
- 只是增加了一些寄存器,汇编指令还是一样的。
通用寄存器
-
32位通用寄存器,共8个,每个32位
EAX ESP EBX EBP ECX ESI EDX EDI -
所有的通用寄存器

- 注意观察:ESP/EBP/ESI/EDI,这四个没有8位的寄存器。
- AH/BH/CH/DH分别是由AX/BX/CX/DX的高八位。
内存
- 虚拟内存映射到物理内存,注意物理内存还不是内存条,他还需要再一次映射到内存条中。

- 内存地址32位
-
往内存中写数据时一定要注明宽度
mov byte ptr ds:[0018fff0],1 常见宽带:BYTE /WORD /DWORD
内存地址在汇编中的5种表示形式
-
形式一:
[立即数]//从内存中读取数据 MOV EAX,DWORD PTR DS:[0X13FFC4] //向内存中写入数据 MOV DWORD PTR DS:[0X13FFC4] EAX, -
形式二:
[reg]reg代表寄存器,可以是8个通用寄存器中的任意一个。读取内存的值: ;立即数(内存地址)传到ECX寄存器 MOV ECX,0X13FFD0 ;速去内存的值 MOV EAX,DWORD PTR DS:[ECX] 向内存中写入数据 MOV EDX,0X13FFD8 ;将立即数存入内存 MOV DWORD PTR DS:[EDX],0X87654321 -
形式三:
[reg+立即数]读取内存的值: MOV ECX,0X13FFD0 ;速去内存的值 MOV EAX,DWORD PTR DS:[ECX+4] 向内存中写入数据 MOV EDX,0X13FFD8 ;将立即数存入内存 MOV DWORD PTR DS:[EDX+0xC],0X87654321 - 形式四:
[reg+reg*{1,2,4,8}]- 乘的数字只能是1、2、4、8
读取内存的值: MOV EAX,13FFC4 MOV ECX,2 MOV EDX,DWORD PTR DS:[EAX+ECX*4] -
形式五:
[reg+ret*{1,2,4,8}+立即数]读取内存的值: MOV EAX,13FFC4 MOV ECX,2 MOV EDX,DWORD PTR DS:[EAX+ECX*4+4]
数据的存储模式(大小端)
- x86 的cpu采用的是小端模式存储的
- arm 采用的是大端模式存储
常用汇编指令
- mov指令
;把一个8位的寄存器的值,存入到一个8位寄存器或者8位的内存中,比如:mov byte ptr ds:[0x13FFC4],AL MOV r/m8,r8 r代表通用寄存器 MOV r/m16,r16 m代表内存 MOV r/m32,r32 imm代表立即数 MOV r8,r/m8 r8代表8位通用寄存器 MOV r16,r/m16 m8代表8位内存 MOV r32,r/m32 imm8代表8位立即数 MOV r8,imm8 MOV r16,imm16 MOV r32,imm32 - movs指令
-
mov指令是不允许内存->内存移动数据的,但是movs指令可以
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 - ESI/EDI的第二作用:
- 当要把一串数据从内存的一个地方复制到内存的另一个地方,这时就要用到ESI/EDI
- 即只要用MOVS指令,那么就要用到ESI/EDI
- ESI存储的是要复制的数据地址,EDI存储的是要存放的数据地址。
- 每执行一次MOVS指令,EDI/ESI中的内存地址就会相应的自动增加一次。
- FEL标志寄存器的第10位DF,叫做方向位
- 当DF为0时,每执行一次MOVS,EDI/ESI自动增加一次
- 当DF为1是,每执行一次MOVS,EDI/ESI自动减一次
-
- stos指令
-
将AL/AX/EAX值存储到[EDI]指定的内存单元。
STOS BYTE PTR ES:[EDI] ;简写为STOSB STOS WORD PTR ES:[EDI] ;简写为STOSW STOS DWORD PTR ES:[EDI] ;简写为STOSD -
执行完STOS指令后,EDI值也会加一次,至于加还是减取决于标志寄存器的DF位。
-
- REP指令:
-
按照计数寄存器ECX中指定的次数重复执行字符串指令。
MOV ECX,10 REP MOVSD REP STOSD -
每执行一次,ECX中的值会减1,直到0为止。
-
堆栈操作指令
- 堆栈段就是一块内存,操作系统在程序启动的时候就已经分配好的,供程序执行的时候使用。
- 通过DTDEBUG查看堆栈窗口
- 在DTDEBUG的堆栈窗口展示的,就是当前应用程序由操作系统分配的堆栈区域
- 也可以从内存窗口中查看
- 寄存器窗口中有个FS寄存器,假设内部存储的值为
7EFDD000 -
然后在内存窗口输入命令:
dd 7EFDD0007EFDD000 FFFFFFFF (Pointer to SEH chain) 7EFDD004 00190000 (Top of thread's stack) 7EFDD008 0018D000 (Bottom of thread's stack) - 可以看到,系统分配的堆栈的内存地址为:
0018D000~00190000这段内存空间。 - 然后查看堆栈窗口的内存地址为:
0018D000~0018FFC,0018FFC+4 = 00190000,所以正好为这段内存。
- 寄存器窗口中有个FS寄存器,假设内部存储的值为
- 堆栈内存,是从大往小开始用的,即先用
0018FFC - 可以通过查看esp寄存器,中存储的值,查看当前堆栈用到哪了
- 右击esp->follow in stack ,可以定位到堆栈窗口的相应位置。
- 那么此时的堆栈段:从当前位置到0018FFC这段堆栈已经被之前的程序用掉了。
- 如何使用堆栈?
- 使用push、pop指令。
- 作用:
- 将数据存、取到堆栈段
- 修改ESP寄存器的值。
修改EIP的指令
- EIP寄存器存储的是CPU要执行的下一套指令的值。
- 通过汇编语言不能直接修改EIP的值。
- 通过jmp指令、call指令
- JMP指令的本质
- 修改EIP的值为JMP的值
- call指令的本质
- 修改EIP的值为CALL指令的值
- 将下一条指令的值放入栈中。
- ESP的值减4
- JMP指令的本质
- RET指令
- ESP值+4
- 将堆栈的值存储到EIP
fake反调试
- 反调试是什么?
- 不让别人调试我的代码,比如F8单步执行,但是不允许别人使用F8调试。
- Fake F8:伪装F8,让F8不能调试。
- 断点的原理
- 断点:0xCC
- 设置断点:F2
- 断点的本质就是指令:
int 3,当CPU执行这个指令是就会停下来 - F2设置断点,就是将当前代码设置为int 3;对应的16进制码为0XCC;
- 断点:0xCC
- F7与F8的区别
- 表面区别:
- 在执行call指令时有区别,F8会直接跳过call调用的函数
- F7会进入函数内部一步一步执行。
- 本质区别
- 单步步入(F7):设置EFLAGS标志寄存器的TF为1,不是设置断点。
- 单步步过(F8):在下一行设置断点。所以F8在执行call指令的时候会直接跳过
- 表面区别:
- 如何实现让F8失效?
-
编写call指令:
CALL 0041840d -
在调用函数地址处添加如下代码
MOV DWORD PTR DS:[ESP],00418481 RET -
这么一写如果按得是F8,那么,就会直接执行call函数,但是下一条代码可不是原来CALL下面的代码了,而是这个地址(00418481)的代码。这么一来就跟飞了
- 因为调用call函数,会把下一条指令地址放入栈中,就是DS:[ESP]这个内存单元,但是由于你执行了
MOV DWORD PTR DS:[ESP],00418481,把他改变了,那么当再次执行ret的时候,把栈中的数据返回给EIP的时候,就是00418481了,程序就会从这里执行了
- 因为调用call函数,会把下一条指令地址放入栈中,就是DS:[ESP]这个内存单元,但是由于你执行了
-
JCC指令
-
标志寄存器

- 单步标志:用于调试器
- 中断标志:中级才会使用
- AF为很少用,主要用与BCD运算时用。
- ZF位工作中用到的最多