记一道折腾了我大半天的题,今天红帽杯Misc的PicPic,最后六点多一点做出来了(比赛六点结束😭血亏323pt)。感觉做这个题的时候把所有关于fft代码实现的博客和加解密相关的论文都翻了个遍,短时间内应该不想再看到它了(
challenge 1 题目里面可以看到是一个challenge 1
的文件夹,和next_challenge.rar
的加密压缩包,合理猜测是用challenge 1解出压缩包的密码。
很容易就能发现题目给的create.py
就是生成r
、1.mkv
和2.mkv
的源码,逻辑也很容易走:
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 import osimport cv2import structimport numpy as npdef 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' )
好了,逆向手老本行,勤勤恳恳写逆算法.jpg(一些注释写代码里了):
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 import osimport cv2import structimport numpy as npos.system('ffmpeg -i 1.mkv -r 25 m/%03d.png' ) os.system('ffmpeg -i 2.mkv -r 25 p/%03d.png' ) def ifft (m,p,imgsize ): s1=np.exp(m) s1_angle=p s1_real=s1*np.cos(s1_angle) s1_imag=s1*np.sin(s1_angle) s2=np.zeros(imgsize,dtype=complex ) s2.real=np.array(s1_real) s2.imag=np.array(s1_imag) f2shift=np.fft.ifftshift(s2) img_back=np.fft.ifft2(f2shift) img_back=np.abs (img_back) return img_back def rev_mapping (new_data,t=( ),down=0 ,up=255 ): new_data=new_data.astype(np.float64) if t==(): data_max=2 *np.pi data_min=0 else : data_max=t[1 ] data_min=t[0 ] interval=data_max-data_min new_interval=up-down data=(new_data-down)*interval/new_interval+data_min return data file1=os.listdir('m' ) file2=os.listdir('p' ) unpack=[] with open ('r' ,'rb' ) as f: for i in range (200 ): s=f.read(8 ) unpack.append(struct.unpack('!ff' ,s)) for i in range (200 ): img1=cv2.imread(f'm/{file1[i]} ' , cv2.IMREAD_GRAYSCALE) img2=cv2.imread(f'p/{file2[i]} ' , cv2.IMREAD_GRAYSCALE) m=rev_mapping(img1,unpack[i]) p=rev_mapping(img2) new_img=ifft(m,p,img1.shape) cv2.imwrite(f'frame/{file1[i]} ' ,new_img) os.system('ffmpeg -i frame/%03d.png -r 25 secret.mp4' )
然后能得到一个secret.mp4
,得到了压缩包密码。
zs6hmdlq5ohav5l1
challenge 2 查看hint.txt
可以看到:
1 <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 >
查了一下是Mathematical Markup Language (MathML),在菜鸟教程在线编辑器 跑一下可以看到:
然后写上一关的逆算法的时候看到过两幅图像幅度谱和相位谱替换_陨星落云的博客-CSDN博客 ,联想到这个式子有点幅度谱和相位谱交换的味道,所以直接把文章里的脚本拿来用,秒解:
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 import numpy as npimport cv2from matplotlib import pyplot as plt def magnitude_phase_split (img ): dft = np.fft.fft2(img) dft_shift = np.fft.fftshift(dft) magnitude_spectrum = np.abs (dft_shift) phase_spectrum = np.angle(dft_shift) return magnitude_spectrum,phase_spectrum def magnitude_phase_combine (img_m,img_p ): img_mandp = img_m*np.e**(1j *img_p) img_mandp = np.uint8(np.abs (np.fft.ifft2(img_mandp))) img_mandp =img_mandp/np.max (img_mandp)*255 return img_mandp img1 = cv2.imread("mix1.png" ,0 ) img2= cv2.imread("mix2.png" ,0 ) img1_m,img1_p = magnitude_phase_split(img1) img2_m,img2_p = magnitude_phase_split(img2) img_1mAnd2p = magnitude_phase_combine(img1_m,img2_p) img_2mAnd1p = magnitude_phase_combine(img2_m,img1_p) plt.figure(figsize=(10 ,8 )) plt.subplot(221 ) plt.xlabel("1" ) plt.imshow(img1,cmap="gray" ) plt.subplot(222 ) plt.imshow(img2,cmap="gray" ) plt.xlabel("2" ) plt.subplot(223 ) plt.imshow(img_1mAnd2p,cmap="gray" ) plt.xlabel("1m+2p" ) plt.subplot(224 ) plt.imshow(img_2mAnd1p,cmap="gray" ) plt.xlabel("2m+1p" ) plt.show()
就能看到:
扫一下左下二维码能得到文本:0f88b8529ab6c0dd2b5ceefaa1c5151aa207da114831b371ddcafc74cf8701c1d3318468d50e4b1725179d1bc04b251f
final challenge
被上个challenge的那一串文本误导了,一直以为是用来解密图片的密钥啥的,结果白白卡了三个多小时(菜鸡落泪
中间查了n多资料就不细说了(
总之最后拿相位掩膜+傅立叶变换进行图像加密_isyiming的博客-CSDN博客 的脚本改了一下:
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 I=imread('phase.png' ); A=im2double(I); figure ,imshow(A);title('The original image' ); Y=fftshift(A); figure ,imshow(Y);title('shifted image' );B=fft2(Y); figure ,imshow(B);title('FFT imagea' );M1=0.814 M11=exp (i *2 *pi *M1); M111=B.*M11; figure ,imshow(M111);title('phase mask' );D=fft2(M111); figure ,imshow(D);title('FFT image b' );C=abs (D); C1=ifft2(C); figure ,imshow(C1);title('2-D IFFT b' );C11=C1.*exp (-i *2 *pi *M1); figure ,imshow(C11);title('remove mask' );C111=ifft2(C11); figure ,imshow(C111);title('2-D IFFT a' );C1111=ifftshift(C111); F=abs (C1111); figure ,imshow(F);title('The decrypted image' );
就能看到其中一张图(phase mask
)是:
好像是没分离好(等其他大佬的wp),不过能大概辨认出key是a8bms0v4qer3wgd67ofjhyxku5pi1czl
(其实是用AES解密脚本试的
1 2 3 4 5 6 7 8 9 from Crypto.Cipher import AESimport binasciikey=b"a8bms0v4qer3wgd67ofjhyxku5pi1czl" aes=AES.new(key,AES.MODE_ECB) cipher=binascii.unhexlify("0f88b8529ab6c0dd2b5ceefaa1c5151aa207da114831b371ddcafc74cf8701c1d3318468d50e4b1725179d1bc04b251f" ) text=aes.decrypt(cipher) print(text)
就能得到flag:flag{1ba48c8b-4eca-46aa-8216-d164538af310}