Translation

This post is also available in Simplified Chinese.

image-crackme

I tried to reverse the challenge with IDA Pro, but IDA crashed halfway through loading it, so I had to work by inference. After running image-crackme.exe several times, I found that it always generated a MeePwn.ascii containing a 160×160 block of seemingly random characters. The directory also contained a 160×160 MeePwn.jpg, without which the program would not run. I suspected a relationship between the image pixels and the output. I replaced the original with a completely white 160×160 image so every pixel was identical, then entered 0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} (ASCII 48–126) as the key. The output had a very regular pattern:

Regular output from image-crackme

This pattern is revealing. The input key is likely repeated to fill 160×160 positions (ABCDEF → ABCDEFABCDEF…), after which every byte is combined somehow with the corresponding pixel in MeePwn.jpg to produce the repeated output.

The prompt says the flag has the form MeePwn{…}. We can therefore try flags of different lengths and compare the number of matching symbols between the resulting MeePwn.ascii and MeePwn.ascii.bak to determine the true flag length.

 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
'''

With a key length of 33, the number of matches is clearly much higher than for the others (the factor 11 produces a similarly high result), so the flag is 33 characters long. Once the length is known, each character can be brute-forced independently by counting matches at all positions occupied by that character after the key is repeated.

 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}

I have to admit that there was still a large gap between me and real CTF players. I could make neither head nor tail of the other challenges.