2 个版本

0.1.1 2021年11月3日
0.1.0 2021年10月29日

#13 in #反汇编

Zlib 许可证

485KB
6.5K SLoC

Spore - UEFI 字节码反汇编器

🍄 UEFI 字节码虚拟机的反汇编器。关于虚拟机的详细信息,请参阅 UEFI 规范的第22节

安装

$ cargo install spore-disassembler

# Or alternatively
$ cargo install --git https://github.com/Pebaz/spore

演示

给定以下 FASMG-EBC UEFI 字节码汇编文件

;; Adapted from https://github.com/pbatard/fasmg-ebc/blob/master/hello.asm

include 'ebc.inc'
include 'efi.inc'
include 'format.inc'
include 'utf8.inc'

format peebc efi  ;; PE executable format, EFI Byte Code

entry efi_main

section '.text' code executable readable

efi_main:
    MOVn   R1, @R0(EFI_MAIN_PARAMETERS.SystemTable)
    MOVn   R1, @R1(EFI_SYSTEM_TABLE.ConOut)
    MOVREL R2, string_hello
    PUSHn  R2
    PUSHn  R1
    CALLEX @R1(SIMPLE_TEXT_OUTPUT_INTERFACE.OutputString)
    MOV R0, R0(+2,0)
    JMP efi_main
    RET

section '.data' data readable writeable
    string_hello: du "Hello World!", 0x0A, 0x0

使用 FASMG-EBC 编译它,通过克隆项目并将文件放入项目根目录。

将其保存为熟悉的名字,例如 "bc.asm" 等。

# Generates `bc.efi`
$ make bc.asm

# This is a PE executable that contains UEFI Bytecode
$ file bc.efi
bc.efi: PE32+ executable (DLL) (EFI application) EFI byte code, for MS Windows

现在我们有了可引导的 PE 可执行文件,我们可以输出其中的字节码指令

$ spore bc.efi

然后,Spore 将输出反汇编的字节码指令

      72 81 41 10  MOVnw R1, @R0(+1, +16)
      72 91 85 21  MOVnw R1, @R1(+5, +24)
      79 02 F4 0F  MOVRELw R2, 4084
            35 02  PUSHn R2
            35 01  PUSHn R1
83 29 01 00 00 10  CALL32EXa @R1(+1, +0)
      60 00 02 10  MOVqw R0, R0(+2, +0)
            02 F2  JMP8 -14
               04  RET

用法

为什么

我想学习制作操作系统,因为这很有趣。这实际上相当困难,但回报相当令人满意。

在学习操作系统时,我发现了统一的可扩展固件接口(UEFI)。它基本上是一组 C ABI 接口,允许您编写预操作系统恢复软件、操作系统安装程序或引导管理器。

UEFI 虚拟机实际上是默认预装在现代大多数主板上的。大多数时候,您用 C 编写遵循 UEFI API 的应用程序,然后可以直接引导到它们。令人惊讶的是,您还可以直接引导到用 UEFI 字节码(EBC)编写的应用程序!

这之所以令人惊讶,是因为 EBC 是跨平台的,因此在这一点上与 Java 字节码类似。

更令人惊讶的是,EBC 可以访问与正常 UEFI 应用程序相同的引导和运行时服务。 这意味着您可以使用 EBC 编写引导加载程序。

在我发现这一点后,我开始学习 UEFI 字节码,但并没有找到很多关于它的信息。幸运的是,Pete Batard 构建了 FASMG-EBC,这是 EBC 的汇编器。

我想更好地理解汇编器的输出,为此,我需要更多地与之合作。那时,我决定构建 Spore。 😄

我是如何做到的?

我首先仔细阅读了UEFI规范,发现所有我需要的信息都包含在第22节。从那里,我决定使用Rust来构建反汇编器,因为它是目前我最喜欢的编程语言,原因有很多。

然后,我创建了一个Python脚本,可以生成一些特定的字节码序列,而无需处理PE文件格式。这是整个项目中最重要的步骤之一,因为我学会了如何解析几个指令的二进制结构。

一旦我有了可以工作的字节,我就编写了一些Rust代码来打开文件并遍历字节。令人惊讶的是,整个应用程序通过传递单个迭代器到所有解析指令的函数来运行!所有的解析函数只是推进迭代器并返回任何错误。

这使整个过程变得更简单,因为我只需要查看第一个字节来确定指令的操作码,并将其路由到相应的解析函数。由于许多指令的字节长度不同,迭代器方法工作得非常好。

在我写了很多之后,我发现许多指令都使用了相同的解析例程。我还注意到我一次只实现一条指令,总共有55条指令,所以这不会很好用。然后我不得不退后一步,看看什么可以重用,什么必须保留。然后我阅读了规范中的所有55条指令,并根据它们是如何解析的将每个指令类型分组。完成这些后,我只需要为每种类型编写一个解析函数(总共7个)!

到这个时候,我知道自己要做什么了,并决定需要更好的单元测试来确保在重构过程中没有出错。我一直在逐渐向Python脚本添加增量,以输出每个指令以及每个参数组合,但我知道我需要以某种方式验证我没有破坏任何东西。

字节码生成脚本的模样如下

bc = open('bc.bin')

bc.write(0b00101010_00000001.to_bytes(2, 'big'))  # $ STORESP R1, FLAGS

# The rest of the instructions ...
# This totaled 1162 lines of code

我又写了一个Python脚本,它会从字节码生成脚本中提取所有汇编指令并写入文件。

它是通过查找包含美元符号$的所有注释并将它们写入文件来工作的。结果如下所示

STORESP R1, FLAGS

;; The rest of the instructions

现在,我有了期望从反汇编器中得到的输出,我可以简单地将其与该文件进行比较,以确定是否有任何回归!

有了这个,我可以实现剩余的指令解析器并随意修改现有的解析器,而不用担心会破坏任何东西。

实现剩余的解析器花了一些时间,但完成之后,我需要将Python测试脚本转换为Rust单元测试。

同样,我开始逐个手动转换它们,但很快这变得有些荒谬。

我又写了一个Python脚本,它会解析字节码生成脚本中的bc.write(...)指令,并将它们转换为Rust单元测试。这非常令人满意!🙃

一旦有了工作的Rust单元测试,我就着手改进CLI和修复一些杂项待办事项。其余的就像你在这个README中看到的那样!

总的来说,这个项目花了我20天的时间来完成。

在我为Spore工作期间,我注意到的一些事情

  • Python对于快速原型设计至关重要。我将继续记住Rust和Python如何在未来项目中完美配合。我使用了Python来
    • 生成字节码
    • 验证反汇编器输出
    • 将Python测试转换为Rust单元测试
  • Rust的使用非常棒,因为它没有强迫我考虑代码结构。它只是函数 + 结构体 + 1 个特质 😛
    • 还有一件事也令人印象深刻,那就是代码编译后,几乎很少出错。Rust的编译器是世界一流的。它还使重构变得无所畏惧,因为我知道它会捕获所有东西。
  • Rust生态系统非常棒。我需要一个PE文件加载器,所以我使用了pelite crate,这真是太容易了。
  • colored crate让输出彩色文本变得非常有趣!
  • Rust没有在栈上分配的类似Vec的数据结构。我知道这可能是一个奇怪的要求,但我不喜欢总是分配堆上的所有东西。arrayvec crate允许我有一个具有Vec-like界面的固定大小缓冲区。
  • 我使用了include_str!()来处理CLI使用信息,这竟然让人感到很满足! 😆
  • 字符串。让我们谈谈字符串。我的意思是,为什么我不能轻松地将它们堆栈分配?我本质上想要一个堆栈分配的字符串构建器,以便我甚至不需要进行一次分配,但实现这一点可能需要花费很长时间。 😕

随着时间的推移,星星的数量

Stargazers over time

备注

  • 🍄Spore的名字来源于蘑菇孢子。
  • 👏感谢Pete Batard创建了基于Flat Assembler的FASMG-EBC,没有这个工具,我就不会有反汇编的汇编文件!
  • 🤯《UEFI规范》写得非常好,包含了实现Spore所需的所有信息。
  • 🤷‍♂️尽管Spore是跨平台的(Windows、MacOS、Linux),但我还没有测试FASMG-EBC是否在其他平台上工作。

依赖关系

~0.7–11MB
~67K SLoC