Loading... 1. 十六进制和二进制的关系。 首先我们先来看一下这个对应关系: 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 0 1 2 3 4 5 6 7 8 9 A B C D E F 所我们不难看出:十六进制可以看成二进制的一种简写形式。应牢记其对应关系。 四位二进制数相当于一位十六进制, 两位十六进制相当于一个字节。 1. 二进制的编码方式。 正数:原码=反码=补码 负数:[负数是以补码的方式储存的] 原码就是原来的表达方式 反码就是符号位为1其余按位取反 补码=反码+1 2. 计算机的运算都会转化为位运算。 在不考虑(无)进位的情况下,按位加和抑或的结果都一样。 例如:4+5=?的运算过程: 00000100(4) 00000100 00000101(5) 00000101 异或----------- 判断是否有进位:与--------- 00000001 00000001 00001000 00000101 继续异或--------- 判断是否有进位:与--------- 最终结果:00001001 4+(-5)=?的运算过程: 00000100 00000100 11111001 11111011 异或---------- 判断是否有进位:与--------- 最终结果:11111111 乘除也可化为加减如上运算 3. 移位运算。 <1>.左移:各二进位全部左移若干位,高位丢弃,低位补零。 汇编写法 C语言写法 <2>.右移:各二进位全部右移若干位,低位丢弃,高位补零。 例:无符号型: Shr 11010101 00110101 对应C语言(>>) Unsigned int a = 10; Printf(“%d\\n”,a>>2); 有符号型: Sar 11010101 11110101 对应C语言(>>) Int a = 10; Printf(“%d\\n”,a>>2); 4. 寄存器(在CPU中)。 | 32 位 | 16 位 | 8 位(高) | 8 位(低) | | ----- | ----- | ---------- | ---------- | | EAX | AX | AH | AL | | EBX | BX | BH | BL | | ECX | CX | CH | CL | | EDX | DX | DH | DL | \~H是高八位,\~L是低八位 MOV指令:(1)立即数到寄存器 例:MOV EAX,1 (2)寄存器到寄存器 MOV EDX,EAX 5. 内存: 每个进程有4G内存(假的,实际不占用这么多) 那为啥的?我们通过计算: 因为内存太多没法像寄存器一样起名,所以我们用编号来表示。当我们想向内存中存储数据或者从内存中读取数据的时候,必须要用到这个编号,就像写收件人的地址一样。 编号又称内存地址(32位) 范围:0x00000000\~0xFFFFFFFF FFFFFFFF+1=100000000 因为每一块内存有8位所以还要乘以8 (字节byte是最小的储存单位,位/比特bit是计算机最小的数据传输单位) 所以800000000转化为十进制=34359738368 位/bit 除以8=4294967296 字节/byte 再除1024=4194304 KB 再除1024=4096MB 再除1024=4GB <2>.寄存器到内存: 例:MOV DWORD PTR DS:[ ],EAX <3>.内存到寄存器: 例:MOV EAX,DWORD PTR DS :[ ] 6. [ ]的书写形式。 前面已经写过书写形式1就是直接写内存地址 书写形式2:[reg] 首先定义reg代表写入8个32位通用寄存器中的任意一个 例:读取内存地址位0x0013FFD0的值。 MOV ECX,0x0013FFD0 MOV EAX,DOWORD PTR DS:[ECX] 书写形式3:[reg + 立即数] 例:读取内存地址0x0013FFD4的值 MOV ECX,0x0013FFD4 MOV EAX,DWORD PTR DS:[ECX+4] 书写形式4:[reg+reg\*{1,2,4,8}] 例:读取内存地址为0x0013FFCC的值 MOV EAX,0x0013FFC4 MOV ECX,2 MOV EDX,DWORD PTR DS:[EAX+ECX\*4] 书写形式5:[reg+reg\*{1,2,4,8}+立即数] 7. 数据存储模式: 大端模式:数据高位在低位,数据低位在高位(一般在手机应用) 小端模式:数据低位在低位,数据高位在高位(一般在x86CPU应用) 高位 低位 0x00000002 高位 字节: db 输入地址 查看形式:格式 字:dw 输入地址 双字:dd 输入地址 8. 指令: 首先定义:r代表通用寄存器。m代表内存。imm代表立即数 r8代表8位的通用寄存器。m8代表8位内存。imm8代表8位立即数 <1>.MOV指令: 格式例:1.MOV r8/m8,r8 2. MOV r/m16,r16 3. MOV r/m32,r32 4. MOV r8,r/m8 5. MOV r16,r/m16 6. MOV r32,r/m32 7. MOV r8,imm8 8. MOV r16,imm16 9. MOV r32,imm32 <2>.ADD指令:(加法)(后面的加到前面) <3>.SUB指令:(减法) <4>.AND指令:(与运算) <5>.OR指令:(或运算) <6>.XOR指令:(异或运算) <7>.NOT指令:(非) 例:只含一个参数:1.NOT r/m8 2.NOT r/m16 3.NOT r/m32 11.MOVS指令:移动数据 内存内存 例: MOVS BYTE PTR DS:[EDI],BYTE PTR DS:[ESI] 简写:MOVSB MOVS WORD PTR DS:[EDI],WORD PTR DS:[ESI] 简写:MOVSW MOVS DWORD OTR DS:[EDI],DWORD PTR DS:[ESI] 简写:MOVSD 注意:在标志寄存器EFL的展开的32位中,倒数第十位也就是DF位,如果D位是0,则MOVS指令每执行一次后下一次内存地址会自动的增加对应的字节;如果D位是1则MOVS指令每执行一次后下一次内存地下会自动减对应的字节。 9. STOS指令:将Al/AX/EAX的值储存到[EDI]指定的内存单元 SOTS BYTE PTR DS:[EDI] 简写:SOTSB SOTS WORD PTR DS:[EDI] 简写:SOTSW SOTS DWORD PTR DS:[EDI] 简写:SOTSD 标志寄存器EFI中D位和如上功能一样,实现自增自减 10. REP指令:按计数储存器(ECX)中指定的次数重复执行字符串命令。 例:MOV ECX,10 REP MOVSD REP STOSD 11. 什么是堆栈呢? <1>.就是一块内存,操作系统在启动时就已经分配好的,供程序执行时使用。 栈的简述:是一种先进后出的数据结构,栈有一个存储区、一个栈顶指针。栈顶指针指向堆栈中第一个可用的数据项(被称为栈顶)。用户可以在栈顶上方向栈中加入数据,这个操作被称为压栈(Push),压栈以后,栈顶自动变成新加入数据项的位置,栈顶指针也随之修改。用户也可以从堆栈中取走栈顶,称为弹出栈(pop),弹出栈后,栈顶下的一个元素变成栈顶,栈顶指针随之修改。 <2>.和数据结构的堆栈无关。 12. PUSH指令: 功能:<1>.向堆栈中压入数据。 <2>.修改栈顶指针ESP寄存器。 指令格式: <1>.PUSH r32 <2>.PUSH r16 <3>.PUSH m16 <4>.PUSH m32 <5>.PUSH imm8/16/32 先进后出 13. POP指令: 功能:<1>.将栈顶数据储存到寄存器/内存 <2>.修改栈顶指针ESP寄存器 指令格式:<1>.POP r32 <2>.POP r16 <3>.POP m16 <4>.POP m32 14. 修改EIP的指令: JMP指令:专改EIP,不变栈顶指针 格式:JMP 寄存器/立即数/内存 CALL指令:改变EIP,执行后的下一条地址会压入堆栈中,栈顶指针改变 注意:一定要按F7才能单步执行此条指令 格式:CALL 内存/立即数/寄存器 执行时push下一行地址,这是与jmp的唯一区别:在堆栈中储存call指令下一行地址 RET指令:把栈顶的值放入EIP,并使堆栈指针ESP加4 后面可以写数字,作堆栈内平衡时使用 15. 反调试之fake f8 F2:设置断点,只要在光标定位的位置(上图中灰色条)按F2键即可,再按一次F2键则会删除断点。 F8:单步步过。每按一次这个键执行一条反汇编窗口中的一条指令,遇到 CALL 等子程序不进入其代码。 F7:单步步入。功能同单步步过(F8)类似,区别是遇到 CALL 等子程序时会进入其中,进入后首先会停留在子程序的第一条指令上。 F4:运行到选定位置。作用就是直接运行到光标所在位置处暂停。 F9:运行。按下这个键如果没有设置相应断点的话,被调试的程序将直接开始运行。 16. 汇编眼中什么是函数: 函数就是一系列指令的集合,为了完成某个会重复的特定功能。 <1>.用Jmp指令调用 <2>.用CALL指令调用,一般用CALL方便: CALL RET 17. 什么是堆栈平衡? <1>.如果要返回父程序则当我们在堆栈中进行堆栈的操作时候,一定要保证在RET这条指令执行之前,ESP所指向的是我们压入栈中的地址。 <2>.如果通过堆栈传递参数了,那么在函数执行之后,要平衡堆栈参数所导致的堆栈变化。 18. ESP寻址: 例: PUSH 1 PUSH 2 CALL impsg、0041B3E39 (ADD ESP,8)(外平栈) MOV EAA,DWORD PTR SS:[ESP+8] RETN (8)(内平栈) 当如果要使用到ECX,EDX,EBX等等寄存器时,但是里面的数据还有用怎么办? 虽然可以改变[ESP+ ]加的值,但可能很麻烦。 例如: PUSH 1 PUSH 2 CALL ipmsg、004183E9 0041B3E9|PUSH ECX PUSH EDX PUSH EBX MOV ECX,DWORD PTR SS:[ESP+10] ADD ECX,DWORD PTR SS:[ESP+14] MOV EAX,ECX POP EBX POP EDX POP ECX RETN 19. EBP寻址:EBP可称栈底指针 例: PUSH 1 PUSH 2 CALL ipmg、004183EE ADD ESP,8(外平衡堆栈) 004183EE|PUSH EBP MOV EBP,ESP SUB ESP,10 MOV EAX,DWORD PTR SS:[EBP+8] 通过EBP寻址相对固定 ADD EAX,DWORD PTR SS:[EBP+C] MOV ESP,EBP POP ESP,EBP RETN 20. 标志寄存器EFLAGS: 31 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | 0 | | | | | | | | 0 | | | | OF | DF | IF | TF | SF | ZF | 0 | AF | 0 | PF | 1 | CF | | - | - | - | - | - | - | - | - | - | - | - | - | -- | -- | -- | -- | -- | -- | - | -- | - | -- | - | -- | 溢出标志 方向标志 中断使能标志 单步标志 符号标志 零标志 辅助进位标志 奇偶标志 进位标志 运算方面的指令都会影响标志寄存器。 <1>.CF(bit)[carry flag] 若算数操作产生的结果在最高有效位(most-significant bit)发生进位或者错位则将其置为1,反之则清零。 这个·标志通常用来指示无符号整型运算的溢出状态。 例:MOV AL,0XFF 或者 MOV AL,0x7F ADD AL,2 ADD AL,2 <2>.PF(bit)[parity flag] 如果结果的最低有效字节(least-significant byte)一个字节,包含偶数个1位,则该位置为1。否则清零。 利用PF可进行奇偶校验: 需要传输“1101110”,数据中含5个“1”,所以其奇偶校验位为“0”,同时找“110011100”传输给接收方,接收方收到数据后再一次计算奇偶性,“110011100”中仍然有5个“1”,所以接收方计算出的奇偶校验位还是“0”,与发送方一致,表示在此次传输过程中未发生错误。 <3>.AF(bit)[adjust flag] 如果算数操作在结果的第3位发生进位或者错位则将该标志位置为1,否则清零。 这个标志位在BCD(binary-code decimal)算数运算中被使用。 <4>.ZF(bit)[zero flag] 若结果为0则将其置为1,反之则清零。 经常与CMP指令或者TEST等指令一起使用: 例1: 判断两个值是否相等 MOV EAX,100 MOV ECX,100 CMP EAX,ECX (CMP指令相当于SUB指令,但是相减的结果并不保存到第一个操作数中,只改变标志寄存器) 例2:判断两个值是否为0 TEST EAX,EAX (TEST指令相当于AND指令,但是与出的结果并不保存到第一个操作数中,只改变标志寄存器) <5>.SF(bit 7)[sigb flag] 该标志被设置为有符号整型的最高有效位。 0指示结果为正,反之则为负。 例:MOV AL,0x7F MOV AL 0Xfe ADD AL,2 ADD AL ,2 <6>.OF(bit 11)[over flow flag] 溢出标志位用于反映有符号数加减运算所得出的结果是否溢出。 可以这样理解: 如果是无符号数运算看是否溢出看CF位 如果是有符号数运算看是否溢出看OF位 例:MOV AL,0x7F ADD AL,2 <7>.DF(bit 10)[direction flag] 这个方向标志控制串指令(MOVS,CMPS,SCAS,LODS,STOS) 设置DF标志使得串指令自动递减(从高地址向低地址方向处理字符串),清除该标志位使得串指令自动递增。 21. 滑板指令NOP:直接跳过这一行 22. JCC指令集: <1>.JE,JZ 结果为0则跳转(相等时跳转) ZF=1 <2>.JNE,JNZ 结果不为0则跳转(不相等时跳转) ZF=0 <3>.JS 结果为负则跳转 SF=1 <4>.JNS 结果为非负则跳转 SF=0 <5>.JP,JPE 结果中1的个数为偶数则跳转 PF=1 <6>.JNP,JPO 结果中1的个数为奇数则跳转 PF=0 <7>.JO 结果溢出了则跳转 OF=1 <8>.JNO 结果没有溢出则跳转 OF=0 <9>.JB,JNAE 小于则跳转(无符号数) CF=1 <10>.JNB,JAE 大于等于则跳转(无符号数) CF=0 <11>.JBE,JNA 小于等于则跳转(无符号数) CF=1 or ZF=1 <12>.JNBE,JA 大于则跳转(无符号数) CF=0 and ZF=0 <13>.JL,JNGE 小于则跳转(有符号数) SF ≠ OF <14>.JNL,JGE 大于等于跳转(有符号数) SF = OF <15>.JLE,JNG 小于等于则跳转(有符号数) ZF=1 or SF≠OF <16>.JNLE,JG 大于则跳转(有符号数) ZF=0 and SF=OF 23. 函数调用约定: 是函数调用者和被调用的函数体之间关于参数传递、返回值传递、堆栈清除、寄存器使用的一种约定。 函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算。函数计算结束以后,或者调用者、或者函数本身修改堆栈,使堆栈恢复原装。 在参数传递中,有两个很重要的问题必须得到明确说明: <1>.当参数个数多于一个时,按照什么顺序把参数压入堆栈 函数调用后,由谁来把堆栈恢复原装。 <2>.在高级语言中,通过函数调用约定来说明这两个问题。 24. 常见的调用约定有: stdcall(pascal)多适用于WINAPI 声明语法:int \_\_stdcall function(int a,int b) <1>.参数从右向左压入堆栈 <2>.函数自身清理堆栈 cdecl多适用于C语言,C语言缺省的调用约定 声明语法:int function (int a ,int b) //不加修饰就是C调用约定 int \_\_cdecl function(int a,int b)//明确指出C调用约定 <1>.参数从右向左压入堆栈 <2>.调用者负责清理堆栈 C调用约定允许函数的参数的个数是不固定的,这也是C语言的一大特色。 fastcall 声明语法:int fastcall function(int a,int b) <1>.函数的第一个和第二个DWORD参数(或者尺寸更小的)通过ecx和edx传递,其他参数通过从右向左的顺序压栈 <2>.函数自身清理堆栈 Thiscall C++类成员函数缺省的调用约定 naked call 一般用于实模式驱动程序设计 25. 小议三种函数调用约定 \_\_cdecl、\_\_stdcall、\_\_fastcall是C/C++里中经常见到的三种函数调用方式。其中\_\_cdecl是C/C++默认的调用方式,\_\_stdcall是windows API函数的调用方式,只不过我们在头文件里查看这些API的声明的时候是用了WINAPI的宏进行代替了,而这个宏其实就是\_\_stdcall了。 三种调用方式的区别相信大家应该有些了解,这篇文章主要从实例和汇编的角度阐述这些区别的表现形态,使其对它们的区别认识从理论向实际过渡。 \_\_cdecl: C/C++默认方式,参数从右向左入栈,主调函数负责栈平衡。 \_\_stdcall: windows API默认方式,参数从右向左入栈,被调函数负责栈平衡。 \_\_fastcall: 快速调用方式。所谓快速,这种方式选择将参数优先从寄存器传入(ECX和EDX),剩下的参数再从右向左从栈传入。因为栈是位于内存的区域,而寄存器位于CPU内,故存取方式快于内存,故其名曰“\_\_fastcall”。 最后修改:2023 年 06 月 26 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 5 如果觉得我的文章对你有用,请随意赞赏