前言——汇编语言的出现
1.了解CPU
什么是CPU:了解CPU
CPU执行的的指令是机器指令,机器指令是由0和1组成的二进制编码。CPU所能直接理解和执行的直流就是机器语言,而这些指令在底层上来说是以二进制的形式存在,是计算机硬件能够直接识别的最基本形式
101111000 1110000 1111110000
2.从机器语言到汇编指令的转变
汇编语言的提出概括地来说是由于人类需要一种易于理解和阅读的语言。汇编语言也就是在这种背景下诞生的。
汇编语言是一种使用助记符来表示机器指令的语言。每一条汇编指令直接映射到一条机器指令,例如:
MOV
表示将数据移动到寄存器JMP
表示无条件跳转ADD
表示将两个两个数相加
汇编语言的出现本质上就是为了方便程序员的编写 ,使用符号化的语言来代替机器语言
3.汇编语言的编译过程
汇编语言不能被计算机执行,需要借助汇编器将汇编语言转化为机器语言
例如:
汇编指令MOV EAX, 1
在汇编器的作用下会被转化为 B8 01 00 00 00
从而被CPU执行
机器语言
10111000 00000001 00000000 00000000
汇编语言
MOV EAX, 1
4.汇编语言的组成
1.汇编指令:机器码的助记符,有对应的机器码。
2.伪指令:没有对应的机器码,由编译器执行,计算机不会执行。
3.其他符号:+、-、*等,由编译器识别,没有对应的机器码。
CPU架构
CPU架构介绍
不同的架构决定了汇编语言的编写规则和标准
1.x86架构
x86架构是Intel推出的16位处理器架构。经过多次扩展,发展成为了如今的32位(x86)和64位(x86-64)架构
x86-64架构是AMD推出后被Intel采用,这种架构与32位的x86架构指令兼容,同时也引用了更多寄存器和指令扩展,也是现在电脑端运用最广泛的架构。很多CTF的PC端逆向都是这种架构最早的x86-64指令集是Intel在8086处理器中首创出,后被全球广泛使用
2.ARM架构
与x86架构不同,ARM架构在移动端运用更广,很多手机和平板甚至部分Windows10和11会采用这种架构
不同架构汇编指令的区分
1.x86汇编
适用于32位程序和32位处理器
x86指令集:最早基于Intel 8086处理器架构,原本是16位架构,后来发展为32位架构。x86汇编语言用于32位处理器,其指令和寄存器都与32位处理器兼容。
2.x64汇编(也叫AMD64汇编)
适用于64位程序及64位处理器
x64指令集是对x86指令集的64位扩展,最早由AMD提出并实现。与x86相比多了很多扩展,例如:
使用64位寄存器(RAX,RBX,RCX),代替了32位寄存器(EAX,EBX,ECX)
支持更多的汇编指令等
关于x64汇编指令集在[Intel-64 和 IA-32 体系结构软件开发手册]有详细描述(https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html)
认识常见的汇编指令
先举个例子,这是一个简单的C源文件,当执行程序的时候会输出个hello
world
可以用gcc编译器将c源文件转换为汇编语言
.file "\346\261\207\347\274\226.c"
.text
.section .rdata,"dr"
.LC0:
.ascii "world\0"
.LC1:
.ascii "hello %s\12\0"
.text
.globl main
.def main; .scl 2; .type 32; .endef
.seh_proc main
main:
pushq %rbp
.seh_pushreg %rbp
movq %rsp, %rbp
.seh_setframe %rbp, 0
subq $32, %rsp
.seh_stackalloc 32
.seh_endprologue
movl %ecx, 16(%rbp)
movq %rdx, 24(%rbp)
call __main
leaq .LC0(%rip), %rax
movq %rax, %rdx
leaq .LC1(%rip), %rax
movq %rax, %rcx
call printf
movl $0, %eax
addq $32, %rsp
popq %rbp
ret
.seh_endproc
.def __main; .scl 2; .type 32; .endef
.ident "GCC: (Rev2, Built by MSYS2 project) 14.2.0"
.def printf; .scl 2; .type 32; .endef
这是转换出来的汇编代码,注意汇编程序由三个不同的元素组成:
指示(Directives) 以点号开始,用来指示对编译器,连接器,调试器有用的结构信息。指示本身不是汇编指令。例如,.file 只是记录原始源文件名。.data表示数据段(section)的开始地址, 而 .text 表示实际程序代码的起始。.string 表示数据段中的字符串常量。 .globl main指明标签main是一个可以在其它模块的代码中被访问的全局符号 。至于其它的指示你可以忽略。
标签(Labels) 以冒号结尾,用来把标签名和标签出现的位置关联起来。例如,标签.LC0:表示紧接着的字符串的名称是 .LC0. 标签main:表示指令 pushq %rbp是main函数的第一个指令。按照惯例, 以点号开始的标签都是编译器生成的临时局部标签,其它标签则是用户可见的函数和全局变量名称。
指令(Instructions) 实际的汇编代码 (pushq %rbp), 一般都会缩进,以便和指示及标签区分开来。
注:
不同的C语言编辑器编辑出的c源文件用gcc汇编后是不一样的,就像上面是用Vistaul studio编辑的,用gcc编译的汇编代码看上去和目前阶段CTF中的伪C代码很不一样
其中很常用的几个:
1.MOV 指令
可以用英语单词move来理解,所起到的作用与移动,传送有关
MOV指令可以实现将数据在内存,寄存器之间互相传送
mov指令的顺序是从右到左,如mov a,b,则把b的值复制给a,即a=b
如:
mov rax, 'hgfedcba'
就是将’hgfedcba’传到rax寄存器里面
2.lea指令
lea,官方解释Load Effective Address,即加载有效地址到目标寄存器中的意思,看上去跟mov指令的作用很像,但二者存在区别。
比如:
lea rax, aFlagS
这里的意思是将flag的地址加在到rax寄存器中
二者的区别在于:
·mov 指令用于在寄存器或内存之间传输数据值。
·lea 指令用于计算有效地址,并将结果加载到寄存器中,但不访问该地址处的内存内容。
我的理解是mov指令是跟数据值打交道,而lea指令是跟地址有关
3.call指令
call指令用英语来理解就是呼叫调用,也就是起到调用函数的作用,如:
call printf
就是调用printf(打印)函数
4.push指令与pop指令
push 指令用于将一个寄存器或值压入栈中的指令,相对的pop指令就是出栈指令,用于将值从栈中弹出到一个寄存器或内存位置。
如:
push rbp
这段指令的意思是将基址指针寄存器rbp压入栈中以保存其值
pop rbp
将当前栈顶的 8 字节数据弹出到 rbp 寄存器,并将堆栈指针 rsp 增加 8 字节。