Files

137 lines
5.2 KiB
HTML
Raw Permalink Normal View History

2026-08-08 12:35:28 +02:00
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Widevine CDM Trigger</title>
</head>
<body style="background:#000;color:#fff;font-family:monospace;padding:20px">
<h2>Widevine CDM Direct Trigger</h2>
<p style="color:#aaa">No video needed — directly invokes the CDM to generate a license challenge.</p>
<button id="btn" style="font-size:20px;padding:12px 30px;cursor:pointer">
&#9654; Trigger Widevine (generates RSA_sign)
</button>
<pre id="log" style="margin-top:16px;font-size:13px;line-height:1.5"></pre>
<script>
var log = document.getElementById('log');
function L(s) { log.textContent += s + '\n'; console.log(s); }
// TVP content PSSH
var PSSH_B64 = 'AAAAOnBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABoIARIQ1DG5TL5NQ/2zoimqi0ixiUjj3JWbBg==';
var LICENSE = 'https://vod.tvp.pl/api/products/2993670/drm/widevine/external?platform=BROWSER&type=MOVIE';
function b64ToBytes(b64) {
var s = atob(b64), a = new Uint8Array(s.length);
for (var i = 0; i < s.length; i++) a[i] = s.charCodeAt(i);
return a;
}
// Auto-trigger 1.5 s after page loads (no button click needed)
window.addEventListener('load', function() {
setTimeout(function() { document.getElementById('btn').click(); }, 1500);
});
document.getElementById('btn').onclick = async function() {
this.disabled = true;
L('--- Starting ---');
try {
var cfg = [{
initDataTypes: ['cenc'],
videoCapabilities: [
{ contentType: 'video/mp4; codecs="avc1.42E01E"', robustness: 'SW_SECURE_CRYPTO' },
{ contentType: 'video/mp4; codecs="avc1.42E01E"', robustness: '' }
],
audioCapabilities: [
{ contentType: 'audio/mp4; codecs="mp4a.40.2"', robustness: 'SW_SECURE_CRYPTO' },
{ contentType: 'audio/mp4; codecs="mp4a.40.2"', robustness: '' }
]
}];
L('Requesting com.widevine.alpha access...');
var access = await navigator.requestMediaKeySystemAccess('com.widevine.alpha', cfg);
L('OK: got MediaKeySystemAccess');
var mediaKeys = await access.createMediaKeys();
L('OK: created MediaKeys');
var session = mediaKeys.createSession('temporary');
L('OK: session created');
session.addEventListener('message', async function(e) {
L('');
L('Message type: ' + e.messageType + ' size: ' + e.message.byteLength + ' bytes');
if (e.messageType === 'individualization-request') {
L('Service cert request — setting EMPTY cert to force plaintext client_id...');
try {
// Setting empty cert disables encryption of client_id
await mediaKeys.setServerCertificate(new Uint8Array([0]));
L('Empty service cert set.');
} catch(err) {
L('setServerCertificate error: ' + err.message);
}
// Generate request again — should now produce plaintext client_id
try {
var pssh2 = b64ToBytes(PSSH_B64);
await session.generateRequest('cenc', pssh2);
L('generateRequest called again.');
} catch(e2) {
L('generateRequest error: ' + e2.message);
}
return;
}
if (e.messageType !== 'license-request' && e.messageType !== 'license-renewal') {
L('Unhandled message type: ' + e.messageType);
return;
}
L('*** LICENSE CHALLENGE READY (' + e.message.byteLength + ' bytes) ***');
L('*** RSA_sign fired — Frida should have captured the key! ***');
L('');
// Save challenge to local capture server for client_id extraction
try {
L('Saving challenge to localhost:8765/capture ...');
await fetch('http://localhost:8765/capture', {
method: 'POST', body: e.message,
headers: { 'Content-Type': 'application/octet-stream' }
});
L('Challenge saved to localhost capture server.');
} catch(err) {
L('Capture error: ' + err.message);
}
// Send to license server via local proxy
try {
L('Sending to license server via proxy...');
var r = await fetch('http://localhost:8765/proxy', {
method: 'POST', body: e.message,
headers: { 'Content-Type': 'application/octet-stream' }
});
L('License server HTTP ' + r.status);
if (r.ok) {
var lic = await r.arrayBuffer();
await session.update(new Uint8Array(lic));
L('License installed! Full DRM session active.');
}
} catch(err) {
L('License fetch error: ' + err.message);
}
});
var pssh = b64ToBytes(PSSH_B64);
L('Calling generateRequest with PSSH (' + pssh.length + ' bytes)...');
await session.generateRequest('cenc', pssh);
L('generateRequest returned — challenge was built.');
} catch(e) {
L('ERROR: ' + e.name + ': ' + e.message);
document.getElementById('btn').disabled = false;
}
};
L('Ready. Press the button.');
</script>
</body>
</html>