diff --git a/Cargo.toml b/Cargo.toml index 2151040..478be90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,13 @@ resolver = "2" version = "0.1.0" edition = "2021" authors = ["ShellWen "] + +[workspace.dependencies] +clap = { version = "4.5", features = ["derive"] } +tracing = "0.1" +tracing-subscriber = "0.3" + +[profile.release] +opt-level = "z" +lto = true +codegen-units = 1 diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index c05aafd..9c247d8 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -8,15 +8,17 @@ authors.workspace = true crate-type = ["cdylib"] [dependencies] -frida-gum = { version = "0.13.2", features = ["auto-download", "invocation-listener"] } -lazy_static = "1.4.0" -ctor = "0.2.4" -toml = "0.8.1" -serde = { version = "1.0.188", features = ["derive"] } -regex = "1.10.2" +ctor = "0.2" +frida-gum = { version = "0.13", features = ["auto-download", "invocation-listener"] } +once_cell = "1.19" +regex = "1.10" +serde = { version = "1.0", features = ["derive"] } +toml = "0.8" +tracing = { workspace = true } +tracing-subscriber = { workspace = true } [target.'cfg(target_os = "windows")'.dependencies] -windows = { version = "0.51.1", features = [ +windows = { version = "0.54", features = [ "Win32_Foundation", "Win32_System_Console", ] } diff --git a/crates/core/src/config.rs b/crates/core/src/config.rs index 86a5252..370d6c3 100644 --- a/crates/core/src/config.rs +++ b/crates/core/src/config.rs @@ -15,7 +15,7 @@ pub(crate) struct ConfigRule { pub(crate) processors: Vec, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Default, Debug)] pub(crate) struct Config { #[serde(default)] pub(crate) identifiers: Identifiers, diff --git a/crates/core/src/core.rs b/crates/core/src/core.rs index a2d237e..69e7508 100644 --- a/crates/core/src/core.rs +++ b/crates/core/src/core.rs @@ -1,5 +1,7 @@ use std::ops::Deref; +use tracing::*; + use crate::config::Config; use crate::matcher::SourceMatcher; use crate::source::Source; @@ -15,6 +17,7 @@ pub(crate) unsafe fn process_script( let isolate = v8_context_get_isolate(v8_context); let resource_name = string_from_local_string(isolate, (*v8_source)._resource_name); let source_string = string_from_local_string(isolate, (*v8_source)._source_string); + debug!("Processing source: {resource_name}"); let mut source = Source { resource_name, source_string, @@ -23,17 +26,14 @@ pub(crate) unsafe fn process_script( let (rule_name, rule) = rule_item; let is_match = &rule.matcher.deref().matches(&source); if *is_match { - println!( - "[*] Rule {} matched in {}", - rule_name, &source.resource_name - ); + info!("Rule {} matched in {}", rule_name, &source.resource_name); let processors = &rule.processors; processors.iter().for_each(|processor_item| { let processor = processor_item; let result = processor.process(&mut source); if result.is_err() { - println!( - "[!] Processor {:#?} process failed: {}", + error!( + "Processor {:#?} process failed: {}", processor, result.err().unwrap() ); diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 48b65f2..1047773 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -3,12 +3,16 @@ use std::path::Path; use ctor::ctor; use frida_gum::interceptor::{InvocationContext, InvocationListener}; use frida_gum::{interceptor::Interceptor, Gum}; -use lazy_static::lazy_static; +use once_cell::sync::Lazy; +use tracing::*; +use tracing_subscriber::fmt::time::uptime; -use crate::config::{Config, ReadFromFile}; use crate::core::process_script; -use crate::identifier::Identifier; use crate::v8_sys::{V8Context, V8Source}; +use crate::{ + config::{Config, ReadFromFile}, + identifier::Identifier, +}; mod config; mod core; @@ -18,11 +22,26 @@ mod processor; mod source; mod v8_sys; -lazy_static! { - static ref GUM: Gum = unsafe { Gum::obtain() }; -} +static GUM: Lazy = Lazy::new(|| unsafe { Gum::obtain() }); -static mut CONFIG: Option = None; +static CONFIG: Lazy = Lazy::new(|| { + let config_file_path = std::env::var("V8_KILLER_CONFIG_FILE_PATH"); + match config_file_path { + Ok(config_file_path) => { + info!("V8_KILLER_CONFIG_FILE_PATH: {config_file_path}"); + let path = Path::new(&config_file_path); + let config = Config::load_from_toml(path); + info!("Read Config success: {config:#?}"); + config + } + Err(_) => { + warn!("V8_KILLER_CONFIG_FILE_PATH not found"); + warn!("Please set V8_KILLER_CONFIG_FILE_PATH to config file path"); + warn!("V8 Killer will only tracing source code without config file"); + Default::default() + } + } +}); // v8::ScriptCompiler::CompileFunctionInternal(v8::Local, v8::ScriptCompiler::Source*, unsigned long, v8::Local*, unsigned long, v8::Local*, v8::ScriptCompiler::CompileOptions, v8::ScriptCompiler::NoCacheReason, v8::Local*) struct V8ScriptCompilerCompileFunctionInternalListener; @@ -38,8 +57,7 @@ impl InvocationListener for V8ScriptCompilerCompileFunctionInternalListener { let context = frida_context.arg(1) as *const V8Context; #[cfg(target_os = "windows")] let source = frida_context.arg(2) as *mut V8Source; - let config = CONFIG.as_ref().unwrap(); - process_script(config, context, source); + process_script(&CONFIG, context, source); } } @@ -48,6 +66,11 @@ impl InvocationListener for V8ScriptCompilerCompileFunctionInternalListener { #[ctor] fn init() { + tracing_subscriber::fmt() + .with_timer(uptime()) + .with_max_level(tracing::Level::DEBUG) + .init(); + // Fix no output in the Windows GUI subsystem programs // See also: [#11](https://github.com/ShellWen/v8_killer/issues/11) #[cfg(target_os = "windows")] @@ -57,51 +80,35 @@ fn init() { let _ = AttachConsole(ATTACH_PARENT_PROCESS); } - // 读取环境变量 - let config_file_path = std::env::var("V8_KILLER_CONFIG_FILE_PATH"); - match config_file_path { - Ok(config_file_path) => { - println!("[*] V8_KILLER_CONFIG_FILE_PATH: {}", config_file_path); - let path = Path::new(&config_file_path); - let config = Config::load_from_toml(path); - println!("[*] Read Config success"); - println!("[*] Config: {:?}", config); - unsafe { - CONFIG = Some(config); - } - let mut interceptor = Interceptor::obtain(&GUM); - - interceptor.begin_transaction(); - - let v8_script_compiler_compile_function_internal = unsafe { CONFIG.as_ref().unwrap() } - .identifiers - .V8_SCRIPT_COMPILER_COMPILE_FUNCTION_INTERNAL - .identify(); - - match v8_script_compiler_compile_function_internal { - None => { - println!("[-] v8_script_compiler_compile_function_internal not found") - } - Some(addr) => { - println!( - "[*] v8_script_compiler_compile_function_internal found: {:?}", - addr.0 - ); - let mut v8_script_compiler_compile_function_internal_listener = - V8ScriptCompilerCompileFunctionInternalListener; - interceptor.attach( - addr, - &mut v8_script_compiler_compile_function_internal_listener, - ); - } - } - - interceptor.end_transaction(); + info!("V8 Killer has been injected and started!"); + + let mut interceptor = Interceptor::obtain(&GUM); + + interceptor.begin_transaction(); + + let v8_script_compiler_compile_function_internal = CONFIG + .identifiers + .V8_SCRIPT_COMPILER_COMPILE_FUNCTION_INTERNAL + .identify(); + + match v8_script_compiler_compile_function_internal { + None => { + error!("v8_script_compiler_compile_function_internal not found"); + error!("source processing will not work properly"); } - Err(_) => { - println!("[-] WARN: V8_KILLER_CONFIG_FILE_PATH not found"); - println!("[-] WARN: Please set V8_KILLER_CONFIG_FILE_PATH to config file path"); - println!("[-] WARN: Without config file, V8 Killer will do nothing"); + Some(addr) => { + info!( + "v8_script_compiler_compile_function_internal found: {:?}", + addr.0 + ); + let mut v8_script_compiler_compile_function_internal_listener = + V8ScriptCompilerCompileFunctionInternalListener; + interceptor.attach( + addr, + &mut v8_script_compiler_compile_function_internal_listener, + ); } } + + interceptor.end_transaction(); } diff --git a/crates/core/src/v8_sys.rs b/crates/core/src/v8_sys.rs index 789b1a5..cb84d94 100644 --- a/crates/core/src/v8_sys.rs +++ b/crates/core/src/v8_sys.rs @@ -77,8 +77,6 @@ type v8__String__NewFromUtf8 = unsafe extern "C" fn( pub(crate) unsafe fn v8_context_get_isolate(context: *const V8Context) -> *const V8Isolate { let v8_context_get_isolate_ptr = CONFIG - .as_ref() - .unwrap() .identifiers .V8_CONTEXT_GET_ISOLATE .identify() @@ -93,13 +91,7 @@ pub(super) unsafe fn v8_string_utf8_length( this: *const V8String, isolate: *const V8Isolate, ) -> usize { - let v8_string_utf8_length_ptr = CONFIG - .as_ref() - .unwrap() - .identifiers - .V8_STRING_UTF8LENGTH - .identify() - .unwrap(); + let v8_string_utf8_length_ptr = CONFIG.identifiers.V8_STRING_UTF8LENGTH.identify().unwrap(); let v8_string_utf8_length_func: v8__String__Utf8Length = std::mem::transmute(v8_string_utf8_length_ptr.0); @@ -114,13 +106,7 @@ pub(crate) unsafe fn v8_string_write_utf8( nchars_ref: *mut usize, options: c_int, ) -> c_int { - let v8_string_write_utf8_ptr = CONFIG - .as_ref() - .unwrap() - .identifiers - .V8_STRING_WRITE_UTF8 - .identify() - .unwrap(); + let v8_string_write_utf8_ptr = CONFIG.identifiers.V8_STRING_WRITE_UTF8.identify().unwrap(); let v8_string_write_utf8_func: v8__String__WriteUtf8 = std::mem::transmute(v8_string_write_utf8_ptr.0); @@ -134,8 +120,6 @@ pub(crate) unsafe fn v8_string_new_from_utf8( length: i32, ) -> V8Local { let v8_string_new_from_utf8_ptr = CONFIG - .as_ref() - .unwrap() .identifiers .V8_STRING_NEW_FROM_UTF8 .identify() diff --git a/crates/launcher/Cargo.toml b/crates/launcher/Cargo.toml index 1886d0e..4d2050a 100644 --- a/crates/launcher/Cargo.toml +++ b/crates/launcher/Cargo.toml @@ -5,9 +5,12 @@ edition.workspace = true authors.workspace = true [dependencies] +clap = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } [target.'cfg(target_os = "windows")'.dependencies] -windows = { version = "0.51.1", features = [ +windows = { version = "0.54", features = [ "Win32_System_Threading", "Win32_System_Console", "Win32_System_Memory", diff --git a/crates/launcher/src/lib.rs b/crates/launcher/src/lib.rs index 4f46eec..ea8fe01 100644 --- a/crates/launcher/src/lib.rs +++ b/crates/launcher/src/lib.rs @@ -1,30 +1,25 @@ +use tracing::*; + use std::error::Error; +use std::ffi::OsStr; pub fn default_lib_filename<'a>() -> Result<&'a str, Box> { #[cfg(target_os = "linux")] - { - Ok("libv8_killer_core.so") - } + return Ok("libv8_killer_core.so"); #[cfg(target_os = "windows")] - { - Ok("v8_killer_core.dll") - } + return Ok("v8_killer_core.dll"); #[cfg(target_os = "macos")] - { - Ok("libv8_killer_core.dylib") - } + return Ok("libv8_killer_core.dylib"); // 默认情况,如果没有匹配的操作系统,则返回一个合适的默认值 - #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))] - { - Err("Unsupported platform".into()) - } + #[allow(unreachable_code)] + Err("Unsupported platform".into()) } #[cfg(target_os = "linux")] -pub fn launch(lib_path: &str, executable: &str, args: &[&str]) { +pub fn launch(lib_path: &str, executable: &str, args: &[impl AsRef]) { use std::process::Command; use std::process::ExitStatus; use std::process::Stdio; @@ -41,14 +36,14 @@ pub fn launch(lib_path: &str, executable: &str, args: &[&str]) { let status: ExitStatus = child.wait().expect("Failed to wait for child process"); if status.success() { - println!("Command executed successfully"); + info!("Command executed successfully"); } else { - println!("Command failed with exit code: {:?}", status.code()); + error!("Command failed with exit code: {:?}", status.code()); } } #[cfg(target_os = "windows")] -pub fn launch(lib_path: &str, executable: &str, args: &[&str]) { +pub fn launch(lib_path: &str, executable: &str, args: &[impl AsRef]) { use std::ffi::c_void; use windows::core::PWSTR; use windows::core::{s, w}; @@ -82,7 +77,7 @@ pub fn launch(lib_path: &str, executable: &str, args: &[&str]) { "\"{}\" {}", executable, args.iter() - .map(|arg| arg.to_string()) + .map(|arg| arg.as_ref().to_string_lossy().into_owned()) // .map(|arg| format!("\"{}\"", arg)) .collect::>() .join(" ") @@ -94,7 +89,7 @@ pub fn launch(lib_path: &str, executable: &str, args: &[&str]) { let path_utf16_zeroend_size = get_pwstr_length(path_pwstr) * 2 + 2; let mut process_info = PROCESS_INFORMATION::default(); - println!("[*] Creating process."); + info!("Creating process."); CreateProcessW( None, args_pwstr, @@ -108,8 +103,8 @@ pub fn launch(lib_path: &str, executable: &str, args: &[&str]) { &mut process_info, ) .expect("CreateProcessW calling failed"); - println!("[*] PID: {}", process_info.dwProcessId); - println!("[*] Alloc core lib path memory."); + info!("PID: {}", process_info.dwProcessId); + info!("Alloc core lib path memory."); let remote_memory = VirtualAllocEx( process_info.hProcess, None, @@ -118,8 +113,8 @@ pub fn launch(lib_path: &str, executable: &str, args: &[&str]) { PAGE_READWRITE, ); assert!(!remote_memory.is_null()); - println!("[*] Remote lib path memory Address: {:p}.", remote_memory); - println!("[*] Writing core lib path to process."); + info!("Remote lib path memory Address: {:p}.", remote_memory); + info!("Writing core lib path to process."); WriteProcessMemory( process_info.hProcess, remote_memory, @@ -132,12 +127,12 @@ pub fn launch(lib_path: &str, executable: &str, args: &[&str]) { // GetModuleHandleA("kernel32.dll\0".as_ptr() as _), // "LoadLibraryW\0".as_ptr() as _, // )}; - println!("[*] Getting LoadLibraryW address."); + info!("Getting LoadLibraryW address."); let kernel_handle = GetModuleHandleW(w!("kernel32.dll")).unwrap(); let load_library_address = (GetProcAddress(kernel_handle, s!("LoadLibraryW")).unwrap()) as *const c_void; assert!(!load_library_address.is_null()); - println!("[*] Creating remote thread."); + info!("Creating remote thread."); let load_remote_thread_handle = CreateRemoteThread( process_info.hProcess, None, @@ -151,17 +146,17 @@ pub fn launch(lib_path: &str, executable: &str, args: &[&str]) { None, ) .unwrap(); - println!("[*] Core lib inject success. Waiting for thread end."); + info!("Core lib inject success. Waiting for thread end."); WaitForSingleObject(load_remote_thread_handle, INFINITE); - println!("[*] Thread ended. Resume original thread."); - println!("[*] --- Following is the original process output ---"); + info!("Thread ended. Resume original thread."); + info!("--- Following is the original process output ---"); ResumeThread(process_info.hThread); WaitForSingleObject(process_info.hProcess, INFINITE); } } #[cfg(target_os = "macos")] -pub fn launch(lib_path: &str, executable: &str, args: &[&str]) { +pub fn launch(lib_path: &str, executable: &str, args: &[impl AsRef]) { use std::process::Command; use std::process::ExitStatus; use std::process::Stdio; @@ -178,14 +173,14 @@ pub fn launch(lib_path: &str, executable: &str, args: &[&str]) { let status: ExitStatus = child.wait().expect("Failed to wait for child process"); if status.success() { - println!("Command executed successfully"); + info!("Command executed successfully"); } else { - println!("Command failed with exit code: {:?}", status.code()); + error!("Command failed with exit code: {:?}", status.code()); } } // 非以上系统 #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))] pub fn launch(lib_path: &str, executable: &str, args: &[&str]) { - eprintln!("Unsupported platform."); + error!("Unsupported platform."); } diff --git a/crates/launcher/src/main.rs b/crates/launcher/src/main.rs index 48bba72..b5baef9 100644 --- a/crates/launcher/src/main.rs +++ b/crates/launcher/src/main.rs @@ -1,20 +1,48 @@ +use clap::*; +use tracing::*; +use tracing_subscriber::fmt::time::uptime; + use std::env::current_exe; use v8_killer_launcher::{default_lib_filename, launch}; +/// A simple launcher/injector for V8 Killer +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +struct Arguments { + /// Custom dynamic library to inject + #[arg(long)] + lib_name: Option, + /// Custom configuration file, will pass to the executable by environment variable `V8_KILLER_CONFIG_FILE_PATH` + #[arg(long)] + config: Option, + /// The executable to launch and inject dynamic library + executable: String, + /// The arguments for the executable + #[arg(last = true)] + arguments: Vec, +} + fn main() { - // TODO: Support custom lib_filename - let lib_filename = default_lib_filename().unwrap(); - let mut lib_path = current_exe().unwrap().parent().unwrap().to_path_buf(); - lib_path.push(lib_filename); + tracing_subscriber::fmt().with_timer(uptime()).init(); + let args = Arguments::parse(); + if let Some(config) = &args.config { + std::env::set_var("V8_KILLER_CONFIG_FILE_PATH", config); + } + let lib_filename = if let Some(lib_name) = args.lib_name { + lib_name + } else if let Ok(lib_name) = default_lib_filename() { + lib_name.to_owned() + } else { + error!("Can't get default dynamic library name, your platform may not be supported."); + error!("You can try to specify the dynamic library manually by setting the `--lib-name` argument."); + std::process::exit(1) + }; + let lib_path = current_exe().unwrap().parent().unwrap().join(lib_filename); let lib_path_str = lib_path.to_str().unwrap(); - let exe = std::env::args().nth(1).expect("no executable provided"); - let args = std::env::args().skip(2).collect::>(); - let args = args.iter().map(|s| s.as_str()).collect::>(); - - println!("[*] Executable: {}", exe); - println!("[*] Args: {:?}", args); - println!("[*] Core lib path: {}", lib_path_str); - launch(lib_path_str, &exe, &args); + info!("Executable: {}", args.executable); + info!("Args: {:?}", args.arguments); + info!("Core lib path: {}", lib_path_str); + launch(lib_path_str, &args.executable, args.arguments.as_slice()); }