https://github.com/avast/retdec
LLVM IR 指令序列
LLVM IR 是 LLVM Intermediate Representation(中间形似),它是一种 low-level languange,是一个像RISC的指令集
LLVM是一组编译器和工具链技术,可用于开发任何编程语言的前端和任何指令集架构的后端。LLVM 是围绕独立于语言的中间表示(IR) 设计的,它用作可移植的高级汇编语言,可以通过多次传递的各种转换进行优化。
LLVM 是用C++编写的,专为编译时、链接时、运行时和“空闲时”优化而设计。LLVM 的语言无关设计最初是为C和 C++ 实现的,此后产生了各种各样的前端:具有使用 LLVM 的编译器(或不直接使用 LLVM 但可以生成作为 LLVM IR 的编译程序)的语言包括ActionScript,Ada , C# , [6] [7] [8] Common Lisp , PicoLisp , Crystal , CUDA , D , Delphi ,Dylan , Forth , [9] Fortran , Free Basic , Free Pascal , Graphical G , [10] Halide , Haskell , Java 字节码, Julia , Kotlin , Lua , Objective-C , OpenCL , [11] PostgreSQL的 SQL 和 PLpgSQL, [12] Ruby,[13] Rust,Scala,[14] Swift,XC,[15] Xojo和Zig.
然而可以很表达high-level的ideas,就是说high-level languange可以很干净地map到LLVM IR
LLVM 可以提供完整编译器系统的中间层,从编译器获取中间表示(IR) 代码并发出优化的 IR。然后可以将这个新的 IR 转换并链接成目标平台的机器相关汇编语言代码。LLVM 可以接受来自GNU Compiler Collection (GCC)工具链的 IR ,允许它与为该项目编写的大量现有编译器前端一起使用。
LLVM 还可以在编译时或链接时 生成可重定位的机器代码,甚至在运行时生成二进制机器代码。
LLVM 支持独立于语言的指令集和类型系统。[5]每条指令都采用静态单一赋值形式(SSA),这意味着每个变量(称为类型化寄存器)被赋值一次,然后被冻结。这有助于简化变量之间依赖关系的分析。LLVM 允许静态编译代码,就像在传统的 GCC 系统下一样,或者通过即时编译(JIT) 从 IR 后期编译为机器代码,类似于Java。类型系统由整数或浮点数等基本类型和五种派生类型组成:指针、数组、向量、结构和函数。具体语言中的类型构造可以通过在 LLVM 中组合这些基本类型来表示。例如,C++ 中的类可以由结构、函数和函数指针数组的混合表示。
LLVM JIT 编译器可以在运行时优化程序中不需要的静态分支,因此在程序有很多选项的情况下对部分评估很有用,其中大部分可以很容易地在特定环境中被确定为不需要。此功能用于Mac OS X Leopard (v10.5) 的OpenGL管道,以提供对缺少的硬件功能的支持。[26]
OpenGL 堆栈中的图形代码可以保留在中间表示中,然后在目标机器上运行时进行编译。在具有高端图形处理单元(GPU) 的系统上,生成的代码仍然非常精简,只需极少更改即可将指令传递给 GPU。在具有低端 GPU 的系统上,LLVM 将编译在本地中央处理单元(CPU) 上运行的可选程序,这些程序模拟 GPU 无法在内部运行的指令。LLVM 提高了使用英特尔 GMA芯片组的低端机器的性能。在Gallium3D LLVMpipe下开发了一个类似的系统,并将其合并到GNOME shell 中,使其无需加载适当的 3D 硬件驱动程序即可运行。[27]
对于已编译程序的运行时性能,2011 年 GCC 以前平均比 LLVM 高出 10%。[28] [29] 2013 年的新结果表明 LLVM 现在在这方面已经赶上了 GCC,现在正在编译二进制文件性能大致相同。
这使得我们可以高效地进行代码优化
目标
反编译器不限于任何特定的目标架构、操作系统或可执行文件格式:
- 支持的文件格式:ELF、PE、Mach-O、COFF、AR(存档)、Intel HEX 和原始机器代码
- 支持的架构:
- 32-bit: Intel x86, ARM, MIPS, PIC32, and PowerPC
- 64-bit: x86-64, ARM64 (AArch64)
特征
- 具有详细信息的可执行文件的静态分析。
- 编译器和打包器检测。
- 加载和指令解码。
- 基于签名的静态链接库代码删除。
- 调试信息(DWARF、PDB)的提取和利用。
- 重构教学用语。
- C++ 类层次结构(RTTI、vtables)的检测和重建。
- 从 C++ 二进制文件(GCC、MSVC、Borland)中去除符号。
- 重构函数、类型和高级构造。
- 集成拆卸器。
- 以两种高级语言输出:C 和类似 Python 的语言。
- 生成调用图、控制流图和各种统计信息。
输出
生成的文件
输入文件的默认反编译(没有下面列出的任何特殊选项)input.exe
会生成以下输出文件:
input.exe.dsm
:自定义格式的反汇编输出。指令助记符采用默认的Capstone格式。input.exe.bc
: LLVM 位码格式的Core反编译部分的最终产物。input.exe.ll
: LLVM IR格式的 LLVM 位码的人类可读反汇编。input.exe.config.json
: 反编译过程产生的元数据。input.exe.c
: 反编译的 C 代码。这是主要输出。
只需在输入文件名中添加适当的后缀即可生成输出文件名:<input_file>.{dsm, bc, ll, config.json, c}
.
输出生成选项
以下反编译脚本 ( retdec-decompiler.py
) 选项控制输出生成过程:
-o FILE, --output FILE
如果指定,主反编译输出将存储到FILE
而不是<input_file>.c
. 此外,FILE
(没有潜在的后缀)用作生成其他输出文件名的基本名称:<FILE_w/o_suffix>.{dsm, bc, ll, config.json}
.-l LANGUAGE, --target-language LANGUAGE
设置要生成的目标高级语言。目前,仅支持 C 语言。注意:曾经有一个py
选项可以生成伪 Python 输出。它被删除是因为它几乎没有经过测试,用户很少使用,而且它使添加新功能变得更加困难和缓慢。-f OUTPUT_FORMAT, --output-format OUTPUT_FORMAT
默认plain
选项将主要的反编译输出直接作为高级语言源代码生成到关联的文本文件中(例如,将 C 源代码生成到*.c
文件中)。json
和选项将json-human
输出源代码生成为词法分析器标记流以及附加信息。有关详细的格式说明,请参阅下面的部分。主反编译输出文件的后缀更改为.json
.--cleanup
删除在反编译期间创建的临时文件。仅保留主反编译输出文件和反汇编文件。--stop-after
在给定工具之后停止反编译过程。自然的结果是只创建到该点生成的输出文件。
JSON输出文件格式
解析高级语言源代码并非易事。但是,第 3 方逆向工具可能需要这样做才能利用 RetDec 的输出。此外,可能需要额外的元信息来增强用户体验或自动分析——在传统的高级语言源代码中难以传达的信息。
- RetDec IDA 插件中的语法高亮显示。
- RetDec IDA plugin和RetDec Radare2 plugin中反编译的输出行/元素与原始反汇编指令之间的关系。
为了使这些应用程序成为可能,RetDec 提供了一个选项(参见上一节),以将其输出生成为 JSON 格式的带注释的词法分析器标记序列。可以生成两种 JSON 风格:
json-human
包含适当缩进(选项)的人类可读 JSON 。- 没有任何缩进的机器可读 JSON(选项
json
)。
为了用一个解析器实现来解析这两种风格,它们都使用相同的键和值。
当前的 JSON 模式如下:
{
"language" : "<language_ID>",
"tokens" :
[
{
"addr" : "<address_format>",
"kind" : "<kind_values>",
"val" : "<value>"
},
// ...
]
}
- 所有值都是字符串数据类型。
language
key 标识被标记化的高级语言。可能的<language_ID>
值:
- 源代码被序列化为数组中的令牌对象
tokens
数组。 - 令牌对象包含以下条目:
addr
与带有前缀十六进制格式的键和值的令牌关联的程序集地址(例如0x8048577
)。val
包含源代码中显示的实际令牌字符串的值。kind
带有键和以下可能值的令牌类型:-
Value Description Example(s) nl
New line. "\n"
ws
Any consecutive sequence of white spaces. " "
punc
A single punctuation character. "("
")"
"{"
"}"
"["
"]"
";"
op
Operator. "=="
"-"
"+"
"*"
"->"
"."
i_var
Global/Local variable identifier. "global_var"
i_mem
Structure/Class member identifier. "entry_1"
i_lab
Label identifier. "label_0x1234"
i_fnc
Function identifier. "ackermann"
i_arg
Function argument identifier. "argv"
keyw
High-level-language keyword. "while"
type
Data type. "uint64_t"
preproc
Preprocessor directive. "#include"
inc
String used in an #include
preprocessor directive. Including<>
."<stdlib.h>"
l_bool
Boolean literal. "true"
"false"
l_int
Integer literal. Including potential prefixes and suffixes. "123"
"0x213A"
"1234567890123456789LL"
l_fp
Floating point literal. Including potential prefixes and suffixes. "3.14"
"123.456e-67"
l_str
String literal. Including properly escaped ""
."\"ackerman( %d , %d ) = %d\\n\""
l_sym
Symbolic literal. "UNDEFINED"
l_ptr
Pointer literal. "NULL"
cmnt
Comment. Including delimiter like //
or/* */
."// Detected compiler/packer: gcc (4.7.2)"
-
- 令牌
kind
和val
条目必须始终一起使用,即一个永远不会在没有另一个的情况下使用。 - 连接所有条目会生成与使用format 选项
val
生成的字符串完全相同的字符串。plain
- 地址条目不必存在于每个令牌对象中。如果丢失,则令牌与最后一个地址条目相关联。
- 地址条目可以在没有
kind
和val
条目的情况下使用。在这种情况下,它有效地为即将到来的令牌设置关联地址。-
{ "addr" : "0x80498e8" }, // sequence of tokens associated with address 0x80498e8 { "addr" : "0x80498f4" }, // sequence of tokens associated with address 0x80498f4
-
- 并非所有令牌都必须(或可以)与程序集地址相关联。此类令牌与空地址相关联:
-
{ "addr" : "0x80498e8" }, // sequence of tokens associated with address 0x80498e8 { "addr" : "" }, // sequence of tokens unassociated with any address
-
- 令牌到地址的关联本质上不是基于行的。例如,以下行:
-
printf("ackerman( %d , %d ) = %d\n", x, y, result);
可以分解为几个部分,每个部分与不同的汇编指令相关联:
printf(
– 与CALL
指令相关。"ackerman( %d , %d ) = %d\n"
– 与LOAD
加载调用参数的指令相关。, x
– 与LOAD
加载调用参数的指令相关。, y
– 与LOAD
加载调用参数的指令相关。, result
– 与LOAD
加载调用参数的指令相关。);
– 与CALL
指令相关。
-
文件概述
库(应用程序)
ar-extractor
– 用于从档案中提取目标文件的库(基于 LLVM)。bin2llvmir
– 用于将二进制文件转换为 LLVM IR 模块的 LLVM 通行证库。capstone2llvmir
– LLVM IR 翻译库的二进制指令。common
– 实现所有其他模块中常用的对象的库。config
– 用于表示和管理 RetDec 配置数据库的库。cpdetect
– 用于二进制文件中的编译器和打包器检测的库。crypto
– 密码功能的集合。ctypes
– 用于表示 C 函数数据类型的库。ctypesparser
– 用于将 C 函数数据类型从 JSON 文件解析为ctypes
表示形式的库。debugformat
– 用于统一表示 DWARF 和 PDB 调试信息的库。demangler
– 能够处理由 GCC/Clang、Microsoft Visual C++ 和 Borland C++ 编译器生成的名称的解构库。fileformat
– 用于解析和统一表示各种目标文件格式的库。目前支持以下格式:COFF、ELF、Intel HEX、Mach-O、PE、原始数据。llvm-support
– 一组 LLVM 相关的实用功能。llvmir-emul
– 用于单元测试的 LLVM IR 仿真库。llvmir2hll
– 用于将 LLVM IR 模块转换为高级源代码(C,类 Python 语言)的库。loader
– 用于统一表示加载到内存的二进制文件的库。支持与文件格式相同的格式。macho-extractor
– 用于从胖 Mach-O 二进制文件中提取常规 Mach-O 二进制文件的库(基于 LLVM)。patterngen
– 二进制模式提取器库。pdbparser
– Microsoft PDB 文件解析器库。pelib
– Microsoft 便携式可执行文件操作库。retdec
–主要的反编译库。rtti-finder
– 用于在二进制文件中查找 GCC/Clang 和 MSVC RTTI结构的库。serdes
– 用于序列化和反序列化各种 RetDec 类的库。stacofin
– 静态代码查找器库。unpacker
– 拆包功能的集合。utils
– 通用 C++ 实用程序库。yaracpp
– YARA 的 C++ 包装器。
工具(应用程序)
ar-extractortool
– ar-extractor 库的前端(安装为retdec-ar-extractor
)。bin2llvmirtool
– 库的前端bin2llvmir
(安装为retdec-bin2llvmir
)。bin2pat
– 从二进制文件生成模式(安装为retdec-bin2pat
)。capstone2llvmirtool
– 库的前端capstone2llvmir
(安装为retdec-capstone2llvmir
)。configtool
– 库的前端config
(安装为retdec-config
)。demanglertool
– 库的前端demangler
(安装为retdec-demangler
)。fileinfo
–主要的二进制分析工具。fileformat
支持与(安装为)相同的格式retdec-fileinfo
。getsig
– 从二进制文件(安装为retdec-getsig
)生成签名。idr2pat
– 从 IDR 知识库中提取模式的工具(安装为retdec-idr2pat
)。llvmir2hlltool
– 库的前端llvmir2hll
(安装为retdec-llvmir2hll
)。macho-extractortool
– 库的前端macho-extractor
(安装为retdec-macho-extractor
)。pat2yara
– 将模式处理为 YARA 签名的工具(安装为retdec-pat2yara
)。retdectool
– 库的前端retdec
(安装为retdec
)。stacofintool
– 库的前端stacofin
(安装为retdec-stacofin
)。unpackertool
– 基于插件的解包器(安装为retdec-unpacker
)。
脚本
retdec-decompiler.py
–将它们绑定在一起的主要反编译脚本。这是用于完全二进制到 C 反编译的工具。- 支持使用的脚本
retdec-decompiler.py
:retdec-config.py
– 反编译器的配置文件。retdec-archive-decompiler.py
– 反编译给定 AR 档案中的对象。retdec-fileinfo.py
– Fileinfo 工具包装器。retdec-signature-from-library-creator.py
– 从给定的库中提取函数签名。retdec-unpacker.py
– 尝试使用任何支持的解包器解包给定的可执行文件。retdec-utils.py
– Python 实用程序的集合。
retdec-tests-runner.py
– 运行单元测试目录中的所有测试。type_extractor
– 生成类型信息(仅供内部使用)