逆向入门-汇编基础

前几天开始尝试学习逆向,这两天差不多看了汇编基础的只是,尽力摆脱ida只会f5的状态.

逆向入门-汇编基础

基础解释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
;hello.asm
section .data#用于初始化声明数据
message db "hello,world", 0Ah#定义字
msg_len equ $-message#
section .text# 用于存放程序指令
global _start
_start:
mov rax,1
mov rdi,1
mov rsi,message
mov rdx,msg_len
syscall
mov rax,60
mov rdi,0
syscall

这是最基础的输出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
2
3
4
5
6
7
8
9
10
这些是给汇编器 (NASM)的指令,用于控制汇编过程,它们本身不直接翻译成CPU执行的机器码,但会影响最终生成的机器码或目标文件的结构。
示例:
section .data: 告诉NASM接下来的内容属于数据节。
`db`, `dw`, `dd`, `dq`: 定义数据并分配空间。
`resb`, `resw`, `resd`, `resq`: 在BSS节预留空间。
`equ`: 定义常量符号。
`global`: 将符号导出,使其对链接器可见。
`extern`: 声明一个外部符号 (在其他文件中定义,链接时解析)。
`%define`: 定义宏 (类似于C中的`#define`)。
`%include`: 包含另一个文件的内容。

在我理解看来指令是对内存或者数据的直接操作
伪指令更偏向于定义.

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
2
3
4
5
6
7
8
9
10
section .data
myByte db 0x41 ; 定义一个字节,初始值为十六进制 41 ('A')
myWord dw 12345 ; 定义一个字,初始值为十进制 12345
myDouble dd 3.14159 ; NASM允许用浮点数初始化DD (会被转换为32位IEEE浮点表示)
; (注意:这只是初始化,不是声明浮点类型,实际处理需FPU/SSE指令)
myQuad dq 0x123456789ABCDEF0 ; 定义一个64位整数
message db "Hello, World!", 0 ; 定义一个字符串,以空字符(0)结尾 (C风格字符串)
messageLen equ $-message ; 'equ' 定义一个常量符号,'$'表示当前地址
; '$-message' 计算从message标签开始到当前位置的长度
array db 10, 20, 30, 40, 50 ; 定义一个字节数组

这里的message是一个字符串以0结尾和C语言的字符串一样.

bss段

.bss 节用于声明那些在程序启动时不需要特定初始值(通常默认为0)的变量。这有助于减小可执行文件在磁盘上的大小。

NASM提供了以下伪指令在 .bss 节中预留空间:

  • RESB (Reserve Byte): 预留指定数量的字节。
  • RES (Reserve Word): 预留指定数量的字。
  • RESD (Reserve Double Word): 预留指定数量的双字。
  • RESQ (Reserve Quad Word): 预留指定数量的四字。
    示例:
1
2
3
4
section .bss
buffer resb 256 ; 预留256字节的空间,标签为 buffer
userAge resw 1 ; 预留1个字 (2字节) 的空间,标签为 userAge
matrix resd 100 ; 预留100个双字 (400字节) 的空间,标签为 matrix

程序在加载是为预留这块分配的内存空间并清零

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
2
3
mov rax, rbx
mov rax, [rbp-8]
mov [rsp], rax

MOV (Move) 指令是最是最基本的数据传送指令。它的功能是将源操作数 (source operand) 的内容复制到目标操作数 (destination operand)。
语法

1
MOV destination, source

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]

  1. 获取一个内存位置的地址,而不是其内容。
  2. 执行复杂的算术运算。LEA 可以利用寻址模式中的加法和乘法(比例因子)来进行通用的算术计算,有时比使用一系列 ADDMUL/UL/IMUL 指令更高效。

栈操作指令

PUSH source 指令执行以下操作:

  1. 将栈指针 (RSP/ESP/SP) 减去操作数的大小(2字节、4字节或8字节)。

  2. 将源操作数的内容复制到新的栈顶位置(由更新后的 RSP/ESP/SP 指向)。
    POP destination 指令执行以下操作:

  3. 将当前栈顶位置(由 RSP/ESP/SP 指向)的内容复制到目标操作数。

  4. 将栈指针 (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
推荐音乐


逆向入门-汇编基础
http://example.com/2025/12/19/逆向入门-汇编基础/
Beitragsautor
fox
Veröffentlicht am
December 19, 2025
Urheberrechtshinweis