Ken Thompson的编译器黑客仍然是威胁吗?


156

肯·汤普森·哈克(1984)

肯·汤普森(Ken Thompson)于1984年概述了一种破坏编译器二进制文件(和其他已编译软件,例如* nix系统上的登录脚本)的方法。我很想知道现代编译是否解决了此安全漏洞。

简短的介绍:

重新编写编译器代码以包含2个缺陷:

  • 在编译自己的二进制文件时,编译器必须编译这些缺陷
  • 在编译其他一些预选的代码(登录功能)时,它必须编译一些任意的后门

因此,编译器可以正常工作-编译登录脚本或类似文件时,可以创建安全后门,并且将来在编译自身的较新版本时,它仍保留以前的缺陷- 并且这些缺陷仅存在于编译器中二进制文件,因此很难检测。

问题:

我在网上找不到任何答案:

  • 这与即时编译有什么关系?
  • 运行诸如* nix系统上的登录名的程序之类的功能在运行时是否已编译?
  • 这仍然是一个有效的威胁吗?或者自1984年以来在编译安全性方面的发展阻止了它成为一个重大问题?
  • 这会影响所有语言吗?

我为什么想知道?

我在做作业时遇到了这个问题,这看起来很有趣,但是我缺乏具体了解这个问题是解决当前问题还是解决问题的背景。

参考资料


6
多样双重编译策略是一种检测RoTT操纵的编译器是否存在的合理可靠的方法。
dmckee

3
我想国家安全局(NSA)已经为此类攻击做了很多工作。
Paul M

Answers:


110

必须从上下文中了解这种技巧。它是在一种时代和一种文化下发布的,在该文化中,运行于各种不同硬件上的Unix是主导系统。

使攻击如此可怕的是,C编译器是这些系统核心软件。首次安装时,系统中的几乎所有内容都通过了编译器(由于硬件异构,因此二进制发行版很少见)。每个人都一直在编译东西。人们会定期检查源代码(他们通常不得不进行调整以使其完全得以编译),因此让编译器注入后门似乎是一种“完美犯罪”方案,您无法被抓住。

如今,硬件更加兼容,因此编译器在系统的日常操作中所扮演的角色要小得多。受到威胁的编译器不再是最可怕的情况-rootkit和受破坏的BIOS甚至更难检测和消除。


27
或者,由于大多数人不从源代码进行任何编译(例如,在Windows上),您的平均特洛伊木马就足够了:)(我同意,一个受感染的编译器会导致过大杀伤力)
Andres F.

16
@ArjunShankar:一个非自由专有二进制仅有的编译器不需要,也不可能有,这个后门。后门程序仅适用于从源代码编译自己的编译器。
ruakh

12
除了台式机以外,Unix及其所有变体仍然是主要的操作系统。
罗布

7
@ruakh:也许我不理解您对“这个”的强调,但我不同意。如果该后门被恰好拥有非免费的专有编译器的公司引入,并使用该编译器来编译同一编译器的新版本,则该后门的影响将比原始方案严重得多。您只需要一个攻击媒介即可感染所有病毒。
orithena 2013年

8
假设有人破坏了ubuntu构建服务器并替换了编译器,而没有更改任何源。可能需要花费一些时间才能发现,到那时,ubuntu图像将通过内置的受感染编译器(以及受感染的登录程序集或您拥有的东西)推向所有人。我认为这仍然是一个完全正确的问题。
Jimmy Hoffa 2013年

74

演讲的目的不是强调需要解决的漏洞,也不是提出我们需要意识到的理论漏洞。

目的是在安全性方面,我们不想信任任何人,但是不幸的是这是不可能的。您始终必须信任某人 (因此,标题为:“对信任的反思”)


即使您是偏执狂类型,他会加密自己的台式机硬盘驱动器并拒绝运行您自己未编译的任何软件,您仍然需要信任您的操作系统。即使您自己编译操作系统,您仍然需要信任您使用的编译器。即使如果您编译自己的编译器,你需要信任编译器!甚至没有提到硬件制造商!

你简直无法摆脱不信任的。这就是他试图克服的重点。


2
如果有一个开源编译器,其行为不依赖于任何实现定义的行为或未指定的行为,请使用各种独立开发的编译器(受信任或不信任)对其进行编译,然后使用的所有不同编译版本来编译一个程序。那个开源的,每个编译器应该产生完全相同的输出。如果这样做的话,这表明特洛伊木马存在的唯一方式就是完全相同。这似乎不太可能。但是,我的烦恼之一是带有.net的大部分……
超级猫

9
@supercat:您似乎错过了重点。您是说肯·汤普森(Ken Thompson)提出的骇客可以解决。我是说他选择的特定技巧无关紧要;这只是一个例子,以证明他的长处是必须始终信任某人。这就是为什么这个问题有点无意义的原因- 它完全错过了森林。
BlueRaja-Danny Pflughoeft13年

9
@supercat:由于不同的设计决策,优化等原因,不同的编译器为任何非平凡的程序生成相同的字节码的可能性极小。这引发了一个问题-您甚至怎么知道二进制文件是相同的?
Ankit Soni

1
@AnkitSoni:我的答案会更详细。通过不同的编译器提供适当编写的开源编译器/链接器,应会产生表现相同的不同可执行文件。如果可执行文件实际上的行为相同,那么如果将开源编译器/链接器的代码传递给它们,它们将产生相同的输出。要比较这些文件,可以将它们复制到软盘上,然后使用一台古董计算机进行比较。
2013年

2
这些对话中的某些内容是否仅表示对于您测试的东西,二进制文件/硬件的行为符合预期?可能仍然有一些您没有测试并且没有意识到的东西。
Bart Silverstrim

53

没有

如最初所述,该攻击绝不是威胁。虽然理论上编译器可以做到这一点,但要真正阻止攻击,就需要对编译器进行编程以使其

  • 识别正在编译的源代码何时属于编译器,并且
  • 弄清楚如何修改任意源代码以将hack插入其中。

这需要弄清楚编译器如何从其源代码工作,以便可以对其进行修改而不会造成损坏。

例如,假设链接格式将数据长度或已编译机器代码的偏移量存储在可执行文件中的某个位置。编译器必须自己确定其中哪些需要更新,以及在插入漏洞有效载荷时需要在何处更新。编译器的后续版本(无害版本)可以任意更改此格式,因此利用代码将需要有效地理解这些概念。

这是高级的自定向编程,这是一个棘手的AI问题(最后我检查了一下,现有技术正在生成实际上由其类型决定的代码)。看:几乎没有人能做到这一点;您将必须学习编程语言并首先了解代码库。

即使解决了AI问题,人们也会注意到,编译其微型编译器是否会导致二进制文件带有链接到其中的巨大AI库。

类似攻击:自举信任

但是,攻击的一般化是相关的。基本问题是您的信任链必须从某个地方开始,并且在许多领域,其起源都可能以一种难以察觉的方式破坏整个链。

一个可以在现实生活中轻松实现的示例

您的操作系统(例如Ubuntu Linux)通过对照存储库的签名密钥(使用公共密钥加密)检查下载的更新包来确保更新的安全性(完整性)。但是,这只能保证真实性更新的,如果你能证明签名密钥是通过合法来源所有。

您从哪里获得签名密钥?首次下载操作系统发行版时。

您必须相信信任链的来源(此签名密钥)不是邪恶的。

任何能够使您与Ubuntu下载服务器之间的Internet连接成为MITM的人(可能是您的ISP,控制Internet访问的政府(例如中国)或Ubuntu的托管提供商)都可能劫持了此过程:

  • 检测到您正在下载Ubuntu CD映像。这很简单:请参见将请求发送到任何(公开列出的)Ubuntu镜像,并询问ISO映像的文件名。
  • 服务来自他们自己服务器的请求,为您提供CD映像,其中包含攻击者的公共密钥和存储库位置,而不是Ubuntu的。

此后,您将从攻击者的服务器安全地获取更新。更新以root用户身份运行,因此攻击者拥有完全控制权。

您可以通过确保原件是真实的来防止攻击。但这要求您使用哈希来验证下载的CD映像(实际上很少有人这样做),并且哈希本身必须安全地下载,例如通过HTTPS。而且,如果攻击者可以在计算机上添加证书(在公司环境中常见)或控制证书颁发机构(例如中国),那么即使HTTPS也无法提供保护。


47
这是错误的。编译器仅需确定何时从其自身的源代码中编译具有特定内容的特定源文件,而无需确定何时编译任何编译器!
卡兹(Kaz)2013年

14
@Kaz-在某些时候,对编译器或登录程序的过度修改可能会打败后门的compile-recognizer / login-recognizer,随后的迭代将丢失后门。这类似于赋予某些疾病免疫力的随机生物突变。
罗素·波罗戈夫

12
您的答案的前半部分有Kaz所描述的问题,但后半部分非常好,以至于我无论如何都在+1!
ruakh

7
一个邪恶的编译器只能识别它自己的源代码,很容易构建,但是在实践中却一文不值-已经拥有该编译器二进制文件的人很少会使用它来重建所述二进制文件。为了使攻击能在更长的时间内成功完成,编译器将需要更多的情报来修补其自身来源的较新版本,从而遇到解决方案中描述的问题。
user281377 2013年

5
特定编译器的识别器可能相当笼统,并且面对新版本不太可能会中断。以gcc为例-gcc中的许多代码行都非常老,并且变化不大。名称之类的简单事物几乎永远不会改变。在识别出问题之前,注入的代码很可能会。实际上,这两个问题在很大程度上都是理论上的-在实践中,恶意软件作者可以毫不费力地掌握最新的编译器开发进度。
Eamon Nerbonne 2013年

25

首先,我最喜欢这种黑客的文章称为Strange Loops

当然,今天可以在任何主要的开源OS项目中,特别是Linux,* BSD等项目中,都可以进行这种特殊的破解。我希望它几乎可以相同地工作。例如,您下载了一个FreeBSD副本,该副本具有被利用的编译器来修改openssh。从那时起,每次升级openssh或按源升级编译器时,都会继续出现此问题。假设攻击者首先利用了用于打包FreeBSD的系统(可能是因为映像本身已损坏,或者攻击者实际上是打包者),那么每次系统重建FreeBSD二进制文件时,它都会重新注入问题。攻击失败的方法有很多,但与Ken的攻击失败的方式(**)根本没有不同。世界真的没有太大改变。

当然,类似的攻击可能会被其所有者同样容易(或更容易)注入到Java,iOS SDK,Windows或其他任何系统中。甚至可以将某些类型的安全漏洞设计到硬件中(特别是削弱随机数生成)。

(*)但是,“肯定地”是指“原则上”。您是否应该期望在任何特定系统中都存在这种漏洞?不。出于各种实际原因,我认为这种可能性很小。随着时间的流逝,随着代码的更改和更改,这种黑客入侵会导致奇怪错误的可能性增加。这就增加了被发现的可能性。精巧的后门将需要阴谋来维持。当然,我们知道在各种电信和网络系统中都已安装了“合法拦截”后门,因此在许多情况下,这种精心设计的技巧是不必要的。hack是公开安装的。

因此,总是要深入防御。

(**)假设Ken的袭击确实存在。他只是讨论它如何做到。据我所知,他没有说他实际上做了。


关于您的第二个脚注,Ken说“构建且未分发”。
8bittree

15

这会影响所有语言吗?

此攻击主要影响自托管的语言。那是用编译器本身编写的语言。C,Squeak Smalltalk和PyPy Python解释器将受到此影响。Perl,JavaScript和CPython Python解释器不会。

这与即时编译有什么关系?

不是很多。正是编译器的自托管特性允许隐藏黑客。我不知道任何自托管的JIT编译器。(也许是LLVM?)

运行诸如* nix系统上的登录名的程序之类的功能在运行时是否已编译?

通常不行。但是问题不是何时编译,而是由哪个编译器。如果登录程序是由受污染的编译器编译的,它将被污染。如果它是由干净的编译器编译的,则它将是干净的。

这仍然是一个有效的威胁吗?或者自1984年以来在编译安全性方面的发展阻止了它成为一个重大问题?

这仍然是理论上的威胁,但可能性很小。

您可以减轻它的一件事是使用多个编译器。例如,本身由GCC编译的LLVM编译器不会通过后门。同样,LLVM编译的GCC不会通过后门。因此,如果您担心这种攻击,则可以使用另一种编译器来编译您的编译器。这意味着邪恶的黑客(在您的OS供应商处?)将不得不污染两个编译器以使彼此识别。一个更加困难的问题。


严格来说,您的最后一段不是正确的。从理论上讲,代码可以检测到正在编译的编译器并适当地输出后门。这在现实世界中当然是不切实际的,但是没有任何内在的方法可以阻止它。但是,最初的想法不是关于实际的实际威胁,而是关于信任的教训。
Steven Burnap 2013年

有道理。毕竟,黑客携带了一个后门用于登录,并为编译器提供了一个mod,因此它也可以为另一个编译器提供一个mod。但是,这种可能性越来越小。
肖恩·麦克米兰

及时编译可能是一种享受。如果某些代码仅在对特定代码进行JIT编译时才具有某些漏洞,则可能不会引起注意。(只是纯理论)
GameDeveloper 2015年

12

理论上有可能发生这种情况。但是,有一种方法可以通过David A. Wheeler的Diverse double-compiling来检查特定的编译器(带有可用的源代码)是否已被破坏。

基本上,使用可疑编译器和另一个独立开发的编译器来编译可疑编译器的源代码。这给了你SC SC和SC 牛逼。现在,使用这两个二进制文件编译可疑源。如果生成的二进制文件是相同的(除了可能合理地变化的各种事物(例如各种时间戳记),则可疑编译器实际上并未滥用信任关系。


那个或可信赖的编译器都不如用户所想的可信赖。但是对于一种语言的两个独立实现,它们包含相同后门的可能性可以忽略不计。
Damian Yerrick

或您用来比较它们的差异工具也受到了损害;)
iCodeSometime

@kennycoc但是,编写“这两个文件是否相同”比较工具并不是那么困难(例如,在给定syscall参考的情况下,它应该可以在2-16小时内用二进制机器代码完成)。
Vatine

3

作为一种特定的攻击,它与以往一样具有威胁性,几乎完全没有威胁。

这与即时编译有什么关系?

不知道那是什么意思。准星对此有免疫力吗?不。它更容易受到伤害吗?并不是的。作为开发人员,您的应用程序更容易受到攻击,因为您无法验证未完成该应用程序。请注意,您尚未开发的应用程序基本上不受此以及所有实际变化的影响,您只需要担心比您的代码更新的编译器。

运行诸如* nix系统上的登录名的程序之类的功能在运行时是否已编译?

没关系。

这仍然是一个有效的威胁吗?或者自1984年以来在编译安全性方面的发展阻止了它成为一个重大问题?

编译没有真正的安全性,而且不可能。那确实是他讲话的重点,在某些时候您必须信任某人。

这会影响所有语言吗?

是。从根本上说,有时必须将您的指令转换为计算机可执行的指令,并且转换可能会错误地完成。


-2

大卫·惠勒(David Wheeler)的一篇好文章:http : //www.dwheeler.com/trusting-trust/

我,我更担心硬件攻击。我认为我们需要一个完整的带有FLOSS源代码的VLSI设计工具链,我们可以对其进行修改和编译,从而使我们能够构建一个没有工具插入后门的微处理器。这些工具还应该让我们了解芯片上任何晶体管的用途。然后,我们可以打开成品芯片的样本,并用显微镜对其进行检查,确保它们具有与工具所认为的相同的电路。


3
-1,您的答案大部分无法解决问题。

-3

最终用户可以访问源代码的系统就是您必须隐藏这种类型的攻击的系统。这些将是当今世界上的开源系统。问题是,尽管所有Linux系统都依赖于一个编译器,但是对于所有主要Linux发行版,攻击都必须进入构建服务器。由于这些攻击者不会直接为每个编译器版本下载编译器二进制文件,因此,攻击的源将必须存在于至少一个先前版本的编译器中的生成服务器上。他们必须以二进制文件的形式下载该编译器的第一个版本或该版本的第一个版本。


2
您的答案从问题的表面开始,但并未真正解决所要提出的问题。

-4

如果一个人具有用于编译器/构建系统的源代码,而该系统的输出不应该依赖于所提供源文件的内容之外的其他内容,并且如果一个人具有其他几个编译器并且知道它们都不都包含相同的编译器hack,则可以确保获得的可执行文件不依赖源代码。

假设有人以一种方式编写了一个编译器/链接器程序包(例如Groucho Suite)的源代码,使得其输出将不依赖于任何未指定的行为,也不依赖于输入源文件内容之外的任何东西,并且其中一个进行编译/在各种独立生产的编译器/链接器程序包(例如Harpo Suite,Chico套件和Zeppo Suite)上编码的链接,为每个产生不同的可执行集(称为G-Harpo,G-Chico和G-Zeppo)。这些可执行文件包含不同的指令序列并不奇怪,但是它们在功能上应该相同。然而,证明它们在所有情况下在功能上都是相同的,这可能是一个棘手的问题。

幸运的是,如果仅将生成的可执行文件用于一种目的,则不必再进行这种证明:再次编译Groucho套件。如果一个人使用G-Harpo(生成GG-Harpo),G-Chico(GG-Chico)和G-Zeppo(GG-Zeppo)来编译Groucho套件,则生成的所有三个文件GG-Harpo,GG-Chico和GG-Zeppo,都应逐字节相同。如果文件匹配,则意味着它们中任何一个存在的任何“编译器病毒”都必须在它们中完全相同(因为所有三个文件都是字节对字节相同的,因此它们的行为在任何情况下都不可能有所不同)方式)。

根据其他编译器的年龄和血统,有可能确保此类病毒不会合理地存在于它们中。例如,如果使用一台古老的Macintosh来提供一台编译器,该编译器是通过1980年代编写的MPW版本从2007年从头编写的,则1980年代的编译器将不知道在2007编译器中插入病毒的位置。今天的编译器可能有可能进行足够多的代码分析以找出答案,但是这种分析所需的计算水平将远远超过简单地编译代码所需的计算水平,并且不会很好地被人们忽略。在编译速度是主要卖点的市场中。

我认为,如果正在使用一种编译工具,其中要生成的可执行文件中的字节不应该以任何方式依赖于提交的源文件的内容以外的其他任何东西,那么就有可能获得对Thompson的相当好的免疫力型病毒。不幸的是,由于某些原因,在某些环境中,编译中的不确定性似乎被认为是正常的。我认识到,在多CPU系统上,如果允许代码生成的某些方面根据两个线程中的哪个先完成工作而有所不同,则编译器可能会运行得更快。

另一方面,我不确定我是否有任何理由认为编译器/链接器不应提供“规范输出”模式,其中输出仅取决于源文件和“编译日期”,而用户可能会覆盖该“编译日期” 。即使在这种模式下编译代码所花的时间是普通编译时间的两倍,我还是认为完全能够从源材料中逐字节重新创建任何“发布版本”将具有相当的价值,即使这意味着发布版本将比“正常版本”花费更长的时间。


2
-1。我看不出您的答案如何解决问题的核心方面。

@ GlenH7:当给定位相同的输入时,许多较旧的编译工具将始终产生位相同的输出(诸如TIME之类的东西,可以对其进行调整以报告“正式的”编译时间)。使用这样的工具,可以很好地防御编译器病毒。某些流行的开发框架没有提供“确定性”编译代码的方式,这一事实意味着,本可以保护旧工具中的病毒的技术无法与新工具有效地结合使用。
超级猫

你有尝试过吗?1.引导论文。2.使用较短的段落。3.更明确地说明“功能相同”(第一阶段的结果)和“位相同”(第二阶段的结果)之间的区别,可能列出所有产生的编译器二进制文件及其相互之间的关系。4.引用David A. Wheeler的DDC论文。
Damian Yerrick
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.