diff --git a/README.md b/README.md index e4e5f38..3e0accd 100644 --- a/README.md +++ b/README.md @@ -250,6 +250,8 @@ proxychains4 --group jp curl https://ifconfig.me IPv6 notes: - IPv6 targets are supported through SOCKS5/HTTP proxy hops. +- With `proxy_dns` enabled, hostname lookups avoid system-DNS fallback paths. +- `getaddrinfo(AF_INET6)` is handled via IPv4-mapped fake addresses for compatibility. - SOCKS4/4a does not support IPv6 target addresses. Validation and discovery: diff --git a/proxychains/src/hook/hooks.rs b/proxychains/src/hook/hooks.rs index 7abd5ad..b995897 100644 --- a/proxychains/src/hook/hooks.rs +++ b/proxychains/src/hook/hooks.rs @@ -50,7 +50,8 @@ impl HookState { /// Global hook state static HOOK_STATE: OnceLock = OnceLock::new(); /// Track addrinfo/sockaddr allocations created by our getaddrinfo hook. -static CUSTOM_ADDRINFO_ALLOCATIONS: OnceLock>> = OnceLock::new(); +static CUSTOM_ADDRINFO_ALLOCATIONS: OnceLock>> = + OnceLock::new(); thread_local! { static GETHOSTBYNAME_STORE: RefCell>> = const { RefCell::new(None) }; @@ -63,7 +64,13 @@ struct HostentStore { hostent: libc::hostent, } -fn custom_alloc_map() -> &'static Mutex> { +#[derive(Clone, Copy)] +struct CustomAddrinfoAllocation { + sockaddr_ptr: usize, + family: libc::c_int, +} + +fn custom_alloc_map() -> &'static Mutex> { CUSTOM_ADDRINFO_ALLOCATIONS.get_or_init(|| Mutex::new(HashMap::new())) } @@ -93,6 +100,106 @@ unsafe fn write_hostname_to_buf(host: *mut c_char, hostlen: libc::socklen_t, hos 0 } +unsafe fn store_fake_addrinfo_result( + fake_ip: std::net::Ipv4Addr, + service_port: u16, + hints: *const libc::addrinfo, + requested_family: libc::c_int, + res: *mut *mut libc::addrinfo, +) -> c_int { + match requested_family { + libc::AF_INET => { + let mut sockaddr: libc::sockaddr_in = std::mem::zeroed(); + #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))] + { + sockaddr.sin_len = std::mem::size_of::() as u8; + } + sockaddr.sin_family = libc::AF_INET as libc::sa_family_t; + sockaddr.sin_port = service_port.to_be(); + sockaddr.sin_addr = libc::in_addr { + s_addr: u32::from_ne_bytes(fake_ip.octets()), + }; + + let sockaddr_ptr = Box::into_raw(Box::new(sockaddr)); + let mut ai: libc::addrinfo = std::mem::zeroed(); + ai.ai_flags = if hints.is_null() { 0 } else { (*hints).ai_flags }; + ai.ai_family = libc::AF_INET; + ai.ai_socktype = if hints.is_null() { + libc::SOCK_STREAM + } else { + (*hints).ai_socktype + }; + ai.ai_protocol = if hints.is_null() { + libc::IPPROTO_TCP + } else { + (*hints).ai_protocol + }; + ai.ai_addrlen = std::mem::size_of::() as libc::socklen_t; + ai.ai_addr = sockaddr_ptr as *mut libc::sockaddr; + ai.ai_canonname = std::ptr::null_mut(); + ai.ai_next = std::ptr::null_mut(); + + let ai_ptr = Box::into_raw(Box::new(ai)); + *res = ai_ptr; + custom_alloc_map().lock().insert( + ai_ptr as usize, + CustomAddrinfoAllocation { + sockaddr_ptr: sockaddr_ptr as usize, + family: libc::AF_INET, + }, + ); + 0 + } + libc::AF_INET6 => { + let mut mapped = [0u8; 16]; + mapped[10] = 0xff; + mapped[11] = 0xff; + mapped[12..16].copy_from_slice(&fake_ip.octets()); + + let mut sockaddr: libc::sockaddr_in6 = std::mem::zeroed(); + #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))] + { + sockaddr.sin6_len = std::mem::size_of::() as u8; + } + sockaddr.sin6_family = libc::AF_INET6 as libc::sa_family_t; + sockaddr.sin6_port = service_port.to_be(); + sockaddr.sin6_addr = libc::in6_addr { s6_addr: mapped }; + sockaddr.sin6_scope_id = 0; + + let sockaddr_ptr = Box::into_raw(Box::new(sockaddr)); + let mut ai: libc::addrinfo = std::mem::zeroed(); + ai.ai_flags = if hints.is_null() { 0 } else { (*hints).ai_flags }; + ai.ai_family = libc::AF_INET6; + ai.ai_socktype = if hints.is_null() { + libc::SOCK_STREAM + } else { + (*hints).ai_socktype + }; + ai.ai_protocol = if hints.is_null() { + libc::IPPROTO_TCP + } else { + (*hints).ai_protocol + }; + ai.ai_addrlen = std::mem::size_of::() as libc::socklen_t; + ai.ai_addr = sockaddr_ptr as *mut libc::sockaddr; + ai.ai_canonname = std::ptr::null_mut(); + ai.ai_next = std::ptr::null_mut(); + + let ai_ptr = Box::into_raw(Box::new(ai)); + *res = ai_ptr; + custom_alloc_map().lock().insert( + ai_ptr as usize, + CustomAddrinfoAllocation { + sockaddr_ptr: sockaddr_ptr as usize, + family: libc::AF_INET6, + }, + ); + 0 + } + _ => libc::EAI_FAMILY, + } +} + /// Initialize the hook library pub fn init_hooks(config: Config) -> Result<()> { // Initialize original functions first @@ -161,12 +268,24 @@ pub unsafe fn hook_connect( } // Apply DNAT if configured - let (final_ip, final_port) = state.config.apply_dnat_ip(&target_ip, target_port); + let (dnat_ip, final_port) = state.config.apply_dnat_ip(&target_ip, target_port); - // Check if this is a fake IP (needs remote DNS resolution) - let target_domain = match final_ip { - IpAddr::V4(v4) if is_fake_ip(&v4) => state.dns_resolver.get_hostname(&v4), - _ => None, + // Check if this is a fake IP (needs remote DNS resolution). For IPv6-mapped + // fake addresses, collapse to IPv4 so SOCKS4a path can still use the domain. + let (final_ip, target_domain) = match dnat_ip { + IpAddr::V4(v4) if is_fake_ip(&v4) => (IpAddr::V4(v4), state.dns_resolver.get_hostname(&v4)), + IpAddr::V6(v6) => { + if let Some(v4) = v6.to_ipv4_mapped() { + if is_fake_ip(&v4) { + (IpAddr::V4(v4), state.dns_resolver.get_hostname(&v4)) + } else { + (IpAddr::V6(v6), None) + } + } else { + (IpAddr::V6(v6), None) + } + } + _ => (dnat_ip, None), }; // Check if proxy DNS is enabled and we have a domain @@ -265,65 +384,36 @@ pub unsafe fn hook_getaddrinfo( return original_getaddrinfo(node, service, hints, res); } - // Preserve behavior for literal IPv4 strings. - if hostname.parse::().is_ok() { + // Preserve behavior for literal IP strings. + if hostname.parse::().is_ok() { return original_getaddrinfo(node, service, hints, res); } - // Respect non-IPv4 family hints. - if !hints.is_null() { - let ai_family = (*hints).ai_family; - if ai_family != libc::AF_INET && ai_family != libc::AF_UNSPEC { - return original_getaddrinfo(node, service, hints, res); + let requested_family = if hints.is_null() { + libc::AF_INET + } else { + match (*hints).ai_family { + libc::AF_UNSPEC => libc::AF_INET, + libc::AF_INET => libc::AF_INET, + libc::AF_INET6 => libc::AF_INET6, + _ => return libc::EAI_FAMILY, } - } + }; // Generate fake IP let fake_ip = match state.dns_resolver.resolve(&hostname) { Ok(ip) => ip, Err(e) => { error!("Failed to resolve {} in hook_getaddrinfo: {}", hostname, e); - return original_getaddrinfo(node, service, hints, res); + return libc::EAI_NONAME; } }; let service_port = parse_service_port(service); - - let mut sockaddr: libc::sockaddr_in = std::mem::zeroed(); - #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))] - { - sockaddr.sin_len = std::mem::size_of::() as u8; + let ret = store_fake_addrinfo_result(fake_ip, service_port, hints, requested_family, res); + if ret != 0 { + return ret; } - sockaddr.sin_family = libc::AF_INET as libc::sa_family_t; - sockaddr.sin_port = service_port.to_be(); - sockaddr.sin_addr = libc::in_addr { - s_addr: u32::from_ne_bytes(fake_ip.octets()), - }; - - let sockaddr_ptr = Box::into_raw(Box::new(sockaddr)); - let mut ai: libc::addrinfo = std::mem::zeroed(); - ai.ai_flags = if hints.is_null() { 0 } else { (*hints).ai_flags }; - ai.ai_family = libc::AF_INET; - ai.ai_socktype = if hints.is_null() { - libc::SOCK_STREAM - } else { - (*hints).ai_socktype - }; - ai.ai_protocol = if hints.is_null() { - libc::IPPROTO_TCP - } else { - (*hints).ai_protocol - }; - ai.ai_addrlen = std::mem::size_of::() as libc::socklen_t; - ai.ai_addr = sockaddr_ptr as *mut libc::sockaddr; - ai.ai_canonname = std::ptr::null_mut(); - ai.ai_next = std::ptr::null_mut(); - - let ai_ptr = Box::into_raw(Box::new(ai)); - *res = ai_ptr; - custom_alloc_map() - .lock() - .insert(ai_ptr as usize, sockaddr_ptr as usize); debug!("Assigned fake IP {} for {}", fake_ip, hostname); @@ -361,8 +451,8 @@ pub unsafe fn hook_gethostbyname(name: *const c_char) -> *mut libc::hostent { return original_gethostbyname(name); } - // Preserve behavior for literal IPv4 strings. - if hostname.parse::().is_ok() { + // Preserve behavior for literal IP strings. + if hostname.parse::().is_ok() { return original_gethostbyname(name); } @@ -371,7 +461,7 @@ pub unsafe fn hook_gethostbyname(name: *const c_char) -> *mut libc::hostent { Ok(ip) => ip, Err(e) => { error!("Failed to resolve {} in hook_gethostbyname: {}", hostname, e); - return original_gethostbyname(name); + return std::ptr::null_mut(); } }; @@ -415,8 +505,12 @@ pub unsafe fn hook_freeaddrinfo(res: *mut libc::addrinfo) { return; } - if let Some(sockaddr_ptr) = custom_alloc_map().lock().remove(&(res as usize)) { - let _ = Box::from_raw(sockaddr_ptr as *mut libc::sockaddr_in); + if let Some(alloc) = custom_alloc_map().lock().remove(&(res as usize)) { + if alloc.family == libc::AF_INET6 { + let _ = Box::from_raw(alloc.sockaddr_ptr as *mut libc::sockaddr_in6); + } else { + let _ = Box::from_raw(alloc.sockaddr_ptr as *mut libc::sockaddr_in); + } let _ = Box::from_raw(res); return; } diff --git a/proxychains/src/hook/hooks_windows.rs b/proxychains/src/hook/hooks_windows.rs index aacbae4..4918a5d 100644 --- a/proxychains/src/hook/hooks_windows.rs +++ b/proxychains/src/hook/hooks_windows.rs @@ -4,7 +4,7 @@ //! chain on the original socket handle. use std::collections::HashMap; -use std::ffi::{c_void, CStr}; +use std::ffi::{c_void, CStr, CString}; use std::mem::{self, ManuallyDrop}; use std::net::{IpAddr, Ipv4Addr}; use std::os::windows::io::FromRawSocket; @@ -17,13 +17,14 @@ use tracing::{debug, error, info, warn}; use windows::Win32::Networking::WinSock::{ ADDRINFOA, AF_INET, IN_ADDR, IN_ADDR_0, IPPROTO_TCP, SOCKADDR, SOCKADDR_IN, SOCK_STREAM, SOCKET_ERROR, WSAEALREADY, WSAECONNREFUSED, WSAEFAULT, WSAEINPROGRESS, WSAEINVAL, + WSAHOST_NOT_FOUND, WSAEWOULDBLOCK, WSAGetLastError, WSASetLastError, }; use crate::config::{ChainType, Config, ProxyData, ProxyState}; use crate::dns::{is_fake_ip, DnsResolver}; use crate::error::{Error, Result}; -use crate::net::{get_ip_from_sockaddr, get_port_from_sockaddr}; +use crate::net::{get_ip_from_sockaddr, get_ipaddr_from_sockaddr, get_port_from_sockaddr}; use crate::proxy::{tunnel_through_proxy, TargetAddress}; use super::interpose_windows::{ @@ -253,27 +254,37 @@ pub unsafe extern "system" fn hook_connect_impl( return original_connect(sock, addr, len); } - let target_ip = match get_ip_from_sockaddr(addr) { + let target_ip = match get_ipaddr_from_sockaddr(addr) { Some(ip) => ip, None => return original_connect(sock, addr, len), }; let target_port = get_port_from_sockaddr(addr); - if state.config.should_bypass(&target_ip) { + if state.config.should_bypass_ip(&target_ip) { return original_connect(sock, addr, len); } - let (final_ip, final_port) = state.config.apply_dnat(&target_ip, target_port); - let target_domain = if is_fake_ip(&final_ip) { - state.dns_resolver.get_hostname(&final_ip) - } else { - None + let (dnat_ip, final_port) = state.config.apply_dnat_ip(&target_ip, target_port); + let (final_ip, target_domain) = match dnat_ip { + IpAddr::V4(v4) if is_fake_ip(&v4) => (IpAddr::V4(v4), state.dns_resolver.get_hostname(&v4)), + IpAddr::V6(v6) => { + if let Some(v4) = v6.to_ipv4_mapped() { + if is_fake_ip(&v4) { + (IpAddr::V4(v4), state.dns_resolver.get_hostname(&v4)) + } else { + (IpAddr::V6(v6), None) + } + } else { + (IpAddr::V6(v6), None) + } + } + _ => (dnat_ip, None), }; let target = if let Some(domain) = target_domain { - TargetAddress::from_both(IpAddr::V4(final_ip), domain) + TargetAddress::from_both(final_ip, domain) } else { - TargetAddress::from_ip(IpAddr::V4(final_ip)) + TargetAddress::from_ip(final_ip) }; let max_attempts = state.config.max_chain_retries.max(1); @@ -340,7 +351,7 @@ pub unsafe extern "system" fn hook_getaddrinfo_impl( if !phints.is_null() { let hints = &*(phints as *const ADDRINFOA); if hints.ai_family != 0 && hints.ai_family != AF_INET.0 as i32 { - return original_getaddrinfo(pnode, pservice, phints, ppresult); + return WSAHOST_NOT_FOUND.0; } } @@ -349,7 +360,7 @@ pub unsafe extern "system" fn hook_getaddrinfo_impl( Err(_) => return original_getaddrinfo(pnode, pservice, phints, ppresult), }; - if hostname.parse::().is_ok() { + if hostname.parse::().is_ok() { return original_getaddrinfo(pnode, pservice, phints, ppresult); } @@ -361,7 +372,7 @@ pub unsafe extern "system" fn hook_getaddrinfo_impl( Ok(ip) => ip, Err(e) => { error!("Failed to resolve {} in hook_getaddrinfo_impl: {}", hostname, e); - return original_getaddrinfo(pnode, pservice, phints, ppresult); + return WSAHOST_NOT_FOUND.0; } }; @@ -414,7 +425,54 @@ pub unsafe extern "system" fn hook_freeaddrinfo_impl(pres: *mut c_void) { /// Windows gethostbyname hook implementation. #[cfg(windows)] pub unsafe extern "system" fn hook_gethostbyname_impl(name: *const i8) -> *mut c_void { - original_gethostbyname(name) + let state = match get_hook_state() { + Some(s) if s.config.proxy_dns => s, + _ => return original_gethostbyname(name), + }; + + if name.is_null() { + WSASetLastError(WSAEINVAL.0); + return std::ptr::null_mut(); + } + + let hostname = match CStr::from_ptr(name).to_str() { + Ok(s) => s, + Err(_) => { + WSASetLastError(WSAEINVAL.0); + return std::ptr::null_mut(); + } + }; + + if hostname.parse::().is_ok() { + return original_gethostbyname(name); + } + + if crate::dns::lookup_in_hosts(hostname).is_some() { + return original_gethostbyname(name); + } + + let fake_ip = match state.dns_resolver.resolve(hostname) { + Ok(ip) => ip, + Err(e) => { + error!("Failed to resolve {} in hook_gethostbyname_impl: {}", hostname, e); + WSASetLastError(WSAHOST_NOT_FOUND.0); + return std::ptr::null_mut(); + } + }; + + let fake_ip_cstr = match CString::new(fake_ip.to_string()) { + Ok(v) => v, + Err(_) => { + WSASetLastError(WSAHOST_NOT_FOUND.0); + return std::ptr::null_mut(); + } + }; + + let result = original_gethostbyname(fake_ip_cstr.as_ptr()); + if result.is_null() { + WSASetLastError(WSAHOST_NOT_FOUND.0); + } + result } /// Windows getnameinfo hook implementation. diff --git a/proxychains/src/net/socket_windows.rs b/proxychains/src/net/socket_windows.rs index d5caebf..6c4016f 100644 --- a/proxychains/src/net/socket_windows.rs +++ b/proxychains/src/net/socket_windows.rs @@ -3,7 +3,7 @@ //! This module provides Windows-specific implementations for socket operations //! using the Winsock2 API. -use std::net::Ipv4Addr; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; /// Get the port from a SOCKADDR structure (Windows-specific) #[cfg(windows)] @@ -18,6 +18,10 @@ pub fn get_port_from_sockaddr(addr: *const core::ffi::c_void) -> u16 { // sin_port: u16 (2 bytes) - network byte order let port_ptr = (addr as *const u8).add(2) as *const u16; u16::from_be(*port_ptr) + } else if sa_family == 23 { + // SOCKADDR_IN6 has the same port offset. + let port_ptr = (addr as *const u8).add(2) as *const u16; + u16::from_be(*port_ptr) } else { 0 } @@ -49,6 +53,28 @@ pub fn get_ip_from_sockaddr(addr: *const core::ffi::c_void) -> Option } } +/// Get the IP address (IPv4/IPv6) from a SOCKADDR structure. +#[cfg(windows)] +pub fn get_ipaddr_from_sockaddr(addr: *const core::ffi::c_void) -> Option { + unsafe { + let sa_family = *(addr as *const u16); + if sa_family == 2 { + let ip_ptr = (addr as *const u8).add(4); + let ip_bytes = [*ip_ptr, *ip_ptr.add(1), *ip_ptr.add(2), *ip_ptr.add(3)]; + Some(IpAddr::V4(Ipv4Addr::from(ip_bytes))) + } else if sa_family == 23 { + // SOCKADDR_IN6 layout: + // sin6_family (2), sin6_port (2), sin6_flowinfo (4), sin6_addr (16), sin6_scope_id (4) + let ip_ptr = (addr as *const u8).add(8); + let mut octets = [0u8; 16]; + std::ptr::copy_nonoverlapping(ip_ptr, octets.as_mut_ptr(), 16); + Some(IpAddr::V6(Ipv6Addr::from(octets))) + } else { + None + } + } +} + /// Create a TCP socket (Windows-specific) pub fn create_tcp_socket() -> std::io::Result { use socket2::{Domain, Protocol, Socket, Type}; @@ -126,4 +152,26 @@ mod tests { assert!(is_localhost(&Ipv4Addr::new(127, 0, 0, 1))); assert!(!is_localhost(&Ipv4Addr::new(192, 168, 1, 1))); } + + #[test] + fn test_parse_ipv6_sockaddr_layout() { + // Build a minimal SOCKADDR_IN6 byte layout. + let mut raw = [0u8; 28]; + raw[0] = 23; // AF_INET6 low byte + raw[1] = 0; + raw[2] = 0x13; // port 4919 in network byte order + raw[3] = 0x37; + raw[8..24].copy_from_slice(&[ + 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + ]); + + let addr_ptr = raw.as_ptr() as *const core::ffi::c_void; + assert_eq!(get_port_from_sockaddr(addr_ptr), 4919); + assert_eq!( + get_ipaddr_from_sockaddr(addr_ptr), + Some(IpAddr::V6(Ipv6Addr::new( + 0x2001, 0x0db8, 0, 0, 0, 0, 0, 1 + ))) + ); + } }