ARMV8安全特性:Pointer Authentication

文章目录

  • 前言
  • 一、Introduction
  • 二、Problem Definition
  • 三、Pointer Authentication
    • 3.1 Instructions
    • 3.2 Cryptography
    • 3.3 Key Management
  • 四、Sample Use Cases
    • 4.1 Software Stack Protection
    • 4.2 Control Flow Integrity (CFI)
    • 4.3 Binding Pointers to Addresses
  • 五、Security Properties
    • 5.1 Arbitrary memory read
    • 5.2 Arbitrary memory write
    • 5.3 Guessing and forging PAC values
    • 5.4 Pointer substitution attacks
    • 5.5 Key management concerns and key reuse attacks
    • 5.6 Interpreters and Just-in-Time Compilation (JIT)
  • 六、Conclusion

前言

本文来自于https://www.qualcomm.com/content/dam/qcomm-martech/dm-assets/documents/pointer-auth-v7.pdf的翻译。

一、Introduction

ARMv8.3-A是ARMv8-A架构的2016年新增内容。这些新增内容包括指针认证指令:“与指针认证相关的增强安全机制”。非常令人兴奋的是,这项技术通过与ARM及其合作伙伴的讨论和贡献进一步完善和扩展,并作为新的指针认证指令纳入到架构中。

ARM引入的指针认证方案是一种软件安全原语,可以使攻击者在未被检测到的情况下更难修改内存中的受保护指针。在本文档中,我们将提供关于指针认证机制的更多详细信息,进行安全分析,并讨论使用指针认证原语实施某些软件安全对策,例如堆栈保护和控制流完整性。

二、Problem Definition

软件安全中常见的一个问题是内存损坏漏洞,例如缓冲区溢出。这些漏洞通常通过覆盖内存中的控制数据(函数指针和返回地址)来利用,将代码执行重定向到由攻击者控制的位置。有三种常见的方式来防御内存损坏利用:
(1)将敏感数据和指针放入只读内存以防止损坏:对于静态函数指针表和其他敏感数据,这种方式非常有效。但仍需要确保对这些只读表的任何指针也经过验证或受到保护。不幸的是,对于动态指针,如堆栈上的返回地址或包含函数指针的动态分配对象,这种方式不起作用。

(2)在使用指针之前通过验证来检测损坏:这是软件堆栈保护(SSP)的工作原理。控制流完整性(CFI)和其他返回导向编程(ROP)的缓解措施,例如检查跳转/返回目标的各种属性,也属于这个类别。

(3)加大找到目标的难度:这可以通过某种形式的随机化来实现。随机化是一种良好的通用防御措施,使得可靠地利用系统变得更加困难。这个类别中的对策范围从随机化堆栈/堆(使得更难找到要破坏的指针),到完全的地址空间布局随机化(ASLR)(使得更难确定跳转的位置)。需要注意的是,一些检测对策措施(如堆栈保护)也需要不可预测性(例如,随机的堆栈保护标志)才能发挥作用。

这些技术是互补的,大多数现代对抗设计都使用它们的组合。

高通技术公司的产品安全工作之一是将软件安全对策引入其平台。这不仅涵盖运行主操作系统的应用处理器,还包括用于引导加载程序、调制解调器、WiFi和DSP等外设的镜像,以及用于虚拟化程序和TrustZone等其他执行环境。这些镜像中的大多数已经支持三种基本的对策措施: software stack protection、Data Execution Prevention(DEP/W^X)和 a hardened heap。然而,这些镜像也存在大小和性能限制,使得更高级的对策措施,如ASLR和基于软件的CFI,无法实施。我们希望设计一种方案,可以在最小的大小和性能影响下检查指针的有效性,并抵抗内存泄露漏洞。这排除了使用XOR与随机值以及其他简单的混淆或扰乱指针的方式。我们需要一个具有密码学强度的算法。

在早期的设计决策中选择了使用认证而不是加密。使用认证时,实际的指针值仍然可用,而无需知道秘密密钥。这有许多优点,包括允许处理器的分支预测和调试。此外,通过认证,可以知道发生了损坏的时间,而不仅仅是跳转到一个随机位置并希望崩溃。下一节将描述指针认证的设计。

三、Pointer Authentication

指针认证的基本思想是,在64位架构中,实际的地址空间小于64位。指针值中存在未使用的位,我们可以利用这些位来放置指针认证码(PAC)。在将要写入内存的每个需要保护的指针之前,我们可以插入一个PAC,并在使用之前验证其完整性。想要修改受保护指针的攻击者必须找到/猜测正确的PAC才能控制程序流程。

在程序中,并非每个指针都具有相同的用途。我们希望指针仅在特定的上下文中有效。在指针认证中,这通过两种方式实现:为主要用例使用单独的密钥,并通过对指针和64位上下文计算PAC来实现。指针认证规范定义了五个密钥:两个用于指令指针,两个用于数据指针,以及一个用于在更长的数据序列上计算MAC的单独通用指令。指令编码确定使用哪个密钥。上下文对于隔离与同一密钥一起使用的不同类型的指针非常有用。在计算和验证PAC时,上下文被指定为附加参数,与指针一起使用。

指针认证设计包括三个主要组件:指令(Instructions)、密码学(cryptography)和密钥管理(key management)。接下来将对它们进行描述。

3.1 Instructions

指针认证需要两个主要的操作:计算和添加PAC以及验证PAC并恢复指针值。这分别由PAC和AUT指令集来处理。如果在AUT指令执行期间验证失败,处理器将使用特定模式替换PAC,使得指针值成为非法地址。实际的错误检测是通过非法地址异常来进行的,当解引用无效指针时会触发该异常。这种设计将错误处理与指令解耦,无需使用额外的指令进行错误处理。异常处理程序可以通过检查AUT指令用于表示错误的模式来区分非法地址异常和认证失败。

除了PAC和AUT指令,还有用于去除PAC(XPAC*)和从两个64位输入计算32位认证码的指令(PACGA)。PACGA指令对于保护内存中的敏感数据结构非常有用。例如,可以使用它来计算覆盖所有堆元数据的认证码(AC)值,通过将几个对PACGA指令的调用链接在一起。

ARMv8.3-A架构描述了每个密钥的PAC和AUT指令的变体,并包括组合指令,如验证后返回(RETA*)和验证并跳转(BRA*,BLRA*)。一部分指令被编码在NOP空间中(指令空间的一部分,在早期版本的架构中被视为NOP)。这提供了二进制向后兼容性,允许较旧的ARMv8处理器运行使用这些新指令编译的二进制文件。

3.2 Cryptography

密码学是指针认证的关键部分。我们需要一种快速轻量级的算法,即使在截断为非常短的标签时,也具有足够的密码学强度。所有现有的候选算法,如SipHash和PRINCE,在用于认证指针的构造中具有过高的延迟,可能会对性能造成太大的影响。其中一个问题是PAC必须是由当前环境不可控制的秘密字符串(或密钥)、被标记的指针和本地上下文的函数。这将使得SipHash的输入(以及处理时间)过长,并且像PRINCE这样的分组密码仅允许128位密钥和64位输入。使数据适应PRINCE输入的一种可能的方法是将64位秘密与上下文连接起来以获得用于加密指针的密钥,然后截断密文。然而,这将使攻击者在一定程度上能够控制PRINCE密钥,这对安全性来说非常危险,因为PRINCE的结构及其容易受到相关密钥攻击的影响。

因此,我们设计了QARMA,一种新型的轻量级可调整的分组密码系列。可调整性意味着密码算法对明文的置换取决于秘密密钥和额外的用户可选择的调整参数。

QARMA针对一组非常特定的用例进行了优化。除了在此处使用的截断生成非常短的标签之外,它还设计用于内存加密和构建有密钥哈希函数。它适用于完全展开的硬件实现。该算法属于PRINCE所开创的设计系列,它是一种反射设计,并与密码算法MANTIS相关,但具有不同的设计和比MANTIS更保守的S-盒选择,以减少某些密码分析攻击的可能性。

在用于指针认证时,QARMA的两个输入是指针和上下文。PAC是QARMA截断输出的结果。PAC的大小由处理器虚拟地址大小配置和是否使用“标记地址”功能确定。与PAC不同,“标记地址”功能允许软件为指针添加一个8位的标记,而不影响地址转换。如果未启用“标记地址”功能,则PAC可以使用这些位。由于虚拟地址大小可以配置为32位到52位之间,并且一个位(位55)用于选择虚拟地址空间的高半部分或低半部分,当禁用标记地址时,PAC的大小范围从11位到31位,当启用标记地址时,范围从3位到23位。PACGA指令始终从QARMA的输出生成32位的AC(认证码)。

3.3 Key Management

QARMA使用128位密钥。指针认证规范定义了五个密钥。四个密钥用于PAC和AUT指令(指令/数据和A/B密钥的组合),第五个密钥用于通用的PACGA指令。这些密钥存储在内部寄存器中,EL0(用户模式)无法访问,但与异常级别无关。软件(EL1、EL2和EL3)需要根据需要在异常级别之间切换密钥。通常较高的特权级别控制较低特权级别的密钥。为每个密钥添加了控制位,用于定义异常行为。这允许在需要时按需生成或切换密钥。

这些密钥预计是临时的(对于EL0是每个进程,对于EL1至EL3是每次引导),密钥管理(包括生成高质量的随机数)是软件的责任。

四、Sample Use Cases

本节描述了我们期望使用指针身份验证的不同用例。

4.1 Software Stack Protection

软件堆栈保护是一种针对基于堆栈的缓冲区溢出的对策措施。通常,编译器会对选定的函数进行处理,在进入函数时,在返回地址和堆栈上的缓冲区之间放置一个随机的"canary"值,并在函数返回之前检查"canary"是否被破坏。虽然软件堆栈保护对某些类型的缓冲区溢出是有效的,但它可能会被内存泄露漏洞所攻破。下表说明了代码在进行软件堆栈保护时的工具化。更改部分以红色突出显示:
在这里插入图片描述
基本的指针身份验证指令PAC和AUT可用于实现更强大的堆栈保护版本,开销要小得多:
在这里插入图片描述
上述使用的PACIASP和AUTIASP指令是更通用的PACIA X30, SP和AUTIA X30, SP指令的特殊版本。它们分别使用堆栈指针作为上下文对链接寄存器(X30)进行标记和验证。这些特殊指令位于NOP空间中,这意味着生成的代码与旧处理器保持二进制兼容。如果不需要向后兼容性,可以使用组合的指针认证指令RETAA,它等效于AUTIASP+RET,以节省一条额外的指令。

4.2 Control Flow Integrity (CFI)

CFI(控制流完整性)是程序的一个属性,其中控制流仅遵循预先确定的合法路径。虽然安全的CFI实现需要同时覆盖函数指针和返回地址,但现有的CFI实现通常专注于保护函数指针,而将返回地址的保护留给其他对策,如strong stack protection 或者 shadow/split stack实现。

在CFI中,通过间接调用/跳转可达的函数根据编译时分析或启发式方法进行分类,并限制每个调用点仅调用相同类别的函数指针。通常,这是通过计算和维护内存中的函数指针表,并在每个间接调用/跳转之前添加检查来实现,以确保目标位于表中。

在使用指针认证进行CFI时,一种可能的方法是在加载和动态链接时基于编译时生成的每个指针的类别(上下文)和位置信息,对内存中的函数指针进行预验证。这允许使用在编译时提供的上下文,在调用点使用单个AUT指令对间接函数调用进行验证。

4.3 Binding Pointers to Addresses

指针认证指令可以使用PAC(指针认证代码)指令将指针的值绑定到给定的地址上,其中使用指针的地址作为上下文。这确保了指针的值保持不变,有效地使该位置只读。这对于在程序的生命周期内变化不大的指针非常有用,比如指向只读表格或数据结构的指针。

五、Security Properties

本节描述了指针认证的安全性属性,包括其优点和缺点。请注意,本讨论大部分假设数据执行预防(DEP)已启用,因此代码不可写且数据不可执行。

5.1 Arbitrary memory read

大多数依赖于秘密信息的软件对抗措施都容易受到泄露内存内容的攻击。这包括ASLR(地址空间布局随机化)、软件堆栈保护和其他使用与秘密进行异或运算来混淆指针值的方案。

指针认证旨在抵御内存泄露攻击。PAC是使用具有密码学强度的算法计算的,因此从内存中读取任意数量的经过认证的指针也不会使伪造指针变得更容易。

密钥存储在处理器寄存器中,而这些寄存器对用户模式(EL0)不可访问。因此,内存泄露漏洞不会帮助提取用于PAC生成的密钥。

5.2 Arbitrary memory write

针对内存中可写代码指针的任意内存写入攻击需要猜测或暴力破解PAC,或者找到一种非控制数据的漏洞来修改系统的行为。例如,修改攻击者控制的进程的有效用户ID为root的内核漏洞,以及将内存中的“已认证”标志设置为true以绕过密码检查的远程攻击都是数据攻击的示例。指针认证扩展并没有提供一种通用的机制来防止这些攻击。然而,使用PACGA对敏感数据指针进行认证并保护敏感数据结构可以帮助限制攻击者的能力。

指针认证的一个有趣特性是,即使修改发生在处理器核心之外(例如通过DMA攻击),也可以检测到数据的损坏。这在一般情况下对于代码或其他只读区域可能并没有太大帮助,但在外部访问被限制在物理地址空间的某些区域(例如使用SMMU/IOMMU)时,它可以提供保护。

5.3 Guessing and forging PAC values

猜测目标指针的PAC值的难度取决于PAC的大小,这取决于系统配置。虽然PAC的大小可以低至3位(8分之1的概率),但Linux内核在AArch64架构上默认使用39位虚拟地址空间(512GB)。这在Linux中允许使用24位PAC(400万分之1的概率)。当禁用标记地址时,最大的52位虚拟地址大小配置下,PAC将有11位(2048分之1的概率),这仍然可以与某些ASLR实现相媲美。需要更高安全性的系统(如TrustZone内核和应用程序)可以修改这些执行模式的虚拟地址大小配置,将PAC大小增加到最多31位。

QARMA算法明确设计用于处理短/截断的认证码。我们不期望算法中存在任何可以通过泄露或猜测标签而使后续猜测更容易的快捷方式。实际上,假设成功猜测了一个标签,意味着只知道QARMA输出的少数几位而不是整个密文,而任何在对QARMA进行密码分析攻击时时间或空间复杂性的减少都需要大量已知(甚至是选择的)文本对,这超出了由于有效内存大小而可以收集的信息量。因此,猜测的难度呈指数级增加。任何需要修改或伪造多个指针的攻击都将变得极其昂贵。这实际上对攻击者提高了很高的门槛。

5.4 Pointer substitution attacks

我们预计针对指针认证的大多数攻击形式将是替换一个经过认证的指针为另一个。例如,如果一个指向puts()函数并打印出攻击者提供的字符串的经过认证的函数指针可以被替换为指向system()函数的经过认证的指针,那么这将允许执行攻击者提供的命令。

指针认证设计为不同的用途类别指定了不同的密钥,这些密钥是指令编码的一部分,并在编译时确定。上下文参数可以用于将指针进一步分类为组,以限制可以相互替换的指针。这与细粒度的控制流完整性(Control-Flow-Integrity)没有太大区别,其中每个间接调用点只能根据在编译时生成的控制流图调用函数的一个子集。上下文参数可以用于实施类似的限制。

在架构讨论中,由ARM合作伙伴设计的一种非常有趣的指针替换攻击被ARM描述给我们。该攻击假设除了存在内存破坏漏洞外,还存在一种可以读取堆栈内容并能够在同一堆栈上触发不同功能的原语。Web浏览器可能会满足这些要求。由于在计算返回地址的PAC时使用堆栈指针作为上下文,因此可以收集与不同堆栈地址绑定的多个经过认证的指针。然后,可以使用这些地址来形成一系列的gadget(绑定到堆栈中的原始位置),以执行ROP(Return-Oriented Programming)有效载荷。然而,鉴于攻击者已经拥有的原语的强大功能,很难评估执行此攻击的难度,与以其他方式破坏环境的方式相比。这再次证实了指针认证并非万能解决方案,而且浏览器安全性确实很困难。

5.5 Key management concerns and key reuse attacks

如果攻击者能够猜测密钥或控制与目标进程具有相同密钥的进程,她就可以伪造指针。

第一个关注点是密钥生成时使用的随机性质量。虽然ARM架构不包括熵源,但我们希望假设所有现代基于ARMv8.3-A的设计已经包含一个在早期引导时可以访问的高质量熵源。支持ARMv8的Qualcomm Technologies芯片已经有硬件随机数生成器可用。

第二个问题更常见,特别是在类UNIX系统中,fork()系统调用会创建一个进程的完全副本。这意味着子进程必须具有与其父进程相同的密钥才能继续运行。需要注意的是,这个问题在其他对策中也存在,包括堆栈保护和地址空间布局随机化(ASLR)。

在特权分离设计中,其中一个进程保持特权,而另一个进程降低权限,攻击者如果能够入侵非特权进程,就可以创建在特权进程中有效的经过认证的指针。

在工作进程模型中,根据主进程的需要,会生成多个工作进程来处理请求,远程攻击者可以获得多个、潜在无限次的尝试来暴力破解PAC值,因为所有工作进程都具有相同的密钥,包括由于地址错误而被替换的工作进程。

在这两种情况下,使用fork+exec模型,即子进程在fork之后重新加载自身,可以确保所有进程都使用新的密钥(以及具有ASLR的新地址空间布局)重新启动。由于实施fork+exec需要对设计和现有代码进行更改,并可能影响服务的性能/延迟,内核应该考虑对PAC异常实施特殊处理:当一个进程接收到PAC异常时,内核应该终止不仅是出错的进程,还有所有具有相同密钥的其他进程。这是可行的,因为内核负责管理使用指针认证的进程的密钥。

5.6 Interpreters and Just-in-Time Compilation (JIT)

脚本和字节码解释器为攻击者提供了将数据注入系统并将其解释为指令的良好目标。指针认证不能防止仅针对数据的攻击,但它确实使使用攻击者控制的数据跳转到解释器入口点变得更加困难。

大多数这些运行时环境还支持即时编译(JIT),其中从脚本/字节码动态生成本机代码,以提高性能。一些JIT实现使用可写可执行(RWX)的内存区域,破坏了数据执行保护(DEP),而其他一些JIT实现通过使用别名映射来访问相同物理内存范围的两个虚拟地址(一个可写,一个可执行)来假装遵守DEP/W^X要求。虽然RWX情况可能更糟糕,但这两种方案都为向系统中注入代码提供了目标,潜在地绕过指针认证和其他对策。

随机化可写JIT区域的位置是一种减轻这些攻击的方法。在安全关键环境中,关闭JIT也应该被视为一种选项。

六、Conclusion

在本文中,我们描述了ARMv在本文中,我们描述了ARMv8.3-A规范中引入的ARM指针认证扩展的设计。我们介绍了一些我们预计在工具链中看到的常见用例,并对该方案进行了简要的安全分析。本文中我们提出的场景是一些我们认为指针认证指令会有用的示例。通过提供一种快速验证内存中指针和数据完整性的方式,这些指令将为其他技术和应用程序的出现创造机会。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

B2B领域的客户裂变策略:打造行业内的共赢生态

在日益竞争激烈的B2B市场中,客户裂变作为一种高效的增长策略,不仅能够帮助企业快速扩大客户基础,还能促进行业内资源共享与合作,共同构建一个健康、可持续的共赢生态。本文将探讨B2B领域实施客户裂变策略的关键要素,以…

【数据结构】排序——快速排序

前言 本篇博客我们继续介绍一种排序——快速排序,让我们看看快速排序是怎么实现的 💓 个人主页:小张同学zkf ⏩ 文章专栏:数据结构 若有问题 评论区见📝 🎉欢迎大家点赞👍收藏⭐文章 ​ 目录 …

“学习Pandas中时间序列的基本操作“

目录 # 开篇 1. 创建和操作时间序列对象 2. 时间序列数据的读取和存储 3. 时间序列数据的索引和切片 4. 时间序列数据的操作和转换 5. 时间序列数据的可视化 6. 处理时间序列中的缺失值 7. 时间序列数据的聚合和分组 8. 时间序列的时间区间和偏移量操作 示例代码&…

重要文件放u盘还是硬盘?硬盘和u盘哪个适合长期存储

在数字时代,我们每天都会处理大量的文件。其中,不乏一些对我们而言至关重要的文件,如家庭照片、工作文档、财务记录等。面对这些重要文件的存储问题,我们通常会面临:“重要文件放U盘还是硬盘”、“硬盘和U盘哪个适合长…

qt creator中右边的的类和对象如何显示出来

qt creator中右边的的类和对象如何显示出来? 解决方法: 鼠标右键,重置为默认布局。

特征值究竟体现了矩阵的什么特征?

特征值究竟体现了矩阵的什么特征? 简单来说就是x经过矩阵A映射后和自己平行 希尔伯特第一次提出eigenvalue,这里的eigen就是自己的。所以eigenvalue也称作本征值 特征值和特征向量刻画了矩阵变换空间的特征 对平面上的任意向量可以如法炮制,把他在特征…

【Linux】任务管理

这个任务管理(job control)是用在bash环境下的,也就是说:【当我们登录系统获取bashshell之后,在单一终端下同时执行多个任务的操作管理】。 举例来说,我们在登录bash后,可以一边复制文件、一边查…

Linux--线程ID封装管理原生线程

目录 1.线程的tid(本质是线程属性集合的起始虚拟地址) 1.1pthread库中线程的tid是什么? 1.2理解库 1.3phtread库中做了什么? 1.4线程的tid,和内核中的lwp 1.5线程的局部存储 2.封装管理原生线程库 1.线程的tid…

8.9分王者“水刊”!1区IEEE-Trans,国人主编坐镇!发文量2倍增长,扩刊趋势明显!

关注GZH【欧亚科睿学术】,第一时间了解最新期刊动态! 本期,小编给大家推荐的是一本IEEE旗下王者“水刊”。该期刊目前处于扩刊状态,接收跨学科领域,领域认可度高,还可选择非OA模式无需版面费,是…

css看见彩虹,吃定彩虹

css彩虹 .f111 {width: 200px;height: 200px;border-radius: 50%;box-shadow: 0 0 0 5px inset red, 0 0 0 10px inset orange, 0 0 0 15px inset yellow, 0 0 0 20px inset lime, 0 0 0 25px inset aqua, 0 0 0 30px inset blue, 0 0 0 35px inset magenta;clip-path: polygo…

力扣爆刷第163天之TOP100五连刷81-85(回文链表、路径和、最长重复子数组)

力扣爆刷第163天之TOP100五连刷81-85(回文链表、路径和、最长重复子数组) 文章目录 力扣爆刷第163天之TOP100五连刷81-85(回文链表、路径和、最长重复子数组)一、234. 回文链表二、112. 路径总和三、169. 多数元素四、662. 二叉树…

盲人出行好帮手:蝙蝠避障让走路变简单

在一片无光的世界里,每一步都承载着探索与勇气。我是众多盲人中的一员,每天的出行不仅是从A点到B点的物理移动,更是一场心灵的征程。我的世界,虽然被剥夺了视觉的馈赠,却因科技的力量而变得宽广…

保护企业数据资产的策略与实践:数据安全治理技术之实战篇!

在上篇文章中,我们深入讨论了数据安全治理技术的前期准备工作,包括从建立数据安全运维体系、敏感数据识别、数据的分类与分级到身份认等方面的详细规划和设计。这些准备工作是实现数据安全治理的基础,它们为企业建立起一套系统化、标准化的数…

2.电容(常见元器件及电路基础知识)

一.电容种类 1.固态电容 这种一般价格贵一些,ESR,ESL比较低,之前项目400W电源用的就是这个,温升能够很好的控制 2.铝电解电容 这种一般很便宜,ESR,ESL相对大一些,一般发热量比较大,烫手。 这种一般比上一个贵一点&am…

[leetcode]maximum-binary-tree 最大二叉树

. - 力扣&#xff08;LeetCode&#xff09; class Solution { public:TreeNode* constructMaximumBinaryTree(vector<int>& nums) {return construct(nums, 0, nums.size() - 1);}TreeNode* construct(const vector<int>& nums, int left, int right) {if …

快速掌握 ==== js 正则表达式

git 地址 https://gitee.com/childe-jia/reg-test.git 背景 在日常开发中&#xff0c;我们经常会遇到使用正则表达式的场景&#xff0c;比如一些常见的表单校验&#xff0c;会让你匹配用户输入的手机号或者身份信息是否规范&#xff0c;这就可以用正则表达式去匹配。相信大多数…

CSS3实现彩色变形爱心动画【附源码】

随着前端技术的发展&#xff0c;CSS3 为我们提供了丰富的动画效果&#xff0c;使得网页设计更加生动和有趣。今天&#xff0c;我们将探讨如何使用 CSS3 实现一个彩色变形爱心加载动画特效。这种动画不仅美观&#xff0c;而且可以应用于各种网页元素&#xff0c;比如加载指示器或…

收集路径下的html并写到根目录index.html

我们可以用简单的脚本生成一个简单的导航页。 用于查看当前路径下所有的html。 #!/bin/bash index_root"/root/test/" cd ${index_root} echo "" > index.htmlecho "<html><head><title>Test Report Index</title></…

VBA 批量发送邮件

1. 布局 2. 代码 前期绑定的话&#xff0c;需要勾选 Microsoft Outlook 16.0 Object Library Option ExplicitConst SEND_Y As String "Yes" Const SEND_N As String "No" Const SEND_SELECT_ALL As String "Select All" Const SEND_CANCEL…

VSCode升级后不能打开在MacOS系统上

VSCode 在MacOS无法打开 版本 VSCode version: 1.91.0 (x64) 错误信息&#xff1a; MacBook-Pro ~ % /Users/mac/Downloads/FirefoxDownloads/Visual\ Studio\ Code.app/Contents/MacOS/Electron ; exit; [0710/142747.971951:ERROR:crash_report_database_mac.mm(753)] op…