逆向工程基础:二进制与十六进制基础、CPU架构简介

各位同学,欢迎来到《思科固件逆向分析实战》的第一章。我是你们的老朋友,一个在嵌入式安全领域摸爬滚打了十几年的工程师。今天咱们不聊虚的,直接上手最基础的东西——二进制、十六进制、CPU架构,还有汇编语言。

你可能会问:“这些东西网上随便搜搜都有,为什么还要专门讲?” 嗯,我当年刚入行时也这么想。结果第一次逆向一个思科路由器固件,看到满屏的十六进制数据,整个人都懵了。说白了,基础不牢,地动山摇。咱们今天就把这些地基打扎实。

1. 二进制与十六进制:固件的“细胞”

固件是什么?说白了就是一堆二进制数据。计算机只认0和1,但咱们人类看二进制太费劲了。你想想看,一个32位的地址 10101100111100001111000011110000,眼睛都看花了。所以就有了十六进制,它把4位二进制压缩成1位,方便多了。

核心换算表(建议背下来)

二进制十六进制十进制
000000
000111
001022
001133
010044
010155
011066
011177
100088
100199
1010A10
1011B11
1100C12
1101D13
1110E14
1111F15

我个人习惯在逆向时,把十六进制数据先转成二进制,再按位分析。比如一个寄存器配置值 0x3F,转成二进制就是 0011 1111,低6位全为1。这往往意味着某些功能位被同时使能了。

小技巧: 在Linux终端里,用 printf '%x\n' 255 可以快速把十进制转十六进制。用 echo 'obase=2; ibase=16; FF' | bc 可以把十六进制转二进制。省得手算。

2. CPU架构简介:MIPS、ARM、PowerPC

思科设备里最常见的CPU架构有三种:MIPS、ARM、PowerPC。我最早接触的是MIPS,那时候逆向一台老旧的思科路由器,反汇编出来全是MIPS指令,看得我头皮发麻。

咱们简单过一下这三种架构的特点:

  • MIPS(Microprocessor without Interlocked Pipeline Stages):精简指令集(RISC),指令长度固定为32位。思科很多中低端路由器都用它。特点是延迟槽(Delay Slot),这个坑我踩过——你分析分支指令时,分支后面的那条指令其实先执行
  • ARM(Advanced RISC Machine):也是RISC,但支持Thumb(16位)和Thumb-2(混合)指令集。思科较新的设备开始用ARM,比如一些交换机。ARM的指令条件执行是个亮点,每条指令前面都可以加条件码。
  • PowerPC:由IBM、苹果、摩托罗拉联合开发。思科的高端路由器(如CRS系列)大量使用。PowerPC的指令集比较规整,但寄存器数量多,刚开始容易搞混。

注意: 不同架构的固件,其启动流程、内存布局、中断处理方式完全不同。拿到一个固件,第一步就是识别CPU架构。怎么识别?看入口点代码——MIPS的 luiaddiu 组合很典型,ARM的 LDRSTR 很常见,PowerPC的 lisori 是标志。

我曾经接手过一个固件,怎么都跑不起来。后来发现是字节序搞反了——固件是Big-Endian,但我用Little-Endian模式去解析。嗯,这就引出了下一个重点。

3. 字节序(Endianness)的理解

字节序,说白了就是多字节数据在内存里怎么排列。两种模式:

  • Big-Endian(大端序):高位字节存低地址。比如 0x12345678,在内存里是 12 34 56 78。PowerPC和MIPS默认用大端。
  • Little-Endian(小端序):低位字节存低地址。同样的 0x12345678,在内存里是 78 56 34 12。ARM默认用小端,但可以切换。

你想想看,如果不知道字节序,你看到的十六进制数据完全是反的。我当年逆向一个MIPS固件时,死活找不到正确的函数入口地址。后来发现,固件头里写的地址是Big-Endian,但我用Little-Endian去读,结果地址全错了。浪费了整整两天。

实战判断方法:

用十六进制编辑器打开固件,看前几个字节。如果看到 7F 45 4C 46(ELF魔数),那就是Big-Endian。如果看到 46 4C 45 7F,那就是Little-Endian。当然,更准确的方法是看代码里的 lw(加载字)指令前后的数据排列。

4. 汇编语言入门:看懂固件在干什么

汇编语言是逆向分析的核心工具。你不需要像编译器那样精通,但必须能读懂。咱们以MIPS为例,讲几个最常用的指令:

指令含义示例
lui加载高位立即数lui $t0, 0x8001 → $t0 = 0x80010000
addiu加立即数(无符号)addiu $t0, $t0, 0x1234 → $t0 += 0x1234
lw从内存加载字lw $t1, 0($t0) → $t1 = *($t0)
sw存字到内存sw $t1, 4($t0) → *($t0+4) = $t1
beq相等则分支beq $t0, $t1, label → if($t0==$t1) goto label
jal跳转并链接(调用函数)jal func → 跳转到func,返回地址存$ra

举个例子,你看到这样一段代码:

lui   $t0, 0x8001
addiu $t0, $t0, 0x1234
lw    $t1, 0($t0)
addiu $t1, $t1, 1
sw    $t1, 0($t0)

这其实就是在做:从地址 0x80011234 读一个32位数,加1,再写回去。说白了就是一个内存自增操作。我在逆向思科路由器的配置管理模块时,经常看到这种模式——它们用内存映射I/O来读写硬件寄存器。

我的建议: 刚开始学汇编,别死记硬背指令。先学会看数据流——数据从哪来(加载指令),到哪去(存储指令),中间做了什么运算(算术指令)。抓住这个主线,大部分代码都能看懂。

5. 本章知识体系总览

为了让你更直观地理解本章内容,我画了一张图。它展示了从二进制到汇编的完整知识链条:

逆向工程基础:知识体系 二进制 / 十六进制 CPU架构 字节序 汇编语言入门 固件逆向分析实战 从底层数据到高层分析,环环相扣

这张图把本章的知识点串起来了。二进制和十六进制是基础,CPU架构决定了指令集,字节序影响数据解读,汇编语言是分析工具。最终,这些知识都服务于固件逆向分析。

好了,第一章的内容就到这里。记住,逆向分析没有捷径,但有了扎实的基础,后面的路会越走越顺。下一章咱们会深入固件的文件格式和加载过程,到时候见。


公众号:蓝海资料掘金营,微信deep3321