fix(windows): avoid startup bypass via suspended injection; add probe fail-only
This commit is contained in:
1 parent
301b69a480
commit
c452b0ea42
3 files changed
+114
-15
No files matched your search
+20
-15
@@ -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<i32, String> {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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<i32> {
|
||||
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<u16> = 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::<STARTUPINFOW>() 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<()> {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user