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/CoT4
4UcKNwp7DllpPwo3DtsOID8OPTcOWwrzDpi3CtMOKw4PColrCp
XUYRhXChMK9w6PDhxfDicOdwoAgwpgNw5/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 encoded 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 64 byte 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:

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

dec = ['' for i in range(len(enc))]
for c in range(0, len(enc), 64):
    for n in range(64):
        i = ord(enc[c + P[n] - 1]) - S[n]
        if i < 0:
            i += 256
        dec[c + n] = chr(i)

print ''.join(dec)

And the decrypted flag:

IceCTF{4Lw4y5_US3_5s1_AnD_n3VR4r_mAKe_Y0ur_0wN_cRyp70}........'