汇编笔记

进制

进制的本质不是运算,而是对应规则,俗称查表。无论什么进制,都有自己的一套方法,然后,只要做出加法表,乘法表,便可以加减乘除。如果想像十进制一样熟练计算,那么直需要像上小学那样背诵加法表和乘法表就可以了。

数据宽度/逻辑运算

计算机中,任何存储的数据都是有宽度的,有长度限制。如果超出界限,数据便会丢弃。

缩写 中文 位数
BYTE 字节 8BIT
WORD 16BIT
DWORD 双字节 32BIT

计算机CPU计算加法的方法是:先对两个二进制数X,Y进行二进制的xor异或运算,用R存储数据;再进行X,Y的and运算并向左移一位,用。如果移位之后,不是为0,那么继续运算。直到为0为止,那么异或的结果就是答案。

想测试自己是否真的理解,只需要计算2+3与2-3的结果是否正确即可。

下面这幅图除了主要用途不用背诵外,其他的都要背的滚瓜烂熟。之所以不需要背诵主要用途,是一因为这个主要用途是可以改变的。

汇编笔记_第1张图片
需要背诵

需要熟练记住二进制与十六进制的对应关系,方便理解计算机的无符号数与正数负数的关系。

0x01  ——  0001    |     0x10  ——  0001 0000
0x02  ——  0010    |     0x11  ——  0001 0001
0x03  ——  0011    |     0x12  ——  0001 0010
0x04  ——  0100    |     0x13  ——  0001 0011
0x05  ——  0101    |     0x14  ——  0001 0100
0x06  ——  0110    |     0x15  ——  0001 0101
0x07  ——  0111    |     0x16  ——  0001 0110
0x08  ——  1000    |     0x17  ——  0001 0111
0x09  ——  1001    |     0x18  ——  0001 1000
0x0A  ——  1010    |     0x19  ——  0001 1001
0x0B  ——  1011    |     0x1A  ——  0001 1010
0x0C  ——  1100    |     0x1B  ——  0001 1011
0x0D  ——  1101    |     0x1C  ——  0001 1100
0x0E  ——  1110    |     0x1D  ——  0001 1101
0x0F  ——  1111    |     0x1E  ——  0001 1110
                  |     0x1F  ——  0001 1111

深刻理解下面这幅图,就能理解计算机存储正负数据的方式。

汇编笔记_第2张图片
正负数存储图

汇编指令,用于修改寄存器以及其中的数据。

汇编笔记_第3张图片
ollydbg修改寄存器指令
MOV EAX,0x45 

MOV用来移动数据到某个寄存器中,这里的0x45位置属于源,而EAX属于目标。总的来说,就是将源中的这个0x45数据移动到EAX寄存器中。

通用寄存器/内存读写

下面的这个32位寄存器和16位寄存器以及8位的寄存器都需要按顺序记住。并且,要知道,不同位数的寄存器不是独立的,32位里面包含16位,16位里面又分高位,低位。

汇编笔记_第4张图片
通用寄存器结构图

可以通过ollydbg形象的看到这样的变化。不过,就是初次学习,容易将十六进制和位数搞混。

汇编笔记_第5张图片
观察不同位数的寄存器的结构
汇编笔记_第6张图片
MOV命令用法

就像杯子装水的原理一样,八位的寄存器里面最多填入两个十六进制的数,但是也可以只填一个字符的十六进制数,没填满默认在前面补0

汇编笔记_第7张图片
MOV语法

加法和减法的用法,和MOV类似。但是,还是要记住,杯子装水,寄存器内的大小和操作的大小。

MOV MAX,0x1    让MAX寄存器中的值为1
ADD MAX,0x1    让MAX寄存器中的值增加1,并存入MAX寄存器中
SUB MAX,0x1    让MAX寄存器中的值减1,并存入MAX寄存器中

如果结果为负数,参考我理解的那个圆。之前那个圆理解错了。实际上如果寄存器中存的是有符号的数字,那么那个圆两边的数除去符号后是轴对称关系。

汇编笔记_第8张图片
计算机常用计量单位

计算机是32位还是64位,是按寄存器的寻址地址来计算的,而不是说按寄存器的宽度进行计算。

32位计算机之所以即使装了8G内存,还是只能识别出4G,那是因为32为计算机中,内存寻址最大也就是(0xFFFFFFFF+1)
而(0xFFFFFFFF+1)/1024得到的是kb(千字节),再除以1024得到M,再除以1024得到G,最后结果就是4G
需要注意的时候,计算除以1024前,十六进制要转换成十进制。

但是,如果说32位操作系统打了特定的补丁,那么就可能可以识别出8G内存。正常情况下,32位操作系统只能识别4G内存。

[0x123456]    内存编号
0x123456    立即数
MOV WORD PTR DST:[0x12345678],0x1234
这里只能0x1234是因为表示向内存中写16位立即数,看前面word
同理,也可以修改成BYTE,或者DWORD

内存寻址/堆栈

学了一个新的汇编命令LEA
语法和MOV类似,只不过,获得的不在是寄存器中的值,而是寄存器的地址。

LEA ESP,DWORD PTR DS:[0x0018FFFB]
将该32位(四个字节)内存地址存到ESP寄存器中

注意看下面这幅图,很容易理解错误!这里LEA执行的命令是先获得ECX的值,将这个值当作地址来赋值给EAX。

容易理解错误的地方

地址是可以计算的,下面会有一些计算的语法

汇编笔记_第9张图片
乘以的数只能是1,2,4,8

下面接触到了堆栈的详细概念,有那么一点不懂

汇编笔记_第10张图片
堆栈

想知道自己懂不懂,自己手动实现一下堆栈,去调试

PUSH EAX    在堆栈中压入新的值,值是EAX寄存器中的值,并且栈顶向上移动,即ESP-4
POP EAX    将栈顶所指的地址的值存到EAX中,然后栈顶向下移动
PUSHAD    将8个通用寄存器中的值全都存到堆栈中
POPAD    将堆栈中存的8个通用寄存器的值都返回到那8个寄存器中

上面这样做的好处是,假设一开始不需要用到8个32位通用寄存器的值,但是后期可能会用到,那么就先存到堆栈中,然后就可以对8个通用寄存器为所欲为,为所欲为完了之后,再POPAD指令,那么那8个寄存器就又恢复到PUSHAD到堆栈中的样子了

EFLAGS寄存器

汇编笔记_第11张图片
需要背诵几个字母以及位数

标记蓝色的位都是无法控制的。需要记住的是CF/PF/AF/ZF/SF/OF这几个位

上面叫做标志寄存器,需要注意的是,下面关于标志寄存器的每个操作,都要注意数据宽度!

CF    只有运算结果最高位产生了一个进位或借位,CF的值才为1,否则为0。
注意:最高位是由数据宽度决定的!
PF    奇偶标志,用来判断运算结果中‘1’的个数的奇偶性,‘1’的个数为偶数,那么PF位为1
(但是!PF判断依据只是最低有效字节,即只是根据最后八位来判断,如果用ax寄存器而不是al/ah,会发现有时候即便是偶数,可是PF还是为0)
注意:这里判断的1是二进制情况下的判断
AF    辅助进位标志。假设数据宽度为8位,那么第四位进位,AF就为1
注意:AF是否为1,看得是数据宽度一半的那个值是否要进位

注意看下图的箭头处,EFL的值实际上就是上面要背诵的那幅图的16进制写法,将202转成二进制后,对应上面那幅图就是:
0010 0000 0010

汇编笔记_第12张图片
自己要会拆位

之所以要会拆位,是因为有的地方没有ollydgb那样C\P\A等等这样拆开的,是直接用EFL表示,所以要会自己拆。

XOR EAX,EAX    利用异或运算把EAX清零

ZF    零标志ZF,如果运算结果位数全为0,则ZF为1,否则ZF为0
SF    与运算结果的最高位相同(二进制)
OF    溢出标志符,有符号计算看OF,无符号计算看CF

先看数据宽度,如果无符号运算,那么看计算结果有没有超出数据宽度,如果是有符号计算,那么看计算结果有没有超过数据宽度一半,超过了就说明溢出来。

汇编笔记_第13张图片
计算是否溢出

上面是否溢出,可以参考那个圆,要深刻理解那个圆,就知道计算结果是否溢出。

ADC带移位的加法,所谓带移位的加法,就是在执行这个指令之前,手动将CF位设置为1,然后在运算的时候,带上CF的进位去做运算。最后,将运算之后的结果存到ADC指令的第一个参数里。

汇编笔记_第14张图片
带移位的加法

同理,SBB带借位的减法,运算后也会存到SBB的第一个参数中。
上面的计算操作,前提都是先将CF位变成了1

XCHG是交换指令,将两个参数的内部的值进行交换。
例如:XCHG AL,CL

下面这个指令有点特殊,移动的两个目标的数据宽度必须一致

汇编笔记_第15张图片
可以移动内存到内存

实际上这个参数就是将两个通用寄存器中存储的地址,全都向一个方向进行4/2/1的移位,若DF位为0,则代表向堆栈下方移位,若DF为1则代表向堆栈上方移位。而堆栈是越向下,内存地址越大。

而MOVS操作的EDI,ESI,存储的都是堆栈中的地址编号

汇编笔记_第16张图片
STOS语法
汇编笔记_第17张图片
注意用这个指令的时候,需要[EDI]前面用的是ES,不是DS

STOS这个指令就是将EAX中的值,存储到EDI寄存器中的那个地址中去,之后再将EDI中存储的值进行移位,移位多少,看存多大的数据。存多大的数据,看用的是STOSB/STOSW/STOSD哪一个。至于移位的方向,看DF的值为1还是0

汇编笔记_第18张图片
REP指令语法

JCC

汇编笔记_第19张图片
判断是否有溢出
jmp   寄存器/立即数
修改EIP的值为jmp后的参数,让cpu执行相关地址的汇编指令

call  寄存器/立即数
call使用的时候,需要将call跳转的那个地址打断点

retn    回到call指令存在的那个地址的下一个地址,retn的本质就是POP EIP

jmp是直接跳,只修改EIP的值,而call配合retn还会回到call指令的下一个地址。call感觉是调用某个函数,调用完了之后,还会回到程序执行的下面流程。而EIP的值,不能用mov修改。

汇编笔记_第20张图片
比较指令
汇编笔记_第21张图片
注意ZF和SF位的变化

根据自己尝试发现,当执行 cmp 操作数1,操作数2 的时候,若操作数1大于操作数2,那么ZF位为0,SF位也为0;当操作数1小于操作数2的时候,那么ZF为0,SF为1;当操作数1等于操作数2的时候,ZF位为1,SF为0。

总的来说,cmp指令真的就像是在做sub减法一样,只不过没有将结果存到操作数1中

TEST指令语法

TEST指令就是用来判断某个寄存器中的值是否为空,因为TEST用的是与运算,只有当同一个寄存器中,值为0的时候,与出来的结果才为0,ZF位才为0
TEST 寄存器1,寄存器1

下面的内容需要熟记,推导比较麻烦,所以目前先都记住。

JE/JZ    结果相等时跳转/结果为0时跳转    ZF=1
这两个指令本质上是相同的,都是看ZF位的值,来决定是否跳转

JNE/JNZ    结果不相等时跳转/结果不为0时跳转    ZF=0
这两个指令就是和上面正好相反
汇编笔记_第22张图片
JCC指令表

堆栈图

call执行完后,向堆栈中压入的值,叫做函数的返回地址

堆栈平衡指的是使用完一个堆栈后,堆栈和原来的堆栈没区别。

堆栈图个人感觉自己能根据汇编指令画出来了。但是不知道是不是真的懂了。。。以后如果不懂的话,那么这里还需要再画一遍。

实际上,只要前面的指令记得滚瓜烂熟,那么堆栈图这里就不是问题。

汇编笔记_第23张图片
需要深刻理解堆栈的变化

按F8和F7的区别就在于,F8是一条一条的指令执行,而F7则是会跟着函数去执行

汇编部分终于结束了。JCC的指令还需要查表,其他的得多复习,堆栈是重点。

你可能感兴趣的:(汇编笔记)