程序员的最大噩梦
这是很早之前在Quora(美国的知乎)上看到的文章,被我收藏到Pocket,应该是15年的一篇文章,原文已经打不开了。
翻译了一下,算是对1024迟到的庆祝。
我受雇于一个心理学家来修复一个有‘奇怪输出’的程序,这个程序是心理学家以前的一个研究生写的。该程序读入一个数据文件,输出50个问题,然后根据这50个问题的答案给出一个评分。程序运行在大学机房的一台3B2工作站上。程序跑起来后,在切换问题的时候确实有些奇怪的文字闪来闪去,让人心烦。貌似个简单bug,我们决定先花点时间定位问题(按小时付费),再确定一个最终的费用。
![]()
第一天
我在这台老爷机上用研究生的账户登陆,找到了以前的代码。代码是用C语言写的,故意写的很晦涩,工程一共有15个c文件,每个文件大概有3个函数,文件一个换行都没有,代码都塞在一行里!所有的变量都是三个随机字母。这货绝对是故意的…我找到心理学家,确定按小时收费(我太英明了)。把代码格式化,开始干活。
程序用curses来控制屏幕移动。打印出问题,输出一些白色字符,然后等待0.5秒,后面跟着这个问题的选项,以供选择,所以总有些一闪而过的字符出现在屏幕上。修复应该很简单,找到这些无效输出,删掉。我找了一下,一共有5硬编码的mvprintw()。我删掉这些后重新编译了代码,觉得大势已定。结果,你猜怎么着。。。这些字符又回来了!
我检查了一下刚改过的代码,我的修改全丢,完全又是老样子:15个混淆了变量/函数的,摊在一行的c文件。
我恨不得开枪崩了自己,我怎么就不记得备份呢!这次我长心眼了,重新命名梳理了这些文件,做了个全目录备份,设置了只读属性,重新编译,一切充满希望。。。直到我执行代码的时候。那些字符依然在。检查下代码,这15个阴魂不散的文件又回来了!
好。。。算你狠,磁盘的某处一定有这些文件的备份和一些代码,代码在编译的时候把这些文件copy过来。我得做个检索把它抓出来。我扫描了c的include目录。这套系统除了内核我们都有源代码,所以这个目录很大,检索这些文件需要些时间。
第一天就这么过去了。
第二天
文件检索没有结果!所以我猜测这些文件要么做了文本加密,要么就藏在一个二进制的lib里,因为没法用md5去验证是否文件被修改过,我只好对所有lib文件进行文本搜索。
第二天就这么过去了。
第三天
还是没有结果。好吧,一定是加密了。我的逐级扒拉#include关系,查找可疑的加密串,很花时间。我警告学校的计算机部门他们的机器被人搞了,他们不信我。。。这可以理解。
人肉检索了所有的include关系之后,无果。我断定一定是编译到某个lib里了。还好,我们有所有lib的源代码。
第四天到第六天
最艰难的部分是和学校说明白他们被搞了,最终,我们终于说服了Mark(他是教务长的女婿所以当上了管理员),让他来学习下怎么编译这些库。他花了三天功夫,重新编译了所有的lib,赞!
接下来我开始编译我改好了的源代码,执行,OMG,它又出现了!!简直就是魔法!!
博士也很不开心,他已经花了好些钱,觉得我不应该继续死磕,建议我重新从头写一份。我失魂落魄的坐在电脑边上,很懵圈,很茫然,对博士说“我觉得你说的对,我重写吧,这样还能少花点时间。” 他点点头,说:“太好了,咱们明天开始。“
第七天
去他妈的。老子不认输!This way or no way!我也不要钱了,再给我点时间!这是战争!
第八天到十四天
我开始变聪明了,我着手从汇编代码研究curses库,觉得它被人动过了手脚。虽然我还(暂时)不懂3B2汇编。我开始学!我读了6天手册,看懂了汇编,结果没发现什么异常😂
第十五天
我顿悟了,是编译器,一定是编译器在作妖!每次编译的时候都会偷偷把代码注入进去。
啊哈,我抓住他了,编译器的源代码我有啊,看我在里面发现了什么:
- 在fopen上挂了hook,一旦发现有打开的文件里有博士的问卷
- 把15个文件放到编译目录
- 编译并生成目标文件
编译器被修改了,当编译目标程序的时候编译器会自动引入并编译编译器修改人希望编译的c代码(懂了吗?)。
几天后,AT&T的工程师上门,带着载有编译器的源代码的软盘。我从头编译一遍编译器,把见鬼的老编译器文件替换掉。大功告成!
然而并没有!经过再深入的研究发现,中了毒的编译器在编译编译器的时候,会自动引入并编译编译器修改人希望编译的c代码(这次是编译器源码)。以前的那个研究生的编译器在编译自己的时候会自己给自己下毒!最终我们从别的机器上copy了一份编译器,解决了这个问题。
我们还发现这个研究生还修改了/sbin/login的源码,给自己留了个后门,他可以从任意机器上root进来。这一点,终于引起了学校计算机中心的注意。
这厮真tmd天才!