进制
进制的本质不是运算,而是对应规则,俗称查表。无论什么进制,都有自己的一套方法,然后,只要做出加法表,乘法表,便可以加减乘除。如果想像十进制一样熟练计算,那么直需要像上小学那样背诵加法表和乘法表就可以了。
数据宽度/逻辑运算
计算机中,任何存储的数据都是有宽度的,有长度限制。如果超出界限,数据便会丢弃。
缩写 | 中文 | 位数 |
---|---|---|
BYTE | 字节 | 8BIT |
WORD | 字 | 16BIT |
DWORD | 双字节 | 32BIT |
计算机CPU计算加法的方法是:先对两个二进制数X,Y进行二进制的xor异或运算,用R存储数据;再进行X,Y的and运算并向左移一位,用。如果移位之后,不是为0,那么继续运算。直到为0为止,那么异或的结果就是答案。
想测试自己是否真的理解,只需要计算2+3与2-3的结果是否正确即可。
下面这幅图除了主要用途不用背诵外,其他的都要背的滚瓜烂熟。之所以不需要背诵主要用途,是一因为这个主要用途是可以改变的。
需要熟练记住二进制与十六进制的对应关系,方便理解计算机的无符号数与正数负数的关系。
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
深刻理解下面这幅图,就能理解计算机存储正负数据的方式。
汇编指令,用于修改寄存器以及其中的数据。
MOV EAX,0x45
MOV用来移动数据到某个寄存器中,这里的0x45位置属于源,而EAX属于目标。总的来说,就是将源中的这个0x45数据移动到EAX寄存器中。
通用寄存器/内存读写
下面的这个32位寄存器和16位寄存器以及8位的寄存器都需要按顺序记住。并且,要知道,不同位数的寄存器不是独立的,32位里面包含16位,16位里面又分高位,低位。
可以通过ollydbg形象的看到这样的变化。不过,就是初次学习,容易将十六进制和位数搞混。
就像杯子装水的原理一样,八位的寄存器里面最多填入两个十六进制的数,但是也可以只填一个字符的十六进制数,没填满默认在前面补0
加法和减法的用法,和MOV类似。但是,还是要记住,杯子装水,寄存器内的大小和操作的大小。
MOV MAX,0x1 让MAX寄存器中的值为1
ADD MAX,0x1 让MAX寄存器中的值增加1,并存入MAX寄存器中
SUB MAX,0x1 让MAX寄存器中的值减1,并存入MAX寄存器中
如果结果为负数,参考我理解的那个圆。之前那个圆理解错了。实际上如果寄存器中存的是有符号的数字,那么那个圆两边的数除去符号后是轴对称关系。
计算机是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。
地址是可以计算的,下面会有一些计算的语法
下面接触到了堆栈的详细概念,有那么一点不懂
想知道自己懂不懂,自己手动实现一下堆栈,去调试
PUSH EAX 在堆栈中压入新的值,值是EAX寄存器中的值,并且栈顶向上移动,即ESP-4
POP EAX 将栈顶所指的地址的值存到EAX中,然后栈顶向下移动
PUSHAD 将8个通用寄存器中的值全都存到堆栈中
POPAD 将堆栈中存的8个通用寄存器的值都返回到那8个寄存器中
上面这样做的好处是,假设一开始不需要用到8个32位通用寄存器的值,但是后期可能会用到,那么就先存到堆栈中,然后就可以对8个通用寄存器为所欲为,为所欲为完了之后,再POPAD指令,那么那8个寄存器就又恢复到PUSHAD到堆栈中的样子了
EFLAGS寄存器
标记蓝色的位都是无法控制的。需要记住的是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
之所以要会拆位,是因为有的地方没有ollydgb那样C\P\A等等这样拆开的,是直接用EFL表示,所以要会自己拆。
XOR EAX,EAX 利用异或运算把EAX清零
ZF 零标志ZF,如果运算结果位数全为0,则ZF为1,否则ZF为0
SF 与运算结果的最高位相同(二进制)
OF 溢出标志符,有符号计算看OF,无符号计算看CF
先看数据宽度,如果无符号运算,那么看计算结果有没有超出数据宽度,如果是有符号计算,那么看计算结果有没有超过数据宽度一半,超过了就说明溢出来。
上面是否溢出,可以参考那个圆,要深刻理解那个圆,就知道计算结果是否溢出。
ADC带移位的加法,所谓带移位的加法,就是在执行这个指令之前,手动将CF位设置为1,然后在运算的时候,带上CF的进位去做运算。最后,将运算之后的结果存到ADC指令的第一个参数里。
同理,SBB带借位的减法,运算后也会存到SBB的第一个参数中。
上面的计算操作,前提都是先将CF位变成了1
XCHG是交换指令,将两个参数的内部的值进行交换。
例如:XCHG AL,CL
下面这个指令有点特殊,移动的两个目标的数据宽度必须一致
实际上这个参数就是将两个通用寄存器中存储的地址,全都向一个方向进行4/2/1的移位,若DF位为0,则代表向堆栈下方移位,若DF为1则代表向堆栈上方移位。而堆栈是越向下,内存地址越大。
而MOVS操作的EDI,ESI,存储的都是堆栈中的地址编号
STOS这个指令就是将EAX中的值,存储到EDI寄存器中的那个地址中去,之后再将EDI中存储的值进行移位,移位多少,看存多大的数据。存多大的数据,看用的是STOSB/STOSW/STOSD哪一个。至于移位的方向,看DF的值为1还是0
JCC
jmp 寄存器/立即数
修改EIP的值为jmp后的参数,让cpu执行相关地址的汇编指令
call 寄存器/立即数
call使用的时候,需要将call跳转的那个地址打断点
retn 回到call指令存在的那个地址的下一个地址,retn的本质就是POP EIP
jmp是直接跳,只修改EIP的值,而call配合retn还会回到call指令的下一个地址。call感觉是调用某个函数,调用完了之后,还会回到程序执行的下面流程。而EIP的值,不能用mov修改。
根据自己尝试发现,当执行 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用的是与运算,只有当同一个寄存器中,值为0的时候,与出来的结果才为0,ZF位才为0
TEST 寄存器1,寄存器1
下面的内容需要熟记,推导比较麻烦,所以目前先都记住。
JE/JZ 结果相等时跳转/结果为0时跳转 ZF=1
这两个指令本质上是相同的,都是看ZF位的值,来决定是否跳转
JNE/JNZ 结果不相等时跳转/结果不为0时跳转 ZF=0
这两个指令就是和上面正好相反
堆栈图
call执行完后,向堆栈中压入的值,叫做函数的返回地址
堆栈平衡指的是使用完一个堆栈后,堆栈和原来的堆栈没区别。
堆栈图个人感觉自己能根据汇编指令画出来了。但是不知道是不是真的懂了。。。以后如果不懂的话,那么这里还需要再画一遍。
实际上,只要前面的指令记得滚瓜烂熟,那么堆栈图这里就不是问题。
按F8和F7的区别就在于,F8是一条一条的指令执行,而F7则是会跟着函数去执行
汇编部分终于结束了。JCC的指令还需要查表,其他的得多复习,堆栈是重点。