本篇内容是根据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 工具,那么这会迫使工具开发者,比如 IDA、Ghidra 等,改进对 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: 再见,大家!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。