Ghidra 代码分析 Analyzer 的选择
Posted 银河安全实验室
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Ghidra 代码分析 Analyzer 的选择相关的知识,希望对你有一定的参考价值。
http://galaxylab.com.cn/ghidra%e6%ba%90%e7%a0%81%e5%88%86%e6%9e%90%e4%b8%80/
之前再博客中发了一篇初始阶段的代码分析,但是由于篇幅的限制只到 CodeBrowser 界面打开部分就结束了。
下面会简单介绍一下 "Analysis" 部分的功能是怎么进行的。从中可以看出 Ghidra 源代码构造的规范程度。
当然首先,这里会简单写一下之前工程建立和文件导入的过程。
编译
需要根据 DevGuide.md 中的内容,运行 gradle 命令来执行相应的 gradle 脚本,来完成初始化。
首先要下载必要的 Dependencies 依赖库。
然后又分成 Build 和 Develop 两种方式,根据需要选择。
由于 Windows 要多装 MinGW 等东西,所以推荐直接 Linux 下做就算了。
编译完成并导入 Eclipse 之后是这样的:
从创建工程到打开 CodeBrowser 界面
简单来说,通过使用 FrontEndTool 来显示初始面板。
而在 FrontEndTool 创建的时候,会同时创建诸多的 XXXPlugins,并注册到 PluginManager 里面。
比如这个 ImporterPlugin,它将 "Importer File" 与 doSingleImportAction 函数绑定。
当然 XXXManagers 也会有这样的绑定,但是一般来说 XXXManager 的功能更加深入?
而在 FrontEndPlugin 创建的时候,"Open With" 与 ProjectDataOpenToolAction 绑定。
/* --------------------- 分割线 ----------------------- */
选择 "Open With" -> "CodeBrowser" 实际上是创建了一个 GhidraTool 的实例,根据 FrontEndTool 的经验可以推断,这个 GhidraTool 是用来显示 CodeBrowser 界面的。
在 GhidraTool 创建的时候,一样创建了一堆的 XXXPlugins。
在 GhidraTool 创建完成之后,会去接收并处理我们选择的文件。
实际上是从上面诸多的 Plugins 中选择一个来处理接收到的数据。
这里是 ProgramManagerPlugin.acceptData,从名字上来看也很好理解。
实际上是走到 ProgramManagerPlugin.openPrograms 这里。
openProgram 会用来处理导入文件时候需要进行的后续动作。
可以看到这里有两个 openProgram 的地方,主要区别是后续产生的事件不同。第一个是 "Open" 类型事件,后一个是 "Active" 类型事件。
在导入文件的过程中,会产生事件,这里是 "Open" 类型事件。
最终是通过 GhidraTool 中的 EventManager 类型成员变量来发送事件,也就是这个 sendEvents 函数。
在这个函数中,在此之前,EventManager 用 pluginListenerMap 保存了 XXXEvent 和其相关联的所有 PluginEventListener(Plugin 类实现了这个接口,所以这里其实是各种 XXXPlugin)。
根据 Event 的种类,可以用 get 从 Map 中获取与它相关的所有 Plugin 组成的一个集合 set。比如这里是:
但是它们都是去调用了它们的 eventSent 函数。
之后就是通过 Plugins 各自的 processEvent 来根据事件类型来进行分别处理。
Analyzer 部分
实际上在处理上面提到的 "Open" / "Active" 事件的后续过程中,就触发了 "Analysis" 过程的产生。
在与这两个事件对应的 Plugins 列表中有,AutoAnalysisPlugin 明显更符合我们想要了解 Ghidra 的 "Auto Analysis" 功能的需要。
/* --------------------- 分割线 ----------------------- */
首先看下 AutoAnalysisPlugin 的创建,也就是构造函数 AutoAnalysisPlugin.<init>。
AutoAnalysisPlugin 和 CodeBrowserPlugin 等等 Plugins 一样,在 GhidraTool.<init> 的同时被创建,并加入到 GhidraTool 的 PluginManager 中。
这里关注 AutoAnalysisPluign.createActions 方法。
"Auto Analyze" 选项与 analyzeCallback 函数绑定了。
/* --------------------- 分割线 ----------------------- */
同时根据上面,AutoAnalysisPlugin 还需要用 AutoAnalysisPlugin.processEvent 函数来处理 "Open" 和 "Active" 两个事件。
这里关注 "Active" 的事件 ProgramActivatedPluginEvent,调用了 AutoAnalysisPlugin.programActivated 函数。
实际上也走到了 analyzeCallback,殊途同归。
其中 AutoAnalysisPlugin.showOptionsDialog函数是用来显示 "Analysis Option" 对话框的。
当选择了分析之后,函数会继续向下执行。首先是更新了"Analyzers Options",会给这些设置相关联的 Analyzers 来设置哪些是 "Enabled" 的。
接着创建了一个 AnalysisBackgoundCommand 实例,然后用GhidraTool.executeBackgroundCommand 来执行这个代表 "Auto Analysis" 的命令实例。
实际上是用了 ToolTaskManager.executeCommand 函数。
可以看到这个方式是用来执行后台命令的。主要是创建了一个新的线程来处理,启动线程其实也就是走到 ToolTaskManager.run方法,这个类是实现 Runnable接口的。
当然可以看到这个线程被设置了 2 的优先级,优先级很低。所以这个线程会比较靠后执行。
所以这个线程具体做什么我们之后再看。
(我们先回到 AutoAnalysisPlugin.analyzeCallback 函数)
下面进入到 AutoAnalysisManager.reAnalyzeAll 函数。
首先是AutoAnalysisManager.externalAdded 函数。
byteTasks 是一个 AnalysisTaskList 类型,里面有一个 AnalysisScheduler 的 List。AnalysisScheduler 是 XXXAnalyzer 类的封装。
所以这个 AnalysisTaskList 可以理解成一些类似功能的 Analyzers 的集合。比如 byteTasks 就是可以处理文件中 byte 信息的 Analyzers 的集合。
调用 AnalysisTaskList.notifyAdded 函数。
遍历列表,并对每个 AnalysisScheduler 执行其 added 函数。
进入 AnalysisScheduler.added函数。
(在 "Analyzer Options" 中被选中的 Analyzer 这里的 "enabled" 是 True!!!)
继续进入AnalysisScheduler.schedule 函数。
首先创建一个 AnalysisTask 实例。
这个类继承 BackgroundCommand 类,和之前的 AnalysisBackgroundCommand 一样。
AnalysisTask 实例的名字就是对应的 Analyzer的名字!
然后进入 AutoAnalysisManager.schedule 函数。
首先把之前的 AnalysisTask 加到一个优先级队列里面!!!
再去执行AutoAnalysisManager.startBackgroundAnalysis 函数。这个函数最终会走到上面提到的 ToolTaskManager.executeCommand 函数。
而 AutoAnalysisManager.reAnalyzeAll 中的功能都是类似的。
/* --------------------- 分割线 ----------------------- */
(在执行上面的这些函数,包括 AutoAnalysisManager.reAnalysisAll 函数,都有可能跳到,我们之前提到的,在 ToolTaskManager.executeCommand 函数中创建的新的线程中去。这个时候执行的时 ToolTaskManager.run中的内容!!!)
从 tasks 这个后台命令任务的列表中依次取出一个BackgroundCommandTask(这里是之前的 AnalysisBackgroundCommand)。tasks 是 ToolTaskManager 共享的。
也是线程之间共享的。
去到 BackgroundCommandTask.run函数。
obj 就是Program,cmd 是之前创建的 AnalysisBackgroundCommand 实例。
首先启动一个 Transaction,这里不详细说了。
接下来是调用了 AnalysisBackgroundCommand.applyTo函数,这里才是重要的部分!!!
这个时候调用 AutoAnalysisManager.startAnalysis 函数。
这里我们看到会使用 AutoAnalysisManager.getNextTask函数从 queue 中取出一个 BackgroundCommnad(这里是之前的 AnalysisTask),然后封装成 AutoAnalysisManager$AnalysisTaskWrapper类。
在之后调用 AutoAnalysisManager$AnalysisTaskWrapper.run 函数。并会循环从 queue 上取出分析任务 AnalysisTask进行封装,并执行 run 方法。
首先执行 task.applyTo 函数,task 是 BackgroundCommand,其实是之前提到的 AnalysisTask 类型的实例。
所以实际上执行的是 AnalysisTask.applyTo 方法。
而这个函数中实际执行的是 AnalysisTask 封装的AnalysisScheduler 实例的 AnalysisScheduler.runAnalyzer 的方法。
analyzer 就是我们在创建 AnalysisScheduler 的时候封装进去的。
以Dex 文件,且选择是默认分析选项为例。一开始会用到的Analyzers 有:DexHeaderFormatAnalyzer、DexExceptionHandlersAnalyzer、DexCondenseFillerBytesAnalyzer 这三个。
(当然再执行的过程中还会触发别的 Analyzers,因为分析时候,内容有变化,而内容更新就触发了新的 Analyzer 的封装的 AnalysisScheduler 加入 queue。
AutoAnalysisManager.domainObjectChanged 函数负责处理变动:
比如这里其实就是又去调用了 AnalysisTaskList.notifyAdded 函数。)
/* --------------------- 分割线 ----------------------- */
(以 DexHeaderFormatAnalyzer.added为例!!!)
DexHeaderFormatAnalyzer继承自 FileFormatAnalyzer,所以 added 函数是用的 FileFormatAnalyzer.added。
但是继续的 analyze 则是用的DexHeaderFormatAnalyzer.analyze函数。
再往下就是具体的 Analysis 的地方了,在下章再分析。
以上是关于Ghidra 代码分析 Analyzer 的选择的主要内容,如果未能解决你的问题,请参考以下文章