分析 WebAssembly 二进制文件 - Wasm 逆向工程

我们最近发表了一篇关于WebAssembly (Wasm)的安全问题和基本概念的博文。作为后续,这篇文章将介绍 Wasm 应用程序的逆向工程。考虑一下您遇到未知 Wasm 应用程序的场景,您需要弄清楚它的作用。你将如何分析它?目前几乎没有任何关于该主题的有用文档,因此我们决定部分填补这一空白。

Wasm 应用程序可以通过不同的方式进行分析。今天我们将通过一个非常简单的应用程序来介绍 Chrome 内置的 Wasm 调试功能。随着我们的进行,一些理论将被介绍。

想要直接了解更多技术内容的不耐烦的读者可以从附录部分获取 HTML 文件 test.html,然后直接跳转到“调试我们的示例应用程序”部分。

为什么要分析 Wasm?

为什么我们首先对分析 Wasm 应用程序感兴趣?在深入研究动手技术之前,让我们回答这个问题。

在 Forcepoint,我们对恶意行为者如何利用新兴技术和技术感兴趣。每当出现新的威胁时,例如新的勒索软件系列、物联网蠕虫或更不寻常的东西,安全研究人员都希望分析该恶意代码的功能。当我们知道恶意软件是如何工作的,并且我们知道它的属性时,我们可以编写签名来获得保护。

存在许多用于分析传统恶意软件的工具,无论是混淆的 JavaScript、恶意 Flash 对象、可移植可执行文件 (PE) 还是其他东西。存在一种行之有效的方法来分析这些类型的威胁。

正如我们在本系列的第一篇文章中提到的那样,Wasm 的情况有所不同。几乎没有关于如何分析 Wasm 应用程序的文档,而且大多数常见的逆向工程工具还不了解 Wasm。这篇博文试图阐明对 Wasm 二进制文件的逆向工程。

创建示例“Hello World”Wasm 应用程序

让我们从创建一个简单的 Wasm 应用程序开始,稍后我们将对其进行分析。我们将在浏览器中运行应用程序并使用 Chrome 的开发者工具对其进行分析。

要在浏览器中运行 Wasm 应用程序,我们需要一个 HTML 文件来加载和执行 Wasm 二进制文件。让我们来看看创建这个 HTML 文件的过程。(如前所述,我们最终将获得本文末尾附录中列出的文件。)

从以下骨架开始(我们将进一步修改)并将其保存为名为 test.html 的文件:






为了便于设置并避免安装任何工具,让我们使用一个名为WasmFiddler的在线 Web 应用程序来生成我们的 Wasm。在 WasmFiddler 中,键入以下简单程序:

void hello() {
  printf("Hello World\n");
}

然后单击“构建”,如下面的屏幕截图所示:
分析 WebAssembly 二进制文件 - Wasm 逆向工程_第1张图片
图 1:使用 WasmFiddler 编译 Wasm 应用程序。
在上面屏幕截图的右侧,我们看到了一个名为 utf8ToString() 的函数。将该函数复制并粘贴到我们 HTML 页面的 JavaScript 部分,将其放在 test() 函数上方。

仍然看屏幕截图的右侧,我们可以看到函数 utf8ToString() 之后的几行 JavaScript:

let m = new WebAssembly.Instance(new WebAssembly.Module(buffer));
let h = new Uint8Array(m.exports.memory.buffer);
let p = m.exports.hello();

复制这些行并将它们粘贴到 test() 函数中。这些行将从定义在名为“buffer”的数组中的代码中实例化我们的 Wasm,然后执行我们的 hello() 函数。

那么我们如何真正定义这个缓冲区的内容(Wasm 代码)呢?在 WasmFiddler 中,单击源代码下方的下拉菜单(图 1 中显示“文本格式”的菜单),然后选择“代码缓冲区”。
分析 WebAssembly 二进制文件 - Wasm 逆向工程_第2张图片
图 2:查看 WasmFiddler 中的代码缓冲区。

WasmFiddler 现在将生成二进制 Wasm 代码并将其放入 JavaScript 缓冲区。你应该得到这个(为了简洁而缩短):

var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,...,108,100,0]);

注意:如果你只是得到一个空数组(“var wasmCode = new Uint8Array([null]);”),那么你忘了先编译源代码。在这种情况下,单击构建并重试。

复制此缓冲区并将其粘贴到我们的 test() 函数的开头。将数组从“wasmCode”重命名为“buffer”以匹配由 WasmFiddler 生成的其他代码的命名。

如果您还记得我们在本系列的第一篇博文中,Wasm 应用程序无法自行将文本打印到屏幕上。我们需要定义一个 JavaScript 函数,我们的 Wasm 代码中的 printf() 调用可以使用该函数。在 WasmFiddler 中,在下拉菜单中选择 Text Format 以查看我们编译的 Wasm 应用程序的文本表示:
分析 WebAssembly 二进制文件 - Wasm 逆向工程_第3张图片
图 4: puts() 函数的导入模板。

复制上面看到的“wasmImports”定义并将其粘贴到我们的 JavaScript test() 函数的开头。然后我们需要将此导入定义提供给 Wasm 的实例化。通过使用以下实例化来做到这一点:

var m = new WebAssembly.Instance(new WebAssembly.Module(buffer),wasmImports);

最后,让我们定义 puts() 函数在调用时应该做什么。将其更改为以下内容:

puts: function puts (index) {
  alert(utf8ToString(h, index));
}

现在我们已经完成了构建演示程序的所有必要步骤。在 Chrome 中加载我们的文件 test.html 会给我们一个警告:
分析 WebAssembly 二进制文件 - Wasm 逆向工程_第4张图片
图 5:Chrome 中的警报。

我们可以看到 Wasm 代码成功调用了我们的外部函数。

注意:如果您没有看到弹出窗口,那么问题可能是您的浏览器不支持 Wasm。在这种情况下,请尝试使用更新的浏览器,因为所有主流浏览器的最新版本都应该支持 Wasm。

调试我们的示例应用程序

现在我们终于可以使用 Chrome 开发者工具进行调试了。

在 Chrome 中打开 test.html 文件后,启动 Chrome 开发者工具(按 F12)并选择顶部的 Sources 选项卡。然后按 Ctrl+R 重新加载页面。现在出现一个带有文本“wasm”的小云图标。展开它以及它下面的项目。选择 wasm 子树下的叶子条目。您现在应该看到如下内容:
分析 WebAssembly 二进制文件 - Wasm 逆向工程_第5张图片
图 6:Chrome 开发者工具

让我们单步执行这个函数,以便更好地理解它的作用。单击以“i32”开头的行的左侧以设置断点。一个蓝色条将变为可见,表示已设置断点。接下来,按 Ctrl+R 再次重新加载页面。执行现在将在断点处停止。此时 Wasm 堆栈为空。现在按下调试器中的 Step Over 按钮(F10 或带有弯曲箭头的图标)以执行指令“i32.const 16”,这会将 16 的值放入堆栈:
分析 WebAssembly 二进制文件 - Wasm 逆向工程_第6张图片
图 7:值 16 放入堆栈。

Wasm 中的所有函数都有编号,函数编号 0 对应于 Wasm 从 JavaScript 导入的函数“puts”(函数编号 1 是“hello”函数)。因此,下一条指令 call 0 对应调用 printf/puts 函数,栈上的值 ‘16’ 就是参数。

值“16”如何对应字符串“Hello World”?这个值实际上是一个指向 Wasm 应用程序内存空间中地址的指针。Chrome 的调试器让我们通过展开全局树来查看 Wasm 应用程序的内存:
分析 WebAssembly 二进制文件 - Wasm 逆向工程_第7张图片
图 8:查看 Wasm 应用程序的内存。

让我们看看内存中的第 16 位:
分析 WebAssembly 二进制文件 - Wasm 逆向工程_第8张图片
图 9:Wasm 应用程序内存中的“Hello World”。

正在运行的 Wasm 应用程序的内存空间实际上是作为 JavaScript 数组实现的。该数组在负责加载 Wasm 应用程序的 HTML 文件中声明。在上面的示例中,以下行声明了变量“h”,其中包含应用程序的内存空间:

让 h = new Uint8Array(m.exports.memory.buffer);
现在再次按下 Step Over 按钮以执行呼叫。这最终会给我们 JavaScript 警报。

结论

我们现在已经成功地逆向设计了我们的第一个简单的 Wasm 程序。这个例子非常简单,但我们必须从基础开始。

在逆向过程中,我们了解了 Wasm 如何通过调用在 JavaScript 中声明的导入函数与外部环境进行交互。此外,我们还了解了 JavaScript 和 Wasm 之间如何共享内存。

Forcepoint 安全实验室将继续监控 WASM 的发展,并酌情提供更新。

参考

WasmFiddle,在线编译 Wasm:https /wasdk.github.io/WasmFiddle/?wvzhb

有关如何在浏览器的调试器中调试 Wasm 的视频:https /www.youtube.com/watch?v=R1WtBkMeGds

在 JavaScript 和 Wasm 之间传递值:https /hacks.mozilla.org/2017/07/memory-in-webassembly-and-why-its-safer-than-you-think/

附录:test.html

为了便于参考,这里是我们创建并分析的整个 test.html 文件:






你可能感兴趣的:(wasm,安全)