汇编学习笔记第一次

感谢乐哥能花费时间和精力来带带弟弟,弟弟一定努力

借着这次机会,刚好复习复习汇编的知识,将其熟练。

BL2lX4.png

1
2
3
4
5
6
7
8
9
10
hint:

1. 编译命令:gcc -z execstack -z norelro -no-pie -fno-stack-protector -o program program.c
2. 反编译命令:objdump -d -m i386:x86-64:intel program

gcc -z execstack -z norelro -no-pie -fno-stack-protector -o test test.c
objdump -d -m i386:x86-64:intel test
或者是
gcc -g -c test.c
objdump -d -M intel -S test.o

先巩固下基础知识

16位寄存器

指令 描述
AH累加器(Accumulator) AL 可用于乘、除、输入/输出等操作(在乘除指令中指定用来存放操作数)
BH基地址寄存器(Base Register) BL 在计算存储器地址时,可作为基址寄存器使用。
BL
CH计数寄存器(Count Register) CL 在循环和字符串操作时,要用它来控制循环次数;在位操作中,当移多位时,要用CL来指明移位的位数;
CL
DH数据寄存器(DataRegister) DL 在进行乘、除运算时,它可作为默认的操作数参与运算,也可用于存放I/O的端口地址。在16位CPU中,AX、BX、CX和DX不能作为基址和变址寄存器来存放存储单元的地址,但在32位CPU中,其32位寄存器EAX、EBX、ECX和EDX不仅可传送数据、暂存数据保存算术逻辑运算结果,而且也可作为指针寄存器,所以,这些32位寄存器更具有通用性。
DL

32位寄存器

指令 描述
eax EAX被称为32位累加器
ecx(计数寄存器) ECX中的C即Count(计数之意)。在许多指令中,ECX、CX、CL被用作计数器。
edx(数据寄存器) EDX中的D即Data(数据之意)。在进行乘法等运算时,常用EDX与EAX或DX与AX的组合来存放一个4字数或双字数。此外,DX也用来存放I/O端口地址。
ebx(基址寄存器) EBX中的B即Base(基址之意)。EBX与BX常用来表示内存地址,现在所使用的PC中地址都是较大的整数,一般不会是8位,所以BL就不常使用了。
esp ESP或SP用来指示堆栈段中的栈顶地址。一般情况下不使用ESP或SP做算术运算。
ebp EBP和BP常用来存放内存地址,它们在默认情况下指向堆栈段中的存储单元。
esi ESI或SI在串指令中表示源数据串的地址。
edi EDI或DI在串指令中表示目的数据串的地址。
eip 指令指针EIP、IP(Instruction Pointer)是存放下次将要执行的指令在代码段的偏移量。
eflags(Flags Register) 。标志寄存器也称为程序状态寄存器或状态寄存器。指令指针和标志寄存器不能用作指令的操作数,它们是由CPU直接操纵的。特别地,EIP和EFLAGS的低16位分别由IP和FLAGS标识。
cs(Code Segment Register)代码段寄存器 其值为代码段的段值;
ss(Stack Segment Register)堆栈段寄存器 其值为堆栈段的段值;
ds(Data Segment Register)数据段寄存器 其值为数据段的段值;
es(Extra Segment Register)附加段寄存器 其值为附加数据段的段值;
fs(Extra Segment Register)附加段寄存器 其值为附加数据段的段值;
gs(Extra Segment Register)附加段寄存器 其值为附加数据段的段值。

64位寄存器

指令 描述
rax 与32位类似
rbx 与32位类似
rcx 与32位类似
rdx 与32位类似
rsi 与32位类似
rdi 与32位类似
rbp 与32位类似
rsp 与32位类似
r8 与32位类似
r9 与32位类似
r10 与32位类似
r11 与32位类似
r12 与32位类似
r13 与32位类似
r14 与32位类似
r15 与32位类似
rip 与32位类似
eflags 与32位类似
cs(Code Segment Register)代码段寄存器 其值为代码段的段值;
ss(Stack Segment Register)堆栈段寄存器 其值为堆栈段的段值;
ds(Data Segment Register)数据段寄存器 其值为数据段的段值;
es(Extra Segment Register)附加段寄存器 其值为附加数据段的段值;
fs(Extra Segment Register)附加段寄存器 其值为附加数据段的段值;
gs(Extra Segment Register)附加段寄存器 其值为附加数据段的段值。

寻址方式

寻址方式 示例 操作对象
立即寻址 1000h 1000h这个数字
直接寻址 [1000h] 内存1000h地址的单元
寄存器寻址 RAX RAX这个寄存器
寄存器间接寻址 [RAX] 以RAX中存的数作为地址的内存单元
基址寻址 [RBP+10h] 将RBX中的数作为基址,加上10h,访问这个地址的内存单元
变址寻址 [RDI+10h] 将RDI作为变址寄存器,将其中的数字加上10h,访问这个地址的内存单元
基址加变址寻址 [RBX+RSI+10h] 逻辑同上

常见基础指令

指令类型 操作码 指令示例 对应作用
数据传送指令 mov mov rax,rbx rax = rbx
mov qword ptr [rdi],rax *(rdi) = rax
取地址指令 lea lea rax,[rsi] rax = & *(rsi)
算术运算指令 add add rax,rbx rax += rbx
add qword ptr [rax],rax *(rdi) += rax
sub sub rax,rbx rax -= rbx
逻辑运算指令 and and rax,rbx rax &= rbx
xor xor rax,rbx rax ^= rbx
函数调用指令 call call 0x401000 执行0x40100地址的函数
函数返回指令 ret ret 函数返回
比较函数 cmp cmp rax,rbx 根据rax与rbx比较的结果改变标志位
无条件跳转函数 jmp jmp 0xa01000 跳到0x401000地址执行
栈操作指令 push push rax 将rax的值压入栈中
pop pop rax 从栈上弹出一个元素放入rax

标志寄存器

名称 标志 含义
AF 辅助进位标志 当运算结果在第3位进位
PF 奇偶效验标志 当运算结果的最低有效字节有偶数个1时置1
SF 符号标志 有符号整形的符号位为1时置1,代表这是一个负数
ZF 零标志 当运算结果为全零时置1
OF 溢出标志 运算结果在被操作数是有符号数且溢出时置1
CF 进位标志 运算结果向最高位以上进位时置1,用来判断无符号数的溢出

常见判断指令

指令 cmp a,b 条件(成立就跳转) flag条件
jz/je a = b ZF = 1
jnz/jne a != b ZF = 0
jb/jnae/jc a < b 无符号 CF = 1
ja/jnbe a > b 无符号
jna/jbe a <= b 无符号
jnc/jnb/jae a >= b 无符号 CF = 0
jg/jnle a > b 有符号
jge/jnl a >= b 有符号
jl/jnge a < b 有符号
jle/jng a <= b 有符号
jo OF = 1 (运算结果在被操作数的是有符号数且溢出时置1)
js SF = 1 (这是个负数)

局部变量

人们把每个函数自己的这一片区域称为帧,由于这些帧都在栈上,所以又被称为栈帧,然而栈的内存区域并不一定是固定的,而且随着每次调用路径不同,栈帧的位置也会不同。虽然栈的内容随着进栈和出栈会一直不断变化,但是一个函数中,每个局部变量相当于该函数栈帧的偏移,都是固定的,所以可以引入一个寄存器来专门存储当前栈帧的位置,即用ebp,称为帧指针。程序在函数初始化阶段赋值ebp为栈帧中间的某个位置,这样可以用ebp引用所有的局部变量,由于上一层的父函数也要用ebp,因此要在函数开始时先保存ebp,再赋值ebp为自己的栈帧的值。

1.简单赋值

EBP:栈底寄存器,存放了指向函数栈帧栈底的地址

ESP:栈顶寄存器,存放了指向函数栈帧栈顶的地址

EIP:指令寄存器(程序计算器),存放CPU将要执行的指令

RIP:是保存了当前的程序的指针,加上偏移,就可以索引到静态变量的空间。

rsp: 是随时可能变化的(64位的函数调用)

rbp: 来作为参数和局部变量的基址(64位的函数调用)

1 qword = 2 dword = 4 word = 8 Byte = 64 bit eg:’1’ = 0x31

第一题

1
2
3
4
5
6
7
55                   	push   rbp      ;将函数(比如main)的栈底压到栈
48 89 e5 mov rbp,rsp //esp和ebp都指向栈顶
c7 45 f8 01 00 00 00 mov DWORD PTR [rbp-0x8],0x1 ;给栈中相应的地址赋值 1
c7 45 fc 02 00 00 00 mov DWORD PTR [rbp-0x4],0x2 ;给栈中相应的地址赋值 2
b8 00 00 00 00 mov eax,0x0 ;将寄存器恢复
5d pop rbp ;将函数返回的地址弹出并记录
c3 ret ;返回弹出的地址
1
2
3
4
5
6
#include <stdio.h>
int main()
{
int a[2]={1,2};
return 0;
}

这题就复习复习下一汇编语言

着重分析一下

汇编书中我们是用 ebp 和 esp

而在实际应用环境中,程序是64位的,就需要用rbp和esp来代替32位的ebp和esp寄存器。

rbp指向函数入口上堆栈的顶部,x86的堆栈朝向低地址;

所以访问地址为rbp的负地址[rbp-0x8]

第二题

1
2
3
4
5
6
7
8
9
10
55                   	push   rbp             ;将函数(比如main)的栈底压到栈
48 89 e5 mov rbp,rsp ;esp和ebp都指向栈顶
48 b8 31 32 33 34 35 movabs rax,0x37363534333231
;将一堆数据存入rax寄存器,可猜测是字符串"7654321",c语言中一改
36 37 00 ;字符串为"7654321",看前面的16进制数,应该为"1234567"
48 89 45 f4 mov QWORD PTR [rbp-0xc],rax ;将给栈中相应的地址的值改成rax寄存器中的值
c7 45 fc 02 00 00 00 mov DWORD PTR [rbp-0x4],0x2 ;将给栈中相应的地址的值改成2
b8 00 00 00 00 mov eax,0x0 ;将寄存器恢复为0
5d pop rbp ;将函数返回的地址弹出并记录
c3 ret ;返回弹出的地址
1
2
3
4
5
6
7
#include<stdio.h>
int main()
{
char b[8] = "1234567";
int a=2;
return 0;
}

第三题

1
2
3
4
5
6
7
8
9
10
11
12
13
400457:	55                   push  rbp
400458: 48 89 e5 mov rbp,rsp
40045b: c7 45 fc 02 00 00 00 mov DWORD PTR [rbp-0x4],0x2;将给栈中相应的地址的值改成2
400462: 83 7d fc 03 cmp DWORD PTR [rbp-0x4],0x3;比较栈中的地址与0x3的关系
400466: 7e 09 jle 400471 <main+0x1a>;得知是比较类似 a>3是否成立 不是则跳转
400468: c7 45 f8 01 00 00 00 mov DWORD PTR [rbp-0x8],0x1
; a>3成立,将给栈中相应的地址的值改成1
40046f: eb 07 jmp 400478 <main+0x21> ; 判断结束,跳出判断
400471: c7 45 f8 00 00 00 00 mov DWORD PTR [rbp-0x8],0x0
; 对应上面跳转来的,a<=3 将给栈中相应的地址的值改成0
400478: b8 00 00 00 00 mov eax,0x0 ;从上面的分析可以判断为if判断
40047d: 5d pop rbp
40047e: c3 ret
1
2
3
4
5
6
7
8
9
10
#include<stdio.h>
int main()
{
int a = 2,b;
if(a > 3)
b=1;
else
b=0;
return 0;
}

附加题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
400457:	55                   	push   rbp
400458: 48 89 e5 mov rbp,rsp
40045b: c7 45 fc 02 00 00 00 mov DWORD PTR [rbp-0x4],0x2
;将给栈中相应的地址的值改成2
400462: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
;将给栈中相应的地址的值存到寄存器eax中
400465: 83 f8 02 cmp eax,0x2
;比较eax和0x2
400468: 74 15 je 40047f <main+0x28>
;如果相等就跳转
40046a: 83 f8 03 cmp eax,0x3
;较eax和0x3
40046d: 74 19 je 400488 <main+0x31>
;如果相等就跳转
40046f: 83 f8 01 cmp eax,0x1
;较eax和0x3
400472: 74 02 je 400476 <main+0x1f>
;如果相等就跳转
400474: eb 1a jmp 400490 <main+0x39>
;都不是,跳出整个判断,可以看出这应该是switch
400476: c7 45 f8 02 00 00 00 mov DWORD PTR [rbp-0x8],0x2
;将给栈中相应的地址的值改成2
40047d: eb 11 jmp 400490 <main+0x39>
;跳出整个判断
40047f: c7 45 fc 03 00 00 00 mov DWORD PTR [rbp-0x4],0x3
;将给栈中相应的地址的值改成3
400486: eb 08 jmp 400490 <main+0x39>
;跳出整个判断
400488: c7 45 f8 01 00 00 00 mov DWORD PTR [rbp-0x8],0x1
;将给栈中相应的地址的值改成3
40048f: 90 nop
;结束判断
400490: b8 00 00 00 00 mov eax,0x0
400495: 5d pop rbp
400496: c3 ret
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
int main()
{
int a = 2,b;
switch(a)
{
case 1:
b = 2;
break;
case 2:
a = 3;
break;
case 3:
b = 1;
break;
default:
break;
}
return 0;
}

感谢Prowes5师傅

BL2G7R.png