MeePwnCTF 2018 Qual Re Image_Crackme

Introduction

The programme implements a simple logic in image processing. Since I started to learn GO binary just from CrossCTF, I spend a lot of time reversing the logic of the challenge.

Programme Analysis

The programme first asks you to input a string of any length as key. Then the programme loads one 160*160 image and stores the grayscale of each pixel in a 0x6400-byte array from the first row to the last row.
Then the array is encrypted with key. The encryption algorithm is simple

for i in range(0, len(array)):
     array[i] = (array[i] * key[i % keyLength]); 

Then the programme will generate the output file according to each byte in the encrypted array. According to the range the character resides in, different character is picked for output.

The first step of this challenge is to get the length of the key. Since the key is encrypted with array periodically, we can pad the key with a given length and test if output of MeePwn{XXXXX} fits the corresponding byte in target result.
After deciding the length of the key, use the same method above to decide the byte in key byte by byte.

Exploit

fd = open("./meepwn_log_new", "rb");
content = fd.read(0x6400);
fd.close();

#load image array
imageArray = list();

for i in range(0, len(content)):
    imageArray.append(ord(content[i]));

#load target array
fd = open("./MeePwn.ascii.bak");
lines = fd.readlines();
fd.close();
targetArray = list();

for line in lines:
    line = line[:-1];
    for c in line:
        targetArray.append(ord(c));

print("Raw data length: 0x%x" % len(imageArray));
print("Target data length: 0x%x" % len(targetArray));

def displayRow(array, row):
    for i in range(0, 160):
        print("0x%02x" % array[row*160 + i]);
        if(i % 16 == 15):
            print();

def encryptArray(array, key):
    keyLength =len(key);
    arrLength = len(array);
    ans = list();

    for i in range(0, arrLength):
            ans.append(  (array[i] * ord(key[i%keyLength])) & 0xff);
    return ans;

def calculateChar(value):
    if(value>=0 and value <= 0x1b):
        return '@';
    elif(value >= 0x1c and value <= 0x37):
        return "#";
    elif(value >= 0x38 and value <= 0x53):
        return '8';
    elif(value >= 0x54 and value <= 0x6f):
        return '&';
    elif(value >= 0x70 and value <= 0x8b):
        return 'o';
    elif(value >= 0x8c and value <= 0xa7):
        return ':';
    elif(value >= 0xa8 and value <= 0xc3):
        return '*';
    elif(value >= 0xc4 and value <= 0xdf):
        return '.';
    elif(value >= 0xe0 and value <= 0xff):
        return ' ';

def transformArray(array):
    ans = list();
    arrLength = len(array);
    for i in range(0, arrLength ):
        ans.append( ord(calculateChar(array[i])) );
    return ans;

def showFinalResult(array, row):
    for i in range(0, 160):
        print("%c" % chr(array[row * 160 + i]));
    print();

def checkRow(array, target, row):
    displayRow(array, row);
    print("target");
    displayRow(target, row);

def compareRow(array, target, row):
    for i in range(0, 160):
        if(array[row*160 + i] != target[row*160 + i]):
            return False;
    return True;

def fullCompare(array, target):
    for i in range(0, 160):
        if(not compareRow(array, target, i)):
            return False;
    return True;

## A basic procedure of generating target string
## generate key
#  targetKey = "abcdefghijk";
## encrypt raw data
#  encryptedArr = encryptArray(imageArray, targetKey);
## tranform the encrypted data to readable data
#  transformedArr = transformArray(encryptedArr);
## helper function to read the data
#  displayRow(transformedArr, 159);
#  showFinalResult(transformedArr, 159);
				
def getKeyLengthOld():
    #showFinalResult(targetArray, 0);
	for i in range(0, 0x60):
		tmp = 'A'*i;
		key = "MeePwn{%s}" % tmp;
		encryptedArr = encryptArray(imageArray, key);
		transformedArr = transformArray(encryptedArr);
		flag = True;
		for k in range(0, 0x6400/len(key)):
			if(transformedArr[0 + k*len(key)] != targetArray[0 + k*len(key)] ):
				print("Flag not of size %d at %d th trial" % (i, k));
				flag = False;
				break;
		if(flag):
			print("Flag length is %d" % (i));


def verifyKey(key):
	encryptedArr = encryptArray(imageArray, key);
	print("0x%x" % encryptedArr[3243]);
	transformedArr = transformArray(encryptedArr);
	#load gen array
	fd = open("./MeePwn.ascii");
	lines = fd.readlines();
	fd.close();
	verifyArray = list();

	for line in lines:
		line = line[:-1];
		for c in line:
			verifyArray.append(ord(c));
	print(len(verifyArray));
	print(len(transformedArr));
	for i in range(0, 0x6400):
		if(verifyArray[i]!=transformedArr[i]):
			print("0x%x, 0x%x, 0x%x" % (i, verifyArray[i], transformedArr[i]));
			return False;
	return True;
	
#print(verifyKey("abcdefghijk"));

#getKeyLengthOld();
#get key length 25
def checkSolKeyByte(anskey, n):
	for i in range(0x20, 0x7f):
		c = chr(i);
		tmp = anskey + c;
		tmp = tmp.ljust(25, 'A');
		tmpKey = "MeePwn{%s}" % tmp;
		encryptedArr = encryptArray(imageArray, tmpKey);
		transformedArr = transformArray(encryptedArr);
		flag = True;
		for k in range(0, 0x6400/len(tmpKey)):
			if(transformedArr[7 + n + k*len(tmpKey)] != targetArray[7 + n + k*len(tmpKey)] ):
				#print("Flag not of size %d at %d th trial" % (i, k));
				flag = False;
				break;
		if(flag):
			print("Found char for %d is %c" % (n, chr(i)));
			return chr(i);

def checkSolKey():
	ans = "";
	for i in range(0, 25):
		ch = checkSolKeyByte(ans, i);
		ans = ans + ch;
		print(ans)
	print(ans);

checkSolKey();

Conclusion

In this challenge, I tried to get the gray scale of each pixel of the image with PIL module in python. But I soon find that the value extracted from the image is different from the actual value, so I dump the value directly from memory using debugger. The dump file could be found on my github repo[1]. However, in the first dump, some bytes are corrupted and I spend a lot of time verifying if the dump data is wrong :(.

Reference

[1] https://github.com/dangokyo/CTF/tree/master/CTF2018/MeepwnCTF_Qual/RE/ImageCrackme

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.