
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.
"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:
plain1.txt
: A clear text filecrypt1.enc
: The clear text file encryptedsample.enc
: An encrypted text file that we need to decrypt
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
EO
C
EO
E
C ECTWC"TOHVT
CAEYAXYC&AVEUCT!E
OYAXYC8
Y
AETA
IAEY 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:
00000010
to00000020
: the white space characters are encrypted with value 4f and these two whitespaces are separated by 9 characters00000020
to00000030
: the white space characters are encrypted with value 45 and these two whitespaces are separated by 9 characters00000030
to00000040
: the white space characters are encrypted with value 45 and these two whitespaces are separated by 9 characters00000040
to00000050
: the white space characters are encrypted with value 54 and these two whitespaces are separated by 9 characters00000070
: the white space characters are encrypted with value 59 and these two whitespaces are separated by 9 characters00000090
to000000a0
: 3 white space characters are encrypted with value 59 and these three whitespaces are separated by 9 characters000000d0
: the white space characters are encrypted with value 59 and these two whitespaces are separated by 9 characters
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!