圣诞莫得安排于是跑去做了一下ChaMd5的圣诞题,不得不说让我这个逆向手很有做misc的成就感哈哈哈(误)。既然活动都结束了就来放一下wp吧,顺便整理一下批量处理文件的方法(pyinstaller批量解包+批量补文件头转格式+python和命令行的批量自动交互)。
题目指路:快打开你的2020圣诞礼物! - ChaMd5安全团队
So…begin! 下载题目压缩包,解压可以看到有59个exe。
(好家伙,这是考批量处理文件吧= =不得不说图标还挺好看的,费心了
不过看到自定义图标,逆向人第一个就想到了这些是用pyinstaller打包的exe啦。虽然也有可能是MFC,不过这里明显不是。
随便拖一个exe进ida,查看字符串看到有很多“py”字样,很明显就是pyinstaller这条路。
所以果断走流程解包(单个文件处理可以看->RE套路 - 关于pyinstaller打包文件的复原 | c10udlnk_Log ,这里不细讲了,重点在多文件的批量处理)。
bat批量应用python脚本 这里写bat脚本
1 2 3 4 5 @echo off for %%i in (*) do ( echo %%i python2 pyinstxtractor.py %%i )
(*)指当前目录,%%i就像平时用的for(int i=0;……)的那个i一样是个变量,echo是为了看处理到哪个文件了。
处理结束后得到59个解压文件包
python批量提取 然后继续写脚本(这次用python,因为bat实在是不熟)把文件夹里的无后缀文件提取出来,复制到manyPYC文件夹下,方便后续处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import osimport os.pathimport reimport shutilsrc='./' dst='../manyPYC/' allFile=os.listdir(src) pattern=r'^[0-9]+$' for fileName in allFile: if os.path.isdir(fileName): for file in os.listdir(fileName): if re.match(pattern,file)!=None : num=file shutil.copy(src+fileName+'/' +file,dst+num) if file=='struct' : shutil.copy(src+fileName+'/' +file,dst+num+'_struct' )
一般是两个有用的,一个是struct,另一个文件名在entry point里会提到,一般是跟exe同名的,不过这里不一样(是数字。
另外这里线性扫描一定是先扫到以数字命名的文件再到同目录下的struct文件,所以不用担心num未定义的问题。
python批量补头修复pyc 根据uncompyle的反编译规则,批量添加magic number,并输出.pyc。
1 2 3 4 5 6 7 8 9 10 11 12 13 import ossrc='.' allFile=os.listdir(src) for file in allFile: if 'struct' in file: with open (file,'rb' ) as f: magic=f.read(8 ) with open (file[:-7 ],'rb' ) as f: context=f.read() with open (file[:-7 ]+'.pyc' ,'wb' ) as f: f.write(magic) f.write(context)
提取struct的前八个字节补到同名文件里去,并改名为.pyc。
注意:这里提前用010Editor看了一下以数字命名的文件和struct是差八个字节,有点特殊。
bat批量应用指令 然后写bat脚本执行:
1 2 3 4 5 @echo off for %%i in (*) do ( echo %%i uncompyle6 %%i > %%i.py )
得到59个python文件。
这里其实是出来像1.pyc.py
这样的文件,有强迫症的朋友(比如我)可以写个脚本把中间的.pyc去掉hhh。
python和命令行的批量自动交互 随便查看一个(比如1.py
)
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 import hashlib, base64from Crypto.Cipher import AESfrom Crypto import Randomdef decrypt (data, password ): bs = AES.block_size if len (data) <= bs: return data unpad = lambda s: s[0 :-ord (s[(-1 )])] iv = data[:bs] cipher = AES.new(password, AES.MODE_CBC, iv) data = unpad(cipher.decrypt(data[bs:])) return data encrypt_data = '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' md5 = hashlib.md5() fn = raw_input() md5.update(fn) if '4a47a0db6e60853dedfcfdf08a5ca249' == md5.hexdigest(): print 'Are you robot?' print '25 + 3 = ?' ans = int (raw_input()) if int (ans) == 28 : try : md5 = hashlib.md5() inp = str (int (ans)) md5.update(inp) password = md5.hexdigest() encrypt_data = base64.b64decode(encrypt_data) decrypt_data = decrypt(encrypt_data, password) f = open (fn, 'wb' ) f.write(decrypt_data) f.close() except : pass
可以看到,逻辑是先输入一个字符串,第一个check是md5值等于一个已给值(通过chamd5的网站查到该md5值为1.png,突然扣题?hhh),第二个check是简单的人机测试,都通过的话就会输出一张1.png
的图片。
这个地方的批量处理是我卡得最最最最最最久的一个地方 TvT
因为bat完全是现学,不会写,于是想到用python进行跟命令行的交互。
一开始查到的资料是用subprocess,但是读输出的时候死活读不进去(因为要过人机测试就得把式子读进去),去查subprocess文档没什么头绪,往后查又说是什么输出流阻塞balabala,总之没太看懂(tcl
后来好不容易看到有一篇提到说用pexpect。然后查了一些文档,诶,居然交互成功了!暴风哭泣!
理论上说前面用bat写的地方都可以改成用pexpect写,不过这不是之前还不知道嘛(。)
以及,多亏这里搞通了,后面xctf刚好遇到一道逆向是可以用这种命令行交互爆破的(不是pwntools能连上的那种特殊架构文件),直接拿脚本改改就能用,省了几个小时的搜索时间hhh。
于是开始写脚本进行多次交互啦,逻辑很简单:
1 2 3 4 5 6 7 8 9 10 import pexpectfor i in range (1 ,60 ): r=pexpect.spawn("python2" ,[str (i)+".py" ]) r.sendline(str (i)+".png" ) r.readline() r.readline() exper=r.readline()[:-6 ] r.sendline(str (eval (exper))) r.wait()
得到59个png。
python连接png 当时做的时候因为懒得搞图像识别,所以直接手动记录这些图片上的字符。后来受到官方上发的wp的启发,感觉可以把这些png用PIL库连起来,就不用一张张点着看了。
这里我是把每个图片最左边6px提取出来横向拼接成一张图片,方便照着打hhh,调整了一下让字符大概居中排列(因为原图背景是透明的,所以这里提取出来就成黑的了= =。
png连接脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from PIL import Imagecnt=0 base=6 colorData=[[] for _ in range (base*59 )] for x in range (1 ,60 ): img=Image.open (str (x)+'.png' ) rgb_img=img.convert('RGB' ) width,height=img.size for i in range (base): col=i+cnt*base for j in range (height): colorData[col].append(rgb_img.getpixel((i,j))) cnt+=1 widthALL=len (colorData) heightALL=max ([len (arr) for arr in colorData]) imgALL=Image.new("RGB" ,(widthALL,heightALL)) for i in range (widthALL): h=len (colorData[i]) for j in range (h): imgALL.putpixel([i,(heightALL-h)//2 +j],colorData[i][j]) imgALL.save("res.png" )
通过观察可以发现,有用的字符都为红/绿/蓝的纯色,于是在脚本上做一些改进,把符合条件的像素点置黑,其余置白,更方便观察。
最后改进的脚本:
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 from PIL import Imagecnt=0 base=6 RED=(255 ,0 ,0 ) GREEN=(0 ,255 ,0 ) BLUE=(0 ,0 ,255 ) BLACK=(0 ,0 ,0 ) WHITE=(255 ,255 ,255 ) colorData=[[] for _ in range (base*59 )] for x in range (1 ,60 ): img=Image.open (str (x)+'.png' ) rgb_img=img.convert('RGB' ) width,height=img.size for i in range (base): col=i+cnt*base for j in range (height): if rgb_img.getpixel((i,j)) in [RED,GREEN,BLUE]: colorData[col].append(BLACK) else : colorData[col].append(WHITE) cnt+=1 widthALL=len (colorData) heightALL=max ([len (arr) for arr in colorData]) imgALL=Image.new("RGB" ,(widthALL,heightALL)) for i in range (widthALL): h=len (colorData[i]) for j in range (h): imgALL.putpixel([i,(heightALL-h)//2 +j],colorData[i][j]) imgALL.save("res.png" )
轻松记录下59个字符:https://www.chamd5.org/e1f10acc44f611ebb3780242ac130002.png
打开有
get flag flag{ChaMd5_Ch33r_Up}
没想到这么晚交还水了个第三哈哈哈,溜了溜了(