full metal jacket

SANS National CTF Tournament 2021 - Crypto Hard #2

November 12, 2021

I want to share with you my solution regarding the CTF Crypto Hard #2.

Sir yes sir!
"So you're a killer?
SIR YES SIR!
Let me see your war face!
Sir...
You've got a war face? AHHHHHHHHHHHHHHHHHHHHHHH! That's a war face! Now let me see your war face.
AHHHHHHHHHHHHH
Bullshit! you didn't convince me, let me see your real war face."

I know no best-movie-list that does not contains Full Metal Jacket. This CTF is revolving around this magnificent movie. I loved it from the download to the flag.
Will you have what it takes to solve it?
SHOW ME YOUR WAR FACE !

We are requested to download a zip file that contains 3 files available here:

We are informed that the file sample.enc is encrypted using the same password as the crypt1.enc. We do not know the password but we have intercepted a clear text file plain1.txt and his associated encrypted file crypt1.enc.
It's up to us now to find out the encryption method, the password and then decrypt the requested file that will contains the flag.

We start by opening the plain1.txt file, which contains the following text:

"This is my sample, there are many samples like this one but this one is mine. My sample is my best friend, it is my life. I must master it, as I must master my life. Without me my sample is useless, without my sample I am useless."

Now opening the crypt1.enc:

							
<
Y
AEXH	O 
EO

C
EO
E
C	ECTWC"TOHVT
CAEYAXYC&AVEUCT!E
OYAXYC8  
Y
AETA
IAEY A?YA 

AA
						

Quite ugly. Definitely not intended to be opened by a text editor.
We will need to have an hexadecimal view on this file if we want to find something interesting.
But before that let's just have a look at file sample.enc:

							
.	 CCO'	>.H+"01 
						

Quite ugly also but at least we see that the file is definitely smaller than the crypt1.enc.

Now that the presentations are made, it's time to understand what they are made of. We are gonna need to have a look at their hexadecimal dump and then try to build from that.
On Linux we will use xxd that is a command line hex editor that creates a dump of a file.

						
user@kali:~/Documents/SANS CTF/CH02$ xxd plain1.txt 
00000000: 5468 6973 2069 7320 6d79 2073 616d 706c  This is my sampl
00000010: 652c 2074 6865 7265 2061 7265 206d 616e  e, there are man
00000020: 7920 7361 6d70 6c65 7320 6c69 6b65 2074  y samples like t
00000030: 6869 7320 6f6e 6520 6275 7420 7468 6973  his one but this
00000040: 206f 6e65 2069 7320 6d69 6e65 2e20 4d79   one is mine. My
00000050: 2073 616d 706c 6520 6973 206d 7920 6265   sample is my be
00000060: 7374 2066 7269 656e 642c 2069 7420 6973  st friend, it is
00000070: 206d 7920 6c69 6665 2e20 2049 206d 7573   my life.  I mus
00000080: 7420 6d61 7374 6572 2069 742c 2061 7320  t master it, as 
00000090: 4920 6d75 7374 206d 6173 7465 7220 6d79  I must master my
000000a0: 206c 6966 652e 2020 5769 7468 6f75 7420   life.  Without 
000000b0: 6d65 206d 7920 7361 6d70 6c65 2069 7320  me my sample is 
000000c0: 7573 656c 6573 732c 2077 6974 686f 7574  useless, without
000000d0: 206d 7920 7361 6d70 6c65 2049 2061 6d20   my sample I am 
000000e0: 7573 656c 6573 732e 20                   useless. 
user@kali:~/Documents/SANS CTF/CH02$ xxd crypt1.enc 
00000000: 3c0d 0805 590a 1c41 1911 4512 1714 1303  <...Y..A..E.....
00000010: 0458 4811 0913 0b06 4f00 060d 450c 1717  .XH.....O...E...
00000020: 1a4f 1215 0515 0d13 0a43 0308 1f0d 4515  .O.......C....E.
00000030: 1e10 104f 0e1a 0d45 0303 0d43 1b09 1d1b  ...O...E...C....
00000040: 450e 181c 4306 1254 050c 0f13 5743 2218  E...C..T....WC".
00000050: 541b 040c 0615 064f 0807 4808 1856 1b06  T......O..H..V..
00000060: 1c15 540e 1708 1317 0743 411d 1c45 0805  ..T......CA..E..
00000070: 590e 1641 1801 0304 5859 4326 4119 1d16  Y..A....XYC&A...
00000080: 1556 1402 1c15 111a 4508 0255 430e 1254  .V......E..UC..T
00000090: 2145 0c03 0a17 4f0c 151b 1104 0459 0e16  !E....O......Y..
000000a0: 4118 0103 0458 5943 3808 0000 0a14 0259  A....XYC8......Y
000000b0: 0e0a 4119 1145 1217 1413 0304 5401 1641  ..A..E......T..A
000000c0: 030a 0603 0407 1b49 4101 1017 070e 011c  .......IA.......
000000d0: 450c 0f59 100e 0c04 0400 413f 5902 0241  E..Y......A?Y..A
000000e0: 011b 000d 130a 1041 41                   .......AA
						

At this stage, what I would like to do is to find some redundancy on the encrypted file hex dump.
In other word, what I'm looking for is a clear text character that would be encrypted with the same value at different places in the encrypted format.
The purpose of looking for such redundancy pattern is to have an estimation on the password length and maybe try to identify the encryption mechanism used here.
The most common character in the plain1.txt file is the white space character. Even more interesting we can see that there are sometimes two white space together right before "I must master" and "Without me my sample".
Let's highlight in red the whitespaces value in the plain text and the same hex position in the crypt1.enc file. Hexadecimal value of the white space is 20.

						
user@kali:~/Documents/SANS CTF/CH02$ xxd plain1.txt 
00000000: 5468 6973 2069 7320 6d79 2073 616d 706c  This is my sampl
00000010: 652c 2074 6865 7265 2061 7265 206d 616e  e, there are man
00000020: 7920 7361 6d70 6c65 7320 6c69 6b65 2074  y samples like t
00000030: 6869 7320 6f6e 6520 6275 7420 7468 6973  his one but this
00000040: 206f 6e65 2069 7320 6d69 6e65 2e20 4d79   one is mine. My
00000050: 2073 616d 706c 6520 6973 206d 7920 6265   sample is my be
00000060: 7374 2066 7269 656e 642c 2069 7420 6973  st friend, it is
00000070: 206d 7920 6c69 6665 2e20 2049 206d 7573   my life.  I mus
00000080: 7420 6d61 7374 6572 2069 742c 2061 7320  t master it, as 
00000090: 4920 6d75 7374 206d 6173 7465 7220 6d79  I must master my
000000a0: 206c 6966 652e 2020 5769 7468 6f75 7420   life.  Without 
000000b0: 6d65 206d 7920 7361 6d70 6c65 2069 7320  me my sample is 
000000c0: 7573 656c 6573 732c 2077 6974 686f 7574  useless, without
000000d0: 206d 7920 7361 6d70 6c65 2049 2061 6d20   my sample I am 
000000e0: 7573 656c 6573 732e 20                   useless. 
user@kali:~/Documents/SANS CTF/CH02$ xxd crypt1.enc 
00000000: 3c0d 0805 590a 1c41 1911 4512 1714 1303  <...Y..A..E.....
00000010: 0458 4811 0913 0b06 4f00 060d 450c 1717  .XH.....O...E...
00000020: 1a4f 1215 0515 0d13 0a43 0308 1f0d 4515  .O.......C....E.
00000030: 1e10 104f 0e1a 0d45 0303 0d43 1b09 1d1b  ...O...E...C....
00000040: 450e 181c 4306 1254 050c 0f13 5743 2218  E...C..T....WC".
00000050: 541b 040c 0615 064f 0807 4808 1856 1b06  T......O..H..V..
00000060: 1c15 540e 1708 1317 0743 411d 1c45 0805  ..T......CA..E..
00000070: 590e 1641 1801 0304 5859 4326 4119 1d16  Y..A....XYC&A...
00000080: 1556 1402 1c15 111a 4508 0255 430e 1254  .V......E..UC..T
00000090: 2145 0c03 0a17 4f0c 151b 1104 0459 0e16  !E....O......Y..
000000a0: 4118 0103 0458 5943 3808 0000 0a14 0259  A....XYC8......Y
000000b0: 0e0a 4119 1145 1217 1413 0304 5401 1641  ..A..E......T..A
000000c0: 030a 0603 0407 1b49 4101 1017 070e 011c  .......IA.......
000000d0: 450c 0f59 100e 0c04 0400 413f 5902 0241  E..Y......A?Y..A
000000e0: 011b 000d 130a 1041 41                   .......AA
						

Now we need to use our eyes and our brain to count.
We can see that on following hex lines:

Based on this above analysis we can put the hypothesis that the encryption mechanism is based on a character substitution/rotation. Maybe a xor, maybe some custom strange ceasar.
Also based on our above anlysis, we can postulate that the password length is about 9. There can be other explanations to this but, right now, with the few level of information we have that's the lead I want to follow.

As stated above, we suspect a xor encryption or a strange ceasar. In both cases a particularity of these encryption algorithm is that the plain text, the encrypted text and the password are all linked together by the following:

- plain text XOR/ROT password = encrypted text
- encrypted text XOR/ROT password = plain text
- plain text XOR/ROT encrypted text = password

Guess what? Yeah, the CTF designer provides us the plain text and the encrypted text. That's an indication here, a path to follow. It should be manageable to find the password using the plaintext and the encrypted text.
Now I would like to have a look at the first line of hex characters of the plain1.txt and the first line of hex characters of the crypt1.enc file.
I would like to perform a xor between the alphabet and the plain1.txt and will focus on which letter gives me the crypt1.enc results.
To say it easily, we will focus only on the FIRST letter of the plain text and try to answer the following question:
Which alphabet letter XOR 54 (the T letter) = 3c?
In order to avoid doing this manually I will write a bit of Python code.

						
plainTxt = "This is my sample, there are many samples like this one but this one is mine. 
My sample is my best friend, it is my life.  
I must master it, as I must master my life.  
Without me my sample is useless, without my sample I am useless."
alphabet = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"]
plainHex = []

#Small function to convert string hex into integer
def hex2int(h):
    if len(h) > 1 and h[0:2] == '0x':
        h = h[2:]
    if len(h) % 2:
        h = "0" + h
    return int(h, 16)

#Convert the plain text into Hexadecimal format and store it in plainHex
for c in plainTxt:
    hexChar = c.encode(encoding='utf-8').hex()
    plainHex.append(hexChar)

for x in range(0, len(alphabet)):
    #Perform the xor on the first letter of the plain text against the alphabet
    xor = hex2int(plainHex[0]) ^ ord(alphabet[x % 26])
    #Convert computed xor result into hex
    hexXor = hex(xor)
    if hexXor == "0x3c":
        print(plainHex[0], "XOR", alphabet[x % 26], "= 3c")
						
						

Executing this code output:

						
54 XOR h = 3c
						

Here is our first answer. T XOR h = 3c. The first encrypted character in the crypt1.enc file.
It looks promising, but it could also be a random lucky result, so let's adapt the code and continue with the first 16 characters from the plain text using the above code adapted a little:

						
plainTxt = "This is my sample, there are many samples like this one but this one is mine. 
My sample is my best friend, it is my life.  
I must master it, as I must master my life.  
Without me my sample is useless, without my sample I am useless."
alphabet = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"]
encrypted = ["3c", "d", "8", "5", "59", "a", "1c", "41", "19", "11", "45", "12", "17", "14", "13", "3"]
plainHex = []
password  = []

#Small function to convert string hex into integer
def hex2int(h):
    if len(h) > 1 and h[0:2] == '0x':
        h = h[2:]
    if len(h) % 2:
        h = "0" + h
    return int(h, 16)

#Convert the plain text into Hexadecimal format and store it in plainHex
for c in plainTxt:
    hexChar = c.encode(encoding='utf-8').hex()
    plainHex.append(hexChar)

#Perform the xor on the first 16 character of the plain text against the alphabet
#store the result in the password variable
for i in range(0, 16):
    for x in range(0, len(alphabet)):
        xor = hex2int(plainHex[i]) ^ ord(alphabet[x % 26])
        #Convert computed xor result into hex
        hexXor = hex(xor)
        if hexXor == ("0x"+ encrypted[i]):
            password.append(alphabet[x % 26])
            break;
print(password)
						
						

Again, let's execute the code.

						
['h', 'e', 'a', 'v', 'y', 'c', 'o', 'a', 't', 'h', 'e', 'a', 'v', 'y', 'c', 'o']
						

Well, well, well. Look at that. It seems our instinct was correct and we followed the correct lead.
heavycoat is the password. It also has 9 characters as we have assumed above in our first hexadecimal analysis. Everything fits!
It's time to get the flag!
We just need to write a bit of python to unxor the flag from file sample.enc.

						
flag = ['2e','09','00','11','43','43','4f','27','01','04','09','3e','2e','16','11',
'48','05','2b','22','04','02','1d','1c','17','30','31','06','01','13','00','02','1c']
password = ["h","e","a","v","y","c","o","a","t"]
decrypted = []
for i in range(0, len(flag)):
    decrypted.append(chr(int(flag[i],16) ^ ord(password[i%9])))
prettyFlag = ""
for c in decrypted:
    prettyFlag += c
print(prettyFlag)

Execute this piece of code:

						
Flag:  Full_Xor'd_Jacket_Private
						

We found the flag!
What have I told you at the beginning? From download to the flag it was great and fun! /love

THAT'S OUR WAR FACE!

AAAAAAAAAAAAAAAAHHHHHHHHHHHHHHHHHHHHHHHHHHHH