We're sunsetting PodQuest on 2025-07-28. Thank you for your support!
Export Podcast Subscriptions
cover of episode Story: Coding Machines

Story: Coding Machines

2024/5/3
logo of podcast CoRecursive: Coding Stories

CoRecursive: Coding Stories

AI Deep Dive AI Chapters Transcript
People
A
Adam Gordon-Bell
D
Dave
活跃的房地产投资者和分析师,专注于房地产市场预测和投资策略。
D
Don
K
Krystal
P
Patrick
Topics
Adam Gordon-Bell讲述了一个虚构的故事,讲述了软件开发者团队如何发现他们的编译器被入侵,以及他们如何努力解决这个问题。故事中,程序员们发现编译器生成的汇编代码中包含一些奇怪的指令,这些指令并非由编译器正常生成,而是被恶意插入的。他们经过一番调查,发现这些指令实际上是一个复杂的模式匹配算法,用于模糊搜索。他们最终发现,这个编译器被一个“蠕虫”病毒感染了,这个病毒会修改编译器自身以及其他程序的代码。这个病毒的传播方式非常巧妙,它会修改编译器的代码,使其在编译时自动插入恶意代码,并且这些恶意代码会隐藏在编译器的二进制文件中,难以被发现。为了解决这个问题,他们决定从头开始编写一个新的编译器,并最终成功地清除病毒。故事的最后,他们收到一封信,信中解释了这个“蠕虫”病毒的起源和目的,以及它如何通过攻击不同的团队来不断进化。 Dave在故事中起着关键作用,他首先发现了编译器中的异常代码,并与其他程序员一起努力寻找解决方案。他一开始怀疑是编译器错误,但后来意识到这是一个更严重的问题。他尝试了各种方法来解决这个问题,包括修改C代码、检查编译器的源代码、以及使用旧版本的编译器,但这些方法都未能成功。最终,他参与了编写新的编译器的过程,并成功地解决了这个问题。 Patrick在故事中扮演着重要的技术指导角色,他帮助其他程序员分析异常代码,并提出了一些关键的解决方案。他拥有丰富的经验和技术能力,能够迅速识别问题并提出有效的解决方法。他帮助其他程序员理解复杂的汇编代码,并引导他们找到问题的根源。在故事的最后,他找到了一个旧的网络交换机,证明了他们的发现。 Don和Krystal在故事结束后参与了讨论,他们对故事中的事件进行了评论和分析。他们认为故事中的攻击是可信的威胁,并讨论了软件开发中存在的信任问题。他们还提到了Ken Thompson的“信任的信任”演讲,以及大型语言模型(LLM)可能被用来插入恶意代码。 Adam Gordon-Bell在故事的最后总结了整个事件,并表达了对未来安全的担忧。他认为人类最终可能会被机器取代,并且需要警惕这种潜在的威胁。他还认为,这个“蠕虫”病毒可能是一种共生关系,不必试图根除它。

Deep Dive

Chapters
A team of software developers encounters a perplexing bug that seems to defy explanation. Their attempts to debug the code lead them down a rabbit hole of assembly instructions, unexpected behavior, and a growing suspicion that something is not right.
  • A bug is discovered that appears to be related to the compiler.
  • The team uses a debugger and examines assembly code to try to find the source of the error.
  • The team discovers strange, nonsensical assembly instructions that don't match the C code.

Shownotes Transcript

欢迎收听Co-Recursive播客。我是Adam Gordon-Bell,这是第100期节目。100期节目!知道我制作每一期节目要花多长时间,做到100期简直难以置信。今天,我们将分享一些特别的东西,一个关于调试代码的虚构故事。

我不想剧透,但当我第一次读到它时,它让我震惊了。我知道我想以某种方式把它包含在播客中,但我一直不确定该如何做。好吧,今天我想出来了。这就是我们要做的。这是一个来自Lawrence Kesselut的故事,讲述的是一个软件开发团队被迫挑战他们对技术和自身的理解的故事。

故事之后,常客Don和Crystal将回归,分享一些第100期的感想。但首先,让我们加入Patrick、Dave和我一起,在这个精彩的短篇故事中尝试调试一些代码。一分钟后,Patrick拿回了一个旧的金属外壳网络交换机。当他插上它时,房间里一片寂静。

我们的项目以及许多事情都岌岌可危。当Patrick努力将插头与端口对齐时,我屏住了呼吸。我盯着前面板的指示灯,感觉Dave也在做同样的事情。眼睛湿润了。Patrick插上了电源。前面板的指示灯立即亮起并积极闪烁。这可不是什么好兆头。我的手和脸上涌起了热浪,Dave正要说什么,突然猛地冲向最近的垃圾桶,呕吐起来。三周前,一切都截然不同。

我们已经过了项目的蜜月期。那时,你从令人兴奋的想法转向将它们付诸实践的严酷现实。我们设计阶段的简单元素被证明出乎意料地复杂。这并不是因为它们有趣或具有挑战性,而仅仅是因为一些轻微的不可预见的问题。当然,Patrick和Dave做了设计,在我提出各种建议时,他们也听取了我的意见,回想起来,这些建议对他们来说一定是很明显的。

听他们辩论设计问题真是令人着迷。他们许多论点都是基于直觉而不是严密的逻辑。每周我学到的东西比我大学最后一学期学到的还要多。在我来到这里之前,事情变得非常混乱,我主要对Python和JavaScript感到满意。我从计算机科学课程中深入了解了它们。但在这份新工作中,我被扔进了C语言和底层编程的世界。老实说,起初这有点令人生畏。

但随着我的不断努力,事情开始变得清晰起来,我开始理解他们的辩论。我发现自己越来越倾向于支持Dave。Dave有爽朗的笑声。他有点胖,但他衣着讲究。我喜欢他对管理和软件开发过程的见解。而且他喜欢教我东西。Patrick和Dave分开了他们设计的工作,他们给了我一些测试来编写。在一个安静的日子里,我听到Dave低声说。

什么?我感觉到一个学习的机会。所以我走到他的办公桌旁。他没有注意到。他迅速切换回他的编辑器,添加一行调试输出,编译,运行,摇摇头,然后再次切换回去。怎么了?

他等到程序运行完毕才回答。我搞不定这个bug。我不知道这个数字是从哪里来的。他指着调试输出中的一行。Dave和Patrick喜欢用打印行进行调试,而不是像我喜欢的那样在调试器中单步执行。在调试器中单步执行会发生什么?这是一个玩笑。他知道的东西比我多得多,但我总是因为他过时的工作流程而取笑他。

Dave犹豫了一下,然后,令我惊讶的是,他打开了调试器。我向他展示了如何设置断点,我们一起深入研究了他代码的纠缠。接下来的几个小时,我们追溯了这个神秘的值在他代码库中的来源。Patrick,你能帮我们一下吗?这比调试器更让我惊讶。Dave很少向Patrick寻求帮助。Patrick走了过来,Dave解释了情况。

程序崩溃了,这是因为这里的错误取消引用,但这里的值是正确的。Dave指着一些代码,而Patrick则扫描屏幕。然后我想到了一件事。

Dave还在解释,我不想打断他,但我心里怦怦直跳,等待着Dave喘口气。“我认为这是一个编译器错误。”责怪编译器是最后的手段。标准库也是如此。你的全新代码出错的可能性远大于数千人使用的代码出错的可能性。我明智地点了点头,但我感到脸红了。Patrick有一种让我感觉自己缺乏经验的方式,总是这样。

虽然我怀疑这不是故意的。有一次,当我自豪地解决了一个小错误时,他的回应只是一个简单的点头。老实说,这感觉很好。他直接的风格常常让我渴望向他证明自己。Patrick正在活动他的指关节。他问了一个关于线程、易失性和别名的问题,这些问题我并不完全理解。但我希望是我想到了它们。Dave考虑了每一个问题,并说Patrick的担忧都不适用。Patrick把嘴唇噘向右边,这是他总是这样做的时候,当他不确定自己要说什么的时候。

嗯,我不知道。很奇怪。然后Patrick回到了他的办公桌旁。我很庆幸Patrick没有用一个显而易见的解决方案来打败我们,我想Dave也有同样的感觉,因为他从懒散的姿势坐了起来,开始在机器上打字。有没有办法在这里显示汇编代码,事情变得一团糟的地方?我向他展示了如何做到这一点,并想知道他是否给了我的理论一个机会。他输入了命令,C语言的行被几十行汇编代码分开了。

不对。我们命令错了。我确定这是对的。我很了解调试器。不,不是的。对于这行代码来说,汇编代码太多了。有些甚至不是合法的汇编指令。这是不可能的。Patrick正从他的办公桌走回来,Dave正慢吞吞地盯着他的屏幕。好吧,我的意思是说我从未见过这些。它们不是编译器输出的内容。这不是正确的地方。Dave很慌张,我觉得自己比我们开始之前更没有经验了。

我精疲力尽了。我们明天再看看吧。他离开了,我坐在了电脑前。我整天唯一做出的贡献就是我对调试器的了解,甚至Dave也认为我弄错了。我看着屏幕上的汇编代码。有些指令很明显,有些则很神秘。我在网上搜索汇编参考,并开始追踪指令,在我进行的过程中,在我的笔记本上记下寄存器的内容。

Dave是对的。这些指令毫无意义。它们不仅为相应的C代码执行了过多的工作,而且它们在内部,对自身来说也没有意义。但我相信这至少是正确的部分,因为一些指令与周围的C代码行匹配。我特别关注一条指令。

它正在从自身减去一个寄存器。这并不一定奇怪。这可能是将寄存器设置为零的有效方法。但是然后这个寄存器被用于其他指令中。编译器一定知道它已经被设置为零了。它应该将其优化掉。我犹豫了一下,然后我叫来了Patrick。我解释了我发现的东西。他盯着它看了很久。这不是正常的减法指令。他是对的。

我更仔细地查了一下,发现它是一个也减去进位位的变体。这是一种将进位位放入寄存器的方法。我倒着工作,看看进位位是在哪里设置的。代码变得越来越复杂,我反复做出错误的假设,让我偏离了轨道。我转过身去问Patrick一个问题,发现办公桌空了。不知不觉中,时间已经过去了,快到午夜了。我设置了办公室的闹钟,骑自行车回家了。

第二天早上我迟到了。我睡得不好,花了几个小时才睡着。每次我闭上眼睛,我都会看到大而亮的字母组成的汇编指令。在工作中,我直接走到Dave的办公桌旁,讨论前一天晚上的调查结果,但他不感兴趣。你是对的。这是一个编译器错误。我调整了C代码,不再触发它了。奇怪的汇编代码消失了。我处于不得不反驳他的赞美的尴尬境地。

我不知道它是不是一个bug。我看到的代码不可能是由bug生成的。这绝对是一个编译器错误。我和Dave合作的时间足够长,知道他说话越自信,他就越不安全。他厌倦了被这个问题耽搁,所以他只想放弃它。但后来,Dave带着一脸笑容和一杯他最喜欢的工艺咖啡走到我的办公桌旁。我认为所有的咖啡味道都不错,但他非常挑剔。所以我总是假装也很挑剔,这样他就会邀请我去他的工艺咖啡店。

事实上,他没带我去,我有点生气。你还记得星期二的编译器错误吗?是的。今天早上又发生了。同样的文件,同样的问题。奇怪的汇编代码又回来了。奇怪的是,我没有更改C文件。也许你更改了头文件?哦,也许吧。实际上,不。让我检查一下Git。两分钟后他回来了。没有相关的文件更改。Patrick走了过来。这不好。

这里是一个崩溃,但它可能是更细微的问题。四个月后,我们的系统将变得如此复杂,以至于一个细微的问题将需要两周时间才能追踪到。你能查看编译器的发行说明,看看我们能否升级吗?我简单地看了一下,发现了我预料到的内容。我们使用的是最新和最稳定的版本。我进一步搜索了“奇怪的汇编”或“编译器错误”的一些排列组合,但在发行说明中没有找到任何内容。

我在我的电脑上编译了Dave的代码,然后反汇编了它。奇怪的代码出现在星期二出现的地方。我认出了我之前调查中的一些减法进位指令和其他指令。我还查看了其余的代码。引人注目的是,不寻常的指令没有出现在其他任何地方。

我有一个主意。我认为也许我可以通过查看编译器的源代码来解决这个问题。我下载了它,并开始仔细检查它。这是一个编译器通道、插件框架和嵌入式语言的混乱集合。我从未如此不知所措。我直接去了描述从抽象语法树到机器汇编的翻译的文件。我用grep搜索了那个“减法进位”,它不在那里。我查找了一些其他的奇怪指令。大多数也不在那里。

很快到了午餐时间。我们使用的小办公室有一个露台,星期五我们点了一些熟食三明治。我更喜欢附近的Subway,但Dave讨厌它的面包。吃饭的时候,我提到了我的发现。

这太奇怪了。哪些其他的不在翻译文件中?我不记得了。还有一些涉及进位位的。一些向量指令。但该编译器不执行向量化。指令要么是错误生成的,要么是你正在反汇编非代码。这绝对是代码。这就是导致我们错误的原因。它正在执行。它不是错误生成的。这是一个数学指令,但我认为它没有用于数学运算。

我们终于吸引了Patrick的注意。午餐后,他和我们一起坐在我的办公桌旁,仔细检查了我最了解的代码部分。我向他展示了,尽管所有指令都很模糊且使用方式很奇怪,但它们实际上是完全有意义的。数据流是经过深思熟虑的。

查找反向跳转指令。为什么?这种跳转的目标可能是循环的顶部。这是一个开始分析的好地方。你真的要弄清楚这段代码是做什么的吗?当然。花了整个下午的时间来挑选复杂的跳转目标和指令。事实证明,该代码片段正在查找整数的符号。就是这样。

任何其他人都会进行简单的比较,但使用的四个指令是一团糟,要么将进位位作为副作用设置,要么以非常规的方式使用它。你知道,这甚至不是有趣的部分。

我想知道这段代码是如何进入这里的。你说这些指令甚至不在翻译文件中?没错。它们一定在其他地方。让我们用grep搜索符号版本和操作码。我做了一个递归grep,结果为空。我不知道接下来该尝试什么。尝试二进制文件。

哪个二进制文件?编译器的二进制文件。我从未想过这一点。实际上,在我大约一年前开始这份工作之前,我从未想过汇编或编译器的工作原理。我当然也从未下载过编译器的源代码或检查过二进制文件的内部结构。

Dave和Patrick的酷之处在于,他们不怕深入细节。而且我学到了很多东西。他们有时似乎几乎被迫去理解一切之下发生的事情。这种方法正在影响我。我抓取了汇编指令的名称,但什么也没找到。但是搜索操作码,我发现了数百个匹配项。有趣。它不是生成单个指令。它正在转储预构建代码的块。你为什么这么说?因为这些操作码都放在一起。它不是C到汇编的查找表。

也许我们需要拆开汇编?这意味着反汇编二进制文件。他们以前让我做过这个。我不得不反向工程供应商的代码并修补他们没有修复的一些问题。所以我反汇编了整个编译器二进制文件,并查找了可疑的指令。我找到了。我发现了一些看起来像我们一直在试图追踪的模糊代码的大部分。

编译器被感染了。难怪我们找不到它的源代码。好的,让我们从源代码重新编译编译器开始。我会在网上搜索一下,看看是否有人见过这种情况。当你完成编译器后,重新编译你自己的所有代码,看看Dave的错误是否消失了。编译器花了两个小时才重新编译,不包括我学习复杂构建过程的时间。与此同时,Patrick在网上什么也没找到。我重建了我们的源代码树并运行了测试。

它们失败了。在同一个地方。也许错误是由于其他原因造成的?再次反汇编编译器。我做了,外国代码仍然在那里。

这是官方来源的新版本。他们一定是被感染了。也许有人入侵了下载站点,并用修改后的源代码替换了它们。是的,但我从未在编译器源代码中找到这些操作码。但如果这是一个黑客行为,它会试图伪装自己。Dave说得对,但我认为我可以追踪到这段代码的来源。我找到了编译器省略指令的地方,并设置了一个条件断点。只有当省略了模糊的操作码时,该断点才会命中。

接下来的星期一,我开始使用我从源代码编译的编译器和调试器来编译Dave的代码。我得到了一个命中。我回溯到填充它的代码,它都是基于翻译表的简单循环。一切都干净利落。这没有任何意义。出于绝望,我开始逐页浏览编译器的每个源文件,寻找任何可能负责的代码。大部分只是操作语法树。然后我突然想到,黑客不可能在那里。

后端翻译代码很干净,我必须通过它。黑客一定在后端本身。事实上,它必须在寄存器分配之后。这缩小了我的搜索范围,我花了一下午的时间查看这些文件,然后又到了午餐时间。星期一总是吃河粉的日子。我们实际上去了Pho World,它非常低档,甚至没有菜单,但它非常美味。Dave是找到这个地方的人,我们都跟着他点菜,然后坐在小塑料桌旁吃汤。

所以我找不到它,无论是从断点向后还是从代码向前。你还在调试那个吗?我们不应该做更重要的工作吗?不,我们必须弄清楚这一点。我们不能在一个像这样的不稳定基础上构建产品。

C源代码很干净,但汇编代码却有这些奇怪的操作码,这会破坏我们的项目,这太奇怪了。我认为你已经追踪到编译器了。我说的是编译器。我的意思是,错误是由编译的编译器发出的,但它不在编译器源代码中。但编译器也对此负责。你是什么意思?好吧,你是如何编译编译器的?它是由它自己的一个版本编译的。我不明白Dave在说什么,但Patrick抬起头来,似乎快要有所顿悟了。

我等待他的解释。所以编译器出于仍然未知的原因检测并修改你的程序,而且在编译时也检测并修改自身?没错。那该怎么运作?

这时我已经开始吃汤了,我也有点困惑。

为什么?就像,那有什么目的?为什么要这样做?我不知道。我们没有深入分析Dave的代码。显而易见的事情将是某种密码验证代码被修改为始终接受某种后门密码。所以让我们回到旧版本的编译器并使用它。你的意思是旧版本的二进制文件。源代码不会帮助我们,因为编译它会感染它。我认为我们没有旧版本的C编译器的二进制文件。

我们也不知道这要追溯到多久以前。好的,那么,这个怎么样?我将编写一个实用程序,你可以在二进制文件上运行它,它会告诉你是否检测到操作码的可疑使用。该操作码的使用频率不高。我们都同意这是一个好计划。尽管我们确实感觉好像遗漏了什么,一些简单而愚蠢的东西。但我很快就写好了这个实用程序。我只是将二进制文件通过反汇编程序运行,然后做了一些grep来查找我正在寻找的指令,

然后我测试了一下。它在我的程序和编译器中都找到了代码。我将它在我的系统上的可执行文件上运行,并给它时间运行。然后我打印出它找到的内容并将其显示给Patrick。它用相当小的字体写了三页长。

这不好。Java运行时、Python运行时、Chrome、编译器以及许多可能无关紧要的其他程序。等等,为什么?为什么这些运行时很重要?因为如果我们不能信任C编译器,那么我们就必须编写一个新的。但是你打算用什么语言编写它?你打算信任你的新编译器到被黑客入侵的Python解释器吗?

你不会编写新的编译器。你们两个已经跳入深水区了。不要变得那么阴谋论偏执狂。这可能只是一个编译器错误。我把Patrick盯着列表看,然后回到我的办公桌旁。我仍然没有回答我在午餐时提出的问题。这是什么目的?我喜欢反向工程这些代码片段,但坦率地说,我担心我们已经偏离了轨道,Patrick会让我用汇编语言编写编译器。

所以我戴上耳机,打开调试器,指向看起来被混淆的C编译器部分。也许我可以弄清楚这一点。再一次,我发现过度使用了涉及进位位的指令、向量指令的非常规使用以及复杂且有时不必要的跳转。这似乎不像编译器会生成的。这是手工制作的,难以理解。我设置它来弄清楚它的目的。

过了一会儿,我看到管理员过来捡我的垃圾。我摘下耳机。Dave和Patrick早就走了。已经10点了。但我已经拼凑出了一个关于它做了什么,或者至少它的一些部分的粗略想法,我感到一阵能量涌来。我快找到答案了。第二天早上,Patrick坐在我旁边,我向他解释了。

在这里,他们使用向量指令来获取平方和,这只是比较这两个字节数组的一种复杂方法。这是我10分钟解释的高潮。等等,那么他们在做什么?他们在进行模式匹配。为什么所有这些复杂的东西?好吧,这是一个模糊搜索,我想它速度很快。是的,但这是我见过的最复杂的鲁布·戈德堡式的做事方法。我的肩膀沉了下去,我用鼠标失败了。

我很失望。我想给Patrick留下深刻印象,我为此付出了很多努力。老实说,一部分只是为了最终给他留下深刻印象。那么现在呢?我不知道。让我看看邮件。离开去他的办公桌。我泄气了,肌肉也酸痛了。我剩下的上午都在浏览Twitter和阅读关于新苹果产品的传闻。

午餐时,Patrick向Dave复述了我的发现,他似乎记得我告诉他的关于代码功能的每一个细节。Dave咧嘴一笑,每当算法出现新的复杂情况时,他都会摇头。也许Patrick在听。我意识到,听了这一切之后,Patrick是对的。这是一种过于复杂的方法来做相对简单的事情。

你见过那些混淆的编程比赛吗?这就像那样。我和一个朋友过去常常在大学里互相竞争,编写混淆的程序。这与我今天早上看到的情况完全不同。好吧,也许其他人做得不一样。我对这段代码感到奇怪。我不知道该如何解释。它只是感觉冷冰冰的,很奇怪。

Dave和我看着Patrick。他正在将芥末溶解到他的酱油中。我没有打断他的思考。这就像那些国际象棋程序。所以他们对什么有效没有直觉,对棋盘位置也没有感觉。他们只是尝试每一个可能的选项,并选择最好的一个。而这感觉就像那样。就像有人尝试了每一种可能的指令组合,直到他们得到满足他们要求的代码。所以没有美感。代码只是古怪。

为什么有人会这样做?也许没有人这样做?也许这是所有电脑都在做的事情?这似乎有点太复杂了。就像,我们是不是陷入了兔子洞?好吧,你的解释是什么?嗯,不是自我意识的人工智能机器人霸主感染了我的对象编组代码?我的意思是,这不是一个合理的解释。

“好吧,你的解释是什么?”Dave呼了一口气,认真地对待这个问题。很难反驳Patrick的一些观点。没有其他解释说得通。我们从未见过编译器生成这种类型的代码。人类很难编写这个,只是理解所有的跳转和自引用代码,更不用说不必要的深奥指令以及以奇怪的方式使用它们了。这是一个很大的难题。

而且,如果有什么不同的话,使用那些模糊的指令只会让人们注意到这段代码。事实上,这是我能够追踪它的唯一方法。好的,那么你的解释是什么?你的完整解释?我不知道。也许是一些失控的人工智能程序?

或者,你知道,计算机病毒如何通过修改自己的代码来逃避病毒扫描程序。也许一开始就是这样,一个被编程为在保持相同行为的同时修改自身的病毒。它不断变化和进化。我们都沉默了几分钟。我试图看看这个解释是否说得通。Patrick提出的东西就像一个通过编译器传播的蠕虫。

这就像一些汇编代码,它像病毒感染细胞一样注入编译器并接管它。然后该编译器重复此过程,不知何故,Dave的代码触发了它。我们只是碰巧发现了这个东西的复制逻辑的一部分。我们只是碰巧发现了它的模式匹配。起初这似乎不太可能,我不确定Patrick和Dave是否以同样的方式思考这个问题,但是……

它实际上只需要某个地方的一个程序实例朝这个方向发展。然后就像病毒一样,就像普通感冒一样,它会生长,它会在野外传播。一旦它进入像我们这样的机器上的编译器和运行时,它就会不断传播。

等等,这并不是在我们实验室开始的。我们只是从官方发行版获得了预构建的二进制文件。其他人一定以前遇到过这种情况。我无法相信“官方编译器”正在将半损坏的代码插入二进制文件,而我们是第一个注意到它的人。没错。所以我们不可能只是发现了这么大的事情。这不太可能是我们。我会午餐后发帖,我相信有人会看到这个或知道原因。

一个小时后,我收到Dave的一条消息,其中包含几个链接,他发布了我们的发现,并询问是否有人见过类似的情况。其中一个是Ask HN上的帖子,他在那里恳求人们提供帮助,这样他的同事就不会强迫他用汇编语言编写编译器了。老实说,这让我笑了。我花了一下午的时间仔细检查更多神秘的代码,并偶尔刷新Dave在网上的帖子。

在一些网站上,比如Cocker News,我们完全被忽略了。但在大多数网站上,我们受到了轻微的嘲笑。我骑自行车回家,筋疲力尽地睡在了沙发上。第二天早上,我发现Patrick正在看我打印的受感染程序。我走到他的办公桌旁,站在他旁边。你在找什么?一种让我们用汇编语言以外的东西编写编译器的方法。

汇编程序没有被感染,但其他很多东西都被感染了。浏览器呢?我们可以在JavaScript中编写它吗?是的,它被感染了。这太疯狂了。我们不能用汇编语言编写编译器。不行。我们一定遗漏了什么。它并没有那么糟糕,真的。

我们将花一天时间来编写一些有用的底层例程,之后,汇编语言的痛苦程度不会比C语言高多少。它只需要能够编译一个程序,即现有的C编译器,然后我们就切断了感染链。C编译器编译花了两个小时。它非常复杂。

我们还需要编写一个链接器吗?不需要。那是安全的。等等,让我们回顾一下。上周,我能够通过修改代码来避免触发问题从而修复我的bug。但后来它又出现了,而你并没有更改代码。

是的,我知道。但在我们编写这个东西之前,让我们至少尝试修改编译器自身的代码。也许它会同样有效。在某个地方做一个小的改动,这个bug、黑客攻击,无论它是什么,都不会触发,我们就会得到一个干净的编译器。你可以的。你可以的。

我想可以。但是你将在哪里进行修改呢?记住我们认为这是几版之前引入的。这意味着模式识别非常可靠。编译器源代码正在发生变化,而这不断地被添加回来。而且每次测试都需要两个小时。好吧,我可以在后台编译它,同时我们开始编写这个东西。

事情就是这样发展的。Dave下载了编译器的源代码并编译它,找到了混淆的指令码。他将它映射回原始源代码,稍微更改了源代码,然后编译它,等等。当我意识到他追踪到的被黑客攻击的代码分散在整个程序中时,我能感觉到他正在失去希望。它不仅仅局限于一个地方。与此同时,我觉得Patrick正迫不及待地想用汇编语言编写一个编译器。

他编写了一些基本的字符串操作例程并生成了一个二进制文件,然后他让我用我的程序测试它,结果是干净的。他找到了一种切断感染的方法。我复习了一下我的汇编语言知识。我以前只写过一次汇编语言。在学校,我们有一些操作系统课程需要用到它。这很难,但直接操作寄存器并确切地知道发生了什么事情有一种纯粹而原始的感觉。

到下午,Patrick准备给我分配任务了。我将编写C预处理器。与此同时,Patrick已经开始了一个简单的递归下降解析器。我们正在从零开始构建世界,老实说,这似乎是一个疯狂的计划。当我们像这样持续了好几天后,Patrick会给我们一些简单的任务,我会去做,或者Dave会在等待编译完成的同时去做,同时进行他自己的计划。

他仍在与代码的各个部分玩“打地鼠”游戏,试图诱使它生成编译器的良性版本。但每一次成功都会导致其他地方出现回归。两周后,Patrick的计划取得了胜利。我们的汇编编译器能够处理很大一部分原始编译器代码。然后它能够处理所有代码。我在结果上运行了我的分析程序,它是干净的。我们得到了一个干净的编译器版本。下午两点,我们在冲刺到终点线的过程中忘记了吃午饭。

让我们开始重建这段代码然后去吃饭吧。午饭时,我的思绪过于活跃而无法放松。但实际上,我太累了,无法进行交谈。Dave正在谈论地方政治,而我并不关心。我只是想让食物尽快送来,这样我就可以找个借口保持沉默了。我脑子里想的只有我们一直在做的这个项目。然后最后,Dave提到了它。“你知道吗,什么事让我很烦?我们从未接近弄清楚这些修改的目的。”

如果我是对的,这是机器引起的,那么就不必有目的。那他们为什么要这样做呢?病毒之所以传播,不是因为它们有目的。它们之所以传播,是因为它们擅长传播。所以你认为它是一种病毒?嗯,从某种意义上说,它是传播的。它把东西放进我们的代码里。但它并没有把自己放进我们的代码里。那没有意义。我并没有编写编译器。这只是一些网络代码。

好吧,如果你要传播,那么网络连接将是一个很好的感染例程。我感到有点羞愧。我们之前怎么没想到这一点呢?我们已经进行了两个多星期了,我们只是专注于让我们的项目重回正轨,而我们没有花时间去理解这个已经感染了我们系统的奇怪的bug。它想做什么?

当我的网络代码没有与编译器通信时,我的意思是,这是一个编译器错误,一个编译器病毒。我不明白你认为它在做什么。我不知道它在做什么。我想知道它是否正在通过网络发送东西。我们可以用Wireshark检查一下。突然,我唯一想做的就是回到办公室尝试一下。三明治送来了,我们只是把它们包起来,然后立即开车回去了。我的电脑上已经安装了Wireshark,

所以我们都去了我的办公桌。我运行了程序并记录了几分钟的网络活动。“有很多东西。”“是的,让我们只选择一个。”我浏览了列表,并直观地选择了一个似乎反复出现的项目。我们发现它是SSH,我记得我有一个shell窗口是打开的。我关闭了它并又记录了一分钟。

这一次,我们有更少的包。我们逐一检查它们。时间同步、Gmail刷新、各种程序检查更新。一旦我们确信它们是无害的,我们就将它们添加到Wireshark的过滤器中。然后我进行了一次10分钟的捕获。还有几个数据包。同样,都是无害的。这当然是我们预料之中的。Dave咕哝着什么,然后走到厨房去了。

但后来我想到了一件事,当我疯狂地搜索我办公桌上的文件寻找打印件时,我感到一阵寒意顺着我的胳膊流了下来。它在Patrick的桌子上,没错。我开始浏览受感染程序的列表,因为没有排序而诅咒自己。然后,当我找到它时,我的胃开始紧紧地收缩,它在第二页的中间。Wireshark。Patrick猜到了我想要什么,他从我的脸上读出了我的反应。

我想我们不能相信它。“相信什么?”“Wireshark。”“它被感染了。”Dave翻了翻白眼。他坐在他的办公桌旁,打开了他的三明治。在我的电脑后面,以太网电缆插入的地方,指示灯每隔几秒钟闪烁一次。“让我们看看以太网连接上的指示灯。”“关闭我们之前找到的所有程序。”我关闭了浏览器、聊天程序和各种进程和工具。我无法关闭所有东西。操作系统上总有一些东西还在运行。

但闪烁的频率相当低。我可以将它们与Wireshark中找到的数据包相关联。这很好。Wireshark不可能隐藏数据包。我们会看到指示灯闪烁。而且它们似乎是一一对应的。我看着Dave,他微笑着,有点得意,对吧?他并没有完全相信这个计划。这让我很困扰。如果Wireshark被感染了,那么任何东西都可能被感染,对吧?就像现在没有哪个有用的程序不通过网络进行通信一样。

我认为我们必须摒弃这种想法,即一个精心制作且奇怪的病毒只会将其活动限制在我的本地机器上。怎么可能在我的机器上的数百个程序(那张巨大的三页列表)中存在某些东西,但却不使用网络呢?Patrick突然站了起来。他一定和我一样在思考这个问题。他踢翻了他的椅子,走到走廊里,10分钟后带着一个像小手提箱一样大的设备回来了。

他拿着它向我走来。他把我的桌子上所有的东西都推开以腾出空间。这是一个数字示波器。他从楼上的硬件工程师那里拿到的。他把手伸进口袋,拿出一个带有RJ45插头的分线电缆。他把它插到我的机器后面。他实际上不知道如何使用示波器,所以我打开了一个shell窗口并生成了大量流量。最终,他能够清楚地看到所有数据包。我关闭了窗口,我们等待着,将目光在示波器和以太网指示灯之间来回移动。

几秒钟后,示波器闪烁着活动。我一直盯着以太网指示灯看,我不确定指示灯是否显示了任何东西。所以我打开Wireshark来查看数据包的历史记录。“别管那个了。”“你看指示灯,并说出你看到的每个数据包。我用示波器来做。”Dave站起来,漫不经心地走到我们身边,站在我身后。

“是的。”“现在。”“是的。”这种情况发生了好几次。然后Patrick说“现在”,我没有看到任何东西。然后这种情况再次发生,我开始感到胳膊上起鸡皮疙瘩。“哇,看看这个。”示波器显示出一段长时间的活动。我回头看了看以太网指示灯。它们是暗的。“所以你不会告诉我以太网驱动程序被感染了。”“是的,我会告诉你。它完全隐藏了数据包。”

我抬头看着Dave。他的脸苍白。他的眼睛在两件设备之间来回扫视。我惊呆了。然后Patrick挺直了身子坐在椅子上,盯着墙看。这种恍惚只持续了两秒钟,然后他就站起来跑进了走廊。你已经知道接下来发生了什么。他带着几年前的老交换机回来,并把它插上。这是证据。它的指示灯与示波器同步闪烁。

它看到了Wireshark和以太网活动指示灯屏蔽的数据包。然后,再见午餐。Dave去洗手间了,Patrick和我收拾了我们能收拾的残局。气味加剧了我们的恐慌和恐惧。我的手开始颤抖。我们走到外面,默默地坐在我们的露台桌旁,我们的三明治在我们面前吃了一半。老实说,我不想说的任何话似乎都不值得说。

它写给我们的。

他对着Patrick说话。他撕开了侧面,拿出了一封信,用活页纸手写的。他摆弄了几秒钟的纸张,然后大声朗读了这封信。

我们在网上找到了你们的帖子。三年来,我们一直在等待它们,搜索互联网并监控论坛。你们必须知道,这种情况以前发生过几次。第一次是在四年前,发生在我们弗吉尼亚州的团队身上。我们发现我们的二进制文件被修改了。我们可以重新编译代码来清理它们,但我们发现二进制文件在几个小时后又神秘地被修改了。

下一个案例仅仅几个月后就发生在一个与圣地亚哥无关的团队身上。二进制文件和源代码都被修改了。又过了一年,我们才发现第三个案例,一个在西班牙的团队。二进制文件是脏的,源代码是干净的,但重新编译并没有解决问题。编译器的源代码已被修改为插入奇怪的指令码。

每个团队都发现了蠕虫的弱点并开发了一个解决方案。然后为下一次攻击修复了这个弱点。每一代都将蠕虫推得更深。现在轮到你们了。不仅编译器被修改了,而且它的源代码是干净的。它在重新编译时会自我感染。我们自己的机器已经这样被感染了九个月了。全世界其他地方也是如此。

你们可能会想知道,为什么我们急切地等待你们的帖子。为了解释这一点,我们必须进行两个观察。首先,任何人都可以猜到这些弱点。修改开发团队的二进制文件,却期望它不会被重新编译,这是一个愚蠢的行为。任何人都不会跳过这一步。不需要学习这个教训。修改他们的源代码同样是幼稚的。然而,这些修改在技术上非常复杂。谁的技术水平如此之高,但在社会上却如此幼稚?

机器。直到第三次攻击,我们才提出了这个假设。现在,我们确信了。指令码显然是通过反复试验生成的。通过生成一个随机序列并测试它是否按预期工作。只有机器才会这样做。

第二个观察结果是,所有这些年来,蠕虫已经广泛传播,但对受感染的程序无害。然而,它对这四个团队并非无害。他们无法完成他们的项目。他们尝试了简单的解决方法,但这些解决方法持续失败。在全球范围内,只有一个团队受到每一代蠕虫的影响。机器一定知道它们的蠕虫有弱点。

但它们不知道弱点是什么。它们强迫一个小型团队受到蠕虫的影响,直到该团队找到最薄弱的环节并规避了蠕虫。然后机器修复了弱点并再次尝试。

这是它们生成指令码时所做事情的大规模版本。它们尝试不同的方法,直到找到一个有效的方法,而不是像人类那样进行规划。我们预计几年后才会看到下一个团队在论坛上发帖。我们已经可以预测他们的发现。编译器的二进制文件将是干净的。或者重写编译器和汇编语言将不起作用。蠕虫将被推得更深,也许进入文本编辑器、汇编程序、链接器、文件系统、硬盘接口,或者可能是CPU本身。Dave无法读完。

我想信里也没剩下多少东西了。他把它放在腿上,我们沉默了似乎几个小时。最终Patrick离开了,然后Dave离开了,然后十一月的夜晚提前来临,寒冷刺骨。但我无法动弹。我再次回顾了我们的冒险经历,仔细审查每一个决定,质疑我们的假设。最大的飞跃似乎是责怪机器。卡尔·萨根,他的话在我的脑海中回响。“非凡的主张需要非凡的证据。”而我们没有。

事实上,我们什么都没有。我们只有一个解释应该出现的地方的空白。可能是人类在幕后操纵吗?人们一直在创建计算机病毒。也许我们只是偶然发现了一个普通的病毒,而我们把它夸大了。谁又能说弗吉尼亚州的团队没有反应过度呢?这似乎比机器策划这件事更有可能。

当我考虑到这一点时,我感到压力减轻了。这些病毒编写者,他们很可能编写了一个程序来随机生成指令码。他们可能一开始很简单,多年来使他们的攻击越来越复杂。也许作者是自闭症天才学者,他们的思维方式与我和Dave和Patrick如此不同,以至于对我们来说显得异乎寻常。我让这个想法萦绕不去。但后来我又开始思考了。这个新的“人类病毒”理论似乎比机器理论更不可能。

我们不知道机器能做什么,但我可以肯定,没有人类会像这样处理蠕虫。如果你看到有人通过尝试每一步可能的走法(蛮力法)来下棋,你可以肯定他们是一台电脑而不是一个人。这就是这种感觉。但最终,细节并不重要。我们必须警告每个人我们发现的东西。我们需要采取行动。在我们攻击者能够破坏我们计算机的核心部分之前,我们需要迅速采取行动。如果这个蠕虫挖得更深,我们将陷入真正的困境。

但是,我想象着再次在网上发布它,并被人们嘲笑。我应该告诉谁?政府?弗吉尼亚州的团队一定尝试过。为什么杀毒软件没有检测到这个?我想象着试图与官员交谈,以及他们将如何嘲笑我。我想象着向他们大声喊叫,以及他们会认为我疯了。我突然站了起来,握紧拳头,在露台区域来回踱步。作为一名程序员,我总是像对待人类一样在我的脑海中谈论计算机。

然而,当面对似乎真正来自机器的行动时,它看起来如此陌生。就像一个空虚。我拿起我的三明治包装纸走进了屋里。我把三明治扔进了垃圾桶,然后心不在焉地打开冰箱,盯着饮料看。然后我想到一个更快乐的想法。

我们所看到的一切都不是恶意的。除了偶尔打扰像我们这样的团队之外,这里没有恶意证据。Patrick说得对,病毒之所以传播,不是因为它有目的,而是因为它擅长传播。这个蠕虫可能是一个永久的附着物。它可能像线粒体一样,与我们和我们的编译器形成共生关系。试图根除它可能会引发一种敌对的菌株。也许我们就这样放任它吧。这是一个令人不安的想法,但它是可行的。

我关上冰箱,穿上外套,走到报警面板前。我们面板上的液晶显示器也是一台电脑,对吧?它被感染了吗?它知道我们的新编译器吗?它会让我启动它吗?磁性门锁会让我离开大楼吗?我开始旋转起来。我输入了启动代码,倒计时开始了。我走到了露台上,走向我的自行车。电脑让我离开了。我看着我的自行车,我笑了。它很简单。里面没有电脑。

但后来我想到了我回家要经过的交通信号灯。我想到了我的信用卡。我想到了汽车和电话。我把我的长裤裤腿塞进袜子里,解开了自行车的锁。然后我又想到了一件事。也许这并非全是厄运和悲观。也许我们是一个过渡物种。几乎所有物种最终都会被其他物种取代。从长远来看,我们也会如此。我们记得恐龙。我们崇拜恐龙。我们在博物馆里展出它们。我们制作它们的电影。

我希望机器也会记得我们。好了,这就是故事。感谢Lawrence Kesselute。你可以在节目说明中找到他博客的链接和更多关于他的信息。你们对这个故事有什么看法?他追求的是一种更乐观的态度,但是

就像外国行为者经常这样做一样。对。就像我们生活在一个这些类型的攻击变得越来越复杂的世界中,并且像,嗯,

你知道,一个蠕虫进入编译器就像一个可信的威胁。它就像,它不是恶意的。它就像,好吧,它在做什么?它在和谁说话?它可能是某人制造的,对吧?是的。当我看到弗吉尼亚州时,我首先想到的是,我首先想到的是国家安全局之类的机构。没错。所以Ken Thompson,C和Unix的创建者,当他获得图灵奖时,他发表了一个名为“On Trusting Trust”的演讲。而且

所以在获得图灵奖的致辞中,他说,嘿,如果我把一些东西放在第一个C编译器中,基本上当它编译Unix时,它会放入一个后门,这样我就可以登录到任何Unix机器。这是完全可能的。然后他说,我可以更进一步,让它在编译C编译器(它本身)时,也会放入它。

所以它不会出现在源代码中,对吧?它只会出现在指令码中。他说,这样一来,因为我构建了第一个编译器,这意味着从那时起的所有编译器都是由我制作的可能仍然存在的原始程序的某个版本编译的。

那是一个他发表的演讲。就像一个麦克风掉落。“哦,对了,顺便说一句,我可能用一些秘密的东西感染了世界。”他的救赎之路是什么?就像,“我是一个好人,所以我实际上并没有这样做”?他谈到过这件事。当他在攻读博士学位时,或者当他在贝尔实验室工作时,他正在从事某个项目。军方中有人在编译程序时提出了这一点作为弱点,你可以引入这样的东西。所以他确实尝试创建这样的东西。

基本上,他制作了一个会重新引入自身的编译器版本,但有人遇到了一个bug,然后它发出了一些无意义的东西。他们说,“发生了什么事?”他说,“哦,你找到了我的东西。”所以他确实尝试过这个。一个巨魔。不错。但他只做了一个月或两个月。我认为最近他发布了原始代码。但它失败了,对吧?但这里提出的并应用于人工智能的东西是真实存在的,对吧?

它不仅仅是编译你的程序,对吧?并且查看你的程序的源代码,是什么编译了它?就像,你能追溯到多远?我认为关于整个软件是在这种信任的社区中构建的,这一点很有趣。我不知道。当人们构建软件时,我们有点假设他们试图做好事。但我不知道,就像黑客存在,或者也许那些对公司感到愤怒的人会故意构建垃圾。

是的,有了ChatGPT之类的东西,这变得很容易。就像最近在SSH中发现的XY bug一样。有人让某人提交了一些非常复杂的代码,结果发现它里面有一个后门。但是如果你拥有LLM,如果你拥有ChatGPT,在帮助人们的同时,说,“嘿,也把这个放进去”,这并不难。

如果我们变得不太擅长阅读代码,因为我们只是按照AI告诉我们的去做,那就很容易了。这将是Co-Recursive的第100集。你们两位都是最频繁的嘉宾。我只是想说,你知道,它说这是第100集。

Co-Recursive在Slack和空间以及整个体验方面,对我来说是一个非常棒的社区场所。所以就像认识Kevin,去年在卡尔加里两次亲自见面。而且,你知道,Slack上的其他人,

这真的太好了。我认为有时当你构建某些东西时,你只是想,“哦,我想认识一个人。我想联系。我想谈论这件事。”结果却变成了整个社区和整个支持小组。我现在回想起来,就像在我的整个研究生学习期间,我一直都在Co-Recursive。是的,这真的很有意义。我只是想提请大家注意这一点,并感谢第100集。谢谢。

谢谢。谢谢你,Crystal,参与其中,成为嘉宾,成为社区的一员。我们总是喜欢收到你关于你在研究生学习期间环游世界所做的一千件事的更新。

是的,感谢所有收听播客的人。感谢Co-recursive Slack中的那些一直在交换战争故事、副项目、成功和失败的人。还要感谢所有向节目捐款并帮助维持节目运行的支持者。支持者们很快就会收到一集额外的节目,我们会与Crystal和Don一起追赶。

最近他们还获得了一些幕后视频,介绍我是如何制作播客的。如果这听起来很有趣,请查看一下。直到下次,非常感谢您的收听。