反编译的目的是将编译后的代码恢复为人类可读的源代码,但在名称和结构等细节上遇到了困难。 大型语言模型(大语言模型)显示出编程任务的前景,激励其应用程序进行反编译。 但目前还没有开源的大语言模型可供反编译。 而且,现有的反编译评估系统主要考虑token级别的准确性,很大程度上忽略了代码的可执行性,而这是任何程序最重要的特征。 因此,我们发布了第一个开放获取的反编译大语言模型,范围从1B到33B,在40亿个C源代码和相应的汇编代码上进行了预训练。 开源的大语言模型可以作为该领域进一步发展的基准。 为了确保程序评估的实用性,我们引入了 Decompile-Eval,这是第一个考虑反编译可重编译性和可重执行性的数据集。 该基准强调了从程序语义角度评估反编译模型的重要性。 实验表明,我们的LLM4Decompile能够准确反编译21%的汇编代码,比GPT-4提高了50%。 我们的代码、数据集和模型发布于 LLM4Decompile。
反编译是将已编译的机器代码或字节码转换回高级编程语言的过程。 当源代码无法访问时,通常会这样做来分析软件的工作原理 Brumley 等人 (2013); Katz 等人 (2018);胡赛尼和多兰-加维特 (2022);徐等人 (2023); Armengol-Estapé 等人 (2023);江等人 (2023);黄等人(2023)。 目前已经开发了许多反编译工具,例如 Ghidra Ghidra (2024) 和 IDA Pro Hex-Rays (2024)。 尽管这些工具能够在特定场景下将二进制代码恢复为源代码,但它们通常无法生成易于人类阅读的代码。 反编译的固有困难在于无法完全重新创建源代码,尤其是诸如变量名 Lacomis 等人 (2019) 和主要结构 Wei 等人 (2007) 等更精细的细节,比如循环和条件语句,这些在编译过程中经常会丢失。
图1: 评估反编译的管道。
大语言模型(大语言模型)的最新进展促使研究人员将编程语言视为不同的语言系统,使用预训练代码大语言模型来执行各种编码任务Lippincott (2020); Rozière 等人 (2023);郭等人(2024)。 与传统技术相比,这些模型表现出了令人印象深刻的性能改进Zeng 等人 (2022); Xu 等人 (2022),这让我们想到了应用大语言模型来应对反编译挑战的可能性。 为了说明这一点,基于 Transformer 的模型,例如 Slade Armengol-Estapé 等人 (2023) 和 Nova Jiang 等人 (2023) 已经展示了使用语言模型将语言模型转化为模型的潜力。二进制代码返回源代码,在可读性和结构上更接近源代码。 然而,Slade 和 Nova 的模型范围在某种程度上受到 200M 和 1B 参数的限制,这可能会导致学习和泛化能力下降,而较大的模型通常通过利用其广泛的参数来处理和推广,从而在这些领域表现出显着的改进。整合更广泛的信息Rozière 等人 (2023); OpenAI(2023)。 此外,它们缺乏公众可用性限制了它们对促进该领域进一步进展的贡献。 此外,据我们所知,不存在用于评估和比较反编译技术的标准化基准数据集。 研究人员倾向于使用不同的数据集da Silva 等人 (2021);柯利牧羊犬等人 (2020); Tan 等人 (2017) 来评估他们的结果,使得直接比较变得困难。 因此,非常需要建立一个评估反编译性能的基准,这可以极大地促进反编译领域制定连贯且标准的评估标准。
因此,我们的目标是创建并发布第一个专门用于反编译的开源大语言模型,并通过构建第一个专注于可重编译性和可重执行性的反编译基准来评估其能力。 我们首先使用不同配置的 GCC Stallman 等人 (2003) 将来自 AnghaBench da Silva 等人 (2021) 的一百万个 C 代码样本编译为汇编代码,形成一个数据集40 亿个 Token 中的汇编源代码对。 然后,我们使用该数据集对 DeepSeek-Coder 模型 Guo 等人 (2024)(一个领先的代码大语言模型)进行微调。 然后根据HumanEval Chen 等人(2021)问题和测试样本构建评估基准Decompile-Eval。 具体来说,我们从两个角度制定评估:反编译后的代码是否可以重新编译成功,以及是否通过了测试用例中的所有断言。 图1展示了我们的反编译评估所涉及的步骤。 首先,源代码(表示为 s r c src src)由 GCC 编译器使用特定参数(例如优化级别)进行编译,以生成可执行二进制文件。 然后使用 objdump 工具将该二进制文件反汇编为汇编语言(表示为 a s m asm asm)。 随后对汇编指令进行反编译,以人类可读的格式重建源代码(表示为 s r c ′ src^{′} src′)。 为了评估反编译代码 ( s r c ′ src^{′} src′) 的质量,需要测试其使用原始 GCC 编译器重新编译的能力(可重新编译性)以及通过测试断言的功能(可重新执行性)。
在 Decompile-Eval 上,llm4decompile 模型在反编译二进制文件的能力方面展示了令人鼓舞的结果,令人印象深刻的是,90% 的反编译代码可以使用 GCC 编译器中的相同设置重新编译,这意味着对代码结构和语法的深入理解。 至于执行代码的能力,6B版本的反编译代码中有21%成功捕获了程序的语义并通过了所有测试用例。
总之,我们的贡献主要有两个方面:
将可执行二进制文件反转为其源代码形式的做法(称为反编译)已经研究了数十年。 传统反编译依赖于分析程序 Brumley 等人 (2013) 的控制和数据流,并采用模式匹配,如 Hex-Rays Ida pro Hex-Rays (2024) 等工具中所示 和 Ghidra Ghidra (2024)。 这些系统尝试识别程序控制流图 (CFG) 中与标准编程结构(例如条件语句或循环)相对应的模式。 然而,构建这些基于规则的系统可能具有挑战性,并且容易出错,因为规则创建起来很复杂,通常只部分覆盖 CFG,并且需要大量时间来开发 Armengol-Estapé 等人 (2023);江等人(2023)。 当面对已经优化的代码时,它们特别弱,这是商业编译软件的常见做法。 这种反编译过程的代码输出往往是汇编代码的类似源代码的表示,包括变量到寄存器的直接翻译、goto的使用以及其他低级操作,而不是原始的高级语言构造。 此输出虽然在功能上通常与原始代码相似,但难以理解,并且可能不够高效,无法重新编译 Liu 和 Wang (2020)。
受到神经机器翻译的启发,研究人员将反编译重新表述为一种翻译练习,将机器级指令转换为可读的源代码。 该领域的初步尝试利用循环神经网络 (RNN) Katz 等人 (2018) 进行反编译,并辅以纠错技术来增强结果。 尽管如此,这些努力的有效性受到了限制。
自然语言处理 (NLP) 的最新进展使得可以使用预训练语言模型 (LM) 来执行编码相关任务Rozière 等人 (2023);利平科特(2020);郭等人(2024)。 这些模型通常采用 Transformer 架构 Vaswani 等人 (2017),使用自注意力机制,并在大量文本数据集上进行预训练。 这种方法使语言模型能够捕捉上下文的细微差别,并有助于获得一般语言理解。 在二进制反编译领域,BTC Hosseini 和 Dolan-Gavitt (2022) 是最早为此目的配置 LM 的人之一。 随后,Slade Armengol-Estapé 等人 (2023) 利用 BART 模型,训练了一个具有 2 亿个参数的基于 LM 的反编译器,而 Nova Jiang 等人 (2023)从 StarCoder 检查点开始,开发了一个具有 10 亿个参数的二进制 LM,并对其进行了反编译微调。 虽然这些模型在反编译方面表现出了潜力,但它们的规模有限,例如 2023 年推出的 Code Llama 模型 Rozière 等人 (2023) 至少有 70 亿个参数。
反编译领域存在一个显着的差距:缺乏统一的、公认的衡量反编译工具质量的基准。 各种来源用于评估目的。 例如,BTC Hosseini 和 Dolan-Gavitt (2022) 利用网络数据(包括访谈式编码问题)和广泛的 Debian Linux 存储库来评估反编译准确性。 同时,Slade Armengol-Estapé 等人 (2023) 使用合成代码生成框架 Synth 和 ExeBench da Silva 等人 (2021) 的子集进行了测试; Collie 等人 (2020),由可执行 C 程序组成的基准测试。 除了 CodeFlaws Tan 等人 (2017) 数据集之外,Nova Jiang 等人 (2023) 还在其创建的合成数据集上评估其反编译能力,该数据集旨在识别常见的编码错误。
这些评估所采用的指标主要集中在 N 元语法相似度上,并使用 BLEU 或词符准确性以及编辑相似度 (ES)。 Slade Armengol-Estapé 等人 (2023) 更进一步,结合了输入输出 (IO) 精度 Le 等人 (2014); Liu and Wang (2020)纳入其评估框架。 该指标通过行为平等来评估语义等价性,这意味着它检查反编译代码和原始代码在给定相同输入时是否产生相同的输出。 然而,IO 精度依赖于外部进程来生成输入和输出样本以进行比较。 输入和输出样本的生成通常涉及随机性,导致不确定的结果,并且难以一致地评估反编译器的性能。
因此,我们的目标是开发并制作第一个专为反编译而定制的开源大语言模型(大语言模型)。 我们还旨在建立第一个可重编译性和可重执行性基准,为反编译领域的性能评估制定标准。
在本节中,我们描述预训练数据,呈现不同的模型配置,并讨论预训练大语言模型中涉及的预训练目标。
我们基于 Anghabench da Silva 等人 (2021) 构建 asm-source对,这是一百万个可编译 C 文件的公共集合。 遵循前人作品Armengol-Estapé等人(2023)的做法,我们首先将源代码编译为二进制目标文件,将目标文件反汇编为汇编代码,并将其与源代码配对,这里我们只考虑 x86 Linux 平台。 在实际部署中,程序员会选择不同的编译器优化标志来优化执行性能。 编译器优化是指调整和转换源代码以生成更快、更高效的机器代码的过程。 它涉及消除冗余指令、更好的寄存器分配、循环转换等技术。 不同的优化级别会权衡编译时间与执行时间和调试能力。 关键优化级别为:O0(默认,无优化)到O3(激进优化,编译时间消耗)。 我们将源代码编译为所有四个阶段,即 O0、O1、O2、O3,并将每个阶段与源代码配对。 为了通知模型优化阶段,我们使用以下提示:# This is the assembly code with [优化状态] optimization: [asm代码] # What is the source code?.
我们的 LLM4Decompile 使用与 DeepSeek-Coder 相同的架构,并且我们使用相应的 DeepSeek-Coder 检查点初始化模型。 作为神经翻译器,训练目标可以分为两类,如图2 所示。
图2: 训练目标
1)下一个词符预测(NTP)或语言建模
这是大多数大语言模型模型的预训练目标,目标是在给定先前输入的情况下预测下一个词符。 如方程 1 所示,它最小化了GT token的 x i x_{i} xi 负对数概率:
L = − ∑ i log P i ( x i ∣ x 1 , x 2 , … , x i − 1 ; θ ) (1) \mathcal{L} = −\sum \limits_{i}\log P_{i}(x_{i}\left|x_{1},x_{2},…,x_{i−1};\theta \right.) \tag{1} L=−i∑logPi(xi∣x1,x2,…,xi−1;θ)(1)
其中条件概率 P P P 使用参数为 θ \theta θ 的 LLM4Decompile 模型进行建模。 这些参数通过针对给定词符 x i x_{i} xi之前的输入序列 x 1 , x 2 , … , x i − 1 x_{1},x_{2},…,x_{i−1} x1,x2,…,xi−1应用梯度下降算法来优化。
2)序列到序列预测(S2S)
这是大多数神经机器翻译模型中采用的训练目标,旨在预测给定输入序列的输出。 如公式 2 所示,它最小化了 C 代码标记 x i , … , x j x_{i},…,x_{j} xi,…,xj 的负对数概率:
L = − ∑ i log P i ( x i , … , x j ∣ x 1 , … , x i − 1 ; θ ) (2) \mathcal{L} = −\sum \limits_{i}\log P_{i}(x_{i},…,x_{j}\left|x_{1},…,x_{i−1};\theta \right.) \tag{2} L=−i∑logPi(xi,…,xj∣x1,…,xi−1;θ)(2)
其中仅针对输出序列 x i … x j x_{i}…x_{j} xi…xj 或 C 代码计算损失。
主要区别在于输入序列或输入汇编代码是否包含在训练损失中,而在语言建模中,所有输入都包含在损失计算中。 我们对这两个训练目标进行了不同的消融研究,以探索它们在反编译中的有效性。
目前,似乎没有反编译评估基准来考虑代码是否可以重新编译或正确执行。 在评估反编译模型性能时,研究人员依赖于测量 N-gram 相似度(例如 BLEU 或 CodeBLEU)或编辑相似度 (ES) 的指标。 然而,这些常用于机器翻译和文本生成的指标无法适应编程语言的评估。
编程语言具有高度结构化和逻辑性,对函数和变量的命名不敏感,但对数据和逻辑流非常敏感。 更改变量或函数名称不会影响程序的含义,但单个逻辑错误可能会改变其整个功能和用途。 如图3 所示,使用BLEU和ES来评估代码相似度是有问题的。 对于 s r c 1 src_{1} src1来说,与原始 s r c src src的变化仅限于变量 n u m num num的类型转换,这导致了较高的BLEU和ES分数。 然而,这种改变完全改变了代码的意图。 同样, s r c 2 src_{2} src2 获得了较高的 BLEU 和 ES 分数,但函数的语义丢失了。 相反, s r c 3 src_{3} src3 经历函数和变量名称的规范化,不会导致语义转变,但相对于原始代码,BLEU 得分为零。 s r c 4 src_{4} src4 的例子更为极端:如果程序逻辑被分解为多行,ES 会下降到 41.4%,错误地表明相似度较低。 然而,在编译期间,名称通常由编译器标准化,并且源代码通常根据优化被分解为基本操作。 因此,在评估反编译效果时,重新编译并执行代码的能力比 N-gram 或编辑相似度更具指示性。
图3: 使用 BLEU 和编辑相似度评估反编译结果的限制。
为了解决反编译评估方面的差距,我们引入了 Decompile-Eval,这是第一个评估反编译系统可重编译性和可重执行性的基准。 该基准源自 HumanEval Chen 等人 (2021),它是代码生成评估的领先基准,包括 164 个编程挑战以及随附的 Python 解决方案和断言。 我们将这些 Python 解决方案和断言转换为 C 语言,确保它们使用标准 C 库通过 GCC 编译器进行编译,并传递所有原始断言。 在我们的评估过程中(图1),C源代码首先被编译为二进制文件,然后反汇编为汇编代码,最后输入反编译系统重新构建回C源代码。 重新生成的 C 代码使用 GCC 进行编译,以测试可重新编译性,并与原始断言结合起来,检查它是否可以成功执行并通过这些断言。 可重编译性和可重执行性是验证反编译过程有效性的关键指标。 当反编译的代码可以重新编译时,它提供了语法完整性的有力证据。 它确保反编译的代码不仅可读,而且遵守编译器期望的结构和语法标准。 然而,语法本身并不能保证与原始预编译程序的语义等效。 可重执行性提供了语义正确性的关键衡量标准。 通过重新编译反编译的输出并运行测试用例,我们评估反编译是否保留了程序逻辑和行为。 可重编译性和可重执行性共同表明语法恢复和语义保留——这对于可用和健壮的反编译来说都是必不可少的。
根据 Slade Armengol-Estapé 等人 (2023) 的既定评估实践,我们将 AnghaBench 中的 1000 个样本划分为测试集。 然后,我们利用 BLEU 和 ES 作为我们的主要评估指标。
为了与 SOTA 反编译器进行基准测试,我们选择了两个关键基准。 首先,GPT-4代表了最有能力的大语言模型,提供了大语言模型性能的上限。 作为最大的语言模型之一,GPT-4 在跨模态方面显着超越了之前的大语言模型。 其次,选择DeepSeek-Coder作为当前SOTA开源代码大语言模型。 它代表了专为编码任务量身定制的最前沿的公开可用模型。 虽然最近的学术著作如 BTC Hosseini and Dolan-Gavitt (2022) 和 Slade Armengol-Estapé 等人 (2023) 展示了用于反编译的大语言模型,但这些模型呈现出显着的集成性挑战,例如复杂的预处理设置、非标准化分词器/模型加载,以及需要付出巨大努力来修改和调整它们。 因此,我们选择 GPT-4 和 DeepSeek-Coder 作为可用于评估的代表性前沿开源基线。
表格1: Decompile-Eval 评估结果
Model | Re-compilability | Re -executability | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
Opt-level | O0 | O1 | O2 | O3 | Avg. | O0 | 01 | O2 | O3 | Avg. |
GPT4 | 0.92 | 0.94 | 0.88 | 0.84 | 0.895 | 0.1341 | 0.1890 | 0.1524 | 0.0854 | 0.1402 |
DeepSeek -Coder-33B | 0.0659 | 0.0866 | 0.1500 | 0.1463 | 0.1122 | 0.0000 | 0.0000 | 0.0000 | 0.0000 | 0.0000 |
LLM4Decompile-1b | 0.8780 | 0.8732 | 0.8683 | 0.8378 | 0.8643 | 0.1573 | 0.0768 | 0.1000 | 0.0878 | 0.1055 |
LLM4Decompile-6b | 0.8817 | 0.8951 | 0.8671 | 0.8476 | 0.8729 | 0.3000 | 0.1732 | 0.1988 | 0.1 841 | 0.2140 |
LLM4Decompile-33b | 0.8134 | 0.8195 | 0.8183 | 0.8305 | 0.8204 | 0.3049 | 0.1902 | 0.1817 | 0.1817 | 0.2146 |
表2: Anghabench 上的评估结果。
model | BLEU | Edit Similarity | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
Opt-level | O0 | O1 | O2 | O3 | Avg. | O0 | O1 | O2 | O3 | Avg. |
DeepSeek -Coder33B | 0.0362 | 0.0367 | 0.0306 | 0.0313 | 0.0337 | 0.1186 | 0.1196 | 0.1124 | 0.1133 | 0.116 |
LLM4Decompile-1b | 0.5099 | 0.493 | 0.487 | 0.4835 | 0.4934 | 0.6223 | 0.5946 | 0.5825 | 0.5822 | 0.5954 |
LLM4Decompile-6b | 0.8219 | 0.8246 | 0.8143 | 0.8148 | 0.8189 | 0.8562 | 0.8551 | 0.8422 | 0.8453 | 0.8497 |
LLM4Decompile-33b | 0.7724 | 0.7477 | 0.7485 | 0.7514 | 0.755 | 0.8252 | 0.7974 | 0.7993 | 0.8056 | 0.8069 |
表3: 训练方法的消融研究。
Model | Re-compilability | Re -executability | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
Opt-level | O0 | O1 | O2 | O3 | Avg. | 00 | O1 | O2 | O3 | Avg. |
S2S | 0.8817 | 0.8951 | 0.8671 | 0.8476 | 0.8729 | 0.3000 | 0.1732 | 0.1988 | 0.1841 | 0.2140 |
NTP | 0.8329 | 0.8598 | 0.8317 | 0.8329 | 0.8393 | 0.2805 | 0.1390 | 0.1573 | 0.1341 | 0.1777 |
NTP+S2S | 0.8963 | 0.8598 | 0.8963 | 0.8720 | 0.8811 | 0.3232 | 0.1463 | 0.1951 | 0.1707 | 0.2088 |
我们使用在 Hugging Face Wolf 等人 (2019) 上获得的 DeepSeek-Coder 模型(1.3B、6.7B 和 33B)的 Python 实现。 我们设置了全局 b a t c h s i z e = 2048 batch\ size = 2048 batch size=2048 和 l e a r n i n g r a t e = 2 e − 5 learning\ rate = 2e−5 learning rate=2e−5 并使用 AdamW 优化器 Loshchilov 和 Hutter (2019) 训练模型 2 个周期。 在评估过程中,我们将 m a x _ n e w _ t o k e n s max\_new\_tokens max_new_tokens设置为512。 为了保证时间和空间复杂度分析的公平性,所有实验均在配备8个NVIDIA A100-80GB GPU的集群上进行。
表1 展示了我们研究的主要结果。 最初,DeepSeek-Coder 的基础版本无法准确反编译二进制文件。 它可以生成看起来正确并且有时可以编译的代码,但无法保留原始程序语义。 经过微调后,LLM4Decompile 模型在反编译二进制文件的能力方面表现出显着提高,令人印象深刻的是,大约 90% 的代码是可编译的,这意味着对代码结构和语法的深入理解。 在执行代码的能力方面,LLM4Decompile的6B版本比1B版本显示出了显着的优势,6B版本的反编译代码中有21%成功捕获了程序的语义并通过了所有测试用例,而对于1B版本只有10%可以重新执行。 这一改进凸显了较大模型尺寸在捕获程序语义方面的优势。 尽管如此,模型大小增加到 33B 仅带来了微小的改进,可重执行性平均增加不到一个百分点。 这种稳定状态可能是由于调整 33B 模型的挑战造成的。
表2 总结了AnghaBench上的结果,其中LLM4Decompile显示出非常高的BLEU和ES分数,例如,6B模型达到了0.82 BLEU分数,几乎与源代码相同。 这种出色的性能表明测试集中存在严重的数据泄漏问题。 反编译代码及其变量标准化后,实际上不应该允许如此高的 N-gram/ES 分数。 这种异常现象强调了建立独立、可靠的反编译评估基准的重要性,因为之前的研究中也报告了类似的高 BLEU 和 ES 分数。
正如 [[#3.2 模型配置|3.2]] 节中所讨论的,我们的 LLM4Decompile 模型采用序列到序列 (S2S) 预测方法,由于多种原因,该方法优于其他训练技术。 在此训练方法中,输入(特别是汇编代码)不包含在损失函数的计算中。 这使得模型能够专注于生成准确的输出源代码,从而更好地理解反编译代码的底层模式和结构。 相比之下,将汇编代码集成到训练过程中,如在下一个词符预测(NTP)任务中,既包含输入汇编代码又包含输出源代码,这会导致性能降低约 4 个点,如表3 汇编代码的复杂性是另一个因素;由于本质上是复杂和低级的,当过程中包含汇编代码时,模型很难学习有意义的模式。 通过从损失计算中排除汇编代码,S2S 方法使模型能够避免这种复杂性并专注于高级源代码模式。 尽管替代策略涉及使用汇编和 C 代码进行初始训练步骤,然后针对翻译任务进行微调 (NTP+S2S),但这种方法的性能仍然不如 S2S。
我们提出了第一个开源的以反编译为中心的大语言模型和标准化的可重编译/可重执行基准。 对这个多样化编译 C 代码数据集的分析揭示了有前途的功能 - 我们的 6B LLM4Decompile 实现了 87% 的可重编译性(表明语法理解)和 21% 的可重执行性(表明语义保留)。 作为对数据驱动反编译的初步探索,我们的工作建立了一个开放的基准来激励未来的努力。 公共数据集、模型和分析代表了通过新技术增强反编译的令人鼓舞的第一步。
本研究范围仅限于针对x86平台的C语言的编译和反编译。 虽然我们相信这里开发的方法可以相对轻松地适应其他编程语言和平台,但这些潜在的扩展已保留用于未来的研究。 此外,我们当前的研究仅限于单个函数的反编译,没有考虑交叉引用和外部类型定义等因素。 这提供了反编译过程的简化视图,省略了这些元素引入的复杂性。 解决这些方面将提供对更广泛场景下的反编译的更全面的理解,并且是后续研究的重要途径。