working deobfuscator
This commit is contained in:
parent
0acf8a9fb9
commit
7447d2394b
2 changed files with 134 additions and 36 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "rusty-pipe"
|
name = "rusty-tube"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,79 @@
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
use fancy_regex::Regex;
|
use fancy_regex::Regex;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use quick_js::Context;
|
use reqwest::Client;
|
||||||
use std::result::Result::Ok;
|
use std::result::Result::Ok;
|
||||||
|
use std::time::Instant;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
pub struct Deobfuscator {
|
pub struct Deobfuscator {
|
||||||
// js_url: String,
|
client: Client,
|
||||||
// last_update
|
cache: RwLock<JSCache>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct JSCache {
|
||||||
|
js_url: Option<String>,
|
||||||
|
last_update: Instant,
|
||||||
sig_fn: String,
|
sig_fn: String,
|
||||||
nsig_fn: String,
|
nsig_fn: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deobfuscator {
|
impl Deobfuscator {
|
||||||
pub fn from_js(player_js: &str) -> Result<Self> {
|
pub fn new(client: Client) -> Self {
|
||||||
Ok(Self {
|
Self {
|
||||||
sig_fn: get_sig_fn(player_js)?,
|
client: client,
|
||||||
nsig_fn: get_nsig_fn(player_js)?,
|
cache: RwLock::new(JSCache {
|
||||||
})
|
js_url: None,
|
||||||
|
last_update: Instant::now(),
|
||||||
|
sig_fn: "".to_owned(),
|
||||||
|
nsig_fn: "".to_owned(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deobfuscate_sig(&self, sig: &str) -> Result<String> {
|
async fn update(&self) -> Result<()> {
|
||||||
deobfuscate_sig(sig, &self.sig_fn)
|
let mut cache = self.cache.write().await;
|
||||||
|
|
||||||
|
if cache.is_stale() {
|
||||||
|
let url = get_player_js_url(&self.client)
|
||||||
|
.await
|
||||||
|
.context("Failed to retrieve player.js URL")?;
|
||||||
|
|
||||||
|
let player_js = get_website(&self.client, &url)
|
||||||
|
.await
|
||||||
|
.context("Failed to download player.js")?;
|
||||||
|
|
||||||
|
println!("Downloaded player.js from {}", url);
|
||||||
|
|
||||||
|
let sig_fn = get_sig_fn(&player_js)?;
|
||||||
|
let nsig_fn = get_nsig_fn(&player_js)?;
|
||||||
|
|
||||||
|
*cache = JSCache {
|
||||||
|
js_url: Some(url.to_owned()),
|
||||||
|
last_update: Instant::now(),
|
||||||
|
sig_fn,
|
||||||
|
nsig_fn,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deobfuscate_nsig(&self, nsig: &str) -> Result<String> {
|
pub async fn deobfuscate_sig(&self, sig: &str) -> Result<String> {
|
||||||
deobfuscate_nsig(nsig, &self.nsig_fn)
|
self.update().await?;
|
||||||
|
let cache = self.cache.read().await;
|
||||||
|
deobfuscate_sig(sig, &cache.sig_fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn deobfuscate_nsig(&self, nsig: &str) -> Result<String> {
|
||||||
|
self.update().await?;
|
||||||
|
let cache = self.cache.read().await;
|
||||||
|
deobfuscate_nsig(nsig, &cache.nsig_fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JSCache {
|
||||||
|
fn is_stale(&self) -> bool {
|
||||||
|
self.js_url.is_none() || Instant::now().duration_since(self.last_update).as_secs() > 3600
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,26 +94,25 @@ fn get_sig_fn_name(player_js: &str) -> Result<String> {
|
||||||
FUNCTION_PATTERNS
|
FUNCTION_PATTERNS
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|pattern| pattern.captures(player_js).ok().flatten())
|
.find_map(|pattern| pattern.captures(player_js).ok().flatten())
|
||||||
.map(|c| c.get(1).unwrap().as_str().to_string())
|
.map(|c| c.get(1).unwrap().as_str().to_owned())
|
||||||
.ok_or_else(|| anyhow!("could not find deobf function name"))
|
.ok_or_else(|| anyhow!("could not find deobf function name"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn caller_function(fn_name: &str) -> String {
|
fn caller_function(fn_name: &str) -> String {
|
||||||
"function ".to_string() + DEOBFUSCATION_FUNC_NAME + "(a){return " + &fn_name + "(a);}"
|
"function ".to_owned() + DEOBFUSCATION_FUNC_NAME + "(a){return " + &fn_name + "(a);}"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sig_fn(player_js: &str) -> Result<String> {
|
fn get_sig_fn(player_js: &str) -> Result<String> {
|
||||||
let dfunc_name = get_sig_fn_name(player_js)?;
|
let dfunc_name = get_sig_fn_name(player_js)?;
|
||||||
|
|
||||||
let function_pattern_str = "(".to_string()
|
let function_pattern_str =
|
||||||
+ &dfunc_name.replace('$', "\\$")
|
"(".to_owned() + &dfunc_name.replace('$', "\\$") + "=function\\([a-zA-Z0-9_]+\\)\\{.+?\\})";
|
||||||
+ "=function\\([a-zA-Z0-9_]+\\)\\{.+?\\})";
|
|
||||||
let function_pattern = ok_or_bail!(
|
let function_pattern = ok_or_bail!(
|
||||||
Regex::new(&function_pattern_str),
|
Regex::new(&function_pattern_str),
|
||||||
Err(anyhow!("could not parse function pattern regex"))
|
Err(anyhow!("could not parse function pattern regex"))
|
||||||
);
|
);
|
||||||
|
|
||||||
let deobfuscate_function = "var ".to_string()
|
let deobfuscate_function = "var ".to_owned()
|
||||||
+ some_or_bail!(
|
+ some_or_bail!(
|
||||||
function_pattern.captures(player_js).ok().flatten(),
|
function_pattern.captures(player_js).ok().flatten(),
|
||||||
Err(anyhow!("could not find deobf function"))
|
Err(anyhow!("could not find deobf function"))
|
||||||
|
|
@ -87,7 +135,7 @@ fn get_sig_fn(player_js: &str) -> Result<String> {
|
||||||
.as_str();
|
.as_str();
|
||||||
|
|
||||||
let helper_pattern_str =
|
let helper_pattern_str =
|
||||||
"(var ".to_string() + &helper_object_name.replace('$', "\\$") + "=\\{.+?\\}\\};)";
|
"(var ".to_owned() + &helper_object_name.replace('$', "\\$") + "=\\{.+?\\}\\};)";
|
||||||
let helper_pattern = ok_or_bail!(
|
let helper_pattern = ok_or_bail!(
|
||||||
Regex::new(&helper_pattern_str),
|
Regex::new(&helper_pattern_str),
|
||||||
Err(anyhow!("could not parse helper pattern regex"))
|
Err(anyhow!("could not parse helper pattern regex"))
|
||||||
|
|
@ -101,16 +149,16 @@ fn get_sig_fn(player_js: &str) -> Result<String> {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_str();
|
.as_str();
|
||||||
|
|
||||||
Ok(helper_object.to_string() + &deobfuscate_function + &caller_function(&dfunc_name))
|
Ok(helper_object.to_owned() + &deobfuscate_function + &caller_function(&dfunc_name))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deobfuscate_sig(sig: &str, sig_fn: &str) -> Result<String> {
|
fn deobfuscate_sig(sig: &str, sig_fn: &str) -> Result<String> {
|
||||||
let context = Context::new()?;
|
let context = quick_js::Context::new()?;
|
||||||
context.eval(sig_fn)?;
|
context.eval(sig_fn)?;
|
||||||
let res = context.call_function(DEOBFUSCATION_FUNC_NAME, vec![sig])?;
|
let res = context.call_function(DEOBFUSCATION_FUNC_NAME, vec![sig])?;
|
||||||
|
|
||||||
match res.as_str() {
|
match res.as_str() {
|
||||||
Some(res) => Ok(res.to_string()),
|
Some(res) => Ok(res.to_owned()),
|
||||||
None => bail!("deobfuscation func returned null"),
|
None => bail!("deobfuscation func returned null"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -128,12 +176,12 @@ fn get_nsig_fn_name(player_js: &str) -> Result<String> {
|
||||||
let function_name = fname_match.get(1).unwrap().as_str();
|
let function_name = fname_match.get(1).unwrap().as_str();
|
||||||
|
|
||||||
if fname_match.len() == 1 {
|
if fname_match.len() == 1 {
|
||||||
return Ok(function_name.to_string());
|
return Ok(function_name.to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
let array_num = fname_match.get(2).unwrap().as_str().parse::<u32>()?;
|
let array_num = fname_match.get(2).unwrap().as_str().parse::<u32>()?;
|
||||||
let array_pattern_str =
|
let array_pattern_str =
|
||||||
"var ".to_string() + &fancy_regex::escape(function_name) + "\\s*=\\s*\\[(.+?)];";
|
"var ".to_owned() + &fancy_regex::escape(function_name) + "\\s*=\\s*\\[(.+?)];";
|
||||||
let array_pattern = Regex::new(&array_pattern_str)?;
|
let array_pattern = Regex::new(&array_pattern_str)?;
|
||||||
let array_str = some_or_bail!(
|
let array_str = some_or_bail!(
|
||||||
array_pattern.captures(player_js).ok().flatten(),
|
array_pattern.captures(player_js).ok().flatten(),
|
||||||
|
|
@ -151,7 +199,7 @@ fn get_nsig_fn_name(player_js: &str) -> Result<String> {
|
||||||
array_str
|
array_str
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
Ok(name.to_string())
|
Ok(name.to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn match_to_closing_parenthesis(string: &str, start: &str) -> Option<String> {
|
fn match_to_closing_parenthesis(string: &str, start: &str) -> Option<String> {
|
||||||
|
|
@ -187,14 +235,14 @@ fn get_nsig_fn(player_js: &str) -> Result<String> {
|
||||||
let function_name = get_nsig_fn_name(player_js)?;
|
let function_name = get_nsig_fn_name(player_js)?;
|
||||||
|
|
||||||
// Find using parentheses
|
// Find using parentheses
|
||||||
let function_base = function_name.to_string() + "=function";
|
let function_base = function_name.to_owned() + "=function";
|
||||||
let nsig_fn_code = match match_to_closing_parenthesis(player_js, &function_base) {
|
let nsig_fn_code = match match_to_closing_parenthesis(player_js, &function_base) {
|
||||||
Some(m) => function_base.clone() + &m + ";",
|
Some(m) => function_base.clone() + &m + ";",
|
||||||
None => {
|
None => {
|
||||||
// Find using regex
|
// Find using regex
|
||||||
let player_js_nonl = player_js.replace('\n', "");
|
let player_js_nonl = player_js.replace('\n', "");
|
||||||
|
|
||||||
let function_pattern_str = function_name.to_string() + "=function(.*?}};)\n";
|
let function_pattern_str = function_name.to_owned() + "=function(.*?}};)\n";
|
||||||
let function_pattern = Regex::new(&function_pattern_str)?;
|
let function_pattern = Regex::new(&function_pattern_str)?;
|
||||||
let function = some_or_bail!(
|
let function = some_or_bail!(
|
||||||
function_pattern.captures(&player_js_nonl)?,
|
function_pattern.captures(&player_js_nonl)?,
|
||||||
|
|
@ -204,7 +252,7 @@ fn get_nsig_fn(player_js: &str) -> Result<String> {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_str();
|
.as_str();
|
||||||
|
|
||||||
"function ".to_string() + function
|
"function ".to_owned() + function
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -217,22 +265,45 @@ fn deobfuscate_nsig(sig: &str, nsig_fn: &str) -> Result<String> {
|
||||||
let res = context.call_function(DEOBFUSCATION_FUNC_NAME, vec![sig])?;
|
let res = context.call_function(DEOBFUSCATION_FUNC_NAME, vec![sig])?;
|
||||||
|
|
||||||
match res.as_str() {
|
match res.as_str() {
|
||||||
Some(res) => Ok(res.to_string()),
|
Some(res) => Ok(res.to_owned()),
|
||||||
None => bail!("deobfuscation func returned null"),
|
None => bail!("deobfuscation func returned null"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_player_js_url() -> Result<String> {
|
async fn get_player_js_url(client: &Client) -> Result<String> {
|
||||||
let resp = reqwest::get("https://www.youtube.com/iframe_api").await?;
|
let resp = client
|
||||||
|
.get("https://www.youtube.com/iframe_api")
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.error_for_status()?;
|
||||||
let text = resp.text().await?;
|
let text = resp.text().await?;
|
||||||
println!("{}", text);
|
|
||||||
|
|
||||||
Ok("x".to_string())
|
let player_hash_pattern =
|
||||||
|
Regex::new(r#"https:\\\/\\\/www\.youtube\.com\\\/s\\\/player\\\/([a-z0-9]{8})\\\/"#)
|
||||||
|
.unwrap();
|
||||||
|
let player_hash = some_or_bail!(
|
||||||
|
player_hash_pattern.captures(&text)?,
|
||||||
|
Err(anyhow!("could not find player hash"))
|
||||||
|
)
|
||||||
|
.get(1)
|
||||||
|
.unwrap()
|
||||||
|
.as_str();
|
||||||
|
|
||||||
|
Ok(format!(
|
||||||
|
"https://www.youtube.com/s/player/{}/player_ias.vflset/en_US/base.js",
|
||||||
|
player_hash
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_website(client: &Client, url: &str) -> Result<String> {
|
||||||
|
let resp = client.get(url).send().await?.error_for_status()?;
|
||||||
|
Ok(resp.text().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
const TEST_JS: &str = include_str!("../notes/base.js");
|
const TEST_JS: &str = include_str!("../notes/base.js");
|
||||||
const N_DEOBF_FUNC: &str = r#"Vo=function(a){var b=a.split(""),c=[function(d,e,f){var h=f.length;d.forEach(function(l,m,n){this.push(n[m]=f[(f.indexOf(l)-f.indexOf(this[m])+m+h--)%f.length])},e.split(""))},
|
const N_DEOBF_FUNC: &str = r#"Vo=function(a){var b=a.split(""),c=[function(d,e,f){var h=f.length;d.forEach(function(l,m,n){this.push(n[m]=f[(f.indexOf(l)-f.indexOf(this[m])+m+h--)%f.length])},e.split(""))},
|
||||||
|
|
@ -298,6 +369,33 @@ c[36](c[8],c[32]),c[20](c[25],c[10]),c[2](c[22],c[8]),c[32](c[20],c[16]),c[32](c
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_player_js_url() {
|
async fn test_get_player_js_url() {
|
||||||
let x = get_player_js_url().await.unwrap();
|
let client = Client::new();
|
||||||
|
let url = get_player_js_url(&client).await.unwrap();
|
||||||
|
assert!(url.starts_with("https://www.youtube.com/s/player"));
|
||||||
|
assert_eq!(url.len(), 73);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_update() {
|
||||||
|
let client = Client::new();
|
||||||
|
let deobf = Deobfuscator::new(client);
|
||||||
|
|
||||||
|
let deobf_sig = deobf.deobfuscate_sig("GOqGOqGOq0QJ8wRAIgaryQHfplJ9xJSKFywyaSMHuuwZYsoMTAvRvfm51qIGECIA5061zWeyfMPX9hEl_U6f9J0tr7GTJMKyPf5XNrJb5fb5i").await.unwrap();
|
||||||
|
println!("{}", deobf_sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_parallel() {
|
||||||
|
let client = Client::new();
|
||||||
|
let deobf = Deobfuscator::new(client);
|
||||||
|
let deobf_arc = Arc::new(deobf);
|
||||||
|
|
||||||
|
let (deobf_sig, deobf_nsig) = tokio::join!(
|
||||||
|
deobf_arc.deobfuscate_sig("GOqGOqGOq0QJ8wRAIgaryQHfplJ9xJSKFywyaSMHuuwZYsoMTAvRvfm51qIGECIA5061zWeyfMPX9hEl_U6f9J0tr7GTJMKyPf5XNrJb5fb5i"),
|
||||||
|
deobf_arc.deobfuscate_nsig("BI_n4PxQ22is-KKajKUW"),
|
||||||
|
);
|
||||||
|
|
||||||
|
println!("{}", deobf_sig.unwrap());
|
||||||
|
println!("{}", deobf_nsig.unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Reference in a new issue