P-Code:语法、语义与改进建议
1. P-Code的问题点分析
P-Code在实际应用中存在一些问题,下面我们将详细分析。
-
间接调用和返回
:文档中对间接调用和返回的描述存在不准确之处。间接调用并不等同于
BRANCHIND
,而返回指令在高P-Code中可以持有返回值,且指令中不包含返回地址。
-
Fall-through机制
:Ghidra输出的P-Code在Fall-through方面表现不一致,没有固定模式。正常情况下控制流应跳转到输出列表中的下一个块,但实际可能跳转到更早或更晚的块,且块地址缺乏逻辑性。因此,我们建议每个块都以分支指令结束。
-
用户定义指令
:P-Code允许使用“USERDEFINED”指令,用于描述现有P-Code指令无法表达的复杂行为。由于其语义由用户提供,本文暂不考虑。
-
间接指令
:间接指令表示为
out = in0 ← in1
,其含义是
in0
的值应赋给
out
,但可能受
in1
所指指令的间接影响。例如调用外部函数时,无法确定
out
的值。
2. P-Code语法
P-Code程序的语法基于文档中的表示方法,对于一些未定义语法的指令,我们自行选择了合适的表示方式,并根据设计需求进行了一些小的修改。
-
程序模型
:将程序建模为从地址
a
到代码块
b
的映射。
-
输入输出
:由四种不同的varnode建模,分别是寄存器、内存、常量和变量。对于寄存器,使用地址而非名称,例如
(0x18,8)
、
(0x18,4)
和
(0x19,1)
分别对应
RBX
、
EBX
和
BH
。
-
P-Code块
:必须非空,可以有零个或多个指令
i
,并以终止指令
t
结束。这与文档和Ghidra生成的P-Code不同,但有助于解决Fall-through问题。
-
指令类型
:
-
基本操作
:
out = (o | s)
表示将基本操作
o
的结果或函数调用
s
的返回值赋给输出
out
。部分数据操作依赖于输出的大小。
-
函数调用
:表示为
call [in0] in1 ... inn
,
in0
为常量时是直接调用,为寄存器、内存位置或变量时是间接调用。这与文档一致,但与Ghidra生成的P-Code不同。
-
基本操作
:大多数是简单的二元或一元操作,这里讨论两个非标准操作。
-
phi-node
:文档未列出其语法,我们自行定义并增强,使其包含保护值选择的地址。
-
间接操作
:
(in0 ← in1)
表示
in0
的值可能未受影响,也可能被
in1
所指指令改变,无法确定最终值。
-
终止指令
:文档未单独列出,但分离它们有助于构建语义。
-
条件分支
:Ghidra生成的P-Code在条件分支上不一致,我们用自定义指令替代,以便使用Ghidra API获取正确地址。
-
返回指令
:包含偏移量,但实际执行返回时并不使用,为完整性保留该参数。
下面是P-Code语法的相关流程:
graph TD;
A[程序] --> B[地址a到代码块b的映射];
B --> C[代码块b];
C --> D[指令i];
C --> E[终止指令t];
D --> F[基本操作o];
D --> G[函数调用s];
F --> H[赋值给输出out];
G --> I[直接或间接调用];
E --> J[条件分支];
E --> K[返回指令];
3. P-Code语义
为P-Code定义语义并非易事,基于实验和观察,我们对P-Code程序做了以下假设:
-
局部变量不重叠
:局部变量在局部变量地址空间中不重叠,即占用不同的内存位置。
-
无全局变量
:所有声明的变量都是局部的,尽管文档未禁止全局变量地址空间,但实验中未观察到这种情况。
-
常量调用和分支
:直接调用和直接分支以常量varnode作为参数,其他情况视为间接调用和分支。
-
终止指令在块末尾
:分支和返回等终止指令仅出现在块的末尾。
语义评估所需的对象包括状态、内存、寄存器和变量映射:
|对象|描述|
|----|----|
|内存映射M|接受地址
a
和大小
l
,返回常量varnode
(Cc, l)
|
|寄存器映射R|接受寄存器地址
r
和大小
l
,返回常量varnode
(Cc, l)
,使用特殊寄存器
Ret
、
Prev
和
Cur
跟踪相关信息|
|变量映射V|接受变量标识符
v
,返回常量varnode
(Cc, l)
|
varnode的评估和状态更新通过特定的判断规则进行:
-
varnode评估
:判断形式为
σ, in ↓val
,根据varnode类型从内存、寄存器映射或变量映射中获取值。
-
状态更新
:判断形式为
σ, out, in ↑σ′
,根据目标类型选择正确的规则更新状态,确保目标和源的大小相等。
块评估使用判断
p, σ, b −→b σ′
,通过不同规则进行:
-
B-Seq
:先评估指令
i
,再用结果状态评估剩余块。
-
B-Term
:评估以终止指令结尾的块。
终止指令的语义如下:
-
T-Branch
:评估varnode
in
,查找下一个块并进行评估。条件分支中,若条件返回
1
则进入真分支,否则为假分支。
-
T-Return
:使用
Ret
寄存器传递返回值,通过内存更新函数更新状态。
部分指令的语义:
-
I-Assign
:使用操作语义评估
o
,将结果赋值给
out
,根据
out
的类型更新状态。
-
I-AssCall
:依赖调用语义执行调用,用结果更新
out
并返回最终状态。
-
I-Store
:评估目标,转换为地址类型的varnode并更新。
-
I-Load
:评估输入,将其视为内存地址,从内存中查找最终值。
调用语义较为特殊,参数通过寄存器传递,但被调用函数仍从寄存器中获取参数,具体方式取决于原始调用约定。以
AMD64 - ABI
调用约定为例,调用规则如下:
graph TD;
A[程序p,状态σ,调用语句s] --> B[解析函数地址];
B --> C[评估参数];
C --> D[将参数分配到寄存器];
D --> E[查找并评估被调用函数块];
E --> F[忽略局部变量映射,清理寄存器];
F --> G[从寄存器获取返回值];
G --> H[返回新状态σ′和值val];
操作评估规则部分示例:
-
O-Copy
:评估varnode并返回其值。
-
O-phi
:找到与最后一个块地址相等的
ai
,评估所选输入并返回值。
-
O-Indirect-val和-ND规则
:处理间接指令,有两种情况,值未受影响则返回,否则可能返回任意值。
4. P-Code解释器
为验证语义的可执行性,我们用Haskell构建了P-Code解释器,其源代码公开可用,包括解析器、类型定义和解释器本身。同时,我们创建了一个Ghidra脚本用于导出P-Code。
在构建解释器过程中遇到了一些问题:
-
语法和语义差异
:我们假设的语法和语义与Ghidra实际输出存在差异,如调用和分支指令的表示方式不同。
-
信息不足
:Ghidra输出的P-Code在
MULTIEQUAL
、条件分支指令和Fall-through机制方面信息不足,我们通过增强导出脚本来补充。
-
非确定性
:实际程序中的
INDIRECT
指令会引入非确定性,执行可能返回多个结果,解释器将其视为确定性指令。
5. 对Ghidra和P-Code的改进建议
基于上述研究,我们对Ghidra、P-Code及其文档提出以下改进建议:
-
P-Code
:当前的phi-node(
MULTIEQUAL
)不完整,建议采用包含前一个块地址和关联值的定义。
-
Ghidra/SLEIGH
:
-
CBRANCH
:Ghidra在
CBRANCH
指令的目标地址上可能出错,我们通过Ghidra API验证了其存在的问题,建议修复该bug。
-
Fall-through
:Ghidra输出的P-Code在Fall-through方面不符合标准,建议改进以确保控制流跳转的一致性。
综上所述,通过对P-Code的语法、语义进行深入分析,我们发现了现有实现中的问题,并提出了改进建议。同时,构建的解释器验证了语义的可执行性,但实际应用中还需解决非确定性等问题。未来,随着研究的深入,P-Code的性能和可靠性有望得到进一步提升。
P-Code:语法、语义与改进建议
6. 改进建议的详细分析与影响
我们提出的改进建议将对P-Code和Ghidra的使用和性能产生重要影响,下面详细分析这些建议的具体作用。
6.1 P-Code中phi-node的改进
目前P-Code中的phi-node(MULTIEQUAL)仅包含替代值列表,缺乏保护值选择的控制流地址。我们建议采用包含前一个块地址和关联值的定义,这将带来以下好处:
-
增强语义表达
:使phi-node能够更准确地表示控制流信息,在构建语义时更加清晰明确,减少歧义。
-
便于分析
:对于代码分析工具来说,能够更方便地理解和处理控制流的选择,提高分析的准确性和效率。
例如,在复杂的控制流图中,准确的phi-node定义可以帮助分析工具更好地跟踪变量的赋值和使用情况。
6.2 Ghidra/SLEIGH的改进
-
CBRANCH的改进
:Ghidra在CBRANCH指令的目标地址上可能出错,导致条件分支的目标不准确。通过使用Ghidra API获取正确地址并修复这个问题,将带来以下改进:
- 提高准确性 :确保条件分支能够正确跳转到目标地址,避免程序执行出现错误。
- 增强可靠性 :对于依赖条件分支的程序逻辑,修复该问题将提高整个程序的可靠性。
以下是CBRANCH改进的流程:
graph TD;
A[原始CBRANCH指令] --> B[使用Ghidra API获取正确地址];
B --> C[验证地址准确性];
C --> D[修复CBRANCH指令];
D --> E[生成正确的P-Code];
-
Fall-through的改进
:Ghidra输出的P-Code在Fall-through方面不符合标准,控制流跳转缺乏一致性。改进Fall-through机制将带来以下好处:
- 统一标准 :使Fall-through的行为符合常见的汇编语言标准,即控制流跳转到下一个指令或块。
- 简化分析 :对于代码分析和调试工具来说,统一的Fall-through机制将更容易理解和处理控制流。
7. 实际应用中的考虑
在实际应用中,使用P-Code和Ghidra时需要考虑以下几个方面:
-
非确定性问题
:如前所述,P-Code中的INDIRECT指令会引入非确定性,执行可能返回多个结果。在实际应用中,需要谨慎处理这些指令,例如在解释器中可以将其视为确定性指令,但这可能会影响结果的准确性。
-
与现有系统的兼容性
:对P-Code和Ghidra进行改进时,需要考虑与现有系统的兼容性。例如,新的语法和语义定义可能需要对现有的代码分析工具进行相应的修改。
-
性能影响
:改进措施可能会对性能产生一定的影响,例如修复CBRANCH和Fall-through问题可能会增加一定的处理开销。在实际应用中,需要权衡改进带来的好处和性能损失。
8. 总结与展望
通过对P-Code的语法、语义进行深入研究,我们发现了现有实现中存在的问题,并提出了相应的改进建议。构建的P-Code解释器验证了语义的可执行性,但在实际应用中还需要解决非确定性等问题。
未来,我们可以进一步探索以下方向:
-
扩展语义
:将P-Code的语义扩展到支持更多的架构和编程模型,例如哈佛架构。
-
优化解释器
:对P-Code解释器进行优化,提高其执行效率和处理非确定性的能力。
-
集成到工具链
:将改进后的P-Code和Ghidra集成到更广泛的代码分析和调试工具链中,提高软件开发的效率和质量。
总之,P-Code作为一种中间表示语言,在代码分析和逆向工程中具有重要的应用价值。通过不断改进和完善P-Code的语法、语义和工具支持,我们可以更好地利用它来解决实际问题。
| 改进方面 | 具体改进内容 | 预期效果 |
|---|---|---|
| phi-node | 采用包含前一个块地址和关联值的定义 | 增强语义表达,便于分析 |
| CBRANCH | 使用Ghidra API获取正确地址并修复 | 提高准确性,增强可靠性 |
| Fall-through | 改进机制使其符合标准 | 统一标准,简化分析 |
P-Code语法语义与改进建议
超级会员免费看
3166

被折叠的 条评论
为什么被折叠?



