XZ后门故事:初始分析

2024年3月29日,Openwall OSS安全邮件列表上的一条消息“炸醒”了整个信息安全、开源和Linux社区:XZ出现了一个CVSS评分10.0的恶意后门。

这个后门库的特殊危险在于OpenSSH服务器进程sshd使用它。在多个基于systemd的发行版上(包括Ubuntu、Debian和RedHat/Fedora Linux),OpenSSH会以一种方式与systemd通信,因此依赖于这个库(注意Arch Linux和Gentoo不受影响)。攻击者的最终目标很可能是为sshd引入其他人无法使用的远程代码执行功能。

不同于在Node.js、PyPI、FDroid和Linux内核中看到的其他供应链攻击,这次事件是一个多阶段的操作,几乎成功地在全球范围内破坏了SSH服务器。

Liblzma(XZ包的一部分)库中的后门分为两个阶段。构建基础架构的源代码被稍微修改了一下(通过引入一个额外的文件build-to-host.m4),以提取隐藏在测试用例文件(bad-3-corrupt_lzma2.xz)中的下一阶段脚本。这些脚本依次从另一个测试用例文件(good-large_compressed.lzma)中提取恶意二进制组件,该文件在编译过程中与合法库链接,并将其发送到Linux存储库。

 

事件时间轴

2024.01.19:XZ网站被新的维护者(jiaT75)移至GitHub页面;

2024.02.15:“build-to-host. m4”被添加到.gitignore;

2024.02.23:引入了两个包含恶意脚本阶段的“测试文件”;

2024.02.24:XZ 5.6.0版本发布;

2024.02.26:提交入CMakeLists.txt破坏Landlock安全特性;

2024.03.04:后门导致Valgrind出现问题;

2024.03.09:更新两个“测试文件”,修改CRC功能,Valgrind问题被“修复”;

2024.03.09:XZ 5.6.1版本发布;

2024.03.28:漏洞被发现,通知Debian和RedHat;

2024.03.28:Debian回退XZ 5.6.1到5.4.5-0.2版本;

2024.03.29:一封电子邮件在OSS-security邮件列表中发布;

2024.03.29:RedHat宣布发布的Fedora Rawhide和Fedora Linux 40测试版中有植入后门的XZ;

2024.03.30:Debian关闭构建并启动重建进程;

存在后门的发行版

xz-5.6.0

MD5

c518d573a716b2b2bc2413e6c9b5dbde

SHA1

e7bbec6f99b6b06c46420d4b6e5b6daa86948d3b

SHA256

0f5c81f14171b74fcc9777d302304d964e63ffc2d7b634ef023a7249d9b5d875

xz-5.6.1

MD5

5aeddab53ee2cbd694f901a080f84bf1

SHA1

675fd58f48dba5eceaf8bfc259d0ea1aab7ad0a7

SHA256

2398f4a8e53345325f44bdd9f0cc7401bd9025d736c6d43b372f4dea77bf75b8

初始感染分析

XZ git存储库包含一组测试文件,用于测试压缩/解压缩代码以验证其是否正常工作。这个名为“Jia Tan”或“jiaT75”的账户提交了两个初看起来无害的测试文件,但这些文件最终却成为了植入后门程序的引导程序。

两个相关文件分别为:

bad-3-corrupt_lzma2.xz (86fc2c94f8fa3938e3261d0b9eb4836be289f8ae)

good-large_compressed.lzma (50941ad9fd99db6fca5debc3c89b3e899a9527d7)

这些文件旨在包含shell脚本和后门二进制对象本身。但是,它们隐藏在格式不正确的数据中,只有攻击者知道如何在需要时正确地提取它们。

阶段1:修改的build-to-host脚本

当XZ版本准备好后,官方Github存储库会发布项目的源文件。最初,存储库上的这些版本除了包含恶意测试文件之外,是无害的,因为它们没有机会执行。然而,攻击者似乎只在发行版来自https://xz[.]tukaani.org(由Jia Tan控制)时,添加了引导感染的恶意代码。

不幸的是,大多数发行版都使用这个URL。如此一来,在下载时就会附带一个包含恶意代码的“build-to-host.m4”文件。

build-to-host.M4(c86c8f8a69c07fbec8dd650c6604bf0c9876261f)在构建过程中执行,并通过执行下述代码来修复并解压缩添加到测试文件夹的第一个文件。

1713316497_661f22912513c4265c74b.png!small?1713316497987

【build-to-host.m4中去混淆的一行代码】

这行代码使用tr命令替换了bad-3-corrupt_lzma2.xz中的“损坏”数据,并将输出传送到Xz -d命令,以便对数据进行解压缩。解压后的数据包含一个shell脚本,该脚本稍后将通过/bin/bash执行,并由这个.m4文件触发。

阶段2:shell脚本注入

恶意.m4文件注入的恶意脚本会验证它是否正在Linux机器上运行,并且是否正在预期的构建过程中运行。

1713316511_661f229f35afaeb234f4c.png!small?1713316511593

【注入的脚本内容】

为了执行下一阶段,它使用了good-large_compressed.lzma,该文件确实用XZ正确地压缩了,但在解压缩的数据中包含垃圾数据。

垃圾数据清除过程如下:eval函数执行head pipeline,每个head命令要么忽略下一个1024字节,要么提取下一个2048或724字节。

这些命令总共提取了33492字节(2048*16 + 724字节)。然后,tail命令保留文件的最后31265字节,并忽略其余字节。

之后,tr命令对输出应用一个基本替换来消除混淆。第二个XZ命令将转换后的字节解压缩为原始的lzma流,然后将结果传输到shell中。

第三阶段:后门提取

最后一个阶段的shell脚本会执行许多检查,以确保它在预期的环境中运行,例如是否将项目配置为使用IFUNC(这将在下一节中讨论)。此阶段执行的许多其他检查包括确定GCC是否用于编译,或者项目是否包含稍后脚本将使用的特定文件。

1713316615_661f23072fc93960fdb0d.png!small?1713316621641

在这个阶段,它会提取后门二进制代码本身——一个对象文件,当前隐藏在相同的good-large_compressed.Lzma文件但不同的offset函数中。

下述代码处理的就是这个问题:

1713316626_661f2312556c5adef83cd.png!small?1713316626987

【最后一个脚本阶段使用的部分命令】

提取过程通过一系列命令进行操作,每个命令的结果作为下一个命令的输入。格式化后的一行代码如下所示:

1713316636_661f231caaa0d3af3625b.png!small?1713316637282

【格式化后的一行代码】

最初,文件good-large_compressed.lzma是使用XZ工具本身提取的。接下来的步骤包括使用“eval $i”函数调用一连串的head calls(与阶段3提取相同)。

然后使用自定义的类RC4算法来解密二进制数据,其中包含另一个压缩文件。这个压缩文件也可以使用XZ实用程序提取。随后,该脚本使用预定义的值从解压缩数据的开头删除一些字节,并将结果保存为liblzma_la-crc64-fast.O(这是链接过程中使用的后门文件)至磁盘。

最后,该脚本修改liblzma中crc_x86_clmull .h文件的is_arch_extension_supported函数,用_get_cpuid替换对__get_cpuid函数的调用(去掉了一个下划线字符)。这个修改允许将它链接到库中(我们将在下一节中更详细地讨论这个问题)。整个构建感染链可以总结为以下方案:

1713316639_661f231f4837889f41708.png!small?1713316640070

二进制后门分析

隐蔽加载场景

在原始的XZ代码中,有两个特殊的函数用于计算给定数据的CRC:lzma_crc32和lzma_crc64。这两个函数都以IFUNC类型存储在ELF符号表中,这是GNU C库(GLIBC)提供的一个特性。IFUNC允许开发人员动态选择要使用的正确函数。此选择发生在动态链接器加载共享库时。

XZ使用这种方法的原因是,它允许确定是否应该使用lzma_crcX函数的优化版本。优化的版本需要来自现代处理器(CLMUL、SSSE3、SSE4.1)的特殊功能。这些特殊功能需要通过发出cpuid指令来验证,这是使用GLIBC提供的__get_cpuid wrapper/intrinsic来调用的,在这一点上后门利用了加载自身的优势。

该后门程序被存储为一个对象文件,它的主要目标是在编译期间链接到主可执行文件。对象文件包含_get_cpuid符号,因为注入的shell脚本从原始源代码中删除了一个下划线符号,这意味着当代码调用_get_cpuid时,它实际上调用了它的后门版本。

1713316670_661f233e3895acc08d847.png!small?1713316673205

【后门代码入口点】

后门代码分析

初始后门代码被调用了两次,因为lzma_crc32和lzma_crc64都使用相同的修改函数(_get_cpuid)。为了确保对此进行控制,开发人员又创建了一个简单的计数器来验证代码是否已经执行。当lzma_crc64 IFUNC调用_get_cpuid,看到指示函数已被访问的计数器值为1,并启动最后一步重定向到此恶意软件的真正入口点时,实际的恶意活动就开始了。

1713316681_661f2349a090a7ac4a4d0.png!small?1713316682302

【后门构造过程】

要构造恶意代码,后门程序首先需要构造两个结构,这些结构包含有关当前运行进程的核心信息。首先,它使用硬编码的偏移量来定位全局偏移表(GOT)地址,并使用该信息来查找其中的cpuid指针。

1713316723_661f2373063f2f7873f66.png!small?1713316724222

【GOT修改代码】

GOT包含cpuid wrapper等的偏移量。然后,后门会交换指向主恶意软件函数的指针,并像调用cpuid一样调用它。

核心行为分析

该后门程序的主要目标是成功地钩住(hook)特定的功能,使其能够监视到受感染机器的每个连接。目标函数如下所示:

目标函数

描述

RSA_public_decrypt

被libcrypto用于解密由私钥签名的密文

EVP_PKEY_set1_RSA

被libcrypto用于设置给定上下文的RSA密钥

RSA_get0_key

被libcrypto用于检索RSA密钥

然而,对于后门程序来说,钩住所有这些函数并不是一项简单直接的任务。它涉及访问许多内部结构并操纵特定于进程的结构,直至达到目标,并且它还需要尽可能地隐蔽,这意味着恶意代码包含多个检查以避免被分析。

执行环境检查

它首先解析ld-linux(动态链接器),其主要负责动态解析内存中的符号地址。它还提取有关环境的各种信息,并验证后门正在运行的当前进程是否符合下述执行标准:

  • 当前进程为/usr/bin/sshd;
  • 检查终止开关是否存在;

它从argv[0]中提取当前进程的名称,并解析所有进程环境变量,以查看其内部字符串表中是否存在这些变量。如果找到匹配项,或者进程不是sshd,恶意软件就会停止执行。

1713316780_661f23ac429d64c8c8a51.png!small?1713316781039

【流程环境检查】

它自身的表中唯一的变量是yolAbejyiejuvnup=Evjtgvsh5okmkAvj,它在此上下文中充当终止开关。

trie结构

该后门的一个显著特征是使用单一的trie结构进行字符串操作。代码没有直接比较字符串或使用字符串散列来匹配特定的常量(例如,库函数的名称),而是执行一个trie查找,并检查结果是否等于某个常量。例如,ELF文件头的magic value导致trie返回0x300,而系统函数的名称与返回值0x9F8匹配。Trie不仅用于比较:某些使用指向字符串的指针的函数(例如,ssh-2.0)也使用Trie在主机二进制文件中搜索这些字符串,因此在后门的主体中不会有可疑的数据。

trie的实现使用16字节的位掩码,每一半对应于字节输入范围0x00-0x3F和0x40-0x7F,以及2字节的trie叶子节点(leaf node),其中3 bits是标志(方向,终止),其余的留作值(或下一个节点的位置)。

1713316811_661f23cb142b3e14ebd01.png!small?1713316812105

【执行位图(bitmap)匹配的trie查找函数的一部分】

符号解析器

至少有三个与符号解析器相关的例程被该后门用于定位ELF符号结构,后者保存着诸如符号名称及其偏移量之类的信息。所有符号解析器函数都接收要在trie中搜索的密钥。

1713316825_661f23d959d943a81097a.png!small?1713316825782

【符号解析器示例】

其中一个后门解析器函数遍历所有符号并验证哪个符号具有所需的密钥。如果找到了,它将返回Elf64_Sym结构体,该结构体稍后将用于填充保存所有必要函数指针的后门的内部结构。这个过程类似于在带有API散列例程的Windows威胁中常见的过程。

该后门从libcrypto(OpenSSL)库中搜索许多函数,并将其用于后续加密例程。它还会跟踪它能够找到和解决多少函数;这决定了它是正确执行还是应该停止。

另一个有趣的符号解析器滥用lzma_alloc函数,该函数是liblzma库本身的一部分。这个函数可以帮助开发人员使用默认分配器(malloc)或自定义分配器有效地分配内存。在XZ后门的情况下,这个函数被滥用来利用一个假的分配器。实际上,它是另一个符号解析器。用于“分配大小”的参数实际上是trie中的符号键。这个技巧是为了使后门分析复杂化。

1713316856_661f23f8c40e12bdab247.png!small?1713316857293

【使用假分配器结构的符号解析器】

该后门程序会在执行时动态解析其符号;它不一定要一次完成这些操作,或者只在需要使用它们时才这样做。解析的符号/函数范围从合法的OpenSSL函数到用于在机器上执行命令的system等函数。

Symbind钩子

如前所述,后门初始化的主要目标是成功地挂钩函数。为此,该后门程序使用了rtdl-audit,这是动态链接器的一个特性,它允许在链接器中发生某些事件(如符号解析)时通知自定义共享库的创建。在一个典型的场景中,开发人员将按照rddl -audit手册创建一个共享库。但是,XZ后门选择对加载在内存中的已注册(默认)接口执行运行时补丁,从而劫持符号解析例程。

1713316884_661f2414cef43b44d123c.png!small?1713316886195

【Dl-audit运行时补丁】

该恶意构造的结构audit_iface存储在动态链接器内存区域内的dl_audit全局变量中,它包含由动态链接器调用的symbind64回调地址。它将所有符号信息发送给后门控件,后门控件随后会被用于获取目标函数的恶意地址,从而实现hook。

1713316897_661f2421415ec64ef4c75.png!small?1713316897731

【已修改的Symbind回调中的钩子位置】

通过反汇编dl_main和dl_audit_symbind_alt函数,可以获得dl_audit和dl_naudit的地址(其中保存了可用的审计接口数量)。该后门还包含一个用于指令解码的内部极简反汇编器。它广泛使用它,特别是在寻找特定值(如 *audit地址)时。

1713316908_661f242c0a6b018dbbba3.png!small?1713316908561

【Dl_naudit搜索代码】

dl_naudit地址由访问它的dl_main函数代码中的mov指令之一找到。有了这些信息,后门程序就会寻找对内存地址的访问并保存它。

它还会验证获取的内存地址是否与dl_audit_symbind_alt函数在给定偏移量中访问的地址相同。这允许它安全地假设它确实找到了正确的地址。在找到dl_naudit地址之后,它可以很轻松地计算出dl_audit在哪里,因为这两个地址在内存中是紧挨着存储的。

黑客&网络安全如何学习

今天只要你给我的文章点赞,我私藏的网安学习资料一样免费共享给你们,来看看有哪些东西。

 1.学习路线图 

 攻击和防守要学的东西也不少,具体要学的东西我都写在了上面的路线图,如果你能学完它们,你去就业和接私活完全没有问题。

2.视频教程

网上虽然也有很多的学习资源,但基本上都残缺不全的,这是我自己录的网安视频教程,上面路线图的每一个知识点,我都有配套的视频讲解。

内容涵盖了网络安全法学习、网络安全运营等保测评、渗透测试基础、漏洞详解、计算机基础知识等,都是网络安全入门必知必会的学习内容。

 

 (都打包成一块的了,不能一一展开,总共300多集)

因篇幅有限,仅展示部分资料,需要保存下方图片,微信扫码即可前往获取

3.技术文档和电子书

技术文档也是我自己整理的,包括我参加大型网安行动、CTF和挖SRC漏洞的经验和技术要点,电子书也有200多本,由于内容的敏感性,我就不一一展示了。 

 因篇幅有限,仅展示部分资料,需要保存下方图片,微信扫码即可前往获取

4.工具包、面试题和源码

“工欲善其事必先利其器”我为大家总结出了最受欢迎的几十款款黑客工具。涉及范围主要集中在 信息收集、Android黑客工具、自动化工具、网络钓鱼等,感兴趣的同学不容错过。 

还有我视频里讲的案例源码和对应的工具包,需要的话也可以拿走。

 因篇幅有限,仅展示部分资料,需要保存下方图片,微信扫码即可前往获取

 最后就是我这几年整理的网安方面的面试题,如果你是要找网安方面的工作,它们绝对能帮你大忙。

这些题目都是大家在面试深信服、奇安信、腾讯或者其它大厂面试时经常遇到的,如果大家有好的题目或者好的见解欢迎分享。

参考解析:深信服官网、奇安信官网、Freebuf、csdn等

内容特点:条理清晰,含图像化表示更加易懂。

内容概要:包括 内网、操作系统、协议、渗透测试、安服、漏洞、注入、XSS、CSRF、SSRF、文件上传、文件下载、文件包含、XXE、逻辑漏洞、工具、SQLmap、NMAP、BP、MSF…

 因篇幅有限,仅展示部分资料,需要保存下方图片,微信扫码即可前往获取 

  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/28682.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从根源解决问题:构建体系化BOM管理机制与解决方案

BOM(物料清单)是设计与生产间的纽带,其准确及时对企业的竞争力至关重要。然而,维护BOM数据时,常遇到录入错误、信息孤岛及跨部门沟通障碍等难题,直接影响生产效率和成本。为此,道合顺将探讨确保…

Hi3861 OpenHarmony嵌入式应用入门--点灯

本篇实现对gpio的控制,通过控制输出进行gpio的点灯操作。 硬件 我们来操作IO2,控制绿色的灯。 软件 GPIO API API名称 说明 hi_u32 hi_gpio_deinit(hi_void); GPIO模块初始化 hi_u32 hi_io_set_pull(hi_io_name id, hi_io_pull val); 设置某个IO…

如何使用xurlfind3r查找目标域名的已知URL地址

关于xurlfind3r xurlfind3r是一款功能强大的URL地址查询工具,该工具本质上是一个CLI命令行工具,可以帮助广大研究人员从多种在线源来查询目标域名的已知URL地址。 功能介绍 1、从被动在线源获取URL地址以实现最大数量结果获取; 2、支持从Way…

python 地图+经纬度标记

如果你想在地图上显示标注点并在标注点旁边显示文字,可以使用folium.Marker的popup参数来实现。这个参数允许你设置在标注点上点击时显示的文本内容。以下是修改后的示例代码,演示如何在地图上显示带有文字标注的标注点: import folium# 创建…

leetcode打卡#day45 携带研究材料(第七期模拟笔试)、518. 零钱兑换 II、377. 组合总和 Ⅳ、爬楼梯(第八期模拟笔试)

携带研究材料&#xff08;第七期模拟笔试&#xff09; #include<iostream> #include<algorithm> #include<vector>using namespace std;int main() {int N, V;cin >> N >> V;vector<int> weights(N1);vector<int> values(V1);int w…

遗传算法浅理解

1. 什么是遗传算法&#xff1f; ​ 遗传算法&#xff0c;又称为 Genetic algorithm(GA)Genetic algorithm(GA)。其主要思想就是模拟生物的遗传与变异。它的用途非常广泛&#xff0c;可以用于加速某些求最大或者最小值的算法&#xff08;换句话说就是加速算法收敛&#xff0c;最…

实现企业可持续发展目标,ISCC能起什么作用呢?

国际可持续发展和碳认证 (ISCC) 基于气候变化已经成为全球面临的重大挑战之一、可再生能源需求不断增长的这个大背景下&#xff0c;企业的可持续发展诉求正日益显现&#xff0c;尤其当下消费者对于环境和社会责任意识的提升&#xff0c;需要更透明的供应链证明&#xff0c;同时…

移动端专业视频剪辑解决方案,深度编辑,专业级体验

面对众多繁杂的移动端视频编辑软件&#xff0c;如何挑选一款既高效又专业的解决方案&#xff0c;成为众多企业关注的焦点。美摄科技凭借其卓越的技术实力&#xff0c;推出了面向企业的移动端专业视频剪辑解决方案&#xff0c;助力企业轻松打造高质量视频内容。 一、深度编辑&a…

OAuth 2.0:现代应用程序的授权标准

前言 随着互联网和移动应用的发展&#xff0c;应用程序之间的交互变得越来越普遍。用户希望通过单一的身份认证在多个平台上无缝体验&#xff0c;这就要求不同的应用程序能够安全地共享用户数据。而 OAuth 2.0 正是为了解决这一问题而设计的&#xff0c;它提供了一种标准机制&…

6月16日-英语学习日记-(专科生)

我能够走到对岸的&#xff0c;我相信我自己&#xff0c;自己该和过去的事情做一个了断了&#xff01; 符号区别句子&#xff0c;通过感叹符号可以知道那些句子是一个感叹句。 In recent years 最近几年 commander n军官&#xff0c;长官 自己今天对了一道题目&#xff1a; …

Golang 百题(实战快速掌握语法)_1

整形转字符串类型 实验介绍 本实验将展示三种方法来实现整形类型转字符串类型。 知识点 strconvfmt Itoa 函数 代码实例 Go 语言中 strconv 包的 itoa 函数输入一个 int 类型&#xff0c;返回转换后的字符串。下面是一个例子。 package mainimport ("fmt"&qu…

C语言 -- 宏的变长参数定义

C语言宏定义中的可变参数处理 在C语言的宏定义中&#xff0c;我们可以使用可变参数来创建更加灵活和通用的宏。C99标准引入了__VA_ARGS__&#xff0c;而GNU编译器扩展了...args。这两者在处理可变参数时有所不同。本文将介绍它们的区别、使用场景以及相关示例。 背景介绍 __…

wps 二维数据转转一维度数据

HSTACK(TOCOL(C2:H2&A3:A8),TOCOL(B3:B8&C1:H1),TOCOL(C3:H8))

网络编程(三)UDP TFTP协议

文章目录 一、 UDP&#xff08;一&#xff09;概述&#xff08;二&#xff09;流程 二、收发函数&#xff08;一&#xff09;recvfrom&#xff08;二&#xff09;sendto 三、实现一个简单的udp服务端和客户端四、实现tftp客户端协议 一、 UDP &#xff08;一&#xff09;概述 …

Spring-事件

Java 事件/监听器编程模型 设计模式-观察者模式的拓展 可观察者对象(消息发送者) Java.util.Observalbe观察者 java.util.Observer 标准化接口(标记接口) 事件对象 java.util.EventObject事件监听器 java.util.EventListener public class ObserverDemo {public static vo…

React汇率小案例

import React from "react"; import Money from "./components/Money"; class App extends React.Component { // state state { dollar: , money: } transformRmb (value) > { this.setState({ // 保留后两位 并判断valu…

ASPICE标准与ASPICE认证:提升汽车软件开发质量与效率的关键途径

在当今日新月异的科技时代&#xff0c;软件产品的质量和可靠性成为了企业赢得市场的关键。而ASPICE&#xff08;Automotive SPICE&#xff09;标准&#xff0c;作为汽车行业中软件过程评估的国际通用标准&#xff0c;正逐渐引起行业的广泛关注。那么&#xff0c;ASPICE标准究竟…

【Rhino】【Python】Replace specified context of text object批量替换文字对象的指定内容

文章目录 在Rhino中批量修改文字对象内容的Python脚本脚本代码主要功能介绍1. 导入 rhinoscriptsyntax 模块2. 定义批量修改文字对象内容的函数3. 获取所有对象4. 遍历所有对象并修改文字内容5. 输出修改结果6. 设置旧文本和新文本&#xff0c;并运行函数 运行脚本总结 在Rhino…

使用 git 遇到权限错误

如果在执行 git 相关的命令的时候遇到权限错误&#xff0c;可能是因为你之前使用了不同的用户名在本地生成了SSH密钥。你可以尝试以下步骤来解决这个问题&#xff1a; 打开终端&#xff0c;并执行以下命令来删除旧的SSH密钥文件&#xff1a; rm ~/.ssh/id_rsa rm ~/.ssh/id_rsa…

Spring IoC【控制反转】DI【依赖注入】

文章目录 控制反转&#xff08;IoC&#xff09;依赖注入&#xff08;DI&#xff09;IoC原理及解耦IoC 容器的两种实现BeanFactoryApplicationContext IoC 是 Inversion of Control 的简写&#xff0c;译为“控制反转”&#xff0c;它不是一门技术&#xff0c;而是一种设计思想&…