Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
aradotso avatar

Drm Pentesting Toolkit

  • 1 installs
  • 10 repo stars
  • Updated August 4, 2026
  • aradotso/security-skills

DRM Pentesting Toolkit is a Claude skill that drives a plugin-based framework for extracting DRM keys, parsing PSSH from MPD manifests, and testing Widevine/PlayReady device files during authorized pentesting.

About

The DRM Pentesting Toolkit is a plugin-based framework for testing DRM-protected streaming content. It loads Widevine and PlayReady device files, parses PSSH data from DASH/MPD manifests, retrieves decryption keys from license servers, and downloads content via N_m3u8DL-RE. A developer uses it to security-test DRM device credentials and license servers through a CustomTkinter GUI with chainable plugins.

  • Plugin-based toolkit for extracting DRM keys from Widevine and PlayReady devices
  • Parses PSSH from MPD manifests and retrieves decryption keys from license servers
  • CustomTkinter GUI with a chainable plugin stack system

Drm Pentesting Toolkit by the numbers

  • 1 all-time installs (skills.sh)
  • Ranked #1,835 of 2,203 Security skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
At a glance

drm-pentesting-toolkit capabilities & compatibility

Free; requires user-supplied DRM device files and external executables.

Capabilities
security audit · key extraction
Use cases
security audit
Platforms
Linux · macOS · Windows
Pricing
Free
From the docs

What drm-pentesting-toolkit says it does

The DRM Pentesting Toolkit is a comprehensive framework for:
SKILL.md
Parsing PSSH (Protection System Specific Header) data from DASH/MPD manifests
SKILL.md
Chaining multiple operations through a plugin stack system
SKILL.md
npx skills add https://github.com/aradotso/security-skills --skill drm-pentesting-toolkit

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs1
repo stars10
Last updatedAugust 4, 2026
Repositoryaradotso/security-skills

What it does

Security-test DRM device credentials and extract license-server keys from streaming content during authorized pentesting.

Who is it for?

Security researchers testing Widevine or PlayReady device files and license servers on content they are authorized to assess.

Skip if: Building application features or non-DRM code security review.

When should I use this skill?

You need to extract DRM keys, parse PSSH from an MPD manifest, or validate DRM device credentials.

What you get

Decryption keys extracted and M3U playlists generated through a chained plugin workflow.

  • Extracted decryption keys
  • M3U playlists with embedded keys
  • Downloaded/decrypted content

By the numbers

  • 8 defined trigger phrases
  • 6 required Python dependencies

Files

SKILL.mdMarkdownGitHub ↗

DRM Pentesting Toolkit Skill

Skill by ara.so — Security Skills collection.

This skill enables AI agents to assist with DRM pentesting using a modular, plugin-based toolkit. The project provides a CustomTkinter GUI for managing DRM devices (Widevine .wvd, PlayReady .prd), extracting PSSH from MPD manifests, communicating with license servers, and downloading protected content.

What It Does

The DRM Pentesting Toolkit is a comprehensive framework for:

  • Loading and validating Widevine/PlayReady device files
  • Parsing PSSH (Protection System Specific Header) data from DASH/MPD manifests
  • Extracting decryption keys from license servers
  • Generating M3U playlists with embedded keys
  • Downloading and decrypting content via N_m3u8DL-RE
  • Managing credentials, proxies, and HTTP configurations
  • Chaining multiple operations through a plugin stack system

Installation

# Clone the repository
git clone https://github.com/fairy-root/drm-pentesting-toolkit.git
cd drm-pentesting-toolkit

# Install dependencies
pip install -r requirements.txt

# Set up directory structure
mkdir -p devices/widevine devices/playready credentials proxies N_m3u8DL-RE OUTPUT

Required dependencies (from requirements.txt):

customtkinter>=5.2.0
pywidevine>=1.8.0
pyplayready>=1.3.0
requests>=2.31.0
Brotli>=1.1.0
protobuf>=4.25.0

Additional setup: 1. Place DRM device files in devices/widevine/*.wvd or devices/playready/*.prd 2. Download N_m3u8DL-RE and shaka-packager executables to N_m3u8DL-RE/ 3. Create credential files in credentials/ (format: username:password) 4. Create proxy files in proxies/ (format: host:port:user:pass)

Running the Application

# Launch GUI
python main.py

The GUI provides tabs for:

  • MPD Data: Configure manifest URLs, headers, cookies
  • License Data: Set license server URLs and authentication
  • Logs & Output: Monitor plugin execution
  • Downloads: Track N_m3u8DL-RE download progress

Core Architecture

Plugin System

Plugins are Python files in plugins/ that define a run() function. The plugin manager uses introspection to: 1. Auto-detect required parameters 2. Map UI context to function arguments 3. Pass results between chained plugins

Plugin context variables available:

device_path: str          # Selected device file path
device_type: str          # 'widevine' or 'playready'
cdm: object              # CDM instance (Widevine/PlayReady)
session_id: bytes        # Active CDM session ID
pssh: str                # Extracted PSSH box
license_url: str         # License server URL
mpd_url: str             # MPD manifest URL
mpd_headers: dict        # HTTP headers for MPD requests
mpd_cookies: dict        # Cookies for MPD requests
license_headers: dict    # HTTP headers for license requests
license_cookies: dict    # Cookies for license requests
license_post_data: str   # POST data for license requests
keys: list               # Extracted decryption keys
credentials: dict        # Exposed credentials (if enabled)
proxy_line: str          # Selected proxy configuration
search_query: str        # User search input

Creating a Plugin

# plugins/my_custom_plugin.py
def run(mpd_url, mpd_headers, log_callback=None):
    """
    Custom plugin that fetches and analyzes MPD manifest
    
    Args:
        mpd_url: The DASH manifest URL
        mpd_headers: Dictionary of HTTP headers
        log_callback: Function to log messages to GUI
    
    Returns:
        dict: Context updates for next plugin
    """
    import requests
    
    if log_callback:
        log_callback(f"Fetching MPD from: {mpd_url}")
    
    try:
        response = requests.get(mpd_url, headers=mpd_headers, timeout=10)
        response.raise_for_status()
        
        # Parse MPD content
        mpd_content = response.text
        
        if log_callback:
            log_callback(f"MPD size: {len(mpd_content)} bytes")
        
        # Return context for next plugin
        return {
            'mpd_content': mpd_content,
            'mpd_size': len(mpd_content)
        }
    
    except Exception as e:
        if log_callback:
            log_callback(f"Error: {str(e)}")
        raise

Common Workflows

Key Extraction Workflow

# Plugin sequence:
# 1. Load device
# 2. Parse PSSH
# 3. Send license request
# 4. Extract keys

# Example: 1_load_devices.py
def run(device_path, device_type, log_callback=None):
    from pywidevine.cdm import Cdm as WidevineCdm
    from pywidevine.device import Device as WidevineDevice
    
    if device_type == 'widevine':
        device = WidevineDevice.load(device_path)
        cdm = WidevineCdm.from_device(device)
        session_id = cdm.open()
        
        if log_callback:
            log_callback(f"Widevine session opened: {session_id.hex()}")
        
        return {
            'cdm': cdm,
            'session_id': session_id,
            'device_security_level': device.security_level
        }

# Example: 2_parse_pssh.py
def run(mpd_url, mpd_headers, mpd_cookies, http_method='GET', log_callback=None):
    import requests
    import re
    from base64 import b64decode
    
    response = requests.request(
        method=http_method,
        url=mpd_url,
        headers=mpd_headers,
        cookies=mpd_cookies,
        timeout=15
    )
    
    # Extract PSSH from MPD
    pssh_pattern = r'<cenc:pssh>([A-Za-z0-9+/=]+)</cenc:pssh>'
    matches = re.findall(pssh_pattern, response.text)
    
    if matches:
        pssh = matches[0]
        if log_callback:
            log_callback(f"PSSH extracted: {pssh[:50]}...")
        return {'pssh': pssh}
    
    raise ValueError("No PSSH found in MPD")

# Example: 4_send_license.py
def run(cdm, session_id, pssh, license_url, license_headers, 
        license_cookies, license_post_data, log_callback=None):
    import requests
    from base64 import b64decode, b64encode
    
    # Generate license challenge
    challenge = cdm.get_license_challenge(session_id, b64decode(pssh))
    
    # Prepare license request
    payload = license_post_data.replace('{CHALLENGE}', b64encode(challenge).decode())
    
    response = requests.post(
        license_url,
        headers=license_headers,
        cookies=license_cookies,
        data=payload
    )
    
    # Parse license
    cdm.parse_license(session_id, response.content)
    
    # Extract keys
    keys = []
    for key in cdm.get_keys(session_id):
        if key.type == 'CONTENT':
            key_str = f"{key.kid.hex()}:{key.key.hex()}"
            keys.append(key_str)
            if log_callback:
                log_callback(f"Key: {key_str}")
    
    return {'keys': keys}

Download Workflow

# Example: 6_n_m3u8dl_re.py
def run(mpd_url, keys, search_query='output', log_callback=None):
    import subprocess
    import os
    
    exe_path = os.path.join('N_m3u8DL-RE', 'N_m3u8DL-RE.exe')
    
    # Build key arguments
    key_args = []
    for key in keys:
        key_args.extend(['--key', key])
    
    # Build command
    cmd = [
        exe_path,
        mpd_url,
        '--save-dir', 'OUTPUT',
        '--save-name', search_query,
        '--auto-select',
        '--no-log'
    ] + key_args
    
    if log_callback:
        log_callback(f"Starting download: {search_query}")
    
    # Execute download
    process = subprocess.Popen(
        cmd,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        text=True
    )
    
    return {
        'download_process': process,
        'output_name': search_query
    }

Credential Integration

# Plugins can access credentials when "Expose Credentials" is enabled

def run(credentials, license_url, log_callback=None):
    """
    Use credentials to authenticate with license server
    
    credentials structure:
    {
        'username': 'user@example.com',
        'password': 'pass123',
        'api_key': 'abc123xyz',
        'mac_address': '00:11:22:33:44:55'
    }
    """
    import requests
    
    # Example: Use credentials for authentication
    if 'api_key' in credentials:
        headers = {
            'Authorization': f"Bearer {credentials['api_key']}"
        }
    elif 'username' in credentials and 'password' in credentials:
        headers = {
            'X-Username': credentials['username'],
            'X-Password': credentials['password']
        }
    else:
        raise ValueError("No valid credentials found")
    
    response = requests.post(license_url, headers=headers)
    
    if log_callback:
        log_callback(f"Authenticated with {list(credentials.keys())}")
    
    return {'auth_headers': headers}

Configuration

Environment Settings

Edit settings/environment.ini to configure credential types:

[CREDENTIALS]
EMAIL_PASSWORD = ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}:.+$
USERNAME_PASSWORD = ^[a-zA-Z0-9_]+:.+$
API_KEY = ^[a-zA-Z0-9]{20,}$
MAC_ADDRESS = ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$

State Persistence

Application state is saved to settings/savestate.json:

{
  "mpd_url": "https://example.com/manifest.mpd",
  "mpd_headers": "{\"User-Agent\": \"Mozilla/5.0\"}",
  "license_url": "https://license.example.com/widevine",
  "selected_device": "devices/widevine/device.wvd",
  "selected_credentials": "credentials/account.txt",
  "plugin_stack": ["1_load_devices", "2_parse_pssh", "4_send_license"]
}

Proxy Configuration

Format proxy files as proxies/myproxies.txt:

# Non-authenticated
proxy1.example.com:8080
192.168.1.100:3128

# Authenticated
proxy2.example.com:8080:username:password

Access in plugins:

def run(proxy_line, log_callback=None):
    if proxy_line:
        parts = proxy_line.split(':')
        if len(parts) == 2:
            proxy = f"http://{parts[0]}:{parts[1]}"
        elif len(parts) == 4:
            proxy = f"http://{parts[2]}:{parts[3]}@{parts[0]}:{parts[1]}"
        
        proxies = {
            'http': proxy,
            'https': proxy
        }
        
        return {'proxies': proxies}

Programmatic Usage

Using Plugin Manager Directly

from core.plugin_manager import PluginManager

# Initialize plugin manager
pm = PluginManager(plugins_dir='plugins')
pm.scan_plugins()

# Build context
context = {
    'device_path': 'devices/widevine/l3.wvd',
    'device_type': 'widevine',
    'mpd_url': 'https://example.com/manifest.mpd',
    'mpd_headers': {'User-Agent': 'Mozilla/5.0'},
    'license_url': 'https://license.example.com/widevine'
}

# Execute plugin stack
plugin_stack = ['1_load_devices', '2_parse_pssh', '4_send_license']

for plugin_name in plugin_stack:
    plugin_info = pm.plugins[plugin_name]
    result = pm.run_plugin(plugin_info, context, log_callback=print)
    
    # Merge results into context
    if isinstance(result, dict):
        context.update(result)

# Access extracted keys
print(f"Extracted keys: {context.get('keys', [])}")

Validating Device Files

from pywidevine.cdm import Cdm
from pywidevine.device import Device

def validate_widevine_device(device_path):
    """Validate a Widevine device file"""
    try:
        device = Device.load(device_path)
        cdm = Cdm.from_device(device)
        session_id = cdm.open()
        
        print(f"✓ Valid Widevine device")
        print(f"  Security Level: {device.security_level}")
        print(f"  Client ID: {device.client_id.token[:20].hex()}...")
        
        cdm.close(session_id)
        return True
    except Exception as e:
        print(f"✗ Invalid device: {e}")
        return False

validate_widevine_device('devices/widevine/device.wvd')

Troubleshooting

Plugin Not Detecting Parameters

Issue: Plugin not receiving expected context variables

Solution: Check function signature matches available context:

# Wrong - parameter name doesn't match context
def run(manifest_url, log_callback=None):
    pass

# Correct - matches context key 'mpd_url'
def run(mpd_url, log_callback=None):
    pass

PSSH Extraction Fails

Issue: No PSSH found in MPD manifest

Solution: Check HTTP method and authentication:

# Try different HTTP method
response = requests.request(
    method='POST',  # Some MPDs require POST
    url=mpd_url,
    headers=mpd_headers
)

# Or check for alternative PSSH locations
pssh_patterns = [
    r'<cenc:pssh>([^<]+)</cenc:pssh>',
    r'cenc:default_KID="([^"]+)"',
    r'<widevine:license>([^<]+)</widevine:license>'
]

License Request Fails

Issue: 403/401 errors from license server

Solution: Verify headers and credential handling:

# Check required headers
required_headers = {
    'User-Agent': 'Mozilla/5.0...',
    'Origin': 'https://example.com',
    'Referer': 'https://example.com/player',
    'Content-Type': 'application/octet-stream'  # Often required
}

# Verify POST data format
# Some servers expect base64 challenge, others expect JSON
payload_formats = [
    b64encode(challenge).decode(),  # Raw base64
    json.dumps({'challenge': b64encode(challenge).decode()}),  # JSON
    f'spc={b64encode(challenge).decode()}'  # Form-encoded
]

Download Process Hangs

Issue: N_m3u8DL-RE download not progressing

Solution: Check executable path and permissions:

import os
import stat

exe_path = 'N_m3u8DL-RE/N_m3u8DL-RE.exe'

# Verify file exists
if not os.path.exists(exe_path):
    raise FileNotFoundError(f"N_m3u8DL-RE not found at {exe_path}")

# Check execute permissions (Unix)
if os.name != 'nt':
    st = os.stat(exe_path)
    os.chmod(exe_path, st.st_mode | stat.S_IEXEC)

Device Loading Errors

Issue: CDM fails to initialize

Solution: Verify device file format and dependencies:

# For Widevine
try:
    from pywidevine.device import Device
    device = Device.load(device_path)
    print(f"Client ID length: {len(device.client_id.token)}")
    print(f"Private key present: {device.private_key is not None}")
except Exception as e:
    print(f"Device load error: {e}")
    # May need to update pywidevine version
    # pip install --upgrade pywidevine

Credentials Not Exposing

Issue: credentials parameter not available in plugin

Solution: Ensure "Expose Credentials to Plugins" checkbox is enabled in GUI, and credentials file is properly formatted:

# credentials/account.txt format
user@example.com:password123
# Not: {"user": "...", "pass": "..."}

Advanced Patterns

Multi-Key Content

def run(cdm, session_id, mpd_content, log_callback=None):
    """Extract keys for multi-period content"""
    import re
    from base64 import b64decode
    
    # Find all PSSH boxes
    pssh_list = re.findall(r'<cenc:pssh>([^<]+)</cenc:pssh>', mpd_content)
    
    all_keys = []
    for i, pssh in enumerate(set(pssh_list)):  # Deduplicate
        try:
            challenge = cdm.get_license_challenge(session_id, b64decode(pssh))
            # ... send license request ...
            keys = cdm.get_keys(session_id)
            all_keys.extend(keys)
            
            if log_callback:
                log_callback(f"Period {i+1}: {len(keys)} keys")
        except Exception as e:
            if log_callback:
                log_callback(f"Period {i+1} failed: {e}")
    
    return {'keys': all_keys}

Custom Headers from Environment

def run(mpd_url, log_callback=None):
    """Use environment variables for sensitive headers"""
    import os
    import requests
    
    headers = {
        'User-Agent': os.getenv('USER_AGENT', 'Mozilla/5.0'),
        'Authorization': os.getenv('AUTH_TOKEN'),  # Never hardcode tokens
        'X-Custom-Header': os.getenv('CUSTOM_HEADER')
    }
    
    # Remove None values
    headers = {k: v for k, v in headers.items() if v is not None}
    
    response = requests.get(mpd_url, headers=headers)
    return {'mpd_content': response.text}

This skill enables AI agents to help developers perform DRM security testing, create custom plugins, and automate key extraction workflows using this comprehensive toolkit.

Related skills

FAQ

What DRM systems does the toolkit support?

It manages Widevine (.wvd) and PlayReady (.prd) device files.

How does the plugin system work?

Plugins are Python files with a run() function; the plugin manager auto-detects parameters, maps UI context to arguments, and passes results between chained plugins.

Securityappsecaudit

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.