CTF竞赛权威指南读书笔记
第三章:汇编基础
CPU架构与指令集
CPU即中央处理单元(central processing unit),有时也简称为处理器。作用是从内存中读取指令,然后解码和执行。
CPU架构就是CPU的内部设计和结构。
指令集架构(Instrucion Set Architecture,ISA)简称指令集,包含了一系列的操作码(opcode),以及由特定CPU执行的基本命令。指令集在CPU中的实现称为微架构,要想设计CPU,首先得决定使用什么样的指令集,然后才是设计硬件电路。
由于指令集是一堆二进制数据,非常不利于阅读和理解,于是就有人发明了汇编语言,用近似人类语言的方式堆指令集进行了描述,每条汇编指令都有对应的指令。
指令集架构
第一个指令集架构称为:CISC(复杂指令集计算机),典型代表就是x86处理器。32位处理器8086,8088,80286都属于x86处理器。后来AMD又将x86架构阔充到了64位,称为AMD64。(Linux发行版分别称i386与amd64)
IBM则又有一套成体系的架构,称为:RISC(精简指令集计算机),它减少了指令的数量,和简化指令的格式来优化和提高CPU的指令执行效率。典型代表就是ARM处理器,其64位版本指令集称为AArch32。(Linux发行版中分别称为aarch64和arm)其具有较高的执行效率与较低的资源消耗,IOS,Android,更多的移动操作系统与嵌入式操作系统都运行在这类处理器上。
CISC与RISC对比
CISC的指令代码不等长,不利于解码与优化操作。
RISC等长的操作指令,会使得平均代码长度更大,占用更多的存储空间。
CISC的操作指令更为复杂,精细,便捷,
RISC则指令数较少,往往一个CIRS指令能够解决的问题,需要使用很多条RISC指令。
CISC的寻址方式多样,RISC只支持寄存器寻址与立即数寻址。
CISC的通用寄存器只有8~16个,RISC的通用寄存器数量高达31个。后者可以完全只利用寄存器传参。
x86/x64汇编基础
CPU操作模式
对于x86系统而言,有三个操作模式:保护模式(子模式8086模式),实地址模式,系统管理模式。
保护模式下所有的指令和特性都是有效的,此特性限制在程序所分配的独立内存区域(内存段)内。超过内存段的部分会被CPU阻止。
实地址模式下,程序可以直接访问硬件,而不必经过虚拟地址的映射。
系统管理模式则为操作系统提供了许多底层管理与安全保护的操作权限。
x86-64系统下,引入了一种新的模式,叫做IA-32e,分为兼容模式与64位模式,兼容模式先32位程序与16位程序无需重新编译,64位模式则会导致处理器在64位的地址空间下运行程序。
语法风格
AT&T风格与Intel风格,更为常用的是Intel风格。
- 16进制数用H作为后缀。
- 间接寻址用[ ]表示
- 操作位数为指令+dword ptr等(如QWORD PTR[RAX])
寄存器与数据类型
具体通用寄存器名称
16位寄存器
32位寄存器)
64位寄存器
注意,在64位模式下,需要在每条汇编指令增加REX的前缀后,操作数才会变成64位,增加8个通用寄存器。
此外,64位模式下与32位模式有着相同的标志位状态;64位模式不能访问通用寄存器的高位字节(AH等)
数据类型有:整型常量,浮点型常量,字符串常量。
常用汇编指令:
数据传送指令:
MOV
tips:数组表示法:
.data
数组名 数据成员大小 数据1,数据2……
数据运算指令:
SUB,ADD,INC,DEC,NEG(减,加,+1,-1,求补)
此处运算皆有溢出可能性,会改变FLAGS寄存器标志位。
跳转与循环指令:
JMP:无条件跳转指令,在编写汇编时,需要一个标号来标识。一般情况下该标号必须和JMP指令位于同一函数中,但使用全局标号则不受影响。(标号象征着CPU中的虚拟地址)
LOOP,ECX寄存器存放LOOP的次数,每次调用LOOP前,ECX中存放的数字先减一,再判断是否等于零,然后再执行。
栈操作指令:
PUSH & POP
查看栈内存大小指令
ulimit -a
栈相关操作指令:
栈寄存器:(E/R)SP,
栈的作用:
- 存储局部变量;
- 执行CALL指令调用函数时,保存函数地址以便函数结束时正确返回。
- 传递函数参数
CALL指令在调用某个子函数时,下一条指令地址会被PUSH入栈,等到函数结束时,程序将执行RET指令跳转到这个返回地址,将控制权交还给调用函数。等价于先POP再JMP到这个地址。
调用子函数这一行为需要使用PROC与ENDP伪指令来定义,且需要分配一个有效标识符,所有的x86汇编程序中都包含标识符为main的函数,而mian函数不需要RET指令。
栈传递函数参数时,最常见的调用约定为cdecl,
其格式为:
push arg3
push arg2
push arg1
call func
被调用函数是不知道调用函数向其传递了多少参数的。所以对于参数数量可变的函数来说,就需要说明符标示格式化说明,明确参数信息。
实验:
例程:
#include <stdio.h>
int main()
{
int a = 12, b = 13;
printf("%d,%d,%d", 9988);
return 0;
}
执行结果:
9988,1984768,0
1984768,0,一般来讲这两个数是printf内部的局部变量。
PUSHFD&POPFD
由于MOV指令不能修改FLAGS寄存器内容,因此使用PUSHF就是保存FLAGS的最佳方式。
总结
指令集——>架构——>CPU实体——>汇编语言出现——>指令集(汇编语言)两种——>操作模式——>编写风格——>常用汇编指令——>重点讲述栈