angr/pyvex

GitHub: angr/pyvex

PyVEX 提供 Valgrind VEX IR 的 Python 绑定,将多种架构的机器代码转换为统一的中间表示,便于跨平台二进制程序分析。

Stars: 376 | Forks: 130

# PyVEX [![最新发布](https://img.shields.io/pypi/v/pyvex.svg)](https://pypi.python.org/pypi/pyvex/) [![Python 版本](https://img.shields.io/pypi/pyversions/pyvex)](https://pypi.python.org/pypi/pyvex/) [![PyPI 统计数据](https://img.shields.io/pypi/dm/pyvex.svg)](https://pypistats.org/packages/pyvex) [![许可证](https://img.shields.io/github/license/angr/pyvex.svg)](https://github.com/angr/pyvex/blob/master/LICENSE) PyVEX 是 VEX IR 的 Python 绑定。 ## 项目链接 项目仓库:https://github.com/angr/pyvex 文档:https://api.angr.io/projects/pyvex/en/latest/ ## 安装 PyVEX PyVEX 可以通过 pip 安装: ``` pip install pyvex ``` ## 使用 PyVEX ``` import pyvex import archinfo # 将位于 0x400400 的 AMD64 basic block (全为 nops) 转换为 VEX irsb = pyvex.lift(b"\x90\x90\x90\x90\x90", 0x400400, archinfo.ArchAMD64()) # 格式化打印 basic block irsb.pp() # 这是 basic block 末尾无条件 exit 的 jump target 的 IR Expression print(irsb.next) # 这是无条件 exit 的 type (例如 call、ret、syscall 等) print(irsb.jumpkind) # 你也可以对其进行格式化打印 irsb.next.pp() # 遍历每个 statement 并打印所有 statement for stmt in irsb.statements: stmt.pp() # 格式化打印表示数据的 IR expression,以及每个 store statement 写入的该 IR expression 的 *type* import pyvex for stmt in irsb.statements: if isinstance(stmt, pyvex.IRStmt.Store): print("Data:", end="") stmt.data.pp() print("") print("Type:", end="") print(stmt.data.result_type) print("") # 格式化打印 basic block 中每个条件 exit 的 condition 和 jump target for stmt in irsb.statements: if isinstance(stmt, pyvex.IRStmt.Exit): print("Condition:", end="") stmt.guard.pp() print("") print("Target:", end="") stmt.dst.pp() print("") # 这些是 IRSB 中每个 temp 的 type print(irsb.tyenv.types) # 这是获取 temp 0 的 type 的一种方法 print(irsb.tyenv.types[0]) ``` 请记住,这只是基本块的*语法*表示。也就是说,它会告诉你该块的含义,但你没有任何上下文信息来确定,例如,store 指令写入的*实际*数据是什么。 ## VEX 中间表示 为了处理各种差异巨大的架构,在中间表示(IR)上进行分析是非常有用的。 在处理不同架构时,IR 抽象掉了几个架构差异,从而允许在所有架构上运行单一的分析: - **寄存器名称。** 不同架构之间寄存器的数量和名称各不相同,但现代 CPU 设计遵循一个共同的主题:每个 CPU 包含几个通用寄存器、一个用于保存栈指针的寄存器、一组用于存储条件标志的寄存器等等。IR 为不同平台上的寄存器提供了一致的、抽象的接口。具体来说,VEX 将寄存器建模为一个独立的内存空间,并使用整数偏移量(例如,AMD64 的 `rax` 存储在此内存空间的地址 16 处)。 - **内存访问。** 不同的架构以不同的方式访问内存。例如,ARM 可以在小端序和大端序模式下访问内存。IR 必须抽象掉这些差异。 - **内存分段。** 某些架构(如 x86)通过使用特殊的段寄存器来支持内存分段。IR 能够理解此类内存访问机制。 - **指令副作用。** 大多数指令都有副作用。例如,ARM 上 Thumb 模式下的大多数操作会更新条件标志,并且栈的 push/pop 指令会更新栈指针。在分析中以*临时*(ad hoc)的方式跟踪这些副作用是不现实的,因此 IR 使得这些影响变得明确。 IR 有很多种选择。我们使用 VEX,因为将二进制代码提升(uplifting)到 VEX 的支持非常好。 VEX 是一种独立于架构、无副作用的多种目标机器语言表示。 它将机器代码抽象为一种旨在使程序分析更轻松的表示。 这种表示包含五大类对象: - **表达式。** IR 表达式表示一个计算出的或常量的值。这包括内存加载、寄存器读取和算术运算的结果。 - **操作。** IR 操作描述了对 IR 表达式的*修改*。这包括整数算术、浮点算术、位运算等等。应用于 IR 表达式的 IR 操作会产生一个 IR 表达式作为结果。 - **临时变量。** VEX 使用临时变量作为内部寄存器:IR 表达式在使用前被存储在临时变量中。可以使用 IR 表达式检索临时变量的内容。这些临时变量是有编号的,从 `t0` 开始。这些临时变量是强类型的(即“64 位整数”或“32 位浮点数”)。 - **语句。** IR 语句对目标机器状态的更改进行建模,例如内存存储和寄存器写入的效果。IR 语句会根据需要使用 IR 表达式来表示值。例如,一个内存存储 *IR 语句* 会使用一个 *IR 表达式* 来表示写入的目标地址,并使用另一个 *IR 表达式* 来表示内容。 - **基本块。** IR 基本块是 IR 语句的集合,表示目标架构中的一个扩展基本块(称为“IR Super Block”或“IRSB”)。一个块可以包含多个出口。对于从基本块中间进行的条件出口,会使用一个特殊的 *Exit* IR 语句。IR 表达式用于表示块末尾无条件出口的目标。 实际上,VEX 仓库中的 `libvex_ir.h` 文件 (https://github.com/angr/vex/blob/dev/pub/libvex_ir.h) 对 VEX IR 有非常详细的文档说明。为了方便读者,我们将详细说明一些你可能会经常与之交互的 VEX 部分。首先,以下是一些 IR 表达式: | IR 表达式 | 求值结果 | VEX 输出示例 | | ------------- | --------------- | ------- | | 常量 | 一个常数值。 | 0x4:I32 | | 读取临时变量 | 存储在 VEX 临时变量中的值。 | RdTmp(t10) | | 获取寄存器 | 存储在寄存器中的值。 | GET:I32(16) | | 加载内存 | 存储在某个内存地址中的值,该地址由另一个 IR 表达式指定。 | LDle:I32 / LDbe:I64 | | 操作 | 应用于指定 IR 表达式参数的特定 IR 操作的结果。 | Add32 | | If-Then-Else | 如果给定的 IR 表达式求值为 0,则返回一个 IR 表达式。否则,返回另一个。 | ITE | | 辅助函数 | VEX 使用 C 辅助函数执行某些操作,例如计算某些架构的条件标志寄存器。这些函数返回 IR 表达式。 | function\_name() | 然后,这些表达式反过来被用于 IR 语句中。以下是一些常见的语句: | IR 语句 | 含义 | VEX 输出示例 | | ------------ | ------- | ------------------ | 写入临时变量 | 将给定的 IR 表达式的值设置给一个 VEX 临时变量。 | WrTmp(t1) = (IR 表达式) | 放置寄存器 | 使用给定 IR 表达式的值更新寄存器。 | PUT(16) = (IR 表达式) | 存储内存 | 使用一个值更新内存中的某个位置,位置和值均由 IR 表达式指定。 | STle(0x1000) = (IR 表达式) | Exit | 基本块的条件出口,跳转目标由 IR 表达式指定。条件也由一个 IR 表达式指定。 | if (条件) goto (Boring) 0x4000A00:I32 | 下面展示了一个在 ARM 上进行 IR 转换的示例。在这个示例中,减法操作被转换为一个包含 5 条 IR 语句的单一 IR 基本块,每条语句都至少包含一个 IR 表达式(尽管在实际情况中,一个 IR 块通常由多条指令组成)。寄存器名称被转换为提供给 *GET* 表达式和 *PUT* 语句的数字索引。 敏锐的读者会观察到,实际的减法操作是由该块的前 4 条 IR 语句建模的,而程序计数器递增以指向下一条指令(在此示例中,位于 `0x59FC8`)则是通过最后一条语句建模的。 以下的 ARM 指令: ``` subs R2, R2, #8 ``` 会被转换为如下的 VEX IR: ``` t0 = GET:I32(16) t1 = 0x8:I32 t3 = Sub32(t0,t1) PUT(16) = t3 PUT(68) = 0x59FC8:I32 ``` 非常酷! ## 引用 PyVEX 如果你在学术研究中使用了 PyVEX,请引用为其开发而撰写的论文: ``` @article{shoshitaishvili2015firmalice, title={Firmalice - Automatic Detection of Authentication Bypass Vulnerabilities in Binary Firmware}, author={Shoshitaishvili, Yan and Wang, Ruoyu and Hauser, Christophe and Kruegel, Christopher and Vigna, Giovanni}, booktitle={NDSS}, year={2015} } ```
标签:Python, 中间语言, 二进制分析, 云安全监控, 云安全运维, 云资产清单, 可配置连接, 无后门, 程序分析, 逆向工具, 逆向工程, 静态分析