翻译

本文另有 英文版本。

image-crackme

想用 IDA Pro 逆向,结果打开到一半就崩溃了,只能靠猜了。试着运行几次 image-crackme.exe,发现生成的 MeePwn.ascii 的内容总是 160x160 的一大堆乱七八糟的字符,同时目录下还有一个 MeePwn.jpg,尺寸同样是 160x160,而且缺少这个文件程序就没法运行。推测图片中的像素和输出有某种联系,用画图做了一张 160x160 的纯白色图片替换了原来的图(为了使所有的像素都相同),然后试着输入 0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}(ASCII 48–126)作为 key,产生了十分有规律的输出:

image-crackme 的规律输出

这个 pattern 就很有意思了,可以推测输入的 key 是被循环扩展到 160x160(即 ABCDEF -> ABCDEFABCDEF…),然后每个字节分别和 MeePwn.jpg 中对应的像素进行某种运算,才会产生这种循环的输出。

题目提示说 flag 是 MeePwn{…} 的形式,可以根据这一特点,用不同长度的 flag 作为输入,比较输出的 MeePwn.ascii 和 MeePwn.ascii.bak 中相同符号的个数,从而得出 flag 的真正长度。

 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
import os

def march():
    maxlen=160
    marches=0
    s1=open('MeePwn.ascii').read().split('\n')
    s2=open('MeePwn.ascii.bak').read().split('\n')
    for i in range(maxlen):
        for j in range(maxlen):
            if s1[i][j]==s2[i][j]:
                marches+=1
    return marches

def main():
    alla=160*160
    maxklen=40
    mars=[]
    keys=[]
    for klen in range(1,maxklen):
        key='MeePwn{'+'?'*klen+'}'
        os.system('echo %s|.\\image_crackme.exe'%key)
        marches=march()
        mars.append(marches)
        keys.append(key)
    print(mars)
    print(keys)
    for i in range(len(mars)):
        print('%d/%d %s %d'%(mars[i],alla,keys[i],len(keys[i])))

if __name__=='__main__':
    main()

'''RESULT
2880/25600 MeePwn{????????????????????????} 32
8107/25600 MeePwn{?????????????????????????} 33
2836/25600 MeePwn{??????????????????????????} 34
'''

结果中 key 长度为 33 时相同符号个数明显高于其他(同样高的还有 33 的因数 11),因此 flag 长度为 33。得到这一长度后,只要逐字符爆破就可以了,即比对 key 中某个字符循环扩展后所在位置上相同符号的个数。

 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
import os

def march(index,klen):
    alla=25600
    i=index
    marches=0
    s1=''.join(open('MeePwn.ascii').read().split('\n'))
    s2=''.join(open('MeePwn.ascii.bak').read().split('\n'))
    while i<alla:
        if s1[i]==s2[i]:
            marches+=1
        i+=klen
    return marches

def test(rawkey,index,charset):
    keylen=33
    keys=[]
    mars=[]
    for ch in charset:
        key=rawkey[:index]+ch+rawkey[index+1:]
        os.system('echo %s|.\\image_crackme.exe'%key)
        marches=march(index,keylen)
        mars.append(marches)
        keys.append(key)
    keychars=[]
    for i in range(len(charset)):
        if mars[i]>700:
            keychars.append(charset[i])
    print(keychars)
    return keychars

def main():
    keycharss=[]
    rawkey='MeePwn{?????????????????????????}'
    charset='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-=?!+@%*/\\'
    for index in range(7,32):
        keycharss.append(test(rawkey,index,charset))
    #keycharss=[['g'], ['0'], ['l'], ['4'], ['n'], ['g'], ['_'], ['A'], ['s'], ['c'], ['1'], ['1'], ['A'], ['r'], ['t'], ['_'], ['1'], ['S'], ['_'], ['4'], ['w'], ['S'], ['0'], ['m'], ['e']]
    flag='MeePwn{'+(''.join(map(lambda x:x[0],keycharss)))+'}'
    assert(len(flag)==33)
    print(flag)

if __name__=='__main__':
    main()

flag:MeePwn{g0l4ng_Asc11Art_1S_4wS0me}

不得不承认自己和真正的 CTF 选手还是有很大的差距的,别的题看的一头雾水,毫无思路。