Ghidra源码分析(一)

编译

需要根据 DevGuide.md 中的内容,运行 gradle 命令来执行相应的 gradle 脚本,来完成初始化。

首先要下载必要的 Dependencies 依赖库。

然后又分成 Build Develop 两种方式,根据需要选择。

由于 Windows 要多装 MinGW 等东西,所以推荐直接 Linux 下做就算了。

编译完成并导入 Eclipse 之后是这样的:

 

流程

  • 入口

可以看到入口类是 Framework Utility 包的 ghidra.GhidraLauncher 类。参数是 ghidra.GhidraRun

这个类的 main 函数里面做了一些初始化的工作,然后就反射方式创建 Features Base ghidra.GhidraRun 类实例,并执行了其地 launch 方法。args[0] 就是上图中的参数项 ghidra.GhidraRun

ghidra.GhidraRun.launch 方法中可以看到 Ghidra 使用了多线程的方式,其中主线程运行了 mainTask 中的内容。首先是进行了一些配置,并设置了 TaskMonitor( GUI 有关)。然后对 Application 进行了一些初始化,Application 是一个全局的单例模式。这一步其实就是打开初始界面的,比如说可以看到 TooltipsHelp 界面之类的。之后它会 processArguments 来处理参数,openProject 打开工程之类的。

  • 打开创建工程

可以很明确的看到创建了 ProjectManager(实际继承自 DefaultProjectManager )FrontEndTool 实例(用作 Ghidra Project Window)

经过一系列初始化之后,就进入实际的打开工程的过程了,也就是 doOpenProject 函数,但是是在另一个线程里面。

使用 DefaultProjectManager.openProject 函数来打开工程。这个函数会返回一个 DefaultProject 实例.

然后用 FrontEndTool.setActiveProject 来显示工程界面。这个函数就是用来进行界面上各个 Toolbar 及其绑定的 Action 的设置的。FileActionManagerProjectActionManagerToolActionManager 这几个类,也都在 ghidra.framework.main 包下面,用来给 File、ProjectTool(包括 Tool Chest) 这几个菜单上的按钮绑定 Action 用的。

例如 FileActionManager 在构造函数初始化的时候,调用 createActions 函数。下图中给出了 "New Project" 这个 Button 是如何与函数 newProject 绑定,并注册到 ToolBar 上的。

同样的例子,在 FrontEndTool 构造函数初始化的时候会调用 PluginManager 添加更多的 Plugins,比如 ImporterPlugin。并调用了 ImporterPlugin.<init> ImporterPlugin.init 函数,这个类在 Features Base ghidra.plugin.importer 包下面

(可以看到 FrontEndTool 就是用来处理初始的那个工程界面的。

所以,Ghidra 工程的架构类似于,XXXTool 创建并处理界面,各个 XXXPlugin 被注册添加到 XXXTool 中去。而这些 Plugins 负责相应的功能的实现,比如 Action 和具体函数的绑定!!!)

  • 导入目标文件

由上面可以知道 ImporterPlugin 其实才是负责 "Import File" 这个功能的类。

ImporterPlugin.setupImportAction 函数中,"Import File" 按钮和 ImporterPlugin.doSingleImportAction 函数绑定。

如果目标使 "Single File",那么会进到 ImporterUtilities.importSingleFile 函数中。

这个函数会创建一个新的对话框 ImporterDialog 类的实例。而对导入文件的初步处理则是在在函数 LoaderService.getAllSupportedLoadSpecs 中执行的。

首先是 getAllLoaders 找到所有的 Loader

然后用 Loader.findSupportedLoadSpecs 函数,得到和文件对应的 Loader

以 Dex 文件为例,选取的就是 DexLoader,执行的就是 DexLoader.findSupportedLoadSpecs 函数。这个函数其实就是检查了下 Header

对导入文件初步处理的信息换显示在一个对话框上,确认以后才会进行真正的文件导入。这一步调用另一个 ImporterUtilities.importSingleFile 函数。

可以看到分成两步:

  • 使用 load 函数导入需要导入进来的文件,并且对文件创建 Program 实例,并完成初始化,预处理。
  • 使用 doPostImportProcessing 函数来对 Program 进行进一步的处理。

(先是导入文件的过程)

上面说的 load 函数是 DexLoader.load 函数,实际上是 Loader 的子类同时也是 DexLoader 的父类 AbstractProgramLoader.load 函数(DexLoader 还有一个 load 函数的重写,但那是继承的另一个 load 函数)

首先进到 AbstractLibrarySupportLoader.loadProgram 函数中,它又调用了 AbstractLibrarySupportLoader.doLoad 函数来创建 Program 实例。

首先得到了程序给导入文件(这里是 Dex 文件)选择的 Language。选择的SleighLanguage 类,这个是 Ghidra 自己的 Pcode 语言反编译器用的

然后获取与 SleighLanguage 对应的 CompilerSpec BasicCompilerSpec 。CompilerSpec 就是用来封装 Compiler Option 信息的类。

(可以看到 XXXSpec 意思就是用来存储 XXX Option 信息的一个结构。)

再使用 AbstractProgramLoader.createProgram 函数创建 ProgramDB 的对象。

ProgramDB 类是 Program 接口的实现,可以理解成想数据库一样存储了 Program 的有关信息。

比如一个 XXXManager 的数组。此外还有一堆子项目的 XXXDB

而在获取了 transactionID 之后,又调用了 load 函数,这个时候就是 DexLoader.load 函数了。

首先调用了 MemoryMapDB.createInitializedBlock 创建一个叫 ".dex" MemoryBlock。

然后从文件中抽取出文件的 DexHeader。

再用 createMethodLookupMemoryBlock 函数创建一个叫 "method_lookup" MemoryBlock,它的地址是 0xE0000000

再用 createMethodByteCodeBlock 函数创建一个叫 "method_bytecode" MemoryBlock,它的地址是 0x50000000

可以看到再用 createMethods 这个函数,将方法的 CodeItem 的指令字节码放到了,之前创建的 method_bytecode MemoryBlock 上的相应的偏移上。以及将方法地址偏移放到 method_lookup MemoryBlock 上。

整个这个步骤相当于:

  • 完成了对 DexFile 的解析
  • 为方法的字节码和地址偏移分别创建了一块 MemoryBlock,并和 Program 绑定。

再往后就是一些引入外部库的符号和导入符号,以及关联 Program 和目标文件,以及保存、显示等操作,不再细说了。

  • Open With CodeBrowser

FrontEndPlugin 在初始化的时候就创建了 ProjectDataOpenToolAction,并将其与 "Open With" 绑定。

当使用 "Open With" -> "CodeBrowser" 的时候会根据代表 "CodeBrowser" ToolTemplate,这里其实是 GhidraToolTemplate 类,来创建一个 GhidraTool 实例,这个类和 GhidraToolTemplate 是在一个包下面。类比之前的 FrontEndTool,这个 GhidraTool 就是用来显示 CodeBrowser 界面的。

创建 GhidraTool 的过程中会创建 CodeBrowserPlugin,同时

还会有很多不同的 XXXPlugin 被创建并加入到 PluginManagerpluginList 中,GhidraTool 则包含这个 PluginManager 类型的成员变量 pluginMgr

接下来可以看到 GhidraTool.acceptDomainFiles 函数接收了个 DomainFile 数组,这里是 GhidraFile 的类型,代表我们导入的文件。实际上是调用的 PluginTool.acceptDomainFiles 函数。遍历已注册的 Plugin,来查看哪个 XXXPlugin.acceptData 函数可以来接收并处理数据。

这里对应的使 ProgramManagerPlugin。

给 Swing 创建新的线程去执行 ProgramManager.addProgram,其实是 MultiProgramManager.addProgram 函数。

首先将当前的 GhidraTool 实例加到 ProgramDB 实例的 consumer 成员变量中。

接下来看到 MultiProgramManager.fireOpenEvents 函数

同样可以看到,参数是两个 new 出来的 Event。一个是 ProgramOpenedPluginEvent 另一个是 OpenProgramPluginEvent。从名字上来看,也可以看出来这是与打开工程相关的。

实际上是使用 GhidraTool 的 EventManager 的 fireEvent 函数来处理这两个事件!

之后进入 EventManager.sendEvents 函数。

EventManager pluginListenerMap 保存了 XXXEvent 和其相关联的所有 PluginEventListener(Plugin 类实现了这个接口,所以这里其实是各种 XXXPlugin)

根据 Event 的种类,可以获取与它相关的所有 Plugin 组成的一个集合,比如:

这些 Plugin 会使用它们的 eventSent 函数,这个函数最终要么使用 ProgramPlugin.processEvent 函数,要么使用它自己定义的 processEvent 函数。

Plugin 去对 Event 做处理,而之后各种在 CodeBrowser 打开过程中触发的事件,也是由相应的 Event 来处理的。

本文原创,作者:shaozhuang,其版权均为Galaxy Lab所有。如需转载,请注明出处:http://galaxylab.com.cn/ghidra%e6%ba%90%e7%a0%81%e5%88%86%e6%9e%90%e4%b8%80/
1

发表评论

*