Hacking with Go

part1

本篇内容是根据2021年5月份#205 Hacking with Go音频录制内容的整理与翻译

NatalieMat 从 2 位安全研究人员的角度探讨了 Go 中的黑客行为。 Joakim KennedyJAGS 都使用 Go 进行黑客攻击:编写恶意软件、硬件黑客、逆向工程 Go 代码等等。

过程中为符合中文惯用表达有适当删改, 版权归原作者所有.



Natalie Pistunovich: 大家好,欢迎来到我们关于用 Go 进行黑客攻击的这一期节目。这次我们不谈 Go 和安全,或者类似的事情,而是请来了两位偶尔活跃于 Go 社区的黑客,我们将学习如何使用 Go 进行一些黑客相关的活动,无论这意味着什么。

今天与我们一起的是 Joakim,他是一位研究员。在 Intezer 担任安全研究员,并且自 2016 年成为 Gopher,从 2018 年以来一直在追踪作恶的 Gopher。 (Intezer是 以色列的一家安全公司)

Joakim Kennedy: 没错。

Natalie Pistunovich: 很高兴你能加入我们。

Joakim Kennedy: 谢谢。

Natalie Pistunovich: 我们还请来了 JAGS…

Juan Andrés Guerrero-Saade: 这名字很简单。

Natalie Pistunovich: …他是 SentinelOne 的首席研究员 (美国加州的一家安全公司),同时也是约翰霍普金斯大学 SAIS Alperovitch 研究所的教授。这个地方听起来很棒,能介绍一下吗?

Juan Andrés Guerrero-Saade: 这是约翰霍普金斯大学---我们刚刚在 Dmitri Alperovitch 的支持下成立了一个新的研究所……主要从事网络研究和网络安全相关的工作。

Natalie Pistunovich: 太棒了。非常高兴今天能邀请到你。我还和我的联合主持人 Mat 一起主持节目。

Mat Ryer: 大家好。

Natalie Pistunovich: 你们都认识 Mat,每期节目你们都能听到他的声音,即使他不在场,因为他有一首歌叫 Unpopular Opinion(不受欢迎的观点)。

Mat Ryer: 大家好!

Natalie Pistunovich: Mat,很高兴你能在这里。你第一只宠物叫什么名字?

Mat Ryer: 我的第一只宠物?叫 LB。

Natalie Pistunovich: [笑]

Juan Andrés Guerrero-Saade: 你母亲的娘家姓是什么?

Natalie Pistunovich: 这怎么拼?

Mat Ryer: 哦,我明白你们的套路了…… [笑声] 我上当了。你最喜欢的安全问题是什么,Natalie?

Natalie Pistunovich: “其他”。

Mat Ryer: “其他”。是的,这是个好问题。 [笑声]

Juan Andrés Guerrero-Saade: 这已经变成了一个恶性循环。

Natalie Pistunovich: 先生们---请告诉我们,你们通常使用什么编程语言进行黑客活动?

Juan Andrés Guerrero-Saade: 嗯,首先得澄清一下,把我们归为黑客其实是一种宽泛的说法。我们更多的是安全研究领域的人……虽然我们每天都在与黑客或他们的产物打交道,但更多的是处于接收端。我们通常是反向工程发现的恶意软件,弄清楚它们在做什么,进行一些威胁狩猎。就我而言,我专注于攻击目标、网络间谍活动以及国家支持的黑客行为,因此更多的是从黑客攻击和恶意软件入手,去理解背后的行动、参与者以及他们的目的。

所以就我个人而言,是的,我确实用 Go 编写一些代码,但大多数情况下,Go 对我来说是一个有趣的挑战,因为它是一门新语言,底层看起来非常不同。一旦你跨过链接器,它看起来也非常不同。那么我们看到的是什么?我们该如何反向工程这种非常奇怪的结构和汇编代码呢?

我们可以给你一些关于黑客的答案,但我觉得可能会和你期待的不太一样。

Joakim Kennedy: 是的。我也可以补充一下---比较 Go 编译器生成的二进制文件和其他语言……Go 是一座宝库,里面有很多杂项数据,这让我们的工作有时更轻松一些……因为它有时会包含编译时主机的信息,文件路径等等……因此,你可以开始追踪一些代码上没有其他共同点的家族,但可以通过独特的文件夹名称等信息加以区分。

Juan Andrés Guerrero-Saade: 我想我们有点本末倒置了,对吧?

Joakim Kennedy: 是的。

Juan Andrés Guerrero-Saade: 好的,为了更好地回答 Natalie 的问题,并让大家跟上我们的讨论,通常来说,当你处理恶意软件和一般的黑客攻击时,C 语言是最常用的语言;如果你看到一些更专业的恶意软件,可能会使用一些 C++……但实际上你会看到各种各样的恶意软件。如果你看金融木马,它们几乎总是用 Delphi 编写的;也有人喜欢用编译后的 Python……几乎什么语言都有。

我们的工作是---你通常得到一个被剥离了所有上下文的二进制文件,没有源代码,也没有调试符号,然后你必须反向工作。我们想把这个编译后的二进制文件反向工程,弄清楚它的功能,弄清楚程序员想要实现什么,然后再从那里继续分析。这有点像魔方,每次面对一个不同的二进制文件,你都要弄清楚“他们是怎么构建它的?使用了什么编程语言?什么链接器?什么编译器版本?” 然后逐步构建对抽象层次的理解,直到你可以说“好的,这个 2MB 的二进制文件实际上只是试图劫持你的浏览器,以便窃取你的银行账户信息,这些人试图偷你的钱。”这就是最终的理解过程。但从拿到二进制文件到得出这个结论,中间有很多层的困惑需要你逐步解开。

Joakim Kennedy: 是的。恶意软件作者还会做很多事情让我们的工作更难,比如混淆代码、加密有效负载,并在内存中解密……你最初看时看不到它的原始行为;你必须开始工作,有时就像俄罗斯套娃一样,你不断地打开,打开,直到最后找到你需要的那一部分。

Mat Ryer: 你提到 Go 二进制文件里有很多额外的数据,这让它像一座数据宝库。那么这具体是怎么使得反向工程更容易的呢?是因为你可以解开某些东西,随着深入你能学到更多吗?

Joakim Kennedy: 是的,就像 Juan 刚才说的---大多数时候我们拿到用 C 编写并编译后的二进制文件,它们剥离了所有符号信息,所以你看不到任何函数的名字。而对于 Go 二进制文件,你也可以通过编译器标志剥离这些信息。但这些信息仍然存在。它们在其他数据结构中,而不是符号表中……所以我们使用的很多工具会提取这些信息,并重新创建符号,这样我们就能得到函数名等信息。

有些人更深入地研究了这些内容,但---我们使用的是一些原本不打算用于这些目的的数据,因为它是可用的……比如 Go 的 panic 功能会生成非常详细的堆栈跟踪,而这些信息对我们来说非常有用……所以只要知道在哪里找,就可以反向推导,并将其对齐。

Juan Andrés Guerrero-Saade: 最近我们发布了一个小项目,叫 AlphaGolang。这是对 Alpha Go 和 DeepMind 试图掌握围棋(而不是 Go 编程语言)的致敬……编译是一个熵增的过程,对吧?所有那些让编程对人类来说易于理解的语法糖都被剥离了,无法再恢复,除非你拥有编译项目时的所有额外信息。我认为当人们最初尝试反向工程 Go 时,他们缺乏这些信息,也缺乏对 Go 编程范式的理解……比如,如何处理链接器做的所有“魔法”工作,甚至让简单的事情也能正常工作……比如,为了支持 Go 的多返回值功能(这是 Go 的一个非常棒的特性)---链接器处理这个的方式是每次函数调用时都会添加额外的函数,创建一个运行时堆栈,以便在函数返回时有地方存放这些返回值。

但如果你不了解链接器的工作原理,不了解 Go 的工作原理,你看着这些代码时只会觉得“为什么每次调用函数时它都会调用另一个函数,返回时我找不到参数了……这些东西都去哪儿了?”你就像置身于信息的风暴中,完全不知所措。

我们想要打破的一个迷思---这是几年后的结论---是 Go 可能是最容易反向工程的编程语言之一。因为它的链接器设计得很特别,如果你试图删除二进制文件中的所有调试信息,它实际上会崩溃。

所以事实证明,如果我们使用智能的反向工具,你实际上可以对二进制文件的功能有一个非常全面的理解,而不需要花费大量的分析时间。

Joakim Kennedy: 是的,我同意你的看法。我已经看过很多 Go 的二进制文件了,我觉得它比其他语言更容易处理。

Mat Ryer: 你提到链接器需要这些信息来完成工作……那是否可以在拿到最终的二进制文件后再去做一些额外的混淆工作呢?

Joakim Kennedy: 比如我们使用类型信息,这些信息仍然存在,我们可以利用它来重建所有的类型定义。

Mat Ryer: 包括名称吗?

Joakim Kennedy: 是的,它们的名字都在那儿,因为运行时需要用到这些名字。反射包和运行时,以及链接器,都有一个共享的结构,这些内容实际上是通过文本复制的……所有类型信息就是这么存储的。你可以遍历这个表,重构出所有的类型。从一个结构体类型开始,到它的所有子字段,全部都能重构出来。而这一点是无法去除的……因为每次当你为它分配内存时---当你创建一个新对象时,运行时中有一个函数本质上只是调用了malloc,分配这个结构体所需的内存大小,这个大小就存储在那个数据结构中。所以你不能删除它。

Mat Ryer: 对,它需要这些信息才能正常工作。

Joakim Kennedy: 对。

Juan Andrés Guerrero-Saade: 我觉得我们需要小心,不能激发某种挑战心理……并不是说没有人能想到非常聪明的办法……

Mat Ryer: 是的。

Juan Andrés Guerrero-Saade: 因为这种事情在Windows平台上也发生过……那些编写打包器和混淆器的人,确实开发了一些非常非常聪明的打包器。这也是多态性在杀毒软件和病毒社区中发展的方式,一些人会说:“你们觉得你们很聪明……那看看如果每次执行时这个东西都重新排列会怎样。” 然后你就会觉得:“好吧,这可不妙。” 但是,我觉得你也要权衡一下,用Golang编写恶意软件的价值。比如,“我只需要写一次这个勒索软件,然后就可以跨平台编译。它有这些不错的效率,且并发处理很简单……” [笑] 编程中我们喜欢的所有特性,突然变成了那些做不太好事情的人手中的利器。

Mat Ryer: 所以你会因此更喜欢糟糕的编程语言吗?

Juan Andrés Guerrero-Saade: 嗯,我不知道……其实,我现在开始喜欢逆向Go了,因为它要简单得多。首先,我喜欢用Go来编程,它对我来说更容易理解。我们还写了一些脚本,能够还原编译器剥离掉的调试信息,把函数名重新放回我们通过逆向工具发现的所有函数中。然后我们可以按照包对这些函数名进行排序。所以,Go……我希望Ivan在这里;我知道Ivan本来应该加入我们的。他有一个非常形象的说法,说Go是法西斯版的Python。

这种法西斯主义---不好意思这么说---但这种法西斯主义让我们能做很多事情。你可以说:“嘿,我们可以本质上分离出所有属于标准库的东西,属于GitHub仓库的东西……”和其他你可能想要逆向工程的编程语言不同,在Go的情况下,我们可以直接筛选出哪些是用户编写的函数。

我们看过像Sunburst这样的恶意软件,这个软件是几个月前发生的著名SolarWinds攻击的一部分……当你查看这些二进制文件时,编译后会发现大约有4万个函数。因为有很多东西是由链接器添加的,你根本没考虑到……所以如果你盲目地看它,你会觉得:“哦天哪,4万个函数,我得弄清楚这些都是什么。” 但如果你运行我们刚才谈到的那些处理脚本,你实际上可以将其缩减到大约“这里有22个函数是恶意软件开发者实际编写的”,而不是陷入到运行时中,迷失在fmt包和其他你不想浪费时间逆向的包里。

Joakim Kennedy: 也要补充一点,我上次检查时(可能是Go 1.16),一个Hello World程序大约有1700个函数,其中一个主函数只是打印一行内容。你从几乎什么都没有开始;你到达入口点,那里有一些Go标准库的小片段或用汇编写的Go代码---这是你落脚的地方。然后是运行时的引导和调度器之类的东西,你不需要它们。在这众多函数中,你需要找到调用main.main的地方。在到达那个点之前,你的工作还没真正开始。

Mat Ryer: 这确实有道理。你难道不能拿一个简单的Go二进制文件,做个差异比较,看看有什么不同吗?你能不能编写一个简单的Hello World程序,然后和它做个diff对比---

Joakim Kennedy: 这实际上是人们开始分析Go二进制文件时使用的最初技术之一。你基本上可以利用字符串中获取的信息,来判断这是哪个版本的编译器。“哦,是这个版本,它用了这些导入包。让我构建一个导入这些包的二进制文件,然后为这些函数生成签名,再把签名覆盖到我手头的文件上。没有被检测到的部分,就是我要分析的内容。”

Mat Ryer: 这真是太聪明了。

Juan Andrés Guerrero-Saade: 这是一种解决问题的方式……坦白说,我觉得我们还处在摸索阶段,就像在一间黑暗的房间里四处碰壁,不知道墙在哪里,也不知道开关在哪里。如果你仔细想想,这听起来很简单……比如,“好吧,我们写些包含相同包的东西,编译之类的”,但接下来你会遇到各种变体……比如“他们用的是什么版本?他们用的编译器版本是什么?他们链接了什么?是为哪个目标架构编译的……”如果是在Elf、MacO还是Windows平台上,链接器的工作方式实际上会略有不同……

于是你就会进入这个无限变化的世界,这让你逐渐失去信心。通常情况下这会造成一种有点灾难性的局面。比如,当你看C++时,真的没有太多捷径可走。你可以用flirt签名和其他小工具来处理一些功能,但编译过程中引入的熵意味着,例如类定义会消失;类定义不见了,你有这些虚拟表,我们根本不知道什么引用了什么,所以你甚至没有一个完整的控制流,除非你动态执行样本。

所以有很多情况下,你并没有一条清晰的前进路径,逆向复杂的C++二进制文件需要大量的工作……刚开始接触Go时的感觉也是如此。最终我们发现,其实有一些很好的方法可以重建Go二进制文件。现在我们看到了其中不太好处理的地方。现在我们觉得Go……我不会说我们已经完全掌握了Go,逆向它变得超级简单,但它确实比以前更容易了。而现在当我们看Rust时,我们的反应是:“哦天哪,逆向Rust太糟糕了。”它更像C++,我们又不知道该怎么做了。

Mat Ryer: 这很有趣。我大概不会想到这一点。我本以为Rust从某种程度上会更加确定性……所以这很有趣。

Joakim Kennedy: 问题在于---这些新语言,它们从动态链接库转向了静态链接库。 这是主要的障碍---分析一个动态链接的程序相对容易,因为你知道它导入了哪些特定函数,所以你知道它会调用什么。但当你拿到一个二进制文件,发现里面有SQLite、OpenSSL和其他库,而你只看到一个函数调用时,你根本不知道你在哪里。当然,你也可能在C和C++中遇到这种情况,但在Go和Rust中这是默认的。一般来说,每个Rust二进制文件都会是这样的。

Juan Andrés Guerrero-Saade: 是的。我可能对Rust有点不公平,它其实还算可以---

Mat Ryer: 在这个播客里没关系。

Juan Andrés Guerrero-Saade: 在这个播客里完全没问题;不会有Rust爱好者来敲我的门……但我想我们可能有点不公平,如果我们四年前谈论Go,可能也会说同样的话……比如:“哦天哪,逆向它太糟糕了,我们根本不知道自己在哪里……有一堆与程序无关的静态链接代码……” 现在在Rust中也是这样的感觉。我希望随着我们对这种范式的熟悉以及工具的改进,我们也能对Rust有更好的理解……但没有任何迹象表明这一定会发生。C++已经很多年了,我大概可以数出几个人,是真正精通逆向C++的。像Rolf Rolles这样的逆向工程明星,但这绝对不是我,也不是很多和我一起逆向的人……所以没有迹象表明情况一定会好转,但我们对它抱有很高的希望。

Mat Ryer: 所以当你在逆向工程时,你提到你可以获得函数名和类型名……但是这有多大用处呢?黑客们写代码时,真的会有一个叫“偷信用卡”的函数吗?

Juan Andrés Guerrero-Saade: 是的……?! [笑]

Mat Ryer: 不可能这么简单吧。

Juan Andrés Guerrero-Saade: 有时候是的。

Mat Ryer: 哇哦。

Joakim Kennedy: 我写了一个工具……我在两年前开源了它……它的功能是,你把一个Go二进制文件扔进去,它会提取出那些信息,然后生成一个类似源代码的投影。所以你会看到文件夹、文件,以及函数,然后标出函数开始和结束的行号。你可以查看样本。很多时候,我把一堆Go二进制文件扔进去,它会处理完并输出Loader、Cracked、Ransomware。哦,这是一个新后门……因为你看到了函数名。加密、获取密钥、丢弃通知、遍历文件系统。我只看到加密函数,从没看到解密函数。光看名字就很清楚这是在做什么了。

Natalie Pistunovich: 知道黑客们也遵循好的软件开发实践,这倒挺不错的……

Mat Ryer: 是啊。 [笑]

Juan Andrés Guerrero-Saade: 嗯,大致如此……但接下来你会遇到混淆器,对吧?

Joakim Kennedy: 是的,有些混淆器,但我可以告诉你,到目前为止,我还没有看到任何合法应用或类似的东西使用混淆器。你只会看到乱七八糟的字母。而且,所有UTF-8字符都允许使用的情况下,如果你看到一个函数名是西里尔字母(译者注: 是一种是广泛通行于斯拉夫语族和前苏联疆域之内的字母,亦是除了拉丁字母和阿拉伯字母之后,世界母语人口第三多的拼音型文字) 和韩文字符集以及中文字符集的混合,你就会想:“嗯,这肯定是混淆过的。”

Mat Ryer: 或者是像Natalie这样聪明的人,能说很多很多语言。

Natalie Pistunovich: [笑]

Joakim Kennedy: 对。当你需要安装fmt包才能打印出这些字符……因为每个其他字符都缺失了。

Mat Ryer: 是的。但我觉得你会被我迷惑住。我会把函数叫做“不要偷信用卡信息”。 [笑] 哈哈!

Juan Andrés Guerrero-Saade: 这就是逆向心理战术嘛,对吧?

Mat Ryer: 没错。这实际上是在逆向逆向工程。

Joakim Kennedy: 是的。从逆向工程师的角度来说,我见过最有趣的事情之一,是一个二进制文件……所有的字符串都被隐藏了,但它们被隐藏为函数的函数名。然后,它执行这个函数,利用反射来获取它自己的函数名,然后用一个密钥进行异或操作,重新生成字符串。这是我见过的最有趣的混淆技术之一。

Juan Andrés Guerrero-Saade: 是的,你会发现越来越多的巧妙手段……有些打包器会在你打开一个二进制文件时,里面充满了莎士比亚的段落,而打包器的工作就是从这些段落中挑选出东西,动态重组。特别是在Windows世界,恶意软件开发者和安全研究人员之间有一场猫鼠游戏……

你会有一些奇怪的指标。我们一直在谈论逆向工程是为了理解;我们想知道样本做了什么。但从恶意软件开发者的角度来看,他们最大的初步关注点只是不要被检测到。所以作为一名恶意软件开发者,你得在“我想让Joakim或我自己更难理解这个二进制文件在做什么”和“如何在雷达下飞行,让杀毒软件不认为‘嘿,这看起来很奇怪,检测出来’”之间做出平衡。这是一个微妙的平衡。

Natalie Pistunovich: Mat,看来你在想一些更聪明的函数命名方式,并且还在考虑什么可以做得更好……

Juan Andrés Guerrero-Saade: [笑]

Mat Ryer: 是的,我意识到,我正在掉入那个陷阱,开始把它变成一个游戏……

Natalie Pistunovich: 是的,是的……

Mat Ryer: 它确实有趣,不是吗?在某些情况下,这些事情会产生非常严重的影响……但你无法否认,它确实是编程中很酷的领域,黑客技术。很多人是通过流行文化接触到黑客的,我想有些人可能就是因为这个原因进入编程的。对我来说,编写脚本时,恶作剧无疑是一个动机。我做的事情总是恶作剧。我会在Floppy磁盘上的autoexec.bat文件中覆盖学校电脑的壁纸,或者类似的东西……只要把磁盘插进去,下一次机器启动时,壁纸就会改变。而且总是一些恶作剧之类的东西。但这种东西确实很有吸引力。

Juan Andrés Guerrero-Saade: 但现在形势越来越紧张了。很容易从这种角度来看待它,我们也不想让它变得太沉重或太黑暗,但现在这也是很多国家和犯罪分子的游戏场。如果你在美国,勒索软件的流行基本上是不可避免的。你每天都要谈论它。这时候事情就不那么美好了。如果你在一个医院里,由于他们那些已经过时的Windows XP系统在一个扁平网络中,所有系统同时被攻击,导致医院无法帮助病人……这时候你会想:“嗯,这段代码很有趣……我喜欢拥有这些黑客超能力的想法,但其中有一面并不那么可爱。” 我觉得我们一直在走钢丝,一方面觉得“哦,这太有趣了”,同时又被某些功能吸引,被某人能够完成的事情吸引,往往容易忘记,“哇,这其实是一个更严肃的游戏的一部分。”

Mat Ryer: 是的,完全同意。顺便说一句,如果我闯入医院,唯一的事情就是改变所有的壁纸……[笑] 如果发生了这种事,那就是我的杰作。壁纸上会是我的笑脸。真是个傻瓜。

Juan Andrés Guerrero-Saade: “希望你早日康复”,对吧。

Mat Ryer: 哦,没错。是的。

Natalie Pistunovich: 但你有多常能看到桌面呢?我无法想象,假如你在医院接待处的电脑上工作……你真的会关闭所有东西,看到桌面吗?你永远不会看到那个,Mat。你得想出点更好的主意。

Mat Ryer: 忙不过来,是的。你说得对。

Juan Andrés Guerrero-Saade: 他们可能根本不会注意到……

Natalie Pistunovich: 也许Mat已经做过了,只是没人注意到。

Juan Andrés Guerrero-Saade: 没人注意到,是的。

Mat Ryer: [笑] 那会让人很失望。

Natalie Pistunovich: 如果你想更多地玩黑客游戏,一个有趣的事情是去做CTF,对吧?

译者注: (Capture The Flag,夺旗赛)起源于1996 年DEFCON 全球黑客大会,是网络安全爱好者之间的竞技游戏

Juan Andrés Guerrero-Saade: 没错。好吧,撇开黑暗的一面不谈,红队和渗透测试人员中有一群人很喜欢Go……

Natalie Pistunovich: 对于那些完全不熟悉这些术语的人来说,这些专业术语都是什么意思?

Juan Andrés Guerrero-Saade: 我们属于蓝队,对吧?我们是防御者,只是想确保坏事不会发生。红队则是模拟攻击者的一群人,比如“我们会被雇来故意攻击其中一家医院,当然是在一定约束范围内,告诉他们‘看,你们的网络弱点在这里。这些是你们现在应该修复的地方’。” 所以通常来说,双方是对立的,但我们其实是朋友。实际上有一个很强的社区,他们喜欢做这类开发和用Go编写红队工具。

Natalie Pistunovich: 等一下……所以红队是做渗透测试的人。

Juan Andrés Guerrero-Saade: 对。

Natalie Pistunovich: 蓝队是被雇来修复红队指出的问题的人。

Juan Andrés Guerrero-Saade: 差不多吧。

Joakim Kennedy: 可以叫他们防御者。

Juan Andrés Guerrero-Saade: 对。广义上讲,就是防御者。

Natalie Pistunovich: 那白帽/黑帽又是什么?

Juan Andrés Guerrero-Saade: 所有这些理论上都是白帽子。你会看到一些交集,但如果你是为了改善公司、组织或政府的整体防御姿态而工作,理论上这都属于白帽,对吧?

Mat Ryer: 嗯,那就是我。我会是那种人,温和地说:“我已经证明了,你的壁纸可以被任何人随意更换。广告商也可以这么做,如果你想推广什么东西,可以把它放到所有医院的屏幕上……你懂我的意思吧?” 所以,这肯定是我站的立场。我只是想把这点说清楚,记录在案。

Natalie Pistunovich: 这也可以成为一个很好的安全问题---你的壁纸是什么?

Mat Ryer: 是的。或者你戴的帽子是什么颜色?这也是个好问题。那CTF是什么?CTF代表什么?

Natalie Pistunovich: 等一下,我们还在谈论红帽子和白帽子,还有蓝帽子。

Juan Andrés Guerrero-Saade: 各种颜色的帽子。

Natalie Pistunovich: 各种主题。


Juan Andrés Guerrero-Saade: 是的。我想我们大多数时候的工作都是追踪和防御黑帽子……黑帽子的标准定义是那些未经允许就进入网络的人……如今,他们的行为通常是为了那些显而易见的不良目的,甚至是非法的。所以有间谍活动的一面,也有破坏活动的一面……毫无疑问,勒索软件属于长期存在的网络犯罪传统的一部分。以前,他们只是想进入银行账户,或者窃取信用卡号码。如今,他们感染整个企业网络,然后要求支付3000万美元的赎金才能解锁。这些人都属于黑帽子的范畴……而理想情况下,所有的蓝队、红队、渗透测试员等都应该是白帽子,尽管有些人可能会涉足……不过不是我;我没有这些技能,遗憾的是……但这是个非常有趣的领域。

Mat Ryer: 但即便你有这个技能,你可能也不会这么说,对吧?

Juan Andrés Guerrero-Saade: 我正在学习你们的逆向心理学,尤其是关于黑客的……“绝对不是恶意软件”是我包裹文件的名字。

Mat Ryer: 完全正确。那对我来说确实有用。

Natalie Pistunovich: 是的,完全正确。对于那些想玩一玩黑客技术的人,也有一些CTF(Capture the Flag)的比赛。那么,值得捕获的旗帜是什么呢?

Joakim Kennedy: 有不同的种类。所以你有一些Capture the Flag比赛更偏向红队,这通常涉及到可能攻破一台存在漏洞的机器,或者是一个二进制文件有漏洞,你的目标是编写一个利用该漏洞的代码,获取旗帜,证明你成功破解了系统。

还有一种CTF类型是“破解我”,基本上就是一个二进制文件,你需要对其进行逆向工程,也许让它能够正确运行。

Natalie Pistunovich: 可能不是用Go写的吧……

Joakim Kennedy: 通常在CTF比赛中,他们会用一些非常晦涩的语言,因为没人知道这些语言,从而增加挑战的难度。

Juan Andrés Guerrero-Saade: 像Nim?

Joakim Kennedy: 像Nim,对。

Juan Andrés Guerrero-Saade: 今年有一个非常著名的逆向工程Capture the Flag比赛,叫做Flareon挑战赛Mandiant公司(美国麦迪安网络安全公司)每年都会举办……今年,最后一关---如果你一路通过了比赛,那最后的关卡实际上是一个Go的二进制文件。我没能走到最后;有人找我说:“嘿,看看这个东西。我正试图重建这个二进制文件,好让更聪明的人完成比赛……” 但是,是的,Go确实在逆向工程领域变得越来越重要了。

Mat Ryer: 这对我们来说是好事吗?

Juan Andrés Guerrero-Saade: 我也不太确定……

Joakim Kennedy: 我记得去年DevCon的资格赛中,有一个Rust的二进制文件也是其中的一部分。你会看到各种混合体,谁知道什么时候我们会遇到一个用Nim写的呢。

Juan Andrés Guerrero-Saade: 是的,我认为这更多的是反映了一种新的编程语言范式。这是两个现象的交汇点。首先,病毒编写者们正在老去。有一代新人正在崛起……所以,现在没有那么多人在学校学汇编,也没有像以前那样在东欧破解软件,那时候他们无法获得正版软件。这是老一派的病毒编写者和黑客。

现在,我认为这一代新人开始更多地参与进来,他们更多地使用Go和Rust,尝试学习这些新的编程范式……而且似乎他们中的很多人并没有回过头去学习硬核的汇编或C语言。所以,我们将越来越多地看到这些流行且易于使用的语言在恶意软件中变得更加普遍,这是不可避免的。

Mat Ryer: 是的,考虑到不同编程语言和它们的不同能力,再想想当你需要对它进行逆向工程时,这真的很有趣……我在想,比如Go中的defer语句,它是在一个函数退出后才执行的函数。显然,在二进制文件中,它会以某种方式表示出来,可能是看起来很普通的代码,或者是汇编语言。你能看一个二进制文件,判断出它在哪里使用了defer,或者有并发、协程的地方吗?

Joakim Kennedy: 它是一个不同的返回调用。我记得它叫“deferred return”?它是运行时内的一个函数,只是包含了一个将要调用的函数的指针。

Juan Andrés Guerrero-Saade: 我觉得更容易的地方在于……如果你熟悉这种编程范式,会更容易理解。如果Go对你来说像对我一样容易接近---你知道,我在Google工作了一段时间,我在那里的一位好朋友兼导师,Mike Weisbach,非常喜欢Go……我刚到Google的头两周,他就递给我一本《Go语言编程》书,说:“搞清楚这个。”

我学着去喜欢它,然后当需要逆向它时,很多概念对我来说就容易理解了,比如通道(channels)和defer语句之类的东西。当你已经熟悉这些概念时,它们很容易在逆向工程时映射出来。进入逆向工程的最大、最陡峭的挑战通常是训练自己去识别C级别的结构,盯着汇编代码。基本上,你只是试图熟悉不同的编译器会如何表示某个C概念,也许那个概念并不复杂。“哦,这是一个switch语句。” 但在汇编中它是怎样的呢?学习如何来回切换。而有那些概念的帮助是很有用的,所以我说,也许Rust会对那些理解Rust的人来说变得更容易,他们可以进来写一些脚本,写一些工具。目前我对Rust还不太熟悉,所以当我试图逆向Rust中的某个东西时,就像迷路了一样。你没有任何坐标。你面对成千上万个没有名字的函数,没有类型,有时字符串还被混淆了……你被扔到一个巨大的二进制文件中,没有地图。

Mat Ryer: 所以当你想到这些特性时---顺便说一句,这很有趣,因为我基本上从不看生成的汇编代码……所以我只在语言层面上思考这些特性。这让我意识到,这些特性在某种程度上确实感觉很神奇。通道(channel),当它们工作得很好时,它们非常出色,你有时会忘记它们其实是在底层做着一些无趣的事情……但当Go推出新语言特性时,比如Go 1.18---即将迎来泛型。那会给你带来一些麻烦吗,当它发布时?

Juan Andrés Guerrero-Saade: 可能会……[笑]

Joakim Kennedy: 最大的问题是有多少工具会因此而崩溃……我们面临的一个问题是,我们解析的是没有导出的内部数据结构,而这些结构经常会更改。我记不清有多少次内部map结构发生了变化……而当你解析它时,你需要有一个与二进制文件完全匹配的正确结构,否则你之后读取的所有偏移量都会出错。你会偏离,读取到别的地方,然后就迷路了。而且这些变化不会被宣布。

Mat Ryer: 对。这就像这些接口保持不变;语言也保持不变。

Joakim Kennedy: 是的。

Mat Ryer: 但是当然,编译器可以自由地做任何它需要做的事情。这实际上是使用Go语言的一个优势,人们在幕后做了很多工作,进行了很多更改,而我们根本不需要去考虑这些事情。但显然,你必须考虑这些事情。

Joakim Kennedy: 是的。

Mat Ryer: 那你们的工具是否与每个Go版本相匹配?这就是你们最终的做法吗?

Juan Andrés Guerrero-Saade: 在某种程度上可以这么做,出于必要,我们确实这么做了,因为工具的构建方式就是如此。只是没有人会打电话给我们说:“嘿,你还记得那个你依赖的魔法头文件,用来定位pcln符号表的吗?”

Joakim Kennedy: “我们改了。”

Juan Andrés Guerrero-Saade: 对,“我们在1.16版本中改了。” 他们确实改了,对吧?“我们在1.16版本中改了。祝你好运。” 你知道,这就是逆向工程的本质,你本质上是从空中窃取信息,没有任何东西能保证下次不会发生变化……而通常这些改变是有充分理由的。如果你找到了一个更高效的算法实现方式,当然你会想要实现它。没有人会坐在那里想:“我们该怎么帮助逆向工程师重新找回他们的方向呢?” 我们基本上是依赖一系列的启发式方法,而这些方法会随着时间的推移发生变化,并且很难维持工具的正常运作,同时还能识别新的事物、新的惯例,以及它们在不同编译器设置和目标平台上的不同变体。

Joakim Kennedy: 我遇到过最让我困扰的bug之一,是Go 1.7 beta 1的数据结构---我知道这个bug,因为只有这个版本发布时,数据结构是这种格式。这个结构体是用来存储方法(methods)的……存储类型的方法。有几个字段告诉你如何定位方法的偏移量……而在这个版本中,那个整型的大小是32位。我记得它是int 32。而在beta 2版本中,它被改成了16位。从那以后,它基本上就没有再改过了。所以只有一个beta版本的大小与其他所有版本完全不同……是的,我在一个用这个beta版本编译的恶意软件中遇到了这个问题,它完全搞乱了整个解析流程……你知道,它就这么通过了。

Mat Ryer: 哇哦,这真是太有趣了。Go是开源的,这一点难道没有帮助吗?我的意思是,你不能写一个工具来查看代码,并在某些内容提交和发生更改时提醒你吗?你能做些什么吗?

Joakim Kennedy: 理论上可以……理论上。但这就是你开始过度优化的地方了。现在我们确实看到很多Go恶意软件,但它仍然不是我们主要处理的东西。所以你会陷入一种境地,如果Go真的是编写恶意软件的终极语言,我们知道未来大部分恶意软件都会是Go写的,那当然值得可能去启动一个公司,专门致力于Go的逆向工程。但目前来说,有些俄罗斯威胁行为者每隔一周就会用另一种语言重写他们的代码,希望逃避检测。

有一个叫Zebrocy的组织,他们最初用Delphi编写恶意软件,然后移植到Python,再移植到Go,Rust,Nim……他们几乎尝试了所有语言。对他们来说,这有点像开玩笑。他们只是想让他们的第一阶段加载器不被检测到。而对于我们来说,如果你坐下来试图为每种可能的变体构建工具,你永远也赶不上变化的步伐。

Mat Ryer: 顺便说一句,有些公司也会这么做。他们会不断用不同的语言重写代码,之类的。他们不是在逃避检测……他们只是在逃避自己……[笑]

Joakim Kennedy: 你提到现在有越来越多的恶意软件是用 Go 语言编写的……有没有哪种恶意软件让你觉得特别有趣?因为它们利用了 Go 的某些特性,做了一些有趣的事情?

Joakim Kennedy: 我见过很多……大多数我见过的东西,都是为了加载 shell 代码,或者加载其他恶意软件,或者加密数据。很多是针对 Linux 和服务器的恶意软件,常用于植入加密货币挖矿程序等。

其中一个比较有趣的例子是,如果你熟悉 IPFS 项目,星际文件系统……它是一个尝试做分布式互联网的初创公司。IPFS.io 是他们的网站,他们发布了一个基于 Go 的点对点库,后来我们发现有一个僵尸网络使用了这个库。所以这个僵尸网络是建立在 IPFS 的点对点网络之上的。主要是用来销售代理服务的,但我觉得有趣的部分是,他们将僵尸网络叠加在了一个合法的点对点网络之上。

Mat Ryer: 嗯,我还以为 IPFS 是“互联网协议文件系统”(Internet Protocol File System)。你说得对,它是“星际文件系统”(Interplanetary File System)……他们现在已经到哪个星球了呢? [笑声]

Juan Andrés Guerrero-Saade: 我觉得有趣的是,听这个播客的 Go 开发者的平均水平,可能比我们通常见到编写 Go 恶意软件的开发者要高得多。就像我说的,这还是一个新的编程范式。有时你会觉得那些恶意软件的作者对 Go 还不太熟悉,他们在尝试……尤其是那些像 Zebrocy 这样的组织,他们总是在尝试掌握各种不同的语言,但显然他们从来没有完全掌握过。不过你也会见到一些有趣的情况,比如很多勒索软件开始使用 Go,因为并发功能对加速加密大量文件非常有用。

Joakim Kennedy: 还有加密库在 Git 上,而且很容易使用。

Juan Andrés Guerrero-Saade: 没错。你有好用且强大的加密库,这样你就不会犯下自己实现加密算法的错误,导致密钥被恢复……并发也相对容易使用。所以你会看到他们开始尝试使用通道(channels),他们试图在运行时动态构建列表,快速地进行加密操作……但他们也会做一些愚蠢的事情,比如他们通过 GitHub 使用特定于操作系统的库来与 Windows 交互……所以你会想,“你花了很多功夫,选择了一个易于跨平台编译的语言,但现在你却让它无法跨平台编译。” 你会觉得庆幸,“还好你们不太擅长”,但也有点遗憾,感觉“如果你们稍微更擅长一点,收入来源会更广。” [笑声]

Mat Ryer: 这真的很难……我们这样谈论这些话题,或者有这样的对话,怎么能不惹怒别人呢?

Juan Andrés Guerrero-Saade: 哦,我们确实惹怒了别人。 [笑声] 信息安全在 Twitter 就是充满了热议和相互攻击的污水池……在某些方面这是一个很棒的社区,但在其他方面它可以非常充满敌意。

Mat Ryer: 这听起来不像 Twitter……

Juan Andrés Guerrero-Saade: 对吧?这是现代社区的写照。

Mat Ryer: 是的,现代社区,还有匿名账户。就是这样。

Juan Andrés Guerrero-Saade: 对,文明。

Mat Ryer: 在这里你叫做 JagAss……

Juan Andrés Guerrero-Saade: 对。

Mat Ryer: [笑] 但你一直很有礼貌。

Juan Andrés Guerrero-Saade: 我甚至在这个名字上也帮助了大家,是吧?我不想让你们费力去读我的两个双重姓氏……

Mat Ryer: 哦,我明白了。非常感谢。我真希望Joakim Kennedy也能这么做……

Joakim Kennedy: 也许我们一开始没你在的时候已经谈过这个了,马特。

Mat Ryer: 哦,那是在节目开始之前,对吧?

Joakim Kennedy: 是的。

Mat Ryer: 好吧,那你再说一遍吧。不说了?

Juan Andrés Guerrero-Saade: 抱歉,你在说我的名字吗?

Mat Ryer: 哦,现在你提到了你的名字,我确实很好奇……但如果你不想说,那完全没问题。

Juan Andrés Guerrero-Saade: 没事,我的名字是Juan Andrés Guerrero-Saade。我只是觉得对大家来说,叫 JAGS 或者胡安会更简单一些。

Mat Ryer: 这名字太棒了!你能再说一遍吗?

Juan Andrés Guerrero-Saade: 胡安·安德烈斯……格雷罗……萨德。再来一次。

Mat Ryer: 真棒。

Juan Andrés Guerrero-Saade: 再次强调,叫 JAGS 就行。叫我胡安也行。没人有时间去念全名。

Mat Ryer: 是的,我已经有了我的新密码。无论如何……

Juan Andrés Guerrero-Saade: 就是这样。 [笑声]

Mat Ryer: 没人能猜到。

Joakim Kennedy: 我甚至不会尝试。我觉得我肯定会发错音。这真是个有趣的对话……你提到的这些东西,比如跨平台编译……“哦,太棒了”,然后你突然说这对黑客非常有用,这完全让我大开眼界。

Joakim Kennedy: 是的,你会看到很多这样的情况。特别是当你针对 Unix 系统时……如果你发现了一个 x86 的版本,你几乎可以肯定会找到 ARM 和 MIPS 等其他架构的版本。

Juan Andrés Guerrero-Saade: 是的,我觉得大考验是你会不会看到很多 cgo。如果没有,那么你很可能会轻松地在不同平台间移植。

Joakim Kennedy: 是的。

Juan Andrés Guerrero-Saade: 我想说的是,我尽量不公开谈论这个,但 Go 恶意软件开发社区并不太优秀,而红队社区却很优秀。所以让人惊讶的是,真正的坏人没有直接使用那些模拟坏人的人开发的工具。这些人实际上对 Go 很了解,他们做了很多巧妙的事情……我不想给任何人提供点子,但他们确实开发了不少好东西。不过,真正的黑帽子还没有花时间去研究这个生态系统,看看里面有什么……这对我们来说是好事。我们并不希望他们变得太厉害。

Mat Ryer: 是的。我不知道我们的听众中有多少人符合这种描述。我无法想象一个酷酷的黑客会打开播客听……你能想象吗? [笑] 也许他们会。好吧,欢迎来到 Go Time……

Juan Andrés Guerrero-Saade: 你永远不知道。这也是另一个问题---这些黑客是全职的吗,还是兼职的?有时你会听到一些有趣的消息,比如“这是一个 Kubernetes 开发者,有一份真正的开发工作,但他们恰好决定尝试点别的……” 我认为这就是白领犯罪的心理。你觉得自己很聪明,能够逃脱惩罚,并且这不像是“真正的犯罪”……有时人们觉得他们可以试试水,结果不会有任何后果……有时确实没事,但有时也会出问题。

Joakim Kennedy: 有时你会找到一些线索,然后找到他们的 LinkedIn 个人资料,你会想,“太好了。”

Juan Andrés Guerrero-Saade: “你好,朋友。” [笑声]

Joakim Kennedy: 真有趣。

Juan Andrés Guerrero-Saade: 这是会发生的……

Mat Ryer: 是的。我们会在节目笔记中发布这些链接。 [笑声]

Juan Andrés Guerrero-Saade: 给那些想要用 Go 编写恶意软件的人一个建议---如果你有良好的编码习惯,并且使用 Git 进行增量编写和版本控制,也许不要把你的名字留在里面……不要在你自己的 GitHub 仓库下构建所有东西。令人惊讶的是,很多人没有意识到,标准的、良好的编码习惯在很多方面违反了匿名化的原则。所以你会遇到一些非常聪明的恶意软件,但你会发现,“天哪,这个 GitHub 仓库的用户名和你过去十年使用的账号是一样的……” 我在读你的博客,试图了解你的心情,因为我在一个样本中发现了你的代码。

Mat Ryer: 哇哦……

Joakim Kennedy: 我最喜欢的发现之一是项目所在的路径是 /users/某人的名字/go-project/source/keybase/keybase-team。所有信息都一目了然。

Mat Ryer: 哇哦……

Juan Andrés Guerrero-Saade: 是的……当他们井井有条时,真不错。

Joakim Kennedy: 是的。 [笑声] 良好的源代码结构和其他东西,但……你可能不想把这些信息保留在二进制文件中。

Mat Ryer: 我们在帮他们吗?你觉得我们这样公开讨论这些话题是在帮他们吗?

Joakim Kennedy: 这种事情是显而易见的……这些信息会被发布出来……

Joakim Kennedy: 或者基本上 Reddit 上已经有人提到过了。

Juan Andrés Guerrero-Saade: 这些信息已经存在,并且将继续存在,人们会自己弄明白……老实说,我并不太担心二流罪犯会学会如何更好地使用 Go……我更好奇的是,什么时候我们会看到国家级的攻击者开始真正有效地使用 Go 和 Rust 编写恶意软件。因为目前我们看到的最好的恶意软件通常是用 C++ 写的,特别是美国或者“五眼联盟”国家的恶意软件,往往是高质量的 C++ 代码。你可以看出,他们在开发这些东西时,建立了一整套基础设施和工具……

你面对的是层层叠叠的优秀工程,旨在制作难以追踪的植入程序,这些程序可能会进行大量重定位,使用自定义打包程序,包含加密的有效载荷……所有这些都是经过精心设计的,并且可以重复生产,避免了我们刚才讨论的那些错误。但到目前为止,这还不是我们看到的大规模恶意软件。现在还处于人们在尝试“我们该怎么做?”的早期阶段。也许它已经存在了,也许我们还没有发现。但我在密切关注,想知道什么时候我们会看到那种“由雷神公司编写的政府级恶意软件”,而不是某个兼职开发者编写的。

Joakim Kennedy: 这引发了许多问题……你们期待下一集会讨论 AI 生成的恶意软件吗?

Juan Andrés Guerrero-Saade: 对……

Joakim Kennedy: 最近一年半,国家级攻击者使用 Go 的情况有所增加。之前这种情况非常罕见。

Juan Andrés Guerrero-Saade: 是的。

Mat Ryer: 你知道是哪些国家吗?

Juan Andrés Guerrero-Saade: 肯定有xxx和xx。这两个国家的组织大量涌现,我们的归因通常需要谨慎看待……过去几年,特别是在xxx方面,这些国家受到了很多关注,尤其是在 2016 年的选举黑客事件之后,这些组织受到了太多的关注,以至于他们被迫进行了很多重组。他们丢弃了我们熟悉的大多数工具包,这些工具包大多是用 C 和 C++ 编写的。

有趣的是,现在我们看到xxx的国家级组织喜欢 Go,甚至依赖 Kubernetes。你会看到恶意软件中包含 gRPC,你会想,“天哪,你们的技术越来越专业了。” 这确实很有趣,同时也让人感到有点压力……投入到这些攻击中的资源开始变得相当惊人。

Mat Ryer: 有用 JavaScript 编写的吗?

Juan Andrés Guerrero-Saade: 是的,当然。很多网络上的恶意软件,像加密货币挖矿程序,甚至是登陆页面---JavaScript 非常有用。当你考虑攻击链时,如果有人要使用漏洞利用,或者他们使用一些非常专业的东西,他们实际上需要对你有很多了解,才能使用这些工具。比如,我需要知道马特的电脑运行什么操作系统,使用什么浏览器,或者是什么样的软件栈,然后我才能进行有针对性的复杂攻击。而 JavaScript 通常是第一阶段的工具,比如“我们先让这个页面着陆,也许我们甚至不提供任何恶意内容,但我们可以先分析你的系统”,然后下一步才会向你提供更具针对性的攻击工具。所以 JavaScript 在很多恶意软件操作的早期阶段起到了重要作用。

Mat Ryer: 是的。我还记得 Love Bug,这是我最喜欢的恶意软件之一……它是用 VBScript 写的。

Joakim Kennedy: 是的。微软有一个 JScript,它算是 JavaScript 的一个变种。所以你也会看到用 JScript 编写的恶意软件。

Mat Ryer: 是的,但那个可以在系统上运行,对吧?不仅仅是在浏览器里。

Joakim Kennedy: 是的。

Mat Ryer: 我记得 Love Bug 是用 VBScript 写的。这个病毒利用 Outlook 的自动化 API,把自己发送给你的所有联系人……我想它就是这么做的。它会发一封邮件说“我爱你,看看这个文件吧”,然后附件是 iloveyou.vb 或类似的文件。人们会毫不犹豫地双击打开,然后病毒就会发送出去……这真的是一个病毒。

Joakim Kennedy: 其实,如果你真的想用 JavaScript 写恶意软件,你可以用 Node.js 打包。它有很多包可以生成一个单一的二进制文件,然后你可以发送给别人……虽然这需要用户下载一个 20 到 40 MB 的文件并运行它,但这确实可行。你获得了 V8 引擎和其他一切。

Natalie Pistunovich: 令人震惊。这真是一个引人入胜的节目。感谢你们为社区分享这些见解,希望它只会激励到那些合适的人。

Juan Andrés Guerrero-Saade: 另一方面,Go 开发者总体来说都是很棒的开发者,而我们这个行业---更广泛的信息安全社区---真的非常需要更优秀的工程师参与进来。逆向工程师和威胁猎人---我们有一套特定的技能,但我们通常不是来自强大的工程背景。有些人是,但有些人不是,我自己就是。我们来自国际关系、哲学、物理学等领域;如果你有解决难题的头脑,你就会进入这个领域,学会逆向工程……这往往意味着我们大部分的工具都是拼凑起来的 Python 脚本。

所以如果这期节目有什么作用的话,希望不是激励更多的恶意软件作者,而是说“如果有更多的 Go 开发者,真正优秀的 Go 开发者想进入安全领域,这里有很多机会。”有很多创业机会,有很多机会去革命性地重建一个已经严重老化、失修的软件堆栈,而这个堆栈实际上关系到互联网和整个生态系统的健康。所以如果有更多的 Go 开发者加入我们,而不是去做广告,或者去做 Google 现在在忙的事情,那将会很棒。

Mat Ryer: 如果他们感兴趣,应该去哪里?

Juan Andrés Guerrero-Saade: 有几个地方……如果你特别想进入威胁情报领域,我知道 Ninja Jobs 是一个很好的平台,专门帮助寻找安全领域的工作。老实说,如果你有合适的头脑,你可能真的想自己创业……虽然说起来很简单,但我们依赖的很多工具都太老旧了。

我认为安全领域充满了机会。有大量的投资和需求,而我们中没有人真正知道解决方案是什么……所以即使你只是对人们真正需要的东西做出渐进式的改进,比如理解 DNS,甚至……没人愿意触碰操作变换(Operational Transforms)这种东西。像 Google Drive、Google Docs 可能是唯一一个大家愿意参与的操作变换项目,而且再也不想做了……但这意味着我们没有任何协作平台可以使用,类似这样的问题。

Mat Ryer: 开源工具适合这种需求吗?这样做会让黑客占据上风吗?

Juan Andrés Guerrero-Saade: 我觉得没问题。很多安全检测依赖于 Osquery,它就是开源的……我们有很多开源的东西融入到我们的技术栈中。如果有的话,你会看到围绕这些工具的服务行业非常健康。Osquery 是免费的,但很多人会付费给其他公司,让他们帮助设置和维护这些工具栈。我是在谈论商业方面的事情,但这只是为了说---你知道的,这里有激励机制,“加入我们吧。和我们一起工作。”

Mat Ryer: 是的,绝对是。

Natalie Pistunovich: 听起来这是个很受欢迎的观点呢……

Juan Andrés Guerrero-Saade: [笑]

Joakim Kennedy: 有时候这些东西看起来像魔法,但归根结底,很多都是计算机科学的基本原理。回想一下,如果你在学校学过计算机科学,JAGS 刚才谈到 DNS 之类的东西……我这些年看过很多 RFC 文件。我不是开发者,但这都和理解协议的工作原理有关。开发者和我们之间有很多共享的知识。

Natalie Pistunovich: 很棒的建议。感谢你提供了这个新视角,并且为那些想要跳入这个行业的人提供了建议。我想最后一个问题是:“你们准备好聊聊不受欢迎的观点了吗?”

Juan Andrés Guerrero-Saade: [笑] 当然。

Natalie Pistunovich: 我听说你有两个观点。

Juan Andrés Guerrero-Saade: 是的,我在想哪个更糟糕……哪个会更挑衅一些。 [笑声]

Mat Ryer: 我们可以测试一下两个观点……所以请随意。

Juan Andrés Guerrero-Saade: 针对这个群体,我其实认为软件开发者可能是所有互联网用户中安全性最差的群体之一。

Mat Ryer: 真的吗……?!

Juan Andrés Guerrero-Saade: 没人喜欢这个观点,尤其是那些非常挑剔的 Linux 开发者,他们觉得自己的安全已经很到位了……但是他们中的大多数人都没有运行任何终端安全工具,也没有人相信任何安全解决方案有效……我们用的很多包管理工具都是随意地把代码塞进我们的环境里,没人审计,也没人知道里面到底是什么。很多包名其实是恶意的拼写错误……所以,像 brew、npm 和 pip 这些工具其实是非常可怕的机制,但我们每天都依赖它们,它们已经成为攻击目标,而我们并没有意识到,软件开发者依赖的基础设施实际上是非常脆弱的。

Mat Ryer: 这真有意思,因为你真的不会这么想。你会以为大多数软件开发者至少对安全有一些了解,他们会用 1Password 之类的工具……但嗯,这确实很有趣。

Juan Andrés Guerrero-Saade: 抱歉…… [笑声] 抱歉,伙计们。

Joakim Kennedy: 这戳得很深啊。 [笑声]

Mat Ryer: 是的……它烧得很痛!如果你必须选择,谁的安全性更好呢,软件开发者还是祖母?你会选哪个?

Juan Andrés Guerrero-Saade: 这取决于影响吧?你知道的,很容易看不起祖母们,但---

Mat Ryer: 因为她们个子小?

Juan Andrés Guerrero-Saade: 不,因为你知道---你在谈论一个不同的世代,她们不一定是最精通技术的人,可能更容易被欺骗,确实,有很多这样的情况。但我认为区别在于,她们通常是非常随意的互联网用户。所以你要保护的最多也就是几个密码,可能还有一些家人的照片,这些东西在情感上很重要,但在更大的格局中并不那么重要。而软件开发者---当然,可能更容易欺骗或者对他们进行社会工程攻击,但如果你真的黑进了他们的机器,他们可能有许多不同服务的 SSH 密钥,他们可能对整个公司的源代码库有完全的权限……所以影响通常会大得多。

Mat Ryer: 嗯……我现在很高兴我说了那个蠢问题。这真的很有趣。

Juan Andrés Guerrero-Saade: [笑] 必须为祖母们辩护啊,伙计。

Mat Ryer: 是的。备注:多说些蠢话。Joakim,你有不受欢迎的观点吗?

Joakim Kennedy: 嗯……这可能有点针对使用 Slack 的社区……

Mat Ryer: 来吧,放马过来!

Joakim Kennedy: 是的……我不认为开源社区应该依赖商业产品。相反,他们应该使用相似的开源项目。

Mat Ryer: 这是个非常好的观点。你想多解释一下吗?

Joakim Kennedy: 我需要吗?

Mat Ryer: 因为 Slack 是免费使用的,对吧?

Joakim Kennedy: 是的,它是免费使用的,但你最终会陷入那种依赖商业实体的锁定状态。

Mat Ryer: 打断一下……我们稍后回来。对不起,刚才需要插播广告。你刚才说到哪里了? [笑声]

Joakim Kennedy: 尤其是现在,有一些非常优秀的开源项目正试图打破这种局面……Matrix 就是一项很棒的技术,它是去中心化的,即使他们消失了,社区也不会跟着消失。

Mat Ryer: 是的,但 Matrix 的问题是你必须在头上安装一个东西来插入……很多人反对这种做法。 [笑声] 我很早就做了这件事,当时还是用软盘……我是个非常早的采用者,现在有点后悔,但……那时候---

Joakim Kennedy: 但它确实很快就教会你功夫了……

Mat Ryer: 是的,不过那需要八张软盘,所以确实需要一段时间……而且你自己很难做到。你需要朋友帮忙,比如 Morpheus,或者 Trinity……但不,这确实是个非常好的观点……这个观点是否不受欢迎,真的会很有趣。

我们当然会在 Twitter---另一个商业平台---上测试这些观点,做一个 Twitter 投票,看看这些观点是否真的是不受欢迎的。

Joakim Kennedy: 我想现在的社区已经不像以前的密码朋克社区了,对吧?如果你看看大多数常见的软件开发环境---你把所有东西都存储在微软拥有的产品上,找新工作时也用的是微软拥有的产品,你在 Slack 上交流,把所有东西都上传到 Google Docs……基本上你是在信任地球上最大的公司来支持和推动你新发现的事业。这和 90 年代的社区完全不同……

Mat Ryer: 是的,确实如此。你有另一个不受欢迎的观点吗?你提到过你有两个。

Joakim Kennedy: 有的。我觉得这个观点对欧洲观众来说可能更具争议,而对 Go 社区倒不一定。

Mat Ryer: 你是要说“美国很棒,美国是最好的”吗?

Joakim Kennedy: 哇哦!

Mat Ryer: “美国第一!” 这是你的不受欢迎的观点……

Joakim Kennedy: 完全的爱国主义。美国第一!不,其实是关于我们在安全领域非常敏感的话题---GDPR。我不知道大家对 GDPR 有多熟悉;可以想象,任何处理 PII(个人身份信息)的人都在 GDPR 中度过了噩梦般的日子,而我这个不受欢迎的观点是,GDPR 其实就是一种让人感觉良好的安全姿态。除了让人感觉温暖舒适之外,它几乎没有任何真正的价值。对于那些需要遵守的人来说,它是一个噩梦,而在安全领域,它实际上使我们难以维持我们通常依赖的重要遥测数据。所以是的,我对 GDPR 有很强的意见。

Mat Ryer: 哇哦……是的。

Juan Andrés Guerrero-Saade: 你只是难过你失去了 whois 信息。 [笑声]

Joakim Kennedy: 是的,我确实对失去 whois 信息感到不满。整个论点是“哦,我们要保护人们免受垃圾邮件的困扰。” 但实际上,他们根本没有保护我们免受垃圾邮件的困扰,现在我也无法知道谁拥有这台服务器。

Mat Ryer: 是的……

Natalie Pistunovich: 我觉得这个观点会赢……我赌它会成为下次 Twitter 投票中的不受欢迎的观点赢家。

Mat Ryer: 它的体现就是每个网站都会问你是否接受 cookies。

Joakim Kennedy: 一遍又一遍地问。

Mat Ryer: 还有另一个按钮,你可以去配置它,然后你会进入一个庞大的偏好设置页面---

Natalie Pistunovich: 然后它会加载,加载,加载……

Mat Ryer: 是的……或者是巨大的设置页面……你甚至无法告诉浏览器你是否想要 cookies,然后它就会自作主张替你回答。你每次都必须---

Joakim Kennedy: “我会很友好……”

Mat Ryer: ……点击那个按钮每次进入网站……基本上,人们只是说“是的,接受 cookies”,只是为了摆脱这个讨厌的屏幕。

Natalie Pistunovich: 好吧,真是引人入胜,非常有趣,我希望所有听众都学到了新东西,并得到了启发。首先我要说非常感谢 JAGS 和 Joakim 的参与,也感谢 Mat 的共同主持……肯定会在关于 AI 生成恶意软件的那集节目中再见到你们。

Joakim Kennedy: AI 生成的恶意软件……

Juan Andrés Guerrero-Saade: 总有一天。谢谢你们两位。



—以上为part1—

part2

帮我整理这一期英文播客,翻译为通顺的中文,请保留完整内容,不要删减,谢谢!

本篇内容是根据2022年10月份#205 Hacking with Go: Part 2音频录制内容的整理与翻译

我们再次从安全研究人员的角度探索 Go 中的黑客攻击。这次Natalie & Ian与Ivan Kwiatkowski (又名 Justice Rage)一起讨论!

过程中为符合中文惯用表达有适当删改, 版权归原作者所有.



Natalie Pistunovich:大家好,欢迎收听我们今天的节目!今天是周三,我们的录制时间通常是周二,但因为我们今天有一位非常特别的嘉宾,所以特别安排了这次特别的录制。今天的联合主持人是 Ian。你好,Ian。

Ian Lopshire:你好,Natalie。你最近怎么样?

Natalie Pistunovich:很好!我非常兴奋今天能邀请到 Ivan 来参加我们的节目。Ivan Kwiatkowski,也就是大家在推特上熟悉的 @JusticeRage。他是卡巴斯基 (Kaspersky) 的高级安全研究员。

Ivan Kwiatkowski:是的,大家好,我很高兴能参加今天的节目。确实,我是卡巴斯基的一名高级安全研究员。我的工作领域是威胁情报,日常主要负责分析恶意软件并撰写相关报告。我的工作可以概括为:试图弄清楚攻击者在做什么,他们使用了哪些工具、方法,以及他们的目标受害者类型是什么。然后我们会将这些信息整理成报告,提供给我们的客户。客户通过阅读这些报告,可以判断某个攻击组织是否可能会针对他们发动攻击,以及如果遭受攻击,他们应该如何防御。比如,了解攻击者使用的恶意软件类型、常用的攻击途径等等。这些信息非常关键。

我的大部分时间都花在使用 IDA Pro(逆向工程工具)上,有时候也会为大学或客户进行逆向工程的培训。

Natalie Pistunovich:你还有一个非常酷的视频,是一年前发布的两部分系列,讲解你如何对一个用 Go 语言编写的恶意软件进行逆向工程。

Ivan Kwiatkowski:没错。

Natalie Pistunovich:而且这个恶意软件是来自 SolarWinds 攻击事件的。

Ivan Kwiatkowski:完全正确。这个案例正是来自 SolarWinds 事件。我相信大多数听众对这个事件都有所耳闻,因为它是一个媒体广泛报道的重大安全事件。简单总结一下,这次事件的主角是一家公司---我总是搞混它们的名字。我记得公司名是 SolarWinds,产品叫 Orion IT,但也可能是反过来的?我经常记不住。

Ian Lopshire:我觉得你说的是对的。

Ivan Kwiatkowski:好吧,很高兴我猜对了。无论如何,这家公司遭到了攻击,但攻击者的目标并不是窃取它本身的信息,因为它只是一个软件公司,作为情报目标价值不大。真正的关键是,这家公司有大量高价值客户,其中包括美国政府机构和一些大型企业。攻击者成功地破坏了其软件构建链,将自己的代码插入到该公司的软件中,然后这些修改后的软件被推送给客户。

通过这种方式,攻击者创建了一个后门,所有 SolarWinds 的客户都会自动安装这个后门。这个后门的第一阶段非常隐秘,恶意程序会休眠两到三周以避免被发现。一段时间后,它开始连接到命令与控制服务器 (C2 Server)。对于攻击者感兴趣的目标,他们会接收到第二阶段的恶意负载,从而允许攻击者进入网络,收集情报等。

攻击的第一阶段仅仅是在原始程序代码中进行了一些修改,这部分代码是用 .NET 编写的。而第二阶段的代码,名为 SUNSHUTTLE,是用 Go 语言编写的。这也是我第一次参与对 Go 语言编写的程序进行逆向工程。虽然学习曲线有些陡峭,但我将这次经历当作学习的机会,同时也将其作为后续逆向工程课程的案例。对于那些想学习如何逆向工程 Go 程序的人来说,这是一个很好的例子。此外,如果你是个 Go 语言爱好者,逆向工程可以帮助你更深入地了解这门语言的底层运作机制。我认为这对软件开发者来说也非常有趣。

Ian Lopshire:这是一个非常有名的用 Go 编写的恶意软件。你还能想到其他用 Go 编写的知名恶意软件吗?

Ivan Kwiatkowski: 是的。从同一个 SolarWinds 事件中,还有一个相关案例:Mandiant(现在属于谷歌)是受害公司之一。他们是最早发现网络异常并报告问题的公司。所以,真的要对他们表示敬意,他们做得非常棒。但攻击者对 Mandiant 特别感兴趣的原因之一是,他们想要获取 Mandiant 用于渗透测试和红队演练的工具。而这些工具实际上是用 Go 语言编写的。我认为这对分析师来说非常有趣。

此外,还有一些在 GitHub 上的项目,比如一个叫 Stowaway 的工具。这是一个网络代理工具,可以在不同协议之间传输数据,非常复杂。它也是用 Go 编写的,并且已经被一些威胁行为者修改和利用。

Natalie Pistunovich:我们会在节目笔记中添加这个工具的链接,听起来很有趣。

Ivan Kwiatkowski:好的。这种工具特别麻烦,涉及大量的 goroutines(协程)之间的通信,很难弄清楚其架构。

另一个例子是商业化的后门程序 Brute Ratel。我不完全确定,但我相信它也是用 Go 语言编写的。这款工具是 Cobalt Strike 的一个新竞争对手,专注于规避检测,避开 EDR(终端检测与响应)解决方案。我需要再确认一下,但这些例子表明,用 Go 编写的恶意软件家族正在增加,我认为未来这样的例子会越来越多。

Ian Lopshire:你为什么认为会有越来越多的恶意软件用 Go 编写?是因为 Go 难以逆向工程吗?还是有其他原因?

Ivan Kwiatkowski:是的,有几个原因。第一个原因是开发便利性。Go 语言生成的二进制文件是静态构建的、自包含的,不需要额外的库,这对攻击者来说非常方便。他们可以创建一个后门程序,将其发送给目标,或者通过其他方式部署到目标机器上,然后它就可以直接运行了。无需考虑目标机器上是否有所需的 DLL 文件,或者是否需要引入额外的库。

第二个原因是,Go 程序的逆向工程对我们来说非常困难。Go 编译器生成的汇编代码完全不同于其他语言,它有自己的风格。传统的自动化分析工具无法很好地识别 Go 语言的代码,因为它看起来和其他语言完全不同。

最后,Go 编译器在变量分配方面非常灵活,这使得追踪变量和函数调用变得极其复杂。尽管如此,随着工具的改进,这种逆向工程的挑战可能会逐渐减少。

Natalie Pistunovich: 对于那些没有看过你视频或者不熟悉逆向工程的人来说,我可以简单解释一下:逆向工程大致就是查看程序的指令,然后尝试从入口点(通常是 main 函数)开始,逐步追踪程序的逻辑。这就是逆向工程的基本工作流程。

Ivan Kwiatkowski: 好的,也许我可以为那些不熟悉逆向工程的人简单解释一下。逆向工程的核心思想是,我们尝试在没有源代码的情况下理解一个程序的功能。这种情况在恶意软件的分析中尤其普遍,因为我们不可能联系恶意软件的作者并说:“你好,请把你的代码给我看一下,因为我搞不懂这里到底在干什么。”我们不知道这些人在哪里,他们也不希望被找到,更不会主动把代码交给我们。因此,我们别无选择,只能直接观察程序,看看它向 CPU 发送了哪些指令。然后基于这些底层的 CPU 指令,推测它们可能对应的高级代码是什么。

这并不完全是猜谜游戏,因为它更像是一门相对精确的科学。但同时,这种操作对人类来说是非常不自然的,因为 CPU 指令语言是为机器设计的,而不是为人类设计的。对于我们来说,这些指令非常难以理解,看起来完全没有逻辑。仅仅通过这些指令推测出程序员的意图需要付出很大的努力。这也是为什么整个行业(不仅仅是卡巴斯基)都在寻找擅长逆向工程的人才。因为大多数人会觉得这项工作很不愉快,我自己大部分时间也觉得很痛苦……但在最终搞清楚程序到底在做什么的时候,那种成就感让我觉得非常满足,所以我依然坚持做这份工作。不过,总的来说,这是一件相当困难且耗时的事情,即使是分析最简单的程序也是如此。

Natalie Pistunovich: 尤其是当你连工具都不够用的时候。

Ivan Kwiatkowski: 是的。

Ian Lopshire: 作为参考,比如 Go 语言代码和汇编代码的行数比例,你知道大概是多少吗?比如 1 比 100,还是 1 比 1000?

Ivan Kwiatkowski: 这是个好问题,这取决于代码的复杂性。在 Go 语言中,你可以写一些函数调用链起来的长代码行,我不确定这是否符合 Go 官方的代码风格规范,但如果你这么做了……我们从另一个角度来看吧。如果你写一些普通的 Go 代码,比如一个 “Hello World” 程序,它大概会转化为 10 到 15 行汇编代码。所以我会说一个普通的比例是 1 行 Go 代码对应 15 行汇编代码。但如果你的代码更复杂,比如返回多个值或调用多个函数,那么汇编行数会更多……但总的来说,我觉得这个比例差不多是对的。

Ian Lopshire: 嗯,这让我有了一个大致的概念。

Natalie Pistunovich: 那其他语言呢?汇编量会更多吗?还是更少?或者差不多?

Ivan Kwiatkowski: 我会说基本上是差不多的。C++ 的比例大概和 Go 很接近,而 C 的转化可能更直接一些。C 和汇编代码的对应关系更加直接,这是我能想到的描述。但总体来说,这种比例对编程语言来说是相当普遍的。问题不是 Go 生成了更多的汇编代码,而是它生成的汇编代码和我们习惯看到的不一样,这让我们不喜欢。

Natalie Pistunovich: 有趣的是,也许一两年后,会有更多的支持和模式识别工具来解决这个问题……

Ivan Kwiatkowski: 是的,这取决于攻击者。如果我们看到越来越多的 Go 工具,那么这会迫使工具开发者,比如 IDAGhidra 等,改进对 Go 的检测和支持。我很确定自从上次我尝试对一个 Go 程序使用反编译工具后,IDA 已经有了改进,现在可能没那么糟了。但如果我们继续看到用 Go 编写的攻击工具,那么这些工具肯定会变得更好。

我们仍然需要弄清楚 Go 的汇编是如何工作的,尤其是如果以后它再次发生变化……不过总的来说,至少过去几年对 Go 的支持已经有了很大的改进,我相信如果有需求的话,这种改进还会继续。而且我猜 Go 在攻击性软件中的使用只会越来越普遍。

Natalie Pistunovich: 因为你提到的那些原因。

Ivan Kwiatkowski: 是的,完全正确。

Natalie Pistunovich: 我有一些具体问题……你提到过,当你用 IDA Pro 查看加载的 Go 代码或二进制文件时,有一些行为让你感到意外。我想描述两种情况,你来告诉我这些情况是好是坏,或者与其他语言相比有什么不同……这个话题挺有趣的,但可能会过于深入,所以我们尽量保持在一个稍微高一点的层次上,方便听众理解。比如,你提到跳到下一条指令时,会直接跳到程序中完全不同的地方。

Ivan Kwiatkowski: 是的,没错。这件事让我非常惊讶。当我用 IDA Pro 逆向工程分析程序时,我们可以静态地查看程序,也就是显示指令,然后像看书一样阅读它们……还有另一种方法,不是完全相反,但可以说是一种补充方法,那就是在调试器中查看程序。调试器的工作方式和软件开发中的调试器是一样的:逐条指令或逐行代码地执行程序,同时可以查看各种变量的状态。但我们没有源代码,所以我们只能逐条查看汇编指令的执行。当然,我们仍然可以观察 CPU 寄存器的更新状态等。

当我用调试器调试 Go 程序时,我非常惊讶,有时我从一条指令跳到下一条指令,却被带到了程序中完全随机的地方。最终,通过搜索和研究,我发现这实际上可能是 Go 的调度器在起作用,或者更可能是垃圾回收器在释放那些不再使用的变量。垃圾回收器有时会优先运行,释放内存,然后在运行完成后,再把程序带回之前的地方。

这一点对于我们这些逆向工程师来说特别让人震惊。我们盯着程序中的某个地方,非常专注地皱着眉头,然后按下 F7,想要单步调试,结果突然跳到了一个完全不同的地方,而没有看到任何跳转指令。这时我们会想,“发生了什么?我的程序怎么了?它不应该跳到其他地方啊。”

后来,我弄清楚了发生了什么,明白只要退出垃圾回收函数,就会回到原来的地方,一切都会恢复正常。但最初,这是 Go 的一个非常陌生的特性,我对此很不满。

Natalie Pistunovich: 所以这意味着在其他语言中,这种行为并不常见……

Ivan Kwiatkowski: 哦,不,这种情况我以前从未见过。我知道其他语言也有自己的垃圾回收器,但像 Java 这样的语言,我们通常不用查看汇编指令,因为 Java 是编译成字节码的。我们可以直接读取反汇编或反编译后的代码,并获得类似源代码的东西。虽然可能会被混淆处理,比如变量名被去掉了,或者代码被故意设计得难以阅读……但在 Java 或 .NET 中,我们从来不用担心 CPU 指令,因为它们和语言的相关性不大。

所以对我来说,Go 是一个很大的惊喜。这是我第一次遇到调试一个程序时,代码会在没有任何提示的情况下跳到其他地方,而且这种情况还经常发生。

Natalie Pistunovich: 还有另外一个行为让我觉得很奇怪,你提到过,当有两条连续指令使用同一个变量时,你没有看到返回值,而是直接使用了前后指令的结果。

Ivan Kwiatkowski: 我不确定是否完全记得你提到的这个细节……但我注意到,Go 的编译器在某些方面非常聪明。比如说,当你有链式函数调用时,某些函数的返回值会直接保存在栈中,恰好可以被当作下一个函数的参数使用。所以你不会看到数据在函数之间来回移动;编译器知道返回值正好在下一个调用所需的位置。

对于我们来说,通常会查看函数调用,观察输入和输出的数据流,借此理解程序发生了什么。但在 Go 中,有时候这些操作是隐藏的,因为编译器在栈中构建了一个优化的布局。这对 Go 程序员来说是件好事,因为这意味着程序中不需要那些多余的内存移动操作。而每次内存移动都会消耗时间。虽然对人类的时间尺度来说这很快,但对 CPU 来说,每次访问 RAM 都需要通过总线发送信号到主板,再从 RAM 读取数据返回 CPU。相比之下,直接在 CPU 内部移动数据或者完全不移动数据的性能会显著提升,尤其是当程序中有大量函数调用时。

Natalie Pistunovich: 从外部视角听到这些真的很有意思……

Ian Lopshire: 这让我想更深入了解逆向工程,研究程序的内部机制。

Ian Lopshire: 好的,也许我们换个角度聊聊吧。Go 社区非常重视一致性,比如有很多静态分析工具(linters)和 go fmt 这样的工具来保持代码风格一致。这对逆向工程有没有帮助?还是说在你们分析的层级上,这些工具影响不大?

Ivan Kwiatkowski: 这是个好问题。我必须说,我对这些工具本身了解不多。我写过一些 C 代码,也尝试过写 Go 代码,同时查看生成的汇编代码是否一致。这是我对 Go 的主要体验。我注意到,Go 语言非常严格。我以前用过一个比喻,可能会让你笑……我说,如果你在 Go 程序中没有使用某个返回值,编译器会抱怨。如果你有未使用的变量,编译器也会抱怨。我曾经开玩笑说,Go 感觉像是“法西斯版的 Python”,因为它完全不允许你随便写,只有严格按照规则写,才能通过编译。

对我们来说,这些限制影响不大,因为这些检查是在编译器层面完成的。如果代码不合规,你根本无法生成二进制文件。而且,即使有未使用的变量,我们作为逆向工程师也不会在意,因为我们知道它不会被使用,会直接跳过。

不过,这种严格性确实让我们更容易推测程序的行为。比如,当我看到一个返回多个值的函数时,尽管我不是 Go 开发者,但我会假设最后一个返回值或第一个返回值是主要的结果(我需要确认哪个是对的)。因为我知道 Go 社区有这样的规范,而且编译器会强制检查,即使开发者不想遵守。因此,我可以根据这些约定来做出更有把握的推测,这对我们来说其实挺有帮助的。

Natalie Pistunovich: 那你觉得 Go 是一个适合黑客或者安全研究员学习的语言吗?

Ivan Kwiatkowski: 嗯,我并不想帮助攻击者变得更高效……但如果非要回答,我觉得 Go 确实是一个值得学习的语言。基本上,任何远离传统语言的选择对我们来说都会更麻烦,因为我们不熟悉它。我认为 Rust 也是不错的选择。我自己对 Rust 了解不多,但我的同事研究过,他说 Rust 就像更难的 C++。这已经是个很高的标准了。所以,我的建议是 Go 和 Rust 都是不错的选择……不过,这可不是真正的建议,请不要用来做坏事。

Ian Lopshire: 如果这些是新兴语言(Go 和 Rust),那么从历史上看,黑客和研究人员主要使用哪些语言呢?

Ivan Kwiatkowski: 实际上,几乎所有语言都被用过。你知道墨菲定律吧?它说“任何可以被滥用的东西,最终都会被滥用。”编程语言一次又一次地证明了这条定律的正确性。作为分析人员,我们接触到的是黑客所使用的工具,我们并不能选择他们用什么语言。黑客会根据自己的熟悉程度、喜好或者方便程度选择语言。这就是为什么我们有时会遇到一些非常离谱的情况,比如用 AutoIt 写的恶意软件。不知道你听说过没有,AutoIt 是一种用于 UI 测试的脚本语言,可以模拟按键和鼠标操作。结果有人用它写恶意软件。

所以,任何曾经存在的编程语言,最终都可能被用来写恶意软件。这是逆向工程师的宿命:无论收到什么样的恶意软件,不管是用 C、C++,还是 Go、Delphi、Pascal,甚至 Erlang 写的,我们都得研究它。因为我们的工作目标是弄清楚特定事件中到底发生了什么。我们无法挑剔语言,只能适应,因为我们迟早会遇到各种语言编写的代码。

Ian Lopshire: 你刚才提到,你的研究对象是黑客留下的东西,无论是恶意软件还是其他内容。除了实际的二进制文件,还有什么其他东西可以研究?比如日志之类的吗?

Ivan Kwiatkowski: 通常在一次安全事件中,会有专门的人进入我们所说的“取证模式”。他们会收集所有的日志、硬盘,并试图搞清楚网络内部到底发生了什么。他们不仅会收集机器的日志,比如 DNS 日志,还会收集所有由 Windows 机器生成的事件日志,以及 HTTP 代理保存的日志等。如果可能的话,还会收集 NetFlow(网络流量信息),尽管通常这种信息很少能拿到。在安全事件中,往往能获取的数据是有限的。不过,这些事情是应急响应团队的职责,而我并不是一名应急响应人员,我已经有足够多的事情要操心了。我主要关注的是实际的恶意软件,我们可以通过卡巴斯基的杀毒软件获取有关恶意软件执行环境的信息。比如,我们可以看到“这个进程启动了那个进程”等等。我们有这些信息。在更大的事件背景下,通过这些信息可以更清楚地了解受害者网络中发生的所有事情。这些信息可以帮助我们重建整个事件的时间线。

比如你会发现,在某个时间点,某个前端网站有可疑的请求。随后你可能会看到同一台网站服务器上创建了一个文件。接着,你可能会观察到某些奇怪的、可疑的请求被发送到 Active Directory(活动目录)服务器,比如使用了 Golden Ticket 或 Mimikatz(工具)等 lateral movement(横向移动)技术。最终,攻击者可能会在某处投放一些二进制文件,以帮助他们在受害者的计算机上保持持久性,或者进一步渗透到网络的更深层。因为攻击者会尽可能避免部署任何东西。一些非常谨慎的攻击者甚至不会在磁盘上部署任何文件,他们只会将所需的程序加载到内存中。这种方法非常隐秘,但一旦机器重启,内存中的所有内容就会消失。如果攻击者没有办法重新进入受害者的机器,那么他们部署的访问权限就会丢失。一些非常隐秘的攻击者宁愿放弃访问权限,也不愿在硬盘上留下取证痕迹。但大多数攻击者,可能 90% 或 99% 的人,会觉得留下某种痕迹是可以接受的,因为他们知道大多数人不会去查看这些信息。而我们这些分析人员,如果发现了事件并收集了所有数据,最终就能分析这些二进制文件。

Ian Lopshire: 你提到应急响应团队负责收集所有这些数据和信息,对吧?

Ivan Kwiatkowski: 是的,没错。我们卡巴斯基也有这样的团队,大多数网络安全公司都会有自己的内部应急响应团队……

Ian Lopshire: 突然介入?

Ivan Kwiatkowski: ……或者会有长期合作的外部承包商,他们可以在任何时候被叫来支援,无论是白天还是夜晚。这些团队会带着“重武器”迅速介入,处理所有异常情况。当然,这并不意味着我们完全不和这些团队合作。只是说,这是他们的主要职责,而我们更像是“后勤部门”,我们接收升级上来的问题,然后深入分析。

不过,我们的大部分情报其实并非来自应急响应案例。我觉得如果我们能从这些案例中获取更多信息,那会是一个很好的补充,因为这些信息非常有价值。但我们主要依赖杀毒软件收集的遥测数据,包括所有可疑的样本或被上传到云端分析的样本。我们也会静悄悄地介入,查看这些数据,寻找“有趣”的点,比如“我们之前从未见过的东西”,或者像某种 10 年前出现过的恶意软件,现在又经过了一些修改。我们会关注这些变化。但我们的工作往往和具体的事件有些脱节,更专注于分析我们拥有的庞大数据湖,试图从中找出有价值的信息。

Ian Lopshire: 很酷,谢谢你的分享。

Natalie Pistunovich: 那从另一面来说,对于写安全软件的开发者,特别是用 Go 的开发者,你有什么建议吗?或者即使不是 Go 特定的建议,一些通用的建议也很有用。

Ivan Kwiatkowski: 好的,我觉得 Go 的一个主要吸引力在于,它不需要你像使用其他语言那样过多地考虑安全问题。Go 是一种内存安全的语言(如果我没记错的话),编译器不会让你犯低级错误,比如创建一个过小的数组,然后向其中写入超出范围的内容---这是根本不可能的。所以它消除了很多我们称之为“内存损坏”的漏洞。这意味着那些困扰 C 和 C++ 程序几十年的经典缓冲区溢出问题,在 Go 中永远不会发生。当然,这并不意味着用 Go 写的程序就完全没有安全问题,只是这些问题不会与“我写了一个编程错误”相关,而更多是设计上的问题。例如,内存安全的语言并不能帮助你实现一个安全的认证方案,也不能帮你设计一个完善的网络协议。

我注意到 Go 对于加密操作也提供了很多帮助。它很难让你选择不安全的算法。默认情况下,你只能选择一些安全的算法,比如 AES。我发现,Go 中的一些加密模式,比如加密块的工作模式,通常是由系统默认选择的,而这些默认值是安全的……所以你不会因为选择了一个糟糕的选项而犯错。

我之前用 Go 写过一些代码,涉及 AES 加密。当时我试图弄清楚初始化向量(IV)是如何生成的,结果发现开发者根本没有写任何相关代码。经过研究,我发现是 Go 默认生成了 IV,并在最终加密的缓冲区中附加了这个向量。而在其他语言中,这通常需要开发者自己完成,这也成为常见的错误来源。如果你选择了一个糟糕的 IV,比如全是零,或者根本没有选择,那么你的加密就会有问题。但 Go 不会允许你犯这些错误。

所以,我很明显能感受到,Go 是以安全为核心设计的。但这种安全并不是为开发者提供的,而是由 Go 的创造者设计好的。他们不希望你“自毁长城”,并确保你几乎不可能犯低级错误,除非你非常刻意地去做。

尽管有这些保护措施,加密仍然可能被滥用。如果你选择了一个糟糕的密钥,没有人能救得了你。如果你的协议存在问题,Go 也无法保护你。但我认为 Go 能够让开发者更多地关注设计缺陷,而不是编程错误。这已经大大减轻了开发者的负担。


Natalie Pistunovich: 这是一个非常有趣的见解。

Ian Lopshire: 确实很有意思。我经常在 Go 社区之外,比如 Hacker News 上,看到人们抱怨 Go 默认选择了 TLS 配置,或者不让你做某些事情……但我完全支持这种做法。如果我不需要思考这些问题,那我也不想去管,更不想犯错。

Ivan Kwiatkowski: 你能自信地为 TLS 选择默认配置吗?我觉得我自己都不敢。你需要对加密学非常精通,才能做出这样的决定。所以我认为 Go 不让你做这些决定是一件好事,这是我的看法。

Natalie Pistunovich: 关于你对 Go 的兴趣,你提到你是因为恶意软件才开始接触 Go 的,对吗?

Ivan Kwiatkowski: 是的,没错。但我不会说我开始“使用” Go,而是说我“被迫”学习了 Go。当然,这并不是说我对此感到不满,也不是说这是一件坏事。我的意思是,我并不真正写 Go 代码。我做的是处理由 Go 编译器生成的汇编代码,然后试图弄明白它的逻辑。我会看着汇编代码,猜测“这可能是由 Go 代码生成的汇编”,然后打开我的 Go IDE,编译一些代码,看看两者是否一致。

当我想要逆向工程某种语言时,我觉得写一些简单的程序然后编译它们,并观察底层的汇编代码是非常有用的。比如,写一个简单的函数,比如加两个整数的函数,这样可以帮助我看到程序使用了什么类型的函数调用,以及语言生成了什么样的结构。不过,我遇到的问题是 Go 编译器对我来说“太聪明”了……它倾向于内联所有过于简单的函数调用。我的意思是,如果你写了一个非常简单的函数,然后调用它,Go 编译器会觉得,“这个函数调用不值得,我会把这个函数的代码直接插入到调用点。”所以,当你试图通过汇编代码查看函数调用是什么样子时,这种优化并不会对你有帮助。但好在,我找到了正确的编译器标志,可以禁用所有优化,然后一切就顺利了。

Natalie Pistunovich: 你提到 IDA 是你主要使用的工具之一,但它和另一个工具并不完全支持 Go。如果有人想尝试逆向工程,尤其是针对 Go 语言,你会建议他们怎么做?

Ivan Kwiatkowski: 如果你要逆向工程 Go 程序,我认为目前你并没有太多选择。所以你仍然需要使用 IDA Pro 或 Ghidra。我个人想最终转向 Ghidra,但目前还没有这样做,所以我不能对它的能力发表太多评论。我听说它在快速改进,所以可能是一个不错的选择……不过说到 IDA,情况确实有所改善。我记得几个月前,或者说可能已经有一年了,你们邀请了 SentinelOne 的 Juan Andrés Guerrero-Saade,他是我的好朋友之一,他可能在节目中提到了他写的各种插件,这些插件可以帮助大家用 IDA 逆向工程 Go 程序。我自己也为他的代码库贡献了一些脚本,我觉得这些脚本挺有用的。

总的来说,尽管 IDA 在处理 Go 程序时可能不是最完美的工具,但它仍然是仅有的两个选择之一。所以无论如何,你都得用它。这让我觉得,虽然起初逆向工程 Go 会有些困难,但最终我发现自己更喜欢逆向工程 Go 程序,而不是 C++ 程序。C++ 程序往往非常复杂,有虚函数表,还有那些用于表示类的复杂结构……而对于 Go 语言,它更像是一种脚本语言。最终,你会发现所有东西都变成了对某个 API 函数的调用,或者对 Go 标准库中某个函数的调用。所以,如果你能够使用调试器查看所有参数---当然前提是你知道怎么做---查看所有 Go 函数的参数和返回值(这些函数是有文档的),那么其实程序的意义就会显现出来,即使你无法完全理解中间所有的指令,或者跟踪程序中所有的东西。

总的来说,我对想要开始学习 Go 逆向工程的人建议是,虽然这会和你习惯的方式非常不同,但到最后,你可能会发现自己比想象中更喜欢它,因为它比看上去要简单得多。

Ian Lopshire: 那对于那些完全没有进行过逆向工程,但想要开始学习的听众,你有没有什么好的资源推荐?我知道你自己也做过一些视频,能谈谈这个吗?还有其他有帮助的资源吗?

Ivan Kwiatkowski: 是的,我发布的视频是专门针对 Go 语言的。如果你要学习逆向工程,我不建议你从 Go 开始。并不是因为它更难,而是因为逆向工程的基础知识通常和传统的 C 代码或由 C 生成的汇编代码有关。这些是你的基础知识。一旦你能理解 C 语言及其生成的汇编代码,你就可以转向其他语言,看看它们和 C 有什么不同。

我认为 C 一直是其他语言的参考点。当你看汇编代码时,你首先会试图像理解 C 那样理解它,然后如果发现不同,再进行调整。但如果你的基础是 Go,而你试图用 Go 的经验去理解其他语言的汇编代码,你会遇到麻烦,因为其他语言的代码看起来完全不像 Go。

我们在卡巴斯基有一些课程,大家可以去看看。另外也有一些不错的在线课程。比如一个叫 beginners.re 的网站,它以前是免费的,现在可能需要付费了,但它曾经是一个非常棒的逆向工程课程,由一位作者写的,内容非常全面。还有一本书叫《Practical Malware Analysis》(《实用恶意软件分析》),虽然这本书现在有点老了,但它的内容仍然很有价值。这是 No Starch Press 出版的,我认为对于初学者来说,这本书是个很好的入门教材,它解释了所有内容,还提供了各种可能需要的工具的链接等,总体来说是一个很好的资源。

最后,如果你想从有趣的角度入手,我可以推荐一些非常棒的 Steam 游戏,让你感受逆向工程的乐趣。其中一个叫 Turing Complete。这个游戏的核心理念是让你自己构建一台计算机。你一开始会得到一些逻辑门,比如 XOR 门和电缆,然后基于这些,你需要一步步构建一个 CPU。随着关卡的推进,抽象程度会越来越高。

这个游戏非常有助于理解程序或计算机的工作原理。它让你从更高的视角理解 CPU 是如何构造和运行的。而了解 CPU 的工作原理对逆向工程非常有帮助。

另外,还有一些由一个叫 Zachtronics 的开发者制作的游戏。这些是非常奇怪的益智游戏,但都与计算问题有关。其中一个叫 TIS-100,另一个叫 EXAPUNKS。这些游戏被称为“你不知道自己需要的汇编游戏”,这个描述非常贴切。因为这些游戏有自己的奇怪而有限的汇编语言,你需要用它们解决各种难题。你要编程一些小机器,让它完成任务,这个过程需要用汇编语言。这种设计的好处是,它让你学会如何操作 CPU 或理解汇编指令。所以我非常推荐这些游戏给想入门的人。

Ian Lopshire: 我以前从没想过游戏这个方式。我得抽空去看看。

Ivan Kwiatkowski: 如果你在大学工作,或者是某所学校的老师,Zachtronics 曾经有一个非常全面的教育计划。如果你在大学教授计算机科学课程或类似课程,你可以发邮件给他们,他们会免费提供所有的游戏授权,让你把它们用作教学工具。我觉得这真的非常棒。而且这些游戏真的很好玩。如果你喜欢汇编的话(虽然我知道这是我的偏见)……不过我还是强烈推荐它们。

Natalie Pistunovich: 你提到的很多内容简直就是一份逆向工程的备忘录。有很多有用的信息,我还有许多关于 Go 和逆向工程的具体问题想问你。我们可能需要再录一期节目,因为时间快到了。

Ivan Kwiatkowski: 当然可以。你们随时可以叫我回来。

Natalie Pistunovich: 我们会准备好问题,问你一些关于比如泛型之类的内容……

Ivan Kwiatkowski: 看来我也得准备这些问题了,不过没问题(笑)。

Natalie Pistunovich: 好了,现在是时候进入“不受欢迎的观点”环节了。

Natalie Pistunovich: 那么,Ivan,你今天要分享的“不受欢迎的观点”是什么呢?

Ivan Kwiatkowski: 天啊,我完全忘了这个环节了。不过没关系,好在我有很多“不受欢迎的观点”,所以我可以随便挑一个来说。你们可以告诉我想深入了解哪个。比如,我认为网络空间永远不会被真正监管;我认为 NFT 是骗局;我认为没有任何政治意愿去限制网络攻击工具的销售……类似这些。我确实有很多“不受欢迎的政治观点”,但我不想强加给你们。你们对我已经很友善了。

Natalie Pistunovich: 那你怎么看欧盟关于 USB-C 接口标准化的规定呢?

Ivan Kwiatkowski: 哦,我对此非常非常高兴。虽然这可能给一些设备厂商带来了压力,但我这些年来一直随身携带各种不同的充电器,实在太烦了。知道未来所有设备都会统一用 USB-C 接口,这让我感到无比开心。

我还有另一个“不受欢迎的观点”,你也可以加到列表里:我其实不是 Apple 的粉丝,一点都不是。我不喜欢他们的生态系统。我不想展开讲太多,但其中一个让我很不喜欢的点是,每次他们发布新产品,人们就得花 40 美元买新的充电器。而且每次充电器还不一样。我很高兴这次的规定切断了他们这部分收入来源,因为我觉得这种情况本来就不应该存在。

Ian Lopshire: 那你怎么看那些封闭的生态系统,比如 Google Play Store、Apple Store 和 Amazon Store?从安全实践的角度来看,他们声称这种方式更安全。你同意吗?

Ivan Kwiatkowski: 这是个很好的问题,我对此有点矛盾。我确实认为从安全的角度来说,这种做法有一定好处。它相当于增加了一层保护,防止人们在设备上做一些愚蠢的事情。比如说,我经常帮我妈妈的朋友修电脑,卸载恶意软件,修打印机之类的。所以,当有这样的保护措施时,我很高兴这些问题能少一些。不过,这些措施也并非完美解决方案。

我认为 Apple Store 在安全性方面做得不错,而 Google Play Store 的表现就稍差一些,尤其是在托管恶意软件方面的记录不太好。当然,我并不是说他们工作没做好,我认为他们的工作非常难。但事实是,Google Play Store 上确实有不少应用程序,要么是彻底的恶意软件,要么是收集个人数据的工具。

所以,我觉得保护这些设备的更好方法不是控制应用商店,而是提升设备本身的保护能力。比如 iOS 和 Android 近年来在这方面做得很好---确保应用不能随便访问用户的任何信息,仅仅因为用户在安装时点了“同意”。所以,重点应该是确保个人信息不能轻易被获取,而不是试图监管应用商店里的成千上万的应用程序。我觉得现实中你不可能完全保证这些应用程序总是安全的。

不过,封闭生态系统的另一个问题是,它可能确实提供了一些安全保障,但同时也剥夺了我作为用户的一些自主权。我非常喜欢完全拥有我使用的设备,但当有些限制告诉我“你不能安装这个应用,因为 Google 不允许”或“你不能卸载这个应用,也是因为 Google 不允许”时,这种情况让我非常非常生气。

Natalie Pistunovich: 你提到了很多“不受欢迎的观点”……

Ivan Kwiatkowski: 是的。

Natalie Pistunovich: 我们播客的 Twitter 机制是,我们会挑选一个“不受欢迎的观点”,然后发起投票,看看人们是否同意你的观点。我们还有一个“不受欢迎观点”的名人堂,用来记录最受欢迎和最具争议的观点。你列了好几个……你希望我们挑选哪个来投票呢?

Ivan Kwiatkowski: 如果我想赢得比赛,我可能会选择 NFT 那个观点,因为我知道这个话题很有争议。我觉得你们的观众可能会……我不是说他们一定站在我这边,但我觉得他们会形成自己的看法。不过,我更感兴趣的是让大家讨论网络监管这个问题。我认为网络空间永远不会被真正监管,也许我需要多说一些,让大家能更好地理解我的观点。

我的观点是,目前联合国有很多关于网络空间行为规范的高层讨论。比如,各国会讨论“什么样的网络攻击是合法的?”像间谍活动可能被认为可以接受,而破坏性攻击则可能不被接受。当然,我不是说这是对的,我只是说这可能是他们正在讨论的话题。我们可能对哪些攻击可以接受、哪些不可以接受,或者攻击是否可以接受本身有不同看法,但这些都不重要。

关键是,我认为我们永远无法在这个问题上达成一致。因为各国并没有动力去真正监管网络攻击。他们更倾向于保留进行网络行动的能力和框架。因为当他们进行网络行动时,他们知道自己能获得什么,比如通过网络手段收集情报,然后用这些情报达成一定的成果。而且这些成果是可以量化的。

但另一方面,网络攻击的代价,比如国内企业因缺乏规范而遭到攻击所造成的损失,却很难量化。你永远无法知道,你的公司是否因为某次网络攻击而失去了海外合同,比如飞机销售合同,因为很可能没人知道攻击发生过。

所以,决策者在权衡风险和收益时会发现,“网络攻击能带来的收益很大,而代价却很模糊,甚至完全不知道。”因此,我认为那些关于“我们需要一个更安全的互联网,等等”的讨论,可能是出于某种程度上的虚伪。因为实际上并没有政治意愿去停止这些行为。这就是我的“不受欢迎的观点”,尤其是在外交圈子里。

Natalie Pistunovich: 好的,到时候会在社交媒体上标记你,我们会关注投票结果。

Ivan Kwiatkowski: 好的。

Ian Lopshire: 我也很好奇这个结果。

Natalie Pistunovich: 是的。

Ian Lopshire: 这是一个很有趣的思考角度。

Ivan Kwiatkowski: 是的,我也想知道结果。

Natalie Pistunovich: 很棒,谢谢你跟我们分享你的知识、想法和观点。这真的非常有趣。我们很乐意再邀请你参加节目。谢谢你,Ivan。

Ivan Kwiatkowski: 非常感谢你们邀请我。如果之后有需要,随时联系我,我很乐意再来。

Natalie Pistunovich: 谢谢 Ian 的参与,和你一起主持很有趣。

Ian Lopshire: 谢谢你们,这次体验很棒。

Natalie Pistunovich: 再见,大家!




part 3

Hacking with Go: Part 3

Ivan Kwiatkowski 再次与 Natalie 一起探讨《Hacking with Go:第 2 部分》的后续剧集。这次我们将从用户/黑客的角度了解 Ivan 对 Go 安全功能的设计和使用方式的看法。当然,我们还将讨论人工智能如何融入这一切……

本篇内容是根据2022年11月份#259 Hacking with Go: Part 3音频录制内容的整理与翻译

过程中为符合中文惯用表达有适当删改, 版权归原作者所有.



Natalie Pistunovich: 今天,Ivan Kwiatkowski,你再次加入我们,继续探讨关于用 Go 进行黑客技术的一些内容,并补充我们上一期没来得及覆盖的部分。

Ivan Kwiatkowski: 是的,非常高兴能回来。

Natalie Pistunovich: 那么,对于没有收听上一期节目的听众,你可以简单介绍一下自己吗?

Ivan Kwiatkowski: 当然可以。我的名字是 Ivan Kwiatkowski,我是一名法国的网络安全研究员,目前在 Kaspersky 工作。我在威胁情报团队中工作,我的职责除了编写报告等常规工作外,主要是对同事提供的恶意软件进行逆向工程。基本上,我的同事们负责威胁狩猎,他们找到一些值得研究的东西,识别出攻击者使用的植入程序,然后交给我。接下来的工作就是利用逆向工程师的那些出色工具,比如 IDA Pro,阅读这些程序的汇编代码,尽可能弄清楚它们的功能。这就是我的工作内容。

Natalie Pistunovich: 在上一期节目中,我们聊了一些关于 IDA 对 Go 的支持问题。我们提到,虽然支持有所改善,但仍有改进的空间……

Ivan Kwiatkowski: 是的,完全正确。如果你在一两年前尝试对 Go 程序进行逆向工程,那将是非常困难的,因为当时工具还不够完善。这需要使用一些第三方插件,或者从一些---我不想说“可疑的”,但确实是没有很好维护的 GitHub 仓库中获取代码,这些代码也没有清晰的使用说明。所以,对于逆向工程师来说,这是一条相当艰难的道路。幸运的是,随着时间推移,IDA 的开发者---一个位于比利时的公司 Hex Rays---他们听取了用户的反馈,做出了许多改进。这些改进让我们更容易支持 Go 程序,包括识别来自 Go 标准库的各种函数,对 Go 二进制文件的整体支持也更好了……据我所知,在最新版本中,一些我们之前用第三方插件实现的功能,比如 SentinelOne 的 Juan Andres Guerrero-Saade 和我用 Python 手动实现的功能,现在已经被集成进了 IDA Pro 的主线版本中。

所以,我预计也许再过一年,这个问题可能就不再是个话题了。当然,从汇编代码的角度来看,Go 仍然是一个相对“陌生”的语言,但我相信,工具层面的难题很快会得到解决。这对我们来说是个好消息。

Natalie Pistunovich: 那么,Go 对安全研究员或者黑客来说,是更好的语言选择吗?

Ivan Kwiatkowski: 对安全研究员来说,我们其实不需要写太多程序。我们使用的大多数工具都是社区已经提供好的。我提到过 IDA Pro ---没人会重新开发一个 IDA Pro。当然,确实有人这么做了,所以说“没人”也不完全公平,但大多数人不会这么做。如果真要开发这样的工具,我认为选择 Go 或 C++ 并不会对项目规模有什么实质性影响。

对于黑客来说,我认为 Go 语言可能仍然是一个很好的选择,因为根据我从同事和其他领域的逆向工程师那里得到的非正式反馈,大多数人仍然非常不喜欢处理 Go 语言的程序---真的非常不喜欢。

不过,就我个人而言,如果我要写恶意程序,我会选择 Rust。因为 Rust 生成的代码实际上更加复杂,目前我还不太确定该如何处理这种代码。我需要花些时间研究,但我的直觉是选择 Rust,因为我知道这样可以让未来某些人的工作变得非常糟糕(笑)。

Natalie Pistunovich: 当你说“复杂”时,是指逆向工程、分析和理解代码的过程更加困难吗?

Ivan Kwiatkowski: 没错。我是从这个角度说的---也许这只是我个人的体会。我花了一些时间试图搞清楚 Go 是如何运作的,不是从语言层面,而是至少从汇编层面。我不敢说自己是 Go 的专家,或者对 Go 的内部机制有深入了解……但如果你给我一个用 Go 写的二进制文件,我相信最终我可以告诉你它的作用。然而,对于 Rust 来说,这对我来说仍然是未知领域。据我所知---虽然我没有深入研究,但我可能很快就需要这样做……目前看起来像 IDA 这样的工具,对 Rust 的支持似乎没有对 Go 的支持那么好。它无法识别那么多的东西。而且就结果来看,Rust 生成的代码结构非常像 C++,而 C++ 本身已经足够复杂了。C++ 是一个非常强大的语言,我个人很喜欢它。如果我需要写一个复杂的程序,我会选择 C++,因为这是我最熟悉的语言。但如果让我阅读用 C++ 写的汇编代码,天啊,那真是太复杂了,每一层都会增加很多间接性。所以,这不是我愿意面对的事情。而 Rust 作为一个新的、更复杂、更“陌生”的 C++,让我更加头疼。

Natalie Pistunovich: 那 Go 的跨平台编译功能呢?你只需要写一次代码,就能生成一个可以直接运行的二进制文件,然后再运行一个命令,就可以生成适配任意架构的版本。这个功能对你来说有什么意义吗?

Ivan Kwiatkowski: 这个功能确实有其意义,从开发者的角度来看,这种能力还是挺酷的。不过,我一直觉得很多 Go 语言的支持者强调的这项功能有点被过度吹捧了。我并不认为它是那么重要的功能。并不是说我们不需要跨平台编译,或者不需要能在任何地方运行的程序,而是我觉得 Go 语言在这方面并没有带来什么全新的东西。比如说,我用 C++ 写代码时,只要写得合适,代码本身就已经可以在任何地方运行了。再往前追溯,大概10年前我还在学校的时候,我们学的是 Java,当时我用 Java 写过一些项目。理论上,Java 也应该像 Go 一样能在任何平台上运行,对吧?虽然由于种种原因,它没有达到我们预期的效果,但总体来看,这种可以在任何平台运行并编译的能力,并不是 Go 独有的。我觉得这是我们已经具备的能力,Go 只是让它对很多开发者来说更简单了一些。但这并不是一个能让我因此转向 Go 的理由,绝对算不上什么颠覆性的功能。

顺便说一句,可能我可以反过来问你一个问题……我对 Go 在一些“冷门”平台上的支持情况并不了解。比如说,在 Solaris 或者 ARM 架构上运行 Go 程序,这些是能够开箱即用的吗?因为我很清楚,当有新 CPU 推出时,制造商发布的第一批工具中总会有一个 C 或 C++ 编译器。所以我们能确定这些语言最终总是会支持的。但我有一种感觉---可能是错的---如果是 Go 语言,当有一个新的平台出现时,你得等 Google 发布对应的编译器,而这可能需要一些时间。是这样吗?

Natalie Pistunovich: 你问的时候,我用 Google 搜了一下 [听不清的命令 00:11:35.24],这是用来干这件事的命令,对于 Solaris 来说是开箱即用的。那么你刚才问的第二个是什么?

Ivan Kwiatkowski: 我猜应该是受支持的,比如编译到 ARM 或者 MIPS 这种可能不太常用的架构……但我想至少 ARM 应该是支持得很好的。

Natalie Pistunovich: 是的,ARM 确实是开箱即用的,没问题。

Ivan Kwiatkowski: 好的。

Natalie Pistunovich: ARM-64 也支持。而 MIPS - 一些变种不支持,但大多数还是支持的。不支持的主要是 MIPS-64 P32 和 MIPS-64 P32LE。

Ivan Kwiatkowski: 嗯,对。总的来说,这些可能是大多数人根本不关心的架构。所以我不认为这是 Go 语言的重大缺陷。不过我想说的是,就我而言,C 已经是一个跨平台的语言了。如果其他语言也能提供这种能力,那很好,但对我来说,这并不是什么革命性的功能。

Natalie Pistunovich: 是的,这很公平。不过很多 DevOps 人员确实喜欢这个功能,因为你几乎不用做什么就能把程序适配到你喜欢的任何架构上。

Ivan Kwiatkowski: 是的,这个方面确实很重要,而且也很受恶意软件作者的欢迎。因为,当你用 C 写一个程序时,可能会有一些模块是以 DLL 文件的形式分发给 Windows 系统的,或者是以 .so 动态库的形式分发给 Linux。然后你会得到一个程序---一个可执行文件,以及几个附带的对象库。接着,当你想分发它时,你需要发送一个包含许多文件的大型归档文件。但是如果是用 Go 写的程序,你只会得到一个单一的二进制文件,这确实很有用,尤其是在你无法控制客户端,也就是“受害者的机器”的情况下。在这种情况下,只需要发送一个可执行文件,并且知道它是自包含的,可以随时随地运行,这无疑是一个巨大的优势。虽然用 C 或 C++ 也可以做到这一点,但需要一些额外的工作……而这些工作在用 Go 时是不需要的。所以这一点上,我会给 Go 加一分。

Natalie Pistunovich: 还有一个功能,你觉得怎么样,就是模块的概念?它会有一个文件,说明了所有的依赖项,以及每个依赖项使用的具体版本,比如如果你使用了某个旧版本的包之类的。既然这些都被编译到一个模块里,并被作为一个整体发送出去---这对黑客或安全研究员来说有价值吗?

Ivan Kwiatkowski: 对安全研究员或黑客来说,我不确定这是否有直接的价值。对于开发者来说,这可以防止他们陷入依赖地狱。这种问题我上周在一个 Python 项目里刚好遇到过。我更新了所有的包,它们之间有些不兼容,结果整个项目在生产环境直接崩了……总之,这很有趣(笑)。其实我本可以通过固定版本号来避免这种情况,这是你应该做的事情。但总体来说,有这样的机制还是很好的。

在安全方面,我们并不太担心黑客能否编译他们的程序。我们更担心的是,当你得到一个包含所有内容的单一二进制文件时,这对逆向工程师来说是个问题。相比之下,用 C 或 C++ 编写的程序可能会有多个 DLL 文件,而这些不同的文件本身就代表了一种代码的分离方式。DLL 文件可能会有相关的名称,也可能没有。但无论如何,你知道它们是按照某种功能拆分的。某些功能的代码会放在特定的 DLL 里,而程序的主要逻辑会放在主可执行文件里,等等。

所以,当你需要处理那些大型恶意软件平台时,有多个文件其实是对我们有利的。而当你面对一个 5 到 10 MB 的 Go 二进制文件时,你必须深入其中,试图找出有趣代码的位置---这可能是好事;哪些是无趣的库代码(工具通常能很好地识别这些)。但你仍然面对一个包含所有内容的大型程序。如果有这些分离的文件,你可以更专注于某些具体功能,即使你还没完全深入整个项目。所以从这个意义上讲,这个功能对攻击方来说是非常有用的。

Natalie Pistunovich: 我很好奇是否有任何特定的功能对防御方有利……我会继续问问题,直到找到一个答案。或者你有什么想法吗?

Ivan Kwiatkowski: 我确实有一个想法……我认为 Go 相比于 C 和 C++ 这些非托管语言最大的优势之一是,当你用 Go 编写程序时,你不用担心内存损坏、缓冲区溢出之类的问题。我会非常惊讶如果 Go 语言允许你访问数组边界之外的内容。这些问题已经被 Go 帮你考虑到了。这虽然不会让我的日常逆向工程工作更容易,但它提供了一个好处,那就是开发者更难“搬起石头砸自己的脚”。这对整个行业来说是件好事,即使是在安全领域之外---如果用 Go 写的程序,比如 FTP 服务器、邮件服务器等等,我们至少可以不用担心缓冲区溢出的问题。这意味着我们可以减少因为某些客户没有及时打补丁,或者因为某个零日漏洞被发现并在野外被利用,而导致的应急响应工作。

所以,对防御者来说,减少漏洞和减少开发者犯严重错误的可能性,总归是件好事。我觉得这一点实际上超越了攻击者在工具层面上对我们的任何优势。

Natalie Pistunovich: 这是一个非常有趣的观点。我想如果能整体评估一下用 Go 编写的代码是否更安全,那会很有意思。不过这要怎么衡量可能是个难题。

Ivan Kwiatkowski: 嗯,想要精准的指标总是困难的。但如果你比较用 Go 写的项目和用 C 写的项目的 CVE 数量,我觉得很可能会发现,用内存非托管语言(比如 C、C++ 等)编写的程序总是会有更多漏洞。因为从一开始,这些语言就提供了更多“搬起石头砸自己脚”的机会。

如果让两位开发者能力相当,一个开发者因为语言限制少犯了很多错误,而另一个开发者因为语言本身的原因多犯了一倍的错误,对我来说这很显然---无论开发者水平多高,使用不安全语言的人总会犯更多错误。

Natalie Pistunovich: 你见过用 COBOL 写的恶意软件吗?

Ivan Kwiatkowski: 其实没有见过。

Natalie Pistunovich: 我很好奇你会怎么评价它---更安全还是更不安全?你怎么看?

Ivan Kwiatkowski: 我完全不清楚(笑)。COBOL 是我听说过的语言之一。我知道,如果你想进银行工作并赚大钱,那你绝对应该学 COBOL,因为所有会写 COBOL 的开发者基本都已经退休了,所以很难找……除此之外,我这辈子,或者至少过去十年,从没听说过有用 COBOL 写的恶意软件。如果我真的遇到了,这一定会是一篇很有趣的博客文章,但对我来说可能会是痛苦的一周,因为我得学 COBOL 并弄清楚它的工作原理。不过,说实话,这周我刚好逆向了一个用 Pascal 写的程序……

Natalie Pistunovich: Pascal 是 Go 的精神父母。

Ivan Kwiatkowski: 是的,我猜是这样。它也是许多其他语言的精神父母,因为它大概是 80 年代甚至 70 年代的产物。我记得在学校学习过 Pascal 的基础知识,大概是15年前的事了。

Natalie Pistunovich: 我也是。我的高中毕业项目就必须用 Pascal 写。

Ivan Kwiatkowski: 是啊,就是这样。我自己可能也写过一点 Pascal,虽然没写过什么真正有意义的项目……不过我确实接触过一些 APT 恶意软件,也就是现实中的 APT。

Natalie Pistunovich: APT 是什么意思?

Ivan Kwiatkowski: 啊,不好意思,APT 是 “高级持续性威胁”(Advanced Persistent Threat)的缩写。APT 是我们日常工作中跟踪的一类黑客。你可以把攻击者分为两类:一类是以经济利益为动机的,比如网络犯罪、勒索软件团伙等;另一类就是我们所说的 APT,基本上是国家支持的攻击者或者雇佣兵性质的团伙,这些人专注于网络间谍活动。最初,APT 这个名字大概是由 Mandiant 公司在 2010 年左右的第一份报告中提出的。当时,这个名字的意义很明确:一方面是做普通犯罪软件的低端网络犯罪分子,另一方面是进行非常复杂攻击的国家支持攻击者。

但到了今天,我觉得用复杂程度来区分攻击者已经不太有意义了,因为现在一些勒索软件团伙非常专业,他们使用最前沿的渗透测试方法……与此同时,也有一些 APT 非常糟糕,比如操作安全(OpSec)很差,甚至不知道如何正确使用工具等等。所以到了 2022 年,当你听到 APT 时,最好直接把它理解为“间谍活动”。这是一个更恰当的理解方式。

不过无论如何,这周我正在处理另一个 APT 案例,发生在某些 STEM 国家,也就是独联体(CIS,独立国家联合体,这应该是它的全称)的一些地方。我们在其中发现了一种恶意软件植入程序,居然是用 Pascal 写的。这对我来说是一场回忆之旅,不仅要重新搞清楚 Pascal 是什么,还得理解这个 Pascal 编译器生成的汇编代码是什么样的。其实也没那么糟,比重新接触 Go 语言要好得多。

Natalie Pistunovich: 这很有趣,因为 Go 和 Pascal 之间有很多相似之处,但它们的翻译结果不一样。

Ivan Kwiatkowski: 哦,是的。我可以告诉你,尽管我不是任何一种语言的专家,但尽管两种语言在代码层面、语法结构以及声明方式上可能有一些相似之处,但在汇编层面,它们完全不一样。

Natalie Pistunovich: 我本以为如果它们在概念上相似,可能结构上也会类似,但看来并不是这样。

Ivan Kwiatkowski: 并不是这样。我想它们的语法可能有些相互借鉴……但编译器的实现方式完全不同。Go 编译器确实有自己的一套东西。

Natalie Pistunovich: 当你说每种语言在汇编表示或逆向工程的可视化表示上都有自己的特点时,能有多少种不同的方式?每次真的会完全不一样吗?

Ivan Kwiatkowski: 并不是总是完全不一样,但确实会有一些显著的差异。我认为 C 语言---也许这是我的一种误解,因为 C 语言通常是你学习逆向工程的起点---对我来说,C 语言是最接近 CPU 的语言。当你编译一个 C 程序时,它几乎是直接从 C 代码翻译成汇编代码。编译器不会太“聪明”,不会对你的代码做过多处理;你在 C 中写了什么,基本上就会直接反映在汇编中。当然,你可以通过一些编译器优化来提升速度或减少占用空间,但总体来说,这种翻译是相当直接的。我不会说它容易逆向,但它确实相当直观。你可以比较容易地从 C 代码找到汇编的对应关系。

我想,这也是为什么像 Ghidra 或 IDA Pro 的反编译工具可以从汇编代码转换回 C 代码的原因,因为 C 是最接近底层的一种语言。而当你转向更复杂的语言时,比如 C++,编译器就会做很多事情,这时候你写的代码和生成的汇编代码之间的差距就会非常大。

例如,当你在 C++ 中使用 std::string 时,看起来很简单,对吧?但实际上,std::string 是一个模板类的实例化,它背后是非常复杂的嵌套模板系列。你最终会得到一个奇怪的结构体,里头有一个表格,表格里包含了一些方法的指针---这些东西你在 C 中根本没写过。接着,你会看到方法相互调用,这些方法来自 C++ 标准库的模板库……从这里开始,一切就越来越复杂了。

再以 Pascal 为例---虽然我没怎么写过 Pascal,但当我看汇编代码时,看到引用计数被自动增加和减少,这些都是由编译器自动添加的。这对程序的运行来说可能有用,但对我理解程序来说,这完全是无关紧要的噪音。编译器添加的这些代码并没有给程序增加任何“智能”,只是在妨碍我的工作。

Go 可能是这方面的另一个极端,和 C++ 一样,Go 编译器在底层做了非常多的事情。怎么说呢……

Natalie Pistunovich: 优化吗?

Ivan Kwiatkowski: 对,优化。它会内联任何不值得单独调用的函数……调用约定也有自己的一套……哦,还有垃圾回收机制。当你用 Go 写一个简单的“Hello World”程序时,最后生成的可执行文件可能有一兆字节左右。我知道,现在存储空间已经很便宜了,我们不在乎一兆字节和七千字节的区别。但对逆向工程师来说,如果你需要分析一兆字节的代码而不是七千字节,那真的是天壤之别。而这就是 Go 编译器给你带来的问题,它还会进行许多优化,以及使用非常奇怪的调用约定。

另一个我不喜欢的 Go 功能---虽然它很棒,但作为逆向工程师我不喜欢---就是 goroutine。

Natalie Pistunovich: 是的,我本来想问的下一个问题就是这个。请详细讲讲吧。

Ivan Kwiatkowski: 好的。goroutine 对开发者来说似乎是一种非常简单的方式来创建多线程程序,这对开发者来说确实是很棒的功能……但这也让恶意软件开发者更容易创建多线程程序。而当我们试图理解一个程序时,我们更喜欢线性程序。我们希望看到一步接一步的指令,这样可以轻松调试。当许多线程同时运行时,天哪,追踪程序的逻辑变得极其困难。所以我其实希望多线程程序的编写更困难一些,这样攻击者就不会那么容易使用了。

Natalie Pistunovich: 在逆向工程中,线程的表现形式是什么样的?

Ivan Kwiatkowski: 实际上,线程本身并没有明确的表现形式,因为线程作为一个概念,基本上是运行时的对象。线程是执行代码的一个单元。如果一个程序只有单线程,你可以线性地跟踪代码,逐行理解程序的逻辑。但一旦有多个线程运行,你的复杂度就会成倍增加。作为逆向工程师,你不仅要分析当前函数的逻辑,还要时刻考虑是否有其他线程在某处运行,可能会影响正在执行的逻辑。这样你就无法只依赖当前看到的信息,程序的功能被分散到多个执行单元中,你需要将所有信息都记在脑子里,才能弄清楚程序的行为。这对逆向工程师来说是一个非常沉重的“心智税”。线程越多,跟踪起来就越困难。

一个很好的例子是我在上期播客中提到的 Go 程序,叫 Stowaway。这是一个开源项目,用于渗透测试工具,比如创建隧道、SOCKS 代理等,可能还可以将它们相互连接。不过我可以确定的是,当我试图阅读这个程序的汇编代码时,那种感觉非常痛苦。显然,很多事情是同时发生的,比如网络程序中,来自不同终端的包可以随时到达。同时,你可能还会有多个隧道在运行。

所以,当所有这些事情同时发生时,要弄清楚每一部分的具体作用非常困难。如果我没能找到它是一个开源项目并查到源代码的话,我可能根本无法完全理解这个程序的所有功能,因为要处理的信息量实在太大。而我的记忆力---就像任何人类相比电脑的记忆力一样---其实非常有限。

Natalie Pistunovich: 除非我们开始升级大脑……

Ivan Kwiatkowski: 是啊,我希望如此……

Natalie Pistunovich: 你提到的这些有趣的术语,比如 APT,还有刚刚说到的 Stowaway,我会把它们放在节目笔记里,方便大家回顾,看看如何在逆向工程中处理线程问题。如果能看到一些实际的例子,比如线程是如何影响结果的,那会很有趣。你提到线程之间可能存在数据依赖,或者某些计算结果会返回并共享数据。回到你发布在 YouTube 上的视频《逆向工程一个 Go 程序》,我会把这个视频的链接也放到节目中。在视频里,你通过绘制流程图展示了不同步骤的发生过程,以及你的推测等等。多线程逆向工程中,指令是否会随机出现?你如何在相关的上下文中理清它们?

Ivan Kwiatkowski: 我觉得最好的方式是不要试图以程序运行的形式去理解它,而是想象你在阅读一个 Go 程序的源代码。我想你会同意这样一个事实:如果你拿到一个同事写的 Go 项目,而你对这个项目一无所知,这个项目的源代码会更容易理解一些,前提是它是一个单线程程序,只做一件事。如果这个程序一开始就启动了三个线程,同时做不同的事情,那要弄清楚这个程序的逻辑就会困难得多。

现在,想象一下,你拿到的不是 Go 源代码,而是所有变量名都被擦除了,所有变量都被命名为 A、B、C 等等。你甚至无法通过函数名或变量名来理解程序的意图。再想象一下,代码里没有任何注释。这基本上就是逆向工程师所面对的情况。

Natalie Pistunovich: 不用想象,这种情况经常发生……

Ivan Kwiatkowski: 是的,当然可以(笑)。对于许多人来说,这可能就是他们的现实生活。向他们致敬吧……不过这确实就是逆向工程的本质:你会收到一些代码,不管是汇编代码还是高级语言代码---这确实有些区别,因为汇编代码更难读懂……但最终的过程是相似的。你收到一些代码,需要弄清楚它的作用。而代码越复杂、操作越高级,你理解它的难度就会越大。

Natalie Pistunovich: 这让我想到一个问题:一般来说,Go 的最佳实践,或者说“正确的写法”,是写简单、易读的代码,而不是复杂的,比如三元运算符或其他复杂的表达式……这对逆向工程有帮助吗?

Ivan Kwiatkowski: 我希望如此,但事实证明,对于编译器来说,无论你使用简单的方法还是三元运算符,只要编译器足够智能,最终它生成的汇编代码都是一样的。理想情况下,编译器可以识别出“if…then…else”和三元运算符是同一个逻辑,最终生成的汇编代码是完全相同的。因此,从开发实践的角度来看,这是一件好事……但当你深入到汇编层时,那些对人类友好的代码风格和为了提高可读性所做的努力都会被编译器丢弃。因为这些是为人类设计的,不是为 CPU 设计的,所以它们在编译后的程序中没有立足之地。

Natalie Pistunovich: 这是个很好的观点。

Ivan Kwiatkowski: 其实,前面你问过一个问题,关于不同语言的编译器能做什么区别。我可以再举一个关于 Go 的例子:Go 函数可以返回任意数量的返回值,对吧?而大多数语言并不支持这一点……所以当你观察汇编代码时,最终你会发现它被翻译成 CPU 代码的方式与传统函数完全不同。

对于大多数语言,比如 C 或 C++,函数只能返回单个值,这就形成了一个非常简单的约定:汇编代码中规定返回值会存在寄存器 EAX 中。这是一条非常简单的规则。但在 Go 中,返回值不止一个,所以它们会通过栈来传递,你需要在栈中寻找这些值……这就变得更加复杂,与传统语言非常不同。

不同语言的区别往往体现在这样的约定上,而这些小的差异最终会导致源代码或汇编代码在形式上截然不同。

Natalie Pistunovich: 你觉得,Go 在编译器层面对多返回值的处理效率高吗?

Ivan Kwiatkowski: 是的,据我所知,它确实表现得非常高效。可能我记错了,但在我的印象中,函数调用返回的值会被直接放置在栈上正确的位置,这样另一个函数可以立即将它们作为参数使用。所以,当你在调用一个函数时,如果传入的参数是另一个函数的返回值,这些函数调用在汇编代码中看起来是紧密衔接的。你不需要将返回值从栈中移回来再放回去,它们已经在正确的位置了。我认为在这一点上,Go 的处理效率很高,也非常快。

Natalie Pistunovich: 很棒,听到这个总是令人鼓舞的……所以 Go 的设计方式似乎并不是让你逐行调试代码、设置断点,而是通过处理错误来检查代码是否正常。我不会问恶意软件是否也通常这样写,或者它们的错误处理有多好……除非你知道,那请告诉我们。

Ivan Kwiatkowski: 其实我确实知道,因为我会阅读它们的代码。我通过阅读汇编代码和自己尝试去理解 Go 的运行机制,了解到 Go 语言不会允许你忽视错误处理。如果一个函数返回两个值,而你没有处理它们,那代码是不会编译通过的。当然,你可以用一个下划线变量 _ 来表示“我不关心这个值”。

Natalie Pistunovich: 没错……

Ivan Kwiatkowski: 是的。但至少根据我看到的程序,它们确实会捕获错误、检查错误并妥善处理它们。我觉得这很合理,因为语言强制你这么做。所以虽然你可以用下划线变量来规避检查,但如果你选择了 Go 语言,还是按照它的设计意图去使用会更有意义。而这也是我在分析恶意软件代码时看到的情况。

Natalie Pistunovich: 听到即便是恶意软件也遵守最佳实践,还是有点“治愈”的(笑)。不过确实,错误信息中往往包含了很多有用的信息。

Ivan Kwiatkowski: 作为开发者,你是否经常遇到代码中忽视错误处理的情况?比如你的同事提交的代码?

Natalie Pistunovich: 不,通常这种代码过不了代码审查……但我并不知道黑客们是否进行代码审查(笑)。所以这点很有趣。

Ivan Kwiatkowski: 根据我所看到的,这只是一些个例。就像现实世界中的开发者一样,总有一些黑客会用自己的“糟糕方式”写代码。但从我分析过的恶意软件来看,我觉得它们的代码开发得还算不错。当然,也一定会有某些黑客写出了最糟糕的 Go 代码(笑)。

Natalie Pistunovich: 这当然有道理,但听到总体上还是遵循好习惯让我很开心。不过我想问的是,这种错误检查在汇编中是如何体现的?因为这并不是一个常见的实践……是不是因为错误很少,所以几乎看不到这部分的表现?

Ivan Kwiatkowski: 我观察到的情况是,当我查看汇编代码时,大多数时候我需要查阅被调用函数的文档。有时函数名很直观,但大多数情况下我需要去查 Go 的文档……顺便说一下,Go 的文档非常出色,每次我查找函数的文档都能找到,这是一件好事。相比之下,有些语言的文档就很难找到,甚至是一些基础函数的文档。

所以,我查阅文档时会了解到函数需要哪些参数,以及会返回哪些值。如果函数会返回一个错误值,我还没有遇到过不检查这个错误值的情况。在汇编中,你会看到函数调用后从栈中取出某个随机变量,与零进行比较,类似于 if err == nil 的逻辑。根据错误是否为 nil,程序会进入不同的代码块。这表明攻击者确实检查了函数是否返回了错误值。

Natalie Pistunovich: 你之前提到,逆向工程就像是在阅读同事写的代码,但没有参数名、文档或函数名……这让我想到,可以使用一些 AI 工具,比如 Codex 或 Copilot,它们可以解释代码的作用。你有没有机会用过这些工具?

Ivan Kwiatkowski: 我没有用过,但我知道 GitHub 推出了类似的项目。我对这些工具有一种“宗教式的恐惧”,因为它们会把我写的所有代码上传到云端,进行分析并用于训练机器学习算法。这种恐惧其实有点愚蠢,因为我写的代码最终都会开源……但我还是不喜欢这种方式。

Natalie Pistunovich: 如果代码已经在 GitHub 上,那它也会到同样的地方。

Ivan Kwiatkowski: 没错,最终也会到那里。所以总体来说,我没有什么充分的理由不用这些工具。但我还没试过。我听一些同事说,他们用这些工具写 Python,效果非常惊人。这些工具几乎能猜到你在想什么,这还是挺吓人的。

Natalie Pistunovich: 是在写代码时用的,还是在逆向工程时用的?

Ivan Kwiatkowski: 是写代码。我还没听说有机器学习项目能帮助你进行程序的逆向工程……但我百分之百相信这是可能的。最近我玩了很多图像生成的 AI,尤其是 MidJourney。我还试过生成文本的 AI,比如 Lex……这些 AI 都让我觉得非常厉害。如果一年前有人告诉我,只需要输入一段文字,就能生成一张对应的图像,而且这张图像看起来还非常棒,我肯定不会相信。我会觉得这是科幻小说里的情节,绝对不会在我有生之年看到。也许等我老了,完全搞不清楚这些东西时才会出现。但现在,这一切已经实现了,尤其在人类语言理解和内容生成等复杂应用上。

到这个阶段,如果你告诉我,识别由其他计算机生成的函数还不可能做到,我会非常震惊。这种技术肯定会实现,只是时间问题。我不知道是谁会去做,也许我该试试?尽管我对 [听不清] 一无所知,但我觉得这是一个非常值得投入的项目。最终,这会让我们在处理未知程序时节省大量时间。我们可能需要针对不同语言开发专门的 AI,比如针对 C、C++ 或 Go 的 AI。但我确信这是我们的未来,而且可能就在不远的将来。希望这类工具不会卖得太贵,因为我真的很想用。

Natalie Pistunovich: 那么,关于代码生成,有些语言比其他语言更适合 AI,比如 Go 的表现甚至比 Python 更好。这是因为 Go 内置了 linter(代码规范检查器),并且语言特性非常严格,比如强制使用缩进、花括号和换行。而 Python 和很多其他语言的写法则非常自由,导致 AI 看到的数据集会更加多样化,甚至可能生成不一致的代码。有时同一个文件里会有两种不同的写法,甚至在最佳情况下也可能出错。那么对于逆向工程来说,你觉得这种优势是否还会继续存在?从 AI 的角度来看。

Ivan Kwiatkowski: 既是,也不是。你提到的这种语言的严格性确实让 AI 更容易理解代码的意义并生成代码。我认为这很有道理。比如 Python,本身就是一种极其模糊的语言,对吧?当然,它还没有 JavaScript 或 PHP 那么模糊……在我看来,JavaScript 和 PHP 根本不能算作真正的编程语言,因为它们完全不严谨。比如,当你使用鸭子类型(duck typing),甚至根本不给变量定义类型时,AI 想搞清楚发生了什么就会变得非常困难,在很多情况下可能根本做不到……因为我认为许多东西是在运行时才被确定的,而这是 AI 至少在短期内无法做到的。

另一方面,当你处理汇编代码时,情况就完全不同了。汇编代码是一种非常严格、唯一的语言,这可能是所有 AI 都必须处理的语言。我不太确定 AI 将如何从汇编代码中识别函数,或者生成对应的高级代码。

但对 AI 来说,汇编代码的一个好处是,它完全没有模糊性。可以说,模糊性在一端,而汇编代码则完全站在了另一端。它是 100% 精确的。

Natalie Pistunovich: 是的,汇编代码是最一致的语言。

Ivan Kwiatkowski: 没错,非常一致。而且它实际上以某种方式完成了任务,但只是一系列非常简单的操作,每个操作都是以非常明确的方式执行单一任务。所以在这方面,我认为这对 AI 来说将是非常有利的,只要它们准备好了。

Natalie Pistunovich: 现在已经有一些很不错的工具,可以将二进制代码直接翻译成汇编代码,虽然不能做到 100%,但覆盖率已经很高了。汇编代码本身足够一致,这意味着可能已经有人开发了一个 IDA Pro 的插件,利用 AI 将汇编代码翻译成 Go 代码。

Ivan Kwiatkowski: 这是个好问题,因为你会觉得应该有人在研究这个……但当你观察逆向工程工具的市场时,会发现这个市场其实很小。比如 Hex Rays(IDA Pro 的开发商),是一家有些“老派”的公司。他们的产品很出色,但过去 20 年里几乎没有试图做出任何颠覆性创新。当然,他们对产品做了很多改进,但我觉得部分原因是因为受到了 Ghidra(一个开源竞争者)的挑战。如果没有 Ghidra 的出现,我认为 IDA Pro 可能会在未来十年里保持现状,几乎没有显著变化,因为他们没有竞争对手,也就没有改进的动力。

目前,编译器的工作原理完全是通过算法实现的,并没有使用任何形式的机器学习。他们的反编译过程没有应用 AI。也许他们已经开始研究了,但 Hex Rays 给我的印象---或许我完全错了,因为我不在那里工作---是他们并不是一家专注于突破性研发的公司。不太像是会为下一代反编译器做准备的那种公司。我觉得他们更倾向于对现有产品做些渐进式的改进,每年稍微优化一点。

Ghidra 也有反编译器,它是开源的,我觉得它的表现也不错……但我认为它也没有使用任何形式的 AI。我也没听说他们有计划开始研究这方面的技术。开发一种专注于逆向工程的 AI 产品可能需要非常专业的 AI 知识。而我的感觉是……虽然我不认识安全领域的所有人,但我的印象是,这个领域的人通常非常专注,非常有才华,技能也很强,但他们的专长往往集中在网络安全的某个特定领域。我很少见到既是优秀研究员或者逆向工程师,又是非常合格的数据科学家,能够调优机器学习算法并彻底改变我们的生活。也许某些地方有人在研究这个,但如果真是这样,我并不知道。

如果这个项目是公开的,或者已经发布了,我想我应该会听说。但我仍然希望某家公司,比如在以色列的某个秘密实验室里正在研究这个项目,最终会横空出世,彻底改变市场,让我的工作变得更轻松。

Natalie Pistunovich: 是的,这确实是另一个可以给观众的灵感。除了开发一个能把汇编翻译成 Go 的 AI,我个人还对一个能判断“这个恶意软件是谁写的”的 AI 很感兴趣。我的意思是,现在你已经可以用一些工具,比如 Codex,告诉它“写一个 Go 程序,功能包括这个、那个和另外一个,并以某某的风格来写。”如果你提到某个 GitHub 用户的名字,而这个用户是个知名开发者,有很多星标,或者其他很大的影响力,他们写的代码有一种独特的风格---虽然在 Go 中可能不那么常见,因为 Go 本身很规范---你就能得到他们风格的代码。所以,下一步的魔法插件可以是“这个恶意软件是以谁的风格写的?”这会很有趣。然后那些编程教师就会说,“哦,这个黑客是我教出来的。”

Ivan Kwiatkowski: 这是个很棒的想法。其实,这种想法也许不是传言,而是一些公开的研究项目,这些项目可能在几十年前就已经在情报领域开始了。我记得在 2010 年或 2012 年的某次 CCC(Chaos Communication Congress)会议上,有人提到过类似的研究。他们基本上从开源代码仓库中提取代码,比如下载整个 GitHub,试图分析每个开发者的写作特点,希望未来能够对任何程序进行分析,并推测出“这个可执行文件可能是某个开发者写的。”

现在已经过去 10 到 15 年了……我不确定这个研究是否成功了。我很久没听到这方面的消息了,所以可能它并不像预期的那样有效,或者它其实已经被某些情报机构吸收了……因为这种能力显然在情报应用中有非常大的价值。情报机构可以用它来识别恶意软件的作者,而不需要像警方那样拿出确凿的证据……他们只需要知道某个恶意软件背后是谁,然后用他们常规的手段去“平行构建”证据。

我们知道他们需要这种能力,也知道他们为此投入了资金。我记得一些大学也在研究类似的项目……虽然是为了研究目的,而不是情报应用。但在这些领域,研究和情报总是存在某种交流,尤其是当研究有实际应用时。

如果这项研究取得了成果,那一定是以秘密的方式实现的。以前的研究方式本质上是算法驱动的,他们试图提取代码的特征,而没有使用我们现在所拥有的黑箱式 AI 技术。也许这是这些应用的新研究方向。也许我们未来会知道答案。但据我所知,这仍然是一个现存的问题,人们正在努力解决。我没有看到任何迹象表明这个问题已经被解决了,尽管我也无法确定自己一定会知道。

Natalie Pistunovich: 这一期播客的讨论非常有趣,涉及了很多新工具和项目的创意。记住,你们是第一次从这里听到这些想法的。

Ivan Kwiatkowski: 是的,我完全没想到会聊到这些话题呢。

Natalie Pistunovich: 是啊,这真的很棒。和你聊天很有启发性,Ivan。谢谢你抽出这一小时来和我们交流。

Ivan Kwiatkowski: 当然不客气。

Natalie Pistunovich: 就像上一期一样,这次聊完后我还是有很多悬而未决的问题……希望明年你能再加入我录一期,或者十期也可以。[笑]

Ivan Kwiatkowski: 好啊,当然可以。最终---而且我感觉这可能很快就会发生---我们会扩展我关于 Go 语言的所有知识,对吧?如果我再回来,那到时候我真的需要更深入研究这门语言,也许还要带来一些实际的研究成果和你分享……因为不然的话---我不希望这次对话变无聊,但我会尽力的。

Natalie Pistunovich: 好的,那下次我们可以聊聊泛型……看看 Go 提案的进展如何。

Ivan Kwiatkowski: 我会好好准备的。

Natalie Pistunovich: 对了,这次我们没有聊不受欢迎的观点,但我们提出了两个“独角兽点子”……所以,大家不客气啦。 [笑]

Ivan Kwiatkowski: 我几乎可以向你们个人保证,如果听众中有人真的实现了我们提到的其中一个点子,那他们会变得非常富有。所以,就这样吧。

Natalie Pistunovich: 这几乎和提出最不受欢迎的观点一样有分量了。 [笑] 谢谢大家的收听。非常感谢你,Ivan。

Ivan Kwiatkowski: 谢谢你,我很高兴能参与这次节目,和你聊天很愉快。我们下次见。







part 4

本篇内容是根据2023年3月份#270 Hacking with Go: Part 4音频录制内容的整理与翻译

我们的“Hacking with Go”系列继续!这次 NatalieJohnnyIvan KwiatkowskiJuan Andrés Guerrero-Saade 一起讨论,我们的重点是泛型和人工智能。

过程中为符合中文惯用表达有适当删改, 版权归原作者所有.



Natalie Pistunovich: 今天这一期节目非常令人激动。我个人对此充满期待,我想你们也能听出来。这一次我们将深入探讨使用 Go 进行黑客相关的内容,但会加入 AI 的视角。而且,今天是 3 月 14 日,我们正在录制这期节目。就在几个小时前,GPT-4 刚刚发布了,所以在正式开录前的音效测试中,我们聊到了这个,也聊到了早餐。

今天和我一起的是 Johnny。嗨,Johnny,你怎么样?

Johnny Boursiquot: 我很好,很高兴能来这儿。

Natalie Pistunovich: 和你一起主持节目总是很愉快。

Johnny Boursiquot: 确实如此。

Natalie Pistunovich: 今天我们还有两位非常棒的嘉宾,这已经是他们第 7755 次登上我们的节目了(笑),我们总是很高兴你们回来分享更多关于黑客的有趣内容---Ivan 和 Jax。两位先生可以做一下自我介绍吗?

Ivan Kwiatkowski: 好的,大家好,很高兴能再次参加这个播客。我叫 Ivan Kwiatkowski,是一名法国的网络安全研究员,我在 Kaspersky 工作。

Juan Andrés Guerrero-Saade: 我叫 Juan Andrés Guerrero-Saade,不过大家叫我 Jax 就好……我是 Sentinel Labs 的高级主管。

Natalie Pistunovich: 所以你们两位都是黑客,尽管你们日常工作中并不使用 Go,但在你们的网络安全探索中遇到了一些和 Go 有关的有趣事情可以分享。也许我们可以从最近用 Go 编写的恶意软件开始聊起?

Juan Andrés Guerrero-Saade: 有不少这样的例子。

Natalie Pistunovich: 是的,请告诉我们吧。

Juan Andrés Guerrero-Saade: 说实话,我一开始没想到 Go 会被这么广泛地用于恶意软件。好吧,我们得稍微分类讨论一下。在我看来,很多恶意软件开发者可以分为两类。一类是我们过去称为 VX-er 的老派开发者,他们使用 C,或者 C++,而如果是更高级别的国家支持的开发团队,通常会使用 C++,并有非常成熟的框架。这些框架可能是由国防承包商开发的,经过质量保证等等。另一类则是那些使用 Python 编译代码,或者 Visual Basic、AutoIt 的人,这些人只是快速地尝试窃取数据,代码质量非常糟糕。

如今我们看到 Go 和 Rust 的使用逐渐增加,在我看来主要是 Go,虽然我很庆幸还没有看到太多 Rust 的使用……但我要说的是,你们可能对在现实中发现的 Go 恶意软件的水平不会感到印象深刻。虽然有很多 Red Team 的工具存在,但幸运的是,这些工具还没有被大范围用于真实场景中……不过它们确实已经在那里了。所以,我想 Go 正在慢慢成为恶意软件开发者的一个重要且理想的平台。

Natalie Pistunovich: 提醒我们一下,“Red Team” 是什么意思?

Juan Andrés Guerrero-Saade: 哦,天哪……抱歉,我有点跑题了。在网络安全领域,我们通常用不同颜色的帽子来定义角色,而 Red Team 通常是指渗透测试团队,也就是在受控环境中进行“进攻”操作的人。例如,一家财富 500 强公司可能会雇佣他们来入侵和攻击,以展示“看,这里有这些漏洞,系统设置得很差劲,这就是失败的原因。”

然后还有 Blue Team,就是负责网络防御的人---我想我们大致算是 Blue Team 的一部分,但更倾向于做防病毒签名、逆向工程和恶意软件追踪……不过我和 Ivan 在这个领域中似乎有点像“异类”。

Ivan Kwiatkowski: 是的,我想他们可能会称我们为 Purple Team。Purple Team 是同时观察 Blue Team 和 Red Team 所做工作的团队,他们监控流量,尝试识别问题,或者创建签名来检测发生的一切,并确保未来不会遗漏这些问题。所以,这是一种观察者的角色,我觉得我们比较符合这一定位。

Juan Andrés Guerrero-Saade: 是的,这让事情变得有趣。这也意味着我们可以对其他人使用 Go 的方式进行评判,我觉得这比我们自己写代码更好玩……其实我很喜欢 Go,但我并不假装自己是个称职的开发者。Go 让我对自己的输出稍微有点信心。但和一些用 Go 写的恶意软件相比,我简直是天才。尤其是,许多中国的黑客团队开始使用 Go,或者像在乌克兰战争期间,我们看到一个叫 PartyTicket 的伪勒索软件,它就是用 Go 写的……

Natalie Pistunovich: 等等,什么是“伪勒索软件”?

Juan Andrés Guerrero-Saade: 嗯,在乌克兰战争期间,有大量的“擦除型”恶意软件被使用……

Natalie Pistunovich: 什么是“擦除型”恶意软件?

Juan Andrés Guerrero-Saade: 擦除型恶意软件的意思是,它进入电脑后会删除所有内容。

Johnny Boursiquot: 什么是电脑?[笑]

Juan Andrés Guerrero-Saade: 它会擦除整个系统。好的……我好像来错了播客(笑)。总之,擦除型恶意软件的作用就是进入系统并删除所有文件,使电脑无法工作。当目标是单台电脑时,这种行为显得很蠢,但如果是目标是外交部的 3000 台电脑同时瘫痪,那就很有意思了。

作为当时事件的一部分,他们放出了一个伪装成勒索软件的程序---我称之为“伪勒索软件”,因为它显然从未经过测试。它根本不是为了勒索钱财而设计的,他们压根没打算解锁任何人的设备。这只是为了分散注意力。而且我之所以这么说,是因为他们在代码里犯了一个很明显的并发错误,这是每个 Go 初学者都会犯的错误:线程管理不当。结果是,这个 Go 写的勒索软件在四分钟内就让电脑崩溃了,因为线程失控了……

Johnny Boursiquot: 太过头了(笑)。

Juan Andrés Guerrero-Saade: 是的。它甚至没有机会加密任何内容,就像一个本地的拒绝服务攻击。所以它并不是真正的勒索软件,只是伪装成勒索软件的样子。

Natalie Pistunovich: 自我 DDoS。

Johnny Boursiquot: 这是一个糟糕的程序(笑)。

Juan Andrés Guerrero-Saade: 是的。

Ivan Kwiatkowski: 我其实也同意这种观察结果,因为我很少看到用 Go 编写的真正好的恶意软件样本。我们通常发现的,都是一些现有的恶意软件团体使用他们已经建立的恶意软件家族,但他们需要一些一次性使用的“投放器”或“初始阶段”的恶意软件。这些恶意软件可以被随意丢弃,如果发现目标电脑有趣,才会部署他们真正重要的恶意软件。

这些初始阶段的恶意软件---也就是我们称为“投放器”的东西---通常是一次性的。他们可能用一周时间用 Go 写一个,然后之后又用 Rust 写另一个。当他们入侵某台机器时,他们会试着运行命令部署某些东西。如果成功了,那很好;如果失败了---比如被杀毒软件拦截了---他们就会从“货架”上拿出另一个继续尝试。所以他们尽可能多地创建不同版本,用尽可能多的语言编写,但他们并不精通任何一种语言。他们可能只是从 Stack Overflow 上拼凑一些代码,直到弄出一个能运行的程序。从工程角度来看,这些代码会让你觉得很糟糕。

Juan Andrés Guerrero-Saade: 是的。不过现在他们有了 GPT-4,对吧?……不,我不想这么说,因为我不想加入网络安全领域那种“追热点”的队伍。现在很多人都在说“GPT-4 会毁了我们所有人”,每次攻击都用到了 GPT-4……实际上,有很多开源的恶意软件。他们根本不需要 GPT-4。如果有的话,他们只是变懒了。

Ivan Kwiatkowski: 是的。而且,当 GitHub Copilot 发布时,为什么没有这么大的反应呢?因为它可能是一种比让 ChatGPT 或 OpenAI API 一页一页生成代码更高效的方式。

Juan Andrés Guerrero-Saade: 我感觉被针对了……[笑] 我感觉被看穿了。

Johnny Boursiquot: 你让 ChatGPT 为你写并发 Go 代码?

Juan Andrés Guerrero-Saade: GPT-4 发布之后---大概在我们录音前 20 分钟吧?我就开始疯狂地问它各种问题,看看“你变聪明了吗?”它确实做了一些让我印象深刻的事情……但这也让人懒惰。比如,“给我写一个完整的 Elasticsearch 集群部署脚本,用 Bash 实现。开始吧。”然后你看着它生成的代码,说“我希望这能用……我们看看结果吧。”从懒惰的 DevOps 角度来说,这确实挺有意思的。

Natalie Pistunovich: 接下来可以问它“这代码有什么问题?有哪些坑?改进一下。”

Ivan Kwiatkowski: 或者直接问它“你的脚本能用吗?”也许它知道……[笑]

Natalie Pistunovich: “怎么改进它?”我觉得它可以给你一些建议。

Juan Andrés Guerrero-Saade: 是的。我觉得目前缺少的是---我不知道 OpenAI 未来有什么计划---但显而易见的缺失是一个类似于 repl 的东西,可以同时写代码、运行代码,一边写一边调试。就像自动化编程一样。如果你把错误信息扔给它,它就会不断修复,只要它没有进入“幻觉模式”……

Johnny Boursiquot: [笑]

Juan Andrés Guerrero-Saade: ……它不会给你警告,也不会变红。但一旦它开始告诉你一些不存在的 API,调用从未写过的函数或包……只要没有进入这种“诡异领域”,它就会继续修复。所以需要一个类似 langchain 的实现,让它写代码、运行代码,并在过程中自己调试……

Natalie Pistunovich: 那你说这些恶意软件代码在并发和其他方面很糟糕时,我得问---它们有测试吗?

Juan Andrés Guerrero-Saade: 哦。我们不知道,因为我们拿到的是编译后的版本。

Johnny Boursiquot: 是的,编译后的版本里没有测试代码。

Juan Andrés Guerrero-Saade: 我猜他们在写这些代码时,完全没有写测试。不过我觉得,我们不应该对那些不写测试的开发者太苛刻。当然,我不是在为那些俄罗斯战争期间的恶意软件操作者争取什么同情,而是为我自己争取一点。

Ivan Kwiatkowski: 如果你指的是单元测试的话,那我敢说,几乎肯定是没有的。如果你指的是检查返回值,确保错误为 nil 之类的东西,那有时候你可能会看到。

Juan Andrés Guerrero-Saade: 我觉得你比我运气好,Ivan。毕竟你分析过 SUNBURST 和那个有趣的 SolarWinds 攻击(有些人可能听说过)。

Ivan Kwiatkowski: 是的。

Juan Andrés Guerrero-Saade: 那个供应链攻击中包含了恶意软件。Ivan 实际上分析过那个。所以我觉得这是个例子,让人觉得“好吧,这个写得稍微好一点”。它确实有效,相比于你分析过的一些其他东西来说。

Ivan Kwiatkowski: 是的。那是我第一次接触 Go 的经验。当时我完全不知道它是否符合 Go 的“正式”语言规范……不过在使用 Go 的过程中,我感觉这个语言尽可能地试图防止你滥用它。但回过头来看,确实觉得 SunShuttle(恶意软件的名字)像个优秀的学生。他们确实检查了所有的返回值,并在他们认为需要的时候进行测试。总体结构也非常合理……所以是的,我认为它属于用 Go 写的更高质量的恶意软件之一。

Juan Andrés Guerrero-Saade: 我试着站在那些听这个播客的资深 Go 开发者的角度来想……我们现在听起来可能像傻子一样(笑)。老实说,我们面对的是完全不同的人群。我觉得 Ivan 描述得很准确,比如,有些老牌的团队需要一些新组件,主要用于攻击的第一阶段。他们需要一个恶意软件,唯一的作用就是进入你的电脑,评估环境,然后部署更专业的恶意软件。所以在这方面,他们可以非常懒惰,对吧?他们只需要用 Go 或 Rust 写一个“加载器”(loader),然后看看能否绕过杀毒软件的检测……之后他们就不在乎了,会部署他们用 .NET 写的东西,或者其他一直在用的工具。

我认为很少有团队是从“资深 Go 开发者”的角度去真正采用 Go 的……这可能反映了一些国家机构的招聘周期。比如说,有多少刚从大学毕业、学了 Go 的学生现在为XX国家安全部工作?XXX也是类似的。这里几乎有一种代际差异---我不是说我们在等待这种变化,但我觉得这种代际差异确实在起作用。

现在我们看到XXX的行动中出现了 Kubernetes,你会想,“好吧,这些人显然不是我过去 15 年里一直在对付的那些老家伙了。” [笑]

Ivan Kwiatkowski: 我甚至会更进一步……当我们看到那些每隔一周就生成的新“投放器”(dropper)时,真的感觉像是某种对新入职实习生的入门考验。他们刚到公司时,可能没有权限或者能力处理那些更严肃的事情,所以他们的任务就是用最近学的语言写一个简单的“投放器”。这就是他们的工作。可能六个月后,或者等他们正式入职后,他们才会处理其他更重要的事情。所以整个过程真的像是某种内部培训循环。新人进来后,先写个“投放器”;如果表现不错,接下来才可能参与实际的操作。

Juan Andrés Guerrero-Saade: 被 Go “考验”了。

Ivan Kwiatkowski: 没错。或者被 Rust “考验”。甚至可能是 Scala。

Johnny Boursiquot: 我发现你们谈论国家支持的安全漏洞、恶意软件等问题的方式……就像一个普通的工作。如果你刚加入一家公司,作为一个刚毕业的新人,不会直接参与核心任务……可能会被安排修复一些小问题之类的。但有一个过程,你需要逐步被提拔,才能处理更重要的事情……不过,当我们想到黑客时,我们通常会联想到那种躲在地下室里、穿着沾满薯片渣 T 恤的孩子……

Juan Andrés Guerrero-Saade: 或是特朗普时代提到的“400 磅的黑客”……

Johnny Boursiquot: 对,就是那种老电影里的刻板印象。但现实完全不是这样。这更像是一份“正式”的工作。

Juan Andrés Guerrero-Saade: 这取决于你在和谁打交道。我认为 Ivan 和我在某种程度上---我们并不是在自吹自擂,而是我们专注于网络安全的一个非常具体的领域。我们分析的是所谓的 APT(高级持续性威胁),这是个委婉的说法,意思是“这可能是一组政府支持的黑客”。因为我们很难将一组恶意软件进一步归因到具体的情报机构或类似的组织。但你会看到各种各样的情况。

今年我们看到黑客活动主义(hacktivism)又火了起来……在这一领域,你可能会遇到那些地下室里搞事情的 Anonymous 成员,他们只是想攻击一个网站;也可能遇到一些“黑帽传奇”,一个 20 年都没露面的黑客突然决定攻击某个大公司,或者像“巴拿马文件”这样的事件---一个没人知道的黑客侵入某个地方,窃取所有数据,并将其发布出来,只是为了制造混乱。然后,一切就崩溃了。所以你会遇到各种各样的情况。

对我们来说,我想我们倾向于---我不想替你说太多,Ivan,但我觉得我们主要专注于政府相关的部分。这些人通常在防御方面做的事情更有趣。

Ivan Kwiatkowski: 是的,完全同意。我想补充的是---是的,虽然我们都知道好莱坞给我们灌输了“戴着连帽衫的黑客”的形象,我们也想相信这种形象,因为它很吸引人……但正如 Jax 提到的,我们的重点是那些情报机构的威胁行为者。而情报机构可能会雇佣穿连帽衫的人;那是他们的业务。但与此同时,他们也有“客户”。情报部门需要为其他部门、其他服务甚至高层领导提供信息。

Juan Andrés Guerrero-Saade: 就像一份工作一样。

Ivan Kwiatkowski: 没错,这就是一份工作。他们需要交付成果。所以他们必须建立各种流程。如果 Bob 某天生病了,Alice 必须能够顶上。因此,他们需要那些所有操作员都能使用的程序,确保团队之间能够无缝切换。这就像一个生产工厂一样,必须持续运转,不能停下来……[笑] 因为这就是一份工作。

Johnny Boursiquot: 说得太好了。[笑]

Juan Andrés Guerrero-Saade: 不同的国家情况也不同。我们在西方,尤其是美国,“五眼联盟”国家,形式非常正式。你可以看到有一个生产流水线,涉及国防承包商和国防工业基础。像 雷神公司(Raytheon)诺斯罗普·格鲁曼(Northrop Grumman)这样的公司都会参与其中。而在某些欧洲国家,很明显某个情报机构雇了五个穿连帽衫的法国人坐在房间里,开发了整个平台。

XX的情况则不同。我们会被大量的XX攻击压垮,因为他们在目标选择上几乎没有区分。他们会说,“我们需要攻入这家制药公司”,然后把任务分配给国家支持的团队、随机的黑客团队,或者为钱而干的那些人……你会看到 12 个团队同时攻击同一个目标,你会想,“拜托,至少装得隐蔽一点,至少假装不想被抓住。”所以不同的文化之间差异很大。

Ivan Kwiatkowski: 不过,尽管 Jax 刚才说了这些,他们最终还是会成功的。

Juan Andrés Guerrero-Saade: 哦,当然。

Ivan Kwiatkowski: 所以虽然我们在这里开玩笑,但最终他们的手段有效。

Johnny Boursiquot: 是啊,如果有 12 个团队在找漏洞……我通常会告诉别人,“安全和减少攻击面,这些都只是缓解措施。”如果有一群人,或者一个国家支持的组织盯上了你,他们很可能会找到办法攻入。如果不是因为你的安全系统或软件漏洞,那也会是因为某个人链条上薄弱的一环,比如有人掉入钓鱼攻击的陷阱……如果有人盯上你,他们最终会得手的。

Ivan Kwiatkowski: 哦,是的。其实问题从来不是防火墙,而是钓鱼攻击。

Juan Andrés Guerrero-Saade: 是的,人们总是关注“哦,有人使用了这些无法修复的零日漏洞,和各种复杂的软件攻击”,但实际上,大约 95% 的攻击只是因为某人打开了一个附件,或者在收到的钓鱼邮件中,把自己的凭据输入了一个伪造的 Facebook 网站。这类攻击数量巨大,但却常常被忽视。不过看看现在供应链攻击的兴起,这种情况正在发生变化。

所以我在这个节目中第一次提出的“不受欢迎的观点”是,软件开发者可能是整个互联网中安全性最差的一群人,因为我们---好吧,我不想说“我们”。在这种情况下,我只能把责任推到你们这些开发者身上……存在一定程度的自负,安装各种随机软件的行为也很普遍……

Johnny Boursiquot: 确实如此……[笑]

Juan Andrés Guerrero-Saade: ……还有对包管理器的高度依赖;没人愿意运行 EDR/XDR/AV(端点检测与响应/扩展检测与响应/杀毒软件)……

Johnny Boursiquot: brew install……[笑]

Juan Andrés Guerrero-Saade: 一切都在 root 模式下运行。而且所有用于更改生产环境的 SSH 密钥和 PGP 密钥都在你的笔记本电脑上。所以我们见过很多操作---其中一些已经被泄露并公开记录---你会发现,“好吧,他们显然是在通过 LinkedIn 搜索开发者”,他们知道这些开发者是公司的系统管理员……然后你就直接进入了系统,你已经拥有了 root 密码,已经可以访问整个环境,甚至还拿到了修改生产环境的权限。而这就是许多供应链攻击的开端,对吧?如果我能更改你的代码库,我就可以直接在你使用的更新管道中添加恶意软件,然后攻击你的每一个下游客户。这种攻击在十年前可能还显得很冷门,但现在我们已经深陷其中了。这种情况正在到处发生。

Natalie Pistunovich: 刚刚说的 brew install 简直太形象了……不过我很好奇,Go 对于使用外部库的保守态度,以及一再建议只使用标准库,这是否在某种程度上有所帮助,让 Go 稍微更安全一些?

Juan Andrés Guerrero-Saade: 我认为这在某种程度上确实创造了一个“单点故障”的风险,但我并不觉得这是坏事,对吧?正如你所描述的,Go 的这种保守性让它的安全状况要好得多。当然,还是有办法攻击它,但我觉得我们如果把 Go 用户和 PyPI(Python 包管理器)用户放在同一水平上,那就有些不公平了。PyPI---坦白说,真的是个糟糕的局面。对不起,但它真的很糟糕。你会发现大量的名字/拼写劫持(name/typosquatting),有人窃取开发者账户,然后用恶意代码替换知名的包……

我们实际上也发现过类似的事情---我们曾研究过一个针对 Rust crates.io 的供应链攻击。他们的团队非常迅速地作出了反应,非常友好,也非常投入……但本质上,攻击者创建了一个伪造的开发者账户,冒充了一位知名开发者,然后篡改了 Rust 的 Decimal 包,希望人们无意中安装这个包。这会下载一个第二阶段的恶意软件,专门设计用来攻击 CI/CD(持续集成/持续交付)流水线。这种攻击的目的是直接命中生产流水线,最终攻击下游客户。这真的很疯狂……事情变得越来越离谱了。

Ivan Kwiatkowski: 是的。不过,我觉得事情不一定是非黑即白,对吧?确实,在 Python 的生态中,任何人都可以创建账户并上传库。实际上,我曾经的一位同事 Felix([听不清 00:23:48.08],现在在一家名为 Sequoia 的法国公司工作)最近做了一些研究,他在 GitHub 上发现了一个项目,可以自动在现有库中植入后门,并将其上传到 Pip。这是一个自动化流程,如果你愿意,一天可以生成上百个这样的包。这是生态中的一端。而在另一端,则是像 Go 这样的语言,外部库的数量非常少,或者至少你会被劝阻不要使用它们,因为 Go 是“自带电池”的(batteries-included)。也许我们可以找到某种中间地带,比如有一个值得信赖或经过策划的库或包的存储库,人们可以从中下载东西,而不是像现在这样需要拉取整个网络的所有 LeftPadding(注:著名的 LeftPad 事件),或者类似的东西……

Johnny Boursiquot: [笑] 这个梗永远不会过时。

Ivan Kwiatkowski: 是啊,永远不会,对吧?

Natalie Pistunovich: 我们应该把这个加到节目笔记里,给那些不熟悉这个梗的听众……

Ivan Kwiatkowski: 然后,也许当人们上传包时,可以通过某种由 Golang 团队进行的审查流程,前提是他们有资源这样做,至少可以策划一个“标准扩展库”,以便其他人能用。

Juan Andrés Guerrero-Saade: 我觉得你刚才的意思是,我们需要自己的 crates.io 或 PyPI。

Ivan Kwiatkowski: 也许吧。

Juan Andrés Guerrero-Saade: 我觉得你最终会走到那一步,对吧?你会想,“如果我们有一个单一的、集中化的方式来审核包,开发者可以给包点个赞或踩……”我觉得最终的结果就是你会走到那个局面。

Ivan Kwiatkowski: 我的意思是,也许该像 Apple Store 那样。不完全开放。

Juan Andrés Guerrero-Saade: 是啊。不过这让我想到了 Google 的库……他们的确似乎有一些更标准化的打包方式,维护得也相对较好……不过我不知道;我们不能完全信任任何东西。但我觉得 Natalie 提到的重点很好,大家倾向于主要依赖标准库,这些库已经被打包好了。但是当你开始推 GitHub 仓库时,这种情况几乎是不可避免的---每个人迟早都会拉取别人的项目。我只是觉得---我不知道,Go 不是首要目标,我就这么说吧。Go 并不是首要攻击目标,正是因为你提到的这些原因。

话虽如此,Go 还是非常有用的。而且既然我们从 GPT-4 开始了这个讨论,我认为 Go 是最适合机器学习生成代码的语言之一。因为正如 Ivan 的说法---它有点像“极权主义的 Python”(fascist Python),这对大型语言模型来说非常完美。它超级标准化,对吧?所有东西的格式都差不多,约定也很一致……你不需要太多的风格指南,因为没有太多可以偏离的空间,而且并发和其他模式都有标准的做法。

所以对我个人而言,我发现 GPT 生成的 Go 代码大部分情况下可以直接编译。它完美吗?它能完全实现我的需求吗?不一定。在许多情况下,并不能。但它至少能编译,对吧?这比很多 Python 或其他代码要好得多……至少在 GPT-3.5 之前是如此。至于 GPT-4,我还不知道,对吧?我们得去测试一下,它才“诞生”40分钟……

Johnny Boursiquot: 那么,既然我们可以用这些工具生成恶意代码,我们是否也可以用这些工具来理解这些恶意代码,并防御它们?

Juan Andrés Guerrero-Saade: 就我个人而言,我真的非常看好 LLM(大型语言模型)为防守方提供的能力。这不是一一对应的关系。Ivan,你曾经开发过第一个、也是最有用的工具之一,我觉得;你愿意聊聊 Gepetto 吗?

Ivan Kwiatkowski: 好的,当然可以。我可以等你讲完之后再说,或者现在就说……

Juan Andrés Guerrero-Saade: 好的,对不起,我稍微解释一下这个概念……我们使用 LLM(大型语言模型)不仅仅是为了构建工具,而是更多地用于代码分析,尤其是在我们进行逆向工程时。逆向工程恶意软件是一项非常专业化的工作,擅长这项工作的人非常少。这是一种非常稀缺的技能,需求量极大,但却很难培养。你不能简单地去上某个学校或参加某个项目,然后就能出来说“好吧,我现在是一名逆向工程师了”。通常,这个技能来自一定的编程实践,可能还包括破解游戏许可证或制作游戏模组的兴趣。

Ivan Kwiatkowski: 还需要一点神经质……

Juan Andrés Guerrero-Saade: 是的,还需要一点神经质,也许还需要一堆 Adderall(注意力缺陷多动症药物)……就是某种能让你走上这条路的东西。所以我看到了 Ivan 的工具(我会让他来介绍),还基于这个工具设计了一整套大学课程,主要是围绕使用他的工具以及 ChatGPT 的应用……因为这个工具非常擅长总结和解释汇编中的 C 伪代码。你可以直接问它:“这个函数在做什么?”

Ivan Kwiatkowski: 好的,也许我可以简单介绍一下这个工具。话说回来,当 ChatGPT 最初发布时,很多人都在担心未来几年内他们是否还能保住工作……我也有同样的疑问,我就在问自己:“好吧,ChatGPT 能不能取代我目前的工作?”于是我拿了一段由我的分析工具生成的伪代码,这是一段恶意软件的代码,我并没有它的源代码。我把这段代码放到 ChatGPT 上,问它:“这是一个 C 函数,它的功能是什么?”因为我的工作基本上就是看这些函数,试图理解它们的作用,然后审查所有未知程序中的函数,这样我也许可以告诉我的雇主这个恶意软件到底在做什么。

让我极为惊讶的是,ChatGPT 在这方面表现得非常好,甚至可以说是极其优秀。事后想想,这其实很合理,因为 ChatGPT 是一个语言模型,而代码本质上也是一种语言。ChatGPT 在理解、解释和重新表述这些内容方面表现得非常出色。所以我所做的就是给它一些由自动化工具生成的未知代码,这些代码没有任何变量名、注释或有意义的函数名称,但它们仍然是代码,而 ChatGPT 能够从中提取意义,并将其以人类语言表达出来。

然后,在我最初的震惊之后,我创建了一个插件,将我的工作工具直接与 OpenAI 的 API 连接起来。最初使用的是 DaVinci-003 模型,现在我切换到了最新的 GPT-3.5……我的工具现在会将伪代码发送到 ChatGPT,而 OpenAI 的模型会返回一段注释,比如“这个函数的作用是这样的”,然后我可能需要检查一下它的内容是否与我看到的情况一致。但总体来说,我只需要按下一个快捷键组合,AI 就会完成我的工作,接着我再把所有结果拼凑起来。这真的节省了大量时间,而且从 GitHub 的活跃度来看,这个工具在社区中得到了很多采用。

Natalie Pistunovich: 我们会在节目笔记中添加链接。另外,我建议你可以利用今天发布的支持更大输入量的新功能,试试能否让它完成你提到的最后一步---整合所有信息并查看它如何工作。

Juan Andrés Guerrero-Saade: 我对此非常期待。老实说……我还没告诉你,Ivan,但我已经用你的工具玩了很久……我一直在想,“好吧,我怎么能递归地使用这个工具呢?从解释一个具体函数开始,到遍历特定的控制流分支,再总结这些总结,等等。最后,我能不能得到一个关于整个程序功能的总结呢?”这几乎是懒到极致的行为。

Ivan Kwiatkowski: 这是一个正在进行的工作。我遇到的主要限制是 OpenAI 的 API 每次请求的 token 数量限制。有一些解决方法,但需要反复调整,而且每次失败都会花费大量 token……因为当你在一个程序中递归时,可能会非常深入,调用成千上万次函数。所以我自己也在调整,但目前还没有找到足够有意义的结果,能让我愿意为每次请求花费 10 或 20 美元。

Johnny Boursiquot: 但这只是成本问题,对吧?仅仅是成本问题。如果有人愿意为你买单呢……假设你有无限预算---那会怎样?

Ivan Kwiatkowski: 但没人会啊……[笑声]

Johnny Boursiquot: 好吧,听众朋友们,如果你愿意资助 Ivan,请联系我们……

Juan Andrés Guerrero-Saade: OpenAI 应该打开钱包资助我们。说实话,这并不算太贵,但的确可能会失控。所以我们肯定需要一些限制措施,再次让它对大家更可用。因为当你在逆向工程代码时,大多数标签信息都已经丢失了……如果恶意软件不是用 Go 生成的,可能很难分辨哪些是库代码,比如标准库代码……所以你很容易沿着错误的路径逆向工程,比如完全逆向 OpenSSL,那是静态编译到二进制文件中的,这太糟糕了,你肯定不想这么做。我觉得这正是 ChatGPT 在某些情况下可能会变得成本过高的原因。如果你试图分析 45,000 个函数,其中 70% 是标准库代码,而你完全没必要这么做……所以我们需要在这里设定一些限制措施。话虽如此---我觉得用 Go 写的代码更容易逆向工程。从某种程度上来说,我会很高兴看到更多的恶意软件开发者选择 Go。

Juan Andrés Guerrero-Saade: 完全同意,因为……我喜欢用 Go 逆向工程。以前这是一场噩梦,但现在……简单多了。

Johnny Boursiquot: 你不知道你刚才的话里包含了多少 Go 的双关梗……我喜欢你自己都没意识到这一点,因为你不像我一样每天都在 Go 社区里混。你刚才提到的梗,我脑海里就一直在响“叮!又一个梗。叮!又一个梗。叮……”[笑]

Juan Andrés Guerrero-Saade: 我甚至没有自己的 gopher(Go 的吉祥物)。我还没到那一步呢。很有趣,我之前和 Natalie 聊过我们最近发布的一篇文章。Alex([听不清 00:34:38.28],我的团队成员)发现了一款用 Go 编写的中国 APT 恶意软件,这是我们第一次发现他们基本上只创建了一个非常简单的 Go 二进制文件;它包含一个名为 Yaegi 的开源 Go 解释器。这个二进制文件非常简单,它运行这个解释器,然后解码所有以 Base64 编码保存的 Go 源代码,最终由解释器在系统上实时运行。

这个想法是,如果杀毒软件或其他安全工具检查这个恶意软件,它看起来非常无害,对吧?就像一个交互式解释器(REPL)之类的东西。但实际上,这只是分阶段的恶意软件。

Ivan Kwiatkowski: 是的,这很好地说明了,很多情况下,优秀的软件开发实践与恶意软件开发者的目标是完全不同的。

Natalie Pistunovich: 如果这还不能让大家遵循良好的开发实践……[笑声]

Juan Andrés Guerrero-Saade: 我不知道,有很多优秀的 Go 攻击工具,很多人还没有意识到它们的存在。我对此没意见,我不会指引他们去找这些工具,但它们已经在那里了。这些人只是没有看对地方。

Ivan Kwiatkowski: 是的,为什么你们还在用 Cobalt Strike?到底怎么回事?

Natalie Pistunovich: 我想说的是,你刚才批评了那些写得差的 Go 恶意软件,但对于写得好的,你还在研究……所以这确实证明了那个观点。

Juan Andrés Guerrero-Saade: 你知道……请让我们的工作更有趣一点……这是一种很奇怪的感觉,对吧?一方面我们总在谈论如何保护人们,帮助他们保护自己的网络……但同时,我们的内心也有一点点邪恶的倾向,你会坐在那里想---如果发现一款前所未见的、Stuxnet(震网病毒)级别的恶意软件,那会非常酷,然后把所有时间都花在研究它上面……这就像在期待一场火车事故的发生。就像看到高速公路上的连环车祸会感到兴奋。这种感觉有点扭曲。

Ivan Kwiatkowski: 是啊,我们通常不会谈论这个。

Johnny Boursiquot: [笑] 就像 Bruno,我们不谈 Bruno。

Ivan Kwiatkowski: 这是我和我的治疗师之间的秘密。

Natalie Pistunovich: Ivan,你写的工具 Gepetto,你之前提到你最近用它比较了带泛型和不带泛型的 Go 代码。

Ivan Kwiatkowski: 其实在这个具体案例中我并没有用它。原因是……首先,我还没试过用 Gepetto 去分析 Go 编译器生成的代码,因为我还没来得及……而且我不确定它的反应如何,因为正如我之前提到的,IDA 反编译器处理 Go 代码的输出可能会有点乱,甚至非常乱,这取决于 Go 的版本。所以我没有太多尝试,我不确定它的效果会如何。但无论如何---是的,我猜到你会问我关于泛型的问题,因为每次我来录播客,你都会问我这个问题……[笑声] 所以这次我有备而来。

Johnny Boursiquot: 哇……

Ivan Kwiatkowski: 所以我做的事情是,我基于官方 Go 教程创建了一个非常小的程序,然后我编译了这个代码,把它放到 IDA 中查看---我对比了一个非泛型函数和一个等价的泛型函数的区别。我本来就有一些预期,结果证明我的预期是正确的。在我透露结果之前,我想解释一下为什么我会有这样的预期并想验证它是否正确:正如我在之前的播客中提到的,Go 语言倾向于自己设计所有东西,对吧?如果你习惯了在 C 或 C++ 中看到某些机制,然后希望在 Go 中找到类似的东西,通常不会相同,因为 Go 的开发者似乎是从头开始设计的,他们希望在不依赖那些“破碎的巨人”基础上做得更好。

所以在这里,Go 实际上做了和 C++ 一样的事情。在 C++ 中,当你有模板函数时,编译器会为程序中实际使用到的每种类型生成一个函数副本。而 Go 在这里做的完全一样。如果你有一个泛型函数,它可以接收整数、浮点数或者字符串作为参数,那么在编译后的程序中,你会有一个接收整数参数的函数版本,函数内部的所有操作都与整数相关;然后你会有一个函数的完全相同的副本,但类型不同。当程序调用这个函数时,根据当前使用的类型,它会调用编译器生成的对应函数实例。

所以实际上,它做的事情和 C++ 完全一样:创建副本,并且由于它在编译时已经知道要使用的类型,它只需调用正确的函数。所以,是的,这就是它的工作原理。

Juan Andrés Guerrero-Saade: 非常简单,对吧?你会觉得“这真的很简单……”

Johnny Boursiquot: 简直小菜一碟……

Juan Andrés Guerrero-Saade: 是啊。很有趣的是,我们的工具很容易出问题。开发社区已经习以为常的一些基础功能,在逆向工程社区却不够完善。比如,Ivan 提到的 IDA Pro,这是大多数逆向工程师使用的反编译/反汇编工具。当它试图处理 Go 时,它就有点崩溃,表现得不太好。这是因为一些非常简单的原因---比如它们从未想过需要处理多返回值函数。超级愚蠢的事情,比如它无法处理多个返回值,所以它需要为函数生成额外的前置代码,试图存储这些东西……是的,你可能会觉得我们可以很容易地修补这些工具……但事实并非如此。

Ivan Kwiatkowski: 我认为一个更根本的问题是,像 IDA 和 Ghidra 这样的反编译工具,它们看到代码后会尝试将其反编译为 C。而它们只能生成 C 代码。问题在于,并非所有东西都可以用 C 表达,尤其是 Go 程序。所以当工具试图提升一个抽象层次,试图生成对应的 C 代码时,显然无法创建任何有意义的 C 表示。这导致了一些问题---比如变量不存在,或者它们生成了错误的变量……整个过程就崩溃了,因为工具最初的假设本身就是错误的。

Natalie Pistunovich: 如果有听众正在用 Go 构建逆向工程工具,请联系我们。我们想在下一期节目中邀请你。

Johnny Boursiquot: 是的,真的。

Natalie Pistunovich: 有很多问题想问你们……

Juan Andrés Guerrero-Saade: 有人能帮忙吗……是的,那太棒了。我确实有点嫉妒,当我看到 CI/CD 的各种发展,以及围绕你们所使用的工具建立起的社区……真的有点嫉妒。我们在我们这个领域中,并没有那么活跃的开发者社区。我想这更能反映出---让我补充一下---我想这更反映了我们这个领域从业者的稀缺性。我们往往都忙得不可开交,所以你会看到,有人可能会发布一个很酷的插件,然后他就又回去忙自己的工作了。

所以,你很少会看到有人专门坐下来反复改进工具,或者说“我来启动一个全新的逆向工程项目”,或者开发一个新的框架……通常情况下,这意味着某个非常优秀的逆向工程师会暂停分析恶意软件的工作三个月,专心去开发这个东西,然后就会面临一个问题---很难判断这是否是他们时间的最佳利用方式。

Ivan Kwiatkowski: 是的,确实如此。对我们大多数人来说,开发只是一个副业。而且很不确定我们的雇主是否会愿意让我们花六个月的时间去开发一个能帮助整个社区的超棒框架。即使是为了内部使用,他们可能也会犹豫。

Johnny Boursiquot: 我不像你们那样活跃在安全圈子里,但我接触过一些工具,似乎就是为了改善开发者工作流或开发者体验而设计的。我想到的一个是基于 Ruby 的,我觉得 Metasploit 对我来说就像这种框架……它提供了工具和挂载方式,让从事相关工作变得更容易。现在还有类似的东西吗?

Juan Andrés Guerrero-Saade: 你描述得没错,那是一个很棒的框架,但它是用来攻击的。所以我们很少有类似的框架是用于防御的---

Johnny Boursiquot: 用于防御的。

Juan Andrés Guerrero-Saade: 相反,你会看到这些流水线式的模型,比如“哦,只要运行那个 Metasploit 就行了。用 Cobalt Strike 就够了。别担心,只有四个选项,然后它会输出新的恶意软件给你。”而我们站在另一边,却在用上世纪 90 年代末的苏联工具,试图……这完全不能相比,对吧?

Johnny Boursiquot: [笑] 进攻比防守更有趣……你是这样告诉我的吗?

Juan Andrés Guerrero-Saade: 我觉得进攻更容易上手。我不确定它是否更有趣,因为我就是那种喜欢做逆向工程和恶意软件狩猎的怪咖……所以对我来说,我更喜欢这些。我觉得如果你在攻防安全领域,却从来没有真正攻击过任何人,那就很奇怪。这可能是我一个不太受欢迎的观点:我不明白为什么你会选择攻防领域,却从来不违法,也从来没有为了好玩而去黑别人,对吧?单单是为企业做一些付费的渗透测试,这听起来是对超能力最无聊的利用方式。

Ivan Kwiatkowski: 这是对娱乐性黑客行为的温和辩护。你们听到了。

Juan Andrés Guerrero-Saade: 没错。其实就像这样,你拥有超能力,却只用它去帮别人把猫从树上救下来。这是个不错的副业,但你有超能力,可以突破系统,可以进入你不该进入的地方,可以得到你不该拥有的东西……然后你选择去抢一个糖果店。这对我来说不太说得通。所以我很满意做防御……好像是个最烂的回答---我认为在防御这边更有趣,但它就像一个自己会转动的魔方。

Ivan Kwiatkowski: 不过我觉得你说得对。我觉得大多数防御者都知道,他们不应该被赋予这样的能力,而攻击者知道这一点,但还是去做了。

Juan Andrés Guerrero-Saade: 他们生活在边缘啊……所以这就是为什么你可以去 NSA 工作,对吧?他们现在招人招得特别凶。所以对于那些刚从 Google 或其他地方失业、感到无聊、不吸大麻、想为国家服务但收入比之前少的人来说……

Ivan Kwiatkowski: 而且必须是美国公民。

Juan Andrés Guerrero-Saade: ……而且必须是美国公民,没有被捕过……有很多条件。但本质上,你可以去 NSA 为国家服务,并且可能会做一些很酷的事情。

Natalie Pistunovich: 不知道为什么这让我想起……我欠 IKEA 一个道歉。 [笑声]

Juan Andrés Guerrero-Saade: 舞台交给你了……

Johnny Boursiquot: 请继续……

Natalie Pistunovich: 几期节目之前,我把写得很差的代码比作 IKEA 家具……

Johnny Boursiquot: 哦……好狠。

Natalie Pistunovich: 这不是作为“不受欢迎的观点”提出的……

Ivan Kwiatkowski: 你收到停止和终止函了吗?

Natalie Pistunovich: 嗯,没有……

Johnny Boursiquot: 我还以为是……

Juan Andrés Guerrero-Saade: 你可能永远不能再进入瑞典了……

Natalie Pistunovich: IKEA 其实已经不是一家瑞典公司了,尽管大家都这么认为……但它已经不是了。它曾经是,现在还是以瑞典品牌自居……不过我的一个朋友是云计算顾问,他在帮助 IKEA 迁移到云端,并且采用 Go……

Ivan Kwiatkowski: 请不要告诉我是在中国……

Natalie Pistunovich: 不是,不是。然后他们对他说,Go 挺好的,但是她在 Go Time 上批评我们,那可不太好。 [笑声] 所以,我要道歉。对不起。

Johnny Boursiquot: 哇哦……

Juan Andrés Guerrero-Saade: 影响挺远的。

Natalie Pistunovich: 我这样做确实不太好……我会改进的。

Johnny Boursiquot: 哇……“她在 Go Time 上批评我们了。”

Juan Andrés Guerrero-Saade: 你得承担起使用这个麦克风所带来的责任啊……

Johnny Boursiquot: 哇,这太惊人了。这真是个惊人的故事。希望 IKEA 听到后会意识到“开玩笑的啦。”Natalie 并没有真的那个意思。

Natalie Pistunovich: 我很想邀请你们上播客。

Johnny Boursiquot: 其实是的,我们确实很想邀请。如果他们在用 Go 做一些东西,我觉得这会是一期很有趣的节目。所以,IKEA……你们收到了来自 Go Time 团队的正式邀请。来和我们聊聊你们正在做的事情吧。

Natalie Pistunovich: 还有一个正式的道歉。

Johnny Boursiquot: 是的,还有一个正式道歉。

Juan Andrés Guerrero-Saade: 他们的肉丸很棒。一切都很棒。

Johnny Boursiquot: [笑] 说到不受欢迎的观点……是不是该聊聊这个了,Natalie?

Natalie Pistunovich: 那么,Johnny,作为提出这个话题的人,你有要分享的观点吗?不是想让你难堪啦……但我得说,在我们做音频测试的时候,有一个声音有点不对,我确实直接指出了问题。所以,先是这个,现在又让你当场表态……对不起,Johnny。这好像成了我的道歉播客了。

Johnny Boursiquot: 你的道歉巡回演讲……

Natalie Pistunovich: 是的。

Johnny Boursiquot: 不受欢迎的观点……嗯,我们可以稍后再回到我这边吗?因为我现在在想怎么把我的观点表达清楚,又不冒犯到 IKEA,就像你之前做的那样……[笑]

Natalie Pistunovich: 那可不是“不受欢迎的观点”环节的一部分。这才是最糟糕的地方。

Juan Andrés Guerrero-Saade: 我觉得在节目过程中,我已经随意地抛出了很多不受欢迎的观点了……

Natalie Pistunovich: 的确如此。那你有一个可以作为总结的吗?

Juan Andrés Guerrero-Saade: 我不知道这算不算一个不受欢迎的观点……好吧,我可以让它在这个播客的语境下变得不受欢迎。我觉得 Python 3.11 正在试图模仿 Go 的一些优秀语法特性,以保持其相关性。而我认为这是不受欢迎的观点的地方在于,我可以想象一种情况,Python 通过偷取一些不错的点子,继续充当懒人用的脚本语言,从而在编程语言市场份额上进一步占据主导地位。

Natalie Pistunovich: 那如果你要将这个观点总结成一条可以投票的推文,你会怎么写?

Juan Andrés Guerrero-Saade: 如果我必须用 140 字来发推,那就是:“Python 3.11 将会干掉 Go。”

Johnny Boursiquot: 好的。

Natalie Pistunovich: 这很可能会在“不受欢迎的观点”排行榜上排名靠前……

Juan Andrés Guerrero-Saade: 是啊,我的意思是,这超级不受欢迎。我并不是完全赞同这个观点,但我想说的是---如果你只是偷点点子,然后这个大多数人用来随便写点脚本、应付点事情的懒人编程语言,突然拥有了 Go 的一些不错的特性,人们就会说:“我还是待在这里吧,直到我准备好真正成为一个优秀的开发者。在此之前,我就这么悠闲地待着吧。”对吧?

Natalie Pistunovich: AI 会改变这一切,我们得再做一期播客聊这个话题。我们几乎还没开始深入探讨这个话题,但我们确实计划要聊的。Ivan,你有什么不受欢迎的观点吗?

Ivan Kwiatkowski: 是的,我试着想了一个,但它跟编程完全是两个领域的事情……所以让我现在有点感觉不合适。但是,总之……嗯,我想到的观点是,其实我不认为有什么“自由意志”这种东西。我认为你所想过的一切,以及你将来会做的一切,实际上都是你大脑中化学过程的结果,而这些过程是由你大脑在前一毫秒的状态决定的。从这个意义上讲,除非你能向我证明存在某种灵魂或精神之类的东西,否则我无法指出“自由意志”应该存在的具体位置。

Juan Andrés Guerrero-Saade: 你切入的范围好广啊,兄弟……

Ivan Kwiatkowski: 哦,是的,确实很广。抱歉。

Juan Andrés Guerrero-Saade: 这就是安全领域的工作对人的影响。[笑声]

Ivan Kwiatkowski: 还有 JavaScript。

Johnny Boursiquot: 哇哦。[笑] 他就像是在说:“接下来我再扔一个重磅炸弹……”

Juan Andrés Guerrero-Saade: “我们没有自由意志,而且 Java 完蛋了。”对吧?[笑声]

Johnny Boursiquot: 在他脑海里,这两件事是有联系的。这真是太有意思了。

Juan Andrés Guerrero-Saade: 我们接受这个观点。彻底的虚无主义,兄弟……

Johnny Boursiquot: 我的“不受欢迎的观点”是---我有一个不好的习惯,总以为我的观点不受欢迎,但最后它们却很受欢迎……除了有一次,我的观点确实不受欢迎。不过,不管怎么说,我的观点是:我认为随着这些能够理解和生成语言的生成式 AI 工具的出现,尽管使用这些工具的门槛很低……我是一个 GitHub Copilot 的付费用户,我用它,而且我很喜欢它……这些工具可能会让编写代码变得更容易、更快,但责任仍在你,开发者自己身上。

我觉得我们正进入一个时代,在这个时代你要学的不是如何写代码,而是如何校对代码。因为如果你可以让机器模型为你生成代码,那么你要做的唯一事情就是验证它是否正确。现在,我发现我从 GitHub Copilot 这样的工具生成的代码中,大约有 50% 需要调整,因为它并不完全符合我的需求……但这仍然意味着有 50% 的代码我不需要自己敲,对吧?所以它确实对我有帮助,也确实提升了我的生产力,我也很乐意付费使用它……但理解它生成的内容的责任仍在开发者自己身上。对我来说,这就像过去我学习网站工作原理时会查看“查看源码”一样……类似的情况是你可以从云端获取源码,但理解 HTML 标签是什么,<p> 标签是做什么的,<img> 标签是做什么的,如果我给某个标签添加一个属性,它会有什么作用---这些仍然是你需要去理解的东西。就算有了这些工具,你仍然需要理解你所得到的内容。

因此,仅仅因为你有了工具,并不意味着所生成代码的责任,包括生产力的提升或者使用这些代码造成的混乱,最终都依然是由你来负责的。如果你作为开发者提交了生成的代码,却没有验证它、校对它、修正它,我会对你更严厉。相比之下,如果你自己写了代码,我会更宽容,因为我知道这是“人为错误”,也许我们需要更好的代码审查实践和其他机制。但如果你只是盲目地生成代码、提交、上线,那么我会对你更严厉。

Juan Andrés Guerrero-Saade: BuzzFeed 的标题几乎都写好了:“某个开发者随便提交了一段 ChatGPT 生成的代码到 Google 3,导致广告系统瘫痪了 45 分钟。”你可以预见到这种懒惰可能带来的后果……我觉得你是对的。而且有趣的是,我看到一些开发者在 YouTube 上……我仍然会看那些真正会编程的人的视频,尽管我自己不会……他们会谈到“哦,Copilot 提升了我的生产力。”但与此同时,你还得坐下来解读代码,这也带来了一些负担。

Ivan Kwiatkowski: 欢迎来到我的世界。

Juan Andrés Guerrero-Saade: 是的,完全正确。这就是我们每天的工作,完全没有问题。我不知道你怎么样,Ivan,但就我个人而言,我其实并没有写多少代码。大部分时间我都在尝试解读代码。所以如果有一个工具能为我生成 70% 有用的代码,我只需要在这个基础上套模板……对我来说,这已经是一个巨大的帮助了。不过,我不确定这对那些更专注于代码生成的人来说,会如何影响他们的日常工作、习惯和流程,和我们相比可能会有很大不同。

Ivan Kwiatkowski: 是啊。不过,从我的角度来看,Johnny 的观点似乎非常受欢迎。

Johnny Boursiquot: 我就说过嘛……[笑]

Ivan Kwiatkowski: 我怎么也无法想象,有人因为粘贴了 StackOverflow 或 GitHub Copilot 上的代码导致出了问题,然后抱怨,最后却在 Reddit 或 Twitter 上获得正面回应。我觉得这根本不可能发生。

Juan Andrés Guerrero-Saade: 我想起来我原本的“不受欢迎的观点”是什么了,但实际上我很高兴自己忘了……我觉得我们还是算了吧。

Johnny Boursiquot: 哦……!

Juan Andrés Guerrero-Saade: 就让 Natalie 来吧---

Johnny Boursiquot: 让“沉睡的狗”继续沉睡吧……

Juan Andrés Guerrero-Saade: 我没事的,不需要收到那些仇恨邮件。没必要。

Natalie Pistunovich: 那我的不受欢迎观点是关于音乐的,特别是 Eurovision(欧洲歌唱大赛)。它还有点背景故事。去年,有一个乐队---

Ivan Kwiatkowski: 你也冒犯到他们了吗?哦,你要开始了。

Natalie Pistunovich: 不,不,不,我很喜欢他们。我很喜欢他们。就是有一个很棒的乐队,叫 Electric Callboy。他们最初的名字是 Eskimo Callboy,他们申请参加 Eurovision。2022 年的时候,很多人都很喜欢他们……但他们被拒绝了,部分原因是因为这个名字。这也是他们后来改名的原因之一。最后,他们没能成为德国的参赛代表。对于所有美国听众来说,Eurovision 是一个欧洲国家之间举办的音乐比赛,也包括一些额外国家,比如澳大利亚……

Juan Andrés Guerrero-Saade: 它就像《好声音》,但带点民族主义的味道?

Natalie Pistunovich: 它其实是二战后开始的,作为一种“让我们以一种积极的方式嘲笑自己”的活动。所以它总是很华丽、很精彩。比赛一般是在五月左右。获胜的国家会在下一年主办比赛,就这样循环。每年的参赛作品都非常多样,超级有趣。我很喜欢。

所以德国的乐队 Electric Callboy 没能入选,后来有请愿活动要求让他们成为参赛选手。而且德国一般确实会因为请愿有反应,只要有足够多人签署请愿,政府就必须认真对待并讨论。当然,这不是政府的问题,但德国人确实喜欢发起请愿。虽然有请愿活动,但德国 Eurovision 委员会(或负责这个事情的机构)没有接受这个提议……最终他们还是被拒绝了。后来选了另一个参赛作品,那个作品还不错,但成绩非常糟糕,说实话并不适合 Eurovision。如果你问我的话,那首歌并不算 Eurovision 水准。

今年的参赛作品是一个很不错的乐队,我确实知道他们……但感觉就像是去年被拒绝乐队的另一个版本。这可能是我冒犯到他们的地方……我觉得你们不错,但不如 Electric Callboy。现在就是我的不受欢迎观点了。德国在这里犯了一个错误,他们应该在今年纠正,但只是打了个补丁。好了,这就是我的长篇不受欢迎观点。祝你好运把它总结成一句推文。

Juan Andrés Guerrero-Saade: 我试图从屏幕上溜走……就别让我参与这部分了……

Johnny Boursiquot: 哎,如果我们有 Twitter Blue 的话,现在可以在推文里发整本书了……我们会想办法的。

Juan Andrés Guerrero-Saade: 现在你在 Twitter 上基本可以随心所欲了。规则已经不重要了……API 用不了,但花 8 美元你可能还能参观数据中心。

Natalie Pistunovich: 我听说下一集会有道歉?

Juan Andrés Guerrero-Saade: 不不,这次没问题。我们可以接受这个观点。谁知道下次录节目时 Twitter 会不会还在……

Ivan Kwiatkowski: 反正他们也没有公关部门,所以也没人会投诉。[笑声] 这是真的,真的。

Juan Andrés Guerrero-Saade: 噢,天哪……

Johnny Boursiquot: 也许 Elon 会听到你的吐槽,也许 Elon 会出现在节目里……Elon,如果你在听 Go Time---你应该听,这是很棒的内容,Musk 先生……如果你听到了……

Natalie Pistunovich: 我们知道你有很多公司在用 Go。请加入我们。

Johnny Boursiquot: 是的,来加入我们录一期节目吧。我们很愿意接待你。

Juan Andrés Guerrero-Saade: 我们可以一起抽点东西,然后聊聊 Go,不嘲笑 Twitter。这简直完美。

Johnny Boursiquot: 嗯,某些事情我们可能不能保证,但……一定会很有趣的。

Natalie Pistunovich: 好吧,非常感谢大家的参与。我们还有一些未展开的话题,也许第五期节目会聊到,谁知道呢?祝所有正在听或者之后会听这期节目的人度过愉快的一天……谢谢你,Ivan,谢谢你,Jax,谢谢你,Johnny。

Juan Andrés Guerrero-Saade: 谢谢大家。



使用 TinyGo 和 Gopherbot 进行硬件黑客攻击

帮我整理这一期英文播客,翻译为通顺的中文,请保留完整内容,不要删减,谢谢!

其中的英语人名请不要翻译.



20241117

使用 TinyGo 和 Gopherbot 进行硬件黑客攻击

本篇内容是根据2019年4月份#84 Hardware hacking with TinyGo and Gopherbot音频录制内容的整理与翻译

Mat Ryer 与特邀嘉宾 Ron Evans 共同主持了第一期一对一采访式节目。 Mat 请 Ron 教我们有关 IoT 中的 Go、GopherconTinyGoGopherbot的硬件黑客技术。

过程中为符合中文惯用表达有适当删改, 版权归原作者所有.



Mat Ryer: 大家好,我是Mat Ryer,猜猜今天是什么时间?对了,今天是Go Time!我们正在为机器人起义做准备,*剧透*:这将是非常可爱的。今天我们要讨论TinyGoGopherbot,我身边有Gopherbot的创造者;我听说有人称他为“开源界的圣诞老人”。他就是Deadprogram,也就是Ron Evans。嗨,Ron,你好吗?

Ron Evans: 嗨,大家好。其实今天是TinyGo Time。

Mat Ryer: 是的,TinyGo Time。

Ron Evans: 没错。

Mat Ryer: 你更喜欢别人叫你Ron,还是Dead?

Ron Evans: 嗯,我还没死,所以…

Mat Ryer: 那我们就叫你Ron吧。

Ron Evans: 对,Ron就好。不过我更喜欢人家叫我Deadprogram,像Prince一样---你不会叫他Pri,你只会叫他Prince。

Mat Ryer: 对,Deadprogram。它也可以像一个崩溃的符号,或者别的什么… 总之,欢迎来到节目。

Ron Evans: 只有在我和唱片公司产生分歧时,我才会这么做。

Mat Ryer: [笑] 说实话,我觉得这很可能会发生,Ron。

Ron Evans: 也不是不可能。

Mat Ryer: 很好。其实这是我们第一次做这样的节目。这会是一个非常亲密的节目,靠近麦克风,充满温暖,今天只有我和Ron,聊聊这些话题。

Ron Evans: 还有机器人,当然了。只有我和你,还有一群机器人。

Mat Ryer: 对。我们会多了解一下这些调皮的小机器人,可能从TinyGo开始,因为我认为这就是一切的起点,Go在这里起了作用,对吧?

Ron Evans: 是的。我以前上过GoTime,讨论过我在Go领域参与的一些其他项目---机器人、无人机、计算机视觉,最著名的是Gobot项目… 去年我还谈到了GoCV,这是使用Go和OpenCV进行计算机视觉的项目… 但我一直梦想的,或者说从一开始就想要的,是能够在最小的芯片上运行Go,以及这些微控制器上。这些是非常小的芯片,存在于所有东西中,比如键盘、鼠标、刹车系统… 这些小芯片连接到物理世界,促使事情在正确的时间发生,或者不发生,希望一切顺利… 但我们从未能在这些小芯片上运行Go,因为它们实在太小了。Go是一门很棒的语言,非常强大,能做很多惊人的事情,但它并不以小巧著称。Go的关键字数量虽然很少,但生成的二进制文件却很大,我们就这么说吧。

Mat Ryer: 是的。所以Go对这些微控制器来说太大了。

Ron Evans: 是的,而且大了好几个数量级。一个简单的Hello world程序,在Linux操作系统上编译时,大约有1.1MB大小。公平地说,它包括了整个Go运行时;它几乎包含了你可能会需要的一切… 一个只输出Hello world的程序,和执行一些相当复杂处理的程序,大小差别并不会太大,因为你一旦达到那个初始基准,差别就不太明显了。但当我们谈到微控制器时,我们可能在讨论的处理器只有64k的RAM,甚至更少。

我一直想要这个,但一直没有实现,人们也总是说,“Deadprogram,你疯了,这永远不可能实现。”不过有几个人尝试过,并做了一些很酷的事情。曾经有一个项目叫Emgo,它基本上把Go转译成C代码,然后你可以用GCC编译器为不同的基于ARM的嵌入式设备编译它。这是朝着正确方向迈出的一步,但它不是纯正的Go代码。尽管如此,这是一个非常酷的项目,它的主要开发者完成了很多了不起的工作… 但它依然未能实现我的目标。

我和其他一些人讨论过,试图说服那些我认为能够真正解决这个问题的人:“我们如何才能把Go编译成这么小的东西呢?”直到去年,大约是9月份,我才知道了这个TinyGo项目,它最初是由我的主要合作者Ayke van Laëthem发起的。他参与了多个嵌入式领域的项目,特别是MicroPython,这是一个可以在微控制器上运行的Python版本。

他的态度是:“如果Python可以在微控制器上运行,如果JavaScript可以在微控制器上运行,那为什么Go不可以?”但他的方法非常独特,也非常强大。

其实,Go是用Go编写的。Go编译器是用Go编写的。

Mat Ryer: 它以前不是用Go写的,对吧?

Ron Evans: 对,以前不是。最早是用C写的…

Mat Ryer: 嗯,那他们当时为什么不用Go呢?

Ron Evans: …所以你必须用C编译器比如GCC来编译Go,然后一旦你编译好了Go编译器,你就可以用它来编译Go程序。所以直到---我记不清是哪个版本了… 可能是1.5左右。

Mat Ryer: 我也这么记得。

Ron Evans: 我不记得具体的版本了,但编译Go编译器本身是一件非常复杂的事情。但后来有一段时间,Go编译器完全用Go编写。这对Go核心团队非常有用,也对任何想为Go编译器做贡献的人有用,因为如果你能读懂Go语言,你就能读懂编译器代码… 这也是其他一些非常酷的开源语言面临的问题,比如Ruby或Node.js,人们说“哦,我想为这个项目做贡献”,可他们会发现:“对不起,它不是用JavaScript写的,它是用C++写的,而我不会C++,所以… 抱歉,我不能真正做贡献。”所以Go是用Go写的,这无疑降低了门槛,同时也创造了新的可能性。

因为Go可以用Go解析自己的语言,你可以使用Go自己的库和工具,然后将其转化为所谓的单一静态赋值(SSA)形式,这就是---当你将Go语言的文本交给Go编译器时,编译器会将其解析,拆解并转化为SSA形式,然后Go编译器的工具链再将其转化为机器代码。

Mat Ryer: 理解。

Ron Evans: 所以Ayke的想法是:“嗯,如果我能拿到这个SSA形式,我就可以用LLVM工具链将其转化为二进制代码。” LLVM是一组已经存在相当长时间的编译器库和工具。我不确定它具体有多久… 至少我用了12年了,但它可能存在的时间更长… 它已经非常有名了,因为有些语言使用了LLVM,而这些语言吸引了很多人的兴趣。其中一个是Swift。Swift是基于LLVM编译器工具链的语言。另一个是Rust。

Ayke利用Go编译器工具链生成的SSA形式,然后TinyGo---这个编译器项目---接过这个SSA形式,转化为LLVM的中间表示(IR),这样就可以为LLVM支持的目标架构进行编译。其中一些目标就是微控制器,比如Arduino,它使用的是AVR微控制器。所有基于ARM的微控制器,比如Adafruit的Circuit Playground Express,这是一个非常酷的微控制器… 所有新的Arduino都基于这些类型的ARM微控制器,比如Arduino Zero和MKR1000。所以几乎所有有趣的、小型的、廉价的芯片都基于这样的微控制器架构,如果我们能将代码编译到这些设备上…

今年2月,我在布鲁塞尔的FOSDEM大会上首次展示了TinyGo,场面非常激动人心。Ayke从他所在的荷兰赶来了,还有其他地方的贡献者… 在我的演讲之后,我们还举行了第一次“志同道合者”会议,FOSDEM的组织者很慷慨地把我的演讲安排在主会场,所以我实际上是第一个在FOSDEM主会场里讲述Go的人。这真是太神奇了。我心想:“你们应该先挑选一个真正的Go专家,但…嘿,TinyGo!让我们开始吧!”不过,能够分享这些成果真的很激动人心,我们发布了软件的第一个版本。

我展示了很多演示,但最吸引人注意的是,我编译了一个500字节大小的TinyGo程序,它运行在一个8位的微控制器上,叫做ATtiny85,它只有8k的RAM。

Mat Ryer: 哇哦。

Ron Evans: 所以我可以为一个只有8k RAM的8位微控制器编译一个Go程序。

Mat Ryer: 那真是非常小。

Ron Evans: 是的,非常小。我选择它基本上是因为这个微控制器的名字里有“tiny”这个词,我承认。

Mat Ryer: 哈哈。所以Go源码被转化为SSA,然后TinyGo工具链接手,从那里生成这些小程序… 那么代价是什么呢?你失去了什么?11k的Hello world中缺少了1.1MB版本中的什么东西?

Ron Evans: 这就是关键---我们无法做到将100%的信息压缩到0%信息,我相信这是不可能的…

Mat Ryer: 对,没错。

Ron Evans: 它并没有包含Go中的所有东西。一个很大的区别是,当你在没有操作系统的裸金属上运行时,运行时是不同的… 所以我们必须以不同的方式实现运行时调用。

另一个挑战是Go的标准库。我们可以编译很多标准库,但有些部分我们不能,因为它们与运行时本身紧密耦合。

Mat Ryer: 是的。

Ron Evans: 所以在这些情况下,我们需要实现一个更偏向操作系统无关的标准库版本。因此,这涉及一些权衡。我们目前还无法编译所有的 Go 代码……但这也并不是我们主要的目标。

Go 核心团队的工作非常出色。我们并不是在尝试取代 Go,也不是说“Go 不好,TinyGo 很好。”完全不是这个意思。恰恰相反,我们的观点是:“哇,Go 是一个如此棒的语言,我们希望在更多的地方运行 Go 代码。我们能否提出一个新的 Go 运行时和标准库实现,同时仍然保持使用同样的 Go 编程语言呢?而且,这样的话,是否可以继续使用 Go 的标准库呢?”也许是全部,或者大部分,至少相当一部分。

但是,由于它需要运行的环境,存在一些权衡。然而,一个巨大的好处是,我们不需要携带所有额外的东西。毕竟,没有人会一次性使用整个标准库。如果你真的这么做了,那你的代码真的是非常庞大啊,朋友们!

Mat Ryer: 你肯定不想在 Tiny 这种边缘硬件上运行这样的大代码吧……有些东西是显而易见的,比如操作系统、打开文件等---在没有多少文件系统的情况下,它们可能会有所不同。我不知道这个例子是否合适。

Ron Evans: 这个例子其实很好。在微控制器上,你根本没有文件系统,对吧?但你可以……我们目前还没有实现它,但它在我们的路线图上。有很多小设备通常会有闪存或者 SD 卡接口。这些设备通常使用 SPI 接口,这是一个低级硬件接口,微控制器可以通过它与设备通信。这样,设备上就可以有一个文件系统。非常常见的是,这些设备默认使用 FAT 文件系统……所以当你获得一个 Adafruit Circuit Playground Express 时,它自带一个 1 MB 的闪存,你可以用它来存储你可能想要从微控制器代码中读取的各种数据文件。最终---我们目前在 TinyGo 中还不支持这一点,但它在我们的路线图上;例如,你可以有 wav 文件,然后使用内置的数模转换器 (DAC) 播放它们……所以你可以作为一些有趣交互的一部分播放 wav 文件。或者,你可以远程记录数据……对于那些并非一直连接云端的设备,或者本来就不打算连接云端的设备,你也许仍然希望能够保存数据。

Mat Ryer: 对。

Ron Evans: 也许你想训练你的设备来识别你的声音,但你不想把任何数据放到云端;你需要某种本地存储。因此,这些是你需要实现的低级接口。有些人使用所谓的实时操作系统来实现这些功能,这并不是一个完整的操作系统,但它可能具有某些功能……比如一些内存分配功能、文件读写功能、网络功能等等……但我们尽量在能用 Go 标准库的地方使用它。

Mat Ryer: 那么在这个例子中,TinyGo 需要有自己的 OS 包吗?这个包的功能完全不同,但可能复制了标准库 OS 包的接口?你会这样处理吗?

Ron Evans: 这实际上就是我们正在做的事情,在多个场景下。例如,我们一直在为 MacOS 添加支持,以一种操作系统无关的方式实现它。但一个更好的例子是裸机环境,在这种情况下我们需要能够说“哦,根本没有操作系统原语”,所以我们要么自己实现它们,要么以某种方式不实现它们。

我们在 TinyGo 的 wiki 页面 上讨论了我们遇到的主要挑战,其中之一就是 Go 本身当前实现的紧密耦合---即运行时和标准库之间的紧密耦合。实际上,有一些非常棒的演讲是关于这个话题的……我参加了其中一个讨论 Go 2.0 设计的演讲。Ian Lance Taylor 做了这个演讲,讨论 Go 2.0 过渡。他做了几次这个演讲,我第一次是在 GopherPalooza,我记得是在旧金山举行的。那是去年举办的一个非常酷的会议。他讨论了运行时和标准库之间的紧密耦合,以及一些解耦的提议……这样,如果你正确实现了运行时,理论上整个标准库仍然可以继续工作。

对于 TinyGo 来说,这是一个技术上的巨大挑战,但我们能够通过重新实现一些逻辑上的原语来绕过这个问题,特别是在你运行在内存非常有限的设备上时。

还有一些其他重要的差异,比如微控制器到底是如何工作的?在微控制器上,你有寄存器,这些寄存器通常用于非常低级的硬件通信。它可以通过 GPIO 接口打开和关闭 LED,或者它可以与直接连接到微控制器的其他芯片通信,比如我们的小闪存示例中的 SPI 接口,或者许多传感器使用的 I2C 接口,比如数字指南针(更正式的名字是磁力计)、加速度计(运动检测器)、温度计……有很多传感器使用 I2C 接口。

TinyGo 最重要的工作之一是创建驱动程序,这些驱动程序提供标准接口---同样是用 Go 定义的---这样你可以编写一些代码,如果它使用的是 Circuit Playground Express 板上的同样的 LIS3DH 数字加速度计,那么同样的代码可以移植……今天 Hacker News 和一些网站上有一个项目是关于一个自制智能手表的---我不知道你们今天是否看到了这个项目---它使用了相同的数字加速度计。所以我们可能---我还没有拿到那个硬件,也没有仔细看,但如果它使用的是 TinyGo 支持的处理器之一,我们理论上可以在这个手表上运行 TinyGo,然后连接到它已经有的同一个传感器,几乎不用更改代码,甚至可能完全不需要更改。

Mat Ryer: 哇,这太棒了。

Ron Evans: 是的,这种硬件抽象层非常重要,因为你不总是能确切知道你想要使用哪种目标硬件,所以我们可以解耦那些知道如何执行标准功能的驱动程序,比如读取温度、检测加速度计的运动,无论它运行在哪个芯片上,无论是来自微芯片的,还是 Nordic Semiconductor、NXP 或者 Intel 的……通过这样做---这符合 Go 的精神,Go 试图做到操作系统和架构无关;我们只是将其扩展到 Go OS 无操作系统……这应该是我们的理想用例。

Mat Ryer: 是的。你刚才提到了接口……这是否意味着你可以在这个地方编写单元测试?如果你正在处理 Go 中的一个接口,这是一个不需要物理硬件的测试机会吗?你可以只针对这个接口编写测试代码。

Ron Evans: 是的,这绝对是这样的……首先,关于测试。测试在任何现代软件开发中都是绝对必要的。

Mat Ryer: 毫无疑问。

Ron Evans: 然而,如今已经是 21 世纪了,大多数嵌入式软件仍然主要使用的是非正式的手动/功能测试。笑话是这样的:“嘿,我刚刚看了一下这个程序,它有 10,000 行代码,没有测试。”“什么?!这不可接受!”“哦,这是一个嵌入式程序。”“哦,没事了。发布吧。”

Mat Ryer: [笑]

Ron Evans: “别担心,它只是在一个喷气式飞机上,或者也许是在一个用于脑外科手术的激光器上……没什么太重要的东西。” “我们实际上也不知道。我们只是把这些芯片卖给某人,他们又把它们卖给一些公司,最后他们用来做某些东西。”

Mat Ryer: 确实是这样……这不是他们的问题,对吧?

Ron Evans: “问题总会得到解决的。”所以这种方式显然不是开发软件的正确方法。因此,首先,正如你所说,我们可以使用接口来测试一些不一定需要连接物理硬件的东西。我们现在使用 QEMU 做了一些这样的工作,这是一个硬件的模拟器。还有一些其他很酷的项目……Antmicro 有一个项目,我现在记不起名字了,但它的具体功能就是提供一些微控制器的模拟。所以有很多我们可以做的事情,来对不同的嵌入式系统进行全面深入的测试,使用现代语言和现代编译器---如果我们谈论的是软件可靠性,这在涉及边缘设备时就变得更加重要,因为安全显然是一个大问题……但还有一些次要影响;如果它收集信息不正确,这可能会导致我们忽视某些问题……比如,洒水系统可能不是关键任务,但如果它浪费了大量的水,长期来看这是非常糟糕的。

Mat Ryer: 是的。

Ron Evans: 短期来看也很糟糕,因为你会收到一张巨额的水费账单。所以通过测试,我们可以避免这些问题。另一个是时间测试。如果我们想测试我们的洒水系统,确保它正常工作,如果我们只有物理功能测试,那么每次迭代我们都要让它运行一整周,以确保它在正确的时间开启和关闭。

如果我们能够编写一些单元测试,来测试它是否在正确的时间触发事件,我们可以加快进度---这是正常的迭代式软件开发,正确的模拟机制和接口设计可以帮助构建更好的架构。如果我们将这些结合起来,这真的是我们编写接触物理世界的软件的唯一希望。

Mat Ryer: 是的。

Ron Evans: 最后一毫米。

Mat Ryer: 是的,接触物理世界是一个很好的点,我现在可以谈谈我们今天要进行的精彩赠品活动。我们要求大家要么对播客进行评价(最好是正面的),要么分享它,告诉你的朋友这个播客,特别是这一集……所以当这一集上线后,如果你在社交媒体上分享它,或者只是推荐给一个朋友。如果你想参加抽奖,请截个屏幕截图证明你确实分享了,并发送到 GoTime@Changelog.com。相关信息会在节目笔记中,但你肯定不想错过这个机会,因为我觉得奖品非常棒。Ron,现在或许是一个好时机来谈谈那个奖品,我们可以深入了解一下那个项目。

Ron Evans: 是的,我们将赠送一个 Gopherbot。什么是 Gopherbot?Gopherbot 是一个可以用 TinyGo 编程的机器 gopher 毛绒玩具。

Ron Evans: 很多人有这些非常棒的 gopher 毛绒玩具,它们是你在 Gophercon 和其他酷活动中获得的……我和我弟弟 Damon 一起合作,他也是一个硬件黑客,他设计了一个 3D 头盔,我在里面安装了很多 LED 和传感器……人们非常喜欢它。所以我们想,“哇,这是一个为我们的开源努力筹集资金的好方法,同时还能创造一个非常酷的、可编程的、可收藏的机器人毛绒玩具。”所以 Gopherbot 是我们的表达方式。它使用来自 Golang Market 的非常棒的 gopher 毛绒玩具,结合了一个 3D 打印的定制头盔。

有两种不同的毛绒颜色,还有十种不同的头盔颜色。我还没有决定有多少种不同的 LED 颜色……我还没告诉大家他们可以选择 LED 颜色。这会是活动结束后的一个惊喜。它内置了一个 Adafruit Circuit Playground Express 微控制器,这是 Adafruit 公司出品的一个非常酷的圆形微控制器。这家公司总部位于纽约,在硬件和软件方面都做了非常创新的事情。

在头盔里有一个 RGB LED 阵列,包含 15 个不同的 LED,每一个都可以单独控制它的 RGB 颜色。我们还有一个可以弹簧式闪烁的 LED,它真的可以弹簧式闪烁。你可以“嗡嗡”地弹动它,它仍然可以工作。

Mat Ryer: 这是我最喜欢的部分。如果你去 Gopherbot.com,有一个 gif,我猜是你做的,Ron,它展示了这个项目,展示了小 Gopherbot。然后其中一个场景就是剪辑到 Ron 在弹他的头顶上的弹簧。这太精彩了,所以你一定要去 Gopherbot.com 看一看。

Ron Evans: 那是最具工程挑战的部分---一个可以安装在弹簧上的 LED,还能够在弹簧弹动时继续闪烁。

Mat Ryer: [笑] 是啊。我是说,作为软件开发的任务要求来说,这个肯定是其中之一。你看,我真的很高兴这件事能发生,因为我看过一些关于 TinyGo 的演讲,每当软件触及现实世界时,我就非常兴奋……因为我基本上整个职业生涯都是做网页开发的。所以当我听说 TinyGo 时,我就在想:“我该怎么玩这个呢?我希望有一个套件,我可以把它组装起来,开始实际编写代码,做点事情……”然后,当然,Gopherbot 就出现了,这正是它的用处,不是吗?

Ron Evans: 没错。这个套件包括 LED 头盔、弹簧、电路板和一个可以连接的背包……电路板内部有很多传感器。有一个光强度传感器,有一个加速度计,可以检测 X、Y、Z 轴的运动,或者检测它被移动的情况……还有一个内置的 MEMS 麦克风,使用 I2S 接口进行声音录制。还有一个数模转换器,它有一个小小的扬声器……它还配有九个 NeoPixel LED,你可以单独控制它们的颜色……

我们在 Furriebot 的 Instagram 上发布了很多视频,我们展示了不同的场景---看起来好像是经过滤镜处理的,但实际上没有一张照片是经过滤镜处理的。它们都是用我的安卓手机拍摄的,只是角度不同。有些照片是和一些著名的机器人或玩具、我的收藏品一起拍的……我真的很喜欢玩具。

Mat Ryer: 那个 Instagram 账号是什么来着?

Ron Evans: Furriebot

Mat Ryer: 我很惊讶这个账号名字居然还没被用掉。所以你可以编写 TinyGo 代码,然后将其刷到 Gopherbot 上吗?是这样的吗?

Ron Evans: 完全正确。你在电脑上编写代码,编译它,然后将代码传输到微控制器上,也就是那个小小的 Circuit Playground Express 板上。从那时起,你可以断开与电脑的连接,因为所有的 Go 代码都在那个小电路板上运行。

例如,如果你想做一个类似于电子宠物的小玩具,带着它到处走,或者你想去参加一个聚会,并希望它的面罩能跟随音乐的节奏闪烁,或者在电池上运行---你可以做到这些。Gopherbot 本身没有内置无线通信功能,但你可以很容易地添加它。

我在 FOSDEM 演讲中展示的一个演示是将一个 ESP8266 Wi-Fi 芯片连接到微控制器上,这样它就有了 Wi-Fi 功能。但这是我们没有内置的东西。我认为人们不一定想要联网的玩具,但他们确实想要可编程的玩具。

Mat Ryer: 是的。

Ron Evans: 这些是两种不同的东西。一种是“哦,Hello Barbie 在监听你,并将你最深处的想法传回我们的公司总部……”你知道的,这不一定是好事。另一种是你可以自己编程让 Barbie 做不同的事情。Barbie 的创造者可能会说:“哦,如果 Barbie 说了我们不想听的话怎么办?”嗯,这就是重点,不是吗?

Mat Ryer: 是啊,这就是关键。

Ron Evans: 可编程玩具会被用于创造者没有预料到的事情中,而这正是重点;这就是让它们有趣的原因。

Mat Ryer: 对。

Ron Evans: 我们看到过一些令人难过的消息,比如 Anki。Anki 是一个非常酷的机器人创业公司,曾获得非常丰厚的资金。他们几年前在苹果 WWDC 上凭借蓝牙遥控赛车大放异彩……但他们突然宣布公司解散了,这太可惜了。令人遗憾的消息。但我认为他们产品的问题之一是他们制造了不可编程的玩具。那些玩具只能做他们 API 内置的功能,无法超越这些限制,所以所有的软件开发工作都依赖于他们自己的团队。而且,如果你想超越这些功能,你要么完全无法做到,要么就像我们对 Tello 无人机所做的那样,需要付出大量的逆向工程成本。如果不是我们六个人对 Tello 稍微有点疯狂地痴迷于让它飞起来,我们不会投入如此多的时间编写 Lua 脚本和其他内容,那它根本不会存在。

玩具制造商真的有必要这样限制你吗?而实际上,只需要稍微多一些努力---在我看来,所有的玩具都应该是可编程的。所有的玩具都应该是可破解的。特别是如果它本身具有可编程能力,那这些能力就应该被开放出来。至少那样我们可以自信地看着它说:“哦,实际上它并没有监视我们并将信息传回 Gopherbot 总部。” [笑] 它是可编程的;你可以自己添加这种功能,但这不是我们打算做的事情。

Mat Ryer: [笑] 没错。但你说得对,实际上我认为这适用于所有东西。让东西变得可破解,这样人们可以拿来玩弄---这才是乐趣所在,不是吗?这就是其中的所有乐趣。对于玩具来说,我小时候也是这样,玩了一段时间之后,可能是几个月,我肯定会找螺丝刀,因为我想知道里面发生了什么。所以我们无论如何都会这么做;他们还不如让我们这么做。

Ron Evans: 是啊,没错。我打赌,你的玩具有多少能撑过一个小时没被拆开?我们得问问你的父母才能确定。[笑] 我敢打赌,几乎没有几个能在头几天里幸免于被拆开,因为它们本来就不应该这样,对吧?如果它们真的说:“是的,这个玩具可以被拆开并重新装好,还能继续工作”,那不是很棒吗?

Mat Ryer: 是的。另一方面,反例就是乐高。乐高如果不去搭建和改装,它就是垃圾。否则它只是一袋砖块。所以是的。

Ron Evans: 没错。踩上去还危险。

Mat Ryer: 没错。实际上,在我们国家,唯一比它更糟糕的东西就是英国的电源插头。

Ron Evans: 哦,是的。

Mat Ryer: 它们本质上非常危险。它们天生就是尖刺朝上的,所以……[笑] 我们很多人都有踩到插头的惨痛记忆。顺便说一下,Ron,这很有趣……Justin Clift 在 Slack 上---顺便说一下,听众们,如果你们在听直播,你们可以加入 Gopher Slack 或 #gotimefm 频道……Justin Clift 在那边,他刚刚在讨论 WebAssembly,有人试了一下,用 TinyGo 编译的 2.5 MB Go WebAssembly 二进制文件只有 575 字节,这挺不错的。

Ron Evans: 是的,我们说过我们并不是想接管 Go 的所有用例,这是真的,但可能有一些我们可以做得更好一点(双关)。我现在可以同时说 Tiny 和 Go 的双关语,理想情况下在同一句话里。

Mat Ryer: 哇,这太棒了。

Ron Evans: 这是三重关语。很少有人尝试,偶尔有人成功,但我看看能不能做到。

Mat Ryer: 偶尔成功的只是双关。

Ron Evans: 是的,没错。WebAssembly 非常有趣。对于那些完全不熟悉它的听众来说---虽然我会感到惊讶---WebAssembly 是一种新的尝试,旨在创建一个新的网页运行时环境,让代码在网页浏览器中能够访问更多本地功能,从而提高处理速度,使其更高效。它并不是为了取代 JavaScript,而是为了多种原因增强它,性能无疑是其中非常重要的一点,尤其是在很多情况下。

有许多语言能够编译成 WebAssembly,实际上它是一种设计用于在沙箱中执行的指令集。LLVM 是编译工具链---你可能还记得,TinyGo 就是使用它来生成代码的……LLVM 可以生成 WebAssembly 作为它的后端。

我们有几个人专注于 WebAssembly 的部分。我对它的了解出乎意料地少,我还在学习。Justin Clift 正在研究这个,Johan Brandhorst 最近在 DotGo 上做了一个很棒的演讲,其中提到了 TinyGo,现在他也是我们 TinyGo 团队的一员,在帮助确保 TinyGo 在 Web 上的安全性。

回到开始---Go 很棒,Go 很强大,但 Go 太大了,下载一个 1 兆的东西到你的移动设备上,这基本上是行不通的,尤其是当我们谈论远程地区时,带宽非常昂贵且有限,甚至可能根本无法实现。

那么,如果我们能够将 Go 代码编译成可以在 WebAssembly 中执行的代码,并且能够利用更小的可执行文件,同时保留我们喜欢的 Go 的语法和功能---哇,对于如此小的东西来说,这真是一个巨大的进步。所以这是 TinyGo 的一个重要发展领域。微控制器是一个,WebAssembly 是另一个,还有两个我想简要提及的。

Justin 在 Slack 频道里说:“用 gzip 压缩后可以降到 408 字节。” 是啊……有时候你会想,“我们还能多省几个字节吗?” 你得节省几个字节,因为你可以……但这也是重要的。我们现在对技术的使用已经非常宽松了。我们对技术的过度使用是惊人的。

作为一个年轻程序员,我曾经看到过最令人惊叹的事情之一是在 Commodore 64 上的 GEOS。它实际上是一个完整的窗口操作系统,运行在 64k 的 RAM 中。简直不可思议。我有一次见到了其中一位创造者,我完全是个粉丝。我说:“你们是怎么做到的,简直太不可思议了。” 然后我想到我们现在浪费了多少资源,想着“哦,没关系,计算是廉价的。” 但计算并不廉价。只是成本被外部化了。发电的成本,冷却的成本---这些成本很多都被转移到了环境,而不是由任何人真正支付。

随着时间的推移,计算效率将变得更加重要,一方面是因为资源的利用,另一方面是因为我们对计算的需求也在增加。我们希望机器学习算法能够在边缘计算设备上执行,在那里它们可以真正发挥作用。尽管 Machine Box 做的东西很酷,但它对无人机的碰撞避免算法没有帮助,因为我们必须在无人机上直接执行该算法,否则如果那天 Verizon 网络不佳,它就无法发挥作用,对吧?

Mat Ryer: 是的,没错。

Ron Evans: 这些东西都有它们的用例。这不是一个非此即彼的选择。这是一种错误的二分法。TinyGo 的使命是说:“有些地方 Go 还没有去过。我们希望 Go 能够去到那里。” 因为任何足够成熟的语言都有不止一种实现。看看 Python 有多少种实现。看看 C 有多少种编译器。C++ 有多少种实现。JavaScript 有多少种实现。而 Go 只有一种,直到 TinyGo 出现。我见过一些其他尝试,但没有一个真正说要“使用 Go 本身来编写一种新的 Go,它仍然是 Go,但能够做一些重要的事情。”

最后一个大话题是未来。未来……[唱歌] “未来啊啊啊啊啊……” [笑声] 我们现在正处于我所见过的计算最激动人心的时代。不是开玩笑。为什么?因为我们正处于定制硅片的寒武纪大爆发的第一步。

我们多年来一直在讨论:“如果你能创建专用芯片,将某些类型的处理变得更高效、更便宜,那不是很棒吗?” 但没有实际的方法来做到这一点。你需要找到那些掌握深奥知识的芯片设计师,还要支付巨额的许可费用给那些提供帮助的公司---不仅仅是因为他们让你这么做,还因为专利问题,但你也确实做不到没有他们的帮助。比如,“我要造一颗新芯片。” 十年后,“是的,我们要造一颗新芯片。” “那你进展如何?” “嗯,这真的很难做到。”

RISC-V 是一种技术,有些人听说过,有些人只是听到这个流行词……它实际上是一个开源的硅片设计集,这样你就可以像我们能够构建自定义操作系统那样构建自己的定制芯片;比如从 Linux 的某些部分创建自己的 Linux 发行版---我们将能够用定制硅片做同样的事情。所以 TinyGo 将能够运行在 RISC-V 上,因为它使用的是 LLVM 后端……我正在等待我的 Sci-Fi 原型板到货。我本来打算在告诉任何人之前就运行一个演示,但我太兴奋了,因为很多人都在用 RISC-V 做一些很酷的事情,而我还在等我的货……我已经等不及了,我必须说出来。

Mat Ryer: [笑声]

Ron Evans: 但如果你考虑一下这意味着什么,尤其是在为解决那些因为所需的传感器太多而太昂贵的问题,或者因为传感器所需的位置太远而太难解决的问题时……你知道我在说什么问题,当然是气候变化。那位著名的 Bret Victor,以在编程界面方面的惊人实验而闻名……如果你还没有关注 Dynamicland 的最新进展……这是一个非常糟糕的名字,但技术却是不可思议的,通过使用物理对象进行现实世界编程……他们做的事情简直令人震惊。

Mat Ryer: 哇。

Ron Evans: 我简直不敢相信我现在忘了那个名字……尴尬。不过他有一篇很棒的博文,叫做《我们作为技术人员能为气候变化做些什么》。显然,这是我们时代最重要的问题之一,因为它会对我们生活的物理世界产生影响。作为技术人员,我们能为此做些什么?他列出了不同的技术,无论是更好的可视化和科学分析技术……这离我有点远,因为我不是科学家(虽然我认识一些),而且我没有经过同行评审的论文……但我是个技术人员,所以其中一个领域是我们需要更好的技术来控制物理世界,监控和控制物理世界,作为我们对这些不断变化的环境条件的广泛应对的一部分。

为了做到这一点,我们首先需要能够重新利用现有的很多技术。现在有很多芯片---我们需要能够回收它们。这就是为什么我花了很多时间研究 TinyGo 的 AVR(Arduino)功能的原因之一。这是一种非常旧的架构,功能非常有限,但有很多芯片和开发板可以被重新利用来做我们需要做的有用的事情。

每一种足够先进的技术起初都是以玩具的形式出现的。这是对 Chris Dixon 的一句话的改编……但是你从玩具概念开始,玩弄这些概念。这样,当你说“我在玩它”时,你就不用真正做任何有用的或工作的东西,对吧?“我在玩 WebAssembly。” 这意味着“我不是真的要负责写一个能工作的 WebAssembly 程序。我只是在尝试。”

Mat Ryer: 是啊。

Ron Evans: 所以没有压力。我可以自由地学习和实验而不需要为任何特定的事情负责。在某个时候,我要么把它放到一边,玩别的东西,要么我跨越了从“你还在玩吗?”到“是的,我现在在用它做 X”的鸿沟,其中 X 是一些有用的任务。这就是一切的开始。所以玩具是我们作为开发者适应这些新事物的第一步---回到 Matt 刚才说的……你是一个网页开发者;你对网页技术有很多了解,并且深入思考过它……现在你可以将这些原则应用于边缘计算,并解决其中的一些问题。我们需要的开发者将来自于这样的工作群体。不会突然冒出一群神秘的机器人专家,他们已经完全成型,准备就绪。我们必须与我们现在拥有的人类合作。我们就是救援队,别无他法。

Mat Ryer: [笑] 这就是为什么我喜欢 Gopherbot 项目,因为它给了每个人这样的机会。它给你一个可以玩的东西,并且它提供了你所需要的一切。所以我建议大家去 Gopherbot.com 看看。

Ron,你会去 Gophercon 吗?

Ron Evans: 是的,实际上今年的 Gophercon 将是有史以来最大最好的一次。它将在圣迭戈举行,这个地方……

Mat Ryer: 很热。

Ron Evans: ……在南加州,我家人住在那儿,所以你可能会见到我的父母……但更重要的是,在社区日---Gophercon 是一个为期四天的会议。第一天是教程和工作坊。会有一些非常棒的工作坊。Mark Bates 会做一个很酷的关于测试的……Mat,你有做什么吗?

Mat Ryer: 没有,我会演讲。

Ron Evans: 我知道 Johnny 会做一个,Bill Kennedy 也会做一个……真的有很多很棒的培训。然后会议的第二、第三天是所有的演讲,满满的学习机会和互动……但会议的最后一天绝对是最棒的。如果你没有计划留下来参加最后一天,那你就错过了。这是社区日,每年我们都会举办硬件黑客活动;我们称之为“Gobots 和其他飞行物”,而且年年越来越大。实际上,它占用了越来越多的空间。他们给了我们一个双倍大的区域。我们有一个无人机专区,你可以用 Go 驱动的无人机飞行……

去年我们做了 GopherCar,这是基于 Donkey Car 的,这是一个使用树莓派的自驾车项目,Go 驱动,使用摄像头,结合了 Gobot 和 GoCV……我们有很多套件可以进行传感器黑客操作,都是由不同的赞助商提供的。所以你不需要带任何硬件;我们会带来很多很多硬件。我们有大量的赠品、活动、乐趣……所以这绝对非常棒,如果你不去,那你一定会后悔……因为不仅你会学到一些东西,更重要的是你会玩得很开心。

Mat Ryer: Ron,我突然想到一个问题。当我们谈论 TinyGo 在浏览器中运行,或者在这些微控制器上运行时,垃圾回收是如何处理的?因为这也是让这些二进制文件变得又大又胖的原因之一---这是由于运行时的存在。那么,TinyGo 是如何处理垃圾回收的呢?这是我一直想知道的一个技术问题。

Ron Evans: 目前 TinyGo 对垃圾回收的支持相对有限。在一些架构上,比如基于 ARM Cortex 的微控制器,TinyGo 实现了一种非常简单的标记清除(mark-and-sweep)垃圾回收。在 AVR 微控制器上,比如 Arduino,则完全没有实现垃圾回收。这部分是因为 8 位处理器的限制,我记得好像是数据和指令不在同一个内存空间,或者类似的原因。AVR 架构有一些奇怪的地方,可能我记错了,但目前还没有实现垃圾回收。

不过,事实证明你可以在没有垃圾回收的情况下编写有用的代码。一种方法是使用包含所需内存的结构体,另一种是使用全局变量。我们还实现了一些像环形缓冲区这样的东西,这些在进行各种 IO 操作时非常有用。所以,即使没有垃圾回收,你也可以编写有用的代码。不过,TinyGo 的目标之一是支持垃圾回收,并且能够支持多种垃圾回收算法和后端。

我们想做的一件事是能够将 TinyGo 插入系统中,以便能够使用实时操作系统的内存分配器和释放器。这样的话,如果你想从 Go 代码中调用一些 C 代码---顺便提一下,编译和链接使用 LLVM 的一个大优势是,我们不需要像主流 Go 实现中的 cgo 那样的所有东西;对我们来说,它们最终只是函数指针,因此我们能够在 Go 和 C 之间进行更快的调用,因为在 TinyGo 中,最终它们都是二进制代码。

Mat Ryer: 这真是太酷了。

Ron Evans: 目前的 TinyGo 版本,我们花了很多时间简化安装过程,去除了一些额外的依赖项。我们没有完全去掉所有依赖项,如果你想为基于 ARM Cortex 的微控制器编译代码,你仍然需要 Clang 编译器。但下一个 TinyGo 版本将消除这一点,所以你将能够只使用 TinyGo 编译器编译 Go 和 C 代码。

这将把我们带入一个非常激动人心的领域---你需要一些现有的 C 代码来运行这些不同的微控制器上的代码。有些是实时操作系统的 SDK,例如 Zephyr (一个小型的实时操作系统,用于资源受限的嵌入式互联设备,主要是微控制器,支持多种架构)、FreeRTOS 和 Riot OS 等等。

另一个例子是有一些闭源的代码,但它们提供开放的 API,比如 Nordic Semiconductor (挪威的一家芯片公司,成立于1983年) 的蓝牙低能耗(Bluetooth Low Energy)实现,比如 BBC micro:bit。这是一个非常酷的小板子,几年前英国的所有孩子都得到了这些板子,现在还有很多……Mat,你可能从邻家的某个小孩那里顺手拿了一个吧……

Mat Ryer: [笑] 不,我是合法途径得到的。

Ron Evans: 是的,先生,我相信你是合法得到的。 [笑声] BBC micro:bit 内置了 Nordic Semiconductor 的 nRF51 芯片,包含了完整的蓝牙低能耗协议栈,如果你能调用这些闭源代码库。

我们的大目标之一是能够将所有这些代码结合在一起,执行非常小且快速的代码,同时仍然使用 Go 的并发模型……因为我们能够在这些微控制器上运行 goroutine,这本身就非常令人惊讶。比如在我们的 Gopherbot 演示套件中,那个末端发光的 LED 实际上是在它自己的 goroutine 中运行的。

Mat Ryer: 哇哦……真令人惊讶。它竟然支持 goroutine。

Ron Evans: 是的。不过有一些差异和限制。TinyGo 中的 goroutine 实现使用的是 Clang 的协程(coroutines),这是另一种并发执行模块,能够完成我们期望 goroutine 做的大部分工作。我们已经实现了 channels,不过有一些限制,其中最大的一个是我们还没有实现 select 语句。一旦 select 语句实现了,channels 将变得更加有用……不过你现在已经可以使用它们了。此外,还有一个正在开发的分支,里面实现了一些 sync/atomic 的功能,这样你就可以在需要的时候进行 goroutine 之间的同步。

Mat Ryer: 太棒了。如果有听众想要参与进来,或者他们对实现 channels 或 select 功能感兴趣,他们应该如何提供帮助?你们现在在寻找什么样的帮助?

Ron Evans: 我们在寻找各个经验水平的 Tiny gophers,尤其是那些没有太多经验的人。如果你从来没有尝试过这些东西,我们特别需要你,因为你是帮助我们改进入门流程的完美人选,让更多人更容易地开始使用 TinyGo,尝试制作各种有趣的设备和小工具,或者工业系统之类的东西。

另一方面是为 TinyGo 本身做出贡献。我们已经花了不少时间改进了从源码安装 TinyGo 的过程。我们有一些 make 任务,可以让你从源码安装最新的 LLVM,这让想深入研究并为 TinyGo 添加新功能的人更容易上手。

其中最有趣的贡献者之一是 Carolyne Van Slyk,她一直在为 TinyGo 添加测试功能。这样我们就可以用 TinyGo 来执行测试代码。这将非常有用,而了解 Go 测试的内部实现也是非常有趣的。

Mat Ryer: 嗯,我想是的。

Ron Evans: 所以没有比通过构建一些使用相同工具的东西来学习它们工作原理的方式更好的方法了。

Mat Ryer: 是的,太棒了。

Ron Evans: 我还想提到一点---对于那些想真正理解底层机制的人,Ayke 昨天在 Aykevl.nl 上发表了一篇非常棒的博文,名为 LLVM from a Go perspective。它通过一些非常简单的 Go 代码示例,展示了这些代码如何首先转换为 Go 的 SSA 形式,然后转换为 LLVM IR 形式。这是一个非常好的方式来了解---不管你是对帮助 TinyGo 做贡献感兴趣,还是只是想了解现代编译器是如何在底层工作的---这篇博文都非常棒。我一直在反复阅读这篇文章,每次都感到“哦,原来是这样”。

Mat Ryer: 这也会是一个很好的演讲题材。

Ron Evans: 每个人都认为我做了所有的艰苦工作,但我认为其他人才是做了所有的艰苦工作。我相信这被称为“良性循环”。当然,开源的力量就在于我们一起合作,不管是直接通过代码贡献,还是间接地通过反馈“我试了一下,但没成功。这是哪里出了问题……”

几年之前,我在伦敦听过一个非常棒的演讲,演讲者是一位涉及 IoT 相关工作的女性。我不记得她的名字了,但她提出了一个非常棒的观点,关于那些刚刚入门的人在进行电路连接时的困惑。一般来说,红线代表正极,黑线代表负极,对吧?

Mat Ryer: 对。

Ron Evans: 但如果你不知道这一点,你就不会意识到颜色是有意义的;这其实是一个约定俗成的规则。没人会向你解释这一点。这是非常关键的信息,所以当你刚开始时,你甚至不知道什么信息是关键的。所以,如果你遇到困难,试图让某些东西正常工作,你并不孤单,我们需要知道这些问题,这样我们可以帮助其他遇到同样问题的人……因为有时候我们可能知道得太多了。我们已经全部安装好了,可能我们根本不知道安装过程中的问题。

Mat Ryer: 是的,这确实是个好观点。所以无论你有没有经验,或者是否觉得自己准备好做出贡献,都不妨看看,因为你可能会感到惊讶。Ron,你给出的建议真的很棒。TinyGo 和 Gopherbot 这样的项目很棒的一点是,贡献者和维护者都对其他人非常友好和包容;这对社区来说非常重要。

我们之前在节目中也提到过一个问题,就是在面试时,我总是给出的建议之一是,如果你没有其他可以在面试中谈论的内容,那就参与一些开源项目。TinyGo 是一个很好的例子,因为它还涉及到现实世界的应用,这总是一个有趣并令人兴奋的话题。所以,这也是我给那些入门级或者刚开始学习编程的人的建议。

Ron Evans: 这绝对是真的……而且你还需要一个优势。这个优势可能是你让某个硬件上的 LED 亮了起来。对这个领域不熟悉的人会对你的新技能感到印象深刻,甚至你自己也会对自己的新技能感到惊讶。

Mat Ryer: 是的,我会为此感到惊讶。

Ron Evans: 是吧?这会让你有信心去学习其他东西,因为事实证明,大部分物联网、机器人技术和无人机技术只是在正确的时间打开或关闭设备,仅此而已。如果你能做到这一点,你就成功了。

Mat Ryer: 你让这一切听起来如此简单,Ron。

Ron Evans: 在激光射向你之前先把它关闭。这是我的座右铭。

Mat Ryer: 顺便说一句,我知道红线是正极,因为所有的詹姆斯邦德电影里都说得很清楚……所以我们应该没问题吧。

Ron Evans: 还记得那部电影《深渊》吗?他们在海底,只有一个发光棒,无法分辨哪根线是哪种颜色,该剪哪根线?

Mat Ryer: 真是噩梦啊……

Ron Evans: 这就是我的人生故事……我的人生故事。 [笑声] 我该剪哪根线?不知道,那就全剪了吧! [笑声]

Mat Ryer: 是的。我总是想,当他们在拆除炸弹时,总是那种“哦哦哦……”的紧张时刻。我心想,软件基本上从来不管用,所以它大概也不会管用……随便弄弄吧,应该没事。它不会爆炸的。这是我的建议,不过我可不在拆弹工作中。

Ron Evans: 是的,最好在连接激光之前先试试 LED。

Mat Ryer: 是啊,没错。

Ron Evans: 这是我曾经得到的一个好建议。

Mat Ryer: 非常好。

Ron Evans: 确保安全……你懂的,确保安全。 [笑声]

Mat Ryer: 你觉得那根弹簧上的 LED 有可能会变成激光吗?还是它会变成眼睛……?

Ron Evans: 我觉得有人很可能会这么做,但我自己可能不会,因为它会直直地向上射,可能会射到某人的眼睛,我就是那个人。

Mat Ryer: 是的。但这样死去也挺酷的,不是吗?

Ron Evans: 别忘了把激光对准地面。

Mat Ryer: Ron,我敢肯定你最终会被你自己创造的东西搞死,这一点毫无疑问。

Ron Evans: 是啊,这世上有两种人---一种是建造机器人的人,另一种是害怕机器人的人。所以你得决定站在哪一边。

Mat Ryer: 是的。

Ron Evans: 但你更愿意机器人瞬间让你灰飞烟灭,还是希望它在识别你是它的创造者时稍微迟疑几毫秒,然后再让你灰飞烟灭?我更倾向于后者。

Mat Ryer: 是的,所以我们确实需要 Machine Box!

Ron Evans: 没错,没错。你总有一天会需要它的。

Mat Ryer: 是啊。对于那些正在建造未来可能会统治世界的机器人的人,你有什么建议吗?毕竟你也在做这件事,对吧?

Ron Evans: 嗯,你知道的,世界很大,容得下机器人帝国。火星目前是我们太阳系中唯一一个完全由机器人居住的星球,所以它是一个和平美好的地方……

最重要的一点是---嗯,有两部分。第一部分是很多人对这些事情知之甚少……所以不要害怕去尝试和探索。你不需要最新最酷的硬件来做这些。事实上,任何你能够找到的旧 Arduino 都可以。如果你有硬件闲置在抽屉里,把它拿出来给别人用,不要等它变成危险废物,这样他们可以学习……但不要害怕去尝试,因为这也是一种有趣酷炫的学习方式。

另一方面是---不要忘记我们为什么要做这些事情。我们可以构建一个充满杀手机器人的世界,也可以构建一个充满有帮助的机器人的世界。这完全取决于我们。现在,称之为“人工智能”其实不太准确,更像是“智能应用程序”。它们在非常特定的领域内具有一些小范围的智能,但并没有任何通用智能……而且也没有迹象表明我们很快就会获得通用智能。

如果你看过 Roger Penrose (英国数学物理学家、哲学家,诺贝尔物理学奖得奖者) 的书,你会知道我们永远不会实现通用智能。但这并不意味着它没有用处。它仍然非常有用,但我们必须非常小心,因为我们作为人类带入这些计算公式中的偏见可能会对现实世界产生直接影响。我们真的需要在构建这些系统之前、而不是之后思考“我们应该构建它吗?”

同样的技术可以用来做好事,也可以用来做坏事,有时是无意的,有时是被不法分子利用的。所以我们在将这些系统部署到现实世界时,必须非常谨慎,因为它们可能会带来我们意想不到的后果。

此外,物理系统一旦安装,往往会比原计划存在更长的时间。任何在工厂或零售店工作过的人都知道,他们安装了一些东西后,它们往往会超期服役。部分原因是他们不愿意更换,另一部分是他们忘记了它还在那里……所以当我们将设备部署到世界的边缘时,我们真的需要考虑这一点。至少在服务器机房里我们还能挨个机架检查设备“这在做什么?那在做什么?”但在物理世界中的设备,我们甚至可能不知道它们具体安装在哪。我们已经见过很多看似无害的设备被利用来做坏事,比如 Mirai 僵尸网络……

所以我们必须在将分布式设备部署出去之前,应用非常严谨的架构和工业化思维,而不是事后才发现“哦,这些设备无法更新了。那太糟了……”这是不可接受的。关于设备开发的伦理问题,必须在开发前就考虑清楚。

Mat Ryer: 是的,这是真的。这确实是个现实问题。好了,节目到此为止了。非常感谢 Deadprogram,也就是 Ron Evans,向我们介绍 TinyGo,并与我们分享他对 Gopherbot.com 的热情。一定要去看看这个项目。别忘了我们的有奖问答。如果你想赢取 Gopherbot 套件,包括所有你需要的零件,这样你就可以编写 TinyGo 代码并创造你自己的 Go 毛绒玩具机器人,那就查看节目笔记中的有奖问答细节吧。你只需要分享这个播客,并发送邮件到 gotime@changelog.com

好了,我们下周见。非常感谢大家!