使用Ghidra分析Dex文件

使用Ghidra分析Dex文件

实验室的小伙伴之前已经完成了一篇《使用Ghidra P-Code进行辅助逆向分析》的博客,这里就不再对如何使用Ghidra做详细介绍了。这里会讲一下Ghidra对Dex文件的解析,Ghidra-Pcode针对Smali语言表现出来的一些特性以及相应的Ghidra-Script编写中实用的API.

  • 反编译部分

Ghidra实际上集成了Baksmali。

在将Dex文件导入工程并执行完Analysis流程之后,可以再Program Trees窗口中看到所需要的表项。

虽然命名有点不清楚,Ghidra对Dex文件的解析还是比较全面的。

比如:

  • method_bytecode栏是对Dex文件各个方法进行反编译出来的表。

  • classes栏实际对应了DexClassDef结构体。
  • class_data栏是DexClassData结构体的DexClassHeader的表。
  • encoded_methods栏对应了DexMethod结构体。

等等。

当然实际使用的时候还是存在一些不便,比如:解析的字符串有时候会是缩写,Field类型没有Reference之类的。

 

  • Pcode部分

下面会详细看下Ghidra-Pcode对Smali的处理。

首先可以看到图中的函数,按照V命名法,有两个局部变量寄存器v0、v1和3个参数寄存器v2 ~ v4。在Pcode中规定了参数是iv*的形式。但是下面可以看到它其实也会使用v*这种形式。

地址0x501241b8到0x501241bc和语句"this.logd("OOOOOOOO....")"相对应。

  • const_string:CPOOL

从上图中可以看到:CPOOL中0x16ef是DexStringId的索引,输出v0则代表这个字符串的Object。

  • invoke_virtual / direct:CPOOL + CALLIND

从上图中可以看到:COPY将所需要参数转变成iv*的形式;CPOOL中0x3e9e是DexMethodId的索引;CALLIND调用了$Uff0,并省略了两个参数iv0和iv1,其中iv0是默认存在的,标识调用这个函数的类的实例。

地址0x501241c4到0x501241c8和语句"ref = new StringBuilder()"相对应。

  • new_instance:CPOOL + NEWOBJECT

从上图中可以看到:CPOOL中0x7aa是DexTypeId的索引;NEWOBJECT给$U2e0实例化了一个Varnode。

之后用CALLIND来处理"<init>"构造函数就不说了。

  • iget / iget_object:CPOOL + LOAD ram()

从上图中可以看到:CPOOL中0x2562是DexFieldId的索引;LOAD从$Ub30代表的内存中读取了这个field。

同理有:

  • iput / iput_object:CPOOL + STORE ram()

(Pcode上的0x值会被直接关联到地址上去,双击上去以后跳转其实并不是对的。)

 

  • Script部分

《使用Ghidra P-Code进行辅助逆向分析》已经提到了如何获取Pcode以及利用getDef来对Varnode的流向做追踪,接下来同样会以Dex中函数参数回溯的脚本作为例子讲一下一些技巧。

  • 处理Dex文件

上面几个其实是Java类,由于Jython的存在,也可以在Python脚本中直接使用它。可以参考https://github.com/NationalSecurityAgency/ghidra/tree/49c2010b63b56c8f20845f3970fedd95d003b1e9/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/

通过DexAnalysisState获取DexHeader。使用DexHeader可以根据DexStringId的索引获取具体的字符串的值。相应的也可以继续根据DexTypeId的索引、DexFieldId的索引、DexMethodId的索引来获取类名、Field名和方法名。

  • Script中的Varnode

实际上Script中输出的Pcode内容比GUI上显示的要详实。比如,上图中会打印出Varnode的所有的信息(Type, Offset, Length)。

其中:

0x100开始表示函数的参数寄存器iv*,0x100代表参数1(一般就是this),0x104代表参数2,0x108代表参数3,依此类推。

使用上述代码可以得到参数Varnode的Index。

0x1000开始则表示函数的变量寄存器v*。

但是也会有特殊的,例如表示类的Field的Varnode统一都是(const, 0x1a1, 4),这样的话,就没有办法去查询Field的References了。

  • 获取Pcode的地址

Pcode本身并没有获取地址的API,但是可以通过上述方法来得到这个地址。

通过地址,接下来就可以获取与Pcode对应的Instruction。并通过它的getFlows()函数来得到接下来的流程。

 

  • END

这里实现了一个Dex函数的参数回溯脚本。

在获取参数所代表的Varnode之后,通过一路向上getDef找到定义它的Pcode。

如果是const_string相关的操作,会解析出相应的字符串。

如果是new_instance相关的操作,通过getDescendants函数找出Varnode相关的所有Pcode操作,并对其参数做更进一步的分析。

当然由于一些原因,比如:

  1. 当通过getDef得到一个new创建出来的一个Varnode之后,只能通过getDescendants函数找到所有使用这个Varnode的Pcode,无法判断分支。
  2. Ghidra没有提供Field的References功能。

同时GHidra的文档注释也很简单,应该可以通过Jython来调用更多的类,来消减开支,此脚本还远没有完善。

脚本之后会开源在https://github.com/PAGalaxyLab/ghidra_scripts/上。

 

  • References

  1. https://github.com/PAGalaxyLab/ghidra_scripts
  2. https://github.com/NationalSecurityAgency/ghidra

 

本文由 Galaxy Lab 作者:shaozhuang 发表,其版权均为 Galaxy Lab 所有,文章内容系作者个人观点,不代表 Galaxy Lab 对观点赞同或支持。如需转载,请注明文章来源。
3

发表评论

*