西电CTF 2026 Assembly
发布于 2026-09-23 07:29:47
1.学习汇编语言(到能看懂教程AI写的)
1.1 通用寄存器(64位 / 32位对应关系)
-
rax/eax:累加器,最重要的寄存器。函数执行完,返回值放在这里;算术运算结果也常放这里。 -
rbx/ebx:基址寄存器,偶尔用来存局部变量,但不多见。 -
rcx/ecx:计数器。循环时经常用来倒数,或者记录循环次数。 -
rdx/edx:数据寄存器。乘除法时会用到,或者作第三个参数。 -
rdi/edi:Destination Index,目的索引。在函数传参时通常是第一个参数。 -
rsi / esi:Source Index,源索引。在函数传参时通常是第二个参数。 -
rbp/ebp:栈底指针。用来标记当前函数的“地基”,局部变量都靠它定位。 -
rsp/esp:栈顶指针。始终指向栈的最顶端。 -
r8~r15:第5个到第9个参数,或者随便用来存数据。
1.2 函数调用与参数传递(Linux x64)
-
参数1:
rdi -
参数2:
rsi -
参数3:
rdx -
参数4:
rcx -
参数5:
r8 -
参数6:
r9 -
返回值:
rax*(Windows x64 则是 *rcx, rdx, r8, r9
第二章:寻址与数据搬运(怎么找东西)
**2.1 核心符号 **[]
汇编里,[] 就是**“去这个地址里拿东西”**的意思。
-
mov rax, rbx:把rbx的值复制给rax。(不带括号,是传值) -
mov rax, [rbx]:以rbx的值作为地址,去那个地址里把数据取出来给rax。(带括号,是去地址拿) -
mov [rbx], rax:把rax的值写入以rbx为地址的内存里。
2.2 数据宽度限定符
内存不会自动告诉你它有多大,所以你经常看到:
-
byte ptr [rsi]:只处理 1 个字节(8位,常用于处理 char 字符串)。 -
word ptr [rsi]:处理 2 个字节(16位)。 -
dword ptr [rsi]:处理 4 个字节(32位,常用于 int)。 -
qword ptr [rsi]:处理 8 个字节(64位,常用于指针)。
2.3 高频搬运指令
-
mov dest, src:赋值。 -
lea dest, [addr]:Load Effective Address,极其重要! 它虽然带着[],但不取数据,而是直接把addr这个地址(或者算式的结果)赋给dest。例如lea rdi, [buf]就是把buf的地址给rdi。 -
push src/pop dest:把数据压入栈顶 / 从栈顶弹出数据(栈是向下生长的,rsp会随之减小/增大)。 -
movzx dest, src/movsx dest, src:零扩展/符号扩展搬运(比如从 1 个字节扩展到 4 个字节,常常用于从字符串取字符并转成 int)。
第三章:算术与逻辑运算(怎么算)
-
add dest, src:dest = dest + src -
sub dest, src:dest = dest - src -
inc dest/dec dest:dest++/dest-- -
xor dest, src:异或。逆向高频考点,常用于简单加密/解密(A ^ B = C,则C ^ B = A)。 -
and dest, src/or dest, src:按位与/或。 -
shl dest, n/shr dest, n:左移/右移 n 位。左移相当于乘2^n,右移相当于除以2^n。 -
imul/idiv:有符号乘除法。(除法结果中,rax存商,rdx存余数)。 -
特殊技巧:
xor eax, eax等价于eax = 0。编译器觉得这样生成代码更短,所以经常这么写。
第四章:控制流(怎么拐弯)
汇编的执行默认从上到下,如果遇到跳转指令,就会改变流程。
4.1 比较与测试
-
cmp a, b:比较 a 和 b。底层做a - b,但不保存结果,只影响标志位(比如 Zero Flag 等)。 -
test a, b:逻辑与。底层做a & b,不保存结果,只影响标志位。test eax, eax常用于判断eax是否为 0(如果为 0,Zero Flag 会被置位)。
4.2 跳转指令(翻译成 C 语言)
-
jmp label:无条件跳转,等于 C 语言的goto label;。 -
je label(Jump if Equal) /jz label(Jump if Zero):相等(或结果为0)就跳。通常跟在cmp或test后。翻译:if (a == b) goto label; -
jne label(Jump if Not Equal) /jnz label:不相等就跳。翻译:if (a != b) goto label; -
jg label(Jump if Greater) /jge:有符号比较,大于/大于等于就跳。翻译:if (a > b) goto label; -
jl label(Jump if Less) /jle:有符号比较,小于/小于等于就跳。 -
ja label(Jump if Above) /jb:无符号比较。 -
jae/jbe:无符号大于等于/小于等于。
4.3 循环结构的识别
汇编里没有直接的 for,通常是以下面这种形式出现:
asm
mov ecx, 5 ; 初始化计数器
loop_start:
; ... 循环体内容 ...
dec ecx ; 计数器减 1
cmp ecx, 0 ; 比较是否为 0
jne loop_start ; 不等于 0 就跳回去继续循环
这就是一个简单的 for (int i = 5; i != 0; i--) 结构。
如果往回跳(比如跳转地址小于当前地址),基本就是循环。
第五章:函数、栈帧与调用(模块化阅读)
看懂函数,是阅读汇编的关键一步。每个函数都有它的“规矩”。
5.1 函数的标准开头(序言)
asm
push rbp ; 保存上一个函数的栈底
mov rbp, rsp ; 建立当前函数的栈底
sub rsp, 0x20 ; 开辟 0x20 字节的栈空间,给局部变量用
-
此时,局部变量通常是
[rbp-4]、[rbp-8]、[rbp-0x10]。 -
传入的参数(如果是通过栈传的)通常是
[rbp+8]、[rbp+0x10]。
5.2 函数的标准结尾(尾声)
asm
leave ; 等价于 mov rsp, rbp; pop rbp; 恢复之前的栈
ret ; 弹出返回地址,跳回调用处
5.3 函数调用
-
call func:调用函数。相当于push 返回地址; jmp func。 -
如果遇到
call printf、call strcmp、call scanf,直接去查这些 C 标准库函数的作用,就能猜出代码在干嘛。
第六章:逆向实战中的“汇编翻译套路”
拿到汇编不要从头读到尾,找模式:
-
字符串遍历模式: 看到
movzx eax, byte ptr [rsi](取一个字符) -> 后面跟着add/xor/sub等运算 -> 再mov [rdi], al(写回)。这就是一个遍历字符串进行运算的循环。 -
判断正确/错误模式: 看到连续的
cmp后跟jne 失败分支,大概率是逐字节比对 Flag。 -
数学运算跳转模式: 像你上一题一样,先算几道题,如果算对了就去解密输出真 Flag,算错了就去输出假 Flag(防爆破/防静态分析)。
-
取地址与赋值模式:
lea rdi, [buf](把 buf 地址给 rdi) ->call某个函数。大概率是把 buf 作为输出缓冲区传给函数。