WriteLn(‘Algorithms + Data Structures = Programs’);
今天首先要怀念一下在2024年1月1日刚刚去世的计算机科学巨擘、1984年图灵奖得主Niklaus Wirth,他最为上个世纪80-90年代出生的读者所熟知的作品可能是Turbo Pascal(上图),当年参加OI的选手大概率是学过Pascal语言的。还有就是Wirth教授撰写的著作《Algorithms + Data Structures = Programs》也就是“算法+数据结构=程序”已经成了计算机历史长河中的不朽名句。
回到今天的论文,我们今天要介绍的是USENIX Security 2024预录用的论文A Taxonomy of C Decompiler Fidelity Issues,在这篇论文中,作者研究了这么一个问题——究竟是什么让你觉得反编译得到的代码读起来不舒服?当然,这个对问题描述是我们用比较随意的语言写下的,实际上作者是在用很严谨的科学研究手段去确定反编译工具生成的代码中,哪些地方和原始代码相比是“失真”的,以及如何将这些失真的情况进行分类,这样才能更好地指导反编译器未来的开发。
首先让我们看一个例子,在下图中,原始的源代码和反编译得到的代码相比,你能说出这里面有哪些不同吗?可能大家最容易注意到的(当然也是反编译器最不擅长的)就是变量名和类型信息的缺失。不过,如果只是凭着直觉,很难科学地总结出反编译器到底在哪些地方做得不够好,因此今天论文的重点就是介绍一套严谨的流程,最后归纳出反编译器的各项“失真度”指标。
要了解本文的工作,首先要学习一个叫做thematic analysis(主题分析?)的概念,这是在人文社科类研究中经常用的技术。编辑部去网上搜索学习了一下(没有用GPT哦,说得不对请读者指出),这个技术是和我们常见的user study里面的访谈或者问卷调查(interview)关联起来的,一般来说,访谈会收集到一大堆反馈答案,都是格式非常松散的文本答案,而主题分析的作用就是从收集的文本内容中归纳总结出来主题的过程。至于具体的细节就不再在这里展开了,大家可以去下面这个链接看看一些简单的入门教程
https://www.scribbr.com/methodology/thematic-analysis/
在thematic analysis技术的加持下,作者首先用Hex-Rays
、Ghidra
、retdec
、angr
四大反编译器来处理了一大堆的开源代码(从GitHub上收集了81137个主要是C语言开发的repo),然后用GHCC
(https://github.com/huzecong/ghcc 很有意思的一个项目)来自动化编译源代码,并进行源代码和反编译代码配对,最后随机抽取出200对“原始代码-反编译代码”(下图是一个例子),然后就使用到让大家瑟瑟发抖的互联网企业三大黑话之一的“对齐”(另两个是“闭环”和“赋能”)——align来比较原始代码和反编译代码的差异。
好了,我们略过alignment和thematic analysis,直接看看本文最后给出的反编译器失真度分类表吧:在论文的第8页给出的这幅图中,把15类典型的失真情况都标记了出来。我们简单解释一下(对应下面的图例来看,就更容易理解):
-
Incorrect identifier name: 顾名思义,原始源代码中的符号名字无法正确恢复;
-
Non-idiomatic dereference: 涉及到指针解引用的时候,结果很不“地道”,例如把
current->next
翻译成*((_QWORD *)(v5 + 8))
; -
Unaligned code: 反编译出来多余的代码(原代码中根本没有)或者丢失了原来代码中的语句;
-
Typecast issues: 本来应该有的类型转换操作在反编译代码中消失了;
-
Nonequivalent expression: (特定部分)反编译代码和原代码的功能不一致;
-
Unaligned variable: 反编译出来多余的变量(原代码中根本没有)或者丢失了原来代码中应有的变量;
-
Non-idiomatic literal representation: 不地道的常量表示,例如把一个字符串
"}n"
给翻译成了常量2685
; -
Obfuscated control flow: 这里不是指代码混淆技术里面的控制流混淆,而是指反编译器在翻译过程中把控制流给搞错了;
-
Issues in representing global variables: 全局变量没法正确恢复(即使二进制代码中保留了相关的信息);
-
Expanded symbol: 这个可叫做“帮你优化”,例如源代码里面的
sizeof(int *)
直接反编译为8
(但是这难道不是编译器的锅吗); -
Use of decompiler-specific macros: 引入一些只有反编译器才喜欢用的宏(例如IDA那个标志性的
LODWORD
宏); -
Abuse of memory layout: 对结构体的内存布局处理存在错误理解,例如把结构体里面的两个相邻的
uint32_t
变量当成一个uint64_t
变量来处理; -
Incorrect return behavior: 对函数返回值的错误处理,例如本来函数没有返回值,反编译器会给它加上一个;
-
Decomposition of a composite variable: 把复合类型的变量当成一个普通类型变量来处理;
-
Type-dependent nonequivalent expression: 类型信息缺失导致的指针算术错误,比如对数组的偏移的计算,反编译器大部分情况下都会按字节偏移来算,而不是按照原始类型(比如一个
int
数组,偏移为1的时候,反编译器就给你算成4);
作者还对上面这些失真情况进行了讨论,表示它们可分为确定性和概率性的失真,而那些确定性的失真(例如反编译器总是自作主张引入一些特定的宏)是可以在未来加以改进的。当然,作者也对所调查的四大反编译器进行了简单的评估(第5.2章),大家感兴趣的可以去读读看。
论文:https://www.usenix.org/system/files/sec23winter-prepub-381-dramko.pdf
实验材料:https://doi.org/10.5281/zenodo.8419614
原文始发于微信公众号(安全研究GoSSIP):G.O.S.S.I.P 阅读推荐 2024-01-04 反编译器的“失真度”分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论