应用安全:JAVA反序列化漏洞之殇

应用安全:JAVA反序列化漏洞之殇

概述

  • 序列化是让Java对象脱离Java运行环境的一种手段,可以有效的实现多平台之间的通信、对象持久化存储。
  • Java 序列化是指把 Java 对象转换为字节序列的过程便于保存在内存、文件、数据库中,ObjectOutputStream类的 writeObject() 方法可以实现序列化。反序列化是指把字节序列恢复为 Java 对象的过程,ObjectInputStream 类的 readObject() 方法用于反序列化。

漏洞成因

  • 序列化和反序列化本身并不存在问题。但当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码。

  • 漏洞代码示例如下:

  • ......
    //读取输入流,并转换对象
    InputStream in=request.getInputStream();
    ObjectInputStream ois = new ObjectInputStream(in);
    //恢复对象
    ois.readObject();
    ois.close();
    
  • 这里特别要注意的是非预期的对象,正因为此java标准库及大量第三方公共类库成为反序列化漏洞利用的关键。安全研究人员已经发现大量利用反序列化漏洞执行任意代码的方法,最让大家熟悉的是Gabriel Lawrence和Chris Frohoff在《Marshalling Pickles how deserializing objects can ruin your day》中提出的利用Apache Commons Collection实现任意代码执行。此后安全研究人员也陆续爆出XML、Json、Yaml等反序列化的相关漏洞。

  • 除了commons-collections 3.1可以用来利用java反序列化漏洞,还有更多第三方库同样可以用来利用反序列化漏洞并执行任意代码,部分如下:

  • commons-fileupload 1.3.1

  • commons-io 2.4

  • commons-collections 3.1

  • commons-logging 1.2

  • commons-beanutils 1.9.2

  • org.slf4j:slf4j-api 1.7.21

  • com.mchange:mchange-commons-java 0.2.11

  • org.apache.commons:commons-collections 4.0

  • com.mchange:c3p0 0.9.5.2

  • org.beanshell:bsh 2.0b5

  • org.codehaus.groovy:groovy 2.3.9

  • ……

Java反序列化详解

序列化数据结构

  • 通过查看序列化后的数据,可以看到反序列化数据开头包含两字节的魔术数字,这两个字节始终为十六进制的0xAC ED。接下来是两字节的版本号0x00 05的数据。此外还包含了类名、成员变量的类型和个数等。

  • 这里以类SerialObject示例来详细进行介绍Java对象序列化后的数据结构:

  • public class SerialObject implements Serializable{private static final long serialVersionUID = 5754104541168322017L;private int id;public String name;public SerialObject(int id,String name){this.id=id;this.name=name;}...
    }
    
  • 序列化SerialObject实例后以二进制格式查看:

  • 00000000: aced 0005 7372 0024 636f 6d2e 7878 7878  ....sr.$com.xxxx
    00000010: 7878 2e73 6563 2e77 6562 2e68 6f6d 652e  xx.sec.web.home.
    00000020: 5365 7269 616c 4f62 6a65 6374 4fda af97  SerialObjectO...
    00000030: f8cc c5e1 0200 0249 0002 6964 4c00 046e  .......I..idL..n
    00000040: 616d 6574 0012 4c6a 6176 612f 6c61 6e67  amet..Ljava/lang
    00000050: 2f53 7472 696e 673b 7870 0000 07e1 7400  /String;xp....t.
    00000060: 0563 7279 696e 0a                        .cryin.
    
  • 序列化的数据流以魔术数字和版本号开头,这个值是在调用ObjectOutputStream序列化时,由writeStreamHeader方法写入:

  • protected void writeStreamHeader() throws IOException {bout.writeShort(STREAM_MAGIC);//STREAM_MAGIC (2 bytes) 0xACEDbout.writeShort(STREAM_VERSION);//STREAM_VERSION (2 bytes) 5}
    
  • 序列化后的SerialObject对象详细结构:

  • STREAM_MAGIC (2 bytes) 0xACED 
    STREAM_VERSION (2 bytes) 0x0005TC_OBJECT (1 byte) 0x73TC_CLASSDESC (1 byte) 0x72classNamelength (2 bytes) 0x24 = 36text (36 bytes) com.xxxxxx.sec.web.home.SerialObjectserialVersionUID (8 bytes) 0x4FDAAF97F8CCC5E1 = 5754104541168322017classDescInfoclassDescFlags (1 byte) 0x02 = SC_SERIALIZABLEfieldscount (2 bytes) 2field[0]primitiveDescprim_typecode (1 byte) I = integerfieldNamelength (2 bytes) 2text (2 bytes) idfield[1]objectDescobj_typecode (1 byte) L = objectfieldNamelength (2 bytes) 4text (4 bytes)  nameclassName1TC_STRING (1 byte) 0x74length (2 bytes) 0x12 = 18text (18 bytes) Ljava/lang/String;classAnnotationTC_ENDBLOCKDATA (1 byte) 0x78superClassDescTC_NULL (1 byte) 0x70classdata[]classdata[0] (4 bytes) 0xe107 = id = 2017classdata[1]TC_STRING (1 byte) 0x74length (2 bytes) 5text (10 bytes) cryin
    

反序列化过程详解

  • Java程序中类ObjectInputStream的readObject方法被用来将数据流反序列化为对象,如果流中的对象是class,则它的ObjectStreamClass描述符会被读取,并返回相应的class对象,ObjectStreamClass包含了类的名称及serialVersionUID。
  • 如果类描述符是动态代理类,则调用resolveProxyClass方法来获取本地类。如果不是动态代理类则调用resolveClass方法来获取本地类。如果无法解析该类,则抛出ClassNotFoundException异常。
  • 如果反序列化对象不是String、array、enum类型,ObjectStreamClass包含的类会在本地被检索,如果这个本地类没有实现java.io.Serializable或者externalizable接口,则抛出InvalidClassException异常。因为只有实现了Serializable和Externalizable接口的类的对象才能被序列化。

反序列化漏洞检测方案

代码审计

  • 反序列化操作一般在导入模版文件、网络通信、数据传输、日志格式化存储、对象数据落磁盘或DB存储等业务场景,在代码审计时可重点关注一些反序列化操作函数并判断输入是否可控,如下:

  • ObjectInputStream.readObject
    ObjectInputStream.readUnshared
    XMLDecoder.readObject
    Yaml.load
    XStream.fromXML
    ObjectMapper.readValue
    JSON.parseObject
    ...
    
  • 同时也要关注第三jar包是否提供了一些公共的反序列化操作接口,如果没有相应的安全校验如白名单校验方案,且输入可控的话就也可能存在安全问题。

进阶审计

  • 对于直接获取用户输入进行反序列化操作这种点比较好审计并发现,目前反序列化漏洞已经被谈起太多次了,所以有经验的开发都会在代码中有相应的修复。但并不是所有修复都无懈可击。比如采用黑名单校验的修复方式,对于这种修复可在工程代码中尝试挖掘新的可以利用的’gadget‘。

  • 代码中有使用到反序列化操作,那自身项目工程中肯定存在可以被反序列化的类,包括Java自身、第三方库有大量这样的类,可被反序列化的类有一个特点,就是该类必定实现了Serializable接口,Serializable 接口是启用其序列化功能的接口,实现 java.io.Serializable 接口的类才是可序列化的。一个典型的示例如下:

  • public class SerialObject implements Serializable{private static final long serialVersionUID = 5754104541168322017L;private int id;public String name;public SerialObject(int id,String name){this.id=id;this.name=name;}public void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{//执行默认的readObject()方法in.defaultReadObject();}
    }
    
  • 所以在代码审计时对这些类也可进行特别关注,分析并确认是否有可能被发序列化漏洞利用执行任意代码。发现新的可利用的类即可突破使用黑名单进行校验的一些应用。

白盒检测

  • 大型企业的应用很多,每个都人工去审计不现实,往往都有相应的自动化静态代码审计工具,这里以ObjectInputStream.readObject()为例,其它反序列化接口的检测原理也相似。在自动化检测时,可通过实现解析java源代码,检测readObject()方法调用时判断其对象是否为java.io.ObjectOutputStream。如果此时ObjectInputStream对象的初始化参数来自外部请求输入参数则基本可以确定存在反序列化漏洞了。这是只需确认是否存在相应的安全修复即可。 可参考lgtm.com对于Deserialization of user-controlled data检测方式的实现。

黑盒检测

  • 调用ysoserial并依次生成各个第三方库的利用payload(也可以先分析依赖第三方包量,调用最多的几个库的paylaod即可),该payload构造为访问特定url链接的payload,根据http访问请求记录判断反序列化漏洞是否利用成功。如:

  • java -jar ysoserial.jar CommonsCollections1 'curl " + URL + " '
    
  • 也可通过DNS解析记录确定漏洞是否存在。现成的轮子很多,推荐NickstaDB写的SerialBrute,还有一个针对RMI的测试工具BaRMIe,也很不错~。.

RASP检测

  • Java程序中类ObjectInputStream的readObject方法被用来将数据流反序列化为对象,如果流中的对象是class,则它的ObjectStreamClass描述符会被读取,并返回相应的class对象,ObjectStreamClass包含了类的名称及serialVersionUID。

  • 类的名称及serialVersionUID的ObjectStreamClass描述符在序列化对象流的前面位置,且在readObject反序列化时首先会调用resolveClass读取反序列化的类名,所以RASP检测反序列化漏洞时可通过重写ObjectInputStream对象的resolveClass方法获取反序列化的类即可实现对反序列化类的黑名单校验。

  • 百度的开源RASP产品就是使用的这种方法,具体可参考其DeserializationHook.java的实现:

  • @Overrideprotected MethodVisitor hookMethod(int access, String name, String desc,String signature, String[] exceptions, MethodVisitor mv) {if ("resolveClass".equals(name) && "(Ljava/io/ObjectStreamClass;)Ljava/lang/Class;".equals(desc)) {return new AdviceAdapter(Opcodes.ASM5, mv, access, name, desc) {@Overrideprotected void onMethodEnter() {loadArg(0);invokeStatic(Type.getType(HookHandler.class),new Method("checkDeserializationClass", "(Ljava/io/ObjectStreamClass;)V"));}};}return mv;}
    
  • 其中检测覆盖的反序列化类黑名单如下:

  • plugin.register('deserialization', function (params, context) {var deserializationInvalidClazz = ['org.apache.commons.collections.functors.InvokerTransformer','org.apache.commons.collections.functors.InstantiateTransformer','org.apache.commons.collections4.functors.InvokerTransformer','org.apache.commons.collections4.functors.InstantiateTransformer','org.codehaus.groovy.runtime.ConvertedClosure','org.codehaus.groovy.runtime.MethodClosure','org.springframework.beans.factory.ObjectFactory','xalan.internal.xsltc.trax.TemplatesImpl']var clazz = params.clazzfor (var index in deserializationInvalidClazz) {if (clazz === deserializationInvalidClazz[index]) {return {action:     'block',message:    '尝试反序列化攻击',confidence: 100}}}return clean
    })
    

攻击检测

  • 通过查看反序列化后的数据,可以看到反序列化数据开头包含两字节的魔术数字,这两个字节始终为十六进制的0xAC ED。接下来是两字节的版本号。我只见到过版本号为5(0x00 05)的数据。考虑到zip、base64各种编码,在攻击检测时可针对该特征进行匹配请求post中是否包含反序列化数据,判断是否为反序列化漏洞攻击。

  • xxxdeMacBook-Pro:demo xxx$ xxd objectexp 00000000: aced 0005 7372 0032 7375 6e2e 7265 666c  ....sr.2sun.refl00000010: 6563 742e 616e 6e6f 7461 7469 6f6e 2e41  ect.annotation.A00000020: 6e6e 6f74 6174 696f 6e49 6e76 6f63 6174  nnotationInvocat00000030: 696f 6e48 616e 646c 6572 55ca f50f 15cb  ionHandlerU.....
    
  • 但仅从特征匹配只能确定有攻击尝试请求,还不能确定就存在反序列化漏洞,还要结合请求响应、返回内容等综合判断是否确实存在漏洞。

Java反序列化漏洞修复方案

通过Hook resolveClass来校验反序列化的类

  • 通过上面序列化数据结构可以了解到包含了类的名称及serialVersionUID的ObjectStreamClass描述符在序列化对象流的前面位置,且在readObject反序列化时首先会调用resolveClass读取反序列化的类名,所以这里通过重写ObjectInputStream对象的resolveClass方法即可实现对反序列化类的校验。这个方法最早是由IBM的研究人员Pierre Ernst在2013年提出《Look-ahead Java deserialization》,具体实现代码示例如下:

  • public class AntObjectInputStream extends ObjectInputStream{public AntObjectInputStream(InputStream inputStream)throws IOException {super(inputStream);}/*** 只允许反序列化SerialObject class*/@Overrideprotected Class<?> resolveClass(ObjectStreamClass desc) throws IOException,ClassNotFoundException {if (!desc.getName().equals(SerialObject.class.getName())) {throw new InvalidClassException("Unauthorized deserialization attempt",desc.getName());}return super.resolveClass(desc);}
    }
    
  • 通过此方法,可灵活的设置允许反序列化类的白名单,也可设置不允许反序列化类的黑名单。但反序列化漏洞利用方法一直在不断的被发现,黑名单需要一直更新维护,且未公开的利用方法无法覆盖。

  • SerialKiller 是由Luca Carettoni利用上面介绍的方法实现的反序列化类白/黑名单校验的jar包。具体使用方法可参考其代码仓库。

  • contrast-rO0是一个轻量级的agent程序,通过通过重写ObjectInputStream来防御反序列化漏洞攻击。使用其中的SafeObjectInputStream类来实现反序列化类白/黑名单控制,示例代码如下:

  • SafeObjectInputStream in = new SafeObjectInputStream(inputStream, true);
    in.addToWhitelist(SerialObject.class);in.readObject();
    

使用ValidatingObjectInputStream来校验反序列化的类

  • 使用Apache Commons IO Serialization包中的ValidatingObjectInputStream类的accept方法来实现反序列化类白/黑名单控制,具体可参考ValidatingObjectInputStream介绍;示例代码如下:

  • private static Object deserialize(byte[] buffer) throws IOException,
    ClassNotFoundException , ConfigurationException {Object obj;ByteArrayInputStream bais = new ByteArrayInputStream(buffer);// Use ValidatingObjectInputStream instead of InputStreamValidatingObjectInputStream ois = new 	ValidatingObjectInputStream(bais); //只允许反序列化SerialObject classois.accept(SerialObject.class);obj = ois.readObject();return obj;
    }
    

使用ObjectInputFilter来校验反序列化的类

  • Java 9包含了支持序列化数据过滤的新特性,开发人员也可以继承java.io.ObjectInputFilter类重写checkInput方法实现自定义的过滤器,,并使用ObjectInputStream对象的setObjectInputFilter设置过滤器来实现反序列化类白/黑名单控制。示例代码如下:

  • import java.util.List;
    import java.util.Optional;
    import java.util.function.Function;
    import java.io.ObjectInputFilter;
    class BikeFilter implements ObjectInputFilter {private long maxStreamBytes = 78; // Maximum allowed bytes in the stream.private long maxDepth = 1; // Maximum depth of the graph allowed.private long maxReferences = 1; // Maximum number of references in a graph.@Overridepublic Status checkInput(FilterInfo filterInfo) {if (filterInfo.references() < 0 || filterInfo.depth() < 0 || filterInfo.streamBytes() < 0 || filterInfo.references() > maxReferences || filterInfo.depth() > maxDepth|| filterInfo.streamBytes() > maxStreamBytes) {return Status.REJECTED;}Class<?> clazz = filterInfo.serialClass();if (clazz != null) {if (SerialObject.class == filterInfo.serialClass()) {return Status.ALLOWED;}else {return Status.REJECTED;}}return Status.UNDECIDED;} // end checkInput
    } // end class BikeFilter
    
  • 上述示例代码,仅允许反序列化SerialObject类对象,上述示例及更多关于ObjectInputFilter的均参考自NCC Group Whitepaper由Robert C. Seacord写的《Combating Java Deserialization Vulnerabilities with Look-Ahead Object Input Streams (LAOIS)》

黑名单校验修复

  • 在反序列化时设置类的黑名单来防御反序列化漏洞利用及攻击,这个做法在源代码修复的时候并不是推荐的方法,因为你不能保证能覆盖所有可能的类,而且有新的利用payload出来时也需要随之更新黑名单。
  • 但有某些场景下可能又不得不选择黑名单方案。写代码的时候总会把一些经常用到的方法封装到公共类,这样其它工程中用到只需要导入jar包即可,此前已经见到很多提供反序列化操作的公共接口,使用第三方库反序列化接口就不好用白名单的方式来修复了。这个时候作为第三方库也不知道谁会调用接口,会反序列化什么类,所以这个时候可以使用黑名单的方式来禁止一些已知危险的类被反序列化,部分的黑名单类如下:
  • org.apache.commons.collections.functors.InvokerTransformer
  • org.apache.commons.collections.functors.InstantiateTransformer
  • org.apache.commons.collections4.functors.InvokerTransformer
  • org.apache.commons.collections4.functors.InstantiateTransformer
  • org.codehaus.groovy.runtime.ConvertedClosure
  • org.codehaus.groovy.runtime.MethodClosure
  • org.springframework.beans.factory.ObjectFactory
  • com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
  • org.apache.commons.fileupload
  • org.apache.commons.beanutils

安全编码建议

  • 更新commons-collections、commons-io等第三方库版本;
  • 业务需要使用反序列化时,尽量避免反序列化数据可被用户控制,如无法避免建议尽量使用白名单校验的修复方式;

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

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

相关文章

MYSQL创建用户以及解决MYSQL新建数据库看不到相关数据库信息的解决方案

MYSQL创建用户以及解决MYSQL新建数据库看不到相关数据库信息的解决方案 首先 我们在root用户下&#xff0c;创建一个mysql用户账户 如创建一个test用户 密码123456 命令如下&#xff1a; CREATE USER ‘test’‘localhost’ IDENTIFIED BY ‘123456’; 创建了一个db1的数据库 …

唯创知音WTN6040F-8S语音芯片在空气消毒机中的应用:提升用户体验与健康保障

在现代生活中&#xff0c;空气质量成为人们越来越关注的问题。空气消毒机作为一种能够净化空气、杀灭病毒细菌的设备&#xff0c;受到了广大消费者的青睐。然而&#xff0c;对于很多用户来说&#xff0c;操作空气消毒机可能存在一定的困惑和不便。为了解决这一问题&#xff0c;…

对Spring源码的学习:Bean实例化流程

目录 SpringBean实例化流程 Spring的后处理器 Bean工厂后处理器 Bean后处理器 SpringBean实例化流程 Spring容器在进行初始化时&#xff0c;会将xml配置的<bean>的信息封装成一个BeanDefinition对象&#xff0c;所有的BeanDefinition存储到一个名为beanDefinitionMa…

Docker容器的可视化管理工具—DockerUI本地部署与远程访问

文章目录 前言1. 安装部署DockerUI2. 安装cpolar内网穿透3. 配置DockerUI公网访问地址4. 公网远程访问DockerUI5. 固定DockerUI公网地址 前言 DockerUI是一个docker容器镜像的可视化图形化管理工具。DockerUI可以用来轻松构建、管理和维护docker环境。它是完全开源且免费的。基…

【GlobalMapper精品教程】066:shp转JSON(GeoJson)案例实现

文章目录 一、JSON与GeoJson的区别二、globalmapper实现shp转JSON1. 加载shp数据2. shp转json一、JSON与GeoJson的区别 JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它基于JavaScript的语法,可以将JavaScript对象中表示的一组数据转换为字符串,在函数之…

ElasticSearch之cat recovery API

命令样例如下&#xff1a; curl -X GET "https://localhost:9200/_cat/recovery?vtrue&pretty" --cacert $ES_HOME/config/certs/http_ca.crt -u "elastic:ohCxPHQBEs5*lo7F9"执行结果输出如下&#xff1a; index shard time type sta…

2023 CCF中国软件大会(CCF ChinaSoft) “程序语义深度理解前沿进展”论坛成功召开...

2023年12月2日&#xff0c;2023年度CCF中国软件大会软件程序语义深度理解前沿进展论坛成功召开。 本次论坛由南京大学卜磊老师和国防科技大学陈振邦老师主持&#xff0c;计算机研究与发展期刊代表侯丽珊老师致辞&#xff0c;旨在反映程序语义理解及其应用相关研究前沿进展与实践…

Vue之模板语法

模板语法有两大类&#xff1a; 1.插值语法 2.指令语法 让我为大家介绍一下吧&#xff01; 一、插值语法 功能:用于解析标签体内容。 写法: {{xxx}}&#xff0c;xxx是js表达式&#xff0c;且可以直接读取到data中的所有属性。 举个例子&#xff1a; <!DOCTYPE html> &l…

探索未来新趋势:鸿蒙系统的崭新时代

探索未来新趋势&#xff1a;鸿蒙系统的崭新时代 随着科技的不断发展&#xff0c;操作系统作为计算机和移动设备的核心&#xff0c;扮演着至关重要的角色。近年来&#xff0c;一种备受瞩目的操作系统——鸿蒙系统&#xff08;HarmonyOS&#xff09;崭露头角&#xff0c;正引领着…

uniapp 微信小程序请求拦截器 接口封装

前言&#xff1a; 请求拦截器可以在我们需要传递请求头的时候使用&#xff0c;例如&#xff1a;token 也会在当token发生变化的时候给予响应&#xff0c;所以我们做好对应的判断即可 话不多说&#xff0c;直接进入正题&#xff1a; 1.首先在根目录创建common文件夹&#xff0c…

Selenium 中并行测试的重要性!

随着技术的进步&#xff0c;测试解决方案变得更具可扩展性&#xff0c;加速了团队从手动测试到Selenium测试自动化的转型。但是成年人的世界&#xff0c;没有什么是容易的。对于许多团队来说&#xff0c;并行运行多个测试仍然是不可扩展的。他们倾向于遵循传统的顺序执行测试方…

MIT6.5840-2023-Lab2A: Raft-leader election

前置知识 什么是一致性算法&#xff1f; 安全性保证&#xff0c;绝对不会返回一个错误的结果&#xff1b;可用性&#xff0c;容忍集群部分节点失败&#xff1b;不依赖时序来保证一致性&#xff1b;一条指令可以尽可能快的在集群中大多数节点响应一轮远程过程调用时完成。小部分…

uniapp实战 —— 可滚动区域 scroll-view (自适配高度,下拉刷新)

自适配高度 自定义的顶部导航栏&#xff0c;可参考博文 https://blog.csdn.net/weixin_41192489/article/details/134852124 如图可见&#xff0c;在页面滚动过程中&#xff0c;顶部导航栏和底栏未动&#xff0c;仅中间的内容区域可滚动。 整个页面的高度设置为 100%&#xf…

鸿蒙开发—学习声明式UI

基本UI描述 ArkTS通过装饰器Component和Entry装饰struct关键字声明的数据结构&#xff0c;构成一个自定义组件。自定义组件中提供了一个build函数&#xff0c;开发者需在该函数内以链式调用的方式进行基本的UI描述&#xff0c;UI描述的方法请参考UI描述规范。 基本概念 stru…

GZ029 智能电子产品设计与开发赛题第4套

2023年全国职业院校技能大赛高职组 “GZ029智能电子产品设计与开发”赛项赛卷四 题目&#xff1a;模拟工业传送带物品检测系统的设计与开发 1 竞赛任务 在智能电视机上播放工业传送带传输物品视频&#xff0c;模拟工业传送带物品检测系统&#xff08;以下简称物品检测系统&…

DALI1.0学习——BIT解码

最近在学习DALI调光相关知识并下载了Microchip提供的基于ATMega88PA的软件工程及硬件设计参考方案。写这些文章的目的就是把自己对知识的理解作一些梳理。 芯片厂果然专业&#xff0c;考虑得相当周到&#xff0c;为了芯片销量连软件和硬件方案全都提供了。芯片厂关于DALI1.0实…

【unity小技巧】实现枪武器随镜头手臂摇摆效果

文章目录 前言方法一、改变武器位置方法二、改变武器旋转结语完结 前言 如果我们视角移动转向&#xff0c;武器如果不跟着进行摇摆&#xff0c;会感觉我们的动作很生硬&#xff0c;特别是射击类游戏&#xff0c;如下 实现武器摇摆这里主要分享两种实现方法&#xff0c;一种是…

xtu oj 1271 color

题目描述 Alice在玩一个游戏&#xff0c;她在一个mn的格子里&#xff0c;随机涂黑k个格子。然后她每次可以把一行或者一列的格子染成红色&#xff0c;但是这一行中不能有黑色的格子。 请问她最多能把多少个格子涂成红色&#xff1f; 输入 第一行是一个整数T(T≤100)&#xf…

华为OD机试 - 数的分解(Java JS Python C)

题目描述 给定一个正整数 n,如果能够分解为 m(m > 1)个连续正整数之和,请输出所有分解中,m最小的分解。 如果给定整数无法分解为连续正整数,则输出字符串"N"。 输入描述 输入数据为一整数,范围为 (1, 2^30] 输出描述 比如输入为: 21 输出: 21=10+11 …

SSD数据在写入NAND之前为何要随机化?-Part1

SSD的存储介质是什么&#xff0c;它就是NAND闪存。那你知道NAND闪存是怎么工作的吗&#xff1f;其实&#xff0c;它就是由很多个晶体管组成的。这些晶体管里面存储着电荷&#xff0c;代表着我们的二进制数据&#xff0c;要么是“0”&#xff0c;要么是“1”。NAND闪存原理上是一…