fix: switch to rquickjs crate for deobfuscator
This commit is contained in:
parent
74fe628f63
commit
cdb56de3bd
3 changed files with 41 additions and 30 deletions
|
|
@ -24,9 +24,7 @@ keywords = ["youtube", "video", "music"]
|
||||||
categories = ["api-bindings", "multimedia"]
|
categories = ["api-bindings", "multimedia"]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
quick-js-dtp = { version = "0.4.1", default-features = false, features = [
|
rquickjs = "0.8.1"
|
||||||
"patch-dateparser",
|
|
||||||
] }
|
|
||||||
once_cell = "1.12.0"
|
once_cell = "1.12.0"
|
||||||
regex = "1.6.0"
|
regex = "1.6.0"
|
||||||
fancy-regex = "0.14.0"
|
fancy-regex = "0.14.0"
|
||||||
|
|
@ -94,7 +92,7 @@ rustls-tls-webpki-roots = ["reqwest/rustls-tls-webpki-roots"]
|
||||||
rustls-tls-native-roots = ["reqwest/rustls-tls-native-roots"]
|
rustls-tls-native-roots = ["reqwest/rustls-tls-native-roots"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
quick-js-dtp.workspace = true
|
rquickjs.workspace = true
|
||||||
once_cell.workspace = true
|
once_cell.workspace = true
|
||||||
regex.workspace = true
|
regex.workspace = true
|
||||||
fancy-regex.workspace = true
|
fancy-regex.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use ress::tokens::Token;
|
use ress::tokens::Token;
|
||||||
|
use rquickjs::{Context, Runtime};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -13,7 +14,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Deobfuscator {
|
pub struct Deobfuscator {
|
||||||
ctx: quick_js::Context,
|
ctx: Context,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
|
@ -76,29 +77,34 @@ impl DeobfData {
|
||||||
impl Deobfuscator {
|
impl Deobfuscator {
|
||||||
/// Instantiate a new deobfuscator with the given data
|
/// Instantiate a new deobfuscator with the given data
|
||||||
pub fn new(data: &DeobfData) -> Result<Self, DeobfError> {
|
pub fn new(data: &DeobfData) -> Result<Self, DeobfError> {
|
||||||
let ctx =
|
let rt = Runtime::new()?;
|
||||||
quick_js::Context::new().or(Err(DeobfError::Other("could not create QuickJS rt")))?;
|
let ctx = Context::full(&rt)?;
|
||||||
ctx.eval(&data.sig_fn)?;
|
ctx.with(|ctx| {
|
||||||
ctx.eval(&data.nsig_fn)?;
|
let mut opts = rquickjs::context::EvalOptions::default();
|
||||||
|
opts.strict = false;
|
||||||
|
ctx.eval_with_options::<(), _>(data.sig_fn.as_bytes(), opts)?;
|
||||||
|
let mut opts = rquickjs::context::EvalOptions::default();
|
||||||
|
opts.strict = false;
|
||||||
|
ctx.eval_with_options::<(), _>(data.nsig_fn.as_bytes(), opts)
|
||||||
|
})?;
|
||||||
Ok(Self { ctx })
|
Ok(Self { ctx })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deobfuscate the `s` parameter from the `signature_cipher` field
|
/// Deobfuscate the `s` parameter from the `signature_cipher` field
|
||||||
pub fn deobfuscate_sig(&self, sig: &str) -> Result<String, DeobfError> {
|
pub fn deobfuscate_sig(&self, sig: &str) -> Result<String, DeobfError> {
|
||||||
let res = self.ctx.call_function(DEOBF_SIG_FUNC_NAME, [sig])?;
|
let res = self
|
||||||
|
.ctx
|
||||||
res.into_string()
|
.with(|ctx| call_fn(&ctx, DEOBF_SIG_FUNC_NAME, sig))?;
|
||||||
.ok_or(DeobfError::Other("sig deobfuscation fn returned no string"))
|
tracing::trace!("deobf sig: {sig} -> {res}");
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deobfuscate the `n` stream URL parameter to circumvent throttling
|
/// Deobfuscate the `n` stream URL parameter to circumvent throttling
|
||||||
pub fn deobfuscate_nsig(&self, nsig: &str) -> Result<String, DeobfError> {
|
pub fn deobfuscate_nsig(&self, nsig: &str) -> Result<String, DeobfError> {
|
||||||
let res = self.ctx.call_function(DEOBF_NSIG_FUNC_NAME, [nsig])?;
|
let res = self
|
||||||
let res = res.into_string().ok_or(DeobfError::Other(
|
.ctx
|
||||||
"nsig deobfuscation fn returned no string",
|
.with(|ctx| call_fn(&ctx, DEOBF_NSIG_FUNC_NAME, nsig))?;
|
||||||
))?;
|
tracing::trace!("deobf nsig: {nsig} -> {res}");
|
||||||
tracing::debug!("deobf nsig: {nsig} -> {res}");
|
|
||||||
if res.starts_with("enhanced_except_") || res.ends_with(nsig) {
|
if res.starts_with("enhanced_except_") || res.ends_with(nsig) {
|
||||||
return Err(DeobfError::Other("nsig fn returned an exception"));
|
return Err(DeobfError::Other("nsig fn returned an exception"));
|
||||||
}
|
}
|
||||||
|
|
@ -279,6 +285,7 @@ fn extract_js_fn(js: &str, offset: usize, name: &str) -> Result<String, DeobfErr
|
||||||
|
|
||||||
let fn_range = (offset + start)..(offset + end);
|
let fn_range = (offset + start)..(offset + end);
|
||||||
let mut code = format!("var {};", &js[fn_range.clone()]);
|
let mut code = format!("var {};", &js[fn_range.clone()]);
|
||||||
|
let rt = rquickjs::Runtime::new()?;
|
||||||
|
|
||||||
for (ident, _) in idents.into_iter().filter(|(_, v)| *v == 1) {
|
for (ident, _) in idents.into_iter().filter(|(_, v)| *v == 1) {
|
||||||
let var_pattern_str = format!(r#"\b{}\b\s*=[^=]"#, regex::escape(&ident));
|
let var_pattern_str = format!(r#"\b{}\b\s*=[^=]"#, regex::escape(&ident));
|
||||||
|
|
@ -288,13 +295,13 @@ fn extract_js_fn(js: &str, offset: usize, name: &str) -> Result<String, DeobfErr
|
||||||
.filter(|m| !fn_range.contains(&m.start()) && !fn_range.contains(&m.end()))
|
.filter(|m| !fn_range.contains(&m.start()) && !fn_range.contains(&m.end()))
|
||||||
.find_map(|m| extract_js_var(&js[m.start()..]));
|
.find_map(|m| extract_js_var(&js[m.start()..]));
|
||||||
if let Some(var_code) = found_variable {
|
if let Some(var_code) = found_variable {
|
||||||
let ctx = quick_js::Context::new()
|
let ctx = Context::full(&rt)?;
|
||||||
.or(Err(DeobfError::Other("could not create QuickJS rt")))?;
|
let var_code = format!("var {var_code};");
|
||||||
if let Err(e) = ctx.eval(var_code) {
|
if let Err(e) = ctx.with(|ctx| ctx.eval::<(), _>(var_code.as_bytes())) {
|
||||||
tracing::warn!("invalid var ({e}): {var_code}");
|
tracing::warn!("invalid var ({e}): {var_code}");
|
||||||
code = format!("var {ident}={{}}; {code}");
|
code = format!("var {ident}={{}}; {code}");
|
||||||
} else {
|
} else {
|
||||||
code = format!("var {var_code}; {code}");
|
code = format!("{var_code} {code}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -362,15 +369,21 @@ fn extract_js_var(js: &str) -> Option<&str> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn call_fn(ctx: &rquickjs::Ctx, fn_name: &str, arg: &str) -> Result<String, rquickjs::Error> {
|
||||||
|
let f: rquickjs::Function = ctx.globals().get(fn_name)?;
|
||||||
|
f.call((arg,))
|
||||||
|
}
|
||||||
|
|
||||||
/// Verify if the deobfuscation function successfully processes a random input string
|
/// Verify if the deobfuscation function successfully processes a random input string
|
||||||
fn verify_fn(js_fn: &str, fn_name: &str) -> Result<(), DeobfError> {
|
fn verify_fn(js_fn: &str, fn_name: &str) -> Result<(), DeobfError> {
|
||||||
let ctx = quick_js::Context::new().or(Err(DeobfError::Other("could not create QuickJS rt")))?;
|
let rt = Runtime::new()?;
|
||||||
ctx.eval(js_fn)?;
|
let ctx = Context::full(&rt)?;
|
||||||
let testinp = util::generate_content_playback_nonce();
|
let testinp = util::generate_content_playback_nonce();
|
||||||
let res = ctx
|
let res = ctx.with(|ctx| {
|
||||||
.call_function(fn_name, [testinp.to_owned()])?
|
ctx.eval::<(), _>(js_fn)?;
|
||||||
.into_string()
|
call_fn(&ctx, fn_name, &testinp)
|
||||||
.ok_or(DeobfError::Other("deobfuscation fn returned no string"))?;
|
})?;
|
||||||
|
|
||||||
if res.is_empty() {
|
if res.is_empty() {
|
||||||
return Err(DeobfError::Other("deobfuscation fn returned empty string"));
|
return Err(DeobfError::Other("deobfuscation fn returned empty string"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ pub(crate) mod internal {
|
||||||
pub enum DeobfError {
|
pub enum DeobfError {
|
||||||
/// Error during JavaScript execution
|
/// Error during JavaScript execution
|
||||||
#[error("js execution error: {0}")]
|
#[error("js execution error: {0}")]
|
||||||
JavaScript(#[from] quick_js::ExecutionError),
|
JavaScript(#[from] rquickjs::Error),
|
||||||
/// Error during JavaScript parsing
|
/// Error during JavaScript parsing
|
||||||
#[error("js parsing: {0}")]
|
#[error("js parsing: {0}")]
|
||||||
JsParser(#[from] ress::error::Error),
|
JsParser(#[from] ress::error::Error),
|
||||||
|
|
|
||||||
Reference in a new issue