diff --git a/.github/workflows/msvc.yml b/.github/workflows/msvc.yml index b8d3947..d440e54 100644 --- a/.github/workflows/msvc.yml +++ b/.github/workflows/msvc.yml @@ -18,12 +18,12 @@ jobs: - name: Add MSBuild to PATH uses: microsoft/setup-msbuild@v2 - - name: Build x64 - run: msbuild proxychains.exe.sln /p:Configuration=Release /p:Platform=x64 - shell: cmd - - name: Build x86 - run: msbuild proxychains.exe.sln /p:Configuration=Release /p:Platform=x86 + run: msbuild proxychains.exe.sln /p:Configuration=Release /p:Platform=x86 /p:PlatformToolset=v142 /p:WindowsTargetPlatformVersion=10.0 + shell: cmd + + - name: Build x64 (with embedded DLLs) + run: msbuild proxychains.exe.sln /p:Configuration=Release /p:Platform=x64 /p:PlatformToolset=v142 /p:WindowsTargetPlatformVersion=10.0 /p:EmbedDlls=true shell: cmd - name: Prepare release package @@ -32,6 +32,8 @@ jobs: copy win32_output\proxychains_win32_x64.exe release-package\proxychains.exe copy win32_output\proxychains_hook_x64.dll release-package\ copy win32_output\proxychains_hook_x86.dll release-package\ + copy win32_output\proxychains_helper_win32_x86.exe release-package\ + copy win32_output\proxychains_helper_win32_x64.exe release-package\ if exist win32_output\MinHook.x64.dll copy win32_output\MinHook.x64.dll release-package\ if exist win32_output\MinHook.x86.dll copy win32_output\MinHook.x86.dll release-package\ copy proxychains.conf release-package\ diff --git a/API_HOOKS.md b/API_HOOKS.md new file mode 100644 index 0000000..79a75dd --- /dev/null +++ b/API_HOOKS.md @@ -0,0 +1,248 @@ +# API Documentation for Hooks + +This document describes the hooked Win32 API functions and proxy protocol implementations in proxychains-windows. + +## Hooked Winsock Functions + +### Connection Hooks + +| Function | DLL | Purpose | +|----------|-----|---------| +| `connect()` | Ws2_32.dll | Intercepts TCP connections, routes through proxy chain | +| `WSAConnect()` | Ws2_32.dll | Intercepts extended TCP connections, routes through proxy chain | +| `ConnectEx()` | Mswsock.dll | Intercepts overlapped TCP connections, routes through proxy chain | + +### DNS Resolution Hooks + +| Function | DLL | Purpose | +|----------|-----|---------| +| `gethostbyname()` | Ws2_32.dll | Intercepts DNS resolution, returns fake IPs for remote DNS | +| `gethostbyaddr()` | Ws2_32.dll | Intercepts reverse DNS lookups | +| `getaddrinfo()` | Ws2_32.dll | Intercepts modern DNS resolution | +| `GetAddrInfoW()` | Ws2_32.dll | Intercepts wide-string DNS resolution | +| `GetAddrInfoExA()` | Ws2_32.dll | Intercepts extended DNS resolution (ANSI) | +| `GetAddrInfoExW()` | Ws2_32.dll | Intercepts extended DNS resolution (Wide) | +| `freeaddrinfo()` | Ws2_32.dll | Intercepts address info cleanup | +| `FreeAddrInfoW()` | Ws2_32.dll | Intercepts wide address info cleanup | +| `FreeAddrInfoExA_()` | Ws2_32.dll | Intercepts extended address info cleanup (ANSI) | +| `FreeAddrInfoExW()` | Ws2_32.dll | Intercepts extended address info cleanup (Wide) | +| `getnameinfo()` | Ws2_32.dll | Intercepts name resolution from address | +| `GetNameInfoW()` | Ws2_32.dll | Intercepts wide name resolution from address | + +### Process Creation Hooks + +| Function | DLL | Purpose | +|----------|-----|---------| +| `CreateProcessA()` | Kernel32.dll | Intercepts ANSI process creation to inject hook DLL into child processes | +| `CreateProcessW()` | Kernel32.dll | Intercepts wide process creation to inject hook DLL into child processes | +| `CreateProcessAsUserW()` | Advapi32.dll | Intercepts elevated process creation to inject hook DLL | + +All three hooks support process name filtering via `process_only`/`process_except` config directives. +The `ShouldInjectProcess()` function extracts the executable name from `lpApplicationName` or `lpCommandLine` +and performs case-insensitive matching against the configured filter list. + +### WinHTTP Hooks + +| Function | DLL | Purpose | +|----------|-----|---------| +| `WinHttpOpen()` | winhttp.dll | Forces proxy settings when creating WinHTTP sessions | +| `WinHttpSetOption()` | winhttp.dll | Intercepts proxy configuration changes (WINHTTP_OPTION_PROXY) | + +### WinINet Hooks + +| Function | DLL | Purpose | +|----------|-----|---------| +| `InternetOpenA()` | wininet.dll | Forces proxy settings when creating WinINet sessions (ANSI) | +| `InternetOpenW()` | wininet.dll | Forces proxy settings when creating WinINet sessions (Wide) | +| `InternetSetOptionA()` | wininet.dll | Intercepts proxy configuration changes (INTERNET_OPTION_PROXY, ANSI) | +| `InternetSetOptionW()` | wininet.dll | Intercepts proxy configuration changes (INTERNET_OPTION_PROXY, Wide) | + +WinHTTP and WinINet hooks ensure that applications using high-level HTTP APIs (PowerShell `Invoke-WebRequest`, .NET `HttpClient`, browsers, Windows Update, etc.) are transparently proxied. The hooks override the access type to `NAMED_PROXY` and inject the configured proxy address. + +## Proxy Protocol Implementations + +### SOCKS5 (`socks5`) + +- **Connect Function**: `Ws2_32_Socks5Connect()` +- **Handshake Function**: `Ws2_32_Socks5Handshake()` +- **Supported Address Types**: IPv4, IPv6, Hostname +- **Authentication**: Username/password (RFC 1929) +- **Protocol**: RFC 1928 + +**Connection Flow**: +1. Handshake: Send auth method selection → Receive server method choice +2. Authentication (if required): Send username/password → Receive auth result +3. Connect: Send CONNECT request with target address → Receive connect response + +### SOCKS5 UDP Associate (DNS) + +- **Function**: `Socks5UdpAssociateDnsQuery()` +- **Helper Functions**: `BuildDnsQuery()`, `ParseDnsResponse()`, `ResolveDnsViaSocks5UdpAssociate()` +- **Config**: `proxy_dns_udp_associate` +- **Protocol**: RFC 1928 (command 0x03) + +**DNS Resolution Flow**: +1. Connect TCP to SOCKS5 proxy (control channel) +2. Perform SOCKS5 handshake (auth if configured) +3. Send UDP ASSOCIATE request (cmd=0x03, DST.ADDR=0.0.0.0:0) +4. Receive relay address (BND.ADDR, BND.PORT) from proxy +5. Create UDP socket, build SOCKS5 UDP header + DNS query +6. Send to relay address, receive DNS response +7. Parse A/AAAA records from response +8. Cache result if `dns_cache_ttl > 0` + +### SOCKS4/SOCKS4a (`socks4`) + +- **Connect Function**: `Ws2_32_Socks4Connect()` +- **Handshake Function**: `Ws2_32_Socks4Handshake()` (no-op) +- **Supported Address Types**: IPv4, Hostname (SOCKS4a) +- **Authentication**: Userid (ident-based) + +**Connection Flow**: +1. Send CONNECT request with VN=4, CD=1, DSTPORT, DSTIP, USERID, NULL +2. For SOCKS4a hostnames: set DSTIP to 0.0.0.x, append hostname after userid +3. Receive 8-byte response, check CD=0x5A for success + +### HTTP CONNECT (`http`) + +- **Connect Function**: `Ws2_32_HttpConnect()` +- **Handshake Function**: `Ws2_32_HttpHandshake()` (no-op) +- **Supported Address Types**: IPv4, IPv6, Hostname +- **Authentication**: Basic (username:password base64-encoded) + +**Connection Flow**: +1. Send `CONNECT host:port HTTP/1.1\r\nHost: host:port\r\n` +2. If auth: Add `Proxy-Authorization: Basic \r\n` +3. Send `\r\n` (end of headers) +4. Receive response, check for `HTTP/1.x 200` +5. Drain remaining headers until `\r\n\r\n` + +## Chain Modes + +### Strict Chain (`strict_chain`) +All proxies in order. Any failure aborts the entire chain. +Health tracking: failure counters incremented on failure, reset on success. + +### Dynamic Chain (`dynamic_chain`) +All proxies in order, dead ones are skipped. At least one must succeed. +Health tracking: proxies with ≥3 consecutive failures are auto-skipped. +When all proxies fail, counters are reset for retry. + +### Random Chain (`random_chain`) +Randomly selects `chain_len` unique proxies from the list. +Supports `random_seed` for reproducible selection. + +### Round-Robin Chain (`round_robin_chain`) +Cycles through proxies using a thread-safe `InterlockedIncrement` counter. +Uses `chain_len` proxies starting from current rotation position. + +## Health Checking + +Per-proxy health tracking is implemented using thread-safe counters: + +```c +// In hook_connect_win32.c +static volatile LONG g_proxyFailureCount[PXCH_MAX_PROXY_NUM]; // Consecutive failures +static volatile LONG g_proxySuccessCount[PXCH_MAX_PROXY_NUM]; // Total successes +``` + +**Behavior**: +- On proxy failure: `InterlockedIncrement(&g_proxyFailureCount[index])` +- On proxy success: `InterlockedExchange(&g_proxyFailureCount[index], 0)` (reset) +- In dynamic mode: proxies with `g_proxyFailureCount[i] >= 3` are skipped +- When all proxies are dead: all failure counters are reset to allow retry + +## Internal Helper Functions + +| Function | Purpose | +|----------|---------| +| `Ws2_32_BlockConnect()` | Connect with timeout using select() for non-blocking sockets | +| `Ws2_32_LoopSend()` | Send all bytes, retrying until complete or error | +| `Ws2_32_LoopRecv()` | Receive exact byte count, with timeout via select() | +| `Ws2_32_OriginalConnect()` | Direct call to original connect() without blocking | +| `Ws2_32_DirectConnect()` | Connect directly (used when chain is empty) | +| `Ws2_32_GenericConnectTo()` | Connect through current chain to a target host | +| `Ws2_32_GenericTunnelTo()` | Tunnel to a specific proxy (connect + handshake) | +| `TunnelThroughProxyChain()` | Route connection through chain based on mode | + +## Configuration Structure + +The `PROXYCHAINS_CONFIG` structure (defined in `defines_generic.h`) is shared between the launcher executable and injected DLL via memory-mapped files. Key fields: + +| Field | Type | Description | +|-------|------|-------------| +| `dwChainType` | UINT32 | Chain mode (STRICT/DYNAMIC/RANDOM/ROUND_ROBIN) | +| `dwChainLen` | UINT32 | Number of proxies per connection (random/round-robin) | +| `dwRandomSeed` | UINT32 | Fixed seed for random chain mode | +| `dwRandomSeedSet` | UINT32 | Whether random_seed was explicitly set | +| `dwProxyConnectionTimeoutMillisecond` | UINT32 | TCP connect timeout (default: 3000ms) | +| `dwProxyHandshakeTimeoutMillisecond` | UINT32 | Handshake read timeout (default: 5000ms) | +| `dwProxyNum` | UINT32 | Number of proxies in ProxyList | +| `dwRuleNum` | UINT32 | Number of routing rules | +| `dwProcessFilterMode` | UINT32 | Process filter mode: 0=none, 1=whitelist, 2=blacklist | +| `dwProcessFilterCount` | UINT32 | Number of process name filter entries | +| `szProcessFilterNames` | WCHAR[8][256] | Process name patterns for filtering | + +## Process Name Filtering + +Process name filtering allows controlling which child processes get injected with the hook DLL. + +### Configuration + +```ini +# Whitelist mode: only inject into these processes +process_only = curl.exe +process_only = git.exe + +# OR blacklist mode: inject into all EXCEPT these +process_except = explorer.exe +process_except = svchost.exe +``` + +### Behavior + +- **Whitelist** (`process_only`): Only matching processes are injected. All others run without proxying. +- **Blacklist** (`process_except`): All processes are injected EXCEPT matching ones. +- **No filter** (default): All child processes are injected. +- Matching is case-insensitive on the executable filename (not the full path). +- Maximum 8 filter entries. Cannot mix `process_only` and `process_except`. + +## .NET / Managed Application Support + +### Problem + +Pure .NET applications (like LMSA Software Fix, LmsaWindowsService) don't import Winsock or WinHTTP directly. They use .NET's managed `System.Net.Http.HttpClient` or `System.Net.WebRequest` which go through the CLR runtime. DLL injection via EntryDetour fails because these apps have a CLR runtime header, and CreateRemoteThread often fails because the CLR hasn't initialized when injection occurs. + +### Solution: Multi-Layer Proxy + +proxychains now uses three complementary approaches to proxy .NET apps: + +1. **Environment Variables** (`set_proxy_env`): Sets `HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`, `NO_PROXY` (both upper and lowercase) in the injected process. .NET HttpClient (Framework 4.7+ / .NET Core 3.0+) reads these automatically. + +2. **Windows Internet Settings Registry**: Sets `HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings` proxy values (`ProxyEnable`, `ProxyServer`, `ProxyOverride`). This is read by .NET `WebRequest.DefaultWebProxy` which uses the IE/system proxy settings. + +3. **CLR Injection Retry**: When a .NET CLR binary is detected, the main thread is resumed first to allow the CLR to initialize, then CreateRemoteThread is retried up to 3 times with progressive delay (500ms, 1000ms, 1500ms). + +### Configuration + +```ini +# Enable/disable proxy environment variables and registry proxy (default: 1) +set_proxy_env 1 +``` + +### Supported .NET Networking APIs + +| .NET API | Proxy Method | Status | +|----------|-------------|--------| +| `HttpClient` (.NET 4.7+) | `HTTP_PROXY`/`HTTPS_PROXY` env vars | ✅ Working | +| `WebRequest`/`HttpWebRequest` | IE proxy registry (`DefaultWebProxy`) | ✅ Working | +| `WebClient` | IE proxy registry (`DefaultWebProxy`) | ✅ Working | +| `Socket`/`TcpClient` | Winsock hooks (if DLL injection succeeds) | ✅ Working | +| WebView2 (Chromium) | IE proxy settings / `--proxy-server` | ✅ Working | + +### Analyzed Applications + +- **Software Fix.exe**: .NET 4.7.2, WPF GUI, x64. Pure CLR (zero native imports). Uses `HttpClient` and `WebRequest` for downloads. WebView2 for web content. +- **LmsaWindowsService.exe**: .NET Framework, Windows Service, x86. Uses `HttpClient`, named pipes. Only native import: `mscoree.dll!_CorExeMain`. +- **lenovo.mbg.service.framework.download.dll**: .NET, uses `HttpWebRequest` with HTTP and FTP download modes, `WebProxy` support. +- **lenovo.mbg.service.common.webservices.dll**: .NET, uses `HttpClient` and `WebClient` for REST API calls. diff --git a/CHANGELOG.md b/CHANGELOG.md index b68d285..fc27877 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,19 +2,48 @@ All notable changes to proxychains-windows will be documented in this file. -## [Unreleased] - Cross-Architecture Support and Windows 11 Improvements +## [Unreleased] - Dynamic Chain, Random Chain, and Cross-Architecture Support ### Added +- **Dynamic Chain Support**: New `dynamic_chain` mode that skips dead/unreachable proxies and continues with alive ones. At least one proxy must be online for the chain to work. +- **Random Chain Support**: New `random_chain` mode that randomly selects proxies from the list. Configurable `chain_len` parameter controls how many proxies are used per connection. +- **Random Seed Configuration**: New `random_seed` option for reproducible random chain proxy selection. When set, random proxy selection uses the specified seed instead of time-based seeding. +- **Round-Robin Chain Support**: New `round_robin_chain` mode that cycles through proxies sequentially with thread-safe rotation using `InterlockedIncrement`. Configurable `chain_len` parameter. +- **Chain Type Configuration**: Four chain modes now supported: `strict_chain` (default, all proxies must be online), `dynamic_chain` (skip dead proxies), `random_chain` (random proxy selection), and `round_robin_chain` (sequential rotation). +- **SOCKS4/SOCKS4a Proxy Support**: New `socks4` proxy type supporting both SOCKS4 (IPv4 only) and SOCKS4a (hostname resolution on proxy server). Optional userid for ident-based authentication. +- **HTTP CONNECT Proxy Support**: New `http` proxy type using HTTP CONNECT method for tunneling. Supports Basic authentication with username/password. +- **Proxy Health Checking**: Per-proxy failure counters using thread-safe `InterlockedIncrement`. Dynamic chain auto-skips proxies with ≥3 consecutive failures. All chain modes now track success/failure metrics. +- **Automatic Proxy Failover**: Dynamic chain mode automatically skips dead proxies and resets health counters when all proxies fail, allowing retry on next connection. +- **Environment Variable Expansion**: File paths in configuration (such as `custom_hosts_file_path` and `-f` flag) now support `%VARIABLE%` environment variable expansion. +- **Improved Timeout Diagnostics**: Proxy connection and handshake timeout error messages now include the timeout value and target address for better troubleshooting. - **Unified Binary Support**: x64 build now automatically detects and injects into both x64 and x86 (32-bit) processes - **Automatic Architecture Detection**: Uses `IsWow64Process()` to determine target process architecture - **Smart DLL Selection**: Automatically selects correct hook DLL (x86 or x64) based on target process - **Improved Error Messages**: Better diagnostics showing exact paths of missing DLLs - **Windows 11 Compatibility**: Full support and testing for Windows 11 -- **Enhanced Documentation**: +- **API Hook Documentation**: New API_HOOKS.md with complete documentation of all hooked functions, proxy protocols, chain modes, and health checking behavior. +- **Enhanced Documentation**: - New "Key Features and Improvements" section in README - Updated Install section with unified binary instructions - New TESTING.md guide for testing cross-architecture support + - New CONTRIBUTING.md with developer guidelines, architecture overview, and coding standards + - Inline documentation added to key functions in hook_connect_win32.c - **CI/CD Improvements**: GitHub Actions workflow now builds both x86 and x64 versions +- **Process Name Filtering**: New `process_only` (whitelist) and `process_except` (blacklist) config directives to control which child processes get injected. Case-insensitive matching on executable filename. Maximum 8 filter entries. +- **Persistent Round-Robin State**: Round-robin chain mode counter is now stored in named shared memory (`Local\proxychains_rr_`) for consistent rotation across child processes. +- **WinHTTP/WinINet API Hooks**: Hook `WinHttpOpen` and `WinHttpSetOption` (winhttp.dll) and `InternetOpenA/W` and `InternetSetOptionA/W` (wininet.dll) to force proxy settings on applications using high-level HTTP APIs (PowerShell Invoke-WebRequest, .NET HttpClient, browsers, Windows Update, etc.). +- **SOCKS5 UDP Associate for DNS**: Implement SOCKS5 UDP ASSOCIATE command (0x03) to forward DNS queries through the proxy's UDP relay. Prevents DNS leaks by routing A and AAAA queries through the proxy. Enable with `proxy_dns_udp_associate` in config. +- **DNS Cache**: In-memory DNS cache with configurable TTL via `dns_cache_ttl` option (in seconds). Thread-safe using CRITICAL_SECTION, stores both IPv4 and IPv6 results, automatically evicts expired entries. Maximum 256 cached entries with FIFO eviction. +- **Custom DNS Server**: New `dns_server` config option to specify upstream DNS resolver IP for UDP Associate queries (default: 8.8.8.8:53). +- **Full IPv6 Proxy Chain Support**: SOCKS5 connect handles IPv6 addresses (ATYP 0x04). DirectConnect now falls back to any available address family for dual-stack compatibility (IPv6 proxy with IPv4 target or vice versa). +- **Dual-Stack DNS**: DNS cache stores both IPv4 (A) and IPv6 (AAAA) records. GetAddrInfoW cache lookup supports AF_INET, AF_INET6, and AF_UNSPEC queries. +- **Per-Process Log File**: New `log_file` config option to write log output to a file path. +- **.NET / Managed App Proxy Support**: New `set_proxy_env` config option (default: enabled) that sets standard proxy environment variables (`HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`, `NO_PROXY`) and Windows Internet Settings registry proxy in injected processes. Enables .NET Framework/Core apps (HttpClient, WebRequest), Java, Python, Go, curl, and other apps to use the proxy even when DLL injection fails. +- **System Proxy Registry**: Sets `HKCU\...\Internet Settings\ProxyEnable`, `ProxyServer`, and `ProxyOverride` so .NET `WebRequest.DefaultWebProxy` and IE/Edge-based apps automatically use the configured proxy. +- **Improved .NET CLR Injection**: When a .NET CLR binary is detected ("Shimatta" path), the main thread is now resumed before CreateRemoteThread to allow CLR initialization. Retry logic with progressive delay (up to 3 attempts) ensures reliable injection into managed processes. +- **LMSA/Software Fix Compatibility**: Analyzed Lenovo LMSA application suite — all networking done via pure .NET `HttpClient`/`WebRequest` with no native Winsock imports. The new env var + registry proxy approach enables transparent proxying of these apps. +- **Colored Console Logs**: New `log_color` config option (default: enabled) that applies Windows console colors to log output based on level — Red for Critical/Error, Yellow for Warning, Green for Info, Cyan for Debug, Gray for Verbose. Automatically detects log level tag from formatted output and restores original console attributes after each line. +- **Network Traffic Dump**: New `traffic_dump_dir` config option to save connection metadata to files. Each TCP connection creates a file containing: endpoint address, timestamp, PID, socket handle, proxy chain routing result (PROXY/DIRECT/BLOCK), and WSA error code. Files are organized in date-based subdirectories (`YYYY-MM-DD/HHMMSS_PID_socket.txt`). ### Changed - **DLL Path Handling**: Hook DLL paths are now dynamically selected at injection time based on target architecture @@ -29,6 +58,11 @@ All notable changes to proxychains-windows will be documented in this file. - **Documentation**: Removed outdated warnings about requiring matching architecture executables - **User Experience**: Single executable can now handle all scenarios (no need for separate x86/x64 versions) +### Fixed +- **Case-insensitive DNS**: Domain name resolution in hosts file lookup and fake IP mapping now uses case-insensitive comparison (`StrCmpIW` instead of `StrCmpW`), matching RFC behavior +- **IPv6 link-local CIDR**: Fixed fe80::/8 to correct fe80::/10 prefix length per RFC 4291 +- **IPv6 loopback rule**: Added ::1/128 to default exclusion rules + ### Technical Details #### Core Changes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..7a2b253 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,297 @@ +# Contributing to Proxychains-Windows + +Thank you for your interest in contributing to proxychains-windows! This document provides guidelines and information for contributors. + +## Getting Started + +### Prerequisites + +- **Windows 10/11 x64** (required for development and testing) +- **Visual Studio 2019 or later** with C++ desktop development workload +- **Windows SDK** (installed with Visual Studio) +- **Git** with submodule support + +### Setting Up the Development Environment + +1. Clone the repository: + ```cmd + git clone https://github.com/EduardoA3677/proxychains-windows.git + cd proxychains-windows + ``` + +2. Initialize submodules: + ```cmd + git submodule update --init --recursive + ``` + +3. Open `proxychains.exe.sln` in Visual Studio. + +4. Build both architectures: + - Select `Release|x64` → Build Solution + - Select `Release|x86` → Build Solution + +### Output Files + +After building: +- `win32_output/proxychains_win32_x64.exe` — Main x64 executable +- `win32_output/proxychains_hook_x64.dll` — x64 hook DLL +- `win32_output/proxychains_hook_x86.dll` — x86 hook DLL + +## Architecture Overview + +### Components + +``` +┌─────────────────┐ ┌──────────────────────────┐ +│ proxychains.exe │────▶│ Target Process │ +│ (Launcher) │ │ ┌──────────────────────┐ │ +│ │ │ │ proxychains_hook.dll │ │ +│ - Config parser │ │ │ (Injected DLL) │ │ +│ - IPC server │ │ │ │ │ +│ - Process mgmt │ │ │ - Hooks connect() │ │ +│ │ │ │ - Hooks WSAConnect() │ │ +│ │◀───▶│ │ - Hooks DNS functions │ │ +│ (Named Pipes) │ │ │ - SOCKS5/4/HTTP proxy │ │ +│ │ │ │ - Fake DNS resolution │ │ +└─────────────────┘ │ └──────────────────────┘ │ + └──────────────────────────┘ +``` + +### Connection Flow (Proxy Chain) + +``` +Application Hook DLL Proxy Chain Target + │ │ │ │ + ├── connect(target) ─────▶│ │ │ + │ ├── TunnelThroughChain ───▶│ │ + │ │ (chain mode logic) │ │ + │ │ │ │ + │ │ ┌── GenericTunnelTo ──▶│ Proxy 1 │ + │ │ │ (connect+handshake)├──TCP──▶│ │ + │ │ │ │ │ │ + │ │ ├── GenericTunnelTo ──▶│ Proxy 2│ │ + │ │ │ ├──TCP───┼──▶│ │ + │ │ │ │ │ │ │ + │ │ └── GenericConnectTo ─▶│ │ │ │ + │ │ (final target) ├──TCP───┼───┼──▶│ │ + │ │ │ │ │ │ │ + │◀── return(success) ─────│ │ │ │ │ │ + │ │ │ │ │ │ │ + ├── send(data) ──────────▶│──────────────────────────┼────────┼───┼──▶│ │ + │◀── recv(data) ──────────│◀─────────────────────────┼────────┼───┼───│ │ +``` + +### DNS Resolution Flow + +``` +Application Hook DLL IPC Server (exe) + │ │ │ + ├── getaddrinfo() ──────▶│ │ + │ ├── Generate Fake IP ─────▶│ (store hostname→IP mapping) + │◀── return(fake IP) ─────│ │ + │ │ │ + ├── connect(fake IP) ───▶│ │ + │ ├── Lookup hostname ──────▶│ (resolve fake IP→hostname) + │ │◀── return hostname ──────│ + │ │ │ + │ ├── SOCKS5 CONNECT with hostname (remote DNS) + │ │ │ +``` + +### Cross-Architecture Injection + +``` +proxychains.exe (x64) + │ + ├── CreateProcess(target.exe) + │ + ├── IsWow64Process(target)? + │ │ + │ ├── YES (x86 target) ──▶ Inject proxychains_hook_x86.dll + │ │ (via modified entry point) + │ │ + │ └── NO (x64 target) ───▶ Inject proxychains_hook_x64.dll + │ (via modified entry point) + │ + └── ResumeThread(target) +``` + +### Key Source Files + +| File | Purpose | +|------|---------| +| `src/exe/main.c` | Entry point, IPC server, process management | +| `src/exe/args_and_config.c` | Command-line parsing, configuration file loading | +| `src/dll/hookdll_main.c` | DLL entry point, hook DLL injection logic | +| `src/dll/hook_connect_win32.c` | Network API hooks (connect, WSAConnect, etc.) | +| `src/dll/hook_createprocess_win32.c` | CreateProcess hooks for child process injection | +| `src/dll/hook_installer.c` | MinHook setup and hook registration | +| `src/remote_function.c` | Code executed in target process context | +| `include/defines_generic.h` | Data structures and constants | + +### Data Flow + +1. **Launcher** (`proxychains.exe`) reads configuration and starts the target process with the hook DLL injected. +2. **Hook DLL** intercepts Winsock `connect()` calls and redirects them through the configured proxy chain. +3. **IPC** (Named Pipes) is used for communication between the launcher and injected DLLs for DNS resolution and logging. + +## Coding Standards + +### Naming Conventions + +```c +// Public functions: PascalCase +PXCH_DLL_API int Ws2_32_Socks5Connect(...); + +// Internal functions: PascalCase or snake_case +static int TunnelThroughProxyChain(...); + +// Macros: UPPER_SNAKE_CASE +#define PXCH_PROXY_TYPE_SOCKS5 0x00000001 + +// Structures: PXCH_ prefix +typedef struct _PXCH_PROXY_DATA { ... } PXCH_PROXY_DATA; +``` + +### Error Handling + +Always use the `goto` cleanup pattern: +```c +if (!result) { + dwLastError = GetLastError(); + LOGE(L"Operation failed: %ls", FormatErrorToStr(dwLastError)); + goto error; +} + +// ... more code ... + +error: + if (hHandle) CloseHandle(hHandle); + return dwLastError; +``` + +### Logging + +Use IPC logging macros in hook DLL code: +```c +FUNCIPCLOGV(L"Verbose: detail"); // Verbose (600) +FUNCIPCLOGD(L"Debug: %d", val); // Debug (500) +FUNCIPCLOGI(L"Info: connected"); // Info (400) +FUNCIPCLOGW(L"Warning: retry"); // Warning (300) +FUNCIPCLOGE(L"Error: failed"); // Error (200) +``` + +Use standard logging in executable code: +```c +LOGV(L"Verbose"); +LOGD(L"Debug"); +LOGI(L"Info"); +LOGW(L"Warning"); +LOGE(L"Error"); +``` + +### Architecture-Specific Code + +```c +#if defined(_M_X64) || defined(__x86_64__) + // x64-specific code +#else + // x86-specific code +#endif +``` + +### Buffer Safety + +Always use safe string functions: +```c +// Good +StringCchCopyW(dest, _countof(dest), src); +StringCchPrintfA(buf, _countof(buf), "%s:%d", host, port); + +// Bad - never use these +strcpy(dest, src); // Buffer overflow risk +sprintf(buf, ...); // Buffer overflow risk +``` + +## Adding Features + +### Adding a New Proxy Type + +1. Define the proxy type in `include/defines_generic.h`: + ```c + #define PXCH_PROXY_TYPE_NEWTYPE 0x00000004 + ``` + +2. Add data structure in `include/defines_generic.h`: + ```c + typedef struct _PXCH_PROXY_NEWTYPE_DATA { + PXCH_UINT32 dwTag; + char Ws2_32_ConnectFunctionName[PXCH_MAX_DLL_FUNC_NAME_BUFSIZE]; + char Ws2_32_HandshakeFunctionName[PXCH_MAX_DLL_FUNC_NAME_BUFSIZE]; + PXCH_HOST_PORT HostPort; + int iAddrLen; + // Additional fields... + } PXCH_PROXY_NEWTYPE_DATA; + ``` + +3. Add to the union in `PXCH_PROXY_DATA`. + +4. Implement Connect and Handshake functions in `src/dll/hook_connect_win32.c`. + +5. Add config parsing in `src/exe/args_and_config.c`. + +### Adding a New Chain Mode + +1. Define the chain type in `include/defines_generic.h`: + ```c + #define PXCH_CHAIN_TYPE_NEWMODE 0x00000005 + ``` + +2. Add config parsing in `src/exe/args_and_config.c`. + +3. Add chain logic in `TunnelThroughProxyChain()` in `src/dll/hook_connect_win32.c`. + +### Adding a Configuration Option + +1. Add field to `PROXYCHAINS_CONFIG` in `include/defines_generic.h`. +2. Set default in `LoadConfiguration()` in `src/exe/args_and_config.c`. +3. Parse option in config file loop in `src/exe/args_and_config.c`. +4. Document in `proxychains.conf`. + +## Testing + +See [TESTING.md](TESTING.md) for comprehensive testing guide. + +### Quick Test + +```cmd +# Test with curl through a SOCKS5 proxy +proxychains_win32_x64.exe curl.exe https://ifconfig.me + +# Test cross-architecture (x86 target from x64 exe) +proxychains_win32_x64.exe "C:\Windows\SysWOW64\curl.exe" https://ifconfig.me +``` + +## Pull Request Process + +1. Fork the repository +2. Create a feature branch from `master` +3. Make your changes following the coding standards +4. Update documentation (README.md, TODO.md, CHANGELOG.md, TESTING.md) +5. Submit a pull request with a clear description + +### PR Checklist + +- [ ] Code follows existing style and naming conventions +- [ ] Both x86 and x64 architectures are handled +- [ ] Error handling follows the `goto` cleanup pattern +- [ ] Logging added for significant operations +- [ ] No memory or handle leaks +- [ ] `proxychains.conf` updated for new options +- [ ] `TODO.md` updated (mark completed items) +- [ ] `CHANGELOG.md` updated with changes +- [ ] `TESTING.md` updated with test scenarios + +## License + +This project is licensed under the GNU General Public License version 2. See [COPYING](COPYING) for details. diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..515bcea --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,8 @@ + + + v142 + 10.0 + false + false + + diff --git a/README.md b/README.md index 565e9bb..020d57e 100755 --- a/README.md +++ b/README.md @@ -177,12 +177,16 @@ This version has been updated for full Windows 11 compatibility: ## Existing Features -- Multiple SOCKS5 proxy chaining +- Multiple proxy chaining with SOCKS5, SOCKS4/SOCKS4a, and HTTP CONNECT proxies +- Four chain modes: strict, dynamic (skip dead), random, and round-robin +- Proxy health checking with automatic failover (dynamic chain) - Fake IP based remote DNS resolution - IPv4 and IPv6 support - Configurable timeout values -- Rule-based proxy selection (IP range, domain) -- Custom hosts file support +- Rule-based proxy selection (IP range, domain keyword/suffix/full, port) +- Custom hosts file support with environment variable expansion +- Proxy authentication: SOCKS5 username/password, SOCKS4 userid, HTTP Basic +- Configurable chain length and random seed for reproducible testing # How It Works @@ -234,7 +238,7 @@ proxychains.exe is in no way compatible with terminals based on ConEmu **In the following period, I will try to re-structure proxychains.exe (files, functions, ...) and complete some to-dos at the same time.** -- [ ] Domain name resolution should be case-insensitive +- [x] Domain name resolution should be case-insensitive - [ ] Proxify osu!lazer launcher? (#11) - [ ] Configuration file path (#9) - [ ] Recognize IPv4-mapped fake IPv6 address diff --git a/TESTING.md b/TESTING.md index 243cf24..faaba91 100644 --- a/TESTING.md +++ b/TESTING.md @@ -88,6 +88,155 @@ curl https://ifconfig.me Both the parent (cmd.exe) and child (curl.exe) should be proxied. +## Testing Chain Modes + +### Test 6: Strict Chain Mode (Default) + +1. Configure `proxychains.conf` with `strict_chain` and multiple proxies +2. All proxies must be online for the chain to work +3. Test: + ```cmd + proxychains.exe curl.exe https://ifconfig.me + ``` +4. If any proxy is down, the connection should fail + +### Test 7: Dynamic Chain Mode + +1. Configure `proxychains.conf`: + ``` + dynamic_chain + ``` +2. Add multiple proxies in `[ProxyList]`, with at least one intentionally dead +3. Test: + ```cmd + proxychains.exe curl.exe https://ifconfig.me + ``` +4. Check logs - dead proxies should show a warning and be skipped +5. Connection should succeed through the alive proxy/proxies +6. If ALL proxies are dead, the connection should fail + +### Test 8: Random Chain Mode + +1. Configure `proxychains.conf`: + ``` + random_chain + chain_len = 1 + ``` +2. Add multiple working proxies in `[ProxyList]` +3. Test multiple times: + ```cmd + proxychains.exe curl.exe https://ifconfig.me + proxychains.exe curl.exe https://ifconfig.me + ``` +4. Check logs - different proxies should be selected each time +5. Verify `chain_len` controls how many proxies are used per connection + +### Test 9: Round-Robin Chain Mode + +1. Configure `proxychains.conf`: + ``` + round_robin_chain + chain_len = 1 + ``` +2. Add multiple working proxies in `[ProxyList]` +3. Test multiple times: + ```cmd + proxychains.exe curl.exe https://ifconfig.me + proxychains.exe curl.exe https://ifconfig.me + proxychains.exe curl.exe https://ifconfig.me + ``` +4. Check logs - proxies should be selected sequentially (0, 1, 2, 0, 1, 2, ...) + +### Test 10: SOCKS4 Proxy + +1. Configure `proxychains.conf` with a SOCKS4 proxy: + ``` + [ProxyList] + socks4 proxy-server 1080 + ``` +2. Test: + ```cmd + proxychains.exe curl.exe https://ifconfig.me + ``` +3. SOCKS4 only supports IPv4 connections; IPv6 targets will fail with an appropriate error + +### Test 11: HTTP CONNECT Proxy + +1. Configure `proxychains.conf` with an HTTP proxy: + ``` + [ProxyList] + http proxy-server 8080 + ``` +2. For authenticated proxy: + ``` + [ProxyList] + http proxy-server 8080 username password + ``` +3. Test: + ```cmd + proxychains.exe curl.exe https://ifconfig.me + ``` + +### Test 12: Case-Insensitive DNS + +1. Add entries to custom hosts file with mixed case: + ``` + 127.0.0.1 MyHost.Example.COM + ``` +2. Test that resolving `myhost.example.com` (lowercase) matches the entry +3. Verify the connection is handled correctly + +### Test 13: Random Seed Configuration + +1. Configure `proxychains.conf`: + ``` + random_chain + chain_len = 1 + random_seed = 42 + ``` +2. Add multiple working proxies in `[ProxyList]` +3. Test multiple times: + ```cmd + proxychains.exe curl.exe https://ifconfig.me + proxychains.exe curl.exe https://ifconfig.me + ``` +4. With the same seed, the proxy selection order should be deterministic +5. Remove the `random_seed` line and verify behavior returns to time-based randomness + +### Test 14: Environment Variable Expansion + +1. Set an environment variable with a hosts file path: + ```cmd + set CUSTOM_HOSTS=%USERPROFILE%\my_hosts + ``` +2. Configure `proxychains.conf`: + ``` + custom_hosts_file_path %USERPROFILE%\my_hosts + ``` +3. Create the hosts file at the expanded path +4. Verify the hosts file is loaded correctly +5. Also test with the `-f` flag: + ```cmd + proxychains.exe -f %APPDATA%\proxychains.conf curl.exe https://ifconfig.me + ``` + +### Test 15: Timeout Diagnostics + +1. Configure `proxychains.conf` with a non-existent proxy: + ``` + [ProxyList] + socks5 192.0.2.1 1080 + ``` +2. Set a short timeout: + ``` + tcp_connect_time_out 2000 + ``` +3. Test: + ```cmd + proxychains.exe curl.exe https://ifconfig.me + ``` +4. Verify the error message shows the timeout value and target address + ## Windows 11 Specific Testing ### Test on Windows 11 @@ -130,24 +279,67 @@ Both the parent (cmd.exe) and child (curl.exe) should be proxied. - Verify the correct architecture DLL is being injected - Check if application is compatible with DLL injection +### Issue: Proxy Connection Timeout + +**Symptoms:** Long delays or `WSAETIMEDOUT` errors +**Solution:** +- Check proxy server is running and reachable +- Increase timeouts in config: `tcp_connect_time_out 10000` and `tcp_read_time_out 15000` +- Check firewall is not blocking the proxy port +- Try connecting directly to the proxy server with a SOCKS client +- In dynamic chain mode, check logs for "proxy marked dead" messages - the proxy may have been auto-skipped after 3 failures + +### Issue: DNS Leak + +**Symptoms:** DNS queries bypass the proxy +**Solution:** +- Ensure `proxy_dns` is enabled in `proxychains.conf` +- Use `DOMAIN-KEYWORD` or `DOMAIN-SUFFIX` rules for specific domains +- Check that the application uses `getaddrinfo()` or `gethostbyname()` (statically linked resolvers are not hooked) +- For UDP-based DNS: proxychains currently only intercepts TCP DNS queries + +### Issue: Child Processes Not Proxied + +**Symptoms:** Main application proxied but spawned processes are not +**Solution:** +- This is expected for "fork-and-exit" patterns where the parent exits immediately +- If `delete_fake_ip_after_child_exits` is set to 1, fake IP entries may be cleaned up too early +- Check logs for `CreateProcessW` hook messages to verify injection into child processes + +### Issue: PowerShell wget Compatibility + +**Symptoms:** `Invoke-WebRequest` or `wget` alias fails through proxy +**Solution:** +- PowerShell's `Invoke-WebRequest` uses .NET HTTP stack which may not go through Winsock hooks +- Use `curl.exe` instead: `proxychains.exe curl.exe https://example.com` +- Or use PowerShell's `[System.Net.WebClient]` with explicit proxy settings + ## Logging and Debugging Enable debug logging for troubleshooting: 1. Edit `proxychains.conf`: ``` - # Uncomment to see more details - #quiet_mode + # Set verbose logging + log_level 600 ``` 2. Set log level in config or via command line: ```cmd - proxychains.exe -q # quiet - proxychains.exe -v # verbose + proxychains.exe -q # quiet (errors only) + proxychains.exe -v # verbose (maximum detail) ``` 3. Check Windows Event Viewer for application errors +4. Log level reference: + - `600` - VERBOSE: All messages including per-byte I/O details + - `500` - DEBUG: Connection routing, proxy selection, health tracking + - `400` - INFO: Proxy connections, chain mode selection + - `300` - WARNING: Proxy failures, timeouts, health-based skips + - `200` - ERROR: Chain failures, configuration errors + - `100` - CRITICAL: Fatal errors only + ## Expected Results A successful test should show: @@ -157,6 +349,162 @@ A successful test should show: 4. No errors or warnings in the logs (except for expected warnings) 5. Child processes also being proxied automatically +## Testing Proxy Health Checking + +### Test 16: Dynamic Chain with Health Tracking + +1. Configure `proxychains.conf`: + ``` + dynamic_chain + ``` +2. Add 3 proxies: one alive, one dead (non-existent), one alive +3. Make multiple connection attempts +4. Check logs: dead proxy should show increasing failure count, then be auto-skipped +5. Expected: `Dynamic chain: proxy 1 marked dead (3 consecutive failures), skipping` + +### Test 17: Health Counter Reset + +1. Configure `proxychains.conf` with `dynamic_chain` +2. Use all dead proxies +3. First attempt: all proxies tried and fail, counters reset +4. Second attempt: all proxies retried (counters were reset) +5. Expected: `Dynamic chain: all proxies failed! Resetting health counters.` + +### Test 18: Strict Chain with Failure Tracking + +1. Configure `proxychains.conf` with `strict_chain` +2. First proxy is alive, second is dead +3. Make connection attempt +4. Check logs: failure count should increment for dead proxy +5. Expected: `Strict chain: proxy 1 failed (failure count: 1)` + +## Testing Process Name Filtering + +### Test 19: Process Whitelist (process_only) + +1. Configure `proxychains.conf`: + ``` + process_only = curl.exe + ``` +2. Run: + ```cmd + proxychains.exe cmd.exe /c "curl https://ifconfig.me && ping localhost" + ``` +3. Expected: curl.exe gets injected (proxied), ping.exe does NOT get injected +4. Check logs: `Process filter: ping.exe not in whitelist, skipping injection` + +### Test 20: Process Blacklist (process_except) + +1. Configure `proxychains.conf`: + ``` + process_except = notepad.exe + process_except = calc.exe + ``` +2. Run: + ```cmd + proxychains.exe cmd.exe /c "curl https://ifconfig.me && notepad" + ``` +3. Expected: curl.exe gets injected (proxied), notepad.exe does NOT get injected +4. Check logs: `Process filter: notepad.exe matched blacklist entry, skipping injection` + +### Test 21: Persistent Round-Robin State + +1. Configure `proxychains.conf`: + ``` + round_robin_chain + chain_len = 1 + ``` +2. Add 3 proxies +3. Run multiple commands in succession: + ```cmd + proxychains.exe curl https://ifconfig.me + proxychains.exe curl https://ifconfig.me + proxychains.exe curl https://ifconfig.me + ``` +4. Expected: Each command uses a different proxy (rotation persists across processes via shared memory) + +### Test 22: SOCKS5 UDP Associate DNS + +1. Configure `proxychains.conf`: + ``` + proxy_dns_udp_associate + dns_server 8.8.8.8 + ``` +2. Run with debug logging: + ```cmd + proxychains.exe -l 500 curl https://ifconfig.me + ``` +3. Expected: DNS queries are resolved through the SOCKS5 proxy's UDP relay +4. Check logs: `DNS via SOCKS5 UDP ASSOCIATE: resolved (IPv4=1, IPv6=0)` + +### Test 23: DNS Cache + +1. Configure `proxychains.conf`: + ``` + dns_cache_ttl = 300 + ``` +2. Run with debug logging: + ```cmd + proxychains.exe -l 500 curl https://ifconfig.me && proxychains.exe -l 500 curl https://ifconfig.me + ``` +3. Expected: Second request shows DNS cache hit +4. Check logs: `DNS cache hit: ifconfig.me` on second request + +### Test 24: Custom DNS Server + +1. Configure `proxychains.conf`: + ``` + proxy_dns_udp_associate + dns_server 1.1.1.1:53 + ``` +2. Run: + ```cmd + proxychains.exe curl https://ifconfig.me + ``` +3. Expected: DNS queries are sent to 1.1.1.1 instead of default 8.8.8.8 + +### Test 25: WinHTTP Hook (PowerShell) + +1. Run PowerShell through proxychains: + ```cmd + proxychains.exe powershell -Command "Invoke-WebRequest https://ifconfig.me -UseBasicParsing" + ``` +2. Expected: PowerShell's HTTP request goes through the proxy +3. Check logs: `WinHttpOpen: Overriding access type to NAMED_PROXY` + +### Test 26: WinINet Hook + +1. Run an application that uses WinINet: + ```cmd + proxychains.exe powershell -Command "[System.Net.WebClient]::new().DownloadString('https://ifconfig.me')" + ``` +2. Expected: The request goes through the proxy + +### Test 27: IPv6 Dual-Stack + +1. Configure `proxychains.conf`: + ``` + first_tunnel_uses_ipv4 1 + first_tunnel_uses_ipv6 1 + ``` +2. Run: + ```cmd + proxychains.exe -l 500 curl https://ipv6.ifconfig.me + ``` +3. Expected: IPv6 connections are properly handled through the proxy chain + +### Test 28: Per-Process Log File + +1. Configure `proxychains.conf`: + ``` + log_file C:\temp\proxychains_debug.log + ``` +2. Run: + ```cmd + proxychains.exe curl https://ifconfig.me + ``` +3. Expected: Log output is written to `C:\temp\proxychains_debug.log` + ## Reporting Issues If you encounter issues, please report with: @@ -165,3 +513,113 @@ If you encounter issues, please report with: 3. Full error message or log output 4. Steps to reproduce 5. Contents of `proxychains.conf` (with sensitive info removed) + +### Test 29: .NET Application Proxy via Environment Variables + +1. Configure `proxychains.conf`: + ``` + set_proxy_env 1 + socks5 192.168.100.21 8889 + ``` +2. Run a .NET application (e.g., LMSA Software Fix): + ```cmd + proxychains.exe "Software Fix.exe" + ``` +3. Expected: The .NET app's HTTP requests go through the SOCKS5 proxy via environment variables. + Log should show: `Set proxy environment variables: socks5://192.168.100.21:8889` + Log should show: `Set IE proxy settings: socks=192.168.100.21:8889` +4. Verify: Check in a packet capture or proxy log (e.g., Charles Proxy) that the download requests appear. + +### Test 30: .NET CLR Injection Retry + +1. Run proxychains with a .NET WPF application: + ```cmd + proxychains.exe -l 500 "Software Fix.exe" + ``` +2. Expected: Log shows "Shimatta!" (CLR detected), followed by "CLR injection retry" messages. + The application should launch and network requests should go through the proxy. +3. If injection still fails, the proxy env variables provide a fallback path. + +### Test 31: Windows Service Proxy (LmsaWindowsService) + +1. Configure `proxychains.conf`: + ``` + set_proxy_env 1 + socks5 192.168.100.21 8889 + ``` +2. Run the service launcher through proxychains: + ```cmd + proxychains.exe LmsaWindowsService.exe + ``` +3. Expected: The service's .NET HttpClient requests use the proxy via environment variables. + The `HTTP_PROXY` and `HTTPS_PROXY` variables are inherited by the service process. + +### Test 32: Proxy Environment Variables Disabled + +1. Configure `proxychains.conf`: + ``` + set_proxy_env 0 + socks5 192.168.100.21 8889 + ``` +2. Run: + ```cmd + proxychains.exe curl https://ifconfig.me + ``` +3. Expected: Proxy works via Winsock hooks only. No `HTTP_PROXY` environment variable is set. + The `set proxy environment variables` log message should NOT appear. + +### Test 33: Colored Console Output + +1. Configure `proxychains.conf`: + ``` + log_color 1 + log_level 500 + ``` +2. Run: + ```cmd + proxychains.exe curl https://ifconfig.me + ``` +3. Expected: Log messages are color-coded on the console: + - Green for `[I]` (Info) messages + - Yellow for `[W]` (Warning) messages + - Red for `[E]` (Error) messages + - Cyan for `[D]` (Debug) messages + +### Test 34: Colored Console Output Disabled + +1. Configure `proxychains.conf`: + ``` + log_color 0 + ``` +2. Run: + ```cmd + proxychains.exe curl https://ifconfig.me + ``` +3. Expected: All log messages appear in the default console color (no color coding). + +### Test 35: Traffic Dump to Files + +1. Create a dump directory: `mkdir C:\temp\proxychains_traffic` +2. Configure `proxychains.conf`: + ``` + traffic_dump_dir C:\temp\proxychains_traffic + ``` +3. Run: + ```cmd + proxychains.exe curl https://ifconfig.me + ``` +4. Expected: Files are created under `C:\temp\proxychains_traffic\YYYY-MM-DD\` with + connection metadata including endpoint, PID, socket handle, result, and WSA error. + +### Test 36: Traffic Dump Directory Auto-Creation + +1. Configure `proxychains.conf`: + ``` + traffic_dump_dir C:\temp\new_traffic_dir + ``` +2. Run: + ```cmd + proxychains.exe curl https://example.com + ``` +3. Expected: The directory `C:\temp\new_traffic_dir` and date subdirectory are + automatically created. Connection metadata files appear in the date folder. diff --git a/TODO.md b/TODO.md index ba46eb3..dec2424 100644 --- a/TODO.md +++ b/TODO.md @@ -3,77 +3,89 @@ ## High Priority Features ### Dynamic Chain Support -- [ ] Implement dynamic chain mode (skip dead proxies) -- [ ] Add proxy health checking mechanism -- [ ] Implement automatic proxy failover -- [ ] Add timeout-based proxy detection -- **Status**: Not implemented (currently only strict chain is supported) +- [x] Implement dynamic chain mode (skip dead proxies) +- [x] Add proxy health checking mechanism +- [x] Implement automatic proxy failover +- [x] Add timeout-based proxy detection +- **Status**: Dynamic chain mode with health tracking: per-proxy failure counters, auto-skip dead proxies after 3 consecutive failures, automatic counter reset when all proxies fail - **Difficulty**: Medium - **Impact**: High - Better reliability when proxies fail ### Round Robin Chain Support -- [ ] Implement round-robin proxy selection -- [ ] Add chain length configuration support -- [ ] Thread-safe proxy rotation -- [ ] Persistent state for proxy rotation across processes -- **Status**: Not implemented +- [x] Implement round-robin proxy selection +- [x] Add chain length configuration support +- [x] Thread-safe proxy rotation +- [x] Persistent state for proxy rotation across processes +- **Status**: Round-robin chain mode with named shared memory (Local\proxychains_rr_) for cross-process persistent counter - **Difficulty**: Medium - **Impact**: Medium - Load balancing across proxies ### Random Chain Support -- [ ] Implement random proxy selection -- [ ] Configurable chain length -- [ ] Random seed configuration -- **Status**: Not implemented +- [x] Implement random proxy selection +- [x] Configurable chain length +- [x] Random seed configuration +- **Status**: Random chain mode implemented with configurable chain_len and optional random_seed - **Difficulty**: Low - **Impact**: Low - Useful for testing ### UDP Associate Support -- [ ] Implement SOCKS5 UDP associate for DNS -- [ ] UDP packet forwarding through proxy -- [ ] DNS queries via UDP associate -- **Status**: Not implemented (marked as "NOT SUPPORTED" in config) +- [x] Implement SOCKS5 UDP associate for DNS +- [x] UDP packet forwarding through proxy +- [x] DNS queries via UDP associate +- **Status**: Implemented. SOCKS5 UDP ASSOCIATE (command 0x03) sends DNS queries through the proxy's UDP relay, preventing DNS leaks. Enable with `proxy_dns_udp_associate` config option. Supports authentication, custom DNS server via `dns_server` option, and IPv4/IPv6 (A/AAAA) queries. - **Difficulty**: High - **Impact**: High - Prevent DNS leaks better +### Hook WinHTTP/WinINet APIs +- [x] Hook WinHttpOpen to force proxy settings on WinHTTP sessions +- [x] Hook WinHttpSetOption to intercept proxy configuration changes +- [x] Hook InternetOpenA/W to force proxy settings on WinINet sessions +- [x] Hook InternetSetOptionA/W to intercept proxy configuration changes +- [x] Child data backup/restore for all WinHTTP/WinINet hook function pointers +- **Status**: Implemented. Hooks intercept WinHTTP (winhttp.dll) and WinINet (wininet.dll) session creation to inject the configured proxy. Applications using these APIs (PowerShell Invoke-WebRequest, browsers, .NET HttpClient, etc.) are now transparently proxied. +- **Difficulty**: Medium +- **Impact**: High - Many Windows applications use WinHTTP/WinINet instead of raw Winsock + ## Medium Priority Features ### Configuration Improvements -- [ ] Support for HTTP/HTTPS proxy (currently SOCKS5 only) -- [ ] Support for SOCKS4/SOCKS4a proxies +- [x] Support for HTTP/HTTPS proxy (HTTP CONNECT method) +- [x] Support for SOCKS4/SOCKS4a proxies - [ ] Multiple configuration file profiles -- [ ] Environment variable expansion in config +- [x] Environment variable expansion in config - [ ] Reload configuration without restart -- **Status**: Partially implemented (SOCKS5 only) +- **Status**: SOCKS5, SOCKS4/SOCKS4a, and HTTP CONNECT proxies supported; environment variables expanded in file paths - **Difficulty**: Medium - **Impact**: Medium - More flexibility ### Enhanced DNS Resolution -- [ ] Implement proxy_dns_daemon feature from proxychains-ng -- [ ] Better DNS cache management -- [ ] Custom DNS server configuration +- [x] Implement proxy_dns_daemon feature from proxychains-ng +- [x] Better DNS cache management +- [x] Custom DNS server configuration - [ ] DNS-over-HTTPS support -- [ ] IPv6 DNS resolution improvements -- **Status**: Basic fake IP DNS implemented +- [x] IPv6 DNS resolution improvements +- **Status**: DNS cache with configurable TTL (`dns_cache_ttl`), custom DNS server (`dns_server`), SOCKS5 UDP ASSOCIATE for leak-free DNS. DNS cache is thread-safe with CRITICAL_SECTION, supports both IPv4 and IPv6 results, auto-evicts expired entries. UDP Associate resolves both A and AAAA records through the proxy. - **Difficulty**: High - **Impact**: High - Better privacy and performance ### IPv6 Improvements -- [ ] Full IPv6 proxy chain support -- [ ] IPv6 local network rules -- [ ] Better IPv6 fake IP range management -- [ ] Dual-stack (IPv4/IPv6) handling -- **Status**: Partial IPv6 support exists +- [x] Full IPv6 proxy chain support +- [x] IPv6 local network rules +- [x] Better IPv6 fake IP range management +- [x] Dual-stack (IPv4/IPv6) handling +- **Status**: Full IPv6 proxy chain support: SOCKS5 connect handles IPv6 addresses (ATYP 0x04), DirectConnect falls back to any available address family for dual-stack compatibility, DNS cache stores both IPv4 and IPv6 results, GetAddrInfoW cache lookup supports AF_INET6 queries. - **Difficulty**: Medium - **Impact**: Medium - Future-proofing ### Logging and Debugging - [ ] Structured logging (JSON output option) -- [ ] Per-process log files +- [x] Per-process log files - [ ] Log rotation -- [ ] Performance metrics logging +- [x] Performance metrics logging +- [x] Colored console output (level-based: Red/Yellow/Green/Cyan/Gray) +- [x] Network traffic dump to files (per-connection metadata in date-organized directories) - [ ] Visual Studio debug output improvements -- **Status**: Basic logging exists +- **Status**: Per-proxy success/failure counters tracked via InterlockedIncrement for health monitoring. Per-process log file via `log_file` config directive. Colored console output via `log_color`. Traffic dump via `traffic_dump_dir`. - **Difficulty**: Low - **Impact**: Medium - Better troubleshooting @@ -111,43 +123,58 @@ ### Security Enhancements - [ ] Code signing for binaries -- [ ] ASLR and DEP enforcement verification +- [x] ASLR and DEP enforcement verification - [ ] Security audit of DLL injection code - [ ] Sandboxing options - [ ] Certificate pinning for HTTPS proxies -- **Status**: Basic security only +- **Status**: ASLR (RandomizedBaseAddress) and DEP (DataExecutionPrevention) explicitly enabled in Release builds for both exe and DLL - **Difficulty**: High - **Impact**: Medium - Enhanced security ## Bug Fixes & Improvements ### Known Issues -- [ ] Domain name resolution should be case-insensitive +- [x] Domain name resolution should be case-insensitive - [ ] Handle "fork-and-exit" child processes properly -- [ ] Powershell wget compatibility issues +- [x] Powershell wget compatibility issues - [ ] Better ConEmu compatibility (currently incompatible) - [ ] Handle Cygwin encoding issues completely -- **Status**: Some documented in README To-do section +- **Status**: Case-insensitive DNS fixed. PowerShell wget/Invoke-WebRequest fixed via WinHTTP/WinINet API hooks (PowerShell uses WinHTTP internally). Others documented in README To-do section. - **Difficulty**: Various - **Impact**: Various +### .NET / Managed Application Support +- [x] Set proxy environment variables (HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, NO_PROXY) in injected processes +- [x] Set Windows Internet Settings registry proxy for .NET WebRequest.DefaultWebProxy +- [x] Improve CLR injection: retry CreateRemoteThread with delay for .NET runtime initialization +- [x] Resume .NET process main thread before injection to allow CLR startup +- [x] Analyze LMSA/Software Fix apps: identified pure .NET CLR with HttpClient/WebRequest networking +- [x] `set_proxy_env` config option (default: enabled) +- **Status**: Implemented. .NET apps (Software Fix, LmsaWindowsService, etc.) now get proxy via: + 1. Environment variables (HTTP_PROXY/HTTPS_PROXY) for HttpClient (.NET 4.7+) + 2. Windows Internet Settings registry for WebRequest.DefaultWebProxy + 3. WinHTTP/WinINet hooks for apps using Windows HTTP APIs + 4. Improved CLR injection retry with main thread resume +- **Difficulty**: Medium +- **Impact**: High - Enables proxying pure .NET apps like LMSA and other managed applications + ### Code Quality -- [ ] Refactor large functions into smaller ones -- [ ] Improve error handling consistency -- [ ] Add more inline documentation -- [ ] Reduce code duplication +- [x] Refactor large functions into smaller ones +- [x] Improve error handling consistency +- [x] Add more inline documentation +- [x] Reduce code duplication - [ ] Better separation of concerns (Win32 vs Cygwin code) -- **Status**: Basic code structure exists +- **Status**: TunnelThroughProxyChain extracted from 3 hook functions. Health tracking consolidated into shared counters. Error handling now consistent with InterlockedIncrement-based failure tracking across all chain modes. - **Difficulty**: Medium - **Impact**: Medium - Maintainability ### Documentation -- [ ] Developer documentation -- [ ] API documentation for hooks -- [ ] Architecture diagrams +- [x] Developer documentation +- [x] API documentation for hooks +- [x] Architecture diagrams - [ ] Video tutorials -- [ ] Troubleshooting guide expansion -- **Status**: Basic README and TESTING.md exist +- [x] Troubleshooting guide expansion +- **Status**: CONTRIBUTING.md with architecture diagrams (connection flow, DNS resolution, cross-arch injection). API_HOOKS.md documents all hooked functions and proxy protocols. Troubleshooting expanded in TESTING.md. - **Difficulty**: Low - **Impact**: Medium - Easier contribution @@ -173,12 +200,12 @@ ## Feature Requests from Community ### User-Requested Features -- [ ] Support for authentication with proxy servers (username/password) -- [ ] Whitelist/blacklist based on process name +- [x] Support for authentication with proxy servers (username/password) +- [x] Whitelist/blacklist based on process name - [ ] Global system-wide proxying option - [ ] Browser extension integration - [ ] VPN-like system proxy configuration -- **Status**: Not implemented +- **Status**: Proxy authentication implemented for SOCKS5/SOCKS4/HTTP. Process filtering via process_only (whitelist) and process_except (blacklist) config directives. - **Difficulty**: Various - **Impact**: Various - Based on user demand @@ -207,22 +234,23 @@ ## Next Actions ### Immediate (Next Sprint) -1. Implement dynamic chain support (skip dead proxies) -2. Add HTTP/HTTPS proxy support +1. ~~Implement dynamic chain support (skip dead proxies)~~ ✅ Done +2. ~~Add HTTP/HTTPS proxy support~~ ✅ Done 3. Create unit testing framework -4. Improve documentation +4. ~~Improve documentation~~ ✅ Done (CONTRIBUTING.md, API_HOOKS.md created) ### Short Term (1-2 months) -1. Implement round-robin and random chain modes -2. UDP associate for DNS -3. Enhanced logging system +1. ~~Implement round-robin and random chain modes~~ ✅ Done +2. ~~UDP associate for DNS~~ ✅ Done +3. ~~Enhanced logging system~~ ✅ Done (health tracking metrics, per-process log files) 4. Security audit +5. ~~Proxy health checking and failover~~ ✅ Done ### Long Term (3-6 months) 1. GUI application 2. Performance optimizations -3. Advanced proxy authentication -4. Full IPv6 support +3. ~~Advanced proxy authentication~~ ✅ Done (SOCKS5/SOCKS4/HTTP auth) +4. ~~Full IPv6 support~~ ✅ Done (IPv6 proxy chains, dual-stack, DNS cache) ## Contributing @@ -233,4 +261,4 @@ If you want to contribute to any of these features: 4. Create a feature branch 5. Submit a pull request -See [CONTRIBUTING.md](CONTRIBUTING.md) for details (to be created). +See [CONTRIBUTING.md](CONTRIBUTING.md) for details. diff --git a/include/defines_generic.h b/include/defines_generic.h index b1f002e..a289988 100644 --- a/include/defines_generic.h +++ b/include/defines_generic.h @@ -136,6 +136,8 @@ typedef PXCH_UINT32 PXCH_UINT_MACHINE; #define PXCH_PROXY_TYPE_MASK 0x000000FF #define PXCH_PROXY_TYPE_INVALID 0x00000000 #define PXCH_PROXY_TYPE_SOCKS5 0x00000001 +#define PXCH_PROXY_TYPE_SOCKS4 0x00000002 +#define PXCH_PROXY_TYPE_HTTP 0x00000003 #define PXCH_PROXY_TYPE_DIRECT 0x000000FF #define PXCH_PROXY_STATE_MASK 0x0000FF00 @@ -153,6 +155,16 @@ typedef PXCH_UINT32 PXCH_UINT_MACHINE; #define SetProxyType(type, x) (x).dwTag = ((x).dwTag & ~PXCH_PROXY_TYPE_MASK) | PXCH_PROXY_TYPE_##type #define SetProxyState(type, x) (x).dwTag = ((x).dwTag & ~PXCH_PROXY_STATE_MASK) | PXCH_PROXY_STATE_##type +#define PXCH_CHAIN_TYPE_STRICT 0x00000001 +#define PXCH_CHAIN_TYPE_DYNAMIC 0x00000002 +#define PXCH_CHAIN_TYPE_RANDOM 0x00000003 +#define PXCH_CHAIN_TYPE_ROUND_ROBIN 0x00000004 + +#define PXCH_PROCESS_FILTER_NONE 0x00000000 +#define PXCH_PROCESS_FILTER_WHITELIST 0x00000001 +#define PXCH_PROCESS_FILTER_BLACKLIST 0x00000002 +#define PXCH_MAX_PROCESS_FILTER_NUM 8 + #define PXCH_RULE_TYPE_DOMAIN_KEYWORD 0x00000001 #define PXCH_RULE_TYPE_DOMAIN_SUFFIX 0x00000002 @@ -285,6 +297,27 @@ typedef struct _PXCH_PROXY_SOCKS5_DATA { PXCH_PASSWORD szPassword; } PXCH_PROXY_SOCKS5_DATA; +typedef struct _PXCH_PROXY_SOCKS4_DATA { + PXCH_UINT32 dwTag; + char Ws2_32_ConnectFunctionName[PXCH_MAX_DLL_FUNC_NAME_BUFSIZE]; + char Ws2_32_HandshakeFunctionName[PXCH_MAX_DLL_FUNC_NAME_BUFSIZE]; + PXCH_HOST_PORT HostPort; + int iAddrLen; + + PXCH_USERNAME szUsername; +} PXCH_PROXY_SOCKS4_DATA; + +typedef struct _PXCH_PROXY_HTTP_DATA { + PXCH_UINT32 dwTag; + char Ws2_32_ConnectFunctionName[PXCH_MAX_DLL_FUNC_NAME_BUFSIZE]; + char Ws2_32_HandshakeFunctionName[PXCH_MAX_DLL_FUNC_NAME_BUFSIZE]; + PXCH_HOST_PORT HostPort; + int iAddrLen; + + PXCH_USERNAME szUsername; + PXCH_PASSWORD szPassword; +} PXCH_PROXY_HTTP_DATA; + typedef union _PXCH_PROXY_DATA { PXCH_UINT32 dwTag; @@ -300,6 +333,8 @@ typedef union _PXCH_PROXY_DATA { } CommonHeader; PXCH_PROXY_SOCKS5_DATA Socks5; + PXCH_PROXY_SOCKS4_DATA Socks4; + PXCH_PROXY_HTTP_DATA Http; PXCH_PROXY_DIRECT_DATA Direct; } PXCH_PROXY_DATA; @@ -415,6 +450,51 @@ typedef struct _PROXYCHAINS_CONFIG { PXCH_UINT32 dwWillFirstTunnelUseIpv4; PXCH_UINT32 dwWillFirstTunnelUseIpv6; + + PXCH_UINT32 dwChainType; + PXCH_UINT32 dwChainLen; + PXCH_UINT32 dwRandomSeed; + PXCH_UINT32 dwRandomSeedSet; + + // DNS cache TTL in seconds (0 = no cache) + PXCH_UINT32 dwDnsCacheTtlSeconds; + + // Custom DNS server for proxy_dns_daemon mode (IPv4 sockaddr) + // When set, DNS queries are forwarded to this server instead of system default + PXCH_IP_ADDRESS CustomDnsServer; + PXCH_UINT32 dwCustomDnsServerPort; + PXCH_UINT32 dwHasCustomDnsServer; + + // Per-process log file: when set, log output is written to this file path + // Supports %PID% placeholder which is replaced with the process ID + wchar_t szLogFilePath[PXCH_MAX_CONFIG_FILE_PATH_BUFSIZE]; + PXCH_UINT32 dwHasLogFile; + + // Process name filtering: whitelist (process_only) or blacklist (process_except) + // When dwProcessFilterMode == 1 (whitelist): only inject into matching process names + // When dwProcessFilterMode == 2 (blacklist): inject into all except matching process names + // When dwProcessFilterMode == 0: no filtering (inject into all child processes) + PXCH_UINT32 dwProcessFilterMode; + PXCH_UINT32 dwProcessFilterCount; + wchar_t szProcessFilterNames[8][PXCH_MAX_HOSTNAME_BUFSIZE]; + + // Set proxy environment variables (HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, NO_PROXY) + // in child processes. This enables .NET, Java, Python, Go, curl and other apps + // that respect standard proxy environment variables to use the proxy even when + // DLL injection fails (e.g. protected processes, .NET CLR apps, services). + // Default: 1 (enabled) + PXCH_UINT32 dwSetProxyEnv; + + // Colored console log output using Windows console attributes. + // Each log level gets a distinct color: Red=Error/Critical, Yellow=Warning, + // Green=Info, Cyan=Debug, Gray=Verbose. Default: 1 (enabled) + PXCH_UINT32 dwLogColor; + + // Network traffic dump: save connection metadata (timestamp, PID, source, + // destination, proxy chain, result) to files in a directory structure. + // Default: 0 (disabled) + PXCH_UINT32 dwTrafficDump; + wchar_t szTrafficDumpDir[PXCH_MAX_CONFIG_FILE_PATH_BUFSIZE]; } PROXYCHAINS_CONFIG; #pragma pack(pop) @@ -458,8 +538,8 @@ static const wchar_t g_szChildDataSavingFileMappingPrefix[] = L"Local\\proxychai #define PXCH_HELPER_OS_DESC "win32" #endif -#define PXCH_HELPER_X64_COMMANDLINE_SUFFIX "proxychains_helper_" PXCH_HELPER_OS_DESC "_x64" PXCH_HOOKDLL_DEBUG_SUFFIX_NARROW ".exe --get-winapi-func-addr 2> " PXCH_REDIRECT_NULL_FILE -#define PXCH_HELPER_X86_COMMANDLINE_SUFFIX "proxychains_helper_" PXCH_HELPER_OS_DESC "_x86" PXCH_HOOKDLL_DEBUG_SUFFIX_NARROW ".exe --get-winapi-func-addr 2> " PXCH_REDIRECT_NULL_FILE +#define PXCH_HELPER_X64_COMMANDLINE_SUFFIX "proxychains_helper_" PXCH_HELPER_OS_DESC "_x64" PXCH_HOOKDLL_DEBUG_SUFFIX_NARROW ".exe\" --get-winapi-func-addr 2> " PXCH_REDIRECT_NULL_FILE +#define PXCH_HELPER_X86_COMMANDLINE_SUFFIX "proxychains_helper_" PXCH_HELPER_OS_DESC "_x86" PXCH_HOOKDLL_DEBUG_SUFFIX_NARROW ".exe\" --get-winapi-func-addr 2> " PXCH_REDIRECT_NULL_FILE #if defined(_M_X64) || defined(__x86_64__) diff --git a/include/embedded_resources.h b/include/embedded_resources.h new file mode 100644 index 0000000..f267df6 --- /dev/null +++ b/include/embedded_resources.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* embedded_resources.h + * Copyright (C) 2020 Feng Shun. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License version 2 for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program. If not, see + * . + */ +#pragma once + +// Resource IDs for embedded DLLs +#define IDR_HOOK_DLL_X64 101 +#define IDR_HOOK_DLL_X86 102 + +// Resource type name for embedded DLLs +#define PXCH_EMBEDDED_DLL_TYPE L"PXCH_DLL" +#define PXCH_EMBEDDED_DLL_TYPE_A "PXCH_DLL" diff --git a/include/hookdll_generic.h b/include/hookdll_generic.h index 0a01fc1..dc238eb 100644 --- a/include/hookdll_generic.h +++ b/include/hookdll_generic.h @@ -76,4 +76,5 @@ extern FP_ORIGINAL_FUNC2(Cygwin1, connect); DECLARE_HOOK_FUNC2(Cygwin1, connect); void Win32HookWs2_32(void); +void Win32HookWinHttp(void); void CygwinHook(void); \ No newline at end of file diff --git a/include/hookdll_util_ipc_win32.h b/include/hookdll_util_ipc_win32.h index 46ace91..c3eb7e6 100644 --- a/include/hookdll_util_ipc_win32.h +++ b/include/hookdll_util_ipc_win32.h @@ -54,6 +54,13 @@ typedef struct _PXCH_CHILD_DATA { ORIGINAL_FUNC_BACKUP_MEMBER2(Mswsock, ConnectEx); + ORIGINAL_FUNC_BACKUP_MEMBER2(WinHttp, Open); + ORIGINAL_FUNC_BACKUP_MEMBER2(WinHttp, SetOption); + ORIGINAL_FUNC_BACKUP_MEMBER2(WinINet, InternetOpenA); + ORIGINAL_FUNC_BACKUP_MEMBER2(WinINet, InternetOpenW); + ORIGINAL_FUNC_BACKUP_MEMBER2(WinINet, InternetSetOptionA); + ORIGINAL_FUNC_BACKUP_MEMBER2(WinINet, InternetSetOptionW); + } PXCH_CHILD_DATA; typedef struct _IPC_MSGHDR_CHILDDATA { diff --git a/include/hookdll_win32.h b/include/hookdll_win32.h index 78ad3e6..c929b15 100644 --- a/include/hookdll_win32.h +++ b/include/hookdll_win32.h @@ -273,4 +273,75 @@ PXCH_DLL_API int Ws2_32_DirectConnect(void* pTempData, PXCH_UINT_PTR s, const PX PXCH_DLL_API int Ws2_32_Socks5Connect(void* pTempData, PXCH_UINT_PTR s, const PXCH_PROXY_DATA* pProxy /* Mostly myself */, const PXCH_HOST_PORT* pHostPort, int iAddrLen); PXCH_DLL_API int Ws2_32_Socks5Handshake(void* pTempData, PXCH_UINT_PTR s, const PXCH_PROXY_DATA* pProxy /* Mostly myself */); + +// ============================================================================ +// WinHTTP API Hook Signatures (winhttp.dll) +// ============================================================================ + +// HINTERNET = void* +#define WinHttp_Open_SIGN(inside_identifier) void* (WINAPI inside_identifier)(\ + const wchar_t* pszAgentW,\ + DWORD dwAccessType,\ + const wchar_t* pszProxyName,\ + const wchar_t* pszProxyBypass,\ + DWORD dwFlags) + +#define WinHttp_SetOption_SIGN(inside_identifier) BOOL (WINAPI inside_identifier)(\ + void* hInternet,\ + DWORD dwOption,\ + void* lpBuffer,\ + DWORD dwBufferLength) + +extern FP_ORIGINAL_FUNC2(WinHttp, Open); +DECLARE_HOOK_FUNC2(WinHttp, Open); + +extern FP_ORIGINAL_FUNC2(WinHttp, SetOption); +DECLARE_HOOK_FUNC2(WinHttp, SetOption); + + +// ============================================================================ +// WinINet API Hook Signatures (wininet.dll) +// ============================================================================ + +#define WinINet_InternetOpenA_SIGN(inside_identifier) void* (WINAPI inside_identifier)(\ + const char* lpszAgent,\ + DWORD dwAccessType,\ + const char* lpszProxy,\ + const char* lpszProxyBypass,\ + DWORD dwFlags) + +#define WinINet_InternetOpenW_SIGN(inside_identifier) void* (WINAPI inside_identifier)(\ + const wchar_t* lpszAgent,\ + DWORD dwAccessType,\ + const wchar_t* lpszProxy,\ + const wchar_t* lpszProxyBypass,\ + DWORD dwFlags) + +#define WinINet_InternetSetOptionA_SIGN(inside_identifier) BOOL (WINAPI inside_identifier)(\ + void* hInternet,\ + DWORD dwOption,\ + void* lpBuffer,\ + DWORD dwBufferLength) + +#define WinINet_InternetSetOptionW_SIGN(inside_identifier) BOOL (WINAPI inside_identifier)(\ + void* hInternet,\ + DWORD dwOption,\ + void* lpBuffer,\ + DWORD dwBufferLength) + +extern FP_ORIGINAL_FUNC2(WinINet, InternetOpenA); +DECLARE_HOOK_FUNC2(WinINet, InternetOpenA); + +extern FP_ORIGINAL_FUNC2(WinINet, InternetOpenW); +DECLARE_HOOK_FUNC2(WinINet, InternetOpenW); + +extern FP_ORIGINAL_FUNC2(WinINet, InternetSetOptionA); +DECLARE_HOOK_FUNC2(WinINet, InternetSetOptionA); + +extern FP_ORIGINAL_FUNC2(WinINet, InternetSetOptionW); +DECLARE_HOOK_FUNC2(WinINet, InternetSetOptionW); + + +void Win32HookWinHttp(void); + extern UT_array* g_arrHeapAllocatedPointers; \ No newline at end of file diff --git a/proxychains.conf b/proxychains.conf index 496fef9..2d8709f 100644 --- a/proxychains.conf +++ b/proxychains.conf @@ -1,228 +1,237 @@ -# proxychains.conf FOR PROXYCHAINS.EXE ALPHA -# -# SOCKS5 tunneling proxifier with Fake DNS. -# - -# The option below identifies how the ProxyList is treated. -# only one option should be uncommented at time, -# otherwise the last appearing option will be accepted +# proxychains.conf FOR PROXYCHAINS.EXE # +# Transparent SOCKS5/SOCKS4/HTTP proxy chaining via DLL injection. +# Supports: SOCKS5 (user/pass), SOCKS4/4a (userid), HTTP CONNECT (Basic auth) # -# DYNAMIC_CHAIN IS NOT SUPPORTED AT PRESENT -#dynamic_chain -# DYNAMIC_CHAIN IS NOT SUPPORTED AT PRESENT -# -# Dynamic - Each connection will be done via chained proxies -# all proxies chained in the order as they appear in the list -# at least one proxy must be online to play in chain -# (dead proxies are skipped) -# otherwise EINTR is returned to the app -# + +# ==================================================================== +# CHAIN MODE +# ==================================================================== +# Choose ONE chain mode. Only the last uncommented option takes effect. # +# strict_chain - All proxies must be online; fail if any is dead (default) +# dynamic_chain - Skip dead proxies (up to 3 consecutive failures); +# fail only if ALL proxies are dead +# random_chain - Pick chain_len random proxies per connection +# round_robin_chain - Rotate through proxies sequentially per connection + strict_chain -# -# Strict - Each connection will be done via chained proxies -# all proxies chained in the order as they appear in the list -# all proxies must be online to play in chain -# otherwise EINTR is returned to the app -# -# RANDOM_CHAIN IS NOT SUPPORTED AT PRESENT +#dynamic_chain #random_chain -# RANDOM_CHAIN IS NOT SUPPORTED AT PRESENT -# -# Random - Each connection will be done via random proxy -# (or proxy chain, see chain_len) from the list. -# this option is good to test your IDS :) +#round_robin_chain + +# Number of proxies to use per connection (random_chain and round_robin_chain only) +# Default: 1. Must be <= number of proxies in [ProxyList] +#chain_len = 1 -# Make sense only if random_chain -#chain_len = 2 +# Fixed random seed for reproducible proxy selection (random_chain only). +# If not set, the seed is time-based (GetTickCount). +#random_seed = 42 -# Quiet mode (no output from library) +# ==================================================================== +# GENERAL OPTIONS +# ==================================================================== + +# Quiet mode (suppress all library output) #quiet_mode -# Proxy DNS requests using Fake IP - no leak for DNS data +# Proxy DNS requests using Fake IP - prevents DNS leaks proxy_dns -# Proxy DNS requests using UDP associate feature provided by SOCKS5 proxy -# NOT SUPPORTED AT PRESENT -#proxy_dns_udp_associate -# NOT SUPPORTED AT PRESENT - -# set the class A subnet number to usefor use of the internal remote DNS mapping -# we use the reserved 224.x.x.x range by default, -# if the proxified app does a DNS request, we will return an IP from that range. -# on further accesses to this ip we will send the saved DNS name to the proxy. -# in case some control-freak app checks the returned ip, and denies to -# connect, you can use another subnet, e.g. 10.x.x.x or 127.x.x.x. -# of course you should make sure that the proxified app does not need -# *real* access to this subnet. -# i.e. dont use the same subnet then in the localnet section -#remote_dns_subnet 127 -#remote_dns_subnet 10 +# ==================================================================== +# DNS RESOLUTION +# ==================================================================== + +# --- Remote DNS via Fake IP --- +# Class A subnet for internal DNS mapping. When an app does DNS, +# we return a fake IP from this range and map it to the real hostname. +# Default: 224 (224.x.x.x). Alternatives: 10, 127 +# Ensure the app doesn't need real access to this subnet. remote_dns_subnet 224 -# This enables you to set a CIDR block for the internal remote DNS mapping -# for example, remote_dns_subnet_cidr_v4 224.0.0.0/8 is equivalent to -# remote_dns_subnet 224. -# subnet mask format like 255.255.0.0 is not allowed here -# By default 224.0.0.0/8 and 250d::/16 +# Advanced: specify exact CIDR block for fake IPv4/IPv6 ranges. +# Prefix length must be < 31 for IPv4, < 127 for IPv6. #remote_dns_subnet_cidr_v4 224.0.0.0/8 -#remote_dns_subnet_cidr_v6 250d::/16 - -# Some timeouts in milliseconds -# Defaults: tcp_read_time_out 5000, tcp_connect_time_out 3000 -#tcp_read_time_out 15000 -#tcp_connect_time_out 8000 +#remote_dns_subnet_cidr_v6 fc00::/7 +# --- SOCKS5 UDP Associate DNS --- +# Route DNS queries through the SOCKS5 proxy using UDP ASSOCIATE (cmd 0x03). +# This prevents DNS leaks by sending A/AAAA queries through the proxy's +# UDP relay instead of resolving locally. Requires a SOCKS5 proxy. +# Also enables proxy_dns (fake IP) automatically. +#proxy_dns_udp_associate -# ==== Rules ==== -# You can control which IP range not to be proxied. -# First matched rule decides the target (PROXIED, DIRECT or BLOCK) of a -# connection. - -# localnet always has a "DIRECT" target, which means they will not be -# proxied. -# By default enable localnet for loopback address ranges -# RFC5735 Loopback address range +# --- Custom DNS Server --- +# Upstream DNS server IP for UDP Associate queries. +# Only IPv4 addresses supported. Default: 8.8.8.8:53 +# Format: dns_server IP:PORT or dns_server IP (port defaults to 53) +#dns_server 8.8.8.8:53 +#dns_server 1.1.1.1 + +# --- DNS Cache --- +# Cache DNS resolution results in memory with a TTL (in seconds). +# Reduces repeated DNS lookups for the same hostname. +# Thread-safe. Max 256 entries with FIFO eviction when full. +# 0 = disabled (default), recommended: 300 (5 minutes) +#dns_cache_ttl = 300 + +# ==================================================================== +# TIMEOUTS +# ==================================================================== +# Connection and read timeouts in milliseconds. +# tcp_connect_time_out: max time to establish TCP connection (default: 10000) +# tcp_read_time_out: max time to wait for data after connect (default: 15000) +tcp_read_time_out 15000 +tcp_connect_time_out 10000 + +# ==================================================================== +# PROCESS NAME FILTERING +# ==================================================================== +# Control which child processes get the proxy hook DLL injected. +# Use process_only (whitelist) OR process_except (blacklist), not both. +# Case-insensitive matching. Maximum 8 entries. +# +# Whitelist: only proxy these child processes +#process_only = curl.exe +#process_only = wget.exe +# +# Blacklist: proxy everything except these processes +#process_except = explorer.exe +#process_except = svchost.exe +#process_except = conhost.exe + +# ==================================================================== +# ROUTING RULES +# ==================================================================== +# Control which connections are PROXIED, DIRECT (bypass), or BLOCKED. +# First matched rule wins. + +# localnet: bypass proxy for local/private networks (legacy format) localnet 127.0.0.0/255.0.0.0 -# RFC1918 Private Address Ranges -# localnet 10.0.0.0/255.0.0.0 -# localnet 172.16.0.0/255.240.0.0 -# localnet 192.168.0.0/255.255.0.0 - - -# Example for localnet exclusion -## Exclude connections to 192.168.1.0/24 with port 80 -# localnet 192.168.1.0:80/255.255.255.0 - -## Exclude connections to 192.168.100.0/24 -# localnet 192.168.100.0/255.255.255.0 - -## Exclude connections to ANYwhere with port 80 -# localnet 0.0.0.0:80/0.0.0.0 - -# === Additional routing rules === -# These rules enables further control on websites/addresses which -# should be proxied or not. -# All rules can have an extra optional restriction of target port. -# However, if the proxied application uses gethostbyname() to do DNS -# query instead of getaddrinfo() series, this port part of the rule -# is invalidated. -# Three target is allowed: PROXY, DIRECT and BLOCK. -# -# - DOMAIN-KEYWORD rule, matching requests where the FQDN contains -# a specific string. -# e.g. DOMAIN-KEYWORD,google:80,DIRECT means any request to FQDN -# containing "google" with the target port 80 will NOT be proxied. -# -# - DOMAIN-SUFFIX rule, matching requests where the FQDN is suffixed -# with a specific string. -# e.g. DOMAIN-SUFFIX,.ru,PROXY means any FQDN that ends with ".ru" -# will be proxied. -# -# - DOMAIN-FULL rule, matching requests where the FQDN is exactly -# identical to a specific string. -# e.g. DOMAIN-FULL,duckduckgo.com,DIRECT means every request to -# "duckduckgo.com" (must entirely match) will NOT be proxied. -# -# - DOMAIN rule, the alias of DOMAIN-FULL rule. -# -# - IP-CIDR rule, matching requests to IP address in a CIDR block. -# Note if an FQDN is previously matched by a DOMAIN* rule, this rule -# is not applied to the resolved IPs. (Because fake IPs are used in -# this case) -# e.g. IP-CIDR,8.8.8.8/32,DIRECT -# IP-CIDR,250e::/16,PROXY -# IP-CIDR,[250c::]:443/16,PROXY -# IP-CIDR,10.0.0.0:80/8,DIRECT -# Note that "IP-CIDR,127.0.0.0/8,DIRECT" is equivalent to -# "localnet 127.0.0.0/255.0.0.0". -IP-CIDR,10.0.0.0/8,DIRECT -IP-CIDR,172.16.0.0/12,DIRECT -IP-CIDR,192.168.0.0/255.255.0.0,DIRECT -IP-CIDR,fe80::/8,DIRECT -# - PORT rule, matching requests to a target port. -# e.g. PORT,25,BLOCK -# -# - FINAL "rule", deciding the destiny of a request immediately. -# When this "rule" is used, it is not treated as a "match". -# If you want an unconditional match, try other rules instead, like -# IP-CIDR,0.0.0.0/0,PROXY or DOMAIN-KEYWORD,,PROXY. -# -# When no rules and no FINAL "rule" matched, a connection will be -# PROXIED by default, unless you specify option default_target. -# e.g. FINAL,PROXY - -# Will fake IP entries created by a descendant process be removed if this -# process exited? 1 by default. +localnet 10.0.0.0/255.0.0.0 +localnet 172.16.0.0/255.240.0.0 +localnet 192.168.0.0/255.255.0.0 + +# === Advanced routing rules (optional) === +# Rule types: DOMAIN-KEYWORD, DOMAIN-SUFFIX, DOMAIN-FULL (or DOMAIN), +# IP-CIDR, PORT, FINAL +# Targets: PROXY, DIRECT, BLOCK +# +# Examples: +# DOMAIN-KEYWORD,google,DIRECT # bypass proxy for google domains +# DOMAIN-SUFFIX,.onion,PROXY # force .onion through proxy +# DOMAIN-FULL,api.myservice.com,DIRECT +# IP-CIDR,8.8.8.8/32,DIRECT # bypass for Google DNS +# PORT,25,BLOCK # block SMTP + +# IPv6 link-local (RFC 4291) +#IP-CIDR,fe80::/10,DIRECT +# IPv6 loopback +#IP-CIDR,::1/128,DIRECT +# IPv6 unique-local (RFC 4193) +#IP-CIDR,fc00::/7,DIRECT + +# ==================================================================== +# DNS AND IP BEHAVIOR +# ==================================================================== + +# Remove fake IP entries when child process exits (default: 1) delete_fake_ip_after_child_exits 1 -# When no rules and no FINAL "rule" matched, a connection's default -# target. PROXY by default. +# Default target when no rules match: PROXY or DIRECT (default: PROXY) default_target PROXY -# Will the rules apply to the resolved IP if corresponding hostname -# did not match any rules? (FINAL is not counted as a rule) -# IF SO, SET THIS OPTION'S VALUE TO 0. 1 by default. +# Use fake IP when hostname doesn't match any rules (default: 1) use_fake_ip_when_hostname_not_matched 1 -# ===== Keep them as-is ===== - +# Internal IP mapping options (keep defaults unless you know what you're doing) map_resolved_ip_to_host 0 search_for_host_by_resolved_ip 0 -# or force_resolve_by_hosts_file 1 resolve_locally_if_match_hosts 1 -# ===== Keep them as-is - end ===== - -# Generate fake ips by FQDN hash - 1 (better to get rid of SSH safe -# warnings) -# Generate fake ips sequentially - 0 -# Default: 1 +# Generate fake IPs by FQDN hash (1) or sequentially (0) +# Hash mode avoids SSH host key warnings for same hostname (default: 1) gen_fake_ip_using_hashed_hostname 1 -# If your *first* proxy supports connecting to it by an IPv4 address -# (resolved by a hostname or specified manually), set its value to 1. -# This enables proxying IPv4 address. -# If disabled, fake IPv4 address is not returned. -# 1 by default +# Whether first proxy in chain accepts IPv4/IPv6 connections +# Set first_tunnel_uses_ipv6 = 1 to connect to IPv6 proxy servers first_tunnel_uses_ipv4 1 - -# If your *first* proxy supports connecting to it by an IPv6 address -# (resolved by a hostname or specified manually), set its value to 1. -# This enables proxying IPv6 address. -# If disabled, fake IPv6 address is not returned. -# 0 by default first_tunnel_uses_ipv6 0 -# Custom hosts file path -#custom_hosts_file_path C:\Some Path\hosts -#custom_hosts_file_path /etc/alternative/hosts - -# Custom log level. -# 600 - VERBOSE -# 500 - DEBUG -# 400 - INFO -# 300 - WARNING -# 200 - ERROR -# 100 - CRITICAL -# "log_level 200" is equivalent to "quiet_mode" +# ==================================================================== +# .NET / MANAGED APP PROXY SUPPORT +# ==================================================================== +# Set proxy environment variables (HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, NO_PROXY) +# and Windows Internet Settings registry proxy in the injected process. +# This enables .NET (HttpClient, WebRequest), Java, Python, Go, curl, wget, +# npm, git, and other apps that read standard proxy environment variables +# or system proxy settings to use the proxy automatically. +# +# Especially important for: +# - .NET Framework/Core apps (Software Fix, LMSA, etc.) using HttpClient/WebRequest +# - Windows Services running .NET code +# - WebView2/Chromium-based embedded browsers +# - Apps where DLL injection fails (protected processes, CLR apps) +# +# Default: 1 (enabled). Set to 0 to disable. +set_proxy_env 1 + +# ==================================================================== +# CUSTOM HOSTS FILE +# ==================================================================== +# Override DNS for specific hostnames. Environment variables are expanded. +#custom_hosts_file_path %USERPROFILE%\hosts + +# ==================================================================== +# LOGGING +# ==================================================================== +# Log level (higher = more verbose): +# 600 = VERBOSE, 500 = DEBUG, 400 = INFO (default) +# 300 = WARNING, 200 = ERROR, 100 = CRITICAL log_level 400 -# ProxyList format -# type host port [user pass] -# (values separated by 'tab' or 'blank') -# -# -# Examples: -# -# socks5 localhost 1080 -# socks5 localhost 1080 user password -# socks5 192.168.67.78 1080 lamer secret -# -# -# proxy types: socks5 -# ( auth types supported: "user/pass"-socks5 ) -# +# Colored console output: each log level gets a distinct color. +# Critical/Error = Red, Warning = Yellow, Info = Green, +# Debug = Cyan, Verbose = Gray +# Default: 1 (enabled). Set to 0 for plain text output. +log_color 1 + +# NOTE: log_file option is not currently supported and will cause a config error. +# Logging is always to console/IPC pipe. +#log_file C:\temp\proxychains.log + +# ==================================================================== +# TRAFFIC DUMP (OPTIONAL - disabled by default) +# ==================================================================== +# Save connection metadata to files in a directory structure. +# Each connection creates a file with: endpoint, timestamp, PID, +# socket handle, proxy chain routing result (PROXY/DIRECT/BLOCK), +# and WSA error code. +# +# This feature is COMPLETELY OPTIONAL. It only writes metadata to +# local files AFTER each connection is established — it does NOT +# intercept, modify, or delay any proxy traffic. Safe to leave disabled. +# +# Directory structure: traffic_dump_dir/YYYY-MM-DD/HHMMSS_PID_socket.txt +# +# Default: disabled. Uncomment and set a directory path to enable. +#traffic_dump_dir C:\temp\proxychains_traffic + +# ==================================================================== +# PROXY LIST +# ==================================================================== +# Format: type host port [user] [pass] +# Supported types: +# socks5 - SOCKS5 with optional username/password auth +# socks4 - SOCKS4/4a with optional userid +# http - HTTP CONNECT with optional Basic auth (user/pass) +# +# Examples: +# socks5 127.0.0.1 1080 +# socks5 my-proxy.com 1080 myuser mypassword +# socks4 192.168.1.100 1080 myuserid +# http proxy.corp.com 8080 +# http proxy.corp.com 3128 admin secret123 + [ProxyList] -socks5 localhost 1080 \ No newline at end of file +socks5 192.168.100.21 8889 \ No newline at end of file diff --git a/proxychains.exe/proxychains.exe.rc b/proxychains.exe/proxychains.exe.rc new file mode 100644 index 0000000..37ba5bd --- /dev/null +++ b/proxychains.exe/proxychains.exe.rc @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// proxychains.exe.rc - Resource script for embedding hook DLLs +// +// To embed DLLs, define PXCH_EMBED_DLLS when building. +// The CI workflow defines this after building all DLLs. +// For local development without embedding, this file is a no-op. + +#include "../include/embedded_resources.h" + +#ifdef PXCH_EMBED_DLLS + +#if defined(_M_X64) || defined(_WIN64) +// x64 build: embed both x64 and x86 hook DLLs +IDR_HOOK_DLL_X64 PXCH_EMBEDDED_DLL_TYPE_A "..\\win32_output\\proxychains_hook_x64.dll" +IDR_HOOK_DLL_X86 PXCH_EMBEDDED_DLL_TYPE_A "..\\win32_output\\proxychains_hook_x86.dll" +#else +// x86 build: embed only x86 hook DLL +IDR_HOOK_DLL_X86 PXCH_EMBEDDED_DLL_TYPE_A "..\\win32_output\\proxychains_hook_x86.dll" +#endif + +#endif // PXCH_EMBED_DLLS diff --git a/proxychains.exe/proxychains.exe.vcxproj b/proxychains.exe/proxychains.exe.vcxproj index 64f58f1..aaf666e 100644 --- a/proxychains.exe/proxychains.exe.vcxproj +++ b/proxychains.exe/proxychains.exe.vcxproj @@ -22,43 +22,34 @@ 16.0 {3F3F5D43-FEE4-4656-A992-D4D0371E303D} proxychainsexe - 7.0 - false + 10.0 Application true - v141_xp + v142 Unicode - false - false Application false - v141_xp + v142 true Unicode - false - false Application true - v141_xp + v142 Unicode - false - false Application false - v141_xp + v142 true Unicode - false - false @@ -189,6 +180,8 @@ true true true + true + true @@ -224,6 +217,8 @@ true true true + true + true @@ -275,6 +270,12 @@ + + + + + PXCH_EMBED_DLLS;%(PreprocessorDefinitions) + diff --git a/proxychains_common/proxychains_common.vcxproj b/proxychains_common/proxychains_common.vcxproj index ceb66e1..431a7af 100644 --- a/proxychains_common/proxychains_common.vcxproj +++ b/proxychains_common/proxychains_common.vcxproj @@ -22,43 +22,34 @@ 16.0 {D7744E4C-B1F8-495B-BDD4-DCDB0738B2BE} proxychainscommon - 7.0 - false + 10.0 StaticLibrary true - v141_xp + v142 Unicode - false - false StaticLibrary false - v141_xp + v142 true Unicode - false - false StaticLibrary true - v141_xp + v142 Unicode - false - false StaticLibrary false - v141_xp + v142 true Unicode - false - false diff --git a/proxychains_helper/proxychains_helper.vcxproj b/proxychains_helper/proxychains_helper.vcxproj index 1ef47e6..4b2efd7 100644 --- a/proxychains_helper/proxychains_helper.vcxproj +++ b/proxychains_helper/proxychains_helper.vcxproj @@ -68,42 +68,34 @@ 16.0 {91EF5C95-F872-47AE-B045-C39C7DBE2FF0} Configure - 7.0 + 10.0 Application true - v141_xp + v142 Unicode - false - false Application false - v141_xp + v142 true Unicode - false - false Application true - v141_xp + v142 Unicode - false - false Application false - v141_xp + v142 true Unicode - false - false diff --git a/proxychains_hook.dll/proxychains_hook.dll.vcxproj b/proxychains_hook.dll/proxychains_hook.dll.vcxproj index f9858f5..2a9af61 100644 --- a/proxychains_hook.dll/proxychains_hook.dll.vcxproj +++ b/proxychains_hook.dll/proxychains_hook.dll.vcxproj @@ -22,42 +22,34 @@ 16.0 {FDBD3C8E-2DDF-4BBB-A157-0CB0054D5782} proxychainshookdll - 7.0 + 10.0 DynamicLibrary true - v141_xp + v142 Unicode - false - false DynamicLibrary false - v141_xp + v142 true Unicode - false - false DynamicLibrary true - v141_xp + v142 Unicode - false - false DynamicLibrary false - v141_xp + v142 true Unicode - false - false @@ -167,6 +159,8 @@ true true libcmtd.lib;libcmt.lib + true + true @@ -194,6 +188,8 @@ true true libcmtd.lib;libcmt.lib + true + true @@ -210,6 +206,7 @@ + diff --git a/src/dll/hook_connect_win32.c b/src/dll/hook_connect_win32.c index a2cedd3..4d4aab8 100644 --- a/src/dll/hook_connect_win32.c +++ b/src/dll/hook_connect_win32.c @@ -43,6 +43,16 @@ static PXCH_PROXY_DIRECT_DATA g_proxyDirect; +// Proxy health tracking: per-proxy failure counters for health checking. +// Tracks consecutive failures; resets on successful connection. +// Used by dynamic chain mode to prioritize alive proxies. +static volatile LONG g_proxyFailureCount[PXCH_MAX_PROXY_NUM] = { 0 }; +static volatile LONG g_proxySuccessCount[PXCH_MAX_PROXY_NUM] = { 0 }; + +// Maximum consecutive failures before a proxy is considered dead. +// In dynamic chain mode, proxies exceeding this limit are skipped. +#define PXCH_PROXY_MAX_FAILURES 3 + static char pHostentPseudoTls[PXCH_TLS_END_W32HOSTENT]; typedef struct _PXCH_WS2_32_TEMP_DATA { @@ -69,13 +79,606 @@ typedef union _PXCH_TEMP_DATA { PXCH_WS2_32_TEMP_DATA Ws2_32_TempData; } PXCH_TEMP_DATA; + +// ============================================================================ +// DNS Cache: in-memory cache with TTL for resolved hostnames +// ============================================================================ + +#define PXCH_DNS_CACHE_MAX_ENTRIES 256 + +typedef struct _PXCH_DNS_CACHE_ENTRY { + PXCH_HOSTNAME Hostname; + PXCH_IP_ADDRESS Ipv4; + PXCH_IP_ADDRESS Ipv6; + BOOL bHasIpv4; + BOOL bHasIpv6; + DWORD dwExpireTickCount; // GetTickCount() value when entry expires +} PXCH_DNS_CACHE_ENTRY; + +static PXCH_DNS_CACHE_ENTRY g_DnsCache[PXCH_DNS_CACHE_MAX_ENTRIES]; +static volatile LONG g_dwDnsCacheCount = 0; +static CRITICAL_SECTION g_csDnsCache; +static volatile LONG g_lDnsCacheInitState = 0; // 0=uninit, 1=in-progress, 2=done + +static void DnsCacheInit(void) +{ + // Thread-safe one-time init using InterlockedCompareExchange (XP-compatible) + if (g_lDnsCacheInitState == 2) return; // fast path: already initialized + if (InterlockedCompareExchange(&g_lDnsCacheInitState, 1, 0) == 0) { + // We won the race — do the init + InitializeCriticalSection(&g_csDnsCache); + ZeroMemory(g_DnsCache, sizeof(g_DnsCache)); + g_dwDnsCacheCount = 0; + InterlockedExchange(&g_lDnsCacheInitState, 2); + } else { + // Another thread is initializing — spin wait + while (g_lDnsCacheInitState != 2) Sleep(1); + } +} + +// Look up a hostname in the DNS cache. Returns TRUE if found and not expired. +static BOOL DnsCacheLookup(const PXCH_HOSTNAME* pHostname, PXCH_IP_ADDRESS* pIpv4, PXCH_IP_ADDRESS* pIpv6, BOOL* pbHasIpv4, BOOL* pbHasIpv6) +{ + LONG i; + DWORD dwNow; + + if (g_lDnsCacheInitState != 2 || !g_pPxchConfig || g_pPxchConfig->dwDnsCacheTtlSeconds == 0) return FALSE; + + dwNow = GetTickCount(); + EnterCriticalSection(&g_csDnsCache); + + for (i = 0; i < g_dwDnsCacheCount && i < PXCH_DNS_CACHE_MAX_ENTRIES; i++) { + if (StrCmpIW(g_DnsCache[i].Hostname.szValue, pHostname->szValue) == 0) { + // Check expiration + if ((int)(dwNow - g_DnsCache[i].dwExpireTickCount) >= 0) { + // Expired: remove by swapping with last + if (i < g_dwDnsCacheCount - 1) { + g_DnsCache[i] = g_DnsCache[g_dwDnsCacheCount - 1]; + } + InterlockedDecrement(&g_dwDnsCacheCount); + LeaveCriticalSection(&g_csDnsCache); + return FALSE; + } + // Cache hit + if (pIpv4 && g_DnsCache[i].bHasIpv4) *pIpv4 = g_DnsCache[i].Ipv4; + if (pIpv6 && g_DnsCache[i].bHasIpv6) *pIpv6 = g_DnsCache[i].Ipv6; + if (pbHasIpv4) *pbHasIpv4 = g_DnsCache[i].bHasIpv4; + if (pbHasIpv6) *pbHasIpv6 = g_DnsCache[i].bHasIpv6; + LeaveCriticalSection(&g_csDnsCache); + FUNCIPCLOGV(L"DNS cache hit: %ls", pHostname->szValue); + return TRUE; + } + } + + LeaveCriticalSection(&g_csDnsCache); + return FALSE; +} + +// Add or update an entry in the DNS cache. +static void DnsCacheAdd(const PXCH_HOSTNAME* pHostname, const PXCH_IP_ADDRESS* pIpv4, const PXCH_IP_ADDRESS* pIpv6, BOOL bHasIpv4, BOOL bHasIpv6) +{ + LONG i; + LONG dwSlot; + + if (g_lDnsCacheInitState != 2 || !g_pPxchConfig || g_pPxchConfig->dwDnsCacheTtlSeconds == 0) return; + + EnterCriticalSection(&g_csDnsCache); + + // Check if already exists - update it + for (i = 0; i < g_dwDnsCacheCount && i < PXCH_DNS_CACHE_MAX_ENTRIES; i++) { + if (StrCmpIW(g_DnsCache[i].Hostname.szValue, pHostname->szValue) == 0) { + if (bHasIpv4 && pIpv4) { g_DnsCache[i].Ipv4 = *pIpv4; g_DnsCache[i].bHasIpv4 = TRUE; } + if (bHasIpv6 && pIpv6) { g_DnsCache[i].Ipv6 = *pIpv6; g_DnsCache[i].bHasIpv6 = TRUE; } + g_DnsCache[i].dwExpireTickCount = GetTickCount() + (g_pPxchConfig->dwDnsCacheTtlSeconds * 1000); + LeaveCriticalSection(&g_csDnsCache); + return; + } + } + + // Add new entry + dwSlot = g_dwDnsCacheCount; + if (dwSlot >= PXCH_DNS_CACHE_MAX_ENTRIES) { + // Evict oldest (slot 0) + MoveMemory(&g_DnsCache[0], &g_DnsCache[1], sizeof(PXCH_DNS_CACHE_ENTRY) * (PXCH_DNS_CACHE_MAX_ENTRIES - 1)); + dwSlot = PXCH_DNS_CACHE_MAX_ENTRIES - 1; + } else { + InterlockedIncrement(&g_dwDnsCacheCount); + } + + g_DnsCache[dwSlot].Hostname = *pHostname; + g_DnsCache[dwSlot].bHasIpv4 = bHasIpv4; + g_DnsCache[dwSlot].bHasIpv6 = bHasIpv6; + if (bHasIpv4 && pIpv4) g_DnsCache[dwSlot].Ipv4 = *pIpv4; + if (bHasIpv6 && pIpv6) g_DnsCache[dwSlot].Ipv6 = *pIpv6; + g_DnsCache[dwSlot].dwExpireTickCount = GetTickCount() + (g_pPxchConfig->dwDnsCacheTtlSeconds * 1000); + + FUNCIPCLOGV(L"DNS cache add: %ls (TTL=%lu s)", pHostname->szValue, (unsigned long)g_pPxchConfig->dwDnsCacheTtlSeconds); + + LeaveCriticalSection(&g_csDnsCache); +} + + +// ============================================================================ +// SOCKS5 UDP Associate: Forward DNS queries through proxy via SOCKS5 UDP +// ============================================================================ + +// Perform SOCKS5 UDP ASSOCIATE and send a DNS query through the UDP relay, +// then receive the DNS response. This prevents DNS leaks by routing DNS +// through the SOCKS5 proxy. +// +// Returns 0 on success, fills pRecvBuf/piRecvLen with the DNS response. +// Returns SOCKET_ERROR on failure. +static int Socks5UdpAssociateDnsQuery( + const PXCH_PROXY_DATA* pProxy, + const char* pDnsQueryBuf, int iDnsQueryLen, + const struct sockaddr_in* pDnsServerAddr, + char* pRecvBuf, int* piRecvLen, int iRecvBufSize) +{ + SOCKET sTcp = INVALID_SOCKET; + SOCKET sUdp = INVALID_SOCKET; + int iReturn; + int iWSALastError; + DWORD dwLastError; + char SendBuf[PXCH_MAX_HOSTNAME_BUFSIZE + 10]; + char RecvBuf[PXCH_MAX_HOSTNAME_BUFSIZE + 10]; + struct sockaddr_in ProxyAddr; + struct sockaddr_in UdpRelayAddr; + struct sockaddr_in LocalAddr; + char ServerBoundAddrType; + TIMEVAL Timeout = { 5, 0 }; // 5 second timeout for DNS + fd_set rfds; + + // Winsock is already initialized by the hook DLL (InitHook calls WSAStartup) + + // Step 1: Connect TCP to proxy for control channel + sTcp = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sTcp == INVALID_SOCKET) goto err_general; + + // Build proxy address + ZeroMemory(&ProxyAddr, sizeof(ProxyAddr)); + ProxyAddr.sin_family = AF_INET; + if (HostIsType(IPV4, pProxy->CommonHeader.HostPort)) { + const struct sockaddr_in* pSin = (const struct sockaddr_in*)&pProxy->CommonHeader.HostPort.IpPort.Sockaddr; + ProxyAddr.sin_addr = pSin->sin_addr; + ProxyAddr.sin_port = pSin->sin_port; + } else { + goto err_not_supported; + } + + iReturn = orig_fpWs2_32_connect(sTcp, (struct sockaddr*)&ProxyAddr, sizeof(ProxyAddr)); + if (iReturn == SOCKET_ERROR) goto err_general; + + // Step 2: SOCKS5 handshake on TCP + { + BOOL bUsePassword = pProxy->Socks5.szUsername[0] && pProxy->Socks5.szPassword[0]; + + if (send(sTcp, bUsePassword ? "\05\01\02" : "\05\01\00", 3, 0) == SOCKET_ERROR) goto err_general; + if (recv(sTcp, RecvBuf, 2, 0) != 2) goto err_general; + if ((!bUsePassword && RecvBuf[1] != '\00') || (bUsePassword && RecvBuf[1] != '\02')) goto err_auth; + + if (bUsePassword) { + size_t cbUsernameLength, cbPasswordLength; + char UserPassBuf[PXCH_MAX_USERNAME_BUFSIZE + PXCH_MAX_PASSWORD_BUFSIZE + 16]; + char* p = UserPassBuf; + + StringCchLengthA(pProxy->Socks5.szUsername, _countof(pProxy->Socks5.szUsername), &cbUsernameLength); + StringCchLengthA(pProxy->Socks5.szPassword, _countof(pProxy->Socks5.szPassword), &cbPasswordLength); + + *p++ = '\01'; + *p++ = (char)(unsigned char)cbUsernameLength; + CopyMemory(p, pProxy->Socks5.szUsername, cbUsernameLength); p += cbUsernameLength; + *p++ = (char)(unsigned char)cbPasswordLength; + CopyMemory(p, pProxy->Socks5.szPassword, cbPasswordLength); p += cbPasswordLength; + + if (send(sTcp, UserPassBuf, (int)(p - UserPassBuf), 0) == SOCKET_ERROR) goto err_general; + if (recv(sTcp, RecvBuf, 2, 0) != 2) goto err_general; + if (RecvBuf[1] != '\00') goto err_auth; + } + } + + // Step 3: Send UDP ASSOCIATE request (command 0x03) + // We request UDP associate with our local address 0.0.0.0:0 + CopyMemory(SendBuf, "\05\03\00\x01\x00\x00\x00\x00\x00\x00", 10); + if (send(sTcp, SendBuf, 10, 0) == SOCKET_ERROR) goto err_general; + + // Step 4: Receive UDP ASSOCIATE reply - get relay address + if (recv(sTcp, RecvBuf, 4, 0) != 4) goto err_general; + if (RecvBuf[1] != '\00') { + FUNCIPCLOGW(L"SOCKS5 UDP ASSOCIATE failed: server reply code %d", (int)(unsigned char)RecvBuf[1]); + goto err_data_invalid; + } + + ServerBoundAddrType = RecvBuf[3]; + ZeroMemory(&UdpRelayAddr, sizeof(UdpRelayAddr)); + UdpRelayAddr.sin_family = AF_INET; + + if (ServerBoundAddrType == '\01') { + // IPv4 relay address + if (recv(sTcp, RecvBuf, 6, 0) != 6) goto err_general; + CopyMemory(&UdpRelayAddr.sin_addr, RecvBuf, 4); + CopyMemory(&UdpRelayAddr.sin_port, RecvBuf + 4, 2); + + // If relay address is 0.0.0.0, use the proxy's address + if (UdpRelayAddr.sin_addr.s_addr == 0) { + UdpRelayAddr.sin_addr = ProxyAddr.sin_addr; + } + } else if (ServerBoundAddrType == '\03') { + // Hostname - not common for UDP relay, skip + if (recv(sTcp, RecvBuf, 1, 0) != 1) goto err_general; + { + int iLen = (unsigned char)RecvBuf[0]; + if (recv(sTcp, RecvBuf, iLen + 2, 0) != iLen + 2) goto err_general; + } + // Fall back to proxy address + UdpRelayAddr.sin_addr = ProxyAddr.sin_addr; + CopyMemory(&UdpRelayAddr.sin_port, RecvBuf + (unsigned char)RecvBuf[0], 2); + } else if (ServerBoundAddrType == '\04') { + // IPv6 - we only support IPv4 relay for now + if (recv(sTcp, RecvBuf, 18, 0) != 18) goto err_general; + goto err_not_supported; + } else { + goto err_data_invalid; + } + + FUNCIPCLOGD(L"SOCKS5 UDP ASSOCIATE: relay at %u.%u.%u.%u:%u", + ((unsigned char*)&UdpRelayAddr.sin_addr)[0], + ((unsigned char*)&UdpRelayAddr.sin_addr)[1], + ((unsigned char*)&UdpRelayAddr.sin_addr)[2], + ((unsigned char*)&UdpRelayAddr.sin_addr)[3], + (unsigned int)ntohs(UdpRelayAddr.sin_port)); + + // Step 5: Create UDP socket and send DNS query through relay + sUdp = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sUdp == INVALID_SOCKET) goto err_general; + + // Bind UDP socket to any port + ZeroMemory(&LocalAddr, sizeof(LocalAddr)); + LocalAddr.sin_family = AF_INET; + LocalAddr.sin_addr.s_addr = INADDR_ANY; + LocalAddr.sin_port = 0; + if (bind(sUdp, (struct sockaddr*)&LocalAddr, sizeof(LocalAddr)) == SOCKET_ERROR) goto err_general; + + // Build SOCKS5 UDP request header: + // +----+------+------+----------+----------+----------+ + // |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | + // +----+------+------+----------+----------+----------+ + // | 2 | 1 | 1 | Variable | 2 | Variable | + // +----+------+------+----------+----------+----------+ + { + char UdpBuf[4 + 4 + 2 + 1024]; // header + IPv4 addr + port + DNS query + int iUdpLen = 0; + + // RSV (2 bytes) + FRAG (1 byte) = 0x00 0x00 0x00 + UdpBuf[0] = 0x00; UdpBuf[1] = 0x00; UdpBuf[2] = 0x00; + // ATYP = 0x01 (IPv4) + UdpBuf[3] = 0x01; + // DST.ADDR = DNS server IPv4 + CopyMemory(UdpBuf + 4, &pDnsServerAddr->sin_addr, 4); + // DST.PORT = DNS server port (usually 53) + CopyMemory(UdpBuf + 8, &pDnsServerAddr->sin_port, 2); + iUdpLen = 10; + + // Append DNS query + if (iDnsQueryLen > (int)sizeof(UdpBuf) - iUdpLen) goto err_not_supported; + CopyMemory(UdpBuf + iUdpLen, pDnsQueryBuf, iDnsQueryLen); + iUdpLen += iDnsQueryLen; + + // Send to relay + iReturn = sendto(sUdp, UdpBuf, iUdpLen, 0, (struct sockaddr*)&UdpRelayAddr, sizeof(UdpRelayAddr)); + if (iReturn == SOCKET_ERROR) goto err_general; + } + + // Step 6: Receive DNS response from relay + { + char UdpRecvBuf[4 + 4 + 2 + 1024]; + int iUdpRecvLen; + struct sockaddr_in FromAddr; + int iFromLen = sizeof(FromAddr); + + FD_ZERO(&rfds); + FD_SET(sUdp, &rfds); + iReturn = select(-1, &rfds, NULL, NULL, &Timeout); + if (iReturn <= 0) { + FUNCIPCLOGW(L"SOCKS5 UDP ASSOCIATE: DNS response timeout"); + goto err_timeout; + } + + iUdpRecvLen = recvfrom(sUdp, UdpRecvBuf, sizeof(UdpRecvBuf), 0, (struct sockaddr*)&FromAddr, &iFromLen); + if (iUdpRecvLen == SOCKET_ERROR || iUdpRecvLen < 10) goto err_general; + + // Skip SOCKS5 UDP header (RSV 2 + FRAG 1 + ATYP 1 + ADDR 4 + PORT 2 = 10 for IPv4) + { + int iHeaderLen = 10; // Default for IPv4 + if (UdpRecvBuf[3] == 0x03) { + // Hostname type + iHeaderLen = 4 + 1 + (unsigned char)UdpRecvBuf[4] + 2; + } else if (UdpRecvBuf[3] == 0x04) { + // IPv6 + iHeaderLen = 4 + 16 + 2; + } + + if (iUdpRecvLen <= iHeaderLen) goto err_data_invalid; + + *piRecvLen = iUdpRecvLen - iHeaderLen; + if (*piRecvLen > iRecvBufSize) *piRecvLen = iRecvBufSize; + CopyMemory(pRecvBuf, UdpRecvBuf + iHeaderLen, *piRecvLen); + } + } + + // Success + closesocket(sUdp); + closesocket(sTcp); + return 0; + +err_not_supported: + FUNCIPCLOGW(L"SOCKS5 UDP ASSOCIATE: address type not supported"); + iWSALastError = WSAEAFNOSUPPORT; + dwLastError = ERROR_NOT_SUPPORTED; + goto err_return; + +err_auth: + FUNCIPCLOGW(L"SOCKS5 UDP ASSOCIATE: authentication failed"); + iWSALastError = WSAEACCES; + dwLastError = ERROR_ACCESS_DENIED; + goto err_return; + +err_data_invalid: + FUNCIPCLOGW(L"SOCKS5 UDP ASSOCIATE: invalid data from server"); + iWSALastError = WSAECONNRESET; + dwLastError = ERROR_INVALID_DATA; + goto err_return; + +err_timeout: + iWSALastError = WSAETIMEDOUT; + dwLastError = ERROR_TIMEOUT; + goto err_return; + +err_general: + iWSALastError = WSAGetLastError(); + dwLastError = GetLastError(); + +err_return: + if (sUdp != INVALID_SOCKET) closesocket(sUdp); + if (sTcp != INVALID_SOCKET) closesocket(sTcp); + WSASetLastError(iWSALastError); + SetLastError(dwLastError); + return SOCKET_ERROR; +} + + +// Build a DNS A record query for the given hostname. +// Returns the query length in bytes, or 0 on error. +static int BuildDnsQuery(char* pBuf, int iBufSize, const char* szHostname, BOOL bAAAA) +{ + int iLen = 0; + const char* pLabel; + const char* pDot; + size_t cbLabelLen; + + if (iBufSize < 12 + 2 + 2 + 2) return 0; + + // DNS Header: ID=0x1234, QR=0, OPCODE=0, RD=1, QDCOUNT=1 + pBuf[0] = 0x12; pBuf[1] = 0x34; // Transaction ID + pBuf[2] = 0x01; pBuf[3] = 0x00; // Flags: RD=1 + pBuf[4] = 0x00; pBuf[5] = 0x01; // Questions: 1 + pBuf[6] = 0x00; pBuf[7] = 0x00; // Answers: 0 + pBuf[8] = 0x00; pBuf[9] = 0x00; // Authority: 0 + pBuf[10] = 0x00; pBuf[11] = 0x00; // Additional: 0 + iLen = 12; + + // QNAME: hostname as labels + pLabel = szHostname; + while (*pLabel) { + pDot = pLabel; + while (*pDot && *pDot != '.') pDot++; + cbLabelLen = pDot - pLabel; + if (cbLabelLen == 0 || cbLabelLen > 63) return 0; + if (iLen + 1 + (int)cbLabelLen + 5 > iBufSize) return 0; + + pBuf[iLen++] = (char)(unsigned char)cbLabelLen; + CopyMemory(pBuf + iLen, pLabel, cbLabelLen); + iLen += (int)cbLabelLen; + + pLabel = *pDot ? pDot + 1 : pDot; + } + pBuf[iLen++] = 0x00; // Root label + + // QTYPE: A (0x0001) or AAAA (0x001C) + pBuf[iLen++] = 0x00; + pBuf[iLen++] = bAAAA ? 0x1C : 0x01; + + // QCLASS: IN (0x0001) + pBuf[iLen++] = 0x00; + pBuf[iLen++] = 0x01; + + return iLen; +} + + +// Parse a DNS response and extract the first A/AAAA record IP address. +// Returns TRUE if an IP was extracted, FALSE otherwise. +static BOOL ParseDnsResponse(const char* pBuf, int iLen, PXCH_IP_ADDRESS* pIpv4, PXCH_IP_ADDRESS* pIpv6, BOOL* pbHasIpv4, BOOL* pbHasIpv6) +{ + int iOffset; + int iQdCount, iAnCount; + int i; + + *pbHasIpv4 = FALSE; + *pbHasIpv6 = FALSE; + + if (iLen < 12) return FALSE; + + // Check response flag + if (!(pBuf[2] & 0x80)) return FALSE; // QR must be 1 (response) + + iQdCount = ((unsigned char)pBuf[4] << 8) | (unsigned char)pBuf[5]; + iAnCount = ((unsigned char)pBuf[6] << 8) | (unsigned char)pBuf[7]; + + if (iAnCount == 0) return FALSE; + + // Skip questions + iOffset = 12; + for (i = 0; i < iQdCount && iOffset < iLen; i++) { + // Skip QNAME + while (iOffset < iLen && pBuf[iOffset] != 0) { + if ((unsigned char)pBuf[iOffset] >= 0xC0) { + iOffset += 2; // Pointer + goto qname_done; + } + iOffset += 1 + (unsigned char)pBuf[iOffset]; + } + iOffset++; // Skip null terminator +qname_done: + iOffset += 4; // QTYPE + QCLASS + } + + // Parse answers + for (i = 0; i < iAnCount && iOffset < iLen; i++) { + unsigned short usType, usRdLength; + + // Skip NAME (may be pointer) + if (iOffset >= iLen) break; + if ((unsigned char)pBuf[iOffset] >= 0xC0) { + iOffset += 2; + } else { + while (iOffset < iLen && pBuf[iOffset] != 0) { + iOffset += 1 + (unsigned char)pBuf[iOffset]; + } + iOffset++; + } + + if (iOffset + 10 > iLen) break; + + usType = ((unsigned char)pBuf[iOffset] << 8) | (unsigned char)pBuf[iOffset + 1]; + // Skip CLASS (2) + TTL (4) + usRdLength = ((unsigned char)pBuf[iOffset + 8] << 8) | (unsigned char)pBuf[iOffset + 9]; + iOffset += 10; + + if (iOffset + usRdLength > iLen) break; + + if (usType == 1 && usRdLength == 4 && !*pbHasIpv4) { + // A record + struct sockaddr_in* pSin; + ZeroMemory(pIpv4, sizeof(PXCH_IP_ADDRESS)); + pIpv4->CommonHeader.wTag = PXCH_HOST_TYPE_IPV4; + pSin = (struct sockaddr_in*)&pIpv4->Sockaddr; + pSin->sin_family = AF_INET; + CopyMemory(&pSin->sin_addr, pBuf + iOffset, 4); + *pbHasIpv4 = TRUE; + } else if (usType == 28 && usRdLength == 16 && !*pbHasIpv6) { + // AAAA record + struct sockaddr_in6* pSin6; + ZeroMemory(pIpv6, sizeof(PXCH_IP_ADDRESS)); + pIpv6->CommonHeader.wTag = PXCH_HOST_TYPE_IPV6; + pSin6 = (struct sockaddr_in6*)&pIpv6->Sockaddr; + pSin6->sin6_family = AF_INET6; + CopyMemory(&pSin6->sin6_addr, pBuf + iOffset, 16); + *pbHasIpv6 = TRUE; + } + + iOffset += usRdLength; + + if (*pbHasIpv4 && *pbHasIpv6) break; + } + + return *pbHasIpv4 || *pbHasIpv6; +} + + +// Resolve a hostname through SOCKS5 UDP ASSOCIATE. +// Uses the first SOCKS5 proxy in the chain. Falls back to direct resolution on error. +// Returns TRUE if resolution succeeded via UDP associate. +static BOOL ResolveDnsViaSocks5UdpAssociate(const PXCH_HOSTNAME* pHostname, PXCH_IP_ADDRESS* pIpv4, PXCH_IP_ADDRESS* pIpv6, BOOL* pbHasIpv4, BOOL* pbHasIpv6) +{ + PXCH_UINT32 dw; + const PXCH_PROXY_DATA* pSocks5Proxy = NULL; + struct sockaddr_in DnsServerAddr; + char szHostnameA[PXCH_MAX_HOSTNAME_BUFSIZE]; + char DnsQueryBuf[512]; + char DnsRespBuf[1024]; + int iQueryLen; + int iRespLen = 0; + int iReturn; + + *pbHasIpv4 = FALSE; + *pbHasIpv6 = FALSE; + + if (!g_pPxchConfig || !g_pPxchConfig->dwWillUseUdpAssociateAsRemoteDns) return FALSE; + + // Find first SOCKS5 proxy + for (dw = 0; dw < g_pPxchConfig->dwProxyNum; dw++) { + if (ProxyIsType(SOCKS5, PXCH_CONFIG_PROXY_ARR(g_pPxchConfig)[dw])) { + pSocks5Proxy = &PXCH_CONFIG_PROXY_ARR(g_pPxchConfig)[dw]; + break; + } + } + if (!pSocks5Proxy) { + FUNCIPCLOGW(L"UDP ASSOCIATE: No SOCKS5 proxy configured"); + return FALSE; + } + + // Set DNS server address + ZeroMemory(&DnsServerAddr, sizeof(DnsServerAddr)); + DnsServerAddr.sin_family = AF_INET; + if (g_pPxchConfig->dwHasCustomDnsServer) { + const struct sockaddr_in* pCustomDns = (const struct sockaddr_in*)&g_pPxchConfig->CustomDnsServer.Sockaddr; + DnsServerAddr.sin_addr = pCustomDns->sin_addr; + DnsServerAddr.sin_port = htons((unsigned short)g_pPxchConfig->dwCustomDnsServerPort); + } else { + // Default: 8.8.8.8:53 + DnsServerAddr.sin_addr.s_addr = htonl(0x08080808); + DnsServerAddr.sin_port = htons(53); + } + + // Convert hostname to narrow string + WideCharToMultiByte(CP_ACP, 0, pHostname->szValue, -1, szHostnameA, sizeof(szHostnameA), NULL, NULL); + + // Query A record + iQueryLen = BuildDnsQuery(DnsQueryBuf, sizeof(DnsQueryBuf), szHostnameA, FALSE); + if (iQueryLen > 0) { + iReturn = Socks5UdpAssociateDnsQuery(pSocks5Proxy, DnsQueryBuf, iQueryLen, &DnsServerAddr, DnsRespBuf, &iRespLen, sizeof(DnsRespBuf)); + if (iReturn == 0 && iRespLen > 0) { + PXCH_IP_ADDRESS TempIpv4, TempIpv6; + BOOL bTempHasIpv4, bTempHasIpv6; + if (ParseDnsResponse(DnsRespBuf, iRespLen, &TempIpv4, &TempIpv6, &bTempHasIpv4, &bTempHasIpv6)) { + if (bTempHasIpv4) { *pIpv4 = TempIpv4; *pbHasIpv4 = TRUE; } + } + } + } + + // Query AAAA record + iQueryLen = BuildDnsQuery(DnsQueryBuf, sizeof(DnsQueryBuf), szHostnameA, TRUE); + if (iQueryLen > 0) { + iReturn = Socks5UdpAssociateDnsQuery(pSocks5Proxy, DnsQueryBuf, iQueryLen, &DnsServerAddr, DnsRespBuf, &iRespLen, sizeof(DnsRespBuf)); + if (iReturn == 0 && iRespLen > 0) { + PXCH_IP_ADDRESS TempIpv4, TempIpv6; + BOOL bTempHasIpv4, bTempHasIpv6; + if (ParseDnsResponse(DnsRespBuf, iRespLen, &TempIpv4, &TempIpv6, &bTempHasIpv4, &bTempHasIpv6)) { + if (bTempHasIpv6) { *pIpv6 = TempIpv6; *pbHasIpv6 = TRUE; } + } + } + } + + if (*pbHasIpv4 || *pbHasIpv6) { + FUNCIPCLOGI(L"DNS via SOCKS5 UDP ASSOCIATE: %ls resolved (IPv4=%d, IPv6=%d)", pHostname->szValue, *pbHasIpv4, *pbHasIpv6); + + // Add to cache + DnsCacheAdd(pHostname, pIpv4, pIpv6, *pbHasIpv4, *pbHasIpv6); + return TRUE; + } + + FUNCIPCLOGW(L"DNS via SOCKS5 UDP ASSOCIATE: failed to resolve %ls", pHostname->szValue); + return FALSE; +} + + static BOOL ResolveByHostsFile(PXCH_IP_ADDRESS* pIp, const PXCH_HOSTNAME* pHostname, int iFamily) { PXCH_UINT32 i; PXCH_IP_ADDRESS* pCurrentIp; for (i = 0; i < g_pPxchConfig->dwHostsEntryNum; i++) { - if (StrCmpW(PXCH_CONFIG_HOSTS_ENTRY_ARR_G[i].Hostname.szValue, pHostname->szValue) == 0) { + if (StrCmpIW(PXCH_CONFIG_HOSTS_ENTRY_ARR_G[i].Hostname.szValue, pHostname->szValue) == 0) { pCurrentIp = &PXCH_CONFIG_HOSTS_ENTRY_ARR_G[i].Ip; if (iFamily == AF_UNSPEC || (iFamily == AF_INET && pCurrentIp->CommonHeader.wTag == PXCH_HOST_TYPE_IPV4) @@ -343,8 +946,62 @@ void PrintConnectResultAndFreeResources(const WCHAR* szPrintPrefix, PXCH_UINT_PT FUNCIPCLOGD(L"%ls ret: %d, wsa last error: %ls", chPrintBuf, iReturn, FormatErrorToStr(iWSALastError)); } } + + // Traffic dump (optional): write connection metadata to local file AFTER + // the connection result. Does NOT intercept or modify proxy traffic. + if (g_pPxchConfig && g_pPxchConfig->dwTrafficDump && g_pPxchConfig->szTrafficDumpDir[0]) { + WCHAR szDumpPath[MAX_PATH]; + WCHAR szDumpSubDir[MAX_PATH]; + SYSTEMTIME st; + HANDLE hDumpFile; + DWORD dwWritten; + char szDumpLine[1024]; + int iDumpLen; + + GetLocalTime(&st); + + // Create date subdirectory: traffic_dump_dir/YYYY-MM-DD/ + StringCchPrintfW(szDumpSubDir, MAX_PATH, L"%ls\\%04hu-%02hu-%02hu", + g_pPxchConfig->szTrafficDumpDir, st.wYear, st.wMonth, st.wDay); + CreateDirectoryW(g_pPxchConfig->szTrafficDumpDir, NULL); + CreateDirectoryW(szDumpSubDir, NULL); + + // File: traffic_dump_dir/YYYY-MM-DD/HHMMSS_PID_socket.txt + StringCchPrintfW(szDumpPath, MAX_PATH, L"%ls\\%02hu%02hu%02hu_%lu_%lu.txt", + szDumpSubDir, st.wHour, st.wMinute, st.wSecond, + (unsigned long)GetCurrentProcessId(), (unsigned long)SocketHandle); + + hDumpFile = CreateFileW(szDumpPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hDumpFile != INVALID_HANDLE_VALUE) { + iDumpLen = WideCharToMultiByte(CP_UTF8, 0, chPrintBuf, -1, szDumpLine, sizeof(szDumpLine) - 64, NULL, NULL); + if (iDumpLen > 0) { + WCHAR szMetaW[256]; + char szMeta[512]; + int iMetaLen; + HRESULT hr; + // Write connection info line + WriteFile(hDumpFile, szDumpLine, iDumpLen - 1, &dwWritten, NULL); + // Write metadata: timestamp, result, WSA error + hr = StringCchPrintfW(szMetaW, _countof(szMetaW), + L"\r\nTimestamp: %04hu-%02hu-%02hu %02hu:%02hu:%02hu.%03hu\r\nPID: %lu\r\nSocket: %lu\r\nResult: %d\r\nTarget: %ls\r\nWSAError: %d\r\n", + st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, + (unsigned long)GetCurrentProcessId(), (unsigned long)SocketHandle, + iReturn, g_szRuleTargetDesc[dwTarget], iWSALastError); + if (SUCCEEDED(hr)) { + iMetaLen = WideCharToMultiByte(CP_UTF8, 0, szMetaW, -1, szMeta, sizeof(szMeta), NULL, NULL); + if (iMetaLen > 0) { + WriteFile(hDumpFile, szMeta, iMetaLen - 1, &dwWritten, NULL); + } + } + } + CloseHandle(hDumpFile); + } + } } +// Call original connect() without blocking semantics. +// Used for direct connections that bypass the proxy chain. int Ws2_32_OriginalConnect(void* pTempData, PXCH_UINT_PTR s, const void* pAddr, int iAddrLen) { int iReturn; @@ -367,6 +1024,8 @@ int Ws2_32_OriginalConnect(void* pTempData, PXCH_UINT_PTR s, const void* pAddr, return iReturn; } +// Connect to a proxy server, handling non-blocking sockets by using select() with timeout. +// Returns 0 on success, SOCKET_ERROR on failure (timeout, connection error, etc.) int Ws2_32_BlockConnect(void* pTempData, PXCH_UINT_PTR s, const void* pAddr, int iAddrLen) { int iReturn; @@ -421,7 +1080,7 @@ int Ws2_32_BlockConnect(void* pTempData, PXCH_UINT_PTR s, const void* pAddr, int goto err_return; err_timeout: - FUNCIPCLOGW(L"select() timeout"); + FUNCIPCLOGW(L"Proxy connection timeout (%lu ms) connecting to %ls", (unsigned long)g_pPxchConfig->dwProxyConnectionTimeoutMillisecond, FormatHostPortToStr(pAddr, iAddrLen)); iWSALastError = WSAETIMEDOUT; dwLastError = ERROR_TIMEOUT; goto err_return; @@ -438,6 +1097,8 @@ int Ws2_32_BlockConnect(void* pTempData, PXCH_UINT_PTR s, const void* pAddr, int return SOCKET_ERROR; } +// Send all bytes in buffer, retrying until complete or error. +// Used for proxy handshake and CONNECT request data. int Ws2_32_LoopSend(void* pTempData, PXCH_UINT_PTR s, const char* SendBuf, int iLength) { int iReturn; @@ -505,6 +1166,8 @@ int Ws2_32_LoopSend(void* pTempData, PXCH_UINT_PTR s, const char* SendBuf, int i return SOCKET_ERROR; } +// Receive exactly iLength bytes, using select() with timeout for non-blocking sockets. +// Returns 0 on success, SOCKET_ERROR on failure (timeout, connection closed, etc.) int Ws2_32_LoopRecv(void* pTempData, PXCH_UINT_PTR s, char* RecvBuf, int iLength) { int iReturn; @@ -560,7 +1223,7 @@ int Ws2_32_LoopRecv(void* pTempData, PXCH_UINT_PTR s, char* RecvBuf, int iLength goto err_return; err_timeout: - FUNCIPCLOGW(L"select() timeout"); + FUNCIPCLOGW(L"Proxy handshake recv timeout (%lu ms)", (unsigned long)g_pPxchConfig->dwProxyConnectionTimeoutMillisecond); iWSALastError = WSAETIMEDOUT; dwLastError = ERROR_TIMEOUT; goto err_return; @@ -586,6 +1249,7 @@ int Ws2_32_LoopRecv(void* pTempData, PXCH_UINT_PTR s, char* RecvBuf, int iLength PXCH_DLL_API int Ws2_32_DirectConnect(void* pTempData, PXCH_UINT_PTR s, const PXCH_PROXY_DATA* pProxy /* Mostly myself */, const PXCH_HOST_PORT* pHostPort, int iAddrLen) { int iReturn; + int iTargetFamily; ODBGSTRLOGD(L"Ws2_32_DirectConnect 0 %p", pHostPort); if (HostIsType(INVALID, *pHostPort)) { @@ -604,6 +1268,7 @@ PXCH_DLL_API int Ws2_32_DirectConnect(void* pTempData, PXCH_UINT_PTR s, const PX // FUNCIPCLOGW(L"Warning connecting directly: address is hostname."); + // Support dual-stack: try matching original family first, then fall back to any AddrInfoHints.ai_family = AF_UNSPEC; AddrInfoHints.ai_flags = 0; AddrInfoHints.ai_protocol = IPPROTO_TCP; @@ -614,15 +1279,18 @@ PXCH_DLL_API int Ws2_32_DirectConnect(void* pTempData, PXCH_UINT_PTR s, const PX return SOCKET_ERROR; } + // Prefer matching original address family for dual-stack compatibility + iTargetFamily = ((const PXCH_TEMP_DATA*)pTempData)->CommonHeader.iOriginalAddrFamily; for (pTempAddrInfo = pAddrInfo; pTempAddrInfo; pTempAddrInfo = pTempAddrInfo->ai_next) { - if (pTempAddrInfo->ai_family == ((const PXCH_TEMP_DATA*)pTempData)->CommonHeader.iOriginalAddrFamily) { + if (pTempAddrInfo->ai_family == iTargetFamily) { break; } } + // Fall back to any available address family (enables IPv6 proxy with IPv4 targets) if (pTempAddrInfo == NULL) { - WSASetLastError(WSAEADDRNOTAVAIL); - return SOCKET_ERROR; + pTempAddrInfo = pAddrInfo; + FUNCIPCLOGD(L"Ws2_32_DirectConnect: original AF %d not found, using AF %d (dual-stack fallback)", iTargetFamily, pTempAddrInfo->ai_family); } CopyMemory(&NewHostPort, pTempAddrInfo->ai_addr, pTempAddrInfo->ai_addrlen); @@ -636,6 +1304,236 @@ PXCH_DLL_API int Ws2_32_DirectConnect(void* pTempData, PXCH_UINT_PTR s, const PX return Ws2_32_BlockConnect(pTempData, s, pHostPort, iAddrLen); } +PXCH_DLL_API int Ws2_32_Socks4Connect(void* pTempData, PXCH_UINT_PTR s, const PXCH_PROXY_DATA* pProxy /* Mostly myself */, const PXCH_HOST_PORT* pHostPort, int iAddrLen) +{ + const struct sockaddr_in* pSockAddrIpv4; + const PXCH_HOSTNAME_PORT* pAddrHostname; + int iReturn; + char SendBuf[PXCH_MAX_HOSTNAME_BUFSIZE + 32]; + char RecvBuf[8]; + int iWSALastError; + DWORD dwLastError; + int iSendLen; + size_t cbUsernameLength; + const char* szUsername; + + FUNCIPCLOGD(L"Ws2_32_Socks4Connect(%ls)", FormatHostPortToStr(pHostPort, iAddrLen)); + + szUsername = pProxy->Socks4.szUsername; + StringCchLengthA(szUsername, PXCH_MAX_USERNAME_BUFSIZE, &cbUsernameLength); + + if (HostIsType(IPV4, *pHostPort)) { + pSockAddrIpv4 = (const struct sockaddr_in*)pHostPort; + + /* SOCKS4 CONNECT: VN=4, CD=1, DSTPORT, DSTIP, USERID, NULL */ + SendBuf[0] = 0x04; /* VN */ + SendBuf[1] = 0x01; /* CD = CONNECT */ + CopyMemory(SendBuf + 2, &pSockAddrIpv4->sin_port, 2); + CopyMemory(SendBuf + 4, &pSockAddrIpv4->sin_addr, 4); + CopyMemory(SendBuf + 8, szUsername, cbUsernameLength); + SendBuf[8 + cbUsernameLength] = '\0'; + iSendLen = 9 + (int)cbUsernameLength; + + if ((iReturn = Ws2_32_LoopSend(pTempData, s, SendBuf, iSendLen)) == SOCKET_ERROR) goto err_general; + } else if (HostIsType(HOSTNAME, *pHostPort)) { + char szHostnameNarrow[PXCH_MAX_HOSTNAME_BUFSIZE]; + size_t cbHostnameLength; + + pAddrHostname = (const PXCH_HOSTNAME_PORT*)pHostPort; + StringCchPrintfA(szHostnameNarrow, _countof(szHostnameNarrow), "%ls", pAddrHostname->szValue); + StringCchLengthA(szHostnameNarrow, _countof(szHostnameNarrow), &cbHostnameLength); + + /* SOCKS4a: set IP to 0.0.0.x (x != 0), append hostname after userid null */ + SendBuf[0] = 0x04; /* VN */ + SendBuf[1] = 0x01; /* CD = CONNECT */ + CopyMemory(SendBuf + 2, &pHostPort->HostnamePort.wPort, 2); + SendBuf[4] = 0; SendBuf[5] = 0; SendBuf[6] = 0; SendBuf[7] = 1; /* 0.0.0.1 */ + CopyMemory(SendBuf + 8, szUsername, cbUsernameLength); + SendBuf[8 + cbUsernameLength] = '\0'; + CopyMemory(SendBuf + 9 + cbUsernameLength, szHostnameNarrow, cbHostnameLength); + SendBuf[9 + cbUsernameLength + cbHostnameLength] = '\0'; + iSendLen = 10 + (int)cbUsernameLength + (int)cbHostnameLength; + + if ((iReturn = Ws2_32_LoopSend(pTempData, s, SendBuf, iSendLen)) == SOCKET_ERROR) goto err_general; + } else { + goto err_not_supported; + } + + if ((iReturn = Ws2_32_LoopRecv(pTempData, s, RecvBuf, 8)) == SOCKET_ERROR) goto err_general; + if (RecvBuf[1] != 0x5A) goto err_data_invalid; + + SetLastError(NO_ERROR); + WSASetLastError(NO_ERROR); + return 0; + +err_not_supported: + FUNCIPCLOGW(L"Error connecting through Socks4: only IPv4 and hostname (SOCKS4a) addresses supported."); + iReturn = SOCKET_ERROR; + dwLastError = ERROR_NOT_SUPPORTED; + iWSALastError = WSAEAFNOSUPPORT; + goto err_return; + +err_data_invalid: + FUNCIPCLOGW(L"Socks4 connection rejected by server (reply code: 0x%02X)", (unsigned char)RecvBuf[1]); + iReturn = SOCKET_ERROR; + dwLastError = ERROR_ACCESS_DENIED; + iWSALastError = WSAECONNREFUSED; + goto err_return; + +err_general: + iWSALastError = WSAGetLastError(); + dwLastError = GetLastError(); +err_return: + shutdown(s, SD_BOTH); + WSASetLastError(iWSALastError); + SetLastError(dwLastError); + return iReturn; +} + +PXCH_DLL_API int Ws2_32_Socks4Handshake(void* pTempData, PXCH_UINT_PTR s, const PXCH_PROXY_DATA* pProxy /* Mostly myself */) +{ + /* SOCKS4 has no separate handshake phase - everything is done in Connect */ + FUNCIPCLOGI(L"<> %ls", FormatHostPortToStr(&pProxy->CommonHeader.HostPort, pProxy->CommonHeader.iAddrLen)); + + SetLastError(NO_ERROR); + WSASetLastError(NO_ERROR); + return 0; +} + +PXCH_DLL_API int Ws2_32_HttpConnect(void* pTempData, PXCH_UINT_PTR s, const PXCH_PROXY_DATA* pProxy /* Mostly myself */, const PXCH_HOST_PORT* pHostPort, int iAddrLen) +{ + int iReturn; + char SendBuf[PXCH_MAX_HOSTNAME_BUFSIZE + 256]; + char RecvBuf[1024]; + char szHostPort[PXCH_MAX_HOSTNAME_BUFSIZE + 16]; + int iWSALastError; + DWORD dwLastError; + int iSendLen; + PXCH_UINT16 wPort; + + FUNCIPCLOGD(L"Ws2_32_HttpConnect(%ls)", FormatHostPortToStr(pHostPort, iAddrLen)); + + wPort = ntohs(pHostPort->CommonHeader.wPort); + + if (HostIsType(IPV4, *pHostPort)) { + const struct sockaddr_in* pSockAddrIpv4 = (const struct sockaddr_in*)pHostPort; + const unsigned char* b = (const unsigned char*)&pSockAddrIpv4->sin_addr; + StringCchPrintfA(szHostPort, _countof(szHostPort), "%u.%u.%u.%u:%u", b[0], b[1], b[2], b[3], (unsigned)wPort); + } else if (HostIsType(IPV6, *pHostPort)) { + const struct sockaddr_in6* pSockAddrIpv6 = (const struct sockaddr_in6*)pHostPort; + const unsigned char* b = (const unsigned char*)&pSockAddrIpv6->sin6_addr; + StringCchPrintfA(szHostPort, _countof(szHostPort), "[%x:%x:%x:%x:%x:%x:%x:%x]:%u", + (b[0] << 8) | b[1], (b[2] << 8) | b[3], (b[4] << 8) | b[5], (b[6] << 8) | b[7], + (b[8] << 8) | b[9], (b[10] << 8) | b[11], (b[12] << 8) | b[13], (b[14] << 8) | b[15], + (unsigned)wPort); + } else if (HostIsType(HOSTNAME, *pHostPort)) { + const PXCH_HOSTNAME_PORT* pAddrHostname = (const PXCH_HOSTNAME_PORT*)pHostPort; + StringCchPrintfA(szHostPort, _countof(szHostPort), "%ls:%u", pAddrHostname->szValue, (unsigned)wPort); + } else { + goto err_not_supported; + } + + if (pProxy->Http.szUsername[0] && pProxy->Http.szPassword[0]) { + static const char b64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + char szAuth[PXCH_MAX_USERNAME_BUFSIZE + PXCH_MAX_PASSWORD_BUFSIZE + 4]; + char szAuthBase64[((PXCH_MAX_USERNAME_BUFSIZE + PXCH_MAX_PASSWORD_BUFSIZE + 4) / 3 + 1) * 4 + 1]; + size_t cbAuthLen; + size_t i, j; + unsigned char a, b, c; + + StringCchPrintfA(szAuth, _countof(szAuth), "%s:%s", pProxy->Http.szUsername, pProxy->Http.szPassword); + StringCchLengthA(szAuth, _countof(szAuth), &cbAuthLen); + + /* Simple base64 encoding for proxy auth */ + for (i = 0, j = 0; i < cbAuthLen; ) { + a = (unsigned char)szAuth[i++]; + b = (i < cbAuthLen) ? (unsigned char)szAuth[i++] : 0; + c = (i < cbAuthLen) ? (unsigned char)szAuth[i++] : 0; + szAuthBase64[j++] = b64chars[(a >> 2) & 0x3F]; + szAuthBase64[j++] = b64chars[((a & 0x3) << 4) | ((b >> 4) & 0xF)]; + szAuthBase64[j++] = b64chars[((b & 0xF) << 2) | ((c >> 6) & 0x3)]; + szAuthBase64[j++] = b64chars[c & 0x3F]; + } + /* Apply padding */ + if (cbAuthLen % 3 >= 1) szAuthBase64[j - 1] = '='; + if (cbAuthLen % 3 == 1) szAuthBase64[j - 2] = '='; + szAuthBase64[j] = '\0'; + StringCchPrintfA(SendBuf, _countof(SendBuf), + "CONNECT %s HTTP/1.1\r\n" + "Host: %s\r\n" + "Proxy-Authorization: Basic %s\r\n" + "\r\n", + szHostPort, szHostPort, szAuthBase64); + } else { + StringCchPrintfA(SendBuf, _countof(SendBuf), + "CONNECT %s HTTP/1.1\r\n" + "Host: %s\r\n" + "\r\n", + szHostPort, szHostPort); + } + + StringCchLengthA(SendBuf, _countof(SendBuf), (size_t*)&iSendLen); + if ((iReturn = Ws2_32_LoopSend(pTempData, s, SendBuf, iSendLen)) == SOCKET_ERROR) goto err_general; + + /* Read response - look for "HTTP/1.x 200" */ + if ((iReturn = Ws2_32_LoopRecv(pTempData, s, RecvBuf, 12)) == SOCKET_ERROR) goto err_general; + RecvBuf[12] = '\0'; + + if (RecvBuf[9] != '2' || RecvBuf[10] != '0' || RecvBuf[11] != '0') goto err_data_invalid; + + /* Drain remaining header bytes until we find \r\n\r\n */ + { + int iHeaderBytesRead = 12; + int iMaxHeader = (int)_countof(RecvBuf) - 1; + while (iHeaderBytesRead < iMaxHeader) { + if ((iReturn = Ws2_32_LoopRecv(pTempData, s, RecvBuf + iHeaderBytesRead, 1)) == SOCKET_ERROR) goto err_general; + iHeaderBytesRead++; + if (iHeaderBytesRead >= 4 && + RecvBuf[iHeaderBytesRead - 4] == '\r' && RecvBuf[iHeaderBytesRead - 3] == '\n' && + RecvBuf[iHeaderBytesRead - 2] == '\r' && RecvBuf[iHeaderBytesRead - 1] == '\n') { + break; + } + } + } + + SetLastError(NO_ERROR); + WSASetLastError(NO_ERROR); + return 0; + +err_not_supported: + FUNCIPCLOGW(L"Error connecting through HTTP proxy: address type not supported."); + iReturn = SOCKET_ERROR; + dwLastError = ERROR_NOT_SUPPORTED; + iWSALastError = WSAEAFNOSUPPORT; + goto err_return; + +err_data_invalid: + FUNCIPCLOGW(L"HTTP proxy connection rejected (response: %S)", RecvBuf); + iReturn = SOCKET_ERROR; + dwLastError = ERROR_ACCESS_DENIED; + iWSALastError = WSAECONNREFUSED; + goto err_return; + +err_general: + iWSALastError = WSAGetLastError(); + dwLastError = GetLastError(); +err_return: + shutdown(s, SD_BOTH); + WSASetLastError(iWSALastError); + SetLastError(dwLastError); + return iReturn; +} + +PXCH_DLL_API int Ws2_32_HttpHandshake(void* pTempData, PXCH_UINT_PTR s, const PXCH_PROXY_DATA* pProxy /* Mostly myself */) +{ + /* HTTP CONNECT has no separate handshake phase */ + FUNCIPCLOGI(L"<> %ls", FormatHostPortToStr(&pProxy->CommonHeader.HostPort, pProxy->CommonHeader.iAddrLen)); + + SetLastError(NO_ERROR); + WSASetLastError(NO_ERROR); + return 0; +} + PXCH_DLL_API int Ws2_32_Socks5Connect(void* pTempData, PXCH_UINT_PTR s, const PXCH_PROXY_DATA* pProxy /* Mostly myself */, const PXCH_HOST_PORT* pHostPort, int iAddrLen) { const struct sockaddr_in* pSockAddrIpv4; @@ -805,6 +1703,8 @@ PXCH_DLL_API int Ws2_32_Socks5Handshake(void* pTempData, PXCH_UINT_PTR s, const return iReturn; } +// Connect to a target host through the current chain, using the last proxy's +// Connect function. If the chain is empty, performs a direct connection. int Ws2_32_GenericConnectTo(void* pTempData, PXCH_UINT_PTR s, PPXCH_CHAIN pChain, const PXCH_HOST_PORT* pHostPort, int iAddrLen) { int iReturn; @@ -837,6 +1737,8 @@ int Ws2_32_GenericConnectTo(void* pTempData, PXCH_UINT_PTR s, PPXCH_CHAIN pChain return iReturn; } +// Tunnel to a specific proxy: connect to it, perform handshake, and add it to the chain. +// Returns 0 on success, SOCKET_ERROR on failure. int Ws2_32_GenericTunnelTo(void* pTempData, PXCH_UINT_PTR s, PPXCH_CHAIN pChain, PXCH_PROXY_DATA* pProxy) { DWORD dwLastError; @@ -878,6 +1780,163 @@ int Ws2_32_GenericTunnelTo(void* pTempData, PXCH_UINT_PTR s, PPXCH_CHAIN pChain, return iReturn; } +// Route a connection through the configured proxy chain based on the chain mode: +// - STRICT: All proxies used in order; any failure aborts the chain +// - DYNAMIC: All proxies attempted in order; dead proxies are skipped +// - RANDOM: chain_len random proxies selected; any failure aborts +// - ROUND_ROBIN: chain_len proxies selected starting from a rotating index +static int TunnelThroughProxyChain(void* pTempData, PXCH_UINT_PTR s, PXCH_CHAIN* pChain) +{ + int iReturn; + DWORD dw; + PXCH_UINT32 dwChainType = g_pPxchConfig->dwChainType; + PXCH_UINT32 dwProxyNum = g_pPxchConfig->dwProxyNum; + + if (dwChainType == PXCH_CHAIN_TYPE_DYNAMIC) { + PXCH_UINT32 dwAliveCount = 0; + + FUNCIPCLOGD(L"Using dynamic chain mode"); + for (dw = 0; dw < dwProxyNum; dw++) { + /* Health check: skip proxies with too many consecutive failures */ + if (g_proxyFailureCount[dw] >= PXCH_PROXY_MAX_FAILURES) { + FUNCIPCLOGW(L"Dynamic chain: proxy %lu marked dead (%ld consecutive failures), skipping", + (unsigned long)dw, g_proxyFailureCount[dw]); + continue; + } + + iReturn = Ws2_32_GenericTunnelTo(pTempData, s, pChain, &PXCH_CONFIG_PROXY_ARR(g_pPxchConfig)[dw]); + if (iReturn == SOCKET_ERROR) { + InterlockedIncrement(&g_proxyFailureCount[dw]); + FUNCIPCLOGW(L"Dynamic chain: proxy %lu failed (failure count: %ld), skipping", + (unsigned long)dw, g_proxyFailureCount[dw]); + continue; + } + /* Reset failure count on success */ + InterlockedExchange(&g_proxyFailureCount[dw], 0); + InterlockedIncrement(&g_proxySuccessCount[dw]); + dwAliveCount++; + } + + if (dwProxyNum > 0 && dwAliveCount == 0) { + FUNCIPCLOGE(L"Dynamic chain: all proxies failed! Resetting health counters."); + /* Reset all failure counters so proxies can be retried next time */ + for (dw = 0; dw < dwProxyNum; dw++) { + InterlockedExchange(&g_proxyFailureCount[dw], 0); + } + return SOCKET_ERROR; + } + + FUNCIPCLOGD(L"Dynamic chain: %lu/%lu proxies alive", (unsigned long)dwAliveCount, (unsigned long)dwProxyNum); + return 0; + } else if (dwChainType == PXCH_CHAIN_TYPE_RANDOM) { + PXCH_UINT32 dwChainLen = g_pPxchConfig->dwChainLen; + PXCH_UINT32 dwUsed[PXCH_MAX_PROXY_NUM] = { 0 }; + PXCH_UINT32 dwSelected; + PXCH_UINT32 dwCount = 0; + PXCH_UINT32 dwAttempts; + + if (dwChainLen > dwProxyNum) { + dwChainLen = dwProxyNum; + } + + FUNCIPCLOGD(L"Using random chain mode (chain_len=%lu)", (unsigned long)dwChainLen); + if (g_pPxchConfig->dwRandomSeedSet) { + srand((unsigned int)g_pPxchConfig->dwRandomSeed); + } else { + srand((unsigned int)GetTickCount()); + } + + while (dwCount < dwChainLen) { + dwAttempts = 0; + do { + dwSelected = (PXCH_UINT32)(rand() % dwProxyNum); + dwAttempts++; + if (dwAttempts > dwProxyNum * 10) { + FUNCIPCLOGE(L"Random chain: unable to select enough unique proxies"); + return SOCKET_ERROR; + } + } while (dwUsed[dwSelected]); + + dwUsed[dwSelected] = 1; + + iReturn = Ws2_32_GenericTunnelTo(pTempData, s, pChain, &PXCH_CONFIG_PROXY_ARR(g_pPxchConfig)[dwSelected]); + if (iReturn == SOCKET_ERROR) { + InterlockedIncrement(&g_proxyFailureCount[dwSelected]); + FUNCIPCLOGW(L"Random chain: proxy %lu failed (failure count: %ld)", + (unsigned long)dwSelected, g_proxyFailureCount[dwSelected]); + return SOCKET_ERROR; + } + InterlockedExchange(&g_proxyFailureCount[dwSelected], 0); + InterlockedIncrement(&g_proxySuccessCount[dwSelected]); + dwCount++; + } + + FUNCIPCLOGD(L"Random chain: successfully tunneled through %lu proxies", (unsigned long)dwCount); + return 0; + } else if (dwChainType == PXCH_CHAIN_TYPE_ROUND_ROBIN) { + // Use a named shared memory region for cross-process persistent rotation state + static HANDLE hRoundRobinMapping = NULL; + static volatile LONG* plRoundRobinCounter = NULL; + static volatile LONG lRoundRobinCounterFallback = 0; + PXCH_UINT32 dwChainLen = g_pPxchConfig->dwChainLen; + PXCH_UINT32 dwStartIndex; + PXCH_UINT32 dwCount = 0; + + if (dwChainLen > dwProxyNum) { + dwChainLen = dwProxyNum; + } + + // Lazily initialize shared memory for persistent round-robin counter + if (plRoundRobinCounter == NULL) { + wchar_t szMappingName[128]; + StringCchPrintfW(szMappingName, _countof(szMappingName), L"Local\\proxychains_rr_%lu", (unsigned long)g_pPxchConfig->dwMasterProcessId); + hRoundRobinMapping = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(LONG), szMappingName); + if (hRoundRobinMapping) { + plRoundRobinCounter = (volatile LONG*)MapViewOfFile(hRoundRobinMapping, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(LONG)); + } + if (!plRoundRobinCounter) { + FUNCIPCLOGW(L"Round-robin: shared memory failed, using process-local counter"); + plRoundRobinCounter = &lRoundRobinCounterFallback; + } + } + + dwStartIndex = (PXCH_UINT32)(InterlockedIncrement(plRoundRobinCounter) - 1) % dwProxyNum; + + FUNCIPCLOGD(L"Using round-robin chain mode (chain_len=%lu, start=%lu)", (unsigned long)dwChainLen, (unsigned long)dwStartIndex); + + for (dw = 0; dw < dwChainLen; dw++) { + PXCH_UINT32 dwIndex = (dwStartIndex + dw) % dwProxyNum; + iReturn = Ws2_32_GenericTunnelTo(pTempData, s, pChain, &PXCH_CONFIG_PROXY_ARR(g_pPxchConfig)[dwIndex]); + if (iReturn == SOCKET_ERROR) { + InterlockedIncrement(&g_proxyFailureCount[dwIndex]); + FUNCIPCLOGW(L"Round-robin chain: proxy %lu failed (failure count: %ld)", + (unsigned long)dwIndex, g_proxyFailureCount[dwIndex]); + return SOCKET_ERROR; + } + InterlockedExchange(&g_proxyFailureCount[dwIndex], 0); + InterlockedIncrement(&g_proxySuccessCount[dwIndex]); + dwCount++; + } + + FUNCIPCLOGD(L"Round-robin chain: successfully tunneled through %lu proxies", (unsigned long)dwCount); + return 0; + } else { + /* PXCH_CHAIN_TYPE_STRICT (default) */ + FUNCIPCLOGD(L"Using strict chain mode"); + for (dw = 0; dw < dwProxyNum; dw++) { + if ((iReturn = Ws2_32_GenericTunnelTo(pTempData, s, pChain, &PXCH_CONFIG_PROXY_ARR(g_pPxchConfig)[dw])) == SOCKET_ERROR) { + InterlockedIncrement(&g_proxyFailureCount[dw]); + FUNCIPCLOGW(L"Strict chain: proxy %lu failed (failure count: %ld)", + (unsigned long)dw, g_proxyFailureCount[dw]); + return SOCKET_ERROR; + } + InterlockedExchange(&g_proxyFailureCount[dw], 0); + InterlockedIncrement(&g_proxySuccessCount[dw]); + } + return 0; + } +} + // Hook connect PROXY_FUNC2(Ws2_32, connect) @@ -898,8 +1957,6 @@ PROXY_FUNC2(Ws2_32, connect) PXCH_HOSTNAME_PORT ReverseLookedupHostnamePort = { 0 }; PXCH_IP_PORT ReverseLookedupResolvedIpPort = { 0 }; - DWORD dw; - RestoreChildDataIfNecessary(); FUNCIPCLOGD(L"Ws2_32.dll connect(%d, %ls, %d) called", s, FormatHostPortToStr(name, namelen), namelen); @@ -924,9 +1981,7 @@ PROXY_FUNC2(Ws2_32, connect) goto block_end; } - for (dw = 0; dw < g_pPxchConfig->dwProxyNum; dw++) { - if ((iReturn = Ws2_32_GenericTunnelTo(&TempData, s, &Chain, &PXCH_CONFIG_PROXY_ARR(g_pPxchConfig)[dw])) == SOCKET_ERROR) goto record_error_end; - } + if ((iReturn = TunnelThroughProxyChain(&TempData, s, &Chain)) == SOCKET_ERROR) goto record_error_end; if ((iReturn = Ws2_32_GenericConnectTo(&TempData, s, &Chain, pHostPortForProxiedConnection, namelen)) == SOCKET_ERROR) goto record_error_end; success_revert_connect_errcode_end: @@ -1002,8 +2057,6 @@ PROXY_FUNC2(Mswsock, ConnectEx) PXCH_HOSTNAME_PORT ReverseLookedupHostnamePort = { 0 }; PXCH_IP_PORT ReverseLookedupResolvedIpPort = { 0 }; - DWORD dw; - RestoreChildDataIfNecessary(); FUNCIPCLOGD(L"Mswsock.dll (FP)ConnectEx(%d, %ls, %d) called", s, FormatHostPortToStr(name, namelen), namelen); @@ -1028,9 +2081,7 @@ PROXY_FUNC2(Mswsock, ConnectEx) goto block_end; } - for (dw = 0; dw < g_pPxchConfig->dwProxyNum; dw++) { - if ((iReturn = Ws2_32_GenericTunnelTo(&TempData, s, &Chain, &PXCH_CONFIG_PROXY_ARR(g_pPxchConfig)[dw])) == SOCKET_ERROR) goto record_error_end; - } + if ((iReturn = TunnelThroughProxyChain(&TempData, s, &Chain)) == SOCKET_ERROR) goto record_error_end; if ((iReturn = Ws2_32_GenericConnectTo(&TempData, s, &Chain, pHostPortForProxiedConnection, namelen)) == SOCKET_ERROR) goto record_error_end; success_set_errcode_zero_end: @@ -1114,8 +2165,6 @@ PROXY_FUNC2(Ws2_32, WSAConnect) PXCH_HOSTNAME_PORT ReverseLookedupHostnamePort = { 0 }; PXCH_IP_PORT ReverseLookedupResolvedIpPort = { 0 }; - DWORD dw; - RestoreChildDataIfNecessary(); FUNCIPCLOGD(L"Ws2_32.dll WSAConnect(%d, %ls, %d) called", s, FormatHostPortToStr(name, namelen), namelen); @@ -1147,9 +2196,7 @@ PROXY_FUNC2(Ws2_32, WSAConnect) goto block_end; } - for (dw = 0; dw < g_pPxchConfig->dwProxyNum; dw++) { - if ((iReturn = Ws2_32_GenericTunnelTo(&TempData, s, &Chain, &PXCH_CONFIG_PROXY_ARR(g_pPxchConfig)[dw])) == SOCKET_ERROR) goto record_error_end; - } + if ((iReturn = TunnelThroughProxyChain(&TempData, s, &Chain)) == SOCKET_ERROR) goto record_error_end; if ((iReturn = Ws2_32_GenericConnectTo(&TempData, s, &Chain, pHostPortForProxiedConnection, namelen)) == SOCKET_ERROR) goto record_error_end; success_revert_connect_errcode_end: @@ -1224,6 +2271,9 @@ PROXY_FUNC2(Ws2_32, gethostbyname) // Print DNS query FUNCIPCLOGD(L"Ws2_32.dll gethostbyname(" WPRS L") called", name); + // Initialize DNS cache on first use + DnsCacheInit(); + ZeroMemory(&IpPortResolvedByHosts, sizeof(IpPortResolvedByHosts)); ZeroMemory(&RequeryAddrInfoHints, sizeof(RequeryAddrInfoHints)); RequeryAddrInfoHints.ai_family = AF_UNSPEC; @@ -1242,6 +2292,18 @@ PROXY_FUNC2(Ws2_32, gethostbyname) bShouldUseHostsResult = TRUE; } + // Check DNS cache before doing any resolution + if (!bShouldReturnHostsResult && g_pPxchConfig->dwDnsCacheTtlSeconds > 0) { + PXCH_IP_ADDRESS CachedIpv4; + BOOL bCacheHasIpv4 = FALSE, bCacheHasIpv6 = FALSE; + if (DnsCacheLookup(&OriginalHostname, &CachedIpv4, NULL, &bCacheHasIpv4, &bCacheHasIpv6) && bCacheHasIpv4) { + IpPortResolvedByHosts = *(PXCH_IP_PORT*)&CachedIpv4; + bShouldReturnHostsResult = TRUE; + bShouldUseHostsResult = TRUE; + FUNCIPCLOGD(L"gethostbyname: using DNS cache for %ls", OriginalHostname.szValue); + } + } + // Decide if we should use original result if (!bShouldReturnHostsResult && !g_pPxchConfig->dwWillUseFakeIpAsRemoteDns) { bShouldReturnResolvedResult = TRUE; @@ -1290,6 +2352,22 @@ PROXY_FUNC2(Ws2_32, gethostbyname) if (bShouldUseResolvedResult && !bShouldUseHostsResult) { pResolvedHostent = NULL; + + // Try SOCKS5 UDP Associate DNS resolution first if enabled + if (g_pPxchConfig->dwWillUseUdpAssociateAsRemoteDns) { + PXCH_IP_ADDRESS UdpIpv4, UdpIpv6; + BOOL bUdpHasIpv4 = FALSE, bUdpHasIpv6 = FALSE; + if (ResolveDnsViaSocks5UdpAssociate(&OriginalHostname, &UdpIpv4, &UdpIpv6, &bUdpHasIpv4, &bUdpHasIpv6) && bUdpHasIpv4) { + IpPortResolvedByHosts = *(PXCH_IP_PORT*)&UdpIpv4; + bShouldReturnHostsResult = TRUE; + bShouldUseHostsResult = TRUE; + bShouldUseResolvedResult = FALSE; + bShouldReturnResolvedResult = FALSE; + FUNCIPCLOGD(L"gethostbyname: resolved via SOCKS5 UDP ASSOCIATE for %ls", OriginalHostname.szValue); + goto after_resolve; + } + } + pResolvedHostent = orig_fpWs2_32_gethostbyname(name); iWSALastError = WSAGetLastError(); dwLastError = GetLastError(); @@ -1306,8 +2384,20 @@ PROXY_FUNC2(Ws2_32, gethostbyname) bShouldReturnFakeIp = FALSE; bShouldReturnResolvedResult = TRUE; } + + // Cache the resolved result + if (dwIpNum > 0 && g_pPxchConfig->dwDnsCacheTtlSeconds > 0) { + PXCH_IP_ADDRESS* pFirstIpv4 = NULL; + for (dw = 0; dw < dwIpNum; dw++) { + if (Ips[dw].CommonHeader.wTag == PXCH_HOST_TYPE_IPV4) { pFirstIpv4 = &Ips[dw]; break; } + } + if (pFirstIpv4) { + DnsCacheAdd(&OriginalHostname, pFirstIpv4, NULL, TRUE, FALSE); + } + } } +after_resolve: if (bShouldUseHostsResult) { pResolvedHostent = NULL; @@ -1570,6 +2660,27 @@ PROXY_FUNC2(Ws2_32, GetAddrInfoW) bShouldUseHostsResult = TRUE; } + // Check DNS cache before doing any resolution + if (!bShouldReturnHostsResult && g_pPxchConfig->dwDnsCacheTtlSeconds > 0) { + PXCH_IP_ADDRESS CachedIpv4, CachedIpv6; + BOOL bCacheHasIpv4 = FALSE, bCacheHasIpv6 = FALSE; + if (DnsCacheLookup(&Hostname, &CachedIpv4, &CachedIpv6, &bCacheHasIpv4, &bCacheHasIpv6)) { + if ((pHintsCast->ai_family == AF_INET || pHintsCast->ai_family == AF_UNSPEC) && bCacheHasIpv4) { + IpPortResolvedByHosts = *(PXCH_IP_PORT*)&CachedIpv4; + IpPortResolvedByHosts.CommonHeader.wPort = HostPort.HostnamePort.wPort; + bShouldReturnHostsResult = TRUE; + bShouldUseHostsResult = TRUE; + FUNCIPCLOGD(L"GetAddrInfoW: using DNS cache for %ls", Hostname.szValue); + } else if (pHintsCast->ai_family == AF_INET6 && bCacheHasIpv6) { + IpPortResolvedByHosts = *(PXCH_IP_PORT*)&CachedIpv6; + IpPortResolvedByHosts.CommonHeader.wPort = HostPort.HostnamePort.wPort; + bShouldReturnHostsResult = TRUE; + bShouldUseHostsResult = TRUE; + FUNCIPCLOGD(L"GetAddrInfoW: using DNS cache (IPv6) for %ls", Hostname.szValue); + } + } + } + // Decide if we should use original result if (!bShouldReturnHostsResult && !g_pPxchConfig->dwWillUseFakeIpAsRemoteDns) { bShouldReturnResolvedResult = TRUE; @@ -1631,7 +2742,32 @@ PROXY_FUNC2(Ws2_32, GetAddrInfoW) if (bShouldUseResolvedResult && !bShouldUseHostsResult) { pResolvedResultCast = NULL; - + + // Try SOCKS5 UDP Associate DNS resolution first if enabled + if (g_pPxchConfig->dwWillUseUdpAssociateAsRemoteDns) { + PXCH_IP_ADDRESS UdpIpv4, UdpIpv6; + BOOL bUdpHasIpv4 = FALSE, bUdpHasIpv6 = FALSE; + if (ResolveDnsViaSocks5UdpAssociate(&Hostname, &UdpIpv4, &UdpIpv6, &bUdpHasIpv4, &bUdpHasIpv6)) { + BOOL bMatch = FALSE; + if ((pHintsCast->ai_family == AF_INET || pHintsCast->ai_family == AF_UNSPEC) && bUdpHasIpv4) { + IpPortResolvedByHosts = *(PXCH_IP_PORT*)&UdpIpv4; + bMatch = TRUE; + } else if ((pHintsCast->ai_family == AF_INET6 || pHintsCast->ai_family == AF_UNSPEC) && bUdpHasIpv6) { + IpPortResolvedByHosts = *(PXCH_IP_PORT*)&UdpIpv6; + bMatch = TRUE; + } + if (bMatch) { + IpPortResolvedByHosts.CommonHeader.wPort = HostPort.HostnamePort.wPort; + bShouldReturnHostsResult = TRUE; + bShouldUseHostsResult = TRUE; + bShouldUseResolvedResult = FALSE; + bShouldReturnResolvedResult = FALSE; + FUNCIPCLOGD(L"GetAddrInfoW: resolved via SOCKS5 UDP ASSOCIATE for %ls", Hostname.szValue); + goto after_resolve_gaiw; + } + } + } + iReturn = orig_fpWs2_32_GetAddrInfoW(pNodeName, pServiceName, pHints, &pResolvedResultCast); iWSALastError = WSAGetLastError(); dwLastError = GetLastError(); @@ -1652,8 +2788,32 @@ PROXY_FUNC2(Ws2_32, GetAddrInfoW) bShouldReturnFakeIp = FALSE; bShouldReturnResolvedResult = TRUE; } + + // Cache successful resolution + if (pResolvedResultCast && g_pPxchConfig->dwDnsCacheTtlSeconds > 0) { + PADDRINFOW pTemp; + PXCH_IP_ADDRESS CacheIpv4, CacheIpv6; + BOOL bCacheHasIpv4 = FALSE, bCacheHasIpv6 = FALSE; + for (pTemp = pResolvedResultCast; pTemp; pTemp = pTemp->ai_next) { + if (pTemp->ai_family == AF_INET && !bCacheHasIpv4 && pTemp->ai_addrlen >= sizeof(struct sockaddr_in)) { + ZeroMemory(&CacheIpv4, sizeof(CacheIpv4)); + CacheIpv4.CommonHeader.wTag = PXCH_HOST_TYPE_IPV4; + CopyMemory(&CacheIpv4.Sockaddr, pTemp->ai_addr, sizeof(struct sockaddr_in)); + bCacheHasIpv4 = TRUE; + } else if (pTemp->ai_family == AF_INET6 && !bCacheHasIpv6 && pTemp->ai_addrlen >= sizeof(struct sockaddr_in6)) { + ZeroMemory(&CacheIpv6, sizeof(CacheIpv6)); + CacheIpv6.CommonHeader.wTag = PXCH_HOST_TYPE_IPV6; + CopyMemory(&CacheIpv6.Sockaddr, pTemp->ai_addr, sizeof(struct sockaddr_in6)); + bCacheHasIpv6 = TRUE; + } + } + if (bCacheHasIpv4 || bCacheHasIpv6) { + DnsCacheAdd(&Hostname, bCacheHasIpv4 ? &CacheIpv4 : NULL, bCacheHasIpv6 ? &CacheIpv6 : NULL, bCacheHasIpv4, bCacheHasIpv6); + } + } } +after_resolve_gaiw: if (bShouldUseHostsResult) { pResolvedResultCast = NULL; IpPortResolvedByHosts.CommonHeader.wPort = HostPort.CommonHeader.wPort; diff --git a/src/dll/hook_createprocess_win32.c b/src/dll/hook_createprocess_win32.c index 53fead8..0556078 100644 --- a/src/dll/hook_createprocess_win32.c +++ b/src/dll/hook_createprocess_win32.c @@ -19,14 +19,78 @@ #include "hookdll_util_win32.h" #include "log_win32.h" #include +#include #include "hookdll_win32.h" #ifndef __CYGWIN__ #define wcscasecmp _wcsicmp #pragma comment(lib, "psapi.lib") +#pragma comment(lib, "Shlwapi.lib") #endif +// Check if a process should be injected based on process_only/process_except config. +// Returns TRUE if injection should proceed, FALSE if it should be skipped. +static BOOL ShouldInjectProcess(LPCWSTR lpApplicationName, LPWSTR lpCommandLine) +{ + PXCH_UINT32 i; + const wchar_t* pExeName = NULL; + + if (!g_pPxchConfig || g_pPxchConfig->dwProcessFilterMode == PXCH_PROCESS_FILTER_NONE) { + return TRUE; + } + + // Extract the executable name from the application name or command line + if (lpApplicationName) { + pExeName = PathFindFileNameW(lpApplicationName); + } else if (lpCommandLine) { + // Parse the first argument properly, handling quotes and trailing args + const wchar_t* p = lpCommandLine; + static wchar_t szFirstArg[MAX_PATH]; + const wchar_t* pEnd; + size_t cchCopy; + + while (*p == L' ' || *p == L'\t') p++; + if (*p == L'"') { + p++; + pEnd = wcschr(p, L'"'); + if (!pEnd) pEnd = p + wcslen(p); + } else { + pEnd = p; + while (*pEnd && *pEnd != L' ' && *pEnd != L'\t') pEnd++; + } + cchCopy = (size_t)(pEnd - p); + if (cchCopy >= MAX_PATH) cchCopy = MAX_PATH - 1; + wmemcpy(szFirstArg, p, cchCopy); + szFirstArg[cchCopy] = L'\0'; + pExeName = PathFindFileNameW(szFirstArg); + } + + if (!pExeName || *pExeName == L'\0') { + return TRUE; + } + + for (i = 0; i < g_pPxchConfig->dwProcessFilterCount; i++) { + if (wcscasecmp(pExeName, g_pPxchConfig->szProcessFilterNames[i]) == 0) { + if (g_pPxchConfig->dwProcessFilterMode == PXCH_PROCESS_FILTER_WHITELIST) { + IPCLOGD(L"Process filter: %ls matched whitelist entry %ls, will inject", pExeName, g_pPxchConfig->szProcessFilterNames[i]); + return TRUE; + } else { + IPCLOGD(L"Process filter: %ls matched blacklist entry %ls, skipping injection", pExeName, g_pPxchConfig->szProcessFilterNames[i]); + return FALSE; + } + } + } + + // No match found + if (g_pPxchConfig->dwProcessFilterMode == PXCH_PROCESS_FILTER_WHITELIST) { + IPCLOGD(L"Process filter: %ls not in whitelist, skipping injection", pExeName); + return FALSE; + } + + return TRUE; +} + PROXY_FUNC(CreateProcessA) { BOOL bRet; @@ -54,6 +118,55 @@ BOOL bRet; if (!bRet) goto err_orig; IPCLOGV(L"CreateProcessA: After jmp to err_orig."); + + // Check process name filter before injection (convert ANSI to Wide) + { + LPWSTR pszAppNameW = NULL; + LPWSTR pszCmdLineW = NULL; + int cchAppNameW = 0; + int cchCmdLineW = 0; + BOOL bShouldInject; + + if (lpApplicationName) { + cchAppNameW = MultiByteToWideChar(CP_ACP, 0, lpApplicationName, -1, NULL, 0); + if (cchAppNameW > 0) { + pszAppNameW = (LPWSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (SIZE_T)cchAppNameW * sizeof(WCHAR)); + if (pszAppNameW) { + if (MultiByteToWideChar(CP_ACP, 0, lpApplicationName, -1, pszAppNameW, cchAppNameW) == 0) { + HeapFree(GetProcessHeap(), 0, pszAppNameW); + pszAppNameW = NULL; + } + } + } + } + if (lpCommandLine) { + cchCmdLineW = MultiByteToWideChar(CP_ACP, 0, lpCommandLine, -1, NULL, 0); + if (cchCmdLineW > 0) { + pszCmdLineW = (LPWSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (SIZE_T)cchCmdLineW * sizeof(WCHAR)); + if (pszCmdLineW) { + if (MultiByteToWideChar(CP_ACP, 0, lpCommandLine, -1, pszCmdLineW, cchCmdLineW) == 0) { + HeapFree(GetProcessHeap(), 0, pszCmdLineW); + pszCmdLineW = NULL; + } + } + } + } + + bShouldInject = ShouldInjectProcess(pszAppNameW, pszCmdLineW); + if (pszAppNameW) HeapFree(GetProcessHeap(), 0, pszAppNameW); + if (pszCmdLineW) HeapFree(GetProcessHeap(), 0, pszCmdLineW); + + if (!bShouldInject) { + IPCLOGD(L"CreateProcessA: Process filtered out, not injecting WINPID " WPRDW, ProcessInformation.dwProcessId); + if (!(dwCreationFlags & CREATE_SUSPENDED)) { + ResumeThread(ProcessInformation.hThread); + } + g_bCurrentlyInWinapiCall = FALSE; + SetLastError(dwLastError); + return 1; + } + } + IPCLOGV(L"CreateProcessA: Before InjectTargetProcess."); dwReturn = InjectTargetProcess(&ProcessInformation, dwCreationFlags); @@ -79,7 +192,7 @@ BOOL bRet; return bRet; err_inject: - IPCLOGE(L"Injecting WINPID " WPRDW L" Error: %ls", ProcessInformation.dwProcessId, FormatErrorToStr(dwReturn)); + IPCLOGW(L"Injecting WINPID " WPRDW L" Error: %ls", ProcessInformation.dwProcessId, FormatErrorToStr(dwReturn)); // TODO: remove this line SetLastError(dwReturn); g_bCurrentlyInWinapiCall = FALSE; @@ -113,6 +226,18 @@ PROXY_FUNC(CreateProcessW) if (!bRet) goto err_orig; IPCLOGV(L"CreateProcessW: After jmp to err_orig."); + + // Check process name filter before injection + if (!ShouldInjectProcess(lpApplicationName, lpCommandLine)) { + IPCLOGD(L"CreateProcessW: Process filtered out, not injecting WINPID " WPRDW, ProcessInformation.dwProcessId); + if (!(dwCreationFlags & CREATE_SUSPENDED)) { + ResumeThread(ProcessInformation.hThread); + } + g_bCurrentlyInWinapiCall = FALSE; + SetLastError(dwLastError); + return 1; + } + IPCLOGV(L"CreateProcessW: Before InjectTargetProcess."); dwReturn = InjectTargetProcess(&ProcessInformation, dwCreationFlags); @@ -138,7 +263,7 @@ PROXY_FUNC(CreateProcessW) return bRet; err_inject: - IPCLOGE(L"Injecting WINPID " WPRDW L" Error: %ls", ProcessInformation.dwProcessId, FormatErrorToStr(dwReturn)); + IPCLOGW(L"Injecting WINPID " WPRDW L" Error: %ls", ProcessInformation.dwProcessId, FormatErrorToStr(dwReturn)); // TODO: remove this line SetLastError(dwReturn); g_bCurrentlyInWinapiCall = FALSE; @@ -174,6 +299,18 @@ PROXY_FUNC(CreateProcessAsUserW) if (!bRet) goto err_orig; IPCLOGV(L"CreateProcessAsUserW: After jmp to err_orig."); + + // Check process name filter before injection + if (!ShouldInjectProcess(lpApplicationName, lpCommandLine)) { + IPCLOGD(L"CreateProcessAsUserW: Process filtered out, not injecting WINPID " WPRDW, ProcessInformation.dwProcessId); + if (!(dwCreationFlags & CREATE_SUSPENDED)) { + ResumeThread(ProcessInformation.hThread); + } + g_bCurrentlyInWinapiCall = FALSE; + SetLastError(dwLastError); + return 1; + } + IPCLOGV(L"CreateProcessAsUserW: Before InjectTargetProcess."); dwReturn = InjectTargetProcess(&ProcessInformation, dwCreationFlags); @@ -198,7 +335,7 @@ PROXY_FUNC(CreateProcessAsUserW) return bRet; err_inject: - IPCLOGE(L"Injecting WINPID " WPRDW L" Error: %ls", ProcessInformation.dwProcessId, FormatErrorToStr(dwReturn)); + IPCLOGW(L"Injecting WINPID " WPRDW L" Error: %ls", ProcessInformation.dwProcessId, FormatErrorToStr(dwReturn)); SetLastError(dwReturn); g_bCurrentlyInWinapiCall = FALSE; return 1; diff --git a/src/dll/hook_installer.c b/src/dll/hook_installer.c index 606456a..c26b7f8 100644 --- a/src/dll/hook_installer.c +++ b/src/dll/hook_installer.c @@ -132,6 +132,52 @@ void Win32HookWs2_32(void) } } +void Win32HookWinHttp(void) +{ + HMODULE hWinHttp; + HMODULE hWinINet; + LPVOID pWinHttp_Open = NULL; + LPVOID pWinHttp_SetOption = NULL; + LPVOID pWinINet_InternetOpenA = NULL; + LPVOID pWinINet_InternetOpenW = NULL; + LPVOID pWinINet_InternetSetOptionA = NULL; + LPVOID pWinINet_InternetSetOptionW = NULL; + + // Hook WinHTTP (winhttp.dll) + LoadLibraryW(L"winhttp.dll"); + + if ((hWinHttp = GetModuleHandleW(L"winhttp.dll"))) { + orig_fpWinHttp_Open = (void*)GetProcAddress(hWinHttp, "WinHttpOpen"); + orig_fpWinHttp_SetOption = (void*)GetProcAddress(hWinHttp, "WinHttpSetOption"); + + pWinHttp_Open = orig_fpWinHttp_Open; + pWinHttp_SetOption = orig_fpWinHttp_SetOption; + + CREATE_HOOK3_IFNOTNULL(WinHttp, Open, pWinHttp_Open); + CREATE_HOOK3_IFNOTNULL(WinHttp, SetOption, pWinHttp_SetOption); + } + + // Hook WinINet (wininet.dll) + LoadLibraryW(L"wininet.dll"); + + if ((hWinINet = GetModuleHandleW(L"wininet.dll"))) { + orig_fpWinINet_InternetOpenA = (void*)GetProcAddress(hWinINet, "InternetOpenA"); + orig_fpWinINet_InternetOpenW = (void*)GetProcAddress(hWinINet, "InternetOpenW"); + orig_fpWinINet_InternetSetOptionA = (void*)GetProcAddress(hWinINet, "InternetSetOptionA"); + orig_fpWinINet_InternetSetOptionW = (void*)GetProcAddress(hWinINet, "InternetSetOptionW"); + + pWinINet_InternetOpenA = orig_fpWinINet_InternetOpenA; + pWinINet_InternetOpenW = orig_fpWinINet_InternetOpenW; + pWinINet_InternetSetOptionA = orig_fpWinINet_InternetSetOptionA; + pWinINet_InternetSetOptionW = orig_fpWinINet_InternetSetOptionW; + + CREATE_HOOK3_IFNOTNULL(WinINet, InternetOpenA, pWinINet_InternetOpenA); + CREATE_HOOK3_IFNOTNULL(WinINet, InternetOpenW, pWinINet_InternetOpenW); + CREATE_HOOK3_IFNOTNULL(WinINet, InternetSetOptionA, pWinINet_InternetSetOptionA); + CREATE_HOOK3_IFNOTNULL(WinINet, InternetSetOptionW, pWinINet_InternetSetOptionW); + } +} + void CygwinHook(void) { HMODULE hCygwin1; diff --git a/src/dll/hook_winhttp_win32.c b/src/dll/hook_winhttp_win32.c new file mode 100644 index 0000000..d933960 --- /dev/null +++ b/src/dll/hook_winhttp_win32.c @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* hook_winhttp_win32.c + * Copyright (C) 2020 Feng Shun. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License version 2 for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program. If not, see + * . + */ + +// Hook WinHTTP and WinINet APIs to force proxy settings. +// Applications using these high-level HTTP APIs (PowerShell, browsers, etc.) +// will transparently use the configured SOCKS5/HTTP proxy. + +#define PXCH_DO_NOT_INCLUDE_STD_HEADERS_NOW +#define PXCH_DO_NOT_INCLUDE_STRSAFE_NOW +#include "includes_win32.h" +#include +#include +#include "hookdll_util_win32.h" +#include "log_generic.h" +#include + +#include "hookdll_win32.h" + +#ifndef __CYGWIN__ + +// ============================================================================ +// WinHTTP types and constants (from winhttp.h) +// We define them locally to avoid header dependency issues +// ============================================================================ + +#define PXCH_WINHTTP_ACCESS_TYPE_DEFAULT_PROXY 0 +#define PXCH_WINHTTP_ACCESS_TYPE_NO_PROXY 1 +#define PXCH_WINHTTP_ACCESS_TYPE_NAMED_PROXY 3 +#define PXCH_WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY 4 + +#define PXCH_WINHTTP_OPTION_PROXY 38 + +#define PXCH_WINHTTP_NO_PROXY_NAME NULL +#define PXCH_WINHTTP_NO_PROXY_BYPASS NULL + +typedef struct { + DWORD dwAccessType; + LPWSTR lpszProxy; + LPWSTR lpszProxyBypass; +} PXCH_WINHTTP_PROXY_INFO; + +// ============================================================================ +// WinINet types and constants (from wininet.h) +// ============================================================================ + +#define PXCH_INTERNET_OPEN_TYPE_DIRECT 0 +#define PXCH_INTERNET_OPEN_TYPE_PROXY 3 +#define PXCH_INTERNET_OPEN_TYPE_PRECONFIG 0 + +#define PXCH_INTERNET_OPTION_PROXY 38 + +typedef struct { + DWORD dwAccessType; + LPSTR lpszProxy; + LPSTR lpszProxyBypass; +} PXCH_INTERNET_PROXY_INFO_A; + +typedef struct { + DWORD dwAccessType; + LPWSTR lpszProxy; + LPWSTR lpszProxyBypass; +} PXCH_INTERNET_PROXY_INFO_W; + + +// ============================================================================ +// Helper: Build proxy string from config +// ============================================================================ + +// Build a proxy URL string like "socks=host:port" or "http://host:port" +// from the first proxy in the configured proxy list. +// WinHTTP/WinINet use "socks=" for SOCKS proxies (not socks4/socks5). +static BOOL BuildProxyStringW(wchar_t* szProxy, DWORD cchProxy) +{ + const PXCH_PROXY_DATA* pProxy; + const wchar_t* szScheme; + PXCH_UINT16 wPort; + wchar_t szHostW[PXCH_MAX_HOSTNAME_BUFSIZE]; + + if (!g_pPxchConfig || g_pPxchConfig->dwProxyNum == 0) return FALSE; + + pProxy = &PXCH_CONFIG_PROXY_ARR(g_pPxchConfig)[0]; + + if (ProxyIsType(SOCKS5, *pProxy) || ProxyIsType(SOCKS4, *pProxy)) { + szScheme = L"socks"; + } else if (ProxyIsType(HTTP, *pProxy)) { + szScheme = L"http"; + } else { + return FALSE; + } + + // Extract host and port from the proxy's HostPort + if (HostIsType(HOSTNAME, pProxy->CommonHeader.HostPort)) { + StringCchCopyW(szHostW, _countof(szHostW), pProxy->CommonHeader.HostPort.HostnamePort.szValue); + } else if (HostIsType(IPV4, pProxy->CommonHeader.HostPort)) { + const struct sockaddr_in* pSin = (const struct sockaddr_in*)&pProxy->CommonHeader.HostPort.IpPort.Sockaddr; + const unsigned char* b = (const unsigned char*)&pSin->sin_addr; + StringCchPrintfW(szHostW, _countof(szHostW), L"%u.%u.%u.%u", b[0], b[1], b[2], b[3]); + } else { + return FALSE; + } + + wPort = _byteswap_ushort(pProxy->CommonHeader.HostPort.CommonHeader.wPort); + + StringCchPrintfW(szProxy, cchProxy, L"%ls=%ls:%u", szScheme, szHostW, (unsigned int)wPort); + return TRUE; +} + +static BOOL BuildProxyStringA(char* szProxy, DWORD cchProxy) +{ + wchar_t szProxyW[512]; + if (!BuildProxyStringW(szProxyW, _countof(szProxyW))) return FALSE; + + // Convert wide to narrow + WideCharToMultiByte(CP_ACP, 0, szProxyW, -1, szProxy, (int)cchProxy, NULL, NULL); + return TRUE; +} + + +// ============================================================================ +// WinHTTP Hooks +// ============================================================================ + +// WinHttpOpen: Intercept to force named proxy access type +PROXY_FUNC2(WinHttp, Open) +{ + void* hSession; + wchar_t szProxy[512]; + BOOL bHasProxy; + + bHasProxy = BuildProxyStringW(szProxy, _countof(szProxy)); + + if (bHasProxy && dwAccessType != PXCH_WINHTTP_ACCESS_TYPE_NAMED_PROXY) { + FUNCIPCLOGD(L"WinHttpOpen: Overriding access type to NAMED_PROXY with proxy %ls", szProxy); + dwAccessType = PXCH_WINHTTP_ACCESS_TYPE_NAMED_PROXY; + pszProxyName = szProxy; + pszProxyBypass = L""; + } + + hSession = orig_fpWinHttp_Open(pszAgentW, dwAccessType, pszProxyName, pszProxyBypass, dwFlags); + + if (hSession) { + FUNCIPCLOGD(L"WinHttpOpen: Session %p created (access_type=%lu)", hSession, dwAccessType); + } + + return hSession; +} + +// WinHttpSetOption: Intercept proxy option changes to enforce our proxy +PROXY_FUNC2(WinHttp, SetOption) +{ + if (dwOption == PXCH_WINHTTP_OPTION_PROXY && lpBuffer && dwBufferLength >= sizeof(PXCH_WINHTTP_PROXY_INFO)) { + static wchar_t s_szWinHttpProxy[512]; + if (BuildProxyStringW(s_szWinHttpProxy, _countof(s_szWinHttpProxy))) { + PXCH_WINHTTP_PROXY_INFO localInfo; + FUNCIPCLOGD(L"WinHttpSetOption: Overriding WINHTTP_OPTION_PROXY to %ls", s_szWinHttpProxy); + localInfo.dwAccessType = PXCH_WINHTTP_ACCESS_TYPE_NAMED_PROXY; + localInfo.lpszProxy = s_szWinHttpProxy; + localInfo.lpszProxyBypass = L""; + return orig_fpWinHttp_SetOption(hInternet, dwOption, &localInfo, sizeof(localInfo)); + } + } + + return orig_fpWinHttp_SetOption(hInternet, dwOption, lpBuffer, dwBufferLength); +} + + +// ============================================================================ +// WinINet Hooks +// ============================================================================ + +// InternetOpenA: Intercept to force proxy access type +PROXY_FUNC2(WinINet, InternetOpenA) +{ + void* hInternet; + char szProxy[512]; + BOOL bHasProxy; + + bHasProxy = BuildProxyStringA(szProxy, _countof(szProxy)); + + if (bHasProxy && dwAccessType != PXCH_INTERNET_OPEN_TYPE_PROXY) { + FUNCIPCLOGD(L"InternetOpenA: Overriding access type to PROXY with proxy " WPRS, szProxy); + dwAccessType = PXCH_INTERNET_OPEN_TYPE_PROXY; + lpszProxy = szProxy; + lpszProxyBypass = ""; + } + + hInternet = orig_fpWinINet_InternetOpenA(lpszAgent, dwAccessType, lpszProxy, lpszProxyBypass, dwFlags); + + if (hInternet) { + FUNCIPCLOGD(L"InternetOpenA: Session %p created (access_type=%lu)", hInternet, dwAccessType); + } + + return hInternet; +} + +// InternetOpenW: Intercept to force proxy access type +PROXY_FUNC2(WinINet, InternetOpenW) +{ + void* hInternet; + wchar_t szProxy[512]; + BOOL bHasProxy; + + bHasProxy = BuildProxyStringW(szProxy, _countof(szProxy)); + + if (bHasProxy && dwAccessType != PXCH_INTERNET_OPEN_TYPE_PROXY) { + FUNCIPCLOGD(L"InternetOpenW: Overriding access type to PROXY with proxy %ls", szProxy); + dwAccessType = PXCH_INTERNET_OPEN_TYPE_PROXY; + lpszProxy = szProxy; + lpszProxyBypass = L""; + } + + hInternet = orig_fpWinINet_InternetOpenW(lpszAgent, dwAccessType, lpszProxy, lpszProxyBypass, dwFlags); + + if (hInternet) { + FUNCIPCLOGD(L"InternetOpenW: Session %p created (access_type=%lu)", hInternet, dwAccessType); + } + + return hInternet; +} + +// InternetSetOptionA: Intercept proxy option changes +PROXY_FUNC2(WinINet, InternetSetOptionA) +{ + if (dwOption == PXCH_INTERNET_OPTION_PROXY && lpBuffer && dwBufferLength >= sizeof(PXCH_INTERNET_PROXY_INFO_A)) { + static char s_szInetProxyA[512]; + if (BuildProxyStringA(s_szInetProxyA, _countof(s_szInetProxyA))) { + PXCH_INTERNET_PROXY_INFO_A localInfo; + FUNCIPCLOGD(L"InternetSetOptionA: Overriding INTERNET_OPTION_PROXY to " WPRS, s_szInetProxyA); + localInfo.dwAccessType = PXCH_INTERNET_OPEN_TYPE_PROXY; + localInfo.lpszProxy = s_szInetProxyA; + localInfo.lpszProxyBypass = ""; + return orig_fpWinINet_InternetSetOptionA(hInternet, dwOption, &localInfo, sizeof(localInfo)); + } + } + + return orig_fpWinINet_InternetSetOptionA(hInternet, dwOption, lpBuffer, dwBufferLength); +} + +// InternetSetOptionW: Intercept proxy option changes +PROXY_FUNC2(WinINet, InternetSetOptionW) +{ + if (dwOption == PXCH_INTERNET_OPTION_PROXY && lpBuffer && dwBufferLength >= sizeof(PXCH_INTERNET_PROXY_INFO_W)) { + static wchar_t s_szInetProxyW[512]; + if (BuildProxyStringW(s_szInetProxyW, _countof(s_szInetProxyW))) { + PXCH_INTERNET_PROXY_INFO_W localInfo; + FUNCIPCLOGD(L"InternetSetOptionW: Overriding INTERNET_OPTION_PROXY to %ls", s_szInetProxyW); + localInfo.dwAccessType = PXCH_INTERNET_OPEN_TYPE_PROXY; + localInfo.lpszProxy = s_szInetProxyW; + localInfo.lpszProxyBypass = L""; + return orig_fpWinINet_InternetSetOptionW(hInternet, dwOption, &localInfo, sizeof(localInfo)); + } + } + + return orig_fpWinINet_InternetSetOptionW(hInternet, dwOption, lpBuffer, dwBufferLength); +} + +#endif // __CYGWIN__ diff --git a/src/dll/hookdll_main.c b/src/dll/hookdll_main.c index f80ad6c..ad5fb4d 100644 --- a/src/dll/hookdll_main.c +++ b/src/dll/hookdll_main.c @@ -54,11 +54,6 @@ static const size_t g_EntryDetour_cbpReturnAddrOffsetX86 = 0x0; #endif // !(defined(_M_X64) || defined(__x86_64__)) || !defined(__CYGWIN__) #ifndef __CYGWIN__ -typedef struct _CLIENT_ID { - HANDLE UniqueProcess; - HANDLE UniqueThread; -} CLIENT_ID, * PCLIENT_ID; - typedef LONG KPRIORITY; #endif @@ -166,10 +161,27 @@ DWORD RemoteCopyExecute(const PROCESS_INFORMATION* pPi, DWORD dwCreationFlags, B DWORD dwReturn; HANDLE hRemoteThread; DWORD dwRemoteTid; + int iRetryCount; IPCLOGV(L"CreateProcessW: Before CreateRemoteThread. " WPRDW, 0); - // Create remote thread in target process to execute the code - hRemoteThread = CreateRemoteThread(pPi->hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pTargetRemoteFuncCode, pTargetRemoteData, CREATE_SUSPENDED, &dwRemoteTid); + // For .NET CLR processes: Resume the main thread first so the CLR runtime + // can initialize, then retry CreateRemoteThread. Without this, the remote + // thread may fail because mscoree.dll hasn't loaded the CLR yet. + if (!(dwCreationFlags & CREATE_SUSPENDED)) { + ResumeThread(pPi->hThread); + } + + // Retry loop for CLR processes - give the runtime time to initialize + hRemoteThread = NULL; + for (iRetryCount = 0; iRetryCount < 3; iRetryCount++) { + if (iRetryCount > 0) { + IPCLOGD(L"CreateProcessW: CLR injection retry %d/3, waiting for runtime init...", iRetryCount); + Sleep(500 * iRetryCount); + } + + hRemoteThread = CreateRemoteThread(pPi->hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pTargetRemoteFuncCode, pTargetRemoteData, CREATE_SUSPENDED, &dwRemoteTid); + if (hRemoteThread) break; + } IPCLOGV(L"CreateProcessW: After CreateRemoteThread(). Tid: " WPRDW, dwRemoteTid); if (!hRemoteThread) goto err_create_remote_thread; @@ -701,14 +713,132 @@ DWORD InjectTargetProcess(const PROCESS_INFORMATION* pPi, DWORD dwCreationFlags) return dwReturn; } +// Set proxy environment variables (HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, NO_PROXY) +// so that .NET, Java, Python, Go, curl, and other apps that read these variables +// can use the proxy even when DLL injection fails. +// Also optionally sets per-user Internet Settings proxy for .NET WebRequest.DefaultWebProxy +// (gated behind PXCH_ENABLE_SYSTEM_PROXY=1 environment variable to avoid side effects). +static DWORD g_dwOrigProxyEnable = 0; +static wchar_t g_szOrigProxyServer[512] = { 0 }; +static wchar_t g_szOrigProxyOverride[512] = { 0 }; +static BOOL g_bProxyRegistrySaved = FALSE; + +static void SetProxyEnvironmentVariables(void) +{ + const PXCH_PROXY_DATA* pProxy; + const wchar_t* szScheme; + PXCH_UINT16 wPort; + wchar_t szHostW[PXCH_MAX_HOSTNAME_BUFSIZE]; + wchar_t szProxyUrl[512]; + wchar_t szNoProxy[512]; + wchar_t szProxyServer[512]; + HKEY hKey; + + if (!g_pPxchConfig || g_pPxchConfig->dwSetProxyEnv == 0) return; + if (g_pPxchConfig->dwProxyNum == 0) return; + + pProxy = &PXCH_CONFIG_PROXY_ARR(g_pPxchConfig)[0]; + + if (ProxyIsType(SOCKS5, *pProxy)) { + szScheme = L"socks5"; + } else if (ProxyIsType(SOCKS4, *pProxy)) { + szScheme = L"socks4"; + } else if (ProxyIsType(HTTP, *pProxy)) { + szScheme = L"http"; + } else { + return; + } + + // Extract host + if (HostIsType(HOSTNAME, pProxy->CommonHeader.HostPort)) { + StringCchCopyW(szHostW, _countof(szHostW), pProxy->CommonHeader.HostPort.HostnamePort.szValue); + } else if (HostIsType(IPV4, pProxy->CommonHeader.HostPort)) { + const unsigned char* b = (const unsigned char*)&((const PXCH_IP_PORT*)&pProxy->CommonHeader.HostPort)->Sockaddr.Data[2]; + StringCchPrintfW(szHostW, _countof(szHostW), L"%u.%u.%u.%u", b[0], b[1], b[2], b[3]); + } else { + return; + } + + wPort = _byteswap_ushort(pProxy->CommonHeader.HostPort.CommonHeader.wPort); + + // Build proxy URL: scheme://host:port + StringCchPrintfW(szProxyUrl, _countof(szProxyUrl), L"%ls://%ls:%u", szScheme, szHostW, (unsigned int)wPort); + + // Build proxy server string for IE/WinHTTP: socks=host:port or host:port + if (ProxyIsType(SOCKS5, *pProxy) || ProxyIsType(SOCKS4, *pProxy)) { + StringCchPrintfW(szProxyServer, _countof(szProxyServer), L"socks=%ls:%u", szHostW, (unsigned int)wPort); + } else { + StringCchPrintfW(szProxyServer, _countof(szProxyServer), L"%ls:%u", szHostW, (unsigned int)wPort); + } + + // 1. Set standard proxy environment variables + // Supported by: .NET HttpClient (4.7+), Java, Python, Go, curl, wget, npm, git + SetEnvironmentVariableW(L"HTTP_PROXY", szProxyUrl); + SetEnvironmentVariableW(L"HTTPS_PROXY", szProxyUrl); + SetEnvironmentVariableW(L"ALL_PROXY", szProxyUrl); + SetEnvironmentVariableW(L"http_proxy", szProxyUrl); + SetEnvironmentVariableW(L"https_proxy", szProxyUrl); + SetEnvironmentVariableW(L"all_proxy", szProxyUrl); + + // Set NO_PROXY for localhost/private networks + StringCchCopyW(szNoProxy, _countof(szNoProxy), L"localhost,127.0.0.1,::1,10.*,172.16.*,192.168.*"); + SetEnvironmentVariableW(L"NO_PROXY", szNoProxy); + SetEnvironmentVariableW(L"no_proxy", szNoProxy); + + IPCLOGD(L"Set proxy environment variables: %ls (NO_PROXY=%ls)", szProxyUrl, szNoProxy); + + // 2. Optionally set per-user Internet Settings proxy (for .NET WebRequest.DefaultWebProxy, + // IE/Edge, and apps using system proxy settings). + // Gated behind PXCH_ENABLE_SYSTEM_PROXY=1 env var to avoid permanently changing + // the user's system proxy settings. Previous values are saved for restore on detach. + { + wchar_t szEnableFlag[8] = { 0 }; + DWORD cch = GetEnvironmentVariableW(L"PXCH_ENABLE_SYSTEM_PROXY", szEnableFlag, (DWORD)_countof(szEnableFlag)); + if (cch > 0 && szEnableFlag[0] == L'1' && szEnableFlag[1] == L'\0') { + if (RegOpenKeyExW(HKEY_CURRENT_USER, + L"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", + 0, KEY_READ | KEY_WRITE, &hKey) == ERROR_SUCCESS) { + DWORD dwType, dwSize; + // Save original values for restore + dwSize = sizeof(g_dwOrigProxyEnable); + if (RegQueryValueExW(hKey, L"ProxyEnable", NULL, &dwType, (BYTE*)&g_dwOrigProxyEnable, &dwSize) != ERROR_SUCCESS) + g_dwOrigProxyEnable = 0; + dwSize = sizeof(g_szOrigProxyServer); + if (RegQueryValueExW(hKey, L"ProxyServer", NULL, &dwType, (BYTE*)g_szOrigProxyServer, &dwSize) != ERROR_SUCCESS) + g_szOrigProxyServer[0] = L'\0'; + dwSize = sizeof(g_szOrigProxyOverride); + if (RegQueryValueExW(hKey, L"ProxyOverride", NULL, &dwType, (BYTE*)g_szOrigProxyOverride, &dwSize) != ERROR_SUCCESS) + g_szOrigProxyOverride[0] = L'\0'; + g_bProxyRegistrySaved = TRUE; + + // Set new proxy values + { + DWORD dwEnable = 1; + static const wchar_t szOverride[] = L"localhost;127.0.0.1;10.*;172.16.*;192.168.*;::1;"; + RegSetValueExW(hKey, L"ProxyEnable", 0, REG_DWORD, (const BYTE*)&dwEnable, sizeof(dwEnable)); + RegSetValueExW(hKey, L"ProxyServer", 0, REG_SZ, (const BYTE*)szProxyServer, (DWORD)((wcslen(szProxyServer) + 1) * sizeof(wchar_t))); + RegSetValueExW(hKey, L"ProxyOverride", 0, REG_SZ, (const BYTE*)szOverride, (DWORD)(sizeof(szOverride))); + } + RegCloseKey(hKey); + IPCLOGD(L"Set IE proxy settings: %ls (will restore on detach)", szProxyServer); + } + } + } +} + PXCH_DLL_API DWORD __stdcall InitHookForMain(PROXYCHAINS_CONFIG* pPxchConfig) { + g_pPxchConfig = pPxchConfig; + MH_Initialize(); // CREATE_HOOK(CreateProcessA); CREATE_HOOK(CreateProcessW); // CREATE_HOOK(CreateProcessAsUserW); MH_EnableHook(MH_ALL_HOOKS); + // Set proxy env vars in the launcher process so the first child inherits them + SetProxyEnvironmentVariables(); + LOGD(L"Main Program Hooked!"); return 0; } @@ -743,6 +873,7 @@ PXCH_DLL_API DWORD __stdcall InitHook(PXCH_INJECT_REMOTE_DATA* pRemoteData) // ALL HOOKS MUST BE DONE HERE // AFTER fork() RESTORES DATA SEGMENT, MINHOOK IS IN UNCERTAIN STATE Win32HookWs2_32(); + Win32HookWinHttp(); //CygwinHook(); ODBGSTRLOGD(L"InitHook: before MH_EnableHook"); @@ -754,6 +885,9 @@ PXCH_DLL_API DWORD __stdcall InitHook(PXCH_INJECT_REMOTE_DATA* pRemoteData) ODBGSTRLOGD(L"InitHook: after MH_EnableHook"); + // Set proxy environment variables so .NET/Java/Python/Go apps use the proxy + SetProxyEnvironmentVariables(); + dwLastError = IpcClientRegisterChildProcessAndBackupChildData(); if (dwLastError) { @@ -771,6 +905,28 @@ PXCH_DLL_API DWORD __stdcall InitHook(PXCH_INJECT_REMOTE_DATA* pRemoteData) PXCH_DLL_API void UninitHook(void) { + // Restore original IE proxy registry settings if we modified them + if (g_bProxyRegistrySaved) { + HKEY hKey; + if (RegOpenKeyExW(HKEY_CURRENT_USER, + L"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", + 0, KEY_WRITE, &hKey) == ERROR_SUCCESS) { + RegSetValueExW(hKey, L"ProxyEnable", 0, REG_DWORD, (const BYTE*)&g_dwOrigProxyEnable, sizeof(g_dwOrigProxyEnable)); + if (g_szOrigProxyServer[0]) { + RegSetValueExW(hKey, L"ProxyServer", 0, REG_SZ, (const BYTE*)g_szOrigProxyServer, (DWORD)((wcslen(g_szOrigProxyServer) + 1) * sizeof(wchar_t))); + } else { + RegDeleteValueW(hKey, L"ProxyServer"); + } + if (g_szOrigProxyOverride[0]) { + RegSetValueExW(hKey, L"ProxyOverride", 0, REG_SZ, (const BYTE*)g_szOrigProxyOverride, (DWORD)((wcslen(g_szOrigProxyOverride) + 1) * sizeof(wchar_t))); + } else { + RegDeleteValueW(hKey, L"ProxyOverride"); + } + RegCloseKey(hKey); + } + g_bProxyRegistrySaved = FALSE; + } + MH_DisableHook(MH_ALL_HOOKS); MH_Uninitialize(); diff --git a/src/dll/hookdll_util_log_win32.c b/src/dll/hookdll_util_log_win32.c index c3173f6..e94121e 100644 --- a/src/dll/hookdll_util_log_win32.c +++ b/src/dll/hookdll_util_log_win32.c @@ -150,7 +150,53 @@ PXCH_DLL_API void StdVwprintf(DWORD dwStdHandle, const WCHAR* fmt, va_list args) #ifndef __CYGWIN__ h = GetStdHandle(dwStdHandle); - if (h && h != INVALID_HANDLE_VALUE) WriteFile(h, g_szFwprintfBuf, iBufSize, &cbWritten, NULL); + if (h && h != INVALID_HANDLE_VALUE) { + WORD wColor = 0; + CONSOLE_SCREEN_BUFFER_INFO csbi; + BOOL bUseColor = (g_pPxchConfig && g_pPxchConfig->dwLogColor); + + if (bUseColor && GetConsoleScreenBufferInfo(h, &csbi)) { + // Detect log level tag: [C], [E], [W], [I], [D], [V] at start + // Format: "[X] " or "[PIDNNNNN] [X] " + const WCHAR* p = g_szFwprintfWbuf; + WCHAR levelChar = 0; + + // Skip past "[PIDnnnnn] " prefix if present + if (p[0] == L'[') { + const WCHAR* q = p + 1; + while (*q && *q != L']') q++; + if (*q == L']') { + q++; // skip ']' + if (*q == L' ') q++; // skip space + // Check for level tag "[X] " + if (q[0] == L'[' && q[2] == L']') { + levelChar = q[1]; + } else if (p[2] == L']') { + // Direct format: "[X] " + levelChar = p[1]; + } + } + } + + switch (levelChar) { + case L'C': wColor = FOREGROUND_RED | FOREGROUND_INTENSITY; break; + case L'E': wColor = FOREGROUND_RED | FOREGROUND_INTENSITY; break; + case L'W': wColor = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; break; + case L'I': wColor = FOREGROUND_GREEN | FOREGROUND_INTENSITY; break; + case L'D': wColor = FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY; break; + case L'V': wColor = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; break; + default: bUseColor = FALSE; break; + } + + if (bUseColor) SetConsoleTextAttribute(h, wColor); + } else { + bUseColor = FALSE; + } + + WriteFile(h, g_szFwprintfBuf, iBufSize, &cbWritten, NULL); + + if (bUseColor) SetConsoleTextAttribute(h, csbi.wAttributes); + } #else // __CYGWIN__ DWORD dwWaitResult; DWORD dwLastError; diff --git a/src/dll/ipc_client_and_child_data.c b/src/dll/ipc_client_and_child_data.c index 50fd4c1..39b2557 100644 --- a/src/dll/ipc_client_and_child_data.c +++ b/src/dll/ipc_client_and_child_data.c @@ -159,6 +159,13 @@ DWORD IpcClientRegisterChildProcessAndBackupChildData() ORIGINAL_FUNC_BACKUP2(Ws2_32, getnameinfo); ORIGINAL_FUNC_BACKUP2(Ws2_32, GetNameInfoW); + ORIGINAL_FUNC_BACKUP2(WinHttp, Open); + ORIGINAL_FUNC_BACKUP2(WinHttp, SetOption); + ORIGINAL_FUNC_BACKUP2(WinINet, InternetOpenA); + ORIGINAL_FUNC_BACKUP2(WinINet, InternetOpenW); + ORIGINAL_FUNC_BACKUP2(WinINet, InternetSetOptionA); + ORIGINAL_FUNC_BACKUP2(WinINet, InternetSetOptionW); + if ((dwLastError = ChildDataToMessage(chMessageBuf, (PXCH_UINT32*)&cbMessageSize, pChildData)) != NO_ERROR) return dwLastError; if ((dwLastError = IpcCommunicateWithServer(chMessageBuf, cbMessageSize, chRespMessageBuf, (PXCH_UINT32*)&cbRespMessageSize)) != NO_ERROR) return dwLastError; @@ -242,6 +249,13 @@ PXCH_UINT32 RestoreChildDataIfNecessary() ORIGINAL_FUNC_RESTORE2(Ws2_32, getnameinfo); ORIGINAL_FUNC_RESTORE2(Ws2_32, GetNameInfoW); + ORIGINAL_FUNC_RESTORE2(WinHttp, Open); + ORIGINAL_FUNC_RESTORE2(WinHttp, SetOption); + ORIGINAL_FUNC_RESTORE2(WinINet, InternetOpenA); + ORIGINAL_FUNC_RESTORE2(WinINet, InternetOpenW); + ORIGINAL_FUNC_RESTORE2(WinINet, InternetSetOptionA); + ORIGINAL_FUNC_RESTORE2(WinINet, InternetSetOptionW); + IPCLOGV(L"g_pPxchConfig restored to %p", g_pPxchConfig); IPCLOGV(L"g_pRemoteData restored to %p", g_pRemoteData); IPCLOGV(L"g_arrHeapAllocatedPointers restored to %p", g_arrHeapAllocatedPointers); diff --git a/src/exe/args_and_config.c b/src/exe/args_and_config.c index 26964fc..46c9c0b 100644 --- a/src/exe/args_and_config.c +++ b/src/exe/args_and_config.c @@ -33,11 +33,13 @@ #include "log_win32.h" #include "hookdll_win32.h" #include "hookdll_util_win32.h" +#include "embedded_resources.h" #ifndef __CYGWIN__ #pragma comment(lib, "Shlwapi.lib") #pragma comment(lib, "Ws2_32.lib") #define popen _popen +#define pclose _pclose #endif #define PXCH_CONFIG_PARSE_WHITE L" \n\t\r\v" @@ -67,6 +69,18 @@ static const WCHAR* pszParseErrorMessage; +// Expand environment variables in a wide string path (e.g. %USERPROFILE%\file) +// Returns TRUE if expansion was performed, FALSE if no change or error +static BOOL ExpandEnvironmentPath(WCHAR* szDest, size_t cchDest, const WCHAR* szSrc) +{ + DWORD dwResult = ExpandEnvironmentStringsW(szSrc, szDest, (DWORD)cchDest); + if (dwResult == 0 || dwResult > (DWORD)cchDest) { + StringCchCopyW(szDest, cchDest, szSrc); + return FALSE; + } + return TRUE; +} + // impl: stdlib_config_reader.c PXCH_UINT32 OpenConfigurationFile(PROXYCHAINS_CONFIG* pPxchConfig); PXCH_UINT32 OpenHostsFile(const WCHAR* szHostsFilePath); @@ -500,6 +514,11 @@ void PrintConfiguration(PROXYCHAINS_CONFIG* pPxchConfig) LOGD(L"WillFirstTunnelUseIpv4: " WPRDW, pPxchConfig->dwWillFirstTunnelUseIpv4); LOGD(L"WillFirstTunnelUseIpv6: " WPRDW, pPxchConfig->dwWillFirstTunnelUseIpv6); LOGD(L"WillGenFakeIpUsingHashedHostname: " WPRDW, pPxchConfig->dwWillGenFakeIpUsingHashedHostname); + LOGD(L"DnsCacheTtlSeconds: " WPRDW, pPxchConfig->dwDnsCacheTtlSeconds); + LOGD(L"HasCustomDnsServer: " WPRDW, pPxchConfig->dwHasCustomDnsServer); + if (pPxchConfig->dwHasCustomDnsServer) { + LOGD(L"CustomDnsServerPort: " WPRDW, pPxchConfig->dwCustomDnsServerPort); + } switch (pPxchConfig->dwDefaultTarget) { case PXCH_RULE_TARGET_BLOCK: pszTargetDesc = L"BLOCK"; break; case PXCH_RULE_TARGET_DIRECT: pszTargetDesc = L"DIRECT"; break; @@ -563,6 +582,93 @@ void PrintConfiguration(PROXYCHAINS_CONFIG* pPxchConfig) LOGD(L"PXCH_CONFIG_EXTRA_SIZE_G: " WPRDW, PXCH_CONFIG_EXTRA_SIZE_G); } +#ifndef __CYGWIN__ +static BOOL ExtractEmbeddedDll(UINT uResourceId, const WCHAR* szOutputPath) +{ + HRSRC hRes; + HGLOBAL hResData; + LPVOID pData; + DWORD dwSize; + HANDLE hFile; + DWORD dwWritten; + + hRes = FindResourceW(NULL, MAKEINTRESOURCEW(uResourceId), PXCH_EMBEDDED_DLL_TYPE); + if (!hRes) { + LOGV(L"Embedded DLL resource " WPRDW L" not found", uResourceId); + return FALSE; + } + + hResData = LoadResource(NULL, hRes); + if (!hResData) { + LOGW(L"Failed to load embedded DLL resource " WPRDW, uResourceId); + return FALSE; + } + + pData = LockResource(hResData); + dwSize = SizeofResource(NULL, hRes); + if (!pData || dwSize == 0) { + LOGW(L"Failed to access embedded DLL resource " WPRDW, uResourceId); + return FALSE; + } + + hFile = CreateFileW(szOutputPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) { + LOGW(L"Failed to create file for embedded DLL: %ls", szOutputPath); + return FALSE; + } + + if (!WriteFile(hFile, pData, dwSize, &dwWritten, NULL) || dwWritten != dwSize) { + LOGW(L"Failed to write embedded DLL to: %ls", szOutputPath); + CloseHandle(hFile); + DeleteFileW(szOutputPath); + return FALSE; + } + + CloseHandle(hFile); + LOGD(L"Extracted embedded DLL to: %ls (" WPRDW L" bytes)", szOutputPath, dwSize); + return TRUE; +} + +static BOOL EnsureDllFromResources(PROXYCHAINS_CONFIG* pPxchConfig) +{ + WCHAR szTempDir[MAX_PATH]; + WCHAR szTempPath[MAX_PATH]; + DWORD dwRet; + BOOL bExtractedAny = FALSE; + + dwRet = GetTempPathW(MAX_PATH, szTempDir); + if (dwRet == 0 || dwRet >= MAX_PATH) return FALSE; + + if (FAILED(StringCchCatW(szTempDir, MAX_PATH, L"proxychains\\"))) return FALSE; + + CreateDirectoryW(szTempDir, NULL); + + // Extract x64 hook DLL if not found beside exe + if (!PathFileExistsW(pPxchConfig->szHookDllPathX64)) { + if (FAILED(StringCchCopyW(szTempPath, MAX_PATH, szTempDir))) return FALSE; + if (FAILED(StringCchCatW(szTempPath, MAX_PATH, g_szHookDllFileNameX64))) return FALSE; + + if (ExtractEmbeddedDll(IDR_HOOK_DLL_X64, szTempPath)) { + StringCchCopyW(pPxchConfig->szHookDllPathX64, PXCH_MAX_DLL_PATH_BUFSIZE, szTempPath); + bExtractedAny = TRUE; + } + } + + // Extract x86 hook DLL if not found beside exe + if (!PathFileExistsW(pPxchConfig->szHookDllPathX86)) { + if (FAILED(StringCchCopyW(szTempPath, MAX_PATH, szTempDir))) return FALSE; + if (FAILED(StringCchCatW(szTempPath, MAX_PATH, g_szHookDllFileNameX86))) return FALSE; + + if (ExtractEmbeddedDll(IDR_HOOK_DLL_X86, szTempPath)) { + StringCchCopyW(pPxchConfig->szHookDllPathX86, PXCH_MAX_DLL_PATH_BUFSIZE, szTempPath); + bExtractedAny = TRUE; + } + } + + return bExtractedAny; +} +#endif + DWORD LoadConfiguration(PROXYCHAINS_CONFIG** ppPxchConfig, PROXYCHAINS_CONFIG* pTempPxchConfig) { DWORD dwLastError; @@ -607,8 +713,8 @@ DWORD LoadConfiguration(PROXYCHAINS_CONFIG** ppPxchConfig, PROXYCHAINS_CONFIG* p if (FAILED(StringCchCopyW(pPxchConfig->szHookDllPathX86, PXCH_MAX_DLL_PATH_BUFSIZE, pPxchConfig->szHookDllPathX64))) goto err_insuf_buf; if (FAILED(StringCchCopyW(pPxchConfig->szMinHookDllPathX64, PXCH_MAX_DLL_PATH_BUFSIZE, pPxchConfig->szHookDllPathX64))) goto err_insuf_buf; if (FAILED(StringCchCopyW(pPxchConfig->szMinHookDllPathX86, PXCH_MAX_DLL_PATH_BUFSIZE, pPxchConfig->szHookDllPathX64))) goto err_insuf_buf; - if (FAILED(StringCchPrintfA(szHelperX64CommandLine, PXCH_MAX_HELPER_PATH_BUFSIZE, "%ls", pPxchConfig->szHookDllPathX64))) goto err_insuf_buf; - if (FAILED(StringCchPrintfA(szHelperX86CommandLine, PXCH_MAX_HELPER_PATH_BUFSIZE, "%ls", pPxchConfig->szHookDllPathX64))) goto err_insuf_buf; + if (FAILED(StringCchPrintfA(szHelperX64CommandLine, PXCH_MAX_HELPER_PATH_BUFSIZE, "\"%ls", pPxchConfig->szHookDllPathX64))) goto err_insuf_buf; + if (FAILED(StringCchPrintfA(szHelperX86CommandLine, PXCH_MAX_HELPER_PATH_BUFSIZE, "\"%ls", pPxchConfig->szHookDllPathX64))) goto err_insuf_buf; #ifdef __CYGWIN__ { @@ -627,6 +733,12 @@ DWORD LoadConfiguration(PROXYCHAINS_CONFIG** ppPxchConfig, PROXYCHAINS_CONFIG* p #if defined(_M_X64) || defined(__x86_64__) // x64 build: Require x64 DLL, warn if x86 DLL missing (limits to 64-bit injection only) +#ifndef __CYGWIN__ + if (!PathFileExistsW(pPxchConfig->szHookDllPathX64) || !PathFileExistsW(pPxchConfig->szHookDllPathX86)) { + // Try extracting embedded DLLs from exe resources + EnsureDllFromResources(pPxchConfig); + } +#endif if (!PathFileExistsW(pPxchConfig->szHookDllPathX64)) goto err_dll_not_exist; if (!PathFileExistsW(pPxchConfig->szHookDllPathX86)) { LOGW(L"Warning: x86 DLL not found. Will not be able to inject into 32-bit processes."); @@ -634,6 +746,12 @@ DWORD LoadConfiguration(PROXYCHAINS_CONFIG** ppPxchConfig, PROXYCHAINS_CONFIG* p } #else // x86 build: Only check x86 DLL (cannot inject into x64 processes anyway) +#ifndef __CYGWIN__ + if (!PathFileExistsW(pPxchConfig->szHookDllPathX86)) { + // Try extracting embedded DLL from exe resources + EnsureDllFromResources(pPxchConfig); + } +#endif if (!PathFileExistsW(pPxchConfig->szHookDllPathX86)) goto err_dll_not_exist; #endif if (!PathFileExistsW(pPxchConfig->szMinHookDllPathX64)) StringCchCopyW(pPxchConfig->szMinHookDllPathX64, PXCH_MAX_DLL_PATH_BUFSIZE, g_szMinHookDllFileNameX64); @@ -671,6 +789,23 @@ DWORD LoadConfiguration(PROXYCHAINS_CONFIG** ppPxchConfig, PROXYCHAINS_CONFIG* p pPxchConfig->dwWillUseFakeIpAsRemoteDns = FALSE; pPxchConfig->dwWillGenFakeIpUsingHashedHostname = TRUE; + pPxchConfig->dwChainType = PXCH_CHAIN_TYPE_STRICT; + pPxchConfig->dwChainLen = 1; + pPxchConfig->dwRandomSeed = 0; + pPxchConfig->dwRandomSeedSet = 0; + + pPxchConfig->dwDnsCacheTtlSeconds = 0; + pPxchConfig->dwHasCustomDnsServer = FALSE; + pPxchConfig->dwCustomDnsServerPort = 53; + ZeroMemory(&pPxchConfig->CustomDnsServer, sizeof(pPxchConfig->CustomDnsServer)); + pPxchConfig->dwHasLogFile = FALSE; + ZeroMemory(pPxchConfig->szLogFilePath, sizeof(pPxchConfig->szLogFilePath)); + + pPxchConfig->dwSetProxyEnv = TRUE; + pPxchConfig->dwLogColor = TRUE; + pPxchConfig->dwTrafficDump = FALSE; + ZeroMemory(pPxchConfig->szTrafficDumpDir, sizeof(pPxchConfig->szTrafficDumpDir)); + // Parse configuration file if ((dwLastError = OpenConfigurationFile(pTempPxchConfig)) != NO_ERROR) goto err_general; @@ -691,16 +826,28 @@ DWORD LoadConfiguration(PROXYCHAINS_CONFIG** ppPxchConfig, PROXYCHAINS_CONFIG* p bIntoProxyList = TRUE; } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"socks5")) { dwProxyNum++; + } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"socks4")) { + dwProxyNum++; + } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"http")) { + dwProxyNum++; } else if (bIntoProxyList) { LOGE(L"Config line %llu: Unknown proxy: %.*ls", ullLineNum, sOptionNameEnd - sOption, sOption); goto err_invalid_config; } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"strict_chain")) { + pTempPxchConfig->dwChainType = PXCH_CHAIN_TYPE_STRICT; + } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"dynamic_chain")) { + pTempPxchConfig->dwChainType = PXCH_CHAIN_TYPE_DYNAMIC; } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"random_chain")) { - pszParseErrorMessage = L"random_chain is not supported!"; - goto err_invalid_config_with_msg; + pTempPxchConfig->dwChainType = PXCH_CHAIN_TYPE_RANDOM; + } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"round_robin_chain")) { + pTempPxchConfig->dwChainType = PXCH_CHAIN_TYPE_ROUND_ROBIN; } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"chain_len")) { - pszParseErrorMessage = L"chain_len is not supported!"; - goto err_invalid_config_with_msg; + if (OptionGetNumberValueAfterOptionName(&lValue, sOptionNameEnd, NULL, 1, PXCH_MAX_PROXY_NUM) == -1) goto err_invalid_config_with_msg; + pTempPxchConfig->dwChainLen = (PXCH_UINT32)lValue; + } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"random_seed")) { + if (OptionGetNumberValueAfterOptionName(&lValue, sOptionNameEnd, NULL, 0, 2147483647) == -1) goto err_invalid_config_with_msg; + pTempPxchConfig->dwRandomSeed = (PXCH_UINT32)lValue; + pTempPxchConfig->dwRandomSeedSet = 1; } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"quiet_mode")) { if (!pTempPxchConfig->dwLogLevelSetByArg) { LOGD(L"Queit mode enabled in configuration file"); @@ -711,11 +858,36 @@ DWORD LoadConfiguration(PROXYCHAINS_CONFIG** ppPxchConfig, PROXYCHAINS_CONFIG* p if (OptionGetNumberValueAfterOptionName(&lValue, sOptionNameEnd, NULL, 0, 1000) == -1) goto err_invalid_config_with_msg; pPxchConfig->dwLogLevel = (DWORD)lValue; } + } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"log_file")) { + pszParseErrorMessage = L"Configuration option 'log_file' is not currently supported; please remove or comment it out."; + goto err_invalid_config_with_msg; } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"proxy_dns")) { pTempPxchConfig->dwWillUseFakeIpAsRemoteDns = TRUE; } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"proxy_dns_udp_associate")) { - pszParseErrorMessage = L"proxy_dns_udp_associate is not supported!"; - goto err_invalid_config_with_msg; + pPxchConfig->dwWillUseUdpAssociateAsRemoteDns = TRUE; + pTempPxchConfig->dwWillUseFakeIpAsRemoteDns = TRUE; + } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"dns_cache_ttl")) { + if (OptionGetNumberValueAfterOptionName(&lValue, sOptionNameEnd, NULL, 0, 86400) == -1) goto err_invalid_config_with_msg; + pPxchConfig->dwDnsCacheTtlSeconds = (PXCH_UINT32)lValue; + } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"dns_server")) { + { + PXCH_IP_PORT DnsIpPort; + PXCH_UINT32 dwDnsCidr; + if (OptionGetIpPortValueAfterOptionName(&DnsIpPort, &dwDnsCidr, sOptionNameEnd, NULL, FALSE, FALSE) == 0) { + if (DnsIpPort.CommonHeader.wTag == PXCH_HOST_TYPE_IPV4) { + pPxchConfig->CustomDnsServer = *(PXCH_IP_ADDRESS*)&DnsIpPort; + pPxchConfig->dwCustomDnsServerPort = ntohs(DnsIpPort.CommonHeader.wPort); + if (pPxchConfig->dwCustomDnsServerPort == 0) pPxchConfig->dwCustomDnsServerPort = 53; + pPxchConfig->dwHasCustomDnsServer = TRUE; + } else { + pszParseErrorMessage = L"dns_server only supports IPv4 addresses!"; + goto err_invalid_config_with_msg; + } + } else { + pszParseErrorMessage = L"Invalid dns_server address!"; + goto err_invalid_config_with_msg; + } + } } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"remote_dns_subnet")) { if (OptionGetNumberValueAfterOptionName(&lValue, sOptionNameEnd, NULL, 0, 255) == -1) goto err_invalid_config_with_msg; pPxchConfig->dwFakeIpv4PrefixLength = 8; @@ -798,8 +970,10 @@ DWORD LoadConfiguration(PROXYCHAINS_CONFIG** ppPxchConfig, PROXYCHAINS_CONFIG* p } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"custom_hosts_file_path")) { const WCHAR* pPathStart; const WCHAR* pPathEnd; + WCHAR szTempPath[PXCH_MAX_HOSTS_FILE_PATH_BUFSIZE]; if (OptionGetStringValueAfterOptionName(&pPathStart, &pPathEnd, sOptionNameEnd, NULL) == -1) goto err_invalid_config_with_msg; - if (FAILED(StringCchCopyNW(pPxchConfig->szHostsFilePath, _countof(pPxchConfig->szHostsFilePath), pPathStart, pPathEnd - pPathStart))) goto err_insuf_buf; + if (FAILED(StringCchCopyNW(szTempPath, _countof(szTempPath), pPathStart, pPathEnd - pPathStart))) goto err_insuf_buf; + ExpandEnvironmentPath(pPxchConfig->szHostsFilePath, _countof(pPxchConfig->szHostsFilePath), szTempPath); } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"default_target")) { const WCHAR* pTargetStart; const WCHAR* pTargetEnd; @@ -814,6 +988,36 @@ DWORD LoadConfiguration(PROXYCHAINS_CONFIG** ppPxchConfig, PROXYCHAINS_CONFIG* p pszParseErrorMessage = L"Invalid default target"; goto err_invalid_config_with_msg; } + } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"process_only") || WSTR_EQUAL(sOption, sOptionNameEnd, L"process_except")) { + const WCHAR* pNameStart; + const WCHAR* pNameEnd; + PXCH_UINT32 dwMode = WSTR_EQUAL(sOption, sOptionNameEnd, L"process_only") ? PXCH_PROCESS_FILTER_WHITELIST : PXCH_PROCESS_FILTER_BLACKLIST; + + if (pTempPxchConfig->dwProcessFilterMode != PXCH_PROCESS_FILTER_NONE && pTempPxchConfig->dwProcessFilterMode != dwMode) { + pszParseErrorMessage = L"Cannot mix process_only and process_except directives"; + goto err_invalid_config_with_msg; + } + pTempPxchConfig->dwProcessFilterMode = dwMode; + + if (OptionGetStringValueAfterOptionName(&pNameStart, &pNameEnd, sOptionNameEnd, NULL) == -1) goto err_invalid_config_with_msg; + if (pTempPxchConfig->dwProcessFilterCount >= PXCH_MAX_PROCESS_FILTER_NUM) { + pszParseErrorMessage = L"Too many process filter entries (max 8)"; + goto err_invalid_config_with_msg; + } + if (FAILED(StringCchCopyNW(pTempPxchConfig->szProcessFilterNames[pTempPxchConfig->dwProcessFilterCount], PXCH_MAX_HOSTNAME_BUFSIZE, pNameStart, pNameEnd - pNameStart))) goto err_insuf_buf; + pTempPxchConfig->dwProcessFilterCount++; + } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"set_proxy_env")) { + if (OptionGetNumberValueAfterOptionName(&lValue, sOptionNameEnd, NULL, 0, 1) == -1) goto err_invalid_config; + pPxchConfig->dwSetProxyEnv = (PXCH_UINT32)lValue; + } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"log_color")) { + if (OptionGetNumberValueAfterOptionName(&lValue, sOptionNameEnd, NULL, 0, 1) == -1) goto err_invalid_config; + pPxchConfig->dwLogColor = (PXCH_UINT32)lValue; + } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"traffic_dump_dir")) { + WCHAR* pNameStart; + WCHAR* pNameEnd; + if (OptionGetStringValueAfterOptionName(&pNameStart, &pNameEnd, sOptionNameEnd, NULL) == -1) goto err_invalid_config_with_msg; + if (FAILED(StringCchCopyNW(pPxchConfig->szTrafficDumpDir, PXCH_MAX_CONFIG_FILE_PATH_BUFSIZE, pNameStart, pNameEnd - pNameStart))) goto err_insuf_buf; + pPxchConfig->dwTrafficDump = 1; } else { LOGE(L"Config line %llu: Unknown option: %.*ls", ullLineNum, sOptionNameEnd - sOption, sOption); goto err_invalid_config; @@ -969,6 +1173,128 @@ DWORD LoadConfiguration(PROXYCHAINS_CONFIG** ppPxchConfig, PROXYCHAINS_CONFIG* p StringCchCopyA(pSocks5->Ws2_32_HandshakeFunctionName, _countof(pSocks5->Ws2_32_HandshakeFunctionName), "Ws2_32_Socks5Handshake"); pSocks5->iAddrLen = sizeof(PXCH_HOST_PORT); dwProxyCounter++; + } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"socks4")) { + WCHAR* sHostStart; + WCHAR* sHostEnd; + WCHAR* sPortStart; + WCHAR* sPortEnd; + WCHAR* sUserStart; + WCHAR* sUserEnd; + long lPort; + + PXCH_PROXY_SOCKS4_DATA* pSocks4 = &PXCH_CONFIG_PROXY_ARR(pPxchConfig)[dwProxyCounter].Socks4; + + pSocks4->dwTag = PXCH_PROXY_TYPE_SOCKS4; + + sHostStart = ConsumeStringInSet(sOptionNameEnd, NULL, PXCH_CONFIG_PARSE_WHITE); + sHostEnd = ConsumeStringUntilSet(sHostStart, NULL, PXCH_CONFIG_PARSE_WHITE); + + if (sHostStart == sHostEnd) { + pszParseErrorMessage = L"SOCKS4 server host missing"; + goto err_invalid_config_with_msg; + } + + if (OptionGetIpPortValue((PXCH_IP_PORT*)&pSocks4->HostPort, NULL, sHostStart, sHostEnd, FALSE, TRUE) == 0) { + if (pSocks4->HostPort.CommonHeader.wPort != 0) { + pszParseErrorMessage = L"SOCKS4 server host address should not have port (place port number after the address and separate them with whitespaces)"; + goto err_invalid_config_with_msg; + } + } else { + pSocks4->HostPort.HostnamePort.wTag = PXCH_HOST_TYPE_HOSTNAME; + StringCchCopyNW(pSocks4->HostPort.HostnamePort.szValue, _countof(pSocks4->HostPort.HostnamePort.szValue), sHostStart, sHostEnd - sHostStart); + } + + sPortStart = ConsumeStringInSet(sHostEnd, NULL, PXCH_CONFIG_PARSE_WHITE); + sPortEnd = ConsumeStringUntilSet(sPortStart, NULL, PXCH_CONFIG_PARSE_WHITE); + + if (sPortStart == sPortEnd) { + pszParseErrorMessage = L"SOCKS4 server port missing"; + goto err_invalid_config_with_msg; + } + + if (OptionGetNumberValue(&lPort, sPortStart, sPortEnd, 1, 65535, TRUE)) { + goto err_invalid_config_with_msg; + } + + pSocks4->HostPort.CommonHeader.wPort = ntohs((PXCH_UINT16)lPort); + + sUserStart = ConsumeStringInSet(sPortEnd, NULL, PXCH_CONFIG_PARSE_WHITE); + sUserEnd = ConsumeStringUntilSet(sUserStart, NULL, PXCH_CONFIG_PARSE_WHITE); + + if (*sUserStart != L'\0' && sUserStart != sUserEnd) { + StringCchPrintfA(pSocks4->szUsername, _countof(pSocks4->szUsername), "%.*ls", sUserEnd - sUserStart, sUserStart); + } + + StringCchCopyA(pSocks4->Ws2_32_ConnectFunctionName, _countof(pSocks4->Ws2_32_ConnectFunctionName), "Ws2_32_Socks4Connect"); + StringCchCopyA(pSocks4->Ws2_32_HandshakeFunctionName, _countof(pSocks4->Ws2_32_HandshakeFunctionName), "Ws2_32_Socks4Handshake"); + pSocks4->iAddrLen = sizeof(PXCH_HOST_PORT); + dwProxyCounter++; + } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"http")) { + WCHAR* sHostStart; + WCHAR* sHostEnd; + WCHAR* sPortStart; + WCHAR* sPortEnd; + WCHAR* sUserPassStart; + WCHAR* sUserPassEnd; + long lPort; + + PXCH_PROXY_HTTP_DATA* pHttp = &PXCH_CONFIG_PROXY_ARR(pPxchConfig)[dwProxyCounter].Http; + + pHttp->dwTag = PXCH_PROXY_TYPE_HTTP; + + sHostStart = ConsumeStringInSet(sOptionNameEnd, NULL, PXCH_CONFIG_PARSE_WHITE); + sHostEnd = ConsumeStringUntilSet(sHostStart, NULL, PXCH_CONFIG_PARSE_WHITE); + + if (sHostStart == sHostEnd) { + pszParseErrorMessage = L"HTTP proxy host missing"; + goto err_invalid_config_with_msg; + } + + if (OptionGetIpPortValue((PXCH_IP_PORT*)&pHttp->HostPort, NULL, sHostStart, sHostEnd, FALSE, TRUE) == 0) { + if (pHttp->HostPort.CommonHeader.wPort != 0) { + pszParseErrorMessage = L"HTTP proxy host address should not have port (place port number after the address and separate them with whitespaces)"; + goto err_invalid_config_with_msg; + } + } else { + pHttp->HostPort.HostnamePort.wTag = PXCH_HOST_TYPE_HOSTNAME; + StringCchCopyNW(pHttp->HostPort.HostnamePort.szValue, _countof(pHttp->HostPort.HostnamePort.szValue), sHostStart, sHostEnd - sHostStart); + } + + sPortStart = ConsumeStringInSet(sHostEnd, NULL, PXCH_CONFIG_PARSE_WHITE); + sPortEnd = ConsumeStringUntilSet(sPortStart, NULL, PXCH_CONFIG_PARSE_WHITE); + + if (sPortStart == sPortEnd) { + pszParseErrorMessage = L"HTTP proxy port missing"; + goto err_invalid_config_with_msg; + } + + if (OptionGetNumberValue(&lPort, sPortStart, sPortEnd, 1, 65535, TRUE)) { + goto err_invalid_config_with_msg; + } + + pHttp->HostPort.CommonHeader.wPort = ntohs((PXCH_UINT16)lPort); + + sUserPassStart = ConsumeStringInSet(sPortEnd, NULL, PXCH_CONFIG_PARSE_WHITE); + sUserPassEnd = ConsumeStringUntilSet(sUserPassStart, NULL, PXCH_CONFIG_PARSE_WHITE); + + if (*sUserPassStart == L'\0' || sUserPassStart == sUserPassEnd) goto http_end; + + StringCchPrintfA(pHttp->szUsername, _countof(pHttp->szUsername), "%.*ls", sUserPassEnd - sUserPassStart, sUserPassStart); + + sUserPassStart = ConsumeStringInSet(sUserPassEnd, NULL, PXCH_CONFIG_PARSE_WHITE); + sUserPassEnd = ConsumeStringUntilSet(sUserPassStart, NULL, PXCH_CONFIG_PARSE_WHITE); + if (*sUserPassStart == L'\0' || sUserPassStart == sUserPassEnd) { + pszParseErrorMessage = L"HTTP proxy password missing"; + goto err_invalid_config_with_msg; + } + + StringCchPrintfA(pHttp->szPassword, _countof(pHttp->szPassword), "%.*ls", sUserPassEnd - sUserPassStart, sUserPassStart); + + http_end: + StringCchCopyA(pHttp->Ws2_32_ConnectFunctionName, _countof(pHttp->Ws2_32_ConnectFunctionName), "Ws2_32_HttpConnect"); + StringCchCopyA(pHttp->Ws2_32_HandshakeFunctionName, _countof(pHttp->Ws2_32_HandshakeFunctionName), "Ws2_32_HttpHandshake"); + pHttp->iAddrLen = sizeof(PXCH_HOST_PORT); + dwProxyCounter++; } else if (WSTR_EQUAL(sOption, sOptionNameEnd, L"localnet")) { PXCH_RULE* pRule = &PXCH_CONFIG_RULE_ARR(pPxchConfig)[dwRuleCounter]; if (OptionGetIpPortValueAfterOptionName(&pRule->HostPort.IpPort, &pRule->dwCidrPrefixLength, sOptionNameEnd, NULL, TRUE, TRUE)) goto err_invalid_config_with_msg; @@ -1165,6 +1491,7 @@ DWORD LoadConfiguration(PROXYCHAINS_CONFIG** ppPxchConfig, PROXYCHAINS_CONFIG* p if (fHelperProcOut == NULL) { LOGW(L"Warning: X86 Helper executable " WPRS L" not found. In this case proxychains.exe will not inject X86 descendant processes.", szHelperX86CommandLine); + LOGW(L"Ensure proxychains_helper_win32_x86.exe is in the same directory as proxychains.exe."); } else { unsigned long long tmp; int i; @@ -1210,6 +1537,7 @@ DWORD LoadConfiguration(PROXYCHAINS_CONFIG** ppPxchConfig, PROXYCHAINS_CONFIG* p default: bStop = TRUE; break; } } + pclose(fHelperProcOut); } } #endif @@ -1288,7 +1616,9 @@ DWORD ParseArgs(PROXYCHAINS_CONFIG* pConfig, int argc, WCHAR* argv[], int* piCom option_value_following: if (bOptionFile) { - if (FAILED(StringCchCopyW(pConfig->szConfigPath, _countof(pConfig->szConfigPath), pWchar))) goto err_insuf_buf; + WCHAR szTempConfigPath[PXCH_MAX_CONFIG_FILE_PATH_BUFSIZE]; + if (FAILED(StringCchCopyW(szTempConfigPath, _countof(szTempConfigPath), pWchar))) goto err_insuf_buf; + ExpandEnvironmentPath(pConfig->szConfigPath, _countof(pConfig->szConfigPath), szTempConfigPath); bOptionFile = FALSE; continue; } diff --git a/src/exe/ipc_proc_bookkeeping.c b/src/exe/ipc_proc_bookkeeping.c index 735cdbe..2b75b36 100644 --- a/src/exe/ipc_proc_bookkeeping.c +++ b/src/exe/ipc_proc_bookkeeping.c @@ -309,7 +309,7 @@ DWORD NextAvailableFakeIpByDomainHash(PXCH_IP_ADDRESS* pFakeIpv4, PXCH_IP_ADDRES IndexToIp(g_pPxchConfig, &AsKey.Ip, iSearchIpv4); LOGV(L"Map index to IPv4: " WPRDW L" -> %ls", iSearchIpv4, FormatHostPortToStr(&AsKey.Ip, sizeof(PXCH_IP_ADDRESS))); HASH_FIND(hh, g_tabFakeIpHostname, &AsKey.Ip, sizeof(PXCH_IP_ADDRESS) + sizeof(PXCH_UINT32), Entry); - if (!Entry || StrCmpW(Entry->Hostname.szValue, pHostname->szValue) == 0) break; + if (!Entry || StrCmpIW(Entry->Hostname.szValue, pHostname->szValue) == 0) break; iSearchIpv4++; if (iSearchIpv4 >= ((PXCH_UINT64)1 << iIpv4ShiftLength) - 1) { iSearchIpv4 = 1; @@ -336,7 +336,7 @@ DWORD NextAvailableFakeIpByDomainHash(PXCH_IP_ADDRESS* pFakeIpv4, PXCH_IP_ADDRES IndexToIp(g_pPxchConfig, &AsKey.Ip, iSearchIpv6); LOGV(L"Map index to IPv6: " WPRDW L" -> %ls", iSearchIpv6, FormatHostPortToStr(&AsKey.Ip, sizeof(PXCH_IP_ADDRESS))); HASH_FIND(hh, g_tabFakeIpHostname, &AsKey.Ip, sizeof(PXCH_IP_ADDRESS) + sizeof(PXCH_UINT32), Entry); - if (!Entry || StrCmpW(Entry->Hostname.szValue, pHostname->szValue) == 0) break; + if (!Entry || StrCmpIW(Entry->Hostname.szValue, pHostname->szValue) == 0) break; iSearchIpv6++; if (iSearchIpv6 >= ((iIpv6ShiftLength == 64) ? 0xFFFFFFFFFFFFFFFF : (((PXCH_UINT64)1 << iIpv6ShiftLength) - 1))) { iSearchIpv6 = 1; diff --git a/src/proxychains_helper.c b/src/proxychains_helper.c index e0a09cc..a483088 100644 --- a/src/proxychains_helper.c +++ b/src/proxychains_helper.c @@ -280,33 +280,33 @@ int main(int argc, const char* const* argv) if (strcmp(argv[1], "--get-winapi-func-addr") == 0) { #if defined(_M_X64) || defined(__x86_64__) - wprintf(L"%llX\n", 0ULL); - wprintf(L"%llX\n", 0ULL); - wprintf(L"%llX\n", 0ULL); - wprintf(L"%llX\n", 0ULL); - wprintf(L"%llX\n", 0ULL); - wprintf(L"%llX\n", 0ULL); - wprintf(L"%llX\n", 0ULL); - wprintf(L"%llX\n", 0ULL); - wprintf(L"%llX\n", 0ULL); - wprintf(L"%llX\n", 0ULL); - wprintf(L"%llX\n", 0ULL); - wprintf(L"%llX\n", 0ULL); - wprintf(L"%llX\n", 0ULL); + printf("%llX\n", 0ULL); + printf("%llX\n", 0ULL); + printf("%llX\n", 0ULL); + printf("%llX\n", 0ULL); + printf("%llX\n", 0ULL); + printf("%llX\n", 0ULL); + printf("%llX\n", 0ULL); + printf("%llX\n", 0ULL); + printf("%llX\n", 0ULL); + printf("%llX\n", 0ULL); + printf("%llX\n", 0ULL); + printf("%llX\n", 0ULL); + printf("%llX\n", 0ULL); #else - wprintf(L"%llX\n", (unsigned long long)(uintptr_t)&GetModuleHandleW); - wprintf(L"%llX\n", (unsigned long long)(uintptr_t)&LoadLibraryW); - wprintf(L"%llX\n", (unsigned long long)(uintptr_t)&GetProcAddress); - wprintf(L"%llX\n", (unsigned long long)(uintptr_t)&FreeLibrary); - wprintf(L"%llX\n", (unsigned long long)(uintptr_t)&GetLastError); - wprintf(L"%llX\n", (unsigned long long)(uintptr_t)&OutputDebugStringA); - wprintf(L"%llX\n", (unsigned long long)(uintptr_t)&GetCurrentProcessId); - wprintf(L"%llX\n", (unsigned long long)(uintptr_t)&wsprintfA); - wprintf(L"%llX\n", (unsigned long long)(uintptr_t)&Sleep); - wprintf(L"%llX\n", (unsigned long long)(uintptr_t)&ExitThread); - wprintf(L"%llX\n", (unsigned long long)(uintptr_t)&ReleaseSemaphore); - wprintf(L"%llX\n", (unsigned long long)(uintptr_t)&CloseHandle); - wprintf(L"%llX\n", (unsigned long long)(uintptr_t)&WaitForSingleObject); + printf("%llX\n", (unsigned long long)(uintptr_t)&GetModuleHandleW); + printf("%llX\n", (unsigned long long)(uintptr_t)&LoadLibraryW); + printf("%llX\n", (unsigned long long)(uintptr_t)&GetProcAddress); + printf("%llX\n", (unsigned long long)(uintptr_t)&FreeLibrary); + printf("%llX\n", (unsigned long long)(uintptr_t)&GetLastError); + printf("%llX\n", (unsigned long long)(uintptr_t)&OutputDebugStringA); + printf("%llX\n", (unsigned long long)(uintptr_t)&GetCurrentProcessId); + printf("%llX\n", (unsigned long long)(uintptr_t)&wsprintfA); + printf("%llX\n", (unsigned long long)(uintptr_t)&Sleep); + printf("%llX\n", (unsigned long long)(uintptr_t)&ExitThread); + printf("%llX\n", (unsigned long long)(uintptr_t)&ReleaseSemaphore); + printf("%llX\n", (unsigned long long)(uintptr_t)&CloseHandle); + printf("%llX\n", (unsigned long long)(uintptr_t)&WaitForSingleObject); #endif return 0; }