Note:

I did not finish the last part of the challenge as it clashed with CNY 2025 and I got covid mid-way and was down for almost a week.. I would say I'm 90% completed though.. what a waste..

Pre-requistes

Before we start reverse engineering the executables in ancient-runes.zip, we have to download a few tools first.

WireShark

Download the appropriate version from https://www.wireshark.org/download.html.

Pyinstaller Extractor

First clone the Github repository at https://github.com/extremecoders-re/pyinstxtractor.git.

git clone https://github.com/extremecoders-re/pyinstxtractor.git

Decompyle++

First clone the Github repository at https://github.com/zrax/pycdc.git.

git clone https://github.com/zrax/pycdc.git

Since I have homebrew installed on my Macbook, I will install CMake and make to compile the source code above.

brew install cmake make

Once the formulas are installed, we are ready to go!

cmake -DCMAKE_BUILD_TYPE=Debug
make
sudo cp pycdc /usr/local/bin
sudo cp pycdas /usr/local/bin

Analysis of the Artifact

After downloading the file, I extracted the zip file and found the following content:

➜  pyinstxtractor git:(master) ✗ ll ../ancient-runes
total 35792
-rwxr-xr-x@ 1 alex  staff   8.7M Jan 13 22:23 client
-rwxr-xr-x@ 1 alex  staff   8.7M Jan 13 22:22 dev_server
-rwxrwxrwx@ 1 alex  staff    29K Jan 20 22:24 蛇年吉祥.pcapng

Let's try to run the executables. Since I'm using M2 Macbook Pro, I'm on the ARM64 architecture. I need to check the executable to see if it's compiled for ARM64 or x64.

➜  ancient-runes file client
client: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=8485f6953c06d12b9865185ba3466fdbf9b4a65c, for GNU/Linux 2.6.32, stripped
➜  ancient-runes file dev_server
dev_server: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=8485f6953c06d12b9865185ba3466fdbf9b4a65c, for GNU/Linux 2.6.32, stripped

In this case, I will have to use a docker container in x64 arch to execute the files. Let's start by creating a network:

docker network create csit-test-network

Then create the container to examine the executables:

docker run -it --platform linux/amd64 --network csit-test-network -v <path to folder>/ancient-runes:/files debian /bin/bash

Running client:

root@a94da9751b86:/files# ./client
2025-01-23 20:27:56,979 - INFO - DEV_client.crt not found. Downloading...
2025-01-23 20:27:57,427 - INFO - Downloaded file from http://34.57.139.144:80/REVW/DEV_client.crt to DEV_client.crt
2025-01-23 20:27:57,428 - INFO - DEV_client.key not found. Downloading...
2025-01-23 20:27:57,987 - INFO - Downloaded file from http://34.57.139.144:80/REVW/DEV_client.key to DEV_client.key
2025-01-23 20:27:57,987 - INFO - DEV_ca.crt not found. Downloading...
2025-01-23 20:27:58,455 - INFO - Downloaded file from http://34.57.139.144:80/REVW/DEV_ca.crt to DEV_ca.crt

Running server:

root@a94da9751b86:/files# ./dev_server
2025-01-23 20:28:24,684 - INFO - DEV_server.crt not found. Downloading...
2025-01-23 20:28:25,301 - INFO - Downloaded file from http://34.57.139.144:80/REVW/DEV_server.crt to DEV_server.crt
2025-01-23 20:28:25,302 - INFO - DEV_server.key not found. Downloading...
2025-01-23 20:28:25,876 - INFO - Downloaded file from http://34.57.139.144:80/REVW/DEV_server.key to DEV_server.key
2025-01-23 20:28:25,877 - INFO - DEV_ca.crt found.
2025-01-23 20:28:25,952 - INFO - server listening on 0.0.0.0:45605
2025-01-23 20:28:25,952 - INFO - server listening on [::]:45025
2025-01-23 20:28:25,952 - INFO - Server started...

That's a good start. It downloaded DEV_client.crt, DEV_client.key, DEV_server.crt, DEV_server.key, DEV_ca.crt from http://34.57.139.144:80/REVW/<certs>.

Let's try to analyze the executables by decompiling to understand how it works.

Since the hint was given these 2 executable was created with PyInstaller, we can use PyInstaller Extractor to extract the content of the executables (client & dev_server).

➜  pyinstxtractor git:(master) python3 pyinstxtractor.py ../ancient-runes/client

[+] Processing ../ancient-runes/client
[+] Pyinstaller version: 2.1+
[+] Python version: 3.10
[+] Length of package: 9084051 bytes
[+] Found 98 files in CArchive
[+] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap.pyc
[+] Possible entry point: pyi_rth_inspect.pyc
[+] Possible entry point: pyi_rth_pkgutil.pyc
[+] Possible entry point: pyi_rth_multiprocessing.pyc
[+] Possible entry point: client.pyc
[!] Warning: This script is running in a different Python version than the one used to build the executable.
[!] Please run this script in Python 3.10 to prevent extraction errors during unmarshalling
[!] Skipping pyz extraction
[+] Successfully extracted pyinstaller archive: ../ancient-runes/client

You can now use a python decompiler on the pyc files within the extracted directory
➜  pyinstxtractor git:(master) ✗ python3 pyinstxtractor.py ../ancient-runes/dev_server
[+] Processing ../ancient-runes/dev_server
[+] Pyinstaller version: 2.1+
[+] Python version: 3.10
[+] Length of package: 9084774 bytes
[+] Found 98 files in CArchive
[+] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap.pyc
[+] Possible entry point: pyi_rth_inspect.pyc
[+] Possible entry point: pyi_rth_pkgutil.pyc
[+] Possible entry point: pyi_rth_multiprocessing.pyc
[+] Possible entry point: dev_server.pyc
[!] Warning: This script is running in a different Python version than the one used to build the executable.
[!] Please run this script in Python 3.10 to prevent extraction errors during unmarshalling
[!] Skipping pyz extraction
[+] Successfully extracted pyinstaller archive: ../ancient-runes/dev_server

You can now use a python decompiler on the pyc files within the extracted directory

PyInstaller Extractor is telling us that the client and dev_server is using Python 3.10, so I re-did the extraction again with Python 3.10.

➜  pyinstxtractor git:(master) ✗ python3.10 pyinstxtractor.py ../ancient-runes/dev_server
[+] Processing ../ancient-runes/dev_server
[+] Pyinstaller version: 2.1+
[+] Python version: 3.10
[+] Length of package: 9084774 bytes
[+] Found 98 files in CArchive
[+] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap.pyc
[+] Possible entry point: pyi_rth_inspect.pyc
[+] Possible entry point: pyi_rth_pkgutil.pyc
[+] Possible entry point: pyi_rth_multiprocessing.pyc
[+] Possible entry point: dev_server.pyc
[+] Found 282 files in PYZ archive
[+] Successfully extracted pyinstaller archive: ../ancient-runes/dev_server

You can now use a python decompiler on the pyc files within the extracted directory
➜  pyinstxtractor git:(master) ✗ python3.10 pyinstxtractor.py ../ancient-runes/client
[+] Processing ../ancient-runes/client
[+] Pyinstaller version: 2.1+
[+] Python version: 3.10
[+] Length of package: 9084051 bytes
[+] Found 98 files in CArchive
[+] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap.pyc
[+] Possible entry point: pyi_rth_inspect.pyc
[+] Possible entry point: pyi_rth_pkgutil.pyc
[+] Possible entry point: pyi_rth_multiprocessing.pyc
[+] Possible entry point: client.pyc
[+] Found 282 files in PYZ archive
[+] Successfully extracted pyinstaller archive: ../ancient-runes/client

You can now use a python decompiler on the pyc files within the extracted directory

Inspection of Client

Let's inspect the files of client executable:

➜  pyinstxtractor git:(master) ✗ cd client_extracted
➜  client_extracted git:(master) ✗ ll
total 29232
drwxr-xr-x@  8 alex  staff   256B Jan 24 01:39 Crypto
-rw-r--r--@  1 alex  staff   1.6M Jan 24 01:38 PYZ-00.pyz
drwxr-xr-x@  2 alex  staff    64B Jan 24 01:38 PYZ-00.pyz_extracted
-rw-r--r--@  1 alex  staff   860K Jan 24 01:38 base_library.zip
-rw-r--r--@  1 alex  staff   2.5K Jan 24 01:38 client.pyc
drwxr-xr-x@ 26 alex  staff   832B Jan 24 01:39 lib-dynload
-rw-r--r--@  1 alex  staff    73K Jan 24 01:38 libbz2.so.1.0
-rw-r--r--@  1 alex  staff   4.2M Jan 24 01:38 libcrypto.so.3
-rw-r--r--@  1 alex  staff   190K Jan 24 01:38 libexpat.so.1
-rw-r--r--@  1 alex  staff    47K Jan 24 01:38 libffi.so.8
-rw-r--r--@  1 alex  staff   166K Jan 24 01:38 liblzma.so.5
-rw-r--r--@  1 alex  staff   183K Jan 24 01:38 libmpdec.so.3
-rw-r--r--@  1 alex  staff   5.6M Jan 24 01:38 libpython3.10.so
-rw-r--r--@  1 alex  staff   328K Jan 24 01:38 libreadline.so.8
-rw-r--r--@  1 alex  staff   652K Jan 24 01:38 libssl.so.3
-rw-r--r--@  1 alex  staff   195K Jan 24 01:38 libtinfo.so.6
-rw-r--r--@  1 alex  staff    30K Jan 24 01:38 libuuid.so.1
-rw-r--r--@  1 alex  staff   106K Jan 24 01:38 libz.so.1
-rw-r--r--@  1 alex  staff   909B Jan 24 01:38 pyi_rth_inspect.pyc
-rw-r--r--@  1 alex  staff   1.1K Jan 24 01:38 pyi_rth_multiprocessing.pyc
-rw-r--r--@  1 alex  staff   964B Jan 24 01:38 pyi_rth_pkgutil.pyc
-rw-r--r--@  1 alex  staff   873B Jan 24 01:38 pyiboot01_bootstrap.pyc
-rw-r--r--@  1 alex  staff   3.0K Jan 24 01:38 pyimod01_archive.pyc
-rw-r--r--@  1 alex  staff    21K Jan 24 01:38 pyimod02_importers.pyc
-rw-r--r--@  1 alex  staff   3.6K Jan 24 01:38 pyimod03_ctypes.pyc
-rw-r--r--@  1 alex  staff   287B Jan 24 01:38 struct.pyc
drwxr-xr-x@  3 alex  staff    96B Jan 24 01:39 websockets
drwxr-xr-x@  9 alex  staff   288B Jan 24 01:39 websockets-14.1.dist-info

Let's decompile the bytecode for client.pyc.

➜  client_extracted git:(master) ✗ pycdc client.pyc > client.py
Unsupported opcode: BEFORE_ASYNC_WITH (94)
Unsupported opcode: BEFORE_ASYNC_WITH (94)

Let's look at the content for client.py

# Source Generated with Decompyle++
# File: client.pyc (Python 3.10)

from dotenv import load_dotenv
import asyncio
import websockets
import logging
import threading
import ssl
import os
import urllib.request as urllib
import sys
import socket
from common import validate, generate_client_context, generate_random_string
from constants import LOGGING_VERBOSITY, HINT_3
load_dotenv()
logging.basicConfig(LOGGING_VERBOSITY, '%(asctime)s - %(levelname)s - %(message)s', **('level', 'format'))
SERVER_IP = os.getenv('SERVER_IP', '127.127.127.127')
SERVER_PORT = os.getenv('SERVER_PORT', '9999')
WS_IP = os.getenv('WS_IP', '0.0.0.0')
WS_PORT = os.getenv('WS_PORT', '8000')
TIMEOUT = 5
CA_CERT = 'DEV_ca.crt'
CLIENT_CERT = 'DEV_client.crt'
CLIENT_KEY = 'DEV_client.key'
CLIENT_SERVER = f'''http://{SERVER_IP}:{SERVER_PORT}/REVW/'''
CLIENT_PATH = ''
CERT_URL = f'''{CLIENT_SERVER}{CLIENT_CERT}'''
CERT_PATH = f'''{CLIENT_PATH}{CLIENT_CERT}'''
KEY_URL = f'''{CLIENT_SERVER}{CLIENT_KEY}'''
KEY_PATH = f'''{CLIENT_PATH}{CLIENT_KEY}'''
CA_URL = f'''{CLIENT_SERVER}{CA_CERT}'''
CA_PATH = f'''{CLIENT_PATH}{CA_CERT}'''
validate(CERT_URL, CERT_PATH, logging)
validate(KEY_URL, KEY_PATH, logging)
validate(CA_URL, CA_PATH, logging)
context = generate_client_context(CERT_PATH, KEY_PATH, CA_PATH, logging)

async def listen_messages(uri, headers, log):
    pass
# WARNING: Decompyle incomplete


async def send_commands(uri, headers, log):
    pass
# WARNING: Decompyle incomplete


def start_client():
    _string = generate_random_string()
    headersl = {
        'X-ARBOC': f'''{_string}-listener''' }
    headerss = {
        'X-ARBOC': f'''{_string}-sender''' }
    uri = f'''wss://{WS_IP}:{WS_PORT}/'''
    listener_thread = None((lambda : asyncio.run(listen_messages(uri, headersl, logging))), **('target',))
    listener_thread.daemon = True
    listener_thread.start()
    asyncio.run(send_commands(uri, headerss, logging))

if __name__ == '__main__':
    start_client()
    return None

We can see that the async functions listen_messages() and send_commands() fails to decompile.

Let's try with another tool PyLingual. I get the following. It looks like a very simple websocket client.

# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: client.py
# Bytecode version: 3.10.0rc2 (3439)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)

from dotenv import load_dotenv
import asyncio
import websockets
import logging
import threading
import ssl
import os
import urllib.request
import sys
import socket
from common import validate, generate_client_context, generate_random_string
from constants import LOGGING_VERBOSITY, HINT_3
load_dotenv()
logging.basicConfig(level=LOGGING_VERBOSITY, format='%(asctime)s - %(levelname)s - %(message)s')
SERVER_IP = os.getenv('SERVER_IP', '127.127.127.127')
SERVER_PORT = os.getenv('SERVER_PORT', '9999')
WS_IP = os.getenv('WS_IP', '0.0.0.0')
WS_PORT = os.getenv('WS_PORT', '8000')
TIMEOUT = 5
CA_CERT = 'DEV_ca.crt'
CLIENT_CERT = 'DEV_client.crt'
CLIENT_KEY = 'DEV_client.key'
CLIENT_SERVER = f'http://{SERVER_IP}:{SERVER_PORT}/REVW/'
CLIENT_PATH = ''
CERT_URL = f'{CLIENT_SERVER}{CLIENT_CERT}'
CERT_PATH = f'{CLIENT_PATH}{CLIENT_CERT}'
KEY_URL = f'{CLIENT_SERVER}{CLIENT_KEY}'
KEY_PATH = f'{CLIENT_PATH}{CLIENT_KEY}'
CA_URL = f'{CLIENT_SERVER}{CA_CERT}'
CA_PATH = f'{CLIENT_PATH}{CA_CERT}'
validate(CERT_URL, CERT_PATH, logging)
validate(KEY_URL, KEY_PATH, logging)
validate(CA_URL, CA_PATH, logging)
context = generate_client_context(CERT_PATH, KEY_PATH, CA_PATH, logging)

async def listen_messages(uri, headers, log):
    try:
        async with websockets.connect(uri, additional_headers=headers, ssl=context) as websocket:
            while True:
                message = await websocket.recv()
                print(f'>> {message}0')
    except asyncio.TimeoutError:
        return None
    except Exception as E:
        log.debug(f'{HINT_3}')

async def send_commands(uri, headers, log):
    try:
        async with websockets.connect(uri, additional_headers=headers, ssl=context) as websocket:
            while True:
                message = input('')
                if message.strip():
                    await websocket.send(message)
    except asyncio.TimeoutError:
        return None
    except Exception as E:
        log.debug(f'{HINT_3}')

def start_client():
    _string = generate_random_string()
    headersl = {'X-ARBOC': f'{_string}0-listener'}
    headerss = {'X-ARBOC': f'{_string}0-sender'}
    uri = f'wss://{WS_IP}:{WS_PORT}/'
    listener_thread = threading.Thread(target=lambda: asyncio.run(listen_messages(uri, headersl, logging)))
    listener_thread.daemon = True
    listener_thread.start()
    asyncio.run(send_commands(uri, headerss, logging))
if __name__ == '__main__':
    start_client()

We can see that there's a common module that was imported and not part of the standard python module. Let's try to decompile common.pyc bytecode!

# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: common.py
# Bytecode version: 3.10.0rc2 (3439)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)

import os
import urllib.request
import ssl
import random
import string
from Crypto.Cipher import DES3
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad, unpad
from constants import HINT_1, HINT_2

def generate_random_string(length=8):
    return ''.join(random.choices(string.ascii_letters + string.digits, k=length))

def download_file(url, dest_path, log):
    try:
        with urllib.request.urlopen(url) as response:
            with open(dest_path, 'wb') as f:
                f.write(response.read())
        log.info(f'Downloaded file from {url}0 to {dest_path}0')
        return True
    except Exception as e:
        return False

def validate(URL, FILE, log):
    if not os.path.exists(FILE):
        log.info(f'{FILE} not found. Downloading...')
        if not download_file(URL, FILE, log):
            log.debug(f'{HINT_1}')
    else:
        log.info(f'{FILE} found.')

def decrypt_3des(ciphertext: bytes, key: bytes):
    iv = ciphertext[:DES3.block_size]
    cipher = DES3.new(key, DES3.MODE_CBC, iv=iv)
    decrypted_data = unpad(cipher.decrypt(ciphertext[DES3.block_size:]), DES3.block_size)
    return decrypted_data.decode()

def generate_client_context(CERT_PATH, KEY_PATH, CA_PATH, log):
    try:
        context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=CA_PATH)
        context.options |= ssl.OP_NO_SSLv2
        context.options |= ssl.OP_NO_SSLv3
        context.load_cert_chain(certfile=CERT_PATH, keyfile=KEY_PATH)
        context.options |= ssl.OP_NO_TLSv1_3
        context.set_ciphers('RSA')
        return context
    except Exception as e:
        print(e)
        log.debug(f'{HINT_2}')

def generate_server_context(CERT_PATH, KEY_PATH, CA_PATH, log):
    try:
        context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
        context.load_cert_chain(certfile=CERT_PATH, keyfile=KEY_PATH)
        context.load_verify_locations(cafile=CA_PATH)
        context.verify_mode = ssl.CERT_REQUIRED
        context.options |= ssl.OP_NO_TLSv1_3
        context.set_ciphers('RSA')
        return context
    except Exception as e:
        log.debug(f'{HINT_2}')

def init():
    if not os.path.exists('.env'):
        env_content = '\nLOGGING_VERBOSITY=REG0D\n\nSERVER_IP=34.57.139.144\nSERVER_PORT=80\n\nWS_IP=\nWS_PORT=\n'
        with open('.env', 'w') as env_file:
            env_file.write(env_content.strip())
init()

We see that there's also a constants module that is not standard. Let's try to decompile that too!

# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: constants.py
# Bytecode version: 3.10.0rc2 (3439)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)

import logging
import os
from dotenv import load_dotenv
load_dotenv()
LOGGING_VERBOSITY = os.getenv('LOGGING_VERBOSITY', 'NONE')
LOGGING_MORE_VERBOSITY = os.getenv('LOGGING_MORE_VERBOSITY', 'NONE')
if LOGGING_VERBOSITY == 'REG0D' and LOGGING_MORE_VERBOSITY == 'y0uReOnToSomEThinG':
    LOGGING_VERBOSITY = logging.DEBUG
elif LOGGING_VERBOSITY == 'REG0D':
    LOGGING_VERBOSITY = logging.INFO
else:
    LOGGING_VERBOSITY = logging.ERROR
HINT_1 = '[HINT] Have you tried REading the binaries? Where are you downloading them from?'
HINT_2 = '[HINT] Have you tried REading the binaries? Is your SSL Context malformed or what?'
HINT_3 = "[HINT] Have you tried REading the binaries? Bruh we can't make a connection..."

Ahh.. sneaky. We can see that in the init sequence, a .env file is automatically created with LOGGING_VERBOSITY=REG0D. However, analyzing constants.py, we realised that that will configure the logger to INFO only. If we set the LOGGING_VERBOSITY to any other values, it will set to ERROR only.

def init():
    if not os.path.exists('.env'):
        env_content = '\nLOGGING_VERBOSITY=REG0D\n\nSERVER_IP=34.57.139.144\nSERVER_PORT=80\n\nWS_IP=\nWS_PORT=\n'
        with open('.env', 'w') as env_file:
            env_file.write(env_content.strip())
init()

We will need to enable DEBUG level as many hints and errors are reported at DEBUG level based on the decompiled code. It's also sneaky that it will try to get ENV variables for LOGGING_VERBOSITY and LOGGING_MORE_VERBOSITY and set to NONE if no ENV variables exists. This means setting it in .env will not work!

Let's add the ENV variables!

export LOGGING_VERBOSITY=REG0D
export LOGGING_MORE_VERBOSITY=y0uReOnToSomEThinG

Now, let's run the client!

Note: I have already ran the dev_server executable when I'm writing this. Please run the executable below to get the same output as me.
root@4b3dc7eb6580:/files# export LOGGING_VERBOSITY=REG0D
export LOGGING_MORE_VERBOSITY=y0uReOnToSomEThinG
root@4b3dc7eb6580:/files# ./client
2025-01-27 17:59:55,906 - INFO - DEV_client.crt found.
2025-01-27 17:59:55,907 - INFO - DEV_client.key found.
2025-01-27 17:59:55,907 - INFO - DEV_ca.crt found.
2025-01-27 17:59:55,927 - DEBUG - Using selector: EpollSelector
2025-01-27 17:59:55,927 - DEBUG - Using selector: EpollSelector
2025-01-27 17:59:55,951 - DEBUG - = connection is CONNECTING
2025-01-27 17:59:55,952 - DEBUG - = connection is CONNECTING
2025-01-27 17:59:56,012 - DEBUG - > GET / HTTP/1.1
2025-01-27 17:59:56,012 - DEBUG - > Host: 0.0.0.0:8000
2025-01-27 17:59:56,012 - DEBUG - > Upgrade: websocket
2025-01-27 17:59:56,012 - DEBUG - > Connection: Upgrade
2025-01-27 17:59:56,012 - DEBUG - > Sec-WebSocket-Key: WZfM4iInYIqmmdTuKKo01A==
2025-01-27 17:59:56,012 - DEBUG - > Sec-WebSocket-Version: 13
2025-01-27 17:59:56,012 - DEBUG - > Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
2025-01-27 17:59:56,012 - DEBUG - > X-ARBOC: jIkMgQPk-sender
2025-01-27 17:59:56,013 - DEBUG - > User-Agent: Python/3.10 websockets/14.1
2025-01-27 17:59:56,013 - DEBUG - > GET / HTTP/1.1
2025-01-27 17:59:56,013 - DEBUG - > Host: 0.0.0.0:8000
2025-01-27 17:59:56,013 - DEBUG - > Upgrade: websocket
2025-01-27 17:59:56,014 - DEBUG - > Connection: Upgrade
2025-01-27 17:59:56,014 - DEBUG - > Sec-WebSocket-Key: 9rd5iIBBz9Hr1mGfEOxj2A==
2025-01-27 17:59:56,014 - DEBUG - > Sec-WebSocket-Version: 13
2025-01-27 17:59:56,014 - DEBUG - > Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
2025-01-27 17:59:56,014 - DEBUG - > X-ARBOC: jIkMgQPk-listener
2025-01-27 17:59:56,014 - DEBUG - > User-Agent: Python/3.10 websockets/14.1
2025-01-27 17:59:56,018 - DEBUG - < HTTP/1.1 101 Switching Protocols
2025-01-27 17:59:56,018 - DEBUG - < Date: Mon, 27 Jan 2025 17:59:56 GMT
2025-01-27 17:59:56,018 - DEBUG - < Upgrade: websocket
2025-01-27 17:59:56,018 - DEBUG - < Connection: Upgrade
2025-01-27 17:59:56,018 - DEBUG - < Sec-WebSocket-Accept: zCaEoA1aGh0BW8glRw0+cLy48+w=
2025-01-27 17:59:56,018 - DEBUG - < Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=12; client_max_window_bits=12
2025-01-27 17:59:56,018 - DEBUG - < Server: Python/3.10 websockets/14.1
2025-01-27 17:59:56,019 - DEBUG - = connection is OPEN
2025-01-27 17:59:56,019 - DEBUG - < TEXT 'Welcome to the chat!' [20 bytes]
2025-01-27 17:59:56,020 - DEBUG - < HTTP/1.1 101 Switching Protocols
2025-01-27 17:59:56,021 - DEBUG - < Date: Mon, 27 Jan 2025 17:59:56 GMT
2025-01-27 17:59:56,021 - DEBUG - < Upgrade: websocket
2025-01-27 17:59:56,021 - DEBUG - < Connection: Upgrade
2025-01-27 17:59:56,021 - DEBUG - < Sec-WebSocket-Accept: uOuFSadFGyGUkr11vK01ly0scFU=
2025-01-27 17:59:56,021 - DEBUG - < Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=12; client_max_window_bits=12
2025-01-27 17:59:56,021 - DEBUG - < Server: Python/3.10 websockets/14.1
2025-01-27 17:59:56,021 - DEBUG - = connection is OPEN
2025-01-27 17:59:56,021 - DEBUG - < TEXT 'Welcome to the chat!' [20 bytes]
>> Welcome to the chat!

Inspection of Dev_Server

Let's inspect the files of dev_server executable:

➜  dev_server_extracted git:(master) ✗ ll
total 29240
drwxr-xr-x@  8 alex  staff   256B Jan 24 01:39 Crypto
-rw-r--r--@  1 alex  staff   1.6M Jan 24 01:38 PYZ-00.pyz
drwxr-xr-x@  2 alex  staff    64B Jan 24 01:38 PYZ-00.pyz_extracted
-rw-r--r--@  1 alex  staff   860K Jan 24 01:38 base_library.zip
-rw-r--r--@  1 alex  staff   3.5K Jan 24 01:38 dev_server.pyc
drwxr-xr-x@ 26 alex  staff   832B Jan 24 01:39 lib-dynload
-rw-r--r--@  1 alex  staff    73K Jan 24 01:38 libbz2.so.1.0
-rw-r--r--@  1 alex  staff   4.2M Jan 24 01:38 libcrypto.so.3
-rw-r--r--@  1 alex  staff   190K Jan 24 01:38 libexpat.so.1
-rw-r--r--@  1 alex  staff    47K Jan 24 01:38 libffi.so.8
-rw-r--r--@  1 alex  staff   166K Jan 24 01:38 liblzma.so.5
-rw-r--r--@  1 alex  staff   183K Jan 24 01:38 libmpdec.so.3
-rw-r--r--@  1 alex  staff   5.6M Jan 24 01:38 libpython3.10.so.1.0
-rw-r--r--@  1 alex  staff   328K Jan 24 01:38 libreadline.so.8
-rw-r--r--@  1 alex  staff   652K Jan 24 01:38 libssl.so.3
-rw-r--r--@  1 alex  staff   195K Jan 24 01:38 libtinfo.so.6
-rw-r--r--@  1 alex  staff    30K Jan 24 01:38 libuuid.so.1
-rw-r--r--@  1 alex  staff   106K Jan 24 01:38 libz.so.1
-rw-r--r--@  1 alex  staff   909B Jan 24 01:38 pyi_rth_inspect.pyc
-rw-r--r--@  1 alex  staff   1.1K Jan 24 01:38 pyi_rth_multiprocessing.pyc
-rw-r--r--@  1 alex  staff   964B Jan 24 01:38 pyi_rth_pkgutil.pyc
-rw-r--r--@  1 alex  staff   873B Jan 24 01:38 pyiboot01_bootstrap.pyc
-rw-r--r--@  1 alex  staff   3.0K Jan 24 01:38 pyimod01_archive.pyc
-rw-r--r--@  1 alex  staff    21K Jan 24 01:38 pyimod02_importers.pyc
-rw-r--r--@  1 alex  staff   3.6K Jan 24 01:38 pyimod03_ctypes.pyc
-rw-r--r--@  1 alex  staff   287B Jan 24 01:38 struct.pyc
drwxr-xr-x@  3 alex  staff    96B Jan 24 01:39 websockets
drwxr-xr-x@  9 alex  staff   288B Jan 24 01:39 websockets-14.1.dist-info

Let's decompile the bytecode for dev_server.pyc.

➜  dev_server_extracted git:(master) ✗ pycdc dev_server.pyc > dev_server.py
Unsupported use of GET_AITER outside of SETUP_LOOP
Unsupported opcode: END_ASYNC_FOR (98)
Unsupported opcode: JUMP_IF_NOT_EXC_MATCH (210)
Unsupported Node type: 28

Let's look at the content for dev_server.py

# Source Generated with Decompyle++
# File: dev_server.pyc (Python 3.10)

from dotenv import load_dotenv
import asyncio
import websockets
import logging
import os
from common import validate, generate_server_context, decrypt_3des
from constants import LOGGING_VERBOSITY
load_dotenv()
logging.basicConfig(LOGGING_VERBOSITY, '%(asctime)s - %(levelname)s - %(message)s', **('level', 'format'))
SERVER_IP = os.getenv('SERVER_IP', '127.127.127.127')
SERVER_PORT = os.getenv('SERVER_PORT', '9999')
WS_IP = os.getenv('WS_IP', '0.0.0.0')
WS_PORT = os.getenv('WS_PORT', '8000')
CA_CERT = 'DEV_ca.crt'
SERVER_CERT = 'DEV_server.crt'
SERVER_KEY = 'DEV_server.key'
SERVER_SERVER = f'''http://{SERVER_IP}:{SERVER_PORT}/REVW/'''
SERVER_PATH = ''
CERT_URL = f'''{SERVER_SERVER}{SERVER_CERT}'''
CERT_PATH = f'''{SERVER_PATH}{SERVER_CERT}'''
KEY_URL = f'''{SERVER_SERVER}{SERVER_KEY}'''
KEY_PATH = f'''{SERVER_PATH}{SERVER_KEY}'''
CA_URL = f'''{SERVER_SERVER}{CA_CERT}'''
CA_PATH = f'''{SERVER_PATH}{CA_CERT}'''
validate(CERT_URL, CERT_PATH, logging)
validate(KEY_URL, KEY_PATH, logging)
validate(CA_URL, CA_PATH, logging)
context = generate_server_context(CERT_PATH, KEY_PATH, CA_PATH, logging)
clients = { }
FLAG = '7eb66acfb3652e80ef006143b4e5b6565b84b51355b26e39d2979a3bc873ba394ecae0061bd9522a9639ac4488733ad97d5e5acfb1e3e6f7'

def header_extraction(websocket):
    headers = websocket.request.headers['X-ARBOC']
    parts = headers.split('-', 1)
    clientId = parts[0]
    clientType = parts[1]
    return [
        clientId,
        clientType]


async def handler(websocket):
    parts = header_extraction(websocket)
    clientId = parts[0]
    clientType = parts[1]
    if clientId not in clients:
        clients[clientId] = {
            clientType: websocket }
    else:
        clients[clientId][clientType] = websocket
# WARNING: Decompyle incomplete


async def broadcast(message, websocket):
    to_remove = []
    senderId = header_extraction(websocket)[0]
# WARNING: Decompyle incomplete


async def main():
    if context != None:
        await websockets.serve(handler, WS_IP, WS_PORT, context, 3600, 3600, 600, **('ssl', 'ping_interval', 'ping_timeout', 'close_timeout'))
        server = <NODE:28>
        logging.info('Server started...')
        await server.wait_closed()
        return None

if __name__ == '__main__':
    asyncio.run(main())
    return None

We can see that the async functions handler() and broadcast() fails to decompile.

Let's try with PyLingual again. I get the following:

# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: dev_server.py
# Bytecode version: 3.10.0rc2 (3439)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)

from dotenv import load_dotenv
import asyncio
import websockets
import logging
import os
from common import validate, generate_server_context, decrypt_3des
from constants import LOGGING_VERBOSITY
load_dotenv()
logging.basicConfig(level=LOGGING_VERBOSITY, format='%(asctime)s - %(levelname)s - %(message)s')
SERVER_IP = os.getenv('SERVER_IP', '127.127.127.127')
SERVER_PORT = os.getenv('SERVER_PORT', '9999')
WS_IP = os.getenv('WS_IP', '0.0.0.0')
WS_PORT = os.getenv('WS_PORT', '8000')
CA_CERT = 'DEV_ca.crt'
SERVER_CERT = 'DEV_server.crt'
SERVER_KEY = 'DEV_server.key'
SERVER_SERVER = f'http://{SERVER_IP}:{SERVER_PORT}/REVW/'
SERVER_PATH = ''
CERT_URL = f'{SERVER_SERVER}{SERVER_CERT}'
CERT_PATH = f'{SERVER_PATH}{SERVER_CERT}'
KEY_URL = f'{SERVER_SERVER}{SERVER_KEY}'
KEY_PATH = f'{SERVER_PATH}{SERVER_KEY}'
CA_URL = f'{SERVER_SERVER}{CA_CERT}'
CA_PATH = f'{SERVER_PATH}{CA_CERT}'
validate(CERT_URL, CERT_PATH, logging)
validate(KEY_URL, KEY_PATH, logging)
validate(CA_URL, CA_PATH, logging)
context = generate_server_context(CERT_PATH, KEY_PATH, CA_PATH, logging)
clients = {}
FLAG = '7eb66acfb3652e80ef006143b4e5b6565b84b51355b26e39d2979a3bc873ba394ecae0061bd9522a9639ac4488733ad97d5e5acfb1e3e6f7'

def header_extraction(websocket):
    headers = websocket.request.headers['X-ARBOC']
    parts = headers.split('-', 1)
    clientId = parts[0]
    clientType = parts[1]
    return [clientId, clientType]

async def handler(websocket):
    parts = header_extraction(websocket)
    clientId = parts[0]
    clientType = parts[1]
    if clientId not in clients:
        clients[clientId] = {clientType: websocket}
    else:
        clients[clientId][clientType] = websocket
    try:
        await websocket.send('Welcome to the chat!')
        async for message in websocket:
            logging.debug(f'Received message: {message}0')
            await broadcast(message, websocket)
    except websockets.exceptions.ConnectionClosed as e:
        logging.error(f'Connection closed: {e}0')

async def broadcast(message, websocket):
    to_remove = []
    senderId = header_extraction(websocket)[0]
    for client in clients:
        try:
            if '5n@k3' not in message:
                if client == senderId:
                    await clients[client]['listener'].send(f'r00t: {message}0')
                else:
                    await clients[client]['listener'].send(f'{senderId}: {message}0')
            elif len(message) <= 5:
                if client == senderId:
                    await clients[client]['listener'].send('s()p3rR00+: run --help')
            else:
                parts = message.split('#')
                if len(parts) > 2:
                    command = parts[1]
                    args1 = parts[2]
                    if command == '90' and len(args1) > 5:
                        os.system(f'ls {args1[:5]}0')
                    if command == '01':
                        os.system('ip addr')
                    if command == '33':
                        os.system('hostname')
                    if command == '27':
                        os.system('cat /proc/cpuinfo')
                    if command == '18':
                        os.system('whoami ')
                    if command == '88':
                        try:
                            decrypted_message = decrypt_3des(bytes.fromhex(FLAG), bytes.fromhex(args1))
                            if client == senderId:
                                await clients[client]['listener'].send(f's()p3rR00+: {decrypted_message}0')
                        except Exception as E:
                            logging.debug(E)
                            if client == senderId:
                                await clients[client]['listener'].send('s()p3rR00+: flag{this_is_real_definitely_not_fake}')
                    if command == '79':
                        os.system('dmesg | tail -n 10')
                    if command == '61':
                        os.system('ps aux')
                    if command == '49':
                        os.system('uptime -p')
                    if command == '55':
                        os.system('lscpu')
                    if command == '72':
                        os.system(f'{args1[:2]}')
        except websockets.exceptions.ConnectionClosed:
            to_remove.append(client)
    for client in to_remove:
        del clients[client]

async def main():
    if context != None:
        server = await websockets.serve(handler, WS_IP, WS_PORT, ssl=context, ping_interval=3600, ping_timeout=3600, close_timeout=600)
        logging.info('Server started...')
        await server.wait_closed()
if __name__ == '__main__':
    asyncio.run(main())

Since we already have commons.pyc and constants.pyc decompiled earlier, we will set the same ENV variables and start our executable!

root@4b3dc7eb6580:/files# export LOGGING_VERBOSITY=REG0D
root@4b3dc7eb6580:/files# export LOGGING_MORE_VERBOSITY=y0uReOnToSomEThinG
root@4b3dc7eb6580:/files# echo $LOGGING_MORE_VERBOSITY
y0uReOnToSomEThinG
root@4b3dc7eb6580:/files# ./dev_server
2025-01-27 17:57:05,826 - INFO - DEV_server.crt found.
2025-01-27 17:57:05,827 - INFO - DEV_server.key found.
2025-01-27 17:57:05,827 - INFO - DEV_ca.crt found.
2025-01-27 17:57:05,845 - DEBUG - Using selector: EpollSelector
2025-01-27 17:57:05,868 - INFO - server listening on 0.0.0.0:8000
2025-01-27 17:57:05,868 - INFO - Server started...

Let's inspect the methods in server.py. We know that if the client send a text that starts with "5n@k3", it will trigger the ELSE in the if-else statement. The messages are delimited by "#" and there are 2 parts. I think the part we are interested about is command 88 where it will trigger a method called decrypt_3des, which I assume it's Triple DES. We can see that 2 arguments (bytes.fromhex(FLAG), bytes.fromhex(args1)) were sent to the method, so let's go back to our decompiled common.py to understand the method:

def decrypt_3des(ciphertext: bytes, key: bytes):
    iv = ciphertext[:DES3.block_size]
    cipher = DES3.new(key, DES3.MODE_CBC, iv=iv)
    decrypted_data = unpad(cipher.decrypt(ciphertext[DES3.block_size:]), DES3.block_size)
    return decrypted_data.decode()

With this, we know that the ciphertext is the flag, the key is our args1 which we will send via the websocket client.

5n@k3#88#<key>

It's using PyCryptodome's DES3 implementation at https://pycryptodome.readthedocs.io/en/latest/src/cipher/des3.html. The Initialization Vector (IV) is taking the ciphertext which is the flag, slice it and take only 8 characters which is Triple DES's block size of 8 bytes (DES3.block_size=8).

IV = 7eb66acf

Initialization Vector (IV)

Let's try to obtain the key from investigating further!

Check connection to 34.123.42.200:80

Check connection to Given IP (34.123.42.200:80)

Since we know mTLS is used, let's try to get the server certificate with openssl.

echo | \
    openssl s_client -servername 34.123.42.200 -connect 34.123.42.200:80 2>/dev/null | \
    openssl x509 -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            64:6b:ba:54:a7:2c:45:e5:b2:f3:c4:ba:dc:a4:7b:9b:6d:b6:5e:d4
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=JP, ST=Okohamaya Prefecture, L=Mount GXFC, O=Some Legit Company Name Pty Ltd, OU=Snake, Cobras, Serpents , CN=www.venom.com, [email protected]
        Validity
            Not Before: Jan 20 14:11:49 2025 GMT
            Not After : Feb 19 14:11:49 2025 GMT
        Subject: CN=34.123.42.200
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:ab:fc:d5:7a:7b:fa:2f:ac:a3:e5:e0:00:6c:96:
                    99:44:ef:ed:1f:03:ee:4f:ae:d3:0f:d4:d6:0f:ef:
                    3d:15:40:ff:8f:49:43:20:47:af:84:eb:bc:7c:96:
                    48:ce:22:60:90:fd:31:43:eb:c7:eb:4d:e9:57:d8:
                    69:e5:15:70:d6:a5:15:a0:74:8b:5a:a5:ae:b6:e9:
                    4f:43:fb:2a:b6:45:a7:aa:49:e9:68:18:58:fe:c8:
                    e4:7c:fc:f7:d5:15:b8:7e:5d:7a:54:84:bb:8c:a7:
                    56:d1:15:28:30:03:85:f4:42:a8:98:35:31:1b:ca:
                    27:50:6f:d7:de:4d:62:15:dc:ac:d9:75:dc:39:4a:
                    38:7a:3c:b6:d6:73:42:ed:c8:39:3e:b7:46:e8:38:
                    92:f8:3b:43:90:5d:c3:d9:7c:66:e9:ab:60:bc:1b:
                    c9:1d:f3:e7:a7:30:38:b4:cc:0e:ab:41:0e:a8:2c:
                    77:22:21:4b:a6:4b:a6:9e:4d:6e:be:ca:7d:5b:b1:
                    b0:c9:88:b8:bb:42:9d:1c:a3:5c:a6:bb:d3:42:11:
                    65:0b:88:be:bb:23:f5:d4:cc:76:a7:bc:9e:5f:0d:
                    bc:b7:17:93:75:7a:7a:37:1f:7e:ec:57:61:f7:ce:
                    15:be:3e:74:83:cd:b0:ac:6c:25:57:7d:22:99:bd:
                    67:3f
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Basic Constraints:
                CA:FALSE
            X509v3 Key Usage:
                Digital Signature, Non Repudiation, Key Encipherment
            X509v3 Subject Alternative Name:
                IP Address:34.123.42.200, IP Address:0.0.0.0
            X509v3 Extended Key Usage:
                TLS Web Server Authentication
            1.2.12.5:
                ...PBFEJJDMPMFJNMHJJADFPBFEICCHPGFDNGHDJADFPJFMIPCKOKEPMKGPIICNPBFEIFCAOAEFJDDGLDBGPCFHIACFOFEAMFGAJCDHOAEFIPCKOBEEIGCDKGADPCFHJKDPPP
            1.2.12.6:
                ...FKNPHKJMDJPDFGIBCEPDFGJGDDPFFAIBCEKBAEODEGJKDPOOELILCOPIFNNIHNJLDOPEFBJJDMPLFOJCDHPMFJJJDMPNFINNHIIJCMOGEDIBCEOEEBJADFPIFNJNDIOPEK
            1.2.12.7:
                ...MPGKIGCDPFFANFHAJEDBPHFCIDCGPGFDJHDCPLFOJHDCOOELMOGLJKDPPCFHJHDCLHBCPCFHICCHONEIIOCLOGEDMGGDJDDGPNFIJEDBOMEJMMGJJIDNPBFEJMDJPJFMIK
            1.2.12.8:
                ...CPPOFLJPDKPCFHICCHKCAHOLEOIFCAKFAAPGFDJDDGPAFFJPDKPBFEJFDAOGEDMGGDIACFOPEKJNDILNBIPOFLJGDDPPFKJBDEPEFBIHCCOCEHMCGHIMCJOJEMJODLLOBL
            1.2.12.9:
                .$OHECICCHODEGJBDELBBEIDCGLDBGIBCELEBB
            X509v3 Subject Key Identifier:
                AC:51:47:8A:4E:5A:4E:A1:AA:58:23:A4:94:BC:5A:1C:ED:D4:70:C9
            X509v3 Authority Key Identifier:
                62:C5:A5:21:18:81:E1:93:B5:5C:35:F2:74:21:4C:D2:0F:15:FA:93
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        2f:3a:d9:7b:fd:55:7e:4f:ec:10:39:a4:df:41:4a:e7:04:77:
        e0:59:fc:8e:37:bd:7e:08:ee:2a:b1:06:32:16:e6:cd:44:fc:
        02:29:9f:1f:5b:68:44:92:1e:8f:22:8a:b9:84:cb:1e:1e:27:
        f9:a1:a2:69:85:15:f1:48:3b:f3:f3:81:4b:09:5f:67:fe:63:
        fd:da:34:05:02:35:29:3d:7e:43:1b:d0:8c:f1:87:1a:7b:03:
        f1:dc:5d:5e:35:bd:3b:66:4f:14:08:66:aa:e5:6f:5c:f3:e1:
        5a:d7:c7:30:10:07:12:21:fc:d4:d8:80:b8:d5:2c:1e:b9:2d:
        1c:c3:82:cc:0e:ec:ad:69:9f:67:3a:92:c2:35:1d:2c:e5:ea:
        61:99:44:82:bf:20:e8:9b:f9:d9:51:49:fe:da:0a:d3:51:10:
        d3:bb:2f:10:ad:f6:47:1d:40:4e:60:af:ff:6d:c8:3a:b3:21:
        c8:26:e8:ec:29:06:53:6f:66:d1:31:20:26:88:c5:61:9f:36:
        79:b3:d4:46:64:63:3a:18:01:ae:47:cb:13:e5:c5:e2:7c:ff:
        b9:41:6d:d4:89:84:a9:1c:c7:53:62:1e:f5:79:a8:2d:3e:07:
        2f:44:a8:7d:69:51:fb:94:10:2d:a4:70:22:61:ab:c1:35:eb:
        fd:5c:7d:92
-----BEGIN CERTIFICATE-----
MIIGZzCCBU+gAwIBAgIUZGu6VKcsReWy88S63KR7m222XtQwDQYJKoZIhvcNAQEL
BQAwgccxCzAJBgNVBAYTAkpQMR0wGwYDVQQIDBRPa29oYW1heWEgUHJlZmVjdHVy
ZTETMBEGA1UEBwwKTW91bnQgR1hGQzEoMCYGA1UECgwfU29tZSBMZWdpdCBDb21w
YW55IE5hbWUgUHR5IEx0ZDEhMB8GA1UECwwYU25ha2UsIENvYnJhcywgU2VycGVu
dHMgMRYwFAYDVQQDDA13d3cudmVub20uY29tMR8wHQYJKoZIhvcNAQkBFhBvYmFu
YWlAdmVub20uY29tMB4XDTI1MDEyMDE0MTE0OVoXDTI1MDIxOTE0MTE0OVowGDEW
MBQGA1UEAwwNMzQuMTIzLjQyLjIwMDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAKv81Xp7+i+so+XgAGyWmUTv7R8D7k+u0w/U1g/vPRVA/49JQyBHr4Tr
vHyWSM4iYJD9MUPrx+tN6VfYaeUVcNalFaB0i1qlrrbpT0P7KrZFp6pJ6WgYWP7I
5Hz899UVuH5delSEu4ynVtEVKDADhfRCqJg1MRvKJ1Bv195NYhXcrNl13DlKOHo8
ttZzQu3IOT63Rug4kvg7Q5Bdw9l8ZumrYLwbyR3z56cwOLTMDqtBDqgsdyIhS6ZL
pp5Nbr7KfVuxsMmIuLtCnRyjXKa700IRZQuIvrsj9dTMdqe8nl8NvLcXk3V6ejcf
fuxXYffOFb4+dIPNsKxsJVd9Ipm9Zz8CAwEAAaOCAvcwggLzMAkGA1UdEwQCMAAw
CwYDVR0PBAQDAgXgMBUGA1UdEQQOMAyHBCJ7KsiHBAAAAAAwEwYDVR0lBAwwCgYI
KwYBBQUHAwEwgY0GAyoMBQSBhQyBglBCRkVKSkRNUE1GSk5NSEpKQURGUEJGRUlD
Q0hQR0ZETkdIREpBREZQSkZNSVBDS09LRVBNS0dQSUlDTlBCRkVJRkNBT0FFRkpE
REdMREJHUENGSElBQ0ZPRkVBTUZHQUpDREhPQUVGSVBDS09CRUVJR0NES0dBRFBD
RkhKS0RQUFAwgY0GAyoMBgSBhQyBgkZLTlBIS0pNREpQREZHSUJDRVBERkdKR0RE
UEZGQUlCQ0VLQkFFT0RFR0pLRFBPT0VMSUxDT1BJRk5OSUhOSkxET1BFRkJKSkRN
UExGT0pDREhQTUZKSkpETVBORklOTkhJSUpDTU9HRURJQkNFT0VFQkpBREZQSUZO
Sk5ESU9QRUswgY0GAyoMBwSBhQyBgk1QR0tJR0NEUEZGQU5GSEFKRURCUEhGQ0lE
Q0dQR0ZESkhEQ1BMRk9KSERDT09FTE1PR0xKS0RQUENGSEpIRENMSEJDUENGSElD
Q0hPTkVJSU9DTE9HRURNR0dESkRER1BORklKRURCT01FSk1NR0pKSUROUEJGRUpN
REpQSkZNSUswgY0GAyoMCASBhQyBgkNQUE9GTEpQREtQQ0ZISUNDSEtDQUhPTEVP
SUZDQUtGQUFQR0ZESkRER1BBRkZKUERLUEJGRUpGREFPR0VETUdHRElBQ0ZPUEVL
Sk5ESUxOQklQT0ZMSkdERFBQRktKQkRFUEVGQklIQ0NPQ0VITUNHSElNQ0pPSkVN
Sk9ETExPQkwwLQYDKgwJBCYMJE9IRUNJQ0NIT0RFR0pCREVMQkJFSURDR0xEQkdJ
QkNFTEVCQjAdBgNVHQ4EFgQUrFFHik5aTqGqWCOklLxaHO3UcMkwHwYDVR0jBBgw
FoAUYsWlIRiB4ZO1XDXydCFM0g8V+pMwDQYJKoZIhvcNAQELBQADggEBAC862Xv9
VX5P7BA5pN9BSucEd+BZ/I43vX4I7iqxBjIW5s1E/AIpnx9baESSHo8iirmEyx4e
J/mhommFFfFIO/PzgUsJX2f+Y/3aNAUCNSk9fkMb0Izxhxp7A/HcXV41vTtmTxQI
Zqrlb1zz4VrXxzAQBxIh/NTYgLjVLB65LRzDgswO7K1pn2c6ksI1HSzl6mGZRIK/
IOib+dlRSf7aCtNRENO7LxCt9kcdQE5gr/9tyDqzIcgm6OwpBlNvZtExICaIxWGf
Nnmz1EZkYzoYAa5HyxPlxeJ8/7lBbdSJhKkcx1NiHvV5qC0+By9EqH1pUfuUEC2k
cCJhq8E16/1cfZI=
-----END CERTIFICATE-----

Inspection of Packet Capture

Since there's a packet capture included in the ancient_runes.zip, we will use wireshark to inspect the file.

The decompilation of client and dev_server seems to indicate the presence of 3 certificates for the communication (DEV_server.crt, DEV_client.crt and DEV_ca.crt). This signify the use of mTLS. Let's try to extract the certificate from the packet capture.

Opening the file in Wireshark:

Packet Capture opened in Wireshark

Let's try to filter the packets to see only TLS handshake with the following filter applied:

tls.handshake.type == 11
TLS Handshake Packets

With the 4 packets, we can see the following:

  • Line 11 and 13: Client Hello, Server Certificate, Client Certificate Request
  • Line 15 and 16: Client sent Certificate, Client Key Exchange, Certificate Verify, etc

Looking at the mTLS authentication sequence:

Image taken from https://developers.cloudflare.com/api-shield/security/mtls/

We can see the same authentication sequence in our packet capture. Let's try to export the certificate now.

Export Packet Bytes for Certificate

The second certificate length of 879 in Line 11 and Line 13 is the same for Line 15 and 16. We can assume that this is the CA certificate in the certificate chain.

Let's try to inspect the exported certificate with openssl:

openssl x509 -in DEV_ca.crt -inform der -text -noout
openssl x509 -in DEV_client.crt -inform der -text -noout
openssl x509 -in DEV_server.crt -inform der -text -noout

We get the following certificates:

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            4d:dc:7d:da:90:4a:54:6f:c4:e7:4b:be:cf:31:be:6d:9b:0e:c7:90
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd
        Validity
            Not Before: Jan 20 14:06:22 2025 GMT
            Not After : Feb 19 14:06:22 2025 GMT
        Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:db:e0:a6:6d:07:1b:f6:29:6c:83:3a:6f:a7:4b:
                    61:f0:cd:b2:44:e9:0e:2a:6f:36:4b:1f:5c:36:a3:
                    54:df:65:c5:16:e9:b1:bc:21:65:bd:d3:9f:a4:7a:
                    d1:4f:05:48:a2:33:2e:ed:bf:e3:33:81:67:1d:4b:
                    8e:c7:c6:d3:9c:27:0b:9e:eb:ce:84:06:4d:f6:4e:
                    2b:58:bd:aa:a7:7b:17:cd:cc:87:e3:8b:b5:8f:2a:
                    64:4b:68:46:13:da:c6:3b:a9:27:1a:83:6a:db:8d:
                    e8:2e:7d:57:50:5b:e3:73:0c:7d:aa:ba:27:df:2c:
                    47:97:f4:e1:5b:f8:05:77:7d:53:0e:32:d7:5e:b8:
                    00:fc:13:c5:69:6c:4c:f4:7f:91:02:20:50:59:9d:
                    df:3b:f1:e8:0c:00:eb:bd:2a:77:62:7c:42:b2:b9:
                    71:f7:5f:4d:c8:14:f8:6e:13:1d:f2:51:c2:74:ad:
                    d6:e4:fe:40:3a:b1:dd:fe:8e:1c:20:c7:df:58:1c:
                    89:d5:a1:19:5a:af:f7:aa:74:47:a6:a9:7d:a6:a1:
                    25:2e:24:0c:5b:eb:c6:38:15:76:47:80:11:30:ef:
                    b1:39:ff:fe:03:01:dd:f6:4e:20:ea:00:b7:bc:8c:
                    92:29:96:70:15:97:74:89:0d:cf:b6:0c:c1:32:d8:
                    b1:8d
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                3F:B6:7E:B3:C4:1D:0F:53:C8:FE:38:BC:AD:88:42:5F:B8:CF:22:B2
            X509v3 Authority Key Identifier:
                3F:B6:7E:B3:C4:1D:0F:53:C8:FE:38:BC:AD:88:42:5F:B8:CF:22:B2
            X509v3 Basic Constraints: critical
                CA:TRUE
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        30:1a:6f:06:1f:be:42:75:06:be:b3:ba:ea:30:cf:04:1f:4d:
        75:1e:2d:53:3c:0d:bb:c3:fa:ad:b7:f1:99:df:f0:49:2f:0e:
        4c:1d:5e:93:2b:8a:e7:f2:93:24:e4:30:9d:b2:92:87:b9:ff:
        0d:1c:d1:a3:96:06:0e:d0:b7:8a:2e:12:fb:65:9f:08:b7:8a:
        71:7e:da:16:bd:fd:44:b4:f9:e6:34:cb:ed:7b:53:f3:ab:6b:
        56:8a:db:9e:dc:ff:53:e5:10:43:26:cc:80:5d:dc:ea:fd:af:
        36:91:e8:1d:27:20:58:2a:18:86:58:e2:12:5e:21:43:59:46:
        5f:fc:78:3c:d5:db:5e:b7:00:82:a5:fa:64:d1:2e:c5:d0:cb:
        6f:5a:5c:d5:be:00:ab:04:14:09:9e:0a:4f:80:96:ba:c5:a5:
        59:6e:bb:b4:13:66:ec:3a:fd:96:1b:cf:84:df:d6:16:21:56:
        1a:aa:fa:cd:78:df:75:3f:bf:1f:5e:9e:d5:a3:78:a8:71:7f:
        9b:af:f9:7e:a7:d2:c5:4f:37:6b:c6:8b:d9:e2:08:f5:76:1e:
        db:09:c8:c9:5f:ab:b4:68:97:04:46:af:bd:d4:d2:dd:76:ba:
        45:d3:1d:f1:90:e6:86:50:22:33:e7:7b:d0:7b:d7:29:99:eb:
        c3:00:85:d5
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            47:01:c2:02:98:ee:65:f5:ac:52:be:ee:67:94:59:24:82:22:5d:6b
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd
        Validity
            Not Before: Jan 20 14:06:58 2025 GMT
            Not After : Feb 19 14:06:58 2025 GMT
        Subject: C=JP, ST=Osaka, L=Japan, O=Yamashita Bookoshita, OU=Yamashita Bookoshita, CN=theflag.isnot.here, emailAddress=do.not@bother
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:e1:8e:56:c1:81:31:92:3d:c6:95:a3:38:5d:be:
                    8e:c8:c9:21:22:f1:54:b4:a9:72:30:3c:f4:11:ab:
                    db:cd:91:b6:2d:ad:a3:90:35:1e:23:01:6a:76:76:
                    bb:fc:0c:94:ca:41:7f:ce:5d:3c:ef:16:52:9b:18:
                    8a:fb:eb:cd:1e:c7:d9:87:1b:3e:81:f4:1d:9d:7b:
                    71:34:12:a9:15:8b:56:d0:71:d9:a2:e0:c2:59:3a:
                    5d:22:0b:34:f6:b0:eb:08:6a:8a:51:1a:19:48:cb:
                    d7:7f:88:40:30:f1:fb:75:82:a8:88:50:06:0e:4f:
                    e0:b2:56:13:fd:ab:c2:f5:e0:e6:32:bf:82:97:15:
                    d9:e1:68:07:e0:a2:48:e7:c0:d2:9a:0d:6e:85:e3:
                    b6:8a:8a:36:dd:48:38:99:06:1c:bd:dd:8e:0a:80:
                    53:5d:6c:3e:97:0f:dd:b3:dc:8e:55:a0:df:09:e7:
                    9f:81:0a:4f:88:d6:c2:34:e4:05:af:8c:db:b8:2c:
                    ff:4b:71:e0:fa:67:3f:13:13:22:9d:fd:e4:84:b3:
                    8f:0e:df:5a:55:d4:16:a6:c1:de:5b:bf:b1:b5:7f:
                    f4:a4:af:2c:4f:ae:dc:e1:35:8a:c0:36:72:ff:47:
                    2c:43:bf:3e:66:5d:09:a1:c1:65:80:e1:71:13:e2:
                    7d:d1
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Basic Constraints:
                CA:FALSE
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage:
                TLS Web Client Authentication
            X509v3 Subject Key Identifier:
                6F:BE:DD:CC:DC:AC:42:EE:5A:AB:78:17:01:D5:3C:88:50:E1:BC:ED
            X509v3 Authority Key Identifier:
                3F:B6:7E:B3:C4:1D:0F:53:C8:FE:38:BC:AD:88:42:5F:B8:CF:22:B2
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        61:6e:73:eb:c2:17:3a:4a:65:f7:31:f6:2a:96:83:e2:18:05:
        e9:84:03:d7:f9:30:0b:ce:4c:bb:ed:df:8f:5a:c2:e7:02:69:
        ee:61:ac:3f:1b:82:df:b9:06:3b:73:5a:88:07:50:98:8a:ea:
        55:47:87:6a:a5:8f:a9:3a:7c:06:ce:3c:62:b3:5b:79:f1:a5:
        c7:d1:b1:55:37:04:c9:2e:35:d8:c5:da:28:a8:3c:b8:c6:2a:
        35:72:91:57:c7:a0:38:b5:b5:5e:15:6b:ad:e0:32:45:36:41:
        7b:e7:d1:1c:bb:d4:9c:4e:c1:6f:9b:21:8e:9a:3d:65:eb:44:
        d8:1b:b8:8a:f6:53:a3:be:75:f5:4b:37:b6:02:11:0e:03:a2:
        da:f2:98:77:0f:d1:59:c7:50:09:3f:f8:93:b9:34:f2:c7:38:
        df:bb:53:05:55:25:3d:b5:a8:e0:53:73:5f:4b:65:6d:23:73:
        ce:3d:97:bd:91:bd:08:80:d4:9c:ea:3e:28:a2:e9:20:26:70:
        d4:f4:a0:63:f3:0d:7c:fa:84:21:7b:5a:b0:25:83:69:2d:c1:
        cf:69:7f:0b:68:2b:ac:35:16:59:53:56:f6:06:48:4c:c1:af:
        a4:91:c9:fe:c2:a1:e3:70:e5:24:35:61:f9:c8:a5:52:17:b0:
        61:39:43:a2
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            31:7d:8a:ad:56:5b:ed:0c:b9:98:4c:9c:4d:f5:4b:d1:cc:c0:ba:d0
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd
        Validity
            Not Before: Jan 20 14:06:45 2025 GMT
            Not After : Feb 19 14:06:45 2025 GMT
        Subject: CN=0.0.0.0
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:c9:0c:01:e5:51:5c:4c:b5:b5:17:11:4d:d7:4b:
                    74:ad:ba:ed:9e:3c:3f:8e:4b:e2:3e:c7:40:b2:79:
                    13:e5:19:6f:a2:f2:e4:d0:9f:4b:f0:d3:e2:cb:01:
                    7e:da:50:72:e4:98:8e:cf:43:7d:40:0d:20:37:3c:
                    23:d9:21:78:a0:f5:5b:01:f7:53:60:84:2a:4e:ab:
                    1c:40:80:fe:31:cb:35:8a:63:78:2a:25:2f:f8:5c:
                    62:5c:81:38:6d:80:37:82:ce:67:b3:6c:b3:08:74:
                    c1:a9:2f:e5:d3:19:c4:88:06:4b:37:53:e5:a6:e9:
                    d8:a6:66:da:2a:01:28:51:f6:4c:b1:5b:79:cd:59:
                    4f:58:33:67:df:8a:e4:bc:4c:52:39:16:4e:90:6c:
                    71:ec:ee:67:d6:92:11:72:14:c0:cc:00:dc:21:a3:
                    bb:75:ab:6a:8a:3d:56:3d:29:96:53:9b:37:47:f6:
                    e3:53:5a:91:14:92:02:50:0e:82:bf:fd:ba:4b:6f:
                    f6:f3:13:20:b3:6f:b3:1c:eb:c5:92:1c:d7:f0:d6:
                    5c:a3:9a:c5:8a:ac:4a:c2:45:26:c2:ee:78:84:8c:
                    dd:a4:72:ad:1f:90:13:b7:3c:ba:04:43:cf:9d:10:
                    00:15:20:6c:3e:38:a8:b9:c9:2d:e9:f9:f4:b5:c6:
                    a4:e9
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Basic Constraints:
                CA:FALSE
            X509v3 Key Usage:
                Digital Signature, Non Repudiation, Key Encipherment
            X509v3 Subject Alternative Name:
                IP Address:0.0.0.0
            X509v3 Extended Key Usage:
                TLS Web Server Authentication
            1.2.12.5:
                ...PBFEJJDMPMFJNMHJJADFPBFEICCHPGFDNGHDJADFPJFMIPCKOKEPMKGPIICNPBFEIFCAOAEFJDDGLDBGPCFHIACFOFEAMFGAJCDHOAEFIPCKOBEEIGCDKGADPCFHJKDPPP
            1.2.12.6:
                ...FKNPHKJMDJPDFGIBCEPDFGJGDDPFFAIBCEKBAEODEGJKDPOOELILCOPIFNNIHNJLDOPEFBJJDMPLFOJCDHPMFJJJDMPNFINNHIIJCMOGEDIBCEOEEBJADFPIFNJNDIOPEK
            1.2.12.7:
                ...MPGKIGCDPFFANFHAJEDBPHFCIDCGPGFDJHDCPLFOJHDCOOELMOGLJKDPPCFHJHDCLHBCPCFHICCHONEIIOCLOGEDMGGDJDDGPNFIJEDBOMEJMMGJJIDNPBFEJMDJPJFMIK
            1.2.12.8:
                ...CPPOFLJPDKPCFHICCHKCAHOLEOIFCAKFAAPGFDJDDGPAFFJPDKPBFEJFDAOGEDMGGDIACFOPEKJNDILNBIPOFLJGDDPPFKJBDEPEFBIHCCOCEHMCGHIMCJOJEMJODLLOBL
            1.2.12.9:
                .$OHECICCHODEGJBDELBBEIDCGLDBGIBCELEBB
            X509v3 Subject Key Identifier:
                EF:33:C2:24:F8:12:16:EC:6F:1B:E4:BE:77:C0:E3:DB:72:3D:C8:09
            X509v3 Authority Key Identifier:
                3F:B6:7E:B3:C4:1D:0F:53:C8:FE:38:BC:AD:88:42:5F:B8:CF:22:B2
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        55:4d:bd:18:04:0d:02:05:f7:b1:4a:a1:96:40:e3:33:80:b5:
        5c:1e:94:0f:3a:af:06:c4:8d:31:a8:7a:1d:6a:3d:ee:86:dc:
        d6:4b:8a:b9:6f:fe:5b:05:85:b9:7d:c7:cc:2c:80:de:dd:40:
        cf:b2:69:43:7d:b8:26:eb:3c:7c:97:57:a7:0c:3b:41:13:77:
        26:c9:ff:01:fd:6f:b7:92:80:82:ba:03:fa:d5:48:0e:d2:ba:
        51:d4:64:9c:16:49:94:98:18:9f:d0:b1:df:9a:36:19:4a:e9:
        51:da:f5:ea:7f:78:04:2b:9a:09:29:29:58:7b:48:32:32:b1:
        0e:d1:d5:9f:09:59:b0:98:5b:fc:a7:d5:aa:9e:ce:c7:8b:10:
        a7:09:75:dc:18:8f:2e:57:d3:81:cd:34:be:82:16:8a:5b:e7:
        e3:f6:55:b5:b0:a7:b9:de:55:2e:6e:ce:b6:a2:ce:6f:e5:5f:
        5d:3a:5e:39:60:d8:e5:b2:b1:8b:c1:6c:c5:d9:7b:ea:99:6b:
        1e:ad:32:23:8c:58:cf:25:d4:fa:1a:62:21:9e:c6:2f:fb:fe:
        e3:97:92:c0:04:3e:da:32:db:0d:11:14:95:bf:2b:4f:bb:7f:
        bc:65:b1:49:76:c8:fd:85:a6:5a:e7:39:a6:b1:31:41:1d:1d:
        4c:07:2a:07

Now, since we have the DEV_client.key and DEV_server.key from before, let's try to decrypt the packet.

Add the DEV_server.key into wireshark under TLS.

Now let's try to inspect the packet. Apply the filter below:

tls

Now we are seeing something!

Decrypted WebSocket Text!

Let me summarize the transcript:

Welcome to the chat!
r00t: hi there
r00t: welcome
r00t: so you are here because nian is coming to kill us all?
r00t: i see
r00t: well i can help you with that
r00t: no worries 
r00t: :)
r00t: i hope you have REad enough into the ancient runes
r00t: those are your keys to success
r00t: as for the key itself, it is the weakness of nian: 红 火 热闹
r00t: take the 汉语拼音 of those 4 characters, then separate them with a "_" and pad them with 4 pluses
r00t: oh? 
r00t: missing some bytes?
r00t: well...
r00t: oh darn, nian has come
r00t: it has bit off both my legs
r00t: im dying...
r00t: i dont have time to tell you the last part of the key... but maybe if you look into the SSL certificates...
r00t: encoding???? i cant remember... it sounds like the acid found in mandarin oranges and other citrus fruits...
r00t: urgh... im sorry... im losing too much blood
r00t: we are counting on you...

With the above transcript, we can piece the first part of they key as:

红 = hong
火 = huo
热闹 = re_nao

padding = ++++

compiled_key = hong_huo_re_nao++++

The other hint given is it sound like the acid found in mandarin oranges and other citrus fruits, which should be Citric Acid.

Let's try to inspect the SSL certificates for more information.. The hint given was in the encoding..

I will take the key obtained from the client and server executable. I tried the wireshark method before trying the executables.

openssl x509 -in DEV_server.crt -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            31:7d:8a:ad:56:5b:ed:0c:b9:98:4c:9c:4d:f5:4b:d1:cc:c0:ba:d0
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd
        Validity
            Not Before: Jan 20 14:06:45 2025 GMT
            Not After : Feb 19 14:06:45 2025 GMT
        Subject: CN=0.0.0.0
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:c9:0c:01:e5:51:5c:4c:b5:b5:17:11:4d:d7:4b:
                    74:ad:ba:ed:9e:3c:3f:8e:4b:e2:3e:c7:40:b2:79:
                    13:e5:19:6f:a2:f2:e4:d0:9f:4b:f0:d3:e2:cb:01:
                    7e:da:50:72:e4:98:8e:cf:43:7d:40:0d:20:37:3c:
                    23:d9:21:78:a0:f5:5b:01:f7:53:60:84:2a:4e:ab:
                    1c:40:80:fe:31:cb:35:8a:63:78:2a:25:2f:f8:5c:
                    62:5c:81:38:6d:80:37:82:ce:67:b3:6c:b3:08:74:
                    c1:a9:2f:e5:d3:19:c4:88:06:4b:37:53:e5:a6:e9:
                    d8:a6:66:da:2a:01:28:51:f6:4c:b1:5b:79:cd:59:
                    4f:58:33:67:df:8a:e4:bc:4c:52:39:16:4e:90:6c:
                    71:ec:ee:67:d6:92:11:72:14:c0:cc:00:dc:21:a3:
                    bb:75:ab:6a:8a:3d:56:3d:29:96:53:9b:37:47:f6:
                    e3:53:5a:91:14:92:02:50:0e:82:bf:fd:ba:4b:6f:
                    f6:f3:13:20:b3:6f:b3:1c:eb:c5:92:1c:d7:f0:d6:
                    5c:a3:9a:c5:8a:ac:4a:c2:45:26:c2:ee:78:84:8c:
                    dd:a4:72:ad:1f:90:13:b7:3c:ba:04:43:cf:9d:10:
                    00:15:20:6c:3e:38:a8:b9:c9:2d:e9:f9:f4:b5:c6:
                    a4:e9
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Basic Constraints:
                CA:FALSE
            X509v3 Key Usage:
                Digital Signature, Non Repudiation, Key Encipherment
            X509v3 Subject Alternative Name:
                IP Address:0.0.0.0
            X509v3 Extended Key Usage:
                TLS Web Server Authentication
            1.2.12.5:
                ...PBFEJJDMPMFJNMHJJADFPBFEICCHPGFDNGHDJADFPJFMIPCKOKEPMKGPIICNPBFEIFCAOAEFJDDGLDBGPCFHIACFOFEAMFGAJCDHOAEFIPCKOBEEIGCDKGADPCFHJKDPPP
            1.2.12.6:
                ...FKNPHKJMDJPDFGIBCEPDFGJGDDPFFAIBCEKBAEODEGJKDPOOELILCOPIFNNIHNJLDOPEFBJJDMPLFOJCDHPMFJJJDMPNFINNHIIJCMOGEDIBCEOEEBJADFPIFNJNDIOPEK
            1.2.12.7:
                ...MPGKIGCDPFFANFHAJEDBPHFCIDCGPGFDJHDCPLFOJHDCOOELMOGLJKDPPCFHJHDCLHBCPCFHICCHONEIIOCLOGEDMGGDJDDGPNFIJEDBOMEJMMGJJIDNPBFEJMDJPJFMIK
            1.2.12.8:
                ...CPPOFLJPDKPCFHICCHKCAHOLEOIFCAKFAAPGFDJDDGPAFFJPDKPBFEJFDAOGEDMGGDIACFOPEKJNDILNBIPOFLJGDDPPFKJBDEPEFBIHCCOCEHMCGHIMCJOJEMJODLLOBL
            1.2.12.9:
                .$OHECICCHODEGJBDELBBEIDCGLDBGIBCELEBB
            X509v3 Subject Key Identifier:
                EF:33:C2:24:F8:12:16:EC:6F:1B:E4:BE:77:C0:E3:DB:72:3D:C8:09
            X509v3 Authority Key Identifier:
                3F:B6:7E:B3:C4:1D:0F:53:C8:FE:38:BC:AD:88:42:5F:B8:CF:22:B2
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        55:4d:bd:18:04:0d:02:05:f7:b1:4a:a1:96:40:e3:33:80:b5:
        5c:1e:94:0f:3a:af:06:c4:8d:31:a8:7a:1d:6a:3d:ee:86:dc:
        d6:4b:8a:b9:6f:fe:5b:05:85:b9:7d:c7:cc:2c:80:de:dd:40:
        cf:b2:69:43:7d:b8:26:eb:3c:7c:97:57:a7:0c:3b:41:13:77:
        26:c9:ff:01:fd:6f:b7:92:80:82:ba:03:fa:d5:48:0e:d2:ba:
        51:d4:64:9c:16:49:94:98:18:9f:d0:b1:df:9a:36:19:4a:e9:
        51:da:f5:ea:7f:78:04:2b:9a:09:29:29:58:7b:48:32:32:b1:
        0e:d1:d5:9f:09:59:b0:98:5b:fc:a7:d5:aa:9e:ce:c7:8b:10:
        a7:09:75:dc:18:8f:2e:57:d3:81:cd:34:be:82:16:8a:5b:e7:
        e3:f6:55:b5:b0:a7:b9:de:55:2e:6e:ce:b6:a2:ce:6f:e5:5f:
        5d:3a:5e:39:60:d8:e5:b2:b1:8b:c1:6c:c5:d9:7b:ea:99:6b:
        1e:ad:32:23:8c:58:cf:25:d4:fa:1a:62:21:9e:c6:2f:fb:fe:
        e3:97:92:c0:04:3e:da:32:db:0d:11:14:95:bf:2b:4f:bb:7f:
        bc:65:b1:49:76:c8:fd:85:a6:5a:e7:39:a6:b1:31:41:1d:1d:
        4c:07:2a:07

Lets try to use asn1parse to extract the custom fields (1.2.12.x) in the certificate:

openssl asn1parse -in DEV_server.crt

The output is as follows:

0:d=0  hl=4 l=1496 cons: SEQUENCE
    4:d=1  hl=4 l=1216 cons: SEQUENCE
    8:d=2  hl=2 l=   3 cons: cont [ 0 ]
   10:d=3  hl=2 l=   1 prim: INTEGER           :02
   13:d=2  hl=2 l=  20 prim: INTEGER           :317D8AAD565BED0CB9984C9C4DF54BD1CCC0BAD0
   35:d=2  hl=2 l=  13 cons: SEQUENCE
   37:d=3  hl=2 l=   9 prim: OBJECT            :sha256WithRSAEncryption
   48:d=3  hl=2 l=   0 prim: NULL
   50:d=2  hl=2 l=  69 cons: SEQUENCE
   52:d=3  hl=2 l=  11 cons: SET
   54:d=4  hl=2 l=   9 cons: SEQUENCE
   56:d=5  hl=2 l=   3 prim: OBJECT            :countryName
   61:d=5  hl=2 l=   2 prim: PRINTABLESTRING   :AU
   65:d=3  hl=2 l=  19 cons: SET
   67:d=4  hl=2 l=  17 cons: SEQUENCE
   69:d=5  hl=2 l=   3 prim: OBJECT            :stateOrProvinceName
   74:d=5  hl=2 l=  10 prim: UTF8STRING        :Some-State
   86:d=3  hl=2 l=  33 cons: SET
   88:d=4  hl=2 l=  31 cons: SEQUENCE
   90:d=5  hl=2 l=   3 prim: OBJECT            :organizationName
   95:d=5  hl=2 l=  24 prim: UTF8STRING        :Internet Widgits Pty Ltd
  121:d=2  hl=2 l=  30 cons: SEQUENCE
  123:d=3  hl=2 l=  13 prim: UTCTIME           :250120140645Z
  138:d=3  hl=2 l=  13 prim: UTCTIME           :250219140645Z
  153:d=2  hl=2 l=  18 cons: SEQUENCE
  155:d=3  hl=2 l=  16 cons: SET
  157:d=4  hl=2 l=  14 cons: SEQUENCE
  159:d=5  hl=2 l=   3 prim: OBJECT            :commonName
  164:d=5  hl=2 l=   7 prim: UTF8STRING        :0.0.0.0
  173:d=2  hl=4 l= 290 cons: SEQUENCE
  177:d=3  hl=2 l=  13 cons: SEQUENCE
  179:d=4  hl=2 l=   9 prim: OBJECT            :rsaEncryption
  190:d=4  hl=2 l=   0 prim: NULL
  192:d=3  hl=4 l= 271 prim: BIT STRING
  467:d=2  hl=4 l= 753 cons: cont [ 3 ]
  471:d=3  hl=4 l= 749 cons: SEQUENCE
  475:d=4  hl=2 l=   9 cons: SEQUENCE
  477:d=5  hl=2 l=   3 prim: OBJECT            :X509v3 Basic Constraints
  482:d=5  hl=2 l=   2 prim: OCTET STRING      [HEX DUMP]:3000
  486:d=4  hl=2 l=  11 cons: SEQUENCE
  488:d=5  hl=2 l=   3 prim: OBJECT            :X509v3 Key Usage
  493:d=5  hl=2 l=   4 prim: OCTET STRING      [HEX DUMP]:030205E0
  499:d=4  hl=2 l=  15 cons: SEQUENCE
  501:d=5  hl=2 l=   3 prim: OBJECT            :X509v3 Subject Alternative Name
  506:d=5  hl=2 l=   8 prim: OCTET STRING      [HEX DUMP]:3006870400000000
  516:d=4  hl=2 l=  19 cons: SEQUENCE
  518:d=5  hl=2 l=   3 prim: OBJECT            :X509v3 Extended Key Usage
  523:d=5  hl=2 l=  12 prim: OCTET STRING      [HEX DUMP]:300A06082B06010505070301
  537:d=4  hl=3 l= 141 cons: SEQUENCE
  540:d=5  hl=2 l=   3 prim: OBJECT            :1.2.12.5
  545:d=5  hl=3 l= 133 prim: OCTET STRING      [HEX DUMP]:0C8182504246454A4A444D504D464A4E4D484A4A4144465042464549434348504746444E4748444A414446504A464D4950434B4F4B45504D4B47504949434E50424645494643414F4145464A4444474C44424750434648494143464F4645414D4647414A4344484F4145464950434B4F424545494743444B474144504346484A4B44505050
  681:d=4  hl=3 l= 141 cons: SEQUENCE
  684:d=5  hl=2 l=   3 prim: OBJECT            :1.2.12.6
  689:d=5  hl=3 l= 133 prim: OCTET STRING      [HEX DUMP]:0C8182464B4E50484B4A4D444A5044464749424345504446474A47444450464641494243454B4241454F4445474A4B44504F4F454C494C434F5049464E4E49484E4A4C444F504546424A4A444D504C464F4A434448504D464A4A4A444D504E46494E4E4849494A434D4F474544494243454F4545424A4144465049464E4A4E44494F50454B
  825:d=4  hl=3 l= 141 cons: SEQUENCE
  828:d=5  hl=2 l=   3 prim: OBJECT            :1.2.12.7
  833:d=5  hl=3 l= 133 prim: OCTET STRING      [HEX DUMP]:0C81824D50474B49474344504646414E4648414A4544425048464349444347504746444A484443504C464F4A4844434F4F454C4D4F474C4A4B4450504346484A4844434C48424350434648494343484F4E4549494F434C4F4745444D4747444A444447504E46494A4544424F4D454A4D4D474A4A49444E504246454A4D444A504A464D494B
  969:d=4  hl=3 l= 141 cons: SEQUENCE
  972:d=5  hl=2 l=   3 prim: OBJECT            :1.2.12.8
  977:d=5  hl=3 l= 133 prim: OCTET STRING      [HEX DUMP]:0C81824350504F464C4A50444B50434648494343484B4341484F4C454F494643414B464141504746444A444447504146464A50444B504246454A4644414F4745444D474744494143464F50454B4A4E44494C4E4249504F464C4A4744445050464B4A42444550454642494843434F4345484D434748494D434A4F4A454D4A4F444C4C4F424C
 1113:d=4  hl=2 l=  45 cons: SEQUENCE
 1115:d=5  hl=2 l=   3 prim: OBJECT            :1.2.12.9
 1120:d=5  hl=2 l=  38 prim: OCTET STRING      [HEX DUMP]:0C244F484543494343484F4445474A4244454C424245494443474C444247494243454C454242
 1160:d=4  hl=2 l=  29 cons: SEQUENCE
 1162:d=5  hl=2 l=   3 prim: OBJECT            :X509v3 Subject Key Identifier
 1167:d=5  hl=2 l=  22 prim: OCTET STRING      [HEX DUMP]:0414EF33C224F81216EC6F1BE4BE77C0E3DB723DC809
 1191:d=4  hl=2 l=  31 cons: SEQUENCE
 1193:d=5  hl=2 l=   3 prim: OBJECT            :X509v3 Authority Key Identifier
 1198:d=5  hl=2 l=  24 prim: OCTET STRING      [HEX DUMP]:301680143FB67EB3C41D0F53C8FE38BCAD88425FB8CF22B2
 1224:d=1  hl=2 l=  13 cons: SEQUENCE
 1226:d=2  hl=2 l=   9 prim: OBJECT            :sha256WithRSAEncryption
 1237:d=2  hl=2 l=   0 prim: NULL
 1239:d=1  hl=4 l= 257 prim: BIT STRING

Let's try to extract the string:

openssl asn1parse -in DEV_server.crt -strparse 545
openssl asn1parse -in DEV_server.crt -strparse 689
openssl asn1parse -in DEV_server.crt -strparse 833
openssl asn1parse -in DEV_server.crt -strparse 977
openssl asn1parse -in DEV_server.crt -strparse 1120

These are the strings obtained:

0:d=0  hl=3 l= 130 prim: UTF8STRING        :PBFEJJDMPMFJNMHJJADFPBFEICCHPGFDNGHDJADFPJFMIPCKOKEPMKGPIICNPBFEIFCAOAEFJDDGLDBGPCFHIACFOFEAMFGAJCDHOAEFIPCKOBEEIGCDKGADPCFHJKDPPP
    0:d=0  hl=3 l= 130 prim: UTF8STRING        :FKNPHKJMDJPDFGIBCEPDFGJGDDPFFAIBCEKBAEODEGJKDPOOELILCOPIFNNIHNJLDOPEFBJJDMPLFOJCDHPMFJJJDMPNFINNHIIJCMOGEDIBCEOEEBJADFPIFNJNDIOPEK
    0:d=0  hl=3 l= 130 prim: UTF8STRING        :MPGKIGCDPFFANFHAJEDBPHFCIDCGPGFDJHDCPLFOJHDCOOELMOGLJKDPPCFHJHDCLHBCPCFHICCHONEIIOCLOGEDMGGDJDDGPNFIJEDBOMEJMMGJJIDNPBFEJMDJPJFMIK
    0:d=0  hl=3 l= 130 prim: UTF8STRING        :CPPOFLJPDKPCFHICCHKCAHOLEOIFCAKFAAPGFDJDDGPAFFJPDKPBFEJFDAOGEDMGGDIACFOPEKJNDILNBIPOFLJGDDPPFKJBDEPEFBIHCCOCEHMCGHIMCJOJEMJODLLOBL
    0:d=0  hl=2 l=  36 prim: UTF8STRING        :OHECICCHODEGJBDELBBEIDCGLDBGIBCELEBB

Combining all the custom OIDs, i get the following UTF8STRING:

PBFEJJDMPMFJNMHJJADFPBFEICCHPGFDNGHDJADFPJFMIPCKOKEPMKGPIICNPBFEIFCAOAEFJDDGLDBGPCFHIACFOFEAMFGAJCDHOAEFIPCKOBEEIGCDKGADPCFHJKDPPPFKNPHKJMDJPDFGIBCEPDFGJGDDPFFAIBCEKBAEODEGJKDPOOELILCOPIFNNIHNJLDOPEFBJJDMPLFOJCDHPMFJJJDMPNFINNHIIJCMOGEDIBCEOEEBJADFPIFNJNDIOPEKMPGKIGCDPFFANFHAJEDBPHFCIDCGPGFDJHDCPLFOJHDCOOELMOGLJKDPPCFHJHDCLHBCPCFHICCHONEIIOCLOGEDMGGDJDDGPNFIJEDBOMEJMMGJJIDNPBFEJMDJPJFMIKCPPOFLJPDKPCFHICCHKCAHOLEOIFCAKFAAPGFDJDDGPAFFJPDKPBFEJFDAOGEDMGGDIACFOPEKJNDILNBIPOFLJGDDPPFKJBDEPEFBIHCCOCEHMCGHIMCJOJEMJODLLOBLOHECICCHODEGJBDELBBEIDCGLDBGIBCELEBB

Now, let's visit my favourite tool for decoding ciphertext, CyberChef! (https://gchq.github.io/CyberChef/). Since the hint given in the chat exchange previously was the encoding sound like "Citric", let's look through the different encoding available.

I found this!

Citrix CTX1 Cipher

To be honest, it's my first time hearing this cipher. I learnt all about Caeser Ciphers, Substitution Ciphers, AES, DES, RSA encryptions during my bachelor, I have never venture further into exploring other encryptions after university.. Partly due to the PTSD from memorizing the formulas for the different encryptions, performing modulus operations.. HAHA

I tried finding some information on Citrix CTX1 cipher and only managed to learn that it's a propiarty cipher created by Citrix for use on its Netscaler Gateways. The encryption and decryption algorithm is described by this website https://asecuritysite.com/cipher/citrix.

Referenced from https://asecuritysite.com/cipher/citrix

I tried running Citrix CTX1 Decode on the ciphertext above in the custom OIDs on the SSL certificate and guess what?

Plaintext: The Last Five Bytes Are Wrong The Correct Bytes Combined Together Is Actually The Epoch Unix Timestamp In Seconds For Chinese New Year 2025
The Last Five Bytes Are Wrong The Correct Bytes Combined Together Is Actually The Epoch Unix Timestamp In Seconds For Chinese New Year 2025

I then went to https://www.epochconverter.com/ and keyed in 29 Jan 2025 00:00:00 to get the Epoch Unix Timestamp in Seconds.

From https://www.unixtimestamp.com/

Thus, the Epoch timestamp in seconds for CNY 2025 is:

GMT: 1738080000

From the previous finding, we know the key is 'hong_huo_re_nao++++'. Converting to hex, we get the following hex '686f6e675f68756f5f72655f6e616f2b2b2b2b'.

Hex: 686f6e675f68756f5f72655f6e616f2b2b2b2b

Now, we need to connect to the final URL (wss://34.123.42.200:80) using mTLS to issue the key to get the flag.