NextGen🎉

This commit is contained in:
FoxRefire
2024-04-19 05:48:32 +09:00
parent 078268b069
commit 101d9cb6b8
29 changed files with 235 additions and 159 deletions

View File

@@ -1,10 +1,12 @@
## WVGuesserExtension
## WVGuesserExtension-NextGen
Extension works standalone.
Not anymore need WVCore.Server API setup!
### Instalation
1. Setup [Guesser API](https://github.com/nilaoda/WVCore.Server) ([Guide](https://github.com/nilaoda/Blog/discussions/58#discussioncomment-9052557))
2. Install extension
1. Donwload or clone this code
2. At the same directory of `manifest.json`(root directory of this extension), put the device file named `device.wvd`
3. Install extension
* Firefox

View File

@@ -1,7 +1,7 @@
let psshs=[];
let requests=[];
function convertHeaders(obj){
return Object.fromEntries(obj.map(header => [header.name, header.value]))
return JSON.stringify(Object.fromEntries(obj.map(header => [header.name, header.value])))
}
chrome.webRequest.onBeforeSendHeaders.addListener(
function(details) {

View File

@@ -26,5 +26,6 @@
"browser_action": {
"default_title": "Widevine Guessor"
},
"web_accessible_resources": ["inject.js"]
"web_accessible_resources": ["inject.js"],
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
}

View File

@@ -3,6 +3,7 @@
<meta charset="UTF-8">
<title>Widevine L3 Guessor 2024</title>
<style>div{display:none}</style>
<script src="pyodide/pyodide.js"></script>
</head>
<body>
<div id="noEME" style="display:block">
@@ -11,13 +12,6 @@
</div>
<div id="home">
<form id="wvForm">
<label for="guessr">Guessr API</label>
<input type="text" id="guessr" value="https://proposed-marketa-foxrefire.koyeb.app" disabled/>
<input type="radio" value="public" name="apiType" checked>Public
<input type="radio" value="local" name="apiType">Local
<input type="radio" value="custom" name="apiType">Custom
<br>
<label for="pssh">PSSH</label>
<input type="text" id="pssh" disabled/>
<input type="hidden" id="psshIndex" />

View File

@@ -1,6 +1,3 @@
import CommonWV from './schemes/CommonWV.js';
import DRMToday from './schemes/DRMToday.js';
let psshs=chrome.extension.getBackgroundPage().getPsshs();
let requests=chrome.extension.getBackgroundPage().getRequests();
@@ -31,19 +28,15 @@ function handleRadioChange(event) {
}
async function guess(){
let WVScheme;
switch (document.getElementById('scheme').value) {
case "CommonWV":
WVScheme=CommonWV;
break;
case "DRMToday":
WVScheme=DRMToday;
break;
}
const result=await WVScheme(document.getElementById('guessr').value,
document.getElementById('pssh').value,
requests[userInputs['license']]['url'],
requests[userInputs['license']]['headers'])
let pyodide = await loadPyodide();
await pyodide.loadPackage(["certifi-2024.2.2-py3-none-any.whl","charset_normalizer-3.3.2-py3-none-any.whl","construct-2.8.8-py2.py3-none-any.whl","idna-3.6-py3-none-any.whl","packaging-23.2-py3-none-any.whl","protobuf-4.24.4-cp312-cp312-emscripten_3_1_52_wasm32.whl","pycryptodome-3.20.0-cp35-abi3-emscripten_3_1_52_wasm32.whl","pymp4-1.4.0-py3-none-any.whl","pyodide_http-0.2.1-py3-none-any.whl","pywidevine-1.8.0-py3-none-any.whl","requests-2.31.0-py3-none-any.whl","urllib3-2.2.1-py3-none-any.whl"].map(e=>"wheels/"+e))
let vars=`pssh="${document.getElementById('pssh').value}"\n`
vars+=`licUrl="${requests[userInputs['license']]['url']}"\n`
vars+=`licHeaders='${requests[userInputs['license']]['headers'].replace(/\\/g, "\\\\")}'\n`
let pre=await fetch('python/pre.py').then(res=>res.text())
let after=await fetch('python/after.py').then(res=>res.text())
let scheme=await fetch('python/schemes/CommonWV.py').then(res=>res.text())
let result = await pyodide.runPythonAsync([vars, pre, scheme, after].join("\n"));
document.getElementById('result').value=result;
}

134
pyodide/package.json Normal file
View File

@@ -0,0 +1,134 @@
{
"name": "pyodide",
"version": "0.25.1",
"description": "The Pyodide JavaScript package",
"keywords": [
"python",
"webassembly"
],
"homepage": "https://github.com/pyodide/pyodide",
"repository": {
"type": "git",
"url": "https://github.com/pyodide/pyodide"
},
"bugs": {
"url": "https://github.com/pyodide/pyodide/issues"
},
"license": "Apache-2.0",
"devDependencies": {
"@types/assert": "^1.5.6",
"@types/expect": "^24.3.0",
"@types/mocha": "^9.1.0",
"@types/node": "^20.8.4",
"@types/ws": "^8.5.3",
"chai": "^4.3.6",
"chai-as-promised": "^7.1.1",
"cross-env": "^7.0.3",
"dts-bundle-generator": "^8.1.1",
"error-stack-parser": "^2.1.4",
"esbuild": "^0.17.12",
"express": "^4.17.3",
"mocha": "^9.0.2",
"npm-run-all": "^4.1.5",
"nyc": "^15.1.0",
"prettier": "^2.2.1",
"ts-mocha": "^9.0.2",
"tsd": "^0.24.1",
"typedoc": "^0.25.1",
"typescript": "^4.6.4",
"wabt": "^1.0.32"
},
"main": "pyodide.js",
"exports": {
".": {
"require": "./pyodide.js",
"import": "./pyodide.mjs",
"types": "./pyodide.d.ts"
},
"./ffi": {
"types": "./ffi.d.ts"
},
"./pyodide.asm.wasm": "./pyodide.asm.wasm",
"./pyodide.asm.js": "./pyodide.asm.js",
"./python_stdlib.zip": "./python_stdlib.zip",
"./pyodide.mjs": "./pyodide.mjs",
"./pyodide.js": "./pyodide.js",
"./package.json": "./package.json",
"./pyodide-lock.json": "./pyodide-lock.json"
},
"files": [
"pyodide.asm.js",
"pyodide.asm.wasm",
"python_stdlib.zip",
"pyodide.mjs",
"pyodide.js.map",
"pyodide.mjs.map",
"pyodide.d.ts",
"ffi.d.ts",
"pyodide-lock.json",
"console.html"
],
"browser": {
"child_process": false,
"crypto": false,
"fs": false,
"fs/promises": false,
"path": false,
"url": false,
"vm": false,
"ws": false
},
"scripts": {
"build": "tsc --noEmit && node esbuild.config.mjs",
"test": "npm-run-all test:*",
"test:unit": "cross-env TEST_NODE=1 ts-mocha --node-option=experimental-loader=./test/loader.mjs --node-option=experimental-wasm-stack-switching -p tsconfig.test.json test/unit/**/*.test.*",
"test:node": "cross-env TEST_NODE=1 mocha test/integration/**/*.test.js",
"test:browser": "mocha test/integration/**/*.test.js",
"tsc": "tsc --noEmit",
"coverage": "cross-env TEST_NODE=1 npm-run-all coverage:*",
"coverage:build": "nyc npm run test:node"
},
"mocha": {
"bail": false,
"timeout": 30000,
"full-trace": true,
"inline-diffs": true,
"check-leaks": false,
"global": [
"pyodide",
"page",
"chai"
]
},
"nyc": {
"reporter": [
"html",
"text-summary"
],
"include": [
"*.ts"
],
"all": true,
"clean": true,
"cache": false,
"instrument": false,
"checkCoverage": true,
"statements": 95,
"functions": 95,
"branches": 80,
"lines": 95
},
"tsd": {
"compilerOptions": {
"lib": [
"ES2017",
"DOM"
]
}
},
"dependencies": {
"base-64": "^1.0.0",
"ws": "^8.5.0"
},
"types": "./pyodide.d.ts"
}

File diff suppressed because one or more lines are too long

17
pyodide/pyodide.asm.js Normal file

File diff suppressed because one or more lines are too long

BIN
pyodide/pyodide.asm.wasm Executable file

Binary file not shown.

12
pyodide/pyodide.js Normal file

File diff suppressed because one or more lines are too long

10
pyodide/pyodide.mjs Normal file

File diff suppressed because one or more lines are too long

BIN
pyodide/python_stdlib.zip Normal file

Binary file not shown.

11
python/after.py Normal file
View File

@@ -0,0 +1,11 @@
# parse license challenge
cdm.parse_license(session_id, licence)
# get keys
keys=""
for key in cdm.get_keys(session_id):
keys+=f"[{key.type}] {key.kid.hex}:{key.key.hex()}\n"
# close session, disposes of session data
cdm.close(session_id)
keys

26
python/pre.py Normal file
View File

@@ -0,0 +1,26 @@
from pywidevine.cdm import Cdm
from pywidevine.device import Device
from pywidevine.pssh import PSSH
import json
from pyodide.http import pyfetch
# prepare pssh
pssh = PSSH(pssh)
# load device
with open('device.wvd', 'wb') as f:
wvdExt=await (await pyfetch("device.wvd")).bytes()
f.write(wvdExt)
device = Device.load("device.wvd")
# load cdm
cdm = Cdm.from_device(device)
# open cdm session
session_id = cdm.open()
# get license challenge
challenge = cdm.get_license_challenge(session_id, pssh)
licHeaders=json.loads(licHeaders)

View File

@@ -0,0 +1,5 @@
licence = await (await pyfetch(licUrl,
method="POST",
headers=licHeaders,
body=challenge
)).bytes()

View File

@@ -1,53 +0,0 @@
export default async function(serverAddr, pssh, licUrl,_headers) {
console.group("fetch cert...");
let certBuffer = await fetch(licUrl, {
body: new Uint8Array([0x08, 0x04]),
headers: _headers,
method: "POST"
}).then(resp => resp.arrayBuffer());
let certB64 = btoa(String.fromCharCode(...new Uint8Array(certBuffer)));
console.log(certB64);
console.groupEnd();
console.group("fetch challenge...");
let jsonC = await fetch(serverAddr + "/getchallenge", {
body: JSON.stringify({
"PSSH": pssh,
"CertBase64": certB64
}),
headers: {
"Content-Type": "application/json"
},
method: "POST"
}).then(resp => resp.json());
let challengeBase64 = jsonC.challengeBase64;
console.log(challengeBase64);
console.groupEnd();
console.group("fetch license...");
let licBuffer = await fetch(licUrl, {
body: Uint8Array.from(atob(challengeBase64), (c) => c.charCodeAt(0)),
headers: _headers,
method: "POST"
}).then(resp => resp.arrayBuffer());
let licB64 = btoa(String.fromCharCode(...new Uint8Array(licBuffer)));
console.log(licB64);
console.groupEnd();
console.group("get keys...");
let jsonK = await fetch(serverAddr + "/getkeys", {
body: JSON.stringify({
"PSSH": pssh,
"ChallengeBase64": challengeBase64,
"LicenseBase64": licB64
}),
headers: {
"Content-Type": "application/json"
},
method: "POST"
}).then(resp => resp.json());
let keys = jsonK.keys;
console.log(keys);
console.groupEnd();
return keys;
};

View File

@@ -1,77 +0,0 @@
export default async function(serverAddr, pssh, licUrl,_headers) {
console.group("fetch cert...");
let certBuffer = await fetch(licUrl, {
body: new Uint8Array([0x08, 0x04]),
headers: _headers,
method: "POST",
}).then((resp) => resp.arrayBuffer());
let certB64 = "";
if (isValidJson(String.fromCharCode(...new Uint8Array(certBuffer)))) {
let jsonData = JSON.parse(
String.fromCharCode(...new Uint8Array(certBuffer))
);
if (jsonData.license) {
certB64 = jsonData.license;
} else {
console.log("JSON does not contain 'license'");
}
} else {
certB64 = btoa(String.fromCharCode(...new Uint8Array(certBuffer)));
}
console.log(certB64);
console.groupEnd();
console.group("fetch challenge...");
let jsonC = await fetch(serverAddr + "/getchallenge", {
body: JSON.stringify({
PSSH: pssh,
CertBase64: certB64,
}),
headers: {
"Content-Type": "application/json",
},
method: "POST",
}).then((resp) => resp.json());
let challengeBase64 = jsonC.challengeBase64;
console.log(challengeBase64);
console.groupEnd();
console.group("fetch license...");
let licBuffer = await fetch(licUrl, {
body: Uint8Array.from(atob(challengeBase64), (c) => c.charCodeAt(0)),
headers: _headers,
method: "POST",
}).then((resp) => resp.arrayBuffer());
let licB64 = "";
if (isValidJson(String.fromCharCode(...new Uint8Array(licBuffer)))) {
let jsonData = JSON.parse(
String.fromCharCode(...new Uint8Array(licBuffer))
);
if (jsonData.license) {
licB64 = jsonData.license;
} else {
console.log("JSON does not contain 'license'");
}
} else {
licB64 = btoa(String.fromCharCode(...new Uint8Array(licBuffer)));
}
console.log(licB64);
console.groupEnd();
console.group("get keys...");
let jsonK = await fetch(serverAddr + "/getkeys", {
body: JSON.stringify({
PSSH: pssh,
ChallengeBase64: challengeBase64,
LicenseBase64: licB64,
}),
headers: {
"Content-Type": "application/json",
},
method: "POST",
}).then((resp) => resp.json());
let keys = jsonK.keys;
console.log(keys);
console.groupEnd();
return keys;
};

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.