3. 8051指令集基础:数据传送、算术运算、逻辑运算与控制转移

指令集,说白了就是单片机的“母语”。你要逆向分析固件,就得先听懂它在说什么。8051的指令集不算复杂,总共111条指令,但真正高频出现的,也就四类:数据传送、算术运算、逻辑运算、控制转移。我个人习惯把这四类称为“固件逆向的四块基石”。

今天我们就把这四块基石一块一块掰开来看。别急,我会结合项目里踩过的坑来讲,保证你听完就能用。

3.1 数据传送指令——固件里的“搬运工”

数据传送指令是8051里出现频率最高的指令,没有之一。说白了,就是把数据从一个地方搬到另一个地方。搬来搬去,控制逻辑就出来了。

3.1.1 核心指令速览

指令 功能 字节数 周期
MOV A, #data 立即数送累加器 2 1
MOV A, Rn 寄存器送累加器 1 1
MOV Rn, A 累加器送寄存器 1 1
MOV direct, A 累加器送直接地址 2 1
MOV A, @Ri 间接寻址送累加器 1 2
MOVX A, @DPTR 外部RAM读入累加器 1 2
MOVC A, @A+DPTR 查表读入累加器 1 2
PUSH direct 压栈 2 2
POP direct 出栈 2 2
我的经验:逆向时看到 MOV A, #data 后面紧跟着 MOV direct, A,基本就是在初始化某个寄存器或端口。我曾经逆向一个空调控制板,发现它用这种方式连续写了20多个字节——嗯,那是在初始化LCD的显示缓冲区。

3.1.2 寻址方式与反汇编中的识别

8051有7种寻址方式,但反汇编时你只需要重点关注三种:

  • 立即寻址:操作数直接写在指令里,比如 MOV A, #0x55。反汇编里一眼就能看出来,数值是固定的。
  • 直接寻址:操作数是地址,比如 MOV A, 0x90。0x90是P1口的地址,看到这个你就知道它在读端口。
  • 间接寻址:通过R0或R1指向地址,比如 MOV A, @R0。这种常用于循环操作,比如内存拷贝。
避坑指南:我曾经在分析一个加密芯片的固件时,看到大量 MOVX A, @DPTR 指令,以为它在频繁读写外部RAM。后来才发现,它其实是在访问一个映射到外部地址空间的硬件寄存器。如果你不查硬件手册,很容易误判。

3.2 算术运算指令——加减乘除里的玄机

8051的算术运算指令其实挺“朴素”的,只有加、减、乘、除,而且乘除只能用在累加器A和寄存器B之间。但你别小看这几条指令,控制算法、校验计算、甚至加密解密都靠它们。

3.2.1 核心指令速览

指令 功能 影响标志位
ADD A, Rn A + Rn → A C, AC, OV
ADDC A, Rn A + Rn + CY → A C, AC, OV
SUBB A, Rn A - Rn - CY → A C, AC, OV
INC Rn Rn + 1 → Rn 无
DEC Rn Rn - 1 → Rn 无
MUL AB A × B → B:A OV, CY=0
DIV AB A ÷ B → A余B OV, CY=0
DA A 十进制调整 C

3.2.2 反汇编中的算术逻辑还原

你想想看,在反汇编里看到 ADD A, #0x01 和 INC A,效果是一样的,但编译器选哪个?我观察过,Keil C51编译器倾向于用 INC A,因为它只占1个字节。而手工汇编的固件,经常混用。

还有一个细节:DA A 指令。这玩意儿是BCD码调整用的。我在分析一个电表固件时,看到大量 ADD 后面紧跟 DA A,就知道它在做十进制运算。这种模式在计量类设备里非常常见。

关键点:算术运算指令一定会影响程序状态字(PSW)的标志位。逆向时,如果你看到 ADD 或 SUBB 后面跟着 JC 或 JNC,那基本就是在做条件判断。这是还原控制逻辑的重要线索。

3.3 逻辑运算指令——位操作的利器

8051的逻辑运算指令,我个人觉得是它最强大的地方。尤其是位操作能力,很多现代32位单片机都比不了。你想想看,一个8位单片机,能直接操作每一位,这在控制领域简直是神器。

3.3.1 核心指令速览

指令 功能 典型用途
ANL A, Rn A & Rn → A 位屏蔽
ORL A, Rn A | Rn → A 位置位
XRL A, Rn A ^ Rn → A 位翻转、加密
CLR A 0 → A 清零
CPL A ~A → A 取反
RL A A循环左移 乘2
RR A A循环右移 除2
SWAP A A高低半字节交换 字节拆分

3.3.2 位操作指令——8051的独门绝技

8051有一个专门的位寻址区(20h~2Fh),共128个可位寻址的位。对应的指令有:

  • SETB bit:置位
  • CLR bit:清零
  • CPL bit:取反
  • JB bit, rel:位为1则跳转
  • JNB bit, rel:位为0则跳转
  • JBC bit, rel:位为1则跳转并清零
我的习惯:逆向时看到 JB P1.0, LABEL,我第一反应就是去找P1.0接了什么硬件。可能是按键,可能是传感器,也可能是中断输入。我曾经分析一个遥控器固件,发现它用 JBC 指令来检测按键并自动清除标志位——这个细节帮我快速定位了按键扫描逻辑。

3.4 控制转移指令——程序的“方向盘”

控制转移指令决定了程序的流向。没有它们,程序就只能一条路走到黑。逆向分析时,控制转移指令是你还原程序流程图的关键。

3.4.1 无条件转移 vs 条件转移

类型 指令 跳转范围 字节数
无条件 LJMP addr16 64KB 3
无条件 AJMP addr11 2KB 2
无条件 SJMP rel -128~+127 2
无条件 JMP @A+DPTR 64KB 1
条件 JZ rel -128~+127 2
条件 JNZ rel -128~+127 2
条件 JC rel -128~+127 2
条件 JNC rel -128~+127 2
条件 CJNE A, #data, rel -128~+127 3
条件 DJNZ Rn, rel -128~+127 2

3.4.2 调用与返回指令

子程序调用是结构化编程的基础。8051提供了 LCALL(3字节,64KB范围)和 ACALL(2字节,2KB范围)两种调用指令。返回指令只有 RET 和 RETI(中断返回)。

避坑指南:我曾经在分析一个固件时,发现一个子程序用 RET 返回,但调用它的地方却是一个中断服务程序。这会导致中断系统混乱。后来发现,这是编译器优化后的结果——它把中断服务程序里的公共代码提取成了子程序。嗯,逆向时不能只看指令,还得结合上下文。

3.5 知识体系总览

下面这张图,是我自己总结的8051指令集知识体系。每次做逆向分析前,我都会在脑子里过一遍这个框架。

8051指令集知识体系 8051指令集 数据传送 MOV / MOVX / MOVC PUSH / POP / XCH 算术运算 ADD / ADDC / SUBB INC / DEC / MUL / DIV 逻辑运算 ANL / ORL / XRL CLR / CPL / RL / RR 控制转移 LJMP / AJMP / SJMP JZ / JC / CJNE / DJNZ 固件逆向分析中的应用 识别数据流向 还原算法逻辑 重构控制流程 定位关键数据 / 破解加密算法 / 还原状态机

3.6 实战要点总结

好了,四类指令都讲完了。最后给你几个实战中一定会用到的要点:

  1. 数据传送指令看流向:谁传给谁,决定了数据从哪里来到哪里去。逆向时重点关注 MOVX 和 MOVC,它们往往涉及外部设备或查表数据。
  2. 算术运算指令看标志位:ADD、SUBB 后面的条件跳转,就是控制逻辑的分水岭。我一般会把这些跳转目标地址记下来,画成流程图。
  3. 逻辑运算指令看模式:ANL 用于屏蔽,ORL 用于置位,XRL 用于翻转。看到 XRL 频繁出现,十有八九是在做加密或校验。
  4. 控制转移指令看结构:LCALL 和 RET 成对出现,就是子程序。看到 DJNZ 就是循环。看到 JMP @A+DPTR 就是跳转表,常用于状态机或菜单切换。
一句话总结:指令集是固件的“词汇”,控制逻辑是固件的“语法”。先把词汇搞懂,语法自然就通了。