很有意思的一题,当时只有长亭一家做出来了,我也是赛后才找到了正确的办法,强网杯的群里还有很多同学在问这道题的WriteUp,我就尝试在这里发一下
其实这题一开始拿到的时候,我发现在横纵方向上每隔一定的周期会重复出现相似的点,我就先把高度也改成了580,写了一个脚本提取所有的相似点并且重新拼合,得到了16张清晰可见的图,并且还能拼合回原本580*580的大小,感觉有戏。也在各个颜色通道里发现了‘希尔伯特’和‘Welcome to QWB’,以及九张像二维码但不是二维码的东西,此时虽然已经离答案很接近了,但终究还是一开始路就走错了,依靠着各种奇技淫巧无限接近答案。错误的解题过程只是提一下,脚本就不放上来误导大家的思路了。
其实还是对题目理解得不够透彻。这题的关键其实是降维打击,在threebody,bmp中出现了许多靠近但是色值相差极大的像素点,这是一个很异常的情况。
我们打开010editor这是一张正常的32bit bmp图片一个像素点的结构,四个值分别是BGRR,其中Reserver通道是保留通道,作用则是在某些能够利用该通道的软件中,会把Reserved通道视作Alpha通道看待,也即透明通道。
而当我们打开threebody.bmp时,发现相近的点颜色值相差极大,按理来说一般正常的图片,相近的地方颜色应该差不多才对。实际上你发现这些看似不相近的值,正在跨越像素之间以4为周期出现。结合上面我们提到的正常的bmp图片,你想到了什么?
说明这四个值本来应该是属于一个像素点的,这张图片遭到了‘降维打击’,原本的Reserved通道消失了,但是值却还保留着,发生了偏移。我们应该做的就是重组这些像素,让他们重归4个值一组。这张图片的属性是24bit/像素,我们将图片地址001C处的18修改为20,就可以设置该图片为32bit/像素(详情参见bmp文件的结构),然后保存,就可以得到一张高清的图。这一步其实相当于完成了一个从’BGR’到’BGRR’的一个升维的过程
我们这这张图里也能通过stegsolve看到希尔伯特了
最后的信息隐藏在Alpha通道中,不过因为这么改出来的图片不能直接显示Alpha通道的东西,只能在一些特定的软件,例如PS中看到。还得写脚本提取。得到还原了alpha通道的图片,然后放进stegsolve,查看alpha0通道,得到一张像二维码但不是二维码的小方块,一看边长正好128个像素,可以应用希尔伯特曲线。具体做法就是按照希尔伯特曲线的顺序将其转化为01串,再依据ASCII码还原成原来的信息。
本步操作需先将threebody.bmp重命名为threebody.bin
from PIL import Image
a=Image.new(mode='RGBA',size=(435,580),color=(255,255,255,255))
file=open('threebody.bin','rb').read()
tot=0
i=0
j=0
for i in range(0,435):
for j in range(0,580):
s=[]
for t in range(0,4):
s.append(file[tot])
tot+=1
a.putpixel((i,j),(s[2],s[1],s[0],s[3]))
a.show()
a.save('final.png')
得到还原了透明度的图片
得到的图放stegsolve里,查看alpha0通道,得到最后的小方块
由于上部分脚本没有处理好,在接下来希尔伯特曲线的读取过程需要先将其旋转180°
from PIL import Image
n=7
pos_=[0,0]
dir_=0
img= Image.open.('code.bmp').rotate(180)#完成了分离的128*128像素的小方块,并且旋转了180°
img.show()
black=(0,0,0)
white=(255,255,255)
#0上1右2下3左
if img.getpixel((127,127))==black:
result='0'
else:
result='1'
def move(direction):
global result
global pos_
if direction==0:
pos_[1]=pos_[1]-1
if direction==1:
pos_[0]=pos_[0]+1
if direction==2:
pos_[1]=pos_[1]+1
if direction==3:
pos_[0]=pos_[0]-1
#print((pos_[0]+2**n-1),(pos_[1]+2**n-1))
if img.getpixel((pos_[0]+2**n-1,pos_[1]+2**n-1))==black:
result=result+'0'
if img.getpixel((pos_[0]+2**n-1,pos_[1]+2**n-1))==white:
result=result+'1'
def hilL(n):
global pos_
global dir_
if n==0:
pass
if n>0:
dir_ = (dir_ +1)%4
hilR(n-1)
move(dir_)
dir_ = (dir_-1)%4
hilL(n-1)
move(dir_)
hilL(n-1)
dir_ = (dir_ -1 )%4
move(dir_)
hilR(n-1)
dir_ = (dir_+1)%4
def hilR(n):
global pos_
global dir_
if n==0:
pass
if n>0:
dir_ = (dir_ -1 ) % 4
hilL(n - 1)
move(dir_)
dir_ = (dir_ + 1) % 4
hilR(n - 1)
move(dir_)
hilR(n - 1)
dir_ = (dir_ + 1) % 4
move(dir_)
hilL(n - 1)
dir_ = (dir_ - 1) % 4
hilR(n)
print(result)
print()
运行完以上程序可以得到一个01串,放到cyberchef里,还原得到一串C语言代码,刚开始不知道这串代码是用来干什么的,看起来是一遍又一遍的把自己输出。
结果发现在第11行代码后边存在大量的长短空格,将其分别替换成10,再扔到cyberchef里,得到flag{D1mEn5i0nAl_Pr061em}
心得体会
一开始走的歪路其实都是没有理解’降维打击‘在这题中的含义,不得不说这题的题目与题目内容结合得非常好