feat(windows): add connectex-overlapped and dns api hardening with ci e2e

This commit is contained in:
tianrking committed 2026-03-20 18:03:05 +08:00
1 parent 04a548ca23
commit 38806cbbb1
7 files changed
+406 -4

No files matched your search

+1
View File
@@ -31,6 +31,7 @@ tracing-subscriber.workspace = true
windows = { version = "0.54", features = [
"Win32_Foundation",
"Win32_Networking_WinSock",
"Win32_System_IO",
"Win32_System_Threading",
"Win32_System_Memory",
"Win32_System_LibraryLoader",
+171 -3
View File
@@ -22,6 +22,8 @@ use windows::Win32::Networking::WinSock::{
WSASetLastError,
};
use windows::core::GUID;
use windows::Win32::System::IO::OVERLAPPED;
use windows::Win32::System::Threading::SetEvent;
use crate::config::{ChainType, Config, ProxyData, ProxyState};
use crate::dns::{is_fake_ip, DnsResolver};
@@ -32,7 +34,8 @@ use crate::ConfigParser;
use super::interpose_windows::{
init_original_functions, original_connect, original_freeaddrinfo, original_getaddrinfo,
original_getaddrinfow, original_gethostbyname, original_getnameinfo, original_wsa_ioctl,
original_getaddrinfoexw, original_getaddrinfow, original_gethostbyname, original_getnameinfo,
original_dns_query_a, original_dns_query_w, original_wsa_ioctl,
};
use super::reload::config_reload_interval;
@@ -461,7 +464,6 @@ const WSAID_CONNECTEX: GUID = GUID::from_u128(0x25a207b9_ddf3_4660_8ee9_76e58c74
/// ConnectEx replacement used when applications query extension function pointers via WSAIoctl.
///
/// This implementation is intentionally synchronous:
/// - `lpOverlapped != NULL` is rejected (`WSAEOPNOTSUPP`) to avoid silent bypass.
/// - `dwSendDataLength > 0` is rejected (`WSAEOPNOTSUPP`) because initial-send semantics are
/// not yet mirrored.
#[cfg(windows)]
@@ -474,7 +476,7 @@ pub unsafe extern "system" fn hook_connect_ex_impl(
bytes_sent: *mut u32,
overlapped: *mut c_void,
) -> i32 {
if !overlapped.is_null() || (!send_buf.is_null() && send_len > 0) {
if !send_buf.is_null() && send_len > 0 {
WSASetLastError(WSAEOPNOTSUPP.0);
return 0;
}
@@ -487,6 +489,12 @@ pub unsafe extern "system" fn hook_connect_ex_impl(
if ret == SOCKET_ERROR {
0
} else {
if !overlapped.is_null() {
let ov = &mut *(overlapped as *mut OVERLAPPED);
if !ov.hEvent.is_invalid() {
let _ = SetEvent(ov.hEvent);
}
}
1
}
}
@@ -759,6 +767,166 @@ pub unsafe extern "system" fn hook_getaddrinfow_impl(
0
}
/// Windows GetAddrInfoExW hook implementation.
#[cfg(windows)]
pub unsafe extern "system" fn hook_getaddrinfoexw_impl(
pname: *const u16,
pservice: *const u16,
namespace: u32,
pnspid: *mut c_void,
hints: *const c_void,
ppresult: *mut *mut c_void,
timeout: *mut c_void,
overlapped: *mut c_void,
completion_routine: *mut c_void,
pname_handle: *mut c_void,
) -> i32 {
let state = match get_hook_state() {
Some(s) => s,
None => {
return original_getaddrinfoexw(
pname,
pservice,
namespace,
pnspid,
hints,
ppresult,
timeout,
overlapped,
completion_routine,
pname_handle,
)
}
};
maybe_reload_config(state);
let config = state.config.lock().clone();
if !config.proxy_dns {
return original_getaddrinfoexw(
pname,
pservice,
namespace,
pnspid,
hints,
ppresult,
timeout,
overlapped,
completion_routine,
pname_handle,
);
}
let dns_resolver = DnsResolver::new(config.proxy_dns, config.remote_dns_subnet);
if pname.is_null() {
return WSAEINVAL.0;
}
let hostname = match parse_wide_string(pname) {
Ok(s) => s,
Err(_) => return WSAEINVAL.0,
};
if hostname.parse::<IpAddr>().is_ok() || crate::dns::lookup_in_hosts(&hostname).is_some() {
return original_getaddrinfoexw(
pname,
pservice,
namespace,
pnspid,
hints,
ppresult,
timeout,
overlapped,
completion_routine,
pname_handle,
);
}
let fake_ip = match dns_resolver.resolve(&hostname) {
Ok(ip) => ip,
Err(_) => return WSAHOST_NOT_FOUND.0,
};
let fake_wide: Vec<u16> = fake_ip
.to_string()
.encode_utf16()
.chain(std::iter::once(0))
.collect();
original_getaddrinfoexw(
fake_wide.as_ptr(),
pservice,
namespace,
pnspid,
hints,
ppresult,
timeout,
overlapped,
completion_routine,
pname_handle,
)
}
const DNS_ERROR_RCODE_NAME_ERROR: i32 = 9003;
/// Windows DnsQuery_A hook implementation.
#[cfg(windows)]
pub unsafe extern "system" fn hook_dns_query_a_impl(
name: *const i8,
query_type: u16,
options: u32,
extra: *mut c_void,
result: *mut *mut c_void,
reserved: *mut c_void,
) -> i32 {
let state = match get_hook_state() {
Some(s) => s,
None => return original_dns_query_a(name, query_type, options, extra, result, reserved),
};
maybe_reload_config(state);
let config = state.config.lock().clone();
if !config.proxy_dns || name.is_null() {
return original_dns_query_a(name, query_type, options, extra, result, reserved);
}
let hostname = match CStr::from_ptr(name).to_str() {
Ok(s) => s,
Err(_) => return DNS_ERROR_RCODE_NAME_ERROR,
};
if hostname.parse::<IpAddr>().is_ok() || crate::dns::lookup_in_hosts(hostname).is_some() {
return original_dns_query_a(name, query_type, options, extra, result, reserved);
}
DNS_ERROR_RCODE_NAME_ERROR
}
/// Windows DnsQuery_W hook implementation.
#[cfg(windows)]
pub unsafe extern "system" fn hook_dns_query_w_impl(
name: *const u16,
query_type: u16,
options: u32,
extra: *mut c_void,
result: *mut *mut c_void,
reserved: *mut c_void,
) -> i32 {
let state = match get_hook_state() {
Some(s) => s,
None => return original_dns_query_w(name, query_type, options, extra, result, reserved),
};
maybe_reload_config(state);
let config = state.config.lock().clone();
if !config.proxy_dns || name.is_null() {
return original_dns_query_w(name, query_type, options, extra, result, reserved);
}
let hostname = match parse_wide_string(name) {
Ok(s) => s,
Err(_) => return DNS_ERROR_RCODE_NAME_ERROR,
};
if hostname.parse::<IpAddr>().is_ok() || crate::dns::lookup_in_hosts(&hostname).is_some() {
return original_dns_query_w(name, query_type, options, extra, result, reserved);
}
DNS_ERROR_RCODE_NAME_ERROR
}
/// Windows freeaddrinfo hook implementation.
#[cfg(windows)]
pub unsafe extern "system" fn hook_freeaddrinfo_impl(pres: *mut c_void) {
+133 -1
View File
@@ -15,7 +15,8 @@ use crate::error::{Error, Result};
use super::hooks_windows::{
hook_connect_impl, hook_freeaddrinfo_impl, hook_getaddrinfo_impl, hook_getaddrinfow_impl,
hook_gethostbyname_impl, hook_getnameinfo_impl, hook_wsa_connect_impl, hook_wsa_ioctl_impl,
hook_getaddrinfoexw_impl, hook_gethostbyname_impl, hook_getnameinfo_impl,
hook_dns_query_a_impl, hook_dns_query_w_impl, hook_wsa_connect_impl, hook_wsa_ioctl_impl,
};
type ConnectFn = unsafe extern "system" fn(usize, *const c_void, i32) -> i32;
@@ -32,6 +33,18 @@ type GetAddrInfoFn =
unsafe extern "system" fn(*const i8, *const i8, *const c_void, *mut *mut c_void) -> i32;
type GetAddrInfoWFn =
unsafe extern "system" fn(*const u16, *const u16, *const c_void, *mut *mut c_void) -> i32;
type GetAddrInfoExWFn = unsafe extern "system" fn(
*const u16,
*const u16,
u32,
*mut c_void,
*const c_void,
*mut *mut c_void,
*mut c_void,
*mut c_void,
*mut c_void,
*mut c_void,
) -> i32;
type FreeAddrInfoFn = unsafe extern "system" fn(*mut c_void);
type GetHostByNameFn = unsafe extern "system" fn(*const i8) -> *mut c_void;
type GetNameInfoFn =
@@ -47,15 +60,34 @@ type WsaIoctlFn = unsafe extern "system" fn(
*mut c_void,
*mut c_void,
) -> i32;
type DnsQueryAFn = unsafe extern "system" fn(
*const i8,
u16,
u32,
*mut c_void,
*mut *mut c_void,
*mut c_void,
) -> i32;
type DnsQueryWFn = unsafe extern "system" fn(
*const u16,
u16,
u32,
*mut c_void,
*mut *mut c_void,
*mut c_void,
) -> i32;
static ORIGINAL_CONNECT: OnceLock<ConnectFn> = OnceLock::new();
static ORIGINAL_WSA_CONNECT: OnceLock<WsaConnectFn> = OnceLock::new();
static ORIGINAL_GETADDRINFO: OnceLock<GetAddrInfoFn> = OnceLock::new();
static ORIGINAL_GETADDRINFOW: OnceLock<GetAddrInfoWFn> = OnceLock::new();
static ORIGINAL_GETADDRINFOEXW: OnceLock<GetAddrInfoExWFn> = OnceLock::new();
static ORIGINAL_FREEADDRINFO: OnceLock<FreeAddrInfoFn> = OnceLock::new();
static ORIGINAL_GETHOSTBYNAME: OnceLock<GetHostByNameFn> = OnceLock::new();
static ORIGINAL_GETNAMEINFO: OnceLock<GetNameInfoFn> = OnceLock::new();
static ORIGINAL_WSA_IOCTL: OnceLock<WsaIoctlFn> = OnceLock::new();
static ORIGINAL_DNS_QUERY_A: OnceLock<DnsQueryAFn> = OnceLock::new();
static ORIGINAL_DNS_QUERY_W: OnceLock<DnsQueryWFn> = OnceLock::new();
static HOOKS_READY: AtomicBool = AtomicBool::new(false);
@@ -69,6 +101,16 @@ unsafe fn install_api_hook<F>(proc_name: &str, detour: *mut c_void) -> Result<F>
Ok(std::mem::transmute_copy::<*mut c_void, F>(&trampoline))
}
unsafe fn install_api_hook_from_module<F>(
module: &str,
proc_name: &str,
detour: *mut c_void,
) -> Result<F> {
let trampoline = MinHook::create_hook_api(module, proc_name, detour)
.map_err(|s| mh_to_error("create_hook_api", s))?;
Ok(std::mem::transmute_copy::<*mut c_void, F>(&trampoline))
}
/// Store for original function pointers.
pub struct OriginalFunctions;
@@ -114,6 +156,32 @@ impl OriginalFunctions {
let _ = ORIGINAL_FREEADDRINFO.set(freeaddrinfo_fn);
let _ = ORIGINAL_GETHOSTBYNAME.set(gethostbyname_fn);
let _ = ORIGINAL_GETNAMEINFO.set(getnameinfo_fn);
if let Ok(getaddrinfoexw_fn) = install_api_hook(
"GetAddrInfoExW",
hook_getaddrinfoexw_impl as *const () as *mut c_void,
) {
let _ = ORIGINAL_GETADDRINFOEXW.set(getaddrinfoexw_fn);
} else {
debug!("GetAddrInfoExW hook not installed (symbol unavailable)");
}
if let Ok(dns_query_a_fn) = install_api_hook_from_module(
"dnsapi.dll",
"DnsQuery_A",
hook_dns_query_a_impl as *const () as *mut c_void,
) {
let _ = ORIGINAL_DNS_QUERY_A.set(dns_query_a_fn);
} else {
debug!("DnsQuery_A hook not installed (dnsapi.dll unavailable)");
}
if let Ok(dns_query_w_fn) = install_api_hook_from_module(
"dnsapi.dll",
"DnsQuery_W",
hook_dns_query_w_impl as *const () as *mut c_void,
) {
let _ = ORIGINAL_DNS_QUERY_W.set(dns_query_w_fn);
} else {
debug!("DnsQuery_W hook not installed (dnsapi.dll unavailable)");
}
MinHook::enable_all_hooks().map_err(|s| mh_to_error("enable_all_hooks", s))?;
}
@@ -237,6 +305,38 @@ pub unsafe fn original_getaddrinfow(
}
}
/// Call the original GetAddrInfoExW function.
pub unsafe fn original_getaddrinfoexw(
node: *const u16,
service: *const u16,
namespace: u32,
nspid: *mut c_void,
hints: *const c_void,
res: *mut *mut c_void,
timeout: *mut c_void,
overlapped: *mut c_void,
completion_routine: *mut c_void,
name_handle: *mut c_void,
) -> i32 {
if let Some(f) = ORIGINAL_GETADDRINFOEXW.get() {
f(
node,
service,
namespace,
nspid,
hints,
res,
timeout,
overlapped,
completion_routine,
name_handle,
)
} else {
WSASetLastError(WSAHOST_NOT_FOUND.0);
WSAHOST_NOT_FOUND.0
}
}
/// Call the original freeaddrinfo function.
pub unsafe fn original_freeaddrinfo(res: *mut c_void) {
if let Some(f) = ORIGINAL_FREEADDRINFO.get() {
@@ -270,6 +370,38 @@ pub unsafe fn original_getnameinfo(
}
}
/// Call the original DnsQuery_A function.
pub unsafe fn original_dns_query_a(
name: *const i8,
query_type: u16,
options: u32,
extra: *mut c_void,
result: *mut *mut c_void,
reserved: *mut c_void,
) -> i32 {
if let Some(f) = ORIGINAL_DNS_QUERY_A.get() {
f(name, query_type, options, extra, result, reserved)
} else {
9003
}
}
/// Call the original DnsQuery_W function.
pub unsafe fn original_dns_query_w(
name: *const u16,
query_type: u16,
options: u32,
extra: *mut c_void,
result: *mut *mut c_void,
reserved: *mut c_void,
) -> i32 {
if let Some(f) = ORIGINAL_DNS_QUERY_W.get() {
f(name, query_type, options, extra, result, reserved)
} else {
9003
}
}
#[cfg(test)]
mod tests {
use super::*;