use anyhow::Result;
use chromiumoxide::{Page, cdp};
use futures::future::join_all;
use std::path::Path;
use tokio::fs;
use tracing::{debug, info, warn};
mod config;
use config::Config;
const EVASION_SCRIPTS: &[&str] = &[
"evasions/proxy_utils.js", "evasions/core_utils.js", "evasions/cdp_evasion.js", "evasions/navigator_webdriver.js", "evasions/navigator_vendor.js", "evasions/navigator_language.js", "evasions/navigator_plugins.js", "evasions/navigator_permissions.js", "evasions/hardware_concurrency.js", "evasions/user_agent_data.js", "evasions/media_codecs.js", "evasions/webgl_vendor_override.js", "evasions/font_spoof.js", "evasions/canvas_noise.js", "evasions/window_outerdimensions.js", "iframe_content_window.js", "evasions/chrome_app.js", "evasions/chrome_runtime.js", ];
pub async fn inject(page: Page) -> Result<()> {
let session_seed: Vec<u8> = (0..16).map(|_| rand::random::<u8>()).collect();
let session_seed_hex = hex::encode(&session_seed);
info!("Injecting stealth scripts");
let config = Config::default();
let kromekover_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("src")
.join("kromekover");
let grok_config = format!(
r#"
window.grokConfig = {{
acceptLanguage: "{}",
platform: "{}",
language: "{}",
languages: {},
screenWidth: {},
screenHeight: {},
webglVendor: "{}",
webglRenderer: "{}",
hardwareConcurrency: {},
sessionSeed: "{}"
}};
"#,
config.accept_language,
config.platform,
config.language,
serde_json::to_string(&config.languages).unwrap_or_else(|_| "[]".to_string()),
config.screen_width,
config.screen_height,
config.webgl_vendor,
config.webgl_renderer,
config.hardware_concurrency,
session_seed_hex,
);
info!("Injecting window.grokConfig");
page.execute(
cdp::browser_protocol::page::AddScriptToEvaluateOnNewDocumentParams {
source: grok_config,
include_command_line_api: None,
world_name: None,
run_immediately: None,
},
)
.await?;
info!("Loading {} evasion scripts", EVASION_SCRIPTS.len());
let read_futures: Vec<_> = EVASION_SCRIPTS
.iter()
.map(|script| {
let script_path = kromekover_dir.join(script);
let script_name = (*script).to_string();
async move {
let source = fs::read_to_string(&script_path).await;
(script_name, source)
}
})
.collect();
let read_results = join_all(read_futures).await;
let mut scripts = Vec::new();
let mut failed_reads = Vec::new();
for (script_name, result) in read_results {
match result {
Ok(source) => {
debug!("✓ Loaded: {}", script_name);
scripts.push((script_name, source));
}
Err(e) => {
warn!("✗ Failed to load {}: {}", script_name, e);
failed_reads.push(script_name);
}
}
}
info!(
"Loaded {}/{} scripts successfully",
scripts.len(),
EVASION_SCRIPTS.len()
);
if !failed_reads.is_empty() {
warn!(
"Failed to load {} scripts: {:?}",
failed_reads.len(),
failed_reads
);
}
info!("Injecting {} scripts", scripts.len());
let inject_futures: Vec<_> = scripts
.into_iter()
.map(|(script_name, source)| {
let page = page.clone();
async move {
let result = page
.execute(
cdp::browser_protocol::page::AddScriptToEvaluateOnNewDocumentParams {
source,
include_command_line_api: None,
world_name: None,
run_immediately: None,
},
)
.await;
(script_name, result)
}
})
.collect();
let inject_results = join_all(inject_futures).await;
let mut success_count = 0;
let mut failed_injections = Vec::new();
for (script_name, result) in inject_results {
match result {
Ok(_) => {
debug!("✓ Injected: {}", script_name);
success_count += 1;
}
Err(e) => {
warn!("✗ Failed to inject {}: {}", script_name, e);
failed_injections.push(script_name);
}
}
}
info!(
"Successfully injected {}/{} scripts",
success_count,
EVASION_SCRIPTS.len()
);
if !failed_injections.is_empty() {
warn!(
"Failed to inject {} scripts: {:?}",
failed_injections.len(),
failed_injections
);
}
if success_count == 0 {
return Err(anyhow::anyhow!(
"Failed to inject any stealth scripts. Total failures: {} load, {} inject",
failed_reads.len(),
failed_injections.len()
));
}
info!("Configuring user agent");
let ua = page
.execute(cdp::browser_protocol::browser::GetVersionParams {})
.await?;
let modified_ua = ua.user_agent.replace("Headless", "");
page.execute(cdp::browser_protocol::network::SetUserAgentOverrideParams {
user_agent: modified_ua,
accept_language: Some(config.accept_language.clone()),
platform: Some(config.platform.clone()),
user_agent_metadata: None,
})
.await?;
info!(
"Stealth injection complete: {}/{} scripts active",
success_count,
EVASION_SCRIPTS.len()
);
Ok(())
}