IceCTF: Analyzing PCAPs and reversing encryption

IceCTF started a little while a go and we got a team together to try and grab some of these flags. If you're doing the challenges right now... well spoiler alert.

One of the challenges provided a PCAP with a conversation between two parties that contained the flag somewhere.

PCAP analysis

Alright, let me open this in Wireshark:

Interesting... some IRC chatter. If I right-click and "Follow TCP stream" I can read the full conversation:

PRIVMSG Cold_Storm :Hi  
:Cold_Storm!~finalC@localhost PRIVMSG Ice_Venom :It's not safe here
PRIVMSG Cold_Storm :What do you mean?  
:Cold_Storm!~finalC@localhost PRIVMSG Ice_Venom :Someone is listening
PRIVMSG Cold_Storm :What?!  
:Cold_Storm!~finalC@localhost PRIVMSG Ice_Venom :Yea, someone intercepted our last encounter
PING LAG1470558285526987  
:irc.glitch.is PONG irc.glitch.is :LAG1470558285526987
:Cold_Storm!~finalC@localhost PRIVMSG Ice_Venom :We need to be careful about what we say on open channels.
PRIVMSG Cold_Storm :Oh 0k, so what do w3 do?  
:Cold_Storm!~finalC@localhost PRIVMSG Ice_Venom :I made som3thing...
:Cold_Storm!~finalC@localhost PRIVMSG Ice_Venom :S0mething that will allow us to exchange s3crets secure1y
PING LAG1470558315569084  
:irc.glitch.is PONG irc.glitch.is :LAG1470558315569084
:Cold_Storm!~finalC@localhost PRIVMSG Ice_Venom :.DCC SEND encode.pyc 1494322064 1117 1737.
PING LAG1470558345608305  
:irc.glitch.is PONG irc.glitch.is :LAG1470558345608305
PRIVMSG Cold_Storm :Ok  
PING LAG1470558375659432  
:irc.glitch.is PONG irc.glitch.is :LAG1470558375659432
PRIVMSG Cold_Storm :Wmkvw680HDzDqMK6UBXChDXCtC7CosKmw7R9w7JLwr/CoT44UcKNwp7DllpPwo3DtsOID8OPTcOWwrzDpi3CtMOKw4PColrCpXUYRhXChMK9w6PDhxfDicOdwoAgwpgNw5/Cvw==  

Looking through there are a bunch of PRIVMSG IRC messages being exchanged which is just a private conversation between two parties.

Towards the end there's a base64 encoded string, presumably generated by one of the parties. If you try to base64 decode the data, it's gibberish.

Wmkvw680HDzDqMK6UBXChDXCtC7CosKmw7R9w7JLwr/CoT44UcKNwp7DllpPwo3DtsOID8OPTcOWwrzDpi3CtMOKw4PColrCpXUYRhXChMK9w6PDhxfDicOdwoAgwpgNw5/Cvw==

Just before this though, there's a DCC SEND operation of an encode.pyc file. DCC is how the IRC protocol handles client to client data transfer. In this case, a tool that probably generated the encrypted message above.

File carving

Since I have a packet capture of the whole conversation, I will isolate this file and save it to disk so I can reverse it.

Same as before, right click and follow the TCP stream:

Python compiled files have a specific signature, a version number (0x16 0x0d) followed by \r\n (0x0a 0x0d). The first 4 bytes of the file confirm this is indeed a .pyc file (0x16 0x0d 0x0d 0x0a):

I'm interested in the one way conversation that starts with these bytes. I'll save it as encode.pyc:

To decompile the pyc and I'll need uncompyl6. This tool supports all versions of Python (2.7->3.6).

I did try pycdc but in my case, it was decompiling to broken code.

encode.pyc -> encode.py:

# Python bytecode 3.5 (3350) disassembled from Python 2.7
# Embedded file name: encode.py
import random  
import base64  
P = [27, 35, 50, 11, 8, 20, 44, 30, 6, 1, 5, 2, 33, 16, 36, 64, 3, 61, 54, 25, 12, 21, 26, 10, 57, 53, 38, 56, 58, 37, 43, 17, 42, 47, 4, 14, 7, 46, 34, 19, 23, 40, 63, 18, 45, 60, 13, 15, 22, 9, 62, 51, 32, 55, 29, 24, 41, 39, 49, 52, 48, 28, 31, 59]  
S = [68, 172, 225, 210, 148, 172, 72, 38, 208, 227, 0, 240, 193, 67, 122, 108, 252, 57, 174, 197, 83, 236, 16, 226, 133, 94, 104, 228, 135, 251, 150, 52, 85, 56, 174, 105, 215, 251, 111, 77, 44, 116, 128, 196, 43, 210, 214, 203, 109, 65, 157, 222, 93, 74, 209, 50, 11, 172, 247, 111, 80, 143, 70, 89]  
inp = input()  
inp += ''.join((chr(random.randint(0, 47)) for _ in range(64 - len(inp) % 64)))  
ans = ['' for i in range(len(inp))]  
for j in range(0, len(inp), 64):  
    for i in range(64):
        ans[j + P[i] - 1] = chr((ord(inp[j + i]) + S[i]) % 256)

ans = ''.join(ans)  
print(base64.b64encode(ans.encode('utf8')).decode('utf8'))  
# okay decompiling ../../../encode.pyc
Reversing the algorithm

It's a bit convoluted, but basically the algorithm will:

  • let 'I' represent the user input
  • split 'I' into 64 byte chunks
  • let 'c' represent each chunk
  • let 'n[0..63]' represent each byte in a given chunk
  • add 'S(c+n)' to the numeric value of 'I(c+n)'
    • perform mod 256 operation on the result
    • ie: 127 % 256 = 127, 321 % 256 = 65
  • store the result in the return variable at position 'c+P(n)-1'.

The algorithm takes each character, adds a number from S to it and stores the result in the encrypted string array at an arbitrary position defined by P.

To decrypt the resulting string, I will walk backwards:

  • iterate through each byte in each 64byte chunk
  • jump to 'c+P(n)-1' and get the numerical value
  • subtract 'S(c+n)'
    • if that number is below 0, add 256 before converting to string
  • store the result in the return variable at position 'n'.

The decryption algorithm:

inp = base64.b64decode("Wmkvw680HDzDqMK6UBXChDXCtC7CosKmw7R9w7JLwr/CoT44UcKNwp7DllpPwo3DtsOID8OPTcOWwrzDpi3CtMOKw4PColrCpXUYRhXChMK9w6PDhxfDicOdwoAgwpgNw5/Cvw==").decode('utf8')

ans = ['' for i in range(len(inp))]  
for j in range(0, len(inp), 64):  
    for i in range(64):
        x = ord(inp[j + P[i] - 1]) - S[i]
        if x < 0:
            x += 256
        ans[j + i] = chr(x)

ans = ''.join(ans)  
print(ans)  

And the decrypted flag:

IceCTF{4Lw4y5_US3_5s1_AnD_n3VR4r_mAKe_Y0ur_0wN_cRyp70}"!%    '