逆向入门-汇编基础
前几天开始尝试学习逆向,这两天差不多看了汇编基础的只是,尽力摆脱ida只会f5的状态.
逆向入门-汇编基础
基础解释
1 | |
这是最基础的输出hello-world的汇编代码,这里对其做出解释
可以看出分成了三段,data初始化全局变量,bss段存放未初始化的全局变量,最后test段存放指令
现在来看看test段,global作为全局声明让_start能被其他链接器调用
计算机基础
程序
静态的程序
当一个程序还是可执行文件时,他是一个静态的实体包含
- 文件头:标识标识这是一个可执行文件。程序执行的第一行代码的位置,告知系统下面的
.text、.data在文件的什么地方。 - 只读数据段:存储常量const
- 代码段(code\text):cpu能够执行的命令
- Data段:存放已经初始化的全局变量和静态变量
- bss段,存放未初始化的全局变量,静态变量取值为0
- 符号表:函数名变量名及其对应的地址
- 重定位信息:告诉连接器那些地址在运行的时候需要修正
- 调试信息:用于调试的时候与源代码行号映射
当然这只是常用的,其实还有什么.dynstr(动态字符串表)之类的
而一个运行中的程序就是进程
进程
- 内核
- 未映射区域
- 共享库
- 堆(heap):由用户自己开辟管理的空间,地址向高生长
- text段:存放的机器指令
- data段和bss段同上
- 栈(stack):向低地址生长,后进献出,存放了返回地址局部变量,函数参数,保存的寄存器,临时的值.
线程
线程是cpu的最小执行单位,一个进程可以有多个线程也可以只有一个线程,一个进程的线程间共享堆和text,bss,data段. 拥有独立的栈,程序计数器,寄存器和状态.
虚拟内存
为了安全性,程序是无法直接调用物理内存的.都是通过虚拟内存间接的调用的内存的,虚拟内存里面存放虚拟地址,虚拟内存本身没有任何存储的作用
虚拟内存本身是按页表来分的,同理物理内存也有自己的页表,并且利用了多级页表,他们之间通过MMU来进行映射.
寄存器
根据存储器分级
| 层级 | 硬件名称 | 速度 | 容量 |
|---|---|---|---|
| 寄存器 | CPU 寄存器 | 极快(指令级) | 几百字节 |
| L1 缓存 | CPU 内部缓存 | 非常快 | 几十 KB |
| L2 缓存 | CPU 内部缓存 | 快 | 几 MB |
| L3 缓存 | CPU 共享缓存 | 中等 | 几十 MB |
| 内存 | RAM (内存条) | 慢(相对于 CPU) | 几 GB / 几十 GB |
| 硬盘 | SSD / HDD | 极慢 | 几 TB |
汇编基础(只会一点点,真的很基础)
指令和伪指令
- 指令 (Instructions):
- 这些是直接翻译成机器码的操作码,CPU会实际执行它们。
- 它们代表了CPU可以执行的基本操作,如数据传送 (
MOV)、算术运算 (ADD,SUB)、逻辑运算 (AND,OR)、控制流 (JMP,CALL`) 等。 - 示例:
mov rax, 1,add ebx, ecx,jmp my_label
伪指令 (Directives / Pseudo-instructions):
1 | |
在我理解看来指令是对内存或者数据的直接操作
伪指令更偏向于定义.
data段
初始化全局数据,这里我们使用一系列伪指令来定义数据的大小和类型,并用标签(类似与变量)来命名
- 标签
标签是自定义的符号,代表这个数据在内存中的起始地址.链接器可以通过解析标签来访问数据 DB(Define Byte): 定义一个或多个字节 (8位)。DW(Define Word): 定义一个或多个字 (16位)。DD(Define Double Word): 定义一个或多个双字 (32位)。DQ(Define Quad Word): 定义一个或多个四字 (64位)。DT(Define Ten Bytes): 定义一个十字节数 (用于浮点数)。DO(Define Octa Word): 定义一个八字 (128位)。
示例:
1 | |
这里的message是一个字符串以0结尾和C语言的字符串一样.
bss段
.bss 节用于声明那些在程序启动时不需要特定初始值(通常默认为0)的变量。这有助于减小可执行文件在磁盘上的大小。
NASM提供了以下伪指令在 .bss 节中预留空间:
RESB(Reserve Byte): 预留指定数量的字节。RES(Reserve Word): 预留指定数量的字。RESD(Reserve Double Word): 预留指定数量的双字。RESQ(Reserve Quad Word): 预留指定数量的四字。
示例:
1 | |
程序在加载是为预留这块分配的内存空间并清零
text节
这里就是存放可执行指令的地方.
这里我们只介绍基础指令的操作.我才开始一般边做变搜哈哈哈
函数调用
一般来说利用call来进行函数跳转,但是在跳转前,我们一般会考虑函数的传入的值
在x86-64 Linux中,发起系统调用是通过 syscall 指令完成的。在执行 syscall 之前,我们需要按照特定的约定将参数放入指定的寄存器中:
| 寄存器 | 用途 |
|---|---|
RAX |
系统调用号 |
RDI |
第一个参数 |
RSI |
第二个参数 |
RDX |
第三个参数 |
R0 |
第四个参数 |
R8 |
第五个参数 |
R9 |
第六个参数 |
| 返回值 | 通常在 RAX 中 |
常用的系统调用号 (x86-64 Linux):
1:sys_write(写入文件描述符,如标准输出)0:sys_read(从文件描述符读取)60:sys_exit(终止程序)
函数调用
不过一般来说,我们写代码的时候都是从库中比如include里面调用的输入输出函数,或者我们自己编写的函数都是直接call函数的入口地址.
数据传输指令
1 | |
MOV (Move) 指令是最是最基本的数据传送指令。它的功能是将源操作数 (source operand) 的内容复制到目标操作数 (destination operand)。
语法
1 | |
tips:MOV [mem1], [mem2] ; 错误:不允许内存到内存的直接传送
| 目标 (Destination) | 源 (Source) | 示例 (x86-64) |
|---|---|---|
| 寄存器 | 寄存器 | MOV RAX RBX |
| 寄存器 | 立即数 | MOV RCX, 100 |
| 寄存器 | 内存 | MOV RDX, [myVariable] |
| 内存 | 寄存器 | MOV[myVariable], RAX |
| 内存 | 立即数 | MOV QWORD [myVar], 0FFFFh |
LEA (Load Effective Address) 指令计算源操作数(必须是内存操作数)的有效地址,并将该地址加载到目标操作数(必须是通用寄存器),而不实际访问该内存地址容。语法:LEA destination_register, [memory_address_expression]
- 获取一个内存位置的地址,而不是其内容。
- 执行复杂的算术运算。
LEA可以利用寻址模式中的加法和乘法(比例因子)来进行通用的算术计算,有时比使用一系列ADD和MUL/UL/IMUL指令更高效。
栈操作指令
PUSH source 指令执行以下操作:
将栈指针 (
RSP/ESP/SP) 减去操作数的大小(2字节、4字节或8字节)。将源操作数的内容复制到新的栈顶位置(由更新后的
RSP/ESP/SP指向)。POP destination指令执行以下操作:将当前栈顶位置(由
RSP/ESP/SP指向)的内容复制到目标操作数。将栈指针 (
RSP/ESP/SP) 加上操作数的大小。
控制流指令
call
把下一条指令地址压栈(return address)
跳转到目标地址(RIP 改为 target
把RIP压入栈,push RIP
再把RIP指向要调用的函数RIP = func
retun把rip弹出栈给rip赋上rsp指向的内存
下集预告
不可能差分分析?,也许吧
代码混淆
最近考完试了可以着手出题和准备联队面试了.赶脚有点迷茫,xxl
…………
https://www.bilibili.com/video/BV1Wu1sBpEwu/?spm_id_from=333.337.search-card.all.click
推荐音乐