Write-Ups

EasyCTF IV – Not OTP

Description

It seems we’ve intercepted 2 strings that were both encrypted with what looks like OTP! Is it possible to decrypt them?

c1 = 38445d4e5311544249005351535f005d5d0c575b5e4f481155504e495740145f4c505c5c0e196044454817564d4e12515a5f4f12465c4a45431245430050154b4d4d415c560c4f54144440415f595845494c125953575513454e11525e484550424941595b5a4b
c2 = 3343464b415550424b415551454b00405b4553135e5f00455f540c535750464954154a5852505a4b00455f5458004b5f430c575b58550c4e5444545e0056405d5f53101055404155145d5f0053565f59524c54574f46416c5854416e525e11506f485206554e51

Resolution

Like the title says, it’s most likely not a One Time Pad because if it was, it would be impossible to decrypt without knowing the key. Since there are 2 ciphertexts, the first thing that came to my mind was “And if it was a Two Time Pad ?”.

I xored the two ciphertexts together and got the following message :

>>> c1 = "38445d4e5311544249005351535f005d5d0c575b5e4f481155504e495740145f4c505c5c0e196044454817564d4e12515a5f4f12465c4a45431245430050154b4d4d415c560c4f54144440415f595845494c125953575513454e11525e484550424941595b5a4b".decode('hex')
>>> c2 = "3343464b415550424b415551454b00405b4553135e5f00455f540c535750464954154a5852505a4b00455f5458004b5f430c575b58550c4e5444545e0056405d5f53101055404155145d5f0053565f59524c54574f46416c5854416e525e11506f485206554e51".decode("hex")
>>> def xor(m,k):
...     r = ""
...     for i in range(len(m)):
...         r += chr(ord(m[i]) ^ ord(k[i%len(k)]))
...     return r
... 
>>> p = xor(c1,c2)
>>> p
'\x0b\x07\x1b\x05\x12D\x04\x00\x02A\x06\x00\x16\x14\x00\x1d\x06I\x04H\x00\x10HT\n\x04B\x1a\x00\x10R\x16\x18E\x16\x04\\I:\x0fE\rH\x02\x15NY\x0e\x19S\x18I\x1e\tF\x0b\x17V\x11\x1d\x00\x06U\x16\x12\x1eQL\x03L\x0e\x01\x00\x19\x1fA\x0c\x0f\x07\x1c\x1b\x00F\x0e\x1c\x11\x14\x7f\x1d\x1aP<\x0c\x16T\x00-\x01\x13_\x0e\x14\x1a'

Now I can start looking for some known plaintext (crib). I already know that the flag begins with “easyctf{” so I can start with that. I replaced all the non interesting characters with “#” for readability.

>>> def crib(m, c):
...     for i in range(len(m)-len(c)+1):
...         p = xor(m, "\x00"*i+c)
...         s = ""
...         for e in p:
...             if e in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}0123456789_ ?!.,'":
...                 s += e
...             else:
...                 s += "#"
...         print s
... 
>>> crib(p, "easyctf{")
<...>
#####D###A#######I#H##HT##B###R##E###I##E#H##NY##S#I##F##V####U###QL#L######m###of########P###T####_###
#####D###A#######I#H##HT##B###R##E###I##E#H##NY##S#I##F##V####U###QL#L#####Aintext u######P###T####_###
#####D###A#######I#H##HT##B###R##E###I##E#H##NY##S#I##F##V####U###QL#L#####A#jfobc2hg#####P###T####_###
<...>

I got what seems to be a part of the original plaintext.

The maths behind it

If c_1 = p_1 \oplus k
and c_2 = p_2 \oplus k
then p = c_1 \oplus c_2 = p_1 \oplus k \oplus p_2 \oplus k = p_1 \oplus p_2

That’s why, by guessing part of p_1 we get a part of p_2. I just dragged the crib all the way through p and once I saw a probable plaintext, I knew I had found the good offset.

Now that I know part of the plaintext, I can try to extend it by guessing :
” plaintext used ”

>>> crib(p, " plaintext used ")
<...>
#####D###A#######I#H##HT##B###R##E###I##E#H##NY##S#I##F##V####U###QL#L## is easyctf{otp_##P###T####_###

” flag is easyctf{otp_

>>> crib(p, " flag is easyctf{otp_")
<...>
#####D###A#######I#H##HT##B###R##E###I##E#H##NY##S#I##F##V####U#fv4le of plaintext used ##P###T####_###

And so on, until I arrived to the point I knew that much of p_1 and p_2 :

p_1 = ” to a sample of plaintext used ”
p_2 = “to guess! flag is easyctf{otp_”

At that point, I got stuck because I couldn’t guess the rest of the plaintext. That’s why I decided to look at the key ! How ? By dragging the plaintext I recovered over the ciphertext and not over p :

k = c_1 \oplus p_1 = c_2 \oplus p_2

>>> crib(c1, " to a sample of plaintext used ")
<...>
8D#NS#TBI#SQS_####W##OH#UPNIW##_LP#####DEH#VMN#QZ_O#F#JEC21, 158, 103, 244, 67, 182, 213EN#R#HEPBIAY#ZK
<...>
>>> crib(c2, "to guess! flag is easyctf{otp_")
<...>
3CFKAUPBKAUQEK###ES##_#E_T#SWPFIT#JXRPZK#E_TX#K_C#W#XU#NTD 1 158, 103, 244, 67, 182, 213XTAnR##PoHR#UNQ

Because the key follows an easy to guess pattern, I could apply the same technique as before and get the flag :

>>> crib(p, " to a sample of plaintext used in codebreaking")
<...>
#####D###A#######I#H##HT##B###R##E###I##E#H##NY##S#I##F##ver guess! flag is easyctf{otp_ttp_cr1b_dr4gz}

The complete flag was easyctf{otp_ttp_cr1b_dr4gz} 😀

Leave a Reply

Your email address will not be published. Required fields are marked *