Cobol OTP challenge

Description

To save the future you have to look at the past. Someone from the inside sent you an access code to a bank account with a lot of money. Can you handle the past and decrypt the code to save the future?

Setup

The challenges include two files otp.cob and out

otp.cob is a Cobol program that at a glance, encrypts a message by xoring an unknown key (50 bytes) with a message (50 bytes as well)

out looks like an encrypted message:

$ hexdump -C out

00000000 45 6e 74 65 72 20 79 6f 75 72 20 6d 65 73 73 61 |Enter your messa|
00000010 67 65 20 74 6f 20 65 6e 63 72 79 70 74 3a 0a a6 |ge to encrypt:..|
00000020 d2 13 96 79 3b 10 64 68 75 9f dd 46 9f 5d 17 55 |...y;.dhu..F.].U|
00000030 6a 68 43 8f 8c 2d 92 31 07 54 60 68 26 9f cd 46 |jhC..-.1.T`h&..F|
00000040 87 31 2a 54 7b 04 5f a6 eb 06 a4 70 30 11 32 4a |.1*T{._....p0.2J|
00000050 0a                                              |.|

Issue 1

Taking a look at the heart of the encryption:

call 'CBL_XOR' using ws-key(ws-ctr:1) ws-flag by value ws-xor-len end-call

Reading the documentation https://documentation.microfocus.com/help/index.jsp?topic=%2FGUID-0E0191D8-C39A-44D1-BA4C-D67107BAF784%2FHRCLRHCALL5H.html we discover that this performs a
xor between ws-key[ws-ctr] and ws-flag with the length of ws-xor-len bytes. The result is stored in ws-flag.

Looking at the types we’ve understood that ws-ctr and ws-xor-len are one decimal digit long only. Therefore ws-ctr will wrap around every 10 bytes.

Thus, this limits the valid key space, since the xor of the key with the encrypted data must yield a printable character. In this case, we see that

encrypted[1]  ^ key[1] = printable_char1
encrypted[11] ^ key[1] = printable_char2
encrypted[21] ^ key[1] = printable_char3
encrypted[31] ^ key[1] = printable_char4
encrypted[41] ^ key[1] = printable_char5

And, of course, this holds for the key[2], etc..

Issue 2

Remembering that Cobol arrays are 1 based, the value of ws-flag(ws_ctr:1) when ws_ctr is 0 yields nothing and the xor is effectively nullified, and this gives us 5 unencrypted chars for free:

*********u*********C*********&*********_*********\n

Since we had ~40 options for every character and still have 9 key character to guess we start by assuming the message looks like “flag{***************}\n“, where the * mean unknown character.

Pluging in the right key chars to yield the above string we get:

flag{***_u_c4n_***_CO2_c3***_&_s4v3***3_fUtUr***}\n

Now we could try to read the message and guess the needed keys. By assuming the word save (s4v3) should have _ after it and future is a reasonable word we tried all the options to get meaningful phrases from:

flag{N**_u_c4n_b**_CO2_c3r**_&_s4v3_**3_fUtUrE**}\n

With a little guess work we think it reads something along the lines of: Now you can buy CO2 cer** & save the future**

Pluging in our guesses gave us the full flag:

flag{N0w_u_c4n_buy_CO2_c3rts_&_s4v3_th3_fUtUrE1!}