XZ后门故事:初始分析

news2024/10/6 18:24:24

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.coloradmin.cn/o/1832826.html

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

相关文章

二叉树-根据先序遍历和中序遍历序列重建二叉树

目录 一、问题描述 二、解题思路 1.首先明确先序遍历和中序遍历的性质: 2.确定根节点及左右子树 3.对子树进行递归操作 4.递归返回条件 三、代码实现 四、刷题链接 一、问题描述 二、解题思路 1.首先明确先序遍历和中序遍历的性质: 先序遍历&am…

基于ChatGPT-4o自然科学研究全流程实践技术应用

自然科学研究遵循严谨的科学方法论,包括文献调研、问题综述、试验设计、提出假设、数据清洗、统计诊断、大数据分析、经典统计模型(回归模型、混合效应模型、结构方程模型、Meta分析模型)、参数优化、机器/深度学习、大尺度模型构建与模拟、论…

Centos7 安装oracle 11.2.0.4

荆轲刺秦王 1. 准备工作 需要下载 Oracle 11g 安装包 2.HostName修改: hostnamectl set-hostname oracle 3. 配置hostname(本机IP映射)注意:192.168.116.129 需要换乘本地ip vi /etc/hosts 192.168.116.129 oracle # 测试hos…

企业用户使用OV SSL证书趋势增长

随着网络安全的需求度日益提高,https证书也成为了当下最受欢迎的数字证书之一,主要是用于保护网站和应用程序的安全,并提升用户对网站的信任度,且只有企业或组织才可申请。 OV SSL证书全称Organization Validation SSL(组织验证性…

【前端】Nesj 学习笔记

1、前置知识 1.1 装饰器 装饰器的类型 declare type ClassDecorator <TFunction extends Function>(target: TFunction) > TFunction | void; declare type PropertyDecorator (target: Object, propertyKey: string | symbol) > void; declare type MethodDe…

CMSIS-RTOS2简介

本文介绍CMSIS-RTOS2。 1.引入 CMSIS-RTOS2在基于Arm Cortex处理器的设备上运行的实时操作系统内核上指定了通用RTOS接口。应用程序和中间件组件可以使用CMSIS-RTOS2 API在各种软件生态系统中实现更好的代码重用和更简单的集成。 CMSIS-RTOS2还指定了RTOS内核使用的标准OS T…

机械师电脑文件丢失怎么办?6个恢复方法,希望能帮到您

机械师电脑作为高性能的计算机品牌&#xff0c;受到众多用户的青睐。然而&#xff0c;即便是品质卓越的电脑&#xff0c;也难免会遇到文件丢失的困扰。无论是由于误操作、系统故障还是硬盘损坏&#xff0c;文件丢失都可能给用户带来不小的麻烦。当您发现机械师电脑上的文件突然…

使用Midjourney为产品创建出色效果图-关键词

使用MJ为产品创建效果图并不难&#xff0c;可以使用这个固定提示词公式。 Mockup empty, blank [ product ], [ decorating items ] [ background or context ], [ 1- 3 descriptive style], [ color palette ] 创建产品形象 首先&#xff0c;你需要准备一个透明背景的产品。…

基于JSP的二手车交易网站

开头语&#xff1a; 你好呀&#xff0c;我是计算机学长猫哥&#xff01;如果你对二手车交易网站感兴趣或有相关开发需求&#xff0c;欢迎随时联系我。我的联系方式可以在文末找到。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSPJava 工具&#…

IPA清洁棉签 IPA清洁擦拭棒:打印机头、电子设备等清洁的有力工具!

在数字化快速发展的今天&#xff0c;打印机头、电子设备等已经成为了我们日常生活和工作中不可或缺的一部分。然而&#xff0c;随着使用时间的增长&#xff0c;这些设备往往会因为灰尘、油渍等污染物的积累而影响其性能。此时&#xff0c;一款高效、便捷的清洁工具就显得尤为重…

可通过小球进行旋转的十字光标(vtkResliceCursor)

前一段事件看到VTK的一个例子&#xff1a; 该案例是vtk.js写的&#xff0c;觉得很有意思&#xff0c;个人正好也要用到&#xff0c;于是萌生了用C修改VTK源码来实现该功能的想法。原本以为很简单&#xff0c;只需要修改一下vtkResliceCursor就可以了&#xff0c;加上小球&#…

压缩列表(ziplist)

压缩列表&#xff08;ziplist&#xff09;&#xff1a; ziplist是列表键和哈希键的底层实现之一 当一个列表键只包含少量列表项&#xff0c;并且每个列表项要么是小整数或者短字符串&#xff0c;那么redis会使用ziplist来做列表键的实现当一个哈希键只包含少量键值对&#xff0…

【机器学习300问】119、什么是语言模型?

语言模型&#xff08;Language Models&#xff09;是自然语言处理&#xff08;NLP&#xff09;的重要组成部分&#xff0c;它的目的是量化一段文本或一个序列的概率。简单讲就是你给语言模型一个句子&#xff0c;它给你计算出特定语言中这个句子出现的概率。这样的概率度量可以…

基于jeecgboot-vue3的Flowable流程--增加我的抄送

因为这个项目license问题无法开源&#xff0c;更多技术支持与服务请加入我的知识星球。 1、前端我的抄送界面代码 <template><div class"p-2"><!--查询区域--><div class"jeecg-basic-table-form-container"><a-form ref"…

身为小白,浅浅聊一聊容器化部署

作为一个萌新小白&#xff0c;常常听人说起容器化部署某某程序……这时候我只能装作波澜不惊的样子&#xff0c;然后回去狠狠补课。 今天就来浅浅聊一下我对容器化部署的理解吧&#xff0c;为和我一样的萌新做个简单的科普&#xff0c;有什么不对的地方也请各位大佬友好交流。 …

EIQ-ABC 分析法在配送中心储位分配中的应用

配送中心运作效率的高低主要取决于仓储业务流程的作业效率&#xff0c;在配送作业流程中&#xff0c;储位分配的是否合理性成为影响配送运作效率的重要因素。为实现储位的合理分配&#xff0c;提出通过对订单信息的分析&#xff0c;并应用 EIQ-ABC 分析法&#xff0c;以此实现缩…

Linux操作系统以及一些操作命令、安装教程

Web课程完结啦&#xff0c;这是Web第一天的课程大家有兴趣可以传送过去学习 http://t.csdnimg.cn/K547r Linux-Day01 课程内容 Linux简介 Linux安装 Linux常用命令 1. 前言 1.1 什么是Linux Linux是一套免费使用和自由传播的操作系统。说到操作系统&#xff0c;大家比…

帮助汽车制造业实现高精度脚垫上下料自动化

随着汽车制造业的快速发展&#xff0c;对生产效率和产品质量的要求日益提高。在汽车制造过程中&#xff0c;脚垫的上下料操作是一个重要的环节&#xff0c;传统的人工操作方式已经无法满足现代生产的需求。富唯智能凭借其先进的3D视觉引导机器人抓取技术&#xff0c;成功解决了…

Paragon NTFS for Mac 15软件下载及安装教程

简介&#xff1a; NTFS For Mac 15是首个支持Mac上读写NTFS外置存储设备解决方案 &#xff0c;解决mac不能读写外置让您更加简单直观的在Mac机上随意对NTFS文件修改、删除等操作。 安 装 包 获 取 地 址&#xff1a; Paragon Ntfs For Mac 15版&#xff1a; ​​https://sou…

05-5.4.1 树的存储结构

&#x1f44b; Hi, I’m Beast Cheng &#x1f440; I’m interested in photography, hiking, landscape… &#x1f331; I’m currently learning python, javascript, kotlin… &#x1f4eb; How to reach me --> 458290771qq.com 喜欢《数据结构》部分笔记的小伙伴可以…