6、汇编语言入门(x86/x64):寄存器详解与常用指令
好,咱们今天聊点硬核的。汇编语言,说白了就是CPU能直接听懂的话。你写C++、Python,最终都得翻译成汇编才能跑。做逆向工程,不懂汇编?那就像开车不看仪表盘——能走,但迟早要出事。
我个人习惯,学汇编先别急着背指令,先把“寄存器”搞明白。寄存器是什么?就是CPU内部的小型存储单元,速度比内存快几个数量级。你操作数据,得先把它搬到寄存器里,才能折腾。
6.1 通用寄存器:EAX、EBX、ECX、EDX
这四个是x86架构里的“老四样”。每个都有32位宽,在x64下扩展成了RAX、RBX、RCX、RDX,但低32位用法完全兼容。
| 寄存器 | 常用用途 | 我的经验 |
|---|---|---|
| EAX | 累加器,函数返回值通常放这里 | 逆向时看到EAX被赋值,八成是函数要返回了 |
| EBX | 基址寄存器,常用于数组基址 | 我遇到过EBX被当作全局指针用,改它游戏就崩了 |
| ECX | 计数器,循环次数、字符串操作常用 | 调试循环时盯着ECX看,比看代码快 |
| EDX | 数据寄存器,I/O操作、乘法结果高位 | 做除法时EDX存余数,这个坑我踩过好几次 |
6.2 栈指针与基址指针:ESP、EBP
这两个寄存器管着“栈”——函数调用的核心数据结构。ESP指向栈顶,EBP指向栈底。你想想看,函数调用时参数怎么传?局部变量怎么存?全靠这俩指针。
我记得有一次逆向一个加壳程序,它把ESP的值改了,导致栈帧全乱套。我当时盯着OD看了半小时才反应过来——嗯,这是反调试的经典手法。
- ESP:栈顶指针,push/pop操作直接影响它
- EBP:栈底指针,用于访问函数参数和局部变量
- 常见模式:函数开头通常有
push ebp; mov ebp, esp
6.3 指令指针:EIP
EIP存着下一条要执行的指令地址。你不能直接改它,但可以通过jmp、call、ret来间接控制。说白了,程序怎么跑,全看EIP指向哪。
我曾经在调试一个外挂时,发现它把EIP改到了一个无效地址,结果游戏直接崩溃。这种手法在反调试里很常见——故意让EIP飞掉,让调试器跟丢。
6.4 常用指令详解
MOV:数据搬运工
MOV是最基础的指令,没有之一。它的作用就是把数据从一个地方搬到另一个地方。注意,不能直接从内存搬到内存,得经过寄存器中转。
MOV EAX, 12345678h ; 把立即数放到EAX
MOV EBX, [EAX] ; 把EAX指向的内存数据读到EBX
MOV [ECX], EDX ; 把EDX的值写到ECX指向的内存
JMP:无条件跳转
JMP就是“跳到哪去”。它直接修改EIP的值。逆向时看到JMP,通常意味着代码逻辑的分支或循环。
JMP 00401000h ; 直接跳到地址00401000
JMP EAX ; 跳到EAX里存的地址
CALL:函数调用
CALL和JMP很像,但它多了一步:先把返回地址(当前EIP的下一条)压栈,再跳转。这样函数执行完,RET指令就能从栈里弹出地址,回到调用点继续跑。
CALL 00402000h ; 调用地址00402000处的函数
; 函数执行完后,RET会回到这里
我个人习惯,逆向时看到CALL,先别急着跟进去。看看它压栈的参数是什么,很多时候光看参数就能猜出函数功能。
CMP:比较指令
CMP本质上是做减法,但不保存结果,只影响标志寄存器。后面通常跟着条件跳转指令,比如JE、JNE、JG等。
CMP EAX, 100 ; 比较EAX和100
JE label ; 如果相等,跳到label
ADD/SUB:加减法
这两个指令太基础了,但要注意:它们会影响标志寄存器。特别是溢出标志OF和进位标志CF,做多字节运算时经常用到。
ADD EAX, 10 ; EAX = EAX + 10
SUB EBX, ECX ; EBX = EBX - ECX
6.5 标志寄存器:EFLAGS
标志寄存器是个“状态位集合”,每个位代表一个状态。你不需要记住所有位,但下面这几个必须烂熟于心:
| 标志位 | 名称 | 什么时候置1 |
|---|---|---|
| ZF | 零标志 | 运算结果为0时 |
| CF | 进位标志 | 无符号运算溢出时 |
| OF | 溢出标志 | 有符号运算溢出时 |
| SF | 符号标志 | 结果为负数时 |
6.6 知识体系总览
下面这张图,把本章的核心知识点串起来了。你保存下来,以后忘了随时看。
这张图你看明白了吗?寄存器是CPU的“工作台”,指令是“工具”,标志寄存器是“状态灯”。三者配合,才能让程序跑起来。逆向时,你盯着这几个东西看,就能猜出程序在干什么。
公众号:蓝海资料掘金营,微信deep3321