CTF | 2021 CISCN初赛 Misc WriteUp


引言

2021 全国大学生信息安全竞赛创新实践能力赛 (CISCN)

比赛时间: 2021-05-15 09:00 ~ 2021-05-16 09:00

比赛官网: http://www.ciscn.cn/

一年一度的国赛又来了,连续打 24h 太难受啦!

今年国赛正好碰上咱 ddl,打完国赛又去赶 ddl 去了,太难顶了。

于是一直也没时间来整理 WriteUp,今天摸鱼,就来写几道 misc 题目好了。

第一阶段

tiny traffic

抓包文件导出 http 对象

192.168.2.1 是路由器,192.168.2.193 是台 NAS,5000端口开了个 python web 服务。看上去最后的是个 rpc

flag_wrapper 导出内容,解压之后得到

testsecret 用 brotli 解压

import brotli

with open('test', 'rb') as fin:
    s = fin.read()
x = brotli.decompress(s).decode()
print(x)

with open('secret', 'rb') as fin:
    s2 = fin.read()
y = brotli.decompress(s2)  
print(y)

with open('secret1', 'wb') as fout:
    fout.write(y)

发现 testProtocol Buffzers

参考 https://github.com/MonsterMeng92/protobuf/blob/master/docs/%E5%AD%A6%E4%B9%A0%E6%8C%87%E5%8D%97(proto3).md

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 是序列化后的密文。

参考 Protobuf协议逆向解析-APP爬虫

先下载 Protobuf 编译器和调用编译器的接口,这里直接用 python 了

https://github.com/protocolbuffers/protobuf/releases/

解码得到

1: 200
2: 15100450
3 {
  1 {
    12: 0x35343332
  }
  2: "7af2c"
}
3 {
  1: "7889b0"
  2: "82bc0"
}
4: 16453958
5: "d172a38dc"

发现并不是很清楚,先生成 python 代码

protoc.exe test.proto --python_out=.

然后写个脚本解码

import test_pb2
r = test_pb2.PBResponse()
with open('secret1', 'rb') as fin:
    s = fin.read()
r.ParseFromString(s)
print(r)

得到

code: 200
flag_part_convert_to_hex_plz: 15100450
dataList {
  flag_part: "e2345"
  junk_data: "7af2c"
}
dataList {
  flag_part: "7889b0"
  junk_data: "82bc0"
}
flag_part_plz_convert_to_hex: 16453958
flag_last_part: "d172a38dc"

拼成 flag,其中 junk_data 是垃圾数据不要

CISCN{e66a22e23457889b0fb1146d172a38dc}

running_pixel

开局一张动图

running_pixel

一共有 382 帧,逐帧查看,导出每一帧。

import os
from PIL import Image, ImageSequence

def parseGIF(gifname):
    # 将gif解析为图片
    # 读取GIF
    im = Image.open(gifname)
    # GIF图片流的迭代器
    iter = ImageSequence.Iterator(im)
    # 获取文件名
    file_name = gifname.split(".")[0]
    index = 1
    # 判断目录是否存在
    pic_dirct = "imgs/{0}".format(file_name)

    def mkdirlambda(x): return os.makedirs(
        x) if not os.path.exists(x) else True  # 目录是否存在,不存在则创建
    mkdirlambda(pic_dirct)
    # 遍历图片流的每一帧
    for frame in iter:
        print("image %d: mode %s, size %s" % (index, frame.mode, frame.size))
        frame.save("imgs/%s/frame%d.png" % (file_name, index))
        index += 1

if __name__ == "__main__":
    parseGIF("running_pixel.gif")

随意看前几帧,放大来看,发现有个像素点在动。

所以题目名称中的跑动的像素应该就是这个了。

提取得到这个像素点颜色 RGB 为 (233, 233, 233),而背景色是 (247, 247, 247)

另外发现每隔一定帧数,这个像素点会消失,然后在另一个地方出现。

于是写个脚本跑所有的帧,把这个像素点的轨迹画出来,每消失一次保存一张图片。

"""
MiaoTony
"""
import cv2
import os
from PIL import Image, ImageOps
import numpy as np

im2 = Image.new('RGB', (400, 400))
last_cnt = 0
# list(os.walk('imgs'))[0][2]
for cnt in range(1, 383):
    img_name = f'imgs/frame{cnt}.png'
    im = Image.open(img_name)
    rgb_im = im.convert('RGB')
    # print(rgb_im.size)
    # (400, 400)
    width = rgb_im.width
    height = rgb_im.height

    for j in range(height):
        for i in range(width):
            r, g, b = rgb_im.getpixel((i, j))
            if (r, g, b) == (233, 233, 233):
                # print('cnt', cnt)
                # print(i, j)
                if cnt != last_cnt + 1:
                    print(cnt)
                    im2.save(f'cnt{cnt}.png')
                im2.putpixel((j, i), (255, 255, 255))
                last_cnt = cnt

im2.show()
im2.save('final.png')

最后的图是这样的。

手动按顺序把出来的图片各个字母提取出来。

(其实更好的思路是每次像素点消失的时候新建一张图的……看的要眼瞎了

出来是个 UUID 的格式。

12504D0F-9DE1-4B00-87A5-A5FDD0986A00

裹上 CISCN,然后发现 19:00,草,交不上去了!

心态炸了!!!

(后来看大佬 wp 说这里还要转成小写再交上去

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

第二阶段

隔空传话

题目描述:Alice 和 Bob 进行隔空传话的时候被我“偷听”到了,你能帮我分析分析他们在交流什么吗?
注意:flag形式:CISCN{XXXXX}

你们又在加密通信!!!

(这题复现的,然而过去有一段时间了,当时找的资料忘记记录了,现在又懒得再找了,就佛系写一写8

(还是去找了下资料 唉

开局给了一堆格式类似的数据。

查了一下发现是 SMS 短信里用的 PDU 编码。

PDU 编码规则

还找到了 在线 PDU 格式编码/解码的网站

https://tool.letmetellyou.xyz/pdu/ 或者 http://www.sendsms.cn/pdu/

在 pypi 找到了一个 python 的 module: smspdu

手动解码前几条

发现是

hello,bob!what is the flag?
the first part of the flag is the first 8 digits of your phone number
那其他部分呢
看看你能从这些数据里发现什么?w465
5b4c4ce7b6d5edd6d5cb961fca84f193ca71471db155b62c9df5ea1ebed933929de07bebcdb7853ddaf6303ac6fbaaa0fff6bb23cbfefbecd716028173e1259796fbeebf3f12f43ea54fcfeee54f11c8
f5a91d7cb54fd0b83e927bbfbe7d6a121d32649748f453ca0fbffe56162c5e5c4e3f757804e9aeb17a8b441513c78591c43c9493bb2567c6a475e69c59912c9e2f0785fe43761a523efa7c7479effdbf
...

flag 的第一部分是号码的前8位,+8615030442000,那就是 15030442

之后就一堆 hex 编码。

写个脚本提取并导出到文件。

from smspdu.codecs import GSM, UCS2
from smspdu.easy import easy_sms
from binascii import unhexlify

# x = easy_sms('04910180F6040D91685130402400F0000012405291443408A0E65A381723DFC6E21ACD4C868971B3724E76138BCDE2F28D6C0BC76431F24C66A3E56E349C99569B8DC330B3D86C2ED76C319B6C5C2BD7C6B4F2CC7CABDD70305A39172E8B63B7304E4CA3C56AB1D9F886ABE56263DA6C9CA3E56662B1AC66BB8D6D61DAAD56B6E5C6B55C2E261BE7CA3233EC86AB99CBB4D9CD160BD764B3B2397C1BDF68B75CD96C268BCD')
# print(x)

with open('data.txt', 'r', encoding='utf-8') as fin:
    s = fin.read()

l = s.strip().split('\n')
l1 = l[4:]

x = ''
for i in l1:
    tmp = easy_sms(i)['content']
    print(tmp)
    x += tmp

print(x)
# x.encode()
y = unhexlify(x)

with open('decode.dat', 'wb') as fout:
    fout.write(y)

binwalk 一下发现有张 png 图片,提取出来。

然而发现打不开……

然后比赛的时候试了用 PCRT 来修复,但没成功。网上找了个工具然后要收钱,气死喵喵了!!!

后来发现需要按照时间顺序排序。

唉好烦(

"""
MiaoTony
"""
from smspdu.codecs import GSM, UCS2
from smspdu.easy import easy_sms
from binascii import unhexlify

with open('data.txt', 'r', encoding='utf-8') as fin:
    s = fin.read()

l = s.strip().split('\n')
l1 = l[4:]

x = []
ts = []
for i in l1:
    tmp = easy_sms(i)
    content = tmp['content']
    t = tmp['date']
    x.append(content)
    ts.append(t)

l2 = sorted(zip(x, ts), key=lambda x: x[1])
l3 = list(zip(*l2))[0]
info = ''.join(l3)
# type(info)
# str
y = unhexlify(info)

with open('decode2.dat', 'wb') as fout:
    fout.write(y)

得到一张图,终于能打开了,而且整个数据就是一整张图片。然而还是没思路。

看了大佬们的 wp 才发现,w465 是宽度为 465 的意思……草!

改了之后得到

于是 flag 就是

CISCN{15030442_b586_4c9e_b436_26def12293e4}

第三阶段

robot

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

Robot.rspag 是 RobotStudio 文件,cap.pcapng 是网络流量抓包,Control 目录下是一个图形化的基于 C# 写的一个图形化上位机,以及对应的 dll 文件。

用 ILSPY 反汇编 exe,发现其实都是调用底层 dll 提供的 SDK 接口,并没有自己去实现网络通信相关的功能。

参考相关的资料也是如此。

https://blog.csdn.net/weixin_39090239/article/details/81275759

https://blog.csdn.net/weixin_42837024/article/details/100542204

https://zhuanlan.zhihu.com/p/62390089?ivk_sa=1024320u

https://developercenter.robotstudio.com/

之后在流量里发现有一系列设置坐标的,盲猜这个就是机器人画的东西了。

于是写个脚本提取一下,画张图。

"""
MiaoTony
"""
import re
import cv2
import os
from PIL import Image
import numpy as np

im = Image.new('RGB', (400, 400))

width = im.width
height = im.height


with open('test.dat', 'r', encoding='utf-8') as fin:
    s = fin.read()

re_pos = re.compile(r'tgPos{(\d+)}\.Value\.(\[\d+,\d+,\d+\])')
l = re_pos.findall(s, re.M)

with open('data.dat', 'w', encoding='utf-8') as fout:
    fout.write(str(l))

points = [eval(x[1])[:2] for x in l]
# l = [[27, 36, 0], [28, 35, 0], [29, 35, 0], [31, 35, 0], [32, 35, 0], [33, 35, 0], [35, 35, 0],
#      [36, 35, 0], [37, 35, 0], [39, 34, 0]]

for i in points:
    im.putpixel(i, (255, 255, 255))

im.show()
im.save('info.png')
# easy_robo_xx

还原出来机器人绘制的字符串为 easy_robo_xx

md5 d4f1fb80bc11ffd722861367747c0f10

CISCN{d4f1fb80bc11ffd722861367747c0f10}

小结

感谢 360 首次将 CTF 靶场变成了考场。

比赛环境

初赛就这么肝也太顶了,打不动了 Orz

华东北赛区也太卷了吧,喵呜呜呜。

唉,不过咱没进复赛,喵呜呜呜呜。

害,问题不大,可能也是最后一次国赛了吧,难得大师傅们带咱玩,也非常感谢队友一起打。

之后大佬们好好玩,咱溜了溜了喵。


文章作者: MiaoTony
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 MiaoTony !
评论
  目录