diff --git a/proxychains-bin/src/main.rs b/proxychains-bin/src/main.rs index d2d195c..279a63a 100644 --- a/proxychains-bin/src/main.rs +++ b/proxychains-bin/src/main.rs @@ -66,6 +66,10 @@ struct Args { #[arg(long)] probe_json: bool, + /// In text probe output, print only failed nodes + #[arg(long)] + probe_fail_only: bool, + /// The command to run #[arg( required_unless_present_any = ["list_groups", "check", "probe"], @@ -265,7 +269,7 @@ fn run_probe(config: &Config, args: &Args) -> usize { serde_json::to_string_pretty(&report).unwrap_or_else(|_| "{}".to_string()) ); } else { - print_probe_report(&report); + print_probe_report(&report, args.probe_fail_only); } failed } @@ -345,11 +349,14 @@ fn format_unix_ts_iso8601(ts: u64) -> String { .unwrap_or_else(|| "1970-01-01T00:00:00Z".to_string()) } -fn print_probe_report(report: &ProbeReport) { +fn print_probe_report(report: &ProbeReport, fail_only: bool) { println!("Proxy probe:"); println!(" timeout_ms={}", report.timeout_ms); println!(" group={}", report.selected_group); for n in &report.nodes { + if fail_only && n.ok { + continue; + } if n.ok { println!( " [{}] OK {:<8} {} {} ms", @@ -597,19 +604,9 @@ fn execute_command(args: &Args, config: &Config) -> Result { debug!("Spawning and injecting: {:?}", process_info); - // Spawn the process and inject DLL - let mut child = injector - .spawn_and_inject(&process_info) - .map_err(|e| format!("Failed to spawn and inject: {}", e))?; - - // Wait for the process to complete - let status = child - .wait() - .map_err(|e| format!("Failed to wait for process: {}", e))?; - - let exit_code = status - .code() - .unwrap_or(1); + let exit_code = injector + .spawn_inject_wait(&process_info) + .map_err(|e| format!("Failed to spawn/inject/wait: {}", e))?; info!("Process exited with code: {}", exit_code); @@ -685,4 +682,12 @@ mod tests { let args = args.unwrap(); assert!(args.probe_json); } + + #[test] + fn test_args_probe_fail_only() { + let args = Args::try_parse_from(["proxychains4", "--probe", "--probe-fail-only"]); + assert!(args.is_ok()); + let args = args.unwrap(); + assert!(args.probe_fail_only); + } } diff --git a/proxychains-injector/src/injector.rs b/proxychains-injector/src/injector.rs index 6d19ea7..52555d7 100644 --- a/proxychains-injector/src/injector.rs +++ b/proxychains-injector/src/injector.rs @@ -133,6 +133,94 @@ impl ProxychainsInjector { Ok(child) } + /// Create a suspended process, inject DLL, resume, and wait for completion (Windows). + #[cfg(windows)] + pub fn spawn_inject_wait(&self, process_info: &ProcessInfo) -> Result { + use std::os::windows::ffi::OsStrExt; + use windows::core::{PCWSTR, PWSTR}; + use windows::Win32::Foundation::{CloseHandle, HANDLE}; + use windows::Win32::System::Threading::{ + CreateProcessW, GetExitCodeProcess, ResumeThread, WaitForSingleObject, + CREATE_SUSPENDED, INFINITE, PROCESS_INFORMATION, STARTUPINFOW, + }; + + fn quote_arg(arg: &str) -> String { + if arg.is_empty() + || arg.contains(' ') + || arg.contains('\t') + || arg.contains('"') + || arg.contains('\\') + { + let escaped = arg.replace('\\', "\\\\").replace('"', "\\\""); + format!("\"{}\"", escaped) + } else { + arg.to_string() + } + } + + let mut command_line = quote_arg(&process_info.command); + for arg in &process_info.args { + command_line.push(' '); + command_line.push_str("e_arg(arg)); + } + + debug!("Spawning suspended process: {}", command_line); + + let mut cmd_wide: Vec = std::ffi::OsStr::new(&command_line) + .encode_wide() + .chain(std::iter::once(0)) + .collect(); + + let mut startup_info = STARTUPINFOW::default(); + startup_info.cb = std::mem::size_of::() as u32; + let mut process_info_win = PROCESS_INFORMATION::default(); + + unsafe { + CreateProcessW( + PCWSTR::null(), + PWSTR(cmd_wide.as_mut_ptr()), + None, + None, + false, + CREATE_SUSPENDED, + None, + PCWSTR::null(), + &startup_info, + &mut process_info_win, + ) + .map_err(|e| InjectorError::ProcessCreationFailed(format!("{:?}", e)))?; + + let process_handle: HANDLE = process_info_win.hProcess; + let thread_handle: HANDLE = process_info_win.hThread; + let pid = process_info_win.dwProcessId; + debug!("Suspended process created with PID: {}", pid); + + if let Err(e) = self.inject_dll(process_handle) { + let _ = CloseHandle(thread_handle); + let _ = CloseHandle(process_handle); + return Err(e); + } + info!("Successfully injected DLL into suspended process {}", pid); + + let resume_ret = ResumeThread(thread_handle); + if resume_ret == u32::MAX { + let _ = CloseHandle(thread_handle); + let _ = CloseHandle(process_handle); + return Err(InjectorError::WindowsApi("ResumeThread failed".into())); + } + + let _ = WaitForSingleObject(process_handle, INFINITE); + let mut exit_code = 1u32; + GetExitCodeProcess(process_handle, &mut exit_code) + .map_err(|e| InjectorError::WindowsApi(format!("GetExitCodeProcess failed: {:?}", e)))?; + + let _ = CloseHandle(thread_handle); + let _ = CloseHandle(process_handle); + + Ok(exit_code as i32) + } + } + /// Internal DLL injection method (Windows) #[cfg(windows)] unsafe fn inject_dll(&self, process: windows::Win32::Foundation::HANDLE) -> Result<()> { diff --git a/proxychains/src/hook/hooks_windows.rs b/proxychains/src/hook/hooks_windows.rs index bef5581..774d6f8 100644 --- a/proxychains/src/hook/hooks_windows.rs +++ b/proxychains/src/hook/hooks_windows.rs @@ -331,6 +331,7 @@ pub unsafe extern "system" fn hook_connect_impl( None => return original_connect(sock, addr, len), }; let target_port = get_port_from_sockaddr(addr); + debug!("hook_connect_impl intercepted target {}:{}", target_ip, target_port); if config.should_bypass_ip(&target_ip) { return original_connect(sock, addr, len); @@ -414,6 +415,11 @@ pub unsafe extern "system" fn hook_wsa_connect_impl( _sqos: *const c_void, _gqos: *const c_void, ) -> i32 { + if !name.is_null() && namelen > 0 { + if let (Some(ip), port) = (get_ipaddr_from_sockaddr(name), get_port_from_sockaddr(name)) { + debug!("hook_wsa_connect_impl intercepted target {}:{}", ip, port); + } + } hook_connect_impl(sock, name, namelen) }