1. 指令
- mov
- 两个操作数必须是同样的大小。
- 两个操作数不能同时为内存操作数。
- 指令指针寄存器(IP、EIP 或 RIP)不能作为目标操作数。
- 64位模式 可以将8位 16位 32位常数送入 高位清零 32位寄存器送入会使高位清零
-
movzx 全零扩展 无符号整数
-
movsx 全符号扩展 有符号整数 不能为常数
-
lahf 将标志寄存器低八位加载到ah
-
sahf 将ah保存到标志寄存器
-
xchg 交换操作数 不能同时为内存操作数 不能为立即操作数,不影响标志位
-
inc dec 加一 减一 不会影响进位标志位
-
add 目的加源值放在目的 操作数与mov相同
-
sub 目的减源值放在目的 操作数与mov相同
-
NEG 取反+1 得到补码
- INC 和 DEC 指令不会影响进位标志位。在非零操作数上应用 NEG 指令总是会将进位标志位置 1。
-
JMP 指令无条件跳转到目标地址,该地址用代码标号来标识,并被汇编器转换为偏移 量
-
LOOP 循环目标必须距离当前地址计数器 -128 到 +127 字节范围内。LOOP 指令的执行有两个步骤:
- 第一步,ECX 减 1。
- 第二步,将 ECX 与 0 比较。
- LOOPZ(为零跳转)LOOPE(相等跳转)指令的工作和 LOOP 指令相同,只是有一个附加条件:为零控制转向目的标号,零标志位必须置 1,
- LOOPNZ(非零跳转)LOOPNE(不相等跳转)指令与 LOOPZ 相对应。当 ECX 中无符号数值大于零(减 1 操作之后)且零标志位等于零时,继续循环
-
PUSH指令 首先减少 ESP 的值,再将源操作数复制到堆栈。内存数需要扩展,立即数会自动对齐 ,操作数是 16 位的,则 ESP 减 2,操作数是 32 位的,则 ESP 减 4
-
POP 指令首先把 ESP 指向的堆栈元素内容复制到一个 16 位或 32 位目的操作数中,再增加 ESP 的值。如果操作数是 16 位的,ESP 加 2,如果操作数是 32 位的,ESP 加 4:
-
PUSHFD 和 POPFD 指令
PUSHFD 指令把 32 位 EFLAGS 寄存器内容压入堆栈,而 POPFD 指令则把栈顶单元内容弹出到 EFLAGS 寄存器
-
PUSHAD,PUSHA,POPAD 和 POPA 指令按照 EAX、ECX、EDX、EBX、ESP(执行 PUSHAD 之前的值)、EBP、ESI 和 EDI 的顺序,将所有 32 位通用寄存器压入堆栈 PUSHA POPA操作16位寄存器
-
CALL RET指令 从物理上来说,CALL 指令将其返回地址压入堆栈,再把被调用过程的地址复制到指令指针寄存器。当过程准备返回时,它的 RET 指令从堆栈把返回地址弹回到指令指针寄存器。32 位模式下,CPU 执行的指令由 EIP(指令指针寄存器)在内存中指岀。16 位模式下,由 IP 指出指令
-
SHL(左移)指令使目的操作数逻辑左移一位,最低位用 0 填充。最高位移入进位标志位,而进位标志位中原来的数值被丢弃 左移一位相当乘2的几次方
-
SHR(右移)指令使目的操作数逻辑右移一位,最高位用 0 填充。最低位复制到进位标志位,而进位标志位中原来的数值被丢弃
-
与SHL一样,每次移动时,SAL 都将目的操作数中的每一位移动到下一个最高位上。最低位用 0 填充;最高位移入进位标志位,该标志位原来的值被丢弃
-
ROL(循环左移)指令把所有位都向左移。最高位复制到进位标志位和最低位。该指令格式与 SHL 指令相同
-
ROR(循环右移)指令把所有位都向右移,最低位复制到进位标志位和最高位
-
RCL(带进位循环左移)指令把每一位都向左移,进位标志位复制到 LSB,而 MSB 复制到进位标志位
-
RCR(带进位循环右移)指令把每一位都向右移,进位标志位复制到 MSB,而 LSB 复制到进位标志位
-
SHLD(双精度左移)指令将目的操作数向左移动指定位数。移动形成的空位由源操作数的高位填充。源操作数不变,但是符号标志位、零标志位、辅助进位标志位、奇偶标志位和进位标志位会受影响
-
SHRD(双精度右移)指令将目的操作数向右移动指定位数。移动形成的空位由源操作数的低位填充
-
MUL MUL指令乘数和被乘数的大小必须保持一致,乘积的大小则是它们的一倍。这三种类型都可以使用寄存器和内存操作数,但不能使用立即数
被乘数 乘数 乘积 AL reg/mem8 AX AX reg/mem16 DX:AX EAX reg/mem32 EDX:EAX 如果乘积的高半部分不为零,则 MUL 会把进位标志位和溢出标志位置 1
-
IMUL(有符号数乘法)指令执行有符号整数乘法。与 MUL 指令不同,IMUL 会保留乘 积的符号,实现的方法是,将乘积低半部分的最高位符号扩展到高半部分。
单操作数 双操作数 三操作数
-
DIV 32 位模式下,DIV(无符号除法)指令执行 8 位、16 位和 32 位无符号数除法。其中,单寄存器或内存操作数是除数
被除数 除数 商 余数 AX reg/mem8 AL AH DX:AX reg/mem16 AX DX EDX:EAX reg/mem32 EAX EDX -
IDICV指令 有符号除法几乎与无符号除法相同,只有一个重要的区别:在执行除法之前,必须对被除数进行符号扩展。
-
符号扩展指令
- CBW(字节转字)指令将 AL 的符号位扩展到 AH,保留了数据的符号
- CWD(字转双字)指令将 AX 的符号位扩展到 DX
- CDQ(双字转四字)指令将 EAX 的符号位扩展到 EDX
-
IDIV(有符号除法)指令执行有符号整数除法,其操作数与 DIV 指令相同。执行 8 位除法之前,被除数(AX)必须完成符号扩展。余数的符号总是与被除数相同。
-
ADC指令 ADC(带进位加法)指令将源操作数和进位标志位的值都与目的操作数相加。该指令格式与 ADD 指令一样,且操作数大小必须相同
-
SBB(带借位减法)指令从目的操作数中减去源操作数和进位标志位的值。
-
AAA (执行加法后进行 ASCII 调整) AAM (执行乘法后进行 ASCII 调整) AAS (执行减法后进行 ASCII 调整) AAD (执行除法前进行 ASCII 调整) -
DAA DAS 指令
-
LEA 指令返回间接操作数的地址。由于间接操作数中包含一个或多个寄存器,因此会在运行时计算这些操作数的偏移量
-
ENTER指令 ENTER 指令为被调用过程自动创建堆栈帧。它为局部变量保留堆栈空间,把 EBP 入栈
enter 8,0 push ebp;mov ebp,esp;sub esp,8
LEVEL指令 mov esp,ebp;pop ebp
LOCAL伪指令 紧跟在PROC伪指令后面,变量名:变量类型 等价于enter+level
-
SETE AL //取标志寄存器中ZF的值, 放到AL中. SETNE取得ZF值后, 取反, 再放到AL中
1.1 布尔指令
- 64 位模式中的 64 位指令与 32 位模式中的操作是一样的。比如,如果源操作数是常数,长度小于 32 位,而目的操作数是一个 64 位寄存器或内存操作数,那么,目的操作数中所有的位都会受到影响,如果源操作数是 32 位常数或寄存器,那么目的操作数中,只有低 32 位会受到影响
- AND指令 AND 指令在两个操作数的对应位之间进行(按位)逻辑与(AND)操作,并将结果存放在目标操作数中
- AND 指令总是清除溢出和进位标志位
- OR指令 OR 指令在两个操作数的对应位之间进行(按位)逻辑或(OR)操作,并将结果存放在目标操作数中
- OR 指令总是清除进位和溢出标志位,并根据目标操作数的值来修改符号标志位、零标志位和奇偶标志位
- NOT AND OR 补集 交集 并集 概念
- XOR 指令在两个操作数的对应位之间进行(按位)逻辑异或(XOR)操作,并将结果存放在目标操作数中,相同为0 不同为1,与 0 异或值保持不变,与 1 异或则被触发(求补)
- XOR 指令总是清除溢岀和进位标志位
- NOT 指令触发(翻转)操作数中的所有位。其结果被称为反码
- NOT指令不影响标志位
- TEST 指令在两个操作数的对应位之间进行 AND 操作,并根据运算结果设置符号标志位、零标志位和奇偶标志位。不改变操作数
- CMP CMP(比较)指令执行从目的操作数中减去源操作数的隐含减法操作,并且不修改任何操作数
1.2 Jcond指令
- 基于特定标志位的值跳转
- 基于两数是否相等,或是否等于(E)CX 的值跳转
- 基于无符号操作数的比较跳转
- 基于有符号操作数的比较跳转
-
助记符 说明 标志位/寄存器 助记符 说明 标志位/寄存器 JZ 为零跳转 ZF=1 JNO 无溢出跳转 OF=0 JNZ 非零跳转 ZF=0 JS 有符号跳转 SF=1 JC 进位跳转 CF=1 JNS 无符号跳转 SF=0 JNC 无进位跳转 CF=0 JP 偶校验跳转 PF=1 JO 溢出跳转 OF=1 JNP 奇校验跳转 PF=0 -
助记符 说明 JE 相等跳转 (leftOp=rightOp) JNE 不相等跳转 (leftOp M rightOp) JCXZ CX=0 跳转 JECXZ ECX=0 跳转 JRCXZ RCX=0 跳转(64 位模式) -
助记符 说明 助记符 说明 JA 大于跳转(若 leftOp > rightOp) JB 小于跳转(若 leftOp < rightOp) JNBE 不小于或等于跳转(与 JA 相同) JNAE 不大于或等于跳转(与 JB 相同) JAE 大于或等于跳转(若 leftOp ≥ rightOp) JBE 小于或等于跳转(若 leftOp ≤ rightOp) JNB 不小于跳转(与 JAE 相同) JNA 不大于跳转(与 JBE 相同) -
助记符 说明 助记符 说明 JG 大于跳转(若 leftOp > rightOp) JL 小于跳转(若 leftOp < rightOp) JNLE 不小于或等于跳转(与 JG 相同) JNGE 不大于或等于跳转(与 JL 相同) JGE 大于或等于跳转(若 leftOp ≥ rightOp) JLE 小于或等于跳转(若 leftOp ≤ rightOp) JNL 不小于跳转(与 JGE 相同) JNG 不大于跳转(与 JLE 相同)
1.3 串操作指令
指令 | 说明 |
---|---|
MOVSB、MOVSW、MOVSD | 传送字符串数据:将 ESI 寻址的内存数据复制到 EDI 寻址的内存位置 |
CMPSB、CMPSW、CMPSD | 比较字符串:比较分别由 ESI 和 EDI 寻址的内存数据 |
SCASB、SCASW、SCASD | 扫描字符串:比较累加器 (AL、AX 或 EAX) 与 EDI 寻址的内存数据 |
STOSB、STOSW、STOSD | 保存字符串数据:将累加器内容保存到 EDI 寻址的内存位置 |
LODSB、LODSW、LODSD | 从字符串加载到累加器:将 ESI 寻址的内存数据加载到累加器 |
REP | ECX > 0 时重复 |
---|---|
REPZ、REPE | 零标志位置 1 且 ECX > 0 时重复 |
REPNZ、REPNE | 零标志位清零且 ECX > 0 时重复 |
SCASB:如果发现该字符,则 EDI 指向匹配字符后面的一个位置。如果未发现匹配字符,则 JNZ 执行退出:
2. 伪指令
-
= 伪指令 替换
-
EQU 伪指令 表达式 符号替换 <文本> 不能重定义
-
TEXTEQU 伪指令 <文本> 已有文本宏 %(表达式)
-
ALIGN bound 对齐伪指令
-
LABEL 伪指令可以插入一个标号,并定义它的大小属性,但是不为这个标号分配存储空间,常见的用法是,为数据段中定义的下一个变量提供不同的名称和大小属性
-
EXTERN伪指令 调用当前模块之外的过程时使用EXTERN伪指令,它确定过程名和堆栈帧大小。
包含头文件 EXTERN 函数名@堆栈帧大小:PROC
-
PROC ENDP伪指令
过程可以非正式地定义为:由返回语句结束的已命名的语句块。过程用 PROC 和 ENDP 伪指令来定义,并且必须为其分配一个名字(有效标识符)。标号只在定义的过程中有效
-
LOCAL伪指令 紧跟在PROC伪指令后面,变量名:变量类型 等价于enter+level
由于 32 位模式中,堆栈偏移量默认为 32 位,因此,var1 可能被认为会存放于 EBP-4 的位置。实际上,如下图所示,MASM 将 EBP 减去 4,但是却把 var1 存放在 EBP-1
-
PROC 伪指令 过程定义
当 PROC 有一个或多个参数时,STDCALL 是默认调用规范,自动生成push ebp;mov ebp,esp;leave;ret (n*4)
属性 说明 distance NEAR 或 FAR。指定汇编器生成的 RET 指令(RET 或 RETF)类型 langtype 指定调用规范(参数传递规范),如 C、PASCAL 或 STDCALL。能覆盖由 .MODEL 伪指令指定的语言 visibility 指明本过程对其他模块的可见性。选项包括 PRIVATE、PUBLIC (默认项)和 EXPORT。若可见性为 EXPORT,则链接器把过程名放入分段可执行文件的导出表。EXPORT 也使之具有了 PUBLIC 可见性 prologuearg 指定会影响开始和结尾代码生成的参数 -
PROTO伪指令
MySub PROTO ;过程原型.INVOKE MySub ;过程调用.MySub PROC ;过程实现
可以覆盖调用规范,参数类型大于声明会报错,小于自动扩展
-
INVOKE伪指令 等价于INVOKE DumpArray, OFFSET array, LENGTHOF array, TYPE array,从右往左依次执行push,call
类型 例子 类型 例子 立即数 10, 3000h, Offset mylist, TYPE array 寄存器 eax, bl, edi 整数表达式 (10*20), COUNT ADDR name ADDR myList 变量 myLIst, array, my Word, myDword OFFSET name OFFSET myList 地址表达式 [myList+2], [ebx+esi] -
覆盖 EAX 和 EDX
如果向过程传递的参数小于 32 位,那么在将参数入栈之前,INVOKE 为了扩展参数常常会使得汇编器覆盖 EAX 和 EDX 的内容。有两种方法可以避免这种情况:
- 其一,传递给 INVOKE 的参数总是 32 位的;
- 其二,在过程调用之前保存 EAX 和 EDX,在过程调用之后再恢复它们的值
-
定义结构使用的是 STRUCT 和 ENDS 伪指令。在结构内,定义字段的语法与一般的变量定义是相同的。结构对其包含字段的数量几乎没有任何限制:
name STRUCT
field-declarations
name ENDS字段初始值若结构字段有初始值,那么在创建结构变量时就要进行赋值。字段初始值可以使用各种类型:
- 无定义:运算符?使字段初始值为无定义。
- 字符串文本:用引号括起的字符串。
- 整数:整数常数和整数表达式。
- 数组:DUP 运算符可以初始化数组元素。
-
-
伪指令 说明 .BREAK 生成代码终止 .WHILE 或 .REPEAT 块 .CONTINUE 生成代码跳转到 .WHILE 或 .REPEAT 块的顶端 .ELSE 当 .IF 条件不满足时,开始执行的语句块 .ELSEIF condition 生成代码测试 condition,并执行其后的语句,直到碰到一个 .ENDIF 或另一个 .ELSEIF 伪指令 .ENDIF 终止 .IF、.ELSE 或 .ELSEIF 伪指令后面的语句块 .ENDW 终止 .WHILE 伪指令后面的语句块 .IF condition 如果 condition 为真,则生成代码执行语句块 .REPEAT 生成代码重复执行语句块,直到条件为真 .UNTIL condition 生成代码重复执行 .REPEAT 和 .UNTIL 伪指令之间的语句块,直到 condition 为真 .UNTILCXZ 生成代码重复执行 .REPEAT 和 .UNTILCXZ 伪指令之间的语句块,直到 CX 为零 .WHILE condition 当 condition 为真时,生成代码执行 .WHILE 和 .ENDW 伪指令之间的语句块 -
.IF、.ELSE、.ELSEIF 和 .ENDIF 伪指令使得程序员易于对多分支逻辑进行编码。它们让汇编器在后台生成 CMP 和条件跳转指令,这些指令显示在输出列表文件中
-
运算符 说明 expr1 == expr2 若 expr1 等于 expr2,则返回“真” expr1 != expr2 若 expr1 不等于 expr2,则返回“真” expr1 > expr2 若 expr1 大于 expr2,则返回"真” expr1 ≥ expr2 若 expr1 大于等于 expr2,则返回“真” expr1 < expr2 若 expr1 小于 expr2,则返回“真” expr1 ≤ expr2 若 expr1 小于等于 expr2,则返回“真” !expr1 若 expr 为假,则返回“真” expr1expr2 对 expr1 和 expr2 执行逻辑 AND 运算 expr1 || expr2 对 1xprl 和 expr2 执行逻辑 OR 运算 expr1 & expr2 对 expr1 和 expr2 执行按位 AND 运算 CARR1? 若进位标志位置 11则返回“真” OVERFLOW ? 若溢出标志位置 1,则返回“真” PARITY ? 若奇偶标志位置 1,则返回“真” SIGN ? 若符号标志位置 1,则返回“真” ZERO ? 若零标志位置 1,则返回“真” -
.REPEAT 和 .WHILE 伪指令还提供了另一种方法来编写循环
.REPEAT
statements
.UNTIL condition.WHILE 伪指令在执行循环体之前测试条件:
.WHILE condition
statements
.ENDW
3. 运算符
-
OFFSET 返回数字标号的偏移量,只能用于编译时已知的地址
-
PTR 运算符可以用来重写一个已经被声明过的操作数的大小类型
-
TYPE 运算符返回变量单个元素的大小,这个大小是以字节为单位计算的
-
LENGTHOF 运算符计算数组中元素的个数,元素个数是由数组标号同一行出现的数值来定义的,加逗号计算下一行
-
SIZEOF 运算符则为 LENGTHOF 与 TYPE 的乘积。
-
TYPEDEF运算符 TYPEDEF 运算符可以创建用户定义类型,这些类型包含了定义变量时内置类型的所有状态,PBYTE TYPEDEF PTR BYTE
-
USES 运算符与 PROC 伪指令一起使用,让程序员列出在该过程中修改的所有寄存器名。USES 告诉汇编器做两件事情:第一,在过程开始时生成 PUSH 指令,将寄存器保存到堆栈;第二,在过程结束时生成 POP 指令,从堆栈恢复寄存器的值。
USES 运算符紧跟在 PROC 之后,其后是位于同一行上的寄存器列表,表项之间用空格符或制表符(不是逗号)分隔。
如果使用偏移量参数,加上寄存器压入的大小
-
$ 当前偏移量
-
ADDR 运算符 传递参数指针 参数必须是汇编时常数
4. 操作数
- 直接偏移量操作数
- 变量名加偏移量
-
间接操作数 任何一个 32 位通用寄存器(EAX、EBX、ECX、EDX、ESI、EDI、EBP 和 ESP)加上括号就能构成一个间接操作数。
-
变址操作数 变址操作数是指,在寄存器上加上常数产生一个有效地址
-
指针 如果一个变量包含另一个变量的地址,则该变量称为指针
5. 标志位
-
CF 无符号整数运算结果超出范围 最高有效位产生进位或借位时 设置该标志位
-
OF 有符号整数运算结构超出范围 设置该标志位
-
两个正数相加,结果为负数
-
两个负数相加,结果为正数
-
ZF 运算结果为零时设置该标志位
-
AF 辅助标志位 最低字节三位有进位时设置该标志位
-
PF 最低字节为1的位数为偶数时设置该标志位
-
SF 符号标志位 运算结果为有符号数是设置该标志位
-
DF 方向标志位 设置为1时地址的变化方向为减少 为0时增加 用于串操作指令 控制地址的变化方向 CLD DF为0 STD DF为1
-
IF 终端允许标记位 为1允许终端 CLI STI
-
TF 陷阱标志位 为1 处理器单步执行指令
6. 堆栈
- 汇编里把一段内存空间定义为一个栈,栈总是先进后出,栈的最大空间为 64K。由于 “栈” 是由高到低使用的,所以新压入的数据的位置更低,ESP 中的指针将一直指向这个新位置,所以 ESP 中的地址数据是动态的。
- 堆栈证明了它很适合于保存信息,包括过程调用嵌套。一般说来,堆栈结构用于程序需要按照特定顺序返回的情况。
-
后进先出 LIFO
-
ESP 扩展堆栈指针 EBP 扩展帧指针
-
入栈:32 位入栈操作把栈顶指针减 4,再将数值复制到栈顶指针指向的堆栈位置。
-
出栈:出栈操作从堆栈删除数据。数值弹岀堆栈后,栈顶指针增加(按堆栈元素大小),指向堆栈中下一个最高位置。
-
堆栈的作用
- 当寄存器用于多个目的时,堆栈可以作为寄存器的一个方便的临时保存区。在寄存器被修改后,还可以恢复其初始值。
- 执行 CALL 指令时,CPU 在堆栈中保存当前过程的返回地址。
- 调用过程时,输入数值也被称为参数,通过将其压入堆栈实现参数传递。
- 堆栈也为过程局部变量提供了临时存储区域。
-
在 32 位模式下,堆栈参数总是由 Windows API 函数使用。然而在 64 位模式下,Windows 函数可以同时接收寄存器参数和堆栈参数。
堆栈帧 (stack frame)( 或活动记录 (activation Tecord)) 是一块堆栈保留区域,用于存放被传递的实际参数、子程序的返回值、局部变量以及被保存的寄存器。
堆栈帧的创建步骤如下所示:
-
被传递的实际参数。如果有,则压入堆栈。
-
当子程序被调用时,使该子程序的返回值压入堆栈。
-
子程序开始执行时,EEP 被压入堆栈。
-
设置 EBP 等于 ESP。从这时开始,EBP 就变成了该子程序所有参数的引用基址。
-
如果有局部变量,修改 ESP 以便在堆栈中为这些变量预留空间。
-
如果需要保存寄存器,就将它们压入堆栈。
程序内存模式和对参数传递规则的选择直接影响到堆栈帧的结构。
学习用堆栈传递参数有个好理由:几乎所有的高级语言都会用到它们。比如,如果想要在 32 位 Windows 应用程序接口 (API) 中调用函数,就必须用堆栈传递参数。而 64 位程序可以使用另一种不同的参数传递规则。
32 位模式中,通过堆栈向子程序传递 64 位参数时,先将参数的高位双字入栈,再将其低位双字入栈。这样就使得整数在堆栈中是按照小端顺序(低字节在低地址)存放的
传入8位16变量出错,立即数自动扩展
-
7. x64 调用规范
-
CALL 指令将 RSP(堆栈指针)寄存器减 8,因为地址是 64 位的。
-
前四个参数依序存入 RCX、RDX、R8 和 R9 寄存器,并传递给过程。如果只有一个参数,则将其放入 RCX。如果还有第二个参数,则将其放入 RDX,以此类推。其他参数,按照从左到右的顺序压入堆栈。
-
调用者的责任还包括在运行时堆栈分配至少 32 字节的影子空间(shadow space),这样,被调用的过程就可以选择将寄存器参数保存在这个区域中。
-
在调用子程序时,堆栈指针(RSP)必须进行 16 字节边界对齐(16 的倍数)。CALL 指令把 8 字节的返回值压入堆栈,因此,除了已经减去的影子空间的 32 之外,调用程序还必须从堆栈指针中减去 8。后面的示例将显示如何实现这些操作。
-
Microsoft 遵循固定模式实现 64 位编程中的参数传递和子程序调用,该模式被称为 Microsoft x64 调用规范(Microsoft x64 calling convention)。它既用于 C 和 C++ 编译器,也用于 Windows API库。
只有在要么调用 Windows 函数,要么调用 C 和 C++ 函数时,才需要使用这个调用规范。它的特点和要求如下所示:
-
由于地址长为 64 位,因此 CALL 指令把 RSP(堆栈指针)寄存器的值减去8。
-
第一批传递给子程序的四个参数依次存放于寄存器 RCX、RDX、R8 和 R9。因此,如果只传递一个参数,它就会被放入 RCX。如果还有第二个参数,它就会被放入 RDX,以此类推。其他参数按照从左到右的顺序入栈。
-
长度不足 64 位的参数不进行零扩展,因此,其高位的值是不确定的。
-
如果返回值的长度小于或等于 64 位,那么它必须放在 RAX 寄存器中。
-
主调者要负责在堆栈中分配至少 32 字节的影子空间,以便被调用的子程序可以选择将寄存器保存在这个区域中。
-
调用子程序时,堆栈指针(RSP)必须对齐 16 字节边界。CALL 指令将 8 字节的返回地址压入堆栈,因此,主调程序除了把堆栈指针减去 32 以便存放寄存器参数之外,还要减去8。
-
被调用子程序执行结束后,主调程序需负责从运行时堆栈中移除所有的参数和影子空间。
-
大于 64 位的返回值存放于运行时堆栈,由 RCX 指出其位置。
-
寄存器 RAX、RCX、RDX、R8、R9、R10 和 R11 常常被子程序修改,因此,如果主调程序想要保存它们的值,就应在调用子程序之前将它们入栈,之后再从堆栈弹出。
-
寄存器 RBX、RBP、RDI、RSI、R12、R13、R14 和 R15 的值必须由子程序保存。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U9DBV7OJ-1616406761400)(E:/%E8%BD%AF%E4%BB%B6%E4%B8%8B%E8%BD%BD/%E5%8A%9E%E5%85%AC/%E7%AC%94%E8%AE%B0/%E5%9B%BE%E7%89%87/image-20201016113242008.png)]
-
8. 调用规范
-
_stdcall 入栈规则 从右到左 平衡堆栈方式 被调用者平衡
-
_cdecl 入栈规则 从右到左 平衡堆栈方式 调用者平衡
C 调用规范则允许子程序声明不同数量的参数,主调程序可以决定传递多少个参数
-
_fastcall 入站规则 左边开始的两个不大于四字节的参数保存到ECX,EDX,其余参数从右到左入栈
平衡堆栈方式 被调用者平衡
-
_thiscall 入栈规则 通过ECX传递this指针,从右到左入栈 平衡堆栈方式 被调用者平衡
9. 参数类型
- 输入 提供变量的值
- 输出 提供缓冲区
- 输入输出型 提供变量的地址
10. 汇编调试器
-
16位:Debug,Trubo DBG
-
32位:OLLYDBG,windbg ,x32dbg,GDB,VSdbg,
-
64位:x64dbg
-
16位汇编调试器:Debug,TD
32位汇编调试器:x32dbg,windbg,OllyDbg
学员答案:
ollyDBG winDBG GDB vs Debugger
11. ollydbg操作
-
L : log 保存日志信息
E :程序的所有模块的信息(加载基址,大小,OEP,路径)
M :程序的内存映射视图
T :线程信息
W :窗口信息
H :句柄表
C :CPU窗口(反汇编窗口)
/ :补丁信息
K :调用堆栈
B :软件断点列表
R :显示参考(数据引用等)
… : RUN跟踪
S : 源码显示窗口 -
F2:下断点
F4:运行到光标选中处
F7:单步步入
F8:单步步入
F9:运行
F12暂停
ctrl+F2:重启调试
ctrl+F9:执行直到返回,到最近的ret
Alt+F9:执行直到用户代码段
shift+F9:忽略异常运行
(Ctrl+A) 分析当前模块的代码段
(Ctrl+B) 开始二进制搜索
(Ctrl+C) 复制所选内容到剪贴板
(Ctrl+E) 以二进制(十六进制)格式编辑所选内容
(Ctrl+F) 开始命令搜索
(Ctrl+G) 输入十六进制地址,快速定位到该地址处。该命令将弹出输入地址或表达式的窗口。该命令不会修改 EIP
(Ctrl+J) 列出所有的涉及到该位置的调用和跳转,在您用这个功能之前,您必须使用分析代码功能
(Ctrl+K) 查看与当前函数相关的调用树[Call tree]。在您用这个功能之前,您必须使用分析代码功能
(Ctrl+L) 搜索下一个,重复上一次的搜索内容
(Ctrl+N) 打开当前模块的名称(标签)列表
(Ctrl+O) 扫描object文件
(Ctrl+R) 搜索所选命令的参考
(Ctrl+S) 多条命令搜索,显示命令查找对话框供您输入汇编命令,并从当前命令开始搜索(Ctrl+D) 中文引擎搜索
Ctrl+X:复制地址
shift+x:复制二进制数据
enter:进入函数
‘-’:返回
-
窗口
CPU窗口 寄存器窗口 堆栈窗口 自动分析小窗口 内存数据窗口 断点列表窗口
-
ctrl+a 分析 右键分析 - 预设参数 - 断点 - WinProc断点 - 选择消息类型(WM_COMMAND在栈中查看)
-
右键查找 - 命令 - 所有命令 选择特征码 - 在每个命令上设置断点 找到关键调用
-
查找用户的所有注释
-
关键窗口API断点回溯找回调函数
-
W窗口查看
-
点击直接修改寄存器,内存信息
-
在内存窗口crtl+b所有模块中搜索
-
右键断点,内存,硬件断点,条件断点,调式中可查看断点,四个硬件断点,一个内存断点
-
检测软件断点,一般来说靠内存校验(crc、关键点监视)判断有没有哪行指令被修改成了断点。
int 3 0xCC,需要可读写的位置ram
软件断点不能设置在ROM/FLASH里面。因为软件断点的实现需要替换要设置断点的位置的内容,这点在ROM/FLASH做不到。
-
检测硬件断点,通常来说是通过相关api或主动触发异常来获得进程的一些运行信息(上下文),其中有包括硬件断点相关的内容。
-
检测内存断点,一般监视内存的属性是否被修改了。
-
12. 常用指令
一、数据传输指令
MOV:数据传递
PUSH: 入栈指令
POP: 出栈指令
XCHG: 内容交换
LEA: 有效地址传送
PUSHF: 标志寄存器入栈
POPF: 标志寄存器出栈
PUSHA: 所有寄存器入栈
POPA: 所有寄存器出栈
二、算术与逻辑运算指令
ADD: 加法指令
ADC: 带进位加法指令
INC: 加1指令
SUB: 减法指令
SBB: 带借位的减法指令
DEC: 减1指令
MUL: 无符号乘法指令
IMUL: 有符号乘法指令
DIV: 无符号除法指令
IDIV: 有符号除法指令
CMP: 比较指令(做减法)
AND: 逻辑与指令(按位与)
OR: 逻辑或指令(按位或)
XOR: 逻辑异或指令(按位异或)
NOT: 逻辑非指令(按位取反)
TEST: 按位与指令(按位与)
三、串操作指令
MOVS: 串传送指令
STOS: 存入串指令
LODS: 串取出指令
CMPS: 串比较指令
REP: 重复操作前缀
四、控制转移指令
JMP: 无条件转移指令
JXX: 条件转移指令
LOOP : 循环指令
CALL: 子程序指令
RET: 子程序返回指令
13.寻址方式
立即数寻址 mov eax,1
寄存器寻址 mov eax,ebx
存储器寻址
- 直接寻址 mov eax,[2000]
- 寄存器间接寻址 mov eax,[esi]
- 寄存器相对寻址 mov eax,[esi+06]
- 基址变址寻址 mov eax,[ebx+esi]
- 相对基址变址寻址 mov ax,[ebx+esi+06]
14. 汇编与反汇编引擎
- 反汇编 BeaEngine,capstone
- 汇编 keystone,XEDParse
15. OPcode
OPCode就是机器码,是CPU微指令对外的接口。
OpCode是Operation Code,意即操作码的意思。
OPCode是汇编代码编译以后的二进制代码。
就像现实生活中一个名称往往并不唯一对应一个事物一样,OpCode和助记符的关系如下:不同的OpCode可能有同样的助记符;一个OpCode可能有几个助记符对应.
不同的CPU对应不同的OPCODE
90 : nop
E9 : jmp
E8 : call
FF15: call [xxxxx]
0xCC int 3
0x40 inc eax
0x50 push eax
0x58 pop eax
66 切换操作数大小 eax … /dword ptr[]… -> ax / word ptr[]… 67 切换地址大小 dword ptr[eax]… dword ptr[ax] F2/F3 重复操作数 rep repne 2E/36/3E/26/64/65 修改默认段,段超越 F0 锁定前缀,变成原子操作 lock
00417000 - E9 CDA1FEFF JMP 000_jmpO.004011D2
00417005 90 NOP
00417006 90 NOP
00417007 > 60 PUSHAD
00417008 BB 00104000 MOV EBX,000_jmpO.00401000 ; 入口地址
0041700D B9 FF9F0000 MOV ECX,0x9FFF
00417012 803419 15 XOR BYTE PTR DS:[ECX+EBX],0x15
00417016 ^ E2 FA LOOPD SHORT 000_jmpO.00417012
00417018 E8 00000000 CALL 000_jmpO.0041701D
0041701D 58 POP EAX
0041701E 2D 1D700100 SUB EAX,0x1701D
00417023 05 D2110000 ADD EAX,0x11D2
00417028 FFD0 CALL EAX