Skip to content

Instantly share code, notes, and snippets.

@Hamid-K
Last active January 2, 2026 16:14
Show Gist options
  • Select an option

  • Save Hamid-K/6f4328a8cd4f1e34798e63a8b82aa428 to your computer and use it in GitHub Desktop.

Select an option

Save Hamid-K/6f4328a8cd4f1e34798e63a8b82aa428 to your computer and use it in GitHub Desktop.
PoC script to detect steganography and hidden files in video files
#!/usr/bin/env python3
import argparse
import json
import math
import subprocess
import sys
def run_ffprobe(path):
cmd = [
"ffprobe",
"-v",
"error",
"-select_streams",
"v:0",
"-show_entries",
"stream=width,height,nb_frames,avg_frame_rate,duration",
"-of",
"json",
path,
]
try:
out = subprocess.check_output(cmd, text=True)
except subprocess.CalledProcessError:
return None
try:
info = json.loads(out)
stream = info["streams"][0]
except Exception:
return None
width = int(stream.get("width"))
height = int(stream.get("height"))
nb_frames = str(stream.get("nb_frames", ""))
avg_rate = str(stream.get("avg_frame_rate", ""))
duration = str(stream.get("duration", ""))
frames = None
if nb_frames.isdigit():
frames = int(nb_frames)
else:
# Try to estimate frames from duration and avg_frame_rate.
try:
num, den = avg_rate.split("/")
fps = float(num) / float(den) if float(den) != 0 else None
dur = float(duration)
if fps and dur:
frames = int(round(fps * dur))
except Exception:
frames = None
return width, height, frames
def entropy(data):
if not data:
return 0.0
counts = [0] * 256
for b in data:
counts[b] += 1
total = len(data)
ent = 0.0
for c in counts:
if c:
p = c / total
ent -= p * math.log2(p)
return ent
def extract_b_lsb_bytes(video_path, want_bytes):
cmd = [
"ffmpeg",
"-v",
"error",
"-i",
video_path,
"-f",
"rawvideo",
"-pix_fmt",
"rgb24",
"-",
]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
out = bytearray()
cur = 0
cnt = 0
try:
while len(out) < want_bytes:
chunk = proc.stdout.read(1024 * 1024)
if not chunk:
break
for i in range(2, len(chunk), 3):
b = chunk[i] & 1
cur = (cur << 1) | b
cnt += 1
if cnt == 8:
out.append(cur)
if len(out) >= want_bytes:
break
cur = 0
cnt = 0
finally:
if proc.stdout:
proc.stdout.close()
proc.terminate()
return bytes(out)
def main():
ap = argparse.ArgumentParser(
description="Detect hide.py-style video steganography without a password."
)
ap.add_argument("video", help="Path to the video file (mp4, mkv, etc.)")
ap.add_argument(
"--dump",
metavar="OUT",
help="Write extracted encrypted payload blob to this file",
)
args = ap.parse_args()
info = run_ffprobe(args.video)
if not info:
print("ffprobe failed to read video metadata.", file=sys.stderr)
sys.exit(2)
width, height, frames = info
if not frames:
print("Could not determine frame count; detection may be weaker.")
else:
capacity_bits = width * height * frames
capacity_bytes = capacity_bits // 8
print(f"Video size: {width}x{height}, frames={frames}, B-LSB capacity={capacity_bytes} bytes")
# Read first 4 bytes to get payload length.
hdr = extract_b_lsb_bytes(args.video, 4)
if len(hdr) < 4:
print("Not enough data to read header; no detection.")
sys.exit(1)
payload_len = int.from_bytes(hdr, "big")
print(f"Header length field: {payload_len} bytes")
plausible = True
if frames:
capacity_bytes = (width * height * frames) // 8
if payload_len <= 16 or payload_len > capacity_bytes - 4:
plausible = False
if payload_len % 16 != 0:
plausible = False
if not plausible:
print("Length field is not plausible for hide.py-style AES-CBC payload.")
sys.exit(0)
# Extract full payload if requested, otherwise sample for entropy.
if args.dump:
blob = extract_b_lsb_bytes(args.video, 4 + payload_len)
if len(blob) < 4 + payload_len:
print("Reached EOF before extracting full payload.", file=sys.stderr)
sys.exit(1)
with open(args.dump, "wb") as f:
f.write(blob)
print(f"Wrote encrypted payload blob: {args.dump}")
else:
sample = extract_b_lsb_bytes(args.video, 4 + min(payload_len, 4096))
ent = entropy(sample[4:])
print(f"Payload entropy (sample): {ent:.2f} bits/byte")
print("Likely hide.py-style steganography: YES")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment