The id0.Blog

Mitre CTF 2019 Writeups

Here are my writeups for Mitre CTF

My team was Definitely Not id01 Casually Soloing for this CTF

Medium is Overrated

200 points, Web

This problem is very similar to the easier Web-150 problem, where we find a Bazaar version control system exposed in the website.
This can be cloned using a simple
bzr branch http://138.247.13.104
Now, here is where the challenges differ. When we branch this challenge, we can notice that it takes noticably longer than Web-150. Looking at the terminal output, we find that this is because it has a total of 587 revisions.
When we browse into this directory, we find two files: index.php and noIdeaWhatImDoing. noIdeaWhatImDoing seems interesting due to the base64 data, but it is ultimately a useless file.
When we look into the bzr log, we can see that we have many nearly identical commits:
------------------------------------------------------------
revno: 587
committer: BZR Lover
branch nick: filePathTraversalHard
timestamp: Thu 2018-12-06 18:07:18 -0500
message:
  What is going on
------------------------------------------------------------
revno: 586
committer: BZR Lover
branch nick: filePathTraversalHard
timestamp: Thu 2018-12-06 18:07:17 -0500
message:
  What is going on
------------------------------------------------------------
revno: 585
committer: BZR Lover
branch nick: filePathTraversalHard
timestamp: Thu 2018-12-06 18:07:17 -0500
What if we filtered out the identical commits and just found the different ones?
This can be done with some grep piping:
$ bzr log | grep -v committer | grep -v timestamp | grep -v message | grep -v 'nick' | grep -v 'What is going on' | grep -v '\-\-' | grep '  ' -B1
revno: 167
  Oops
revno: 166
  CentOS is just RedHat
--
revno: 156
  Nevermind on the blog post
revno: 155
  Add a new blog post!
--
revno: 1
  BZR is so cool!
As we can see, there are 3 groups of commits where things actually happened. One of them is the initial commit, but the other two are more interesting.
If we use bzr diff on those commits, we get the following interesting bits of text:
First group:
+            <h1>Encryption is so cool!</h1>
+            <p>It's so cool that I can paste a block of text here and if its encrypted then none of you will EVER be able to read it! After reading about it, I'm so comfortable with it that I'm willing to paste my Bitcoin Wallet password right here:</p>
+            <p>NWEyYTk5ZDNiYWEwN2JmYmQwOGI5NjEyMDVkY2FlODg3ZmIwYWNmOWYyNzI5MjliYWE3OTExZmFhNGFlNzc1MQ==</p>
+            <p>There's like a whole 3 Bitcoin in there, but none of you will ever be able to get it!</p>
+            <hr>
Second group:
-</html>
-<!-- 6fb3b5b05966fb06518ce6706ec933e79cfaea8f12b4485cba56321c7a62a077 -->
Based on the commit message for the second commit, "Oops", it seems like the second group of commits may have contained the encryption key for the "bitcoin wallet".
Decrypting the Bitcoin wallet using AES-256-ECB with the key provided returns the flag,
MCA{I$love$bitcoin$so$much!}

REBase

400 points, Reverse Engineering

I doubt the way I solved this problem was the intended solution, but it works. Using a cryptanalysis attack on a Reverse Engineering problem seems like a hacker solution to me.
I first tried running the program in a few ways to figure out how it works.
I ran it with the input "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", and got a repeating sequence of the three characters '5V"'.
Running it with a long string a b's and c's gave similar results of three different characters.
Upon further testing, repeated sequences of double characters, like "ababababababab", only repeated every 6 bytes, but three character sequences like "abcabcabcabc" repeated every 3.
Due to the lack of similarities in the bytes for each of the outputs, I assumed that there was some sort of byte substitution happening, with a different substitution every first, second, and third byte.
Now, all we needed to do was bruteforce the flag in groups of 3 bytes.
To do this, I wrote a simple Python script:
import subprocess
from itertools import product
from functools import partial
import multiprocessing

encflag = 'ZXFWtmKgDZCyrmC5B+CiVfsyXUCQVfsyZRFzDU4yX2YCD/F5Ih8='.decode('base64')
possible_characters = '{[email protected]#$%^&*_-}'

def encStr(test):
	p = subprocess.Popen(['./REbase-fix', test], stdout=subprocess.PIPE)
	enctest = p.stdout.read().split('\n')[1]
	return enctest

def checkEncStr(wanted, decList):
	encrypted = encStr(''.join(decList)).decode('base64')
	if encrypted[-3:] == wanted:
		return ''.join(decList)
	return None

pool = multiprocessing.Pool(16)
recoveredflag = 'MCA'
for i in range(1, 12):
	print "Iteration " + str(i)
	strWanted = encflag[i*3:i*3+3]
	
	done = False
	all = product(possible_characters, repeat=3)
	for i in range(pow(len(possible_characters), 3)/16):
		if done == True:
			break
		tomap = [next(all), next(all), next(all), next(all), next(all), next(all), next(all), next(all)]
		tomap += [next(all), next(all), next(all), next(all), next(all), next(all), next(all), next(all)]
		out = pool.map(partial(checkEncStr, strWanted), tomap)
		for s in out:
			if s is not None:
				recoveredflag += s
				print "More of flag recovered, now: " + recoveredflag
				done = True
				break
Running this Python script gave most of the flag at iteration 11:
Iteration 11
More of flag recovered, now: MCA{[email protected]_L3m0n_SqU33z
With all but the last two characters of the flag, we can make an educated guess for what the rest of the flag is:
$ ./REbase-fix MCA{[email protected]_L3m0n_SqU33zy}
38
ZXFWtmKgDZCyrmC5B+CiVfsyXUCQVfsyZRFzDU4yX2YCD/F5Ih8=
ZXFWtmKgDZCyrmC5B+CiVfsyXUCQVfsyZRFzDU4yX2YCD/F5Ih8=
Congratulations!
And we have our flag!