Skip to content

Omegapoint CTF 2023 - Crypto & REV - The Polar Express

Posted on:January 1, 2024 at 02:55 PM

Challenge

Category: OSINT & REV

An elf were found to be doing some shady things behind Santa and has since gone rogue and missing. Hopefully the elf is not doing something harmful to the upcoming christmas eve. I heard that he went by the name the-polar-develf1337 on the interwebs.

Solution

I used sherlock to find the username on github.

sherlock the-polar-develf1337

He had a github repo with code to a discord bot. I cloned the repo to to find out how it worked.

There was no flags, but the git commit history showed that there was a flag.cpython-310.pyc file that was removed in the latest commit.

This is a python compiled file, so i used a tool to decompyle the python bytecode to python code, but since it was python 3.10.x i had a bad time finding a good tool for the job so the result was kind of messed up, but still readable…

# Source Generated with Decompyle++
# File: flag.cpython-310.pyc (Python 3.10)

import os
import discord
import hashlib
from discord.ext import commands

def xor(s1, s2):
    return ''.join((lambda .0: for a, b in .0:
chr(a ^ b))(zip(s1, s2)))

Unsupported Node type: 27
Flag = <NODE:27>((lambda : 
def __init__(self, bot):
self.bot = botself._secret = bytes.fromhex('1b252067113f23283c743e0c18413a1943551b1d13113a06550b1c0d130f01031306481a08401e560e')self._last_message = os.urandom(25)
async def flag(self = None, ctx = None, arg = None, *, member):
if not member:
passmember = ctx.authorif member.display_name == 'The Polar Express':
key = xor(arg.encode(), self._last_message).encode()if hashlib.md5(key).hexdigest() == '4b462bce910ff869f9a8ec2604fc9707':
Unsupported Node type: 28
<NODE:28>else:
Unsupported Node type: 28
<NODE:28>else:
Unsupported Node type: 28
<NODE:28>self._last_message = arg.encode()flag = None(flag)), 'Flag', commands.Cog, 'flag', **('name',))

async def setup(bot):
    await bot.add_cog(Flag(bot))

Looks like there was some xor magic going on so I naivly tried to xor some random stuff together, but that did not work..

I wanted to look at what the flag method did, but it was to unreadable in the decompiled code, so i used the dis module to disassemble the bytecode.

import dis
import flag

flag = flag.Flag("bot")

dis.dis(flag.__cog_commands__[0].callback.__code__)

The most interesting code can be found 60-94, since we can clearly see what is happening.

flag function

              0 GEN_START                1

 20           2 LOAD_FAST                3 (member)
              4 JUMP_IF_TRUE_OR_POP      5 (to 10)
              6 LOAD_FAST                1 (ctx)
              8 LOAD_ATTR                0 (author)
        >>   10 STORE_FAST               3 (member)

 21          12 LOAD_FAST                3 (member)
             14 LOAD_ATTR                1 (display_name)
             16 LOAD_CONST               1 ('The Polar Express')
             18 COMPARE_OP               2 (==)
             20 POP_JUMP_IF_FALSE       57 (to 114)

 22          22 LOAD_GLOBAL              2 (xor)
             24 LOAD_FAST                2 (arg)
             26 LOAD_METHOD              3 (encode)
             28 CALL_METHOD              0
             30 LOAD_FAST                0 (self)
             32 LOAD_ATTR                4 (_last_message)
             34 CALL_FUNCTION            2
             36 LOAD_METHOD              3 (encode)
             38 CALL_METHOD              0
             40 STORE_FAST               4 (key)

 23          42 LOAD_GLOBAL              5 (hashlib)
             44 LOAD_METHOD              6 (md5)
             46 LOAD_FAST                4 (key)
             48 CALL_METHOD              1
             50 LOAD_METHOD              7 (hexdigest)
             52 CALL_METHOD              0
             54 LOAD_CONST               2 ('4b462bce910ff869f9a8ec2604fc9707')
             56 COMPARE_OP               2 (==)
             58 POP_JUMP_IF_FALSE       48 (to 96)

 24          60 LOAD_FAST                1 (ctx)
             62 LOAD_METHOD              8 (send)
             64 LOAD_CONST               3 ('Commencing express delivery! The flag is: ')
             66 LOAD_GLOBAL              2 (xor)
             68 LOAD_FAST                4 (key)
             70 LOAD_CONST               4 (2)
             72 BINARY_MULTIPLY
             74 LOAD_FAST                0 (self)
             76 LOAD_ATTR                9 (_secret)
             78 CALL_FUNCTION            2
             80 FORMAT_VALUE             0
             82 BUILD_STRING             2
             84 CALL_METHOD              1
             86 GET_AWAITABLE
             88 LOAD_CONST               0 (None)
             90 YIELD_FROM
             92 POP_TOP
             94 JUMP_FORWARD            17 (to 130)

 26     >>   96 LOAD_FAST                1 (ctx)
             98 LOAD_METHOD              8 (send)
            100 LOAD_CONST               5 ('Incorrect key. The Polar Express is not pleased.')
            102 CALL_METHOD              1
            104 GET_AWAITABLE
            106 LOAD_CONST               0 (None)
            108 YIELD_FROM
            110 POP_TOP
            112 JUMP_FORWARD             8 (to 130)

 28     >>  114 LOAD_FAST                1 (ctx)
            116 LOAD_METHOD              8 (send)
            118 LOAD_CONST               6 ('The Polar Express has not arrived yet.')
            120 CALL_METHOD              1
            122 GET_AWAITABLE
            124 LOAD_CONST               0 (None)
            126 YIELD_FROM
            128 POP_TOP

 29     >>  130 LOAD_FAST                2 (arg)
            132 LOAD_METHOD              3 (encode)
            134 CALL_METHOD              0
            136 LOAD_FAST                0 (self)
            138 STORE_ATTR               4 (_last_message)
            140 LOAD_CONST               0 (None)
            142 RETURN_VALUE

Since we know that the flag starts with OMEGAPOINT{ we can xor that with the secret to get the first charcahters of the key

def xor(s1, s2):
    return ''.join(chr(a ^ b) for a, b in zip(s1, s2))


flag = b"OMEGAPOINT{"
secret = bytes.fromhex("1b252067113f23283c743e0c18413a1943551b1d13113a06550b1c0d130f01031306481a08401e560e")

xor_key = xor(flag, secret)

print(xor_key)

This outputs: The Polar Exp

Nice!

Using this we can programaticly add more characters to the key, and then the flag to get a readable flag in the end.



def xor(s1, s2):
    return ''.join(chr(a ^ b) for a, b in zip(s1, s2))


flag = b"OMEGAPOINT{"
secret = bytes.fromhex("1b252067113f23283c743e0c18413a1943551b1d13113a06550b1c0d130f01031306481a08401e560e")

xor_key = xor(flag, secret)

print(xor_key)

xor_key = b"The Polar Exp"

flag = xor(xor_key, secret)

print(flag)

xor_key = xor(flag.encode(), secret)

print(xor_key)

xor_key = b"The Polar Express is "

flag = xor(xor_key, secret)

print(flag)

xor_key = xor(flag.encode(), secret)

print(xor_key)

flag = b"OMEGAPOINT{th3_j0urn3y"

xor_key = xor(flag, secret)

print(xor_key)

flag = b"OMEGAPOINT{th3_j0urn3y_"

xor_key = xor(flag, secret)

print(xor_key)

xor_key = b"The Polar Express is "

flag = xor(xor_key, secret)

flag = b"OMEGAPOINT{th3_j0urn3y_"

xor_key = xor(flag, secret)

print("HERE",xor_key)

xor_key = b"The Polar Express is here"

flag = xor(xor_key, secret)

print(flag)

flag = "OMEGAPOINT{th3_j0urn3y_t0"

xor_key = xor(flag.encode(), secret)

print(xor_key)

xor_key = b"The Polar Express is hereThe Polar Express is here"

flag = xor(xor_key, secret)

print(flag)

flag: OMEGAPOINT{th3_j0urn3y_t0_th3_north_p0l3}