目录
一、进制
进制的定义
十进制的定义:由十个符号组成,分别为0 1 2 3 4 5 6 7 8 9 锋十进一。
十六进制的定义:有十六个符号组成即0 1 2 3 4 5 6 7 8 9 A B C D E F。
N进制的定义
由N个符号组成,逢N进一。(这里的符号不一定是从0到N-1的连续的数字,可以是自定义的有顺序的任意数字,字母等等各种符号)。进制的简单计算一般可以用查数来计算,也可以通过查数绘制进制的加法乘法表格,再通过查表就可以进行进制内的加减乘除的运算。
如果把进制的符号以不同于常规的方法定义,就可以用来进行一些加密解密。
例如:我们将常规的十进制的十个符号打乱顺序即 4 5 2 1 3 7 6 9 8 0
如若这样定义十进制那么就会出现4+5=5; 0+5=54;这样在不知道定义的人看来就是错误的,但在我的定义之下就是正确的。
熟练掌握二进制于十六进制的转换,尽量做到会被的程度,就相当于16对特殊字符,背会绝对不会后悔的。这对后期编程和解题也是十分有益的。
二、数据宽度_逻辑运算
数据宽度
是指计算机将我们录入的一些列指令文件转化的二进制码储存的边界,可以通俗的理解为内存的边界,当计算机的二进制信息超过数据宽度时计算机会选择舍弃后面未保存的。我们常用的都是32位和64位的数据宽度。
在计算机中若表示无符号数,那么一个8位容器就可以储存2的16次方个数,在计算机中若表示有符号数,那么一个容器中储存的数就要对半分为正负数,无论是4位宽度还是8位宽度,只要表示有符号数那么1开头的都表示复数,0开头的都表示正数。
4位的二进制:
8位的二进制:
范围:
无符号数:0~FF (0~255)
有符号数:0~7F (0127)、80FF (-128 ~ -1)
逻辑运算
是指计算机进行的运算方法,常见的二进制逻辑运算包括“或运算(or |)”、“与运算(and &)”、“异或运算(xor ^)”、“(非运算(not !)”。快速记住这四种运算方法:
或运算:有1就为1 与运算:有0就为0
异或运算:相同为0,不同为1 非运算 :1变0,0变1
插入知识点 左移:将二进制数左移一位,删去最前一位,在后面加0。例如0010左移得到0100。
CPU 进行加法计算的本质 :首先分别将两个数储存在两个容器中记为A,B,首先对两个数进行异或运算将得到的数出错在第三个容器中记为C,这个时候进行验证,将A和B进行与运算,并将结果进行左移一位储存在第四个容器中记为D,若D为0000,那么C就是正确的结果;若D不为0,用C和D代替A,B进行异或运算结果储存在第五个容器中记为F,F可以代替C;接下来就是循环计算了,直到D为0结束计算,得到结果:此刻的C。
最简单的加密(异或运算):对需要加密的数转换为二进制之后于秘钥转换的二进制进行相同位数的异或运算,再将得到的二进制数转换为与最初数相同的进制,就可以得到加密后的密文。
举个栗子:
加密2022,秘钥为十进制的66,对20和66的二进制数进行异或运算在转化为十进制就是46,同理后面得到的是44,也就是说我们将2022加密得到密文4644。解密过程就是对4644依次和66进行异或运算就可以得到2022。
三、通用寄存器_内存读写
寄存器
常见的32位通用寄存器:(尽量把寄存器的名称背下来)
寄存器 | 主要用途 | 编号 | 储存数据的范围 |
EAX | 累加器 | 0 | 0 —0xFFFFFFFF |
ECX | 计数 | 1 | 0 —0xFFFFFFFF |
EDX | I/0指针 | 2 | 0 —0xFFFFFFFF |
EBX | DS段的数据指针 | 3 | 0 —0xFFFFFFFF |
ESP | 堆栈指针 | 4 | 0 —0xFFFFFFFF |
EBP | SS段的数据指针 | 5 | 0 —0xFFFFFFFF |
ESI | 字符串操作的源指针;SS段的数据指针 | 6 | 0 —0xFFFFFFFF |
EDI | 字符串操作的目标指针;ES段的数据指针 | 7 | 0 —0xFFFFFFFF |
常见的16位寄存器:AX、CX、DX、BX、SP、BP、SI、DI
常见的8位寄存器 :AL、CL、DL、BL、AH、CH、DH、BH
寄存器之间的关系
举个栗子:寄存器结构EAX-AX-AH-AL的对应关系中,16位和8位的寄存器AX AH-AL都不是独立存在的,其中32位寄存器EAX有从0到31,0~15叫低位,16~31叫高位,这里可以说它的低位实际上就是AX,也就是说AX本身就在EAX中,如果将AX对半分开,就可以分为AH和AL。
先在32位的EAX存AAAAAAAA,再在16位的AX存BBBB,你就会发现EAX变成了AAAABBBB
此时再存AH为DD,AL为EE就会发现EAX变为AAAADDEE
内存
寄存器与内存的区别:
寄存器都是有CPU提供的,位于CPU内部,执行速度快,而内存在CPU外部的数据总线上,所以内存速度相对慢,但是成本低,可以做得很大。寄存器和内存没有本质区别,都是存储数据的容器,都是定宽的,寄存器是高速存贮部件,暂时存储指令数据和地址,可对内存数据进行操作。内存是所有运行程序存储的空间。
操作内存-内存写入
mov word ptr DS:[0x12345678],0xFFFF
内存宽度:指定读写改内存的数据宽度(word也表示内存的宽度)
ptr:代表的一个指针,指的是一个地址
DS:段寄存器,固定写法
操作内存-内存读出
mov eax,dword ptr ds:[0x19FF74]
输入这样的代码就可以读出,如下图
四、内存地址_堆栈
内存地址
1、内存是连续的,字节大小的宽度来表示,每一个内存地址标明一个特定的字节。
2、操作多大的数据,就指定相符的数据宽度(byte,word,dword)。
计算机中几个常用计量单位:BYTE,WORD,DWORD
BYTE字节=8(BIT) | 1KB=1024BYTE |
WORD字=16(BIT) | 1MB=1024KB |
DWORD双字=32(BIT) | 1GB=1024MB |
寻址公式:
读取内存的值
第一个存入EAX的是C4,C5,C6,C7
新指令LEA,拿出来的是编号,mov只能取值,lea取的是编号
堆栈
性质如图:
如果windows利用这种结构存储,会往小地址方向移动
压入数据(base叫栈底,top叫栈顶)
mov EBX,12FFE0//base
mov EDX,12FFE0//top
这个时候没有存数据,base和top的值一样,从现在开始,栈底不变,存值的时候top往上偏移然后再存四个字节。
mov dword ptr ds:[edx-4],0x12345678
sub edx,4
存数进去,然后top-4
或者这样
lea edx,dword ptr ds:[edx-4]
mov dword ptr ds:[edx],0x23456789
再或者
mov dword ptr DS:[edx-4],0x3456789A
sub edx,4
都可以的。
读数据
mov esi,dword ptr ds:[ebx-8]
只把值取出来,对于栈底和栈顶的值不改变,或者用顶来取
mov esi,dword ptr ds:[edx+4]
删除数据
mov eax dword ptr ds:[edx]
add edx,4
或者
lea edx dword ptr ds:[edx+4]
mov edx dword ptr ds:[edx-4]
或者
mov ecx dword ptr ds:[edx]
lea edx dword ptr ds:[edx+4]
操作系统和CPU达成某种协议,CPU给操作系统留下两个寄存器,平时存啥都行,但是到关键时候(用堆栈的时候),一个用来存栈顶,一个存栈底。
EBP通常是栈底,ESP是栈顶。
堆栈指令
写入数据:push(将一个值写入ESP-4的地址)
读出数据:EBP_ 或 ESP+
弹出数据:pop(将当前ESP的值弹出到一个寄存器或者内存中)
注意:如果push后面跟一个立即数,输入push ax,esp上面只减2,不能push8位内存,push一个内存DWORD是ESP减4,push一个word是减2,pop也是一样的,都取决于你容器的宽度。
五、标志寄存器
举案例引知识
1、PE
2、下断点(使程序执行到Message Box就停下来)
3、WIN32 API(其实就是Message Box)
4、函数调用
5、熟悉堆栈——会画堆栈图(一个程序刚开始执行的时候,这个栈顶就是谁调用它的这个的返回地址)
6、Call Jee 标志寄存器(JE只看标志寄存器,标志寄存器符合条件就跳,即zero flag的值为1,不符合不跳,即zero flag的值为0;所有的JCC指令都是有标志寄存器控制的)
找到关键点,改成执行,就叫爆破。
标志寄存器
背下来CF/PF/AF/ZF/SF/OF在这个32位的标志寄存器中的位。
1.进位标志寄存器CF(Carry Falg):若运算的结果最高位产生一个进位或错位,那么其值为1,否则为0。
会拆位,会找最高位(需要确定数据宽度),
2.奇偶标志PF(Parity Flag):奇偶标志PF用于反映运算结果中“1”的个数的奇偶性,若1的个数为偶数,则PF的值为1,否则为0。
“1”的个数指的是其对应的二进制中1的个数
3.辅助进位标志AF(Auxiliary Carry Flag):在发生下列情况时,辅助进位标志AF的值为1,否则其值为0。
(1)、在字操作时,发生低字节向高字节进位或借位时。
(2)、在字节操作时,发生低4位向高4位进位或借位时。
4.零标志位ZF(Zero Flag):零标志ZF用来反映运算结果是否为0。
如果运算结果为0,则值为1,否则值为0,在判断运算结果是否为0时,可以用此标志位。
5.符号标志位SF(Sign Flag):符号标志SF用来反映运算结果的符号位,他与运算结果的最高位相同。
6.溢出标志位OF(Overflow Flag):用来反映有符号数加减运算所得结果是否溢出,如果运算结果超过当前运算位数所能表示的范围,则称为溢出,OF的值为1否则为0
CF位和OF位的区别主要是看有无符号,OF位有符号,CF位无符号。
依据左图,在有符号的运算中,有如下规律:
正+正=正 如果结果是负数,则说明有溢出
负+负=负 如果结果是正数,则说明有溢出
正+负 永远都不会溢出
相关指令学习
ADC指令:带进位的加法。
格式:ADC R/M, R/M/IMM(R代表寄存器,M代表内存,IMM代表立即数),注意两边不能同时为内存,并且两边的宽度要一样。
SBB指令:带错位的减法。
格式:SSB R/M,R/M/IMM 注意两边不能同时为内存 且宽度要一样。
XCHG指令:交换数据。
格式:XCHG R/M,R/M 注意两边不能同时为内存,且宽度要一样。
MOVS指令:移动数据 内存-内存。
MOVS BYTE PTR DS:[ESI],BYTE PTR DS:[ESI] 简写为:MOVSB(注意两边的宽度要一样)
STOS指令:将AL/AX/EAX的值存储到[EDI]指定内存单元。
STOS BYTE PTR ES:[EDI] 简写为:STOSB
REP指令:按计数寄存器(ECX)中指定的次数重复执行字符串指令。
六、JCC
EIP寄存器中存储了一个地址,这个地址可以决定cpu下一行要执行的代码是什么。因此,当我们想改变CPU的行为,就要修改EIP寄存器中的。
断点可以理解为让CPU一条一条指令的去执行,从而达到是我们跟好的理解内存和堆栈的变化。
JMP指令:修改EIP的值。
MOV EIP,寄存器/立即数 简写为:JMP 寄存器/立即数。
CALL指令:也可修改EIP的值,但异于JMP指令。
PUSH 地址B。
MOV EIP,地址A/寄存器 简写为:CALL 地址A/寄存器。
CALL与JMP的异同
相同:都将一个值放到EIP中,可以改变EIP的值。
不同:CALL指令下ESP变了,说明它向堆栈里压了一个值——返回地址,也就是CALL的下一个地址。
CMP指令:比较两个操作数,也就是比较两个值。
格式:CMP R/M,R/M/IMM 不能同时为内存。
CMP指令实际上相当于SUB指令,但是相减的结构并不保存到第一个操作数中。只是根据相减的结果来改变零标志位的,当两个操作数相等的时候,零标志位为1 。
TEST指令:
指令格式:TEST R/M,R/M/IMM
该指令在一定程序上和CMP指令时类似的,两个数值进行与操作,结果不保存,但是会改变相应标志位。
常见用法:用这个指令,可以确定某寄存器是否等于0
JCC指令
JCC指令与标志寄存器是合二为一的,缺一不可的(JCC只看标志寄存器)。
根据名字记(死记硬背):
JE , JZ | 结果为零则跳转(相等时候跳转) | ZF=1 |
JNE , JNZ | 结果不为零的时候跳转(不想等的时候跳转) | ZF=0 |
JS | 结果为负则跳转 | SF=1 |
JNS | 结果为非负则跳转 | SF=0 |
JP , JPE | 结果中1的个数为偶数则跳转 | PF=1 |
JNP , JPO | 结果中1的个数为偶数则跳转 | PF=0 |
JO | 结果溢出了则跳转 | OF=1 |
JNO | 结果没有溢出则跳转 | OF=0 |
JB , JNAE | 小于则跳转(无符号数) | CF=1 |
JNB , JAE | 大于则跳转(无符号数) | CF=0 |
JBE , JNA | 小于等于则跳转(无符号数) | CF=1 or ZF=1 |
JNBE , JA | 大于则跳转(无符号数) | CF=0 and ZF=0 |
JL , JNGE | 小于则跳转(有符号数) | SF不等于OF |
JNL , JGE | 大于等于则跳转(有符号数) | SF=OF |
JLE , JNG | 小于等于则跳转(有符号数) | ZF=1 or SF不等于OF |
JNLE , JG | 大于则跳转(有符号数) | ZF=0 and SF=OF |
七、堆栈图
画堆栈图:
从下图鼠标所指位置开始
在此处设置断点(按F2),让CPU运行到这里停下来。
首先我们要十分清楚ESP和EBP这两个寄存器,一个栈顶一个栈底,先将这两个确定下来
PUSH2之后发现栈顶发生变化,如下图
此时注意在这里
不能再按F8了,在这里我们改按F7,防止程序直接执行完毕。
F7之后,
发现ESP位变成了28 ,堆栈的值也是正确的,
当我们再提函数的返回地址,说的就是CALL调用的时候,
他往栈里面压的值,我们称为函数的返回地址。
CALL函数不仅修改EIP的值,还会将这行指令后面的地址压到栈里面。
这里我们会看到JMP指令,因为JMP是不影响堆栈的(JMP指令是跳转),所以直接回车就可以了,但是JMP指令还相当于mov值到EIP,所以这里EIP的值变了。
继续按F8,我们发现ESP使得EBP发生改变,也变成ESP的值了。
接着看到然后看SUN ESP,40
SUB是- 就是ESP- 40个
这里的40是16进制的,所以要上升16个格
缓冲区的概念:
任何一段程序在执行都需要一点空间,不给我一块内存怎么执行,所以需要一块空间叫做缓冲区,缓冲区的大小时不确定的,他会根据你需要的多少来分
这里可以看到 ESP 变成了 0012FEE4
然后PUSH EBX
PUSH ESI EDI 后的堆栈图,现在我们就把ESI EDI EBX的值压到了栈里面
观察可以看到我们的思路是正确的
LEA是取地址编号的,先取EBP-40的地址编号,然后放到EDI里面。
然后接着画,
可以看到堆栈的这个寄存器没有变化,说明LEA这个对堆栈来说没有影响,只改变了EDI的值
然后看到后面的那个SOTS,它的作用是将EAX的值辅给EDI这个地址编号指向的内存,这里的EDI会自动加4或者减4。
这三个MOV ECX,10 MOV EAX,CCCCCCCC REP STOS DWORD PTR SS:[EDI]指令的意思就是,每次都把EAX的一串C放到EDI这个内存地址,指向后EDI的值会自动加4,加4后ECX会自动减1,CC相当于int 3 相当于断点 防止缓冲区溢出
记住10 完事是F 不是9 因为是16进制的。(注意10下来是F,不是9 ,因为这里的10是16进制的)
栈顶和栈底都没变,中间这一串区全是CCCCCCCC
观察,在这里面来看,存入CC数据之前,这里面的数据都有值,这里面的数据我们都可以看为是垃圾数据,可以想成是上个函数对此的遗留数据
F8之后中间就都是CCCCCCCC,和我们画的一样了。
MOV EAX,DWORD PTR SS:[EBP+8]
在调用前用了两个push,因为这个程序要用到这两个值,所以我们把这两个值叫做参数。
这段程序有个名字叫做函数,函数也可以说是一段程序,在CALL执行前,这个参数就已经放到堆栈里面了,没有改变堆栈的栈顶和栈底
ADD EAX,DWORD PTR SS:[EBP+C]
EAX变成了3:1+2 ,
POP EDI ESI EBX
POP就是把栈顶的值放进寄存器里 (恢复现场)。
可以看到那些CC的值都没有变。
观察栈顶和栈底的时候发现,都变成了一样的值,这个过程叫恢复堆栈(程序执行完事需要恢复)。
然后回复原来的栈底RETN:pop eip
堆栈平衡:调用函数之前和调用函数之后你的堆栈应该是一样的,是没有变化的,我们叫做堆栈平衡。