打开网易新闻 查看精彩图片

4.研究Xtensa架构特性
打开网易新闻 查看精彩图片
4.研究Xtensa架构特性

现在我们已经将所有段加载到适当的地址,我们可以开始逆向工程了。

但为了高效地做到这一点,我们需要更多地了解 Xtensa 架构,包括:

1.指令中的参数顺序

2.条件跳转的执行细节

3.编译器调用约定

4.堆栈组织

首先要探索的是指令中的参数顺序。例如:MOV R1, R2. 您可以在所有架构中找到此类指令,但这可能意味着将 R1 复制到 R2 或将 R2 复制到 R1。因此,了解指令中源代码的位置以及目标寄存器的位置至关重要。您可以在 GitHub 上找到Xtensa 指令集描述。

至于该MOV指令,在Xtensa中,表示将R2复制到R1。因此,第一个参数将是大多数简单指令(例如数学相关指令)中的目的地。例如,指令addi a14, a1, 0x38意味着 a14 = a1 + 0x38。

但对于存储数据的指令,情况则相反。例如,该指令s32i.n a5, a1, 0x10意味着 的值a5必须存储在地址 处(a1 + 0x10)。

要学习的第二件事是条件跳转的完成方式。有两种方法可以做到这一点:

1.使用专用指令进行比较操作,设置标志寄存器,然后进行条件跳转。

2.使用一条指令一次性执行所有这些操作。

Xtensa 执行后者:beqz a10, loc_400E1C54

使用单个指令来检查是否a10等于零,然后它要么跳转到loc_400E1C54,要么不跳转。

第三步是检查编译器使用的调用约定:将参数传递给函数的方式以及如何返回值。

Xtensa 以一种非常不寻常的方式传递参数。参数在调用指令之前放入寄存器中。但它们在函数中出现的寄存器与调用之前所在的寄存器不同:

打开网易新闻 查看精彩图片

以下是如何在汇编程序级别将参数传递给函数的示例:

打开网易新闻 查看精彩图片

这里我们有三个论据:

a10 是目的地址

a11 是源地址

a12 是要复印的尺寸

然而,一旦代码进入memcpy函数,这些值就会自动分别传输到a2、a3和a4寄存器中。

同样的技巧也用于返回值。在memcpy函数内部,该值存储在寄存器中a2,但从函数返回后,该值出现在a10.

返回的样子如下0:

这就是检查返回值的样子:

benz.na10在从调用返回时检查寄存器的值。

最后,有必要了解堆栈是如何组织的。

Xtensa 使用 a1 寄存器来创建堆栈帧。每个函数都以入口指令开始:entry a1,0xC0,其中0xC0是堆栈帧的大小,即函数需要用于堆栈变量的堆栈量。

通常,这些函数从初始化堆栈变量开始:

打开网易新闻 查看精彩图片

寄存器中的零值a5被写入基于a1寄存器的堆栈变量中。

在获得有关 Xtensa 架构的所有必要知识后,我们终于可以开始逆向其代码了。

5. 在 IDA 中对 Xtensa 代码进行逆向工程
打开网易新闻 查看精彩图片
5. 在 IDA 中对 Xtensa 代码进行逆向工程

与 ARM、MIPS 和 PowerPC 相比,Xtensa 不是最流行的架构,并且没有完整的功能列表。因此,IDA处理器模块会存在一些我们需要克服的限制。

IDA 中 Xtensa 处理器模块的主要限制是:

函数参数没有自动注释

堆栈帧不会自动创建

一些 ESP32 函数属于 IROM,因此存在对硬编码地址的调用

部分Xtensa指令未反汇编

让我们讨论一些克服这些挑战的技巧。

5.1. 函数参数的类型系统和注释

从 IDA 7.7 开始提供 Xtensa类型系统。在 IDA 中拥有可用的类型系统非常重要,因为它使逆向变得方便。特别是,它允许您导入 C 结构的定义并指定 IDA 使用的函数原型,以便在传输函数参数的指令附近放置自动注释。

但是,如果您没有类型系统,还有一个解决方法。

首先,让我们看看有类型系统时函数是什么样子的:

打开网易新闻 查看精彩图片

屏幕截图 13. 当有可用的类型系统时函数的外观

函数原型设置有参数的名称和类型,以便 IDA 可以使用此信息在调用站点注释参数:

打开网易新闻 查看精彩图片

屏幕截图 14. 函数原型

但 Xtensa 不会有这样的事情。另一种方法是使用 IDA 中的可重复注释功能。如果您在函数的开头设置可重复的注释,它将显示在所有调用站点上。

打开网易新闻 查看精彩图片

屏幕截图 15. 设置可重复注释

打开网易新闻 查看精彩图片

屏幕截图 16. 可重复的注释显示在所有调用站点上

因此,我们可以使用此功能来定义函数参数:

打开网易新闻 查看精彩图片

屏幕截图 17. 使用可重复注释功能定义函数参数

调用站点将如下所示:

打开网易新闻 查看精彩图片

屏幕截图 18. 调用站点

您可以在注释中选择寄存器名称,IDA 会在代码中突出显示它。因此,您可以轻松找到参数值。

5.2. 恢复堆栈帧

要恢复堆栈帧,您需要手动指定堆栈大小,然后通过在每个与堆栈一起使用的指令处按K 键来显示 IDA 的使用位置。

让我们探索一下config_router_safe函数,例如:

打开网易新闻 查看精彩图片

屏幕截图 19. config_router_safe 函数

很明显这里的栈帧大小是 0xC0。我们在函数的堆栈设置中使用该值(Alt+P):

打开网易新闻 查看精彩图片

屏幕截图 20. 使用 0xC0 值(堆栈帧大小)

从视觉上看,什么也不会发生,但是如果您通过按 Ctrl+K 转到该函数的堆栈帧,您会注意到堆栈空间现在已分配:

打开网易新闻 查看精彩图片

屏幕截图 21. 分配堆栈空间

接下来要做的是使用entry指令指定堆栈移位。在此之前,我们建议启用堆栈指针可视化,如下面的屏幕截图所示:

打开网易新闻 查看精彩图片

屏幕截图 22. 启用堆栈指针可视化

现在,代码应该如下所示:

打开网易新闻 查看精彩图片

屏幕截图 23.启用堆栈指针可视化后的代码

000是当前堆栈指针移位值,我们需要将其移位0xC0。为此,请将光标置于入口指令处,然后按Alt+K以查看以下窗口,您可以在其中指定新旧堆栈指针之间所需的差异:

打开网易新闻 查看精彩图片

屏幕截图 24. 将当前堆栈指针值移动 0xC0

作为此操作的结果,代码将如下所示:

打开网易新闻 查看精彩图片

屏幕截图 25. 移动当前堆栈指针移位值后的代码

现在,如果您开始在与寄存器一起使用的每条指令处按Ka1,IDA 将创建堆栈变量:

打开网易新闻 查看精彩图片

屏幕截图 26.IDA 创建新的堆栈变量

还可以编写 IDA 脚本来自动执行这些操作。

5.3. 调用 IROM

调用位于 CPU 的 IROM 部分而不是固件中的某些低级 API 的情况并不少见。在这种情况下,固件仅与包含定义的 IROM 函数地址的特殊链接器定义文件链接。

在逆向期间,IROM 函数调用如下所示:

屏幕截图 27. IROM 函数调用

40058E4C是 IROM 内的地址。但不可能知道固件调用了哪个函数。因此有必要检查 ESP32 工具链以查找链接器定义。

ESP32 芯片的 IDE 是Espressif IDE。在 IDE 文件中搜索 IROM 地址,我们会找到:C:\Espressif\frameworks\esp-idf-v4.4.2\components\esptool_py\esptool\flasher_stub\ld\rom_32.ld

打开网易新闻 查看精彩图片

屏幕截图 28. ESP32 ROM 地址表

这些值可以轻松转换为枚举数据类型:

打开网易新闻 查看精彩图片

屏幕截图 29. 将值转换为枚举数据类型

然后,我们需要导入 IDA,以便将 enum 应用于 IROM 地址值:

打开网易新闻 查看精彩图片

屏幕截图 30. 将枚举应用于 IROM 地址值

如果我们在 IROM 地址附近添加可重复的注释,它将使所有内容更容易阅读:

打开网易新闻 查看精彩图片

屏幕截图 31. 在 IROM 地址附近添加可重复注释后的代码

5.4. 无法识别的指令

经常发生的情况是,处理器模块已针对指令集的某些特定变体实现。然后制造商制造出具有 99% 兼容指令集的新 CPU,其中包含 10 多个新指令,这是最初没人想到的。因此IDA、Ghidra和Radare等工具可能无法反汇编一些新指令。

克服这一挑战的正确方法是扩展处理器模块并添加对新指令的支持。这需要对反汇编器 API 有深入的了解,而这些 API 并不那么容易理解。

让我们讨论一种可能的解决方法,用于解决以下情况:尽管存在一些无法识别的指令,但您只想让 IDA 创建函数。假设 IDA 不知道 RER 指令,并且在包含 RER 操作码的情况下无法创建该函数:

打开网易新闻 查看精彩图片

屏幕截图 32. 如果函数包含 RER 操作码,IDA 无法创建该函数

您可以按P多次。除了控制台窗口中出现错误外,不会发生任何事情:

打开网易新闻 查看精彩图片

屏幕截图 33. 控制台窗口中的错误

但是,这并不意味着 IDA 无法创建遵循 RER 指令的指令。您可以跳过 RER 指令的三个字节,然后创建代码:

打开网易新闻 查看精彩图片

屏幕截图 34. 跳过 RER 指令的三个字节后创建代码

接下来,您可以选择从输入到最后的整段代码retw.n,然后按P:

打开网易新闻 查看精彩图片

屏幕截图 35. 选择从 Entry 到 retw.n 的整段代码

之后,IDA 将创建该函数:

打开网易新闻 查看精彩图片

屏幕截图 36.IDA 创建一个函数

通常,反汇编程序无法识别的扩展指令在逆向过程中不会产生太大差异。可能导致问题的是执行调用、跳转或加载/存储等操作的新指令,因为代码流丢失并且对数据的引用丢失。

结论
打开网易新闻 查看精彩图片
结论

对于涉及逆向工程物联网固件的项目来说,在转向业务逻辑之前研究未知的硬件架构至关重要。尽管逆向工程师可能需要几周的时间来学习该架构,但从长远来看,这种深入的研究有助于提高进一步工作的速度。

参考及来源:https://www.apriorit.com/dev-blog/reverse-reverse-engineer-iot-firmware