为Ghidra编写wasm加载程序。 第1部分:问题陈述和设置环境


本周,国家安全局(NSA)突然向人类献出了礼物,为其软件逆向工程框架开放了资源。 热情洋溢的逆向工程师和安全专家社区开始探索新玩具。 根据反馈,这确实是一个了不起的工具,能够与IDA Pro,R2和JEB等现有解决方案竞争。 该工具称为Ghidra,专业资源充满了研究人员的印象。 实际上,它们有一个很好的理由:并非每天都有政府组织提供对其内部工具的访问。 作为专业的逆向工程师和恶意软件分析师,我自己也无法幸免。 我决定花一个或两个周末来获得该工具的第一印象。 我玩了一些反汇编,并决定检查该工具的可扩展性。 在本系列文章中,我将解释Ghidra加载项的开发,该加载项加载了用于解决CTF任务的自定义格式。 由于它是一个大型框架,因此我选择了非常复杂的任务,因此将本文分为几个部分。

在本部分的最后,我希望设置开发环境并构建最小的模块,该模块将能够识别WebAssembly文件的格式并提出合适的反汇编程序进行处理。

让我们从任务描述开始。 去年,安全公司FireEye举办了名为flare-on的CTF竞赛。 在竞赛中,研究人员必须解决与逆向工程有关的十二项任务。 任务之一是研究使用WebAssembly构建的Web应用程序。 这是一种相对较新的可执行文件格式,据我所知,没有完美的工具来处理它。 在挑战期间,我尝试了多种工具来击败它。 这些是来自github的简单脚本以及知名的反编译器,例如IDA pro和JEB。 令人惊讶的是,我停止使用chrome,后者为WebAssembly提供了非常好的反汇编程序和调试器。 我的目标是解决与ghidra的挑战。 我将尽可能全面地描述这项研究,并提供所有可能的信息来重现我的步骤。 也许,作为一个没有太多乐器经验的人,我可能会讲一些不必要的细节,但事实就是如此。

我要用于学习的任务可以从flareon5挑战网站下载。 有文件05_web2point0.7z:归档文件已加密,带有被感染的可怕单词。 存档中有三个文件:index.html,main.js和test.wasm。 让我们在浏览器中打开文件index.html并检查结果:



好吧,这就是我要处理的。 让我们从html学习开始,尤其是因为它是挑战中最容易的部分。 html代码除加载main.js脚本外不包含任何内容。

<!DOCTYPE html> <html> <body> <span id="container"></span> <script src="./main.js"></script> </body> </html> 

该脚本看起来也很冗长,但是它并没有做任何复杂的事情。 它只是加载文件test.wasm并使用它来创建WebAssembly实例。 然后,它从url中读取参数“ q”,并将其传递给方法match,由实例导出。 如果参数中的字符串不正确,则脚本将显示我们在FireEye开发人员称为“ Poo of poo”的图像。

  let b = new Uint8Array(new TextEncoder().encode(getParameterByName("q"))); let pa = wasm_alloc(instance, 0x200); wasm_write(instance, pa, a); let pb = wasm_alloc(instance, 0x200); wasm_write(instance, pb, b); if (instance.exports.Match(pa, a.byteLength, pb, b.byteLength) == 1) { // PARTY POPPER document.getElementById("container").innerText = "🎉"; } else { // PILE OF POO document.getElementById("container").innerText = "ðŸ'"; } 

该任务的解决方案是找到使函数“ match”返回“ True”的参数q的值。 为此,我将反汇编文件test.wasm并分析功能Match的算法。

毫不奇怪,我将尽力在Ghidra中做到这一点。 但是首先我必须安装它。 可以(并且应该)从https://ghidra-sre.org/下载安装。 由于它是用Java编写的,因此几乎没有安装的特殊要求,也不需要进行任何特殊的安装。 您所需要做的只是解压缩归档文件并运行应用程序。 唯一需要做的就是将JDK和JRE更新到版本11。

让我们创建一个新的ghidra项目( File-> New Project ),并将其命名为“ wasm” /



然后添加文件test.wasm( File→Import file )到项目中,看看ghidra如何处理它



好吧,它什么也做不了。 它不能识别格式,也不能反汇编任何内容,因此处理此任务绝对无能为力。 最后,我们讨论了本文的主题。 没什么可做的,只是编写一个模块,该模块能够加载wasm文件,对其进行分析并反汇编其代码。

首先,我研究了所有可用的文档。 实际上,只有一个合适的文档显示了附件开发的过程:幻灯片GhidraAdvancedDevelopment。 我将按照该文档进行逐项介绍。

不幸的是,附加组件开发需要使用Eclipse。 我在eclipse方面的所有经验是在2012年为Android开发了两款gdx游戏。经历了两个星期的痛苦和磨难,之后我将其从脑中抹去了。 希望经过7年的发展,它比以前更好。

让我们从官方网站下载并安装Eclipse。

然后,为ghidra开发安装扩展程序:

转到eclipse 帮助→安装新软件菜单,单击添加按钮,然后从/扩展名/ Eclipse / GhidraDev /中选择GhidraDev.zip。 安装它并重新启动扩展。 该扩展将模板添加到新的项目菜单中,允许从eclipse调试模块并将模块编译到分发包。

正如开发人员文档所述,必须执行以下步骤来添加用于处理新二进制格式的模块:

  • 创建类,描述数据结构
  • 开发加载器。 加载程序应继承自AbstractLibrarySupportLoader类。 它从文件中读取所有必要的数据,检查数据的完整性,并将二进制数据转换为内部表示形式,以准备进行分析
  • 开发分析仪。 Analyzer继承自AbstractAnalyzer类。 它采用加载程序准备的数据结构并对其进行注释(我不确定这是什么意思,但是我希望在开发过程中能够理解)
  • 添加处理器。 Ghidra有一个抽象:处理器。 它用内部声明性语言编写,并描述了指令集,内存布局和其他体系结构功能。 我将讨论这个主题,编写反汇编程序。

现在,当我们掌握了所有必要的理论后,就该创建模块项目了。 感谢先前安装的Eclipse扩展GhidraDev,我们在File-> New project菜单中有了模块模板。



向导询问需要哪些组件。 如前所述,我们将需要两个:加载器和分析器。



向导将创建包含所有必要部分的项目框架:文件WasmAnalyzer.java中的空白分析器,文件WasmLoader.java中的空白加载器以及目录/数据/语言中的语言框架。



让我们从加载器开始。 如前所述,它应该从AbstractLibrarySupportLoader类继承,并具有三种要重载的方法:

  • getName-此方法应为加载程序的内部名称。 Ghidra在各种地方使用它,例如,将加载程序绑定到处理器
  • findSupportedLoadSpecs-回调,在用户选择要导入的文件时执行。 在此回调加载程序中,应确定它是否能够处理文件并返回LoadSpec类的实例,告诉用户如何处理文件。
  • load-用户加载文件后执行的回调。 在这种方法中,加载器解析文件结构并将其加载到Ghidra中。 将在下一篇文章中更详细地描述它

第一个也是最简单的方法是getName,它只是返回加载程序的名称

  public String getName() { return "WebAssembly"; } 

第二种实现方法是findSupportedLoadSpecs。 它是在导入文件时由工具调用的,应验证加载程序是否能够处理该文件。 如果有能力的方法返回LoadSpec类的对象,则告诉使用哪个对象加载文件以及哪个处理器将反汇编其代码。

方法从格式验证开始。 根据规范 ,wasm文件的前八个字节应为签名“ \ 0asm”和版本。

为了解析标头,我创建了类WasmHeader,实现了接口StructConverter ,该接口是描述结构化数据的基础接口。 WasmHeader的构造函数接收对象BinaryReader-抽象,用于从正在分析的二进制源中读取数据。 构造函数使用它来读取输入文件的头

  private byte[] magic; private byte [] version; public WasmHeader(BinaryReader reader) throws IOException { magic = reader.readNextByteArray(WASM_MAGIC_BASE.length()); version = reader.readNextByteArray(WASM_VERSION_LENGTH); } 

加载程序使用此对象来验证文件的签名。 然后,如果成功,则搜索适当的处理器。 它调用类QueryOpinionService的方法查询,并将其传递给加载程序的名称(“ Webassembly”)。 OpinionService正在寻找与此加载程序关联的处理器,并将其返回。

 List<QueryResult> queries = QueryOpinionService.query(getName(), MACHINE, null); 

当然,它什么也不会返回,因为ghidra不知道称为WebAssembly的处理器,因此需要对其进行定义。 如前所述,向导在目录数据/语言中创建了语言框架。



在当前阶段,有两个可能有趣的文件:Webassembly.opinion和Wbassembly.ldefs。 文件.opinon设置加载程序和处理器之间的对应关系。

 <opinions> <constraint loader="WebAssembly" compilerSpecID="default"> <constraint primary="1" processor="Webassembly" size="16" /> </constraint> </opinions> 

它包含具有少量属性的简单xml。 需要将加载程序的名称设置为属性“ loader”,将处理器的名称设置为属性“ processor”,两者均为“ Webassembly”。 在此步骤中,我将使用随机值填充其他参数。 一旦我对Webassembly处理器架构师有了更多的了解,就将其更改为正确的值。

文件.ldefs描述了处理器的功能,这些功能应从文件中执行代码。

 <language_definitions> <language processor="Webassembly" endian="little" size="16" variant="default" version="1.0" slafile="Webassembly.sla" processorspec="Webassembly.pspec" id="wasm:LE:16:default"> <description>Webassembly Language Module</description> <compiler name="default" spec="Webassembly.cspec" id="default"/> </language> </language_definitions> 

属性“处理器”应与文件.opinion中的属性处理器相同。 让我们保持其他领域不变。 但是请记住,下次可以设置注册表的位数(属性“大小”),描述处理器“ processorspec”体系结构的文件和包含特殊声明性语言“ slafile”的代码描述的文件。 进行拆卸工作会很方便。

现在,该回到加载程序并返回加载程序的规范了。

一切准备就绪,可以进行测试了。 GhidraDev插件在运行时添加了运行选项“ Run→Run As→Ghidra ”:



它在调试模式下运行ghidra并在其中部署模块,这为使用该工具提供了绝佳的机会,同时使用调试器修复了正在开发的模块中的错误。 但是在这个简单的阶段,没有理由使用调试器。 和以前一样,我将创建一个新项目,导入文件,并查看我的努力是否成功。 与上次不同,该文件被识别为WebAssembly,并且加载程序为其建议相应的处理器。 这意味着一切正常,并且我的模块能够识别格式。



在下一篇文章中,我将扩展加载器,使其不仅可以识别,而且可以描述wasm文件的结构。 我认为在此阶段,设置环境之后,这将很容易做到。

该模块的代码可在github仓库中找到。

Source: https://habr.com/ru/post/zh-CN443318/


All Articles