From a124a26d361ad35b35df54edc889bbf0dcef4933 Mon Sep 17 00:00:00 2001 From: techmetx11 Date: Mon, 29 Apr 2024 19:14:08 +0100 Subject: [PATCH] Improve the program a bit, and include a protocol documentation --- README.md | 46 ++++++++++++++++++++++++++++++++++++++++++ src/consts.rs | 6 ++++++ src/jobs.rs | 56 ++++++++++++++++++++++++++++++++++++++++++--------- src/main.rs | 3 ++- 4 files changed, 101 insertions(+), 10 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..b766005 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# Protocol Format + +All data-types bigger than 1 byte are stored in network endian (big-endian) unless stated otherwise. + +## Request Base +| Name | Size (bytes) | Description | +|-----------|--------------|--------------------------------------| +|opcode | 1 | The operation code to perform, A list of operations currently supported (and their data) can be found in the **Operations** chapter | +|request_id | 4 | The ID for the current request, Used to distinguish responses in the current connection | + +The data afterwards depends on the supplied opcode, Please consult the **Operations** chapter for more information. + +## Response Base +| Name | Size (bytes) | Description | +|------------|--------------|---------------------------------------| +|request_id | 4 | The ID for the request that this response is meant for | + +The data afterwards depends on the supplied opcode, Please consult the **Operations** chapter for more information. + +## Operations +### `FORCE_UPDATE` (0x00) +Forces the server to re-fetch the YouTube player, and extract the necessary components from it (`nsig` function code, `sig` function code, signature timestamp). + +#### Request +*No additional data required* + +#### Response +| Name | Size (bytes) | Description | +|------|--------------|-------------| +|status| 4 | The status code of the request: `0xF44F` if successful, `0xFFFF` if no updating is required (YouTube's player ID is equal to the server's current player ID), `0x0000` if an error occurred | + +### `DECRYPT_N_SIGNATURE` (0x01) +Decrypt a provided `n` signature using the server's current `nsig` function code, and return the result (or an error). + +#### Request +| Name | Size (bytes) | Description | +|------|--------------|-------------------------------------| +|size | 2 | The size of the encrypted signature | +|string| *`size`* | The encrypted signature | + +#### Response +| Name | Size (bytes) | Description | +|------|--------------|------------------------------------------------------------------| +|size | 2 | The size of the decrypted signature, `0x0000` if an error occurred | +|string| *`size`* | The decrypted signature | + diff --git a/src/consts.rs b/src/consts.rs index 42febad..69b8f10 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -8,5 +8,11 @@ pub static REGEX_PLAYER_ID: &Lazy = regex!("\\/s\\/player\\/([0-9a-f]{8}) pub static NSIG_FUNCTION_ARRAY: &Lazy = regex!( "\\.get\\(\"n\"\\)\\)&&\\([a-zA-Z0-9$_]=([a-zA-Z0-9$_]+)(?:\\[(\\d+)])?\\([a-zA-Z0-9$_]\\)" ); +pub static REGEX_SIGNATURE_TIMESTAMP: &Lazy = regex!("signatureTimestamp[=:](\\d+)"); + +pub static REGEX_SIGNATURE_FUNCTION: &Lazy = + regex!("\\bc&&\\(c=([a-zA-Z0-9$]{2,})\\(decodeURIComponent\\(c\\)\\)"); +pub static REGEX_HELPER_OBJ_NAME: &Lazy = regex!(";([A-Za-z0-9_\\$]{2,})\\...\\("); pub static NSIG_FUNCTION_NAME: &str = "decrypt_nsig"; +pub static SIG_FUNCTION_NAME: &str = "decrypt_sig"; diff --git a/src/jobs.rs b/src/jobs.rs index 9380889..1fb2474 100644 --- a/src/jobs.rs +++ b/src/jobs.rs @@ -15,6 +15,8 @@ use crate::consts::{NSIG_FUNCTION_ARRAY, NSIG_FUNCTION_NAME, REGEX_PLAYER_ID, TE pub enum JobOpcode { ForceUpdate, DecryptNSignature, + DecryptSignature, + GetSignatureTimestamp, UnknownOpcode, } @@ -23,6 +25,8 @@ impl std::fmt::Display for JobOpcode { match self { Self::ForceUpdate => write!(f, "ForceUpdate"), Self::DecryptNSignature => write!(f, "DecryptNSignature"), + Self::DecryptSignature => write!(f, "DecryptSignature"), + Self::GetSignatureTimestamp => write!(f, "GetSignatureTimestamp"), Self::UnknownOpcode => write!(f, "UnknownOpcode"), } } @@ -32,9 +36,8 @@ impl From for JobOpcode { match value { 0x00 => Self::ForceUpdate, 0x01 => Self::DecryptNSignature, - - // make debugging easier - b'a' => Self::ForceUpdate, + 0x02 => Self::DecryptSignature, + 0x03 => Self::GetSignatureTimestamp, _ => Self::UnknownOpcode, } } @@ -94,19 +97,40 @@ impl GlobalState { } } } -pub async fn process_fetch_update(state: Arc) { + +macro_rules! write_failure { + ($s:ident, $r:ident) => { + $s.write_u32($r).await; + $s.write_u16(0x0000).await; + }; +} + +pub async fn process_fetch_update( + state: Arc, + stream: Arc>, + request_id: u32, +) where + W: tokio::io::AsyncWrite + Unpin + Send, +{ + let cloned_writer = stream.clone(); + let mut writer = cloned_writer.lock().await; + let global_state = state.clone(); let response = match reqwest::get(TEST_YOUTUBE_VIDEO).await { Ok(req) => req.text().await.unwrap(), Err(x) => { println!("Could not fetch the test video: {}", x); + write_failure!(writer, request_id); return; } }; let player_id_str = match REGEX_PLAYER_ID.captures(&response).unwrap().get(1) { Some(result) => result.as_str(), - None => return, + None => { + write_failure!(writer, request_id); + return; + } }; let player_id: u32 = u32::from_str_radix(player_id_str, 16).unwrap(); @@ -118,6 +142,8 @@ pub async fn process_fetch_update(state: Arc) { if player_id == current_player_id { // Player is already up to date + writer.write_u32(request_id).await; + writer.write_u16(0xFFFF).await; return; } @@ -130,6 +156,7 @@ pub async fn process_fetch_update(state: Arc) { Ok(req) => req.text().await.unwrap(), Err(x) => { println!("Could not fetch the player JS: {}", x); + write_failure!(writer, request_id); return; } }; @@ -152,6 +179,7 @@ pub async fn process_fetch_update(state: Arc) { Ok(x) => x, Err(x) => { println!("Error: nsig regex compilation failed: {}", x); + write_failure!(writer, request_id); return; } }; @@ -186,10 +214,19 @@ pub async fn process_fetch_update(state: Arc) { .unwrap() .as_str(); + // Extract signature function name + current_player_info = global_state.player_info.lock().await; current_player_info.player_id = player_id; current_player_info.nsig_function_code = nsig_function_code; - println!("Successfully updated the player") + + writer.write_u32(request_id).await; + // sync code to tell the client the player had updated + writer.write_u16(0xF44F).await; + + writer.flush().await; + + println!("Successfully updated the player"); } pub async fn process_decrypt_n_signature( @@ -200,6 +237,8 @@ pub async fn process_decrypt_n_signature( ) where W: tokio::io::AsyncWrite + Unpin + Send, { + let cloned_writer = stream.clone(); + let mut writer = cloned_writer.lock().await; let global_state = state.clone(); println!("Signature to be decrypted: {}", sig); @@ -219,6 +258,7 @@ pub async fn process_decrypt_n_signature( } else { println!("JavaScript interpreter error (nsig code): {}", n); } + write_failure!(writer, request_id); return; } } @@ -240,13 +280,11 @@ pub async fn process_decrypt_n_signature( } else { println!("JavaScript interpreter error (nsig code): {}", n); } + write_failure!(writer, request_id); return; } }; - let cloned_writer = stream.clone(); - let mut writer = cloned_writer.lock().await; - writer.write_u32(request_id).await; writer.write_u16(u16::try_from(decrypted_string.len()).unwrap()).await; writer.write_all(decrypted_string.as_bytes()).await; diff --git a/src/main.rs b/src/main.rs index 8ddc5b9..f519e40 100644 --- a/src/main.rs +++ b/src/main.rs @@ -86,8 +86,9 @@ async fn process_socket(state: Arc, socket: UnixStream) -> Result<( match opcode { JobOpcode::ForceUpdate => { let cloned_state = state.clone(); + let cloned_stream = cloned_writestream.clone(); tokio::spawn(async move { - process_fetch_update(cloned_state).await; + process_fetch_update(cloned_state, cloned_stream, request_id).await; }); } JobOpcode::DecryptNSignature => {