1983 Ken Thompson

Ken Thompson


Citation
For their development of generic operating systems theory and specifically for the implementation of the UNIX operating system ]

Ken Thompson,C语言前身B语言的作者,Unix的发明人之一(另一个是Dennis M. Riche老大,被尊为DMR),Belle(一个厉害的国际象棋程序)的作者之一, 操作系统Plan 9的主要作者(另一个是大牛人Rob Pike, 前不久被google挖走了)。Ken爷爷也算是计算机历史上开天辟地的人物了。1969年还是计算机史前时代,普通人都认为只有大型机才能运行通用的操作系统,小型机只有高山仰止的份儿。至于用高级语言来写操作系统,更是笑谈。Ken爷爷自然不是池中物,于是他和DMR怒了,在1969年到1970间用汇编在PDP-7上写出了UNIX的第一个版本。他们并不知道,一场轰轰烈烈的UNIX传奇由此拉开了序幕。Ken爷爷在1971年又把Unix用C重写,于是C在随后20年成就了不知多少豪杰的梦想和光荣。

Ken爷爷还有段佳话: 装了UNIX的PDP-11最早被安装在Bell Lab里供大家日常使用。很快大家就发现Ken爷爷总能进入他们的帐户,获得最高权限。Bell Lab里的科学家都心比天高,当然被搞得郁闷无比。于是有高手怒了,跳出来分析了UNIX代码,找到后门,修改代码,然后重新编译了整个UNIX。就在大家都以为"这个世界清净了"的时候,他们发现Ken爷爷还是轻而易举地拿到他们的帐户权限,百思不解后,只好继续郁闷。谁知道这一郁闷,就郁闷了14年,直到Ken爷爷道出个中缘由。原来,代码里的确有后门,但后门不在Unix代码里,而在编译Unix代码的C编译器里。每次C编译器编译UNIX的代码,就自动生成后门代码。而整个Bell Lab的人,都是用Ken爷爷的C编译器。:D

UNIX的创造人之一,Ken Thompson,在他的 Turing Award Lecture中,便由这个主题加以发挥,说了一些有趣的故事。C 是一个被拿来写操作系统的语言。写操作系统的人很难忍得住诱惑,不在系统里面装些后门的。想想看,如果我写操作系统时,偷偷在login 的部份加一段程序代码,使得全世界的这套操作系统只要看到我的account和密码就让我进去,给我root权限,这该是多爽呀。 但是我不能直接在 login 的 source code 里面这样写,否则一下就被人抓到了(既然 source code流通,就是要给人看的呀)。 该怎么办呢?就从compiler里面动手脚,称作patch1吧:在compiler中多加一道手续, 如果发现被compile的原始程序〞疑似〞在作login动作,就把它开个漏洞,让我进得去。

但是这样也不见得行得通。Compiler以后也会改版,新版的compiler可能不是我在写。装系统的人也不见得用我的compiler。怎么办呢?于是我在compiler 的source code中作第二次手脚,称作patch2:如果这个compiler觉得在compile 的程序〞疑似〞另一个 compiler 的 source 的话,就加入上面的patch1和这个 patch2本身。

好,现在操作系统推出了,CC1 是我写的内建compiler,其中有我动的两个手脚。现在某人在compile UNIX, 不得不用这个compiler。然而CC1 中已经有了 patch1,于是一旦compile到login, compile出来的login程序就被动了手脚。只要看到我的名字,就一定让我进系统,给我root权限。
  ,----------.    +---------------+    ,--------------.   
  | login |     | Compiled  |    | login   |   
  | source | =====> | by CC2   | =====> | Program  |
  | (clean) |     | patch 1作用 |     |(受感染了!)|
  `--------- ’    +----------------+     `-----------’
既然 compiler CC1会作怪, 那么自己写 compiler 总可以了吧? 然而,C compiler还是得用C写,写好了之后,用谁来compile呢? 只有用CC1来compile。 CC1发现新写的CC2是一个compiler的source code,于是 patch2 就发挥作用了。 CC1会在CC2中也加入patch1和patch2。于是CC2也被〞污染〞了。
  ,------------.      +-------------+     ,------------------. [ 相约加拿大:枫下论坛 rolia.net/forum ]
  | CC2  |      | Compiled |     | CC2     |   
  | source | =====>  | by CC1  | =====> | patch 2作用 |
  | Program |      | (clean)   |      |含 patch1,2 |
  `-----------’      +-------------+     `-------------------’
如果再用CC2来compile一个正常的login程序,由于CC2中有了patch1,所以 compile出来的login程序也会有后门,让我任意的login;
  ,--------.     +---------------+     ,----------.
   | login |      | Compiled |     | login  |   
   | source |=====> | by CC2   |=====>  | Program |
  | (clean)|      |(patch 1,2) |     | (patched!) |
  `--------’      +--------------+     `--------------’
如果用CC2 compile另一个compiler CC3,由于CC2中已经被加入了 patch2, CC3又会被污染,也就是说CC3这个compiler中还是会有patch1和patch2......如此一来,全世界的每一套UNIX都种下了这个后门,可以让我任意login!
< r o l i a. n e t >
然而这些patch都只在binary档之中出现。CC2的source code一切正常,所以从source code完全看不出有什么不对劲呢!我们还可以进一步湮灭证据。一旦装好一套系统,公开的CC1 source code中不必有动过手脚的程序代码,只要让它被动过手脚的compiler编译就可以了。

有着无辜的包装,事实上内容暗藏玄机的程序,称作〞特洛伊木马〞。 这个特洛伊木马的故事有趣吗?

用C语言写C compiler,写出来的程序会是个什么样子呢? 举个例子,一个C compiler可能有一段前置处理程序在处理C字符串中的溢出字符。比如说,compiler 需要把如下的字符串:

"Figure listings : Figure1 A Complete Tree ....."

给转换成:

Figure listings :<换行码>Figure1A Complete.....
{ 枫下论坛 rolia.net/forum }
这段程序可能看起来像这样(为简单起见,这个程序从标准输入读进原始码,送到标准输出):

  if ((c=getchar())==’’)
   switch (getchar()) {
    case ’n’ : putchar(’ ’); break;
    case ’t’ : putchar(’ ’); break;
         :
  }
  else
   putchar(c);

好象有点奇怪,是吗?明明用if和switch把溢出字符’’以及后面的’n’,’t’, 分开了,在putchar的地方又送出’ ’, ’ ’。如果您见多了用某语言写自己的 compiler的情况,对于这种程序段落也就见怪不怪了
这个版本的UNIX传出去之后,Berkley的叫兽们很快就发现了这个后门,重编译也解决不了,但叫兽们毕竟也不是吃白饭的,于是将编译器的源码编译为汇编代码再编译,就解决了这个问题。
C代码编译为汇编代码的时候,没有插入后门,是为了避免让人一眼就看出破绽。而汇编代码到机器代码的翻译很简单,不依赖于原来的C编译器。这样编译一遍就除掉后门了


posted on 2005-03-24 10:54  NetToad  阅读(626)  评论(0编辑  收藏  举报