0%

ghidra-Pcode

最近想用ghidra分析一些东西,学下ghidra的PCode

PCode

ghidra为了分析多个平台的二进制文件,需要一种架构无关的IR来执行分析,于是ghidra官方提出了PCode针对寄存器内容转移的IR来使得不同的架构可以使用同一架构来分析。
ghidra PCode基本原来为将每条指令翻译为PCode指令序列,将上下文状态的一部分作为输入然后产生输出(有些指令并没有输出),将指令直接翻译的PCode序列被称作raw PCode,raw PCode可以用于直接模拟指令执行,在模拟指令执行的过程中会按照原来的控制流并加上PCode内部的控制流。

PCode的设计初衷为简化在之后反汇编指令中用到的数据流图的构建。varnodes和pcode运算符可以当作是这个图上的节点。在构建数据流图的过程中,第一步一定是raw pcode的翻译,之后会有一些特殊的优化过程,在这个过程中ghidra加入了其他指令,如MULTIEQUAL、INDIRECT等指令,这些指令我将在本文的后半部分讲解。

Address Space

在ghidra的抽象中地址空间就是一个可索引的字节序列,包括Register、ram、constant、temporary等
constant负责编码固定的数值
temporary可以被看作一个很大的临时寄存器,负责记录转换指令到pcode时的立即数

Varnode

一个varnode代表一块寄存器位置或者内存位置,他由三元组形式来表示

1
Varnode: (ram/const/register/other, val, len)

简单来说一个varnode就是把一个连续的字节序列转化为单个值,pcode中所有的数据操作都发生在varnodes上

如果一个varnode为constant地址空间中的值,则它的offset将被视为立即数,即我们通过getOffset方法获得的数字即为constant varnode的值

1
(unique, 0x10000381, 4) COPY (const, 0x134b4, 4)

COPY的input0 即为const varnode,可以使用isConstant方法来判断,getOffset方法来获得constant varnode的值

PCode operation

COPY

Parameters Description
input0 Source varnode.
output Destination varnode.

将input0的内容拷贝任意字节到output,input0和output的size必须相同

LOAD

Parameters Description
input0 (special) Constant ID of space to load from.
input1 Varnode containing pointer offset to data.
output Destination varnode.
1
2
output = *input1;
output = *[input0]input1;

input0的ID是address space的ID由PCode的编译器生成,input1 是对应address space的地址偏移,从对应地址空间的地址偏移处取出值放到output的位置,output的size可以大于input的size,但是output的size必须是inputsize的整数倍
input0的size必须和input1的size相等

STORE

和LOAD相似

BRANCH

raw PCode 提取

1
2
3
4
5
6
7
8
9
10
11
12
13
def dump_raw_pcode(func):
func_body = func.getBody()
listing = currentProgram.getListing()
opiter = listing.getInstructions(func_body, True)
while opiter.hasNext():
op = opiter.next()
raw_pcode = op.getPcode()
print("{}".format(op))
for entry in raw_pcode:
print(" {}".format(entry))

func = getGlobalFunctions("main")[0] # assumes only one function named `main`
dump_raw_pcode(func) # dump raw pcode as strings

refined PCode 提取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from ghidra.util.task import ConsoleTaskMonitor
from ghidra.app.decompiler import DecompileOptions, DecompInterface

# == helper functions =============================================================================
def get_high_function(func):
options = DecompileOptions()
monitor = ConsoleTaskMonitor()
ifc = DecompInterface()
ifc.setOptions(options)
ifc.openProgram(getCurrentProgram())
# Setting a simplification style will strip useful `indirect` information.
# Please don't use this unless you know why you're using it.
#ifc.setSimplificationStyle("normalize")
res = ifc.decompileFunction(func, 60, monitor)
high = res.getHighFunction()
return high

def dump_refined_pcode(func, high_func):
opiter = high_func.getPcodeOps()
while opiter.hasNext():
op = opiter.next()
print("{}".format(op.toString()))

# == run examples =================================================================================
func = getGlobalFunctions("main")[0] # assumes only one function named `main`
hf = get_high_function(func) # we need a high function from the decompiler
dump_refined_pcode(func, hf) # dump straight refined pcode as strings