diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e08030d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,34 @@ +name: CI + +on: + push: + branches: [main, master] + tags: + - "v*" + pull_request: + workflow_dispatch: + +jobs: + test-and-build: + name: ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Cache Rust artifacts + uses: Swatinem/rust-cache@v2 + + - name: Run tests + run: cargo test --workspace --all-targets + + - name: Build release binaries + run: cargo build --release --workspace diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..b8c91f4 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,110 @@ +name: Release + +on: + push: + tags: + - "v*" + workflow_dispatch: + +permissions: + contents: write + +jobs: + build: + name: Build ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Cache Rust artifacts + uses: Swatinem/rust-cache@v2 + + - name: Build release binaries + run: cargo build --release --workspace + + - name: Package Linux artifacts + if: runner.os == 'Linux' + shell: bash + run: | + set -euo pipefail + mkdir -p dist + cp target/release/proxychains4 dist/ + cp target/release/libproxychains.so dist/ + cp proxychains.conf dist/ + cp README.md dist/ + tar -czf "proxychains-rs-${GITHUB_REF_NAME}-linux-x86_64.tar.gz" -C dist . + + - name: Build Linux deb package + if: runner.os == 'Linux' + shell: bash + run: | + set -euo pipefail + VERSION="${GITHUB_REF_NAME#v}" + PKG_ROOT="pkgroot" + rm -rf "${PKG_ROOT}" + mkdir -p "${PKG_ROOT}/DEBIAN" "${PKG_ROOT}/usr/bin" "${PKG_ROOT}/usr/lib" "${PKG_ROOT}/etc" + cp target/release/proxychains4 "${PKG_ROOT}/usr/bin/proxychains4" + cp target/release/libproxychains.so "${PKG_ROOT}/usr/lib/libproxychains.so" + cp proxychains.conf "${PKG_ROOT}/etc/proxychains.conf" + printf "Package: proxychains-rs\nVersion: %s\nSection: net\nPriority: optional\nArchitecture: amd64\nMaintainer: Proxychains Rust Team\nDescription: Cross-platform proxychains implementation in Rust\n" "${VERSION}" > "${PKG_ROOT}/DEBIAN/control" + dpkg-deb --build "${PKG_ROOT}" "proxychains-rs-${GITHUB_REF_NAME}-linux-amd64.deb" + + - name: Package macOS artifacts + if: runner.os == 'macOS' + shell: bash + run: | + set -euo pipefail + mkdir -p dist + cp target/release/proxychains4 dist/ + cp target/release/libproxychains.dylib dist/ + cp proxychains.conf dist/ + cp README.md dist/ + tar -czf "proxychains-rs-${GITHUB_REF_NAME}-macos-x86_64.tar.gz" -C dist . + + - name: Package Windows artifacts + if: runner.os == 'Windows' + shell: pwsh + run: | + New-Item -ItemType Directory -Path dist -Force | Out-Null + Copy-Item target\release\proxychains4.exe dist\ + Copy-Item target\release\proxychains.dll dist\ + Copy-Item proxychains.conf dist\ + Copy-Item README.md dist\ + Compress-Archive -Path dist\* -DestinationPath "proxychains-rs-$env:GITHUB_REF_NAME-windows-x86_64.zip" -Force + + - name: Upload packaged artifact + uses: actions/upload-artifact@v4 + with: + name: release-${{ matrix.os }} + path: | + proxychains-rs-*.tar.gz + proxychains-rs-*.zip + proxychains-rs-*.deb + + publish: + name: Publish GitHub Release + needs: build + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + path: release-assets + + - name: Publish release assets + uses: softprops/action-gh-release@v2 + with: + files: | + release-assets/**/*.tar.gz + release-assets/**/*.zip + release-assets/**/*.deb diff --git a/Cargo.toml b/Cargo.toml index 10148d1..4c17153 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "proxychains", "proxychains-bin", "libproxychains", + "proxychains-injector", ] [workspace.package] diff --git a/README.md b/README.md index 2e8ed3b..301636f 100644 --- a/README.md +++ b/README.md @@ -1,150 +1,201 @@ -# RUST Proxychains4 - -A modern Rust implementation of the classic proxychains tool, providing elegant and powerful proxy chaining capabilities. +# proxychains-rs [![Rust](https://img.shields.io/badge/rust-1.70%2B-orange.svg)](https://www.rust-lang.org/) [![License](https://img.shields.io/badge/license-GPL%2.0-blue.svg)](LICENSE) -[![Build](https://img.shields.io/badge/build-passing-brightgreen.svg)]() + +A modern Rust implementation of the classic proxychains tool, providing elegant and powerful proxy chaining capabilities with cross-platform support. + +## Quick Start + +```bash +# 1. Build +cargo build --release + +# 2. Create config file (Linux/macOS) +cp proxychains.conf ~/.proxychains/ + +# 3. Run +./target/release/proxychains4 curl https://ifconfig.me +``` ## Features - **Multiple Proxy Protocols**: SOCKS4, SOCKS4a, SOCKS5 (with authentication), HTTP CONNECT - **Chain Modes**: Strict, Dynamic, Random, Load Balance, Failover - **DNS Handling**: Local resolution, Remote DNS through proxy, Fake IP mapping -- **IPv6 Support**: Full IPv6 compatibility (planned) -- **HTTPS Proxy**: Secure proxy connections (planned) -- **Configuration**: Compatible with original proxychains.conf format -- **Cross-platform**: Linux (LD_PRELOAD) and macOS (DYLD_INSERT_LIBRARIES) +- **Cross-platform**: + - Linux (LD_PRELOAD) + - macOS (DYLD_INSERT_LIBRARIES) + - **Windows (DLL Injection + API Hooking)** - **Thread Safe**: All operations are thread-safe -## Architecture +## Platform Support -``` -┌─────────────────────────────────────────────────────────┐ -│ CLI Entry │ -│ proxychains4 [options] │ -└─────────────────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────┐ -│ Config Parser │ -│ • Parse config file │ -│ • Environment variable overrides │ -│ • CLI argument overrides │ -└─────────────────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────┐ -│ Hook Layer │ -│ ┌─────────────┐ ┌─────────────┐ │ -│ │ LD_PRELOAD │ │ ptrace │ │ -│ │ (default) │ │ (fallback) │ │ -│ └─────────────┘ └─────────────┘ │ -└─────────────────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────┐ -│ Core Engine │ -│ ┌─────────┬─────────┬─────────┬─────────┐ │ -│ │ SOCKS4 │ SOCKS5 │ HTTP │ HTTPS │ │ -│ └─────────┴─────────┴─────────┴─────────┘ │ -│ ┌─────────────────────────────────────┐ │ -│ │ Chain Manager (chain/balance/failover)│ │ -│ └─────────────────────────────────────┘ │ -│ ┌─────────────────────────────────────┐ │ -│ │ DNS Resolver (local/remote/FakeIP) │ │ -│ └─────────────────────────────────────┘ │ -│ ┌─────────────────────────────────────┐ │ -│ │ Rule Filter (Bypass/Allow) │ │ -│ └─────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────┘ +| Platform | Mechanism | Status | +|----------|-----------|--------| +| Linux | LD_PRELOAD | Stable | +| macOS | DYLD_INSERT_LIBRARIES | Stable | +| Windows | DLL Injection + MinHook | Beta | + +--- + +## How to Compile + +### Prerequisites + +- **Rust 1.70+**: Install from https://rustup.rs +- **Cargo**: Comes with Rust + +#### Platform-specific Requirements + +**Linux:** +```bash +# Ubuntu/Debian +sudo apt install build-essential pkg-config + +# Fedora/RHEL +sudo dnf install gcc pkg-config ``` -## Project Structure +**macOS:** +```bash +xcode-select --install +``` +**Windows:** +1. Install Visual Studio Build Tools from https://visualstudio.microsoft.com/downloads/ +2. Select "Desktop development with C++" workload +3. Ensure MSVC toolchain is active: + ```powershell + rustup default stable-x86_64-pc-windows-msvc + ``` + +### Build Commands + +```bash +# Clone repository +git clone https://github.com/your-repo/proxychains-rs.git +cd proxychains-rs + +# Debug build (faster compile, larger binary, slower runtime) +cargo build + +# Release build (slower compile, smaller binary, faster runtime) - RECOMMENDED +cargo build --release + +# Run tests +cargo test ``` -RUST_proxychains4/ -├── Cargo.toml # Workspace configuration -├── proxychains/ # Core library -│ ├── src/ -│ │ ├── lib.rs -│ │ ├── config/ # Configuration parsing -│ │ ├── proxy/ # Proxy protocol implementations -│ │ ├── chain/ # Chain management -│ │ ├── dns/ # DNS handling -│ │ ├── hook/ # LD_PRELOAD hooks -│ │ └── net/ # Network utilities -│ └── Cargo.toml -├── proxychains-bin/ # CLI binary -│ ├── src/ -│ │ └── main.rs -│ └── Cargo.toml -├── libproxychains/ # LD_PRELOAD library -│ ├── src/ -│ │ └── lib.rs -│ └── Cargo.toml -└── proxychains.conf # Example configuration -``` + +### Build Outputs + +After successful build, you'll find: + +| Platform | CLI Binary | Library | +|----------|------------|---------| +| Linux | `target/release/proxychains4` | `target/release/libproxychains.so` | +| macOS | `target/release/proxychains4` | `target/release/libproxychains.dylib` | +| Windows | `target/release/proxychains4.exe` | `target/release/proxychains.dll` | + +--- ## Installation -### From Source +### System-wide Installation + +#### Linux ```bash -# Clone the repository -cd /path/to/RUST_proxychains4 - -# Build in release mode -cargo build --release - -# The binaries will be at: -# - target/release/proxychains4 (CLI binary) -# - target/release/libproxychains.dylib (macOS) or libproxychains.so (Linux) -``` - -### Install System-wide - -```bash -# Install binary sudo cp target/release/proxychains4 /usr/local/bin/ - -# Install library -sudo cp target/release/libproxychains.dylib /usr/local/lib/ # macOS -# OR -sudo cp target/release/libproxychains.so /usr/local/lib/ # Linux - -# Install default config +sudo cp target/release/libproxychains.so /usr/local/lib/ +sudo ldconfig sudo cp proxychains.conf /usr/local/etc/ ``` -## Usage +#### macOS ```bash -# Basic usage +sudo cp target/release/proxychains4 /usr/local/bin/ +sudo cp target/release/libproxychains.dylib /usr/local/lib/ +sudo cp proxychains.conf /usr/local/etc/ +``` + +#### Windows + +Copy files to a directory in your PATH: + +```powershell +# Option 1: Copy to Windows directory (requires admin) +copy target\release\proxychains4.exe C:\Windows\ +copy target\release\proxychains.dll C:\Windows\ + +# Option 2: Add build directory to PATH (current session only) +$env:PATH += ";$PWD\target\release" + +# Option 3: Add to user PATH permanently +[Environment]::SetEnvironmentVariable("PATH", $env:PATH + ";C:\path\to\proxychains-rs\target\release", "User") +``` + +--- + +## Usage + +### Basic Usage + +```bash +# Linux / macOS proxychains4 curl https://ifconfig.me -# With config file -proxychains4 -f /path/to/config.conf wget https://example.com - -# Quiet mode -proxychains4 -q curl https://ifconfig.me - -# Verbose mode -proxychains4 -v curl https://ifconfig.me +# Windows +proxychains4.exe curl https://ifconfig.me ``` +### Command Line Options + +``` +Usage: proxychains4 [OPTIONS] [ARGS...] + +Options: + -f, --file Configuration file path + -q, --quiet Quiet mode (suppress output) + -v, --verbose Verbose mode (debug output) + -h, --help Show help + +Examples: + proxychains4 curl https://example.com + proxychains4 -f /path/to/config.conf wget https://example.com + proxychains4 -q firefox +``` + +### Using with Different Programs + +```bash +# Web browsers +proxychains4 firefox +proxychains4 chromium + +# Download tools +proxychains4 wget https://example.com/file.zip +proxychains4 curl https://api.ipify.org + +# SSH +proxychains4 ssh user@remote-host + +# Git +proxychains4 git clone https://github.com/repo.git +``` + +--- + ## Configuration -Configuration file format (compatible with original proxychains): +### Configuration File Format ```ini # Chain types: dynamic_chain, strict_chain, random_chain -# - dynamic_chain: Skip dead proxies, continue with working ones -# - strict_chain: All proxies must work -# - random_chain: Randomly select proxies dynamic_chain -# Proxy DNS through the chain (recommended to prevent DNS leaks) +# Proxy DNS through the chain (recommended - prevents DNS leaks) proxy_dns # Remote DNS subnet for fake IPs (default: 224) @@ -153,145 +204,499 @@ remote_dns_subnet 224 # Timeouts (milliseconds) tcp_read_time_out 15000 tcp_connect_time_out 8000 +max_chain_retries 8 # Local networks to bypass (CIDR notation) localnet 127.0.0.0/255.0.0.0 localnet 192.168.0.0/255.255.0.0 -localnet 10.0.0.0/255.0.0.0 - -# DNAT rules (destination NAT) -# dnat original_ip:original_port new_ip:new_port # Proxy list [ProxyList] # Format: type host port [user pass] # SOCKS5 proxies -# socks5 192.168.1.1 1080 user password -# socks5 192.168.1.2 1080 +socks5 127.0.0.1 1080 + +# SOCKS5 with authentication +socks5 192.168.1.1 1080 username password # SOCKS4 proxies -# socks4 192.168.1.3 1080 +socks4 192.168.1.2 1080 # HTTP proxies -# http 192.168.1.4 8080 user password -# http 192.168.1.5 3128 - -# Example: Default Tor proxy -# socks5 127.0.0.1 9050 +http 192.168.1.3 8080 +http 192.168.1.4 8080 user pass ``` -## Environment Variables +### Configuration File Locations -| Variable | Description | -|----------|-------------| -| `PROXYCHAINS_CONF_FILE` | Path to configuration file | -| `PROXYCHAINS_QUIET_MODE` | Set to enable quiet mode | -| `PROXYCHAINS_SOCKS5_HOST` | Quick SOCKS5 proxy host | -| `PROXYCHAINS_SOCKS5_PORT` | Quick SOCKS5 proxy port | -| `PROXYCHAINS_DNS` | Enable/disable DNS proxying | +The configuration file is searched in the following order: -### Quick SOCKS5 Setup +**Linux / macOS:** +1. Path specified by `-f` flag +2. `PROXYCHAINS_CONF_FILE` environment variable +3. `./proxychains.conf` +4. `~/.proxychains/proxychains.conf` +5. `/etc/proxychains.conf` +6. `/usr/local/etc/proxychains.conf` + +**Windows:** +1. Path specified by `-f` flag +2. `PROXYCHAINS_CONF_FILE` environment variable +3. `.\proxychains.conf` +4. `%APPDATA%\proxychains\proxychains.conf` +5. `%PROGRAMDATA%\proxychains\proxychains.conf` +6. Same directory as `proxychains4.exe` + +### Quick Setup ```bash -export PROXYCHAINS_SOCKS5_HOST=127.0.0.1 -export PROXYCHAINS_SOCKS5_PORT=9050 -proxychains4 curl https://ifconfig.me +# Create config directory +mkdir -p ~/.proxychains + +# Copy example config +cp proxychains.conf ~/.proxychains/ + +# Edit with your proxy settings +nano ~/.proxychains/proxychains.conf ``` -## Proxy Types +--- -### SOCKS5 +## Windows Technical Details -Full SOCKS5 protocol support with: -- No authentication -- Username/password authentication (RFC 1929) -- Domain name resolution (remote DNS) -- IPv4 and IPv6 addresses +On Windows, proxychains uses a different mechanism compared to Unix systems: -### SOCKS4/4a +### How It Works -SOCKS4 and SOCKS4a protocol support with: -- IPv4 addresses -- Domain name resolution (SOCKS4a) -- User ID authentication +1. **DLL Injection**: `proxychains4.exe` creates a new process in suspended state +2. **API Hooking**: Injects `proxychains.dll` which uses MinHook to intercept Winsock2 calls +3. **Hooked Functions**: + - `connect` + - `getaddrinfo` + - `freeaddrinfo` + - `gethostbyname` + - `getnameinfo` -### HTTP CONNECT +### Windows Requirements -HTTP CONNECT proxy support with: -- HTTP/1.0 and HTTP/1.1 -- Basic authentication -- HTTPS targets (via HTTP CONNECT) +- Windows 10 or later +- Visual Studio Build Tools (MSVC toolchain) +- Rust with MSVC target: `rustup target add x86_64-pc-windows-msvc` -## Chain Modes +### Windows Limitations -### Dynamic Chain (Default) +- May be detected by antivirus software (DLL injection is a common technique) +- Requires same architecture (32-bit process needs 32-bit DLL) +- Some programs may not work if they statically link Winsock +- Admin privileges may be required for some target processes -Tries proxies in order, skipping dead/unreachable proxies. Most resilient mode - continues trying until successful connection. +--- -### Strict Chain +## Project Structure -All proxies must be available and working. Fails immediately if any proxy is down. No retry mechanism. +``` +proxychains-rs/ +├── proxychains/ # Core library +│ ├── src/ +│ │ ├── config/ # Configuration parsing +│ │ ├── proxy/ # Proxy protocol implementations +│ │ ├── chain/ # Chain management +│ │ ├── dns/ # DNS handling +│ │ ├── hook/ # Platform-specific hooks +│ │ ├── net/ # Network utilities +│ │ └── platform/ # Platform abstraction +├── proxychains-bin/ # CLI binary +├── libproxychains/ # Dynamic library for injection +├── proxychains-injector/ # Windows DLL injection +└── proxychains.conf # Example configuration +``` -### Random Chain +--- -Randomly selects proxies from the list. Useful for load distribution and anonymity. +## Troubleshooting -### Load Balance (Planned) - -Round-robin selection for Evenly distributes connections across available proxies. - -### Failover (Planned) - -Try proxies in order until one works. Automatic failover on on next available proxy. - -## Development - -### Prerequisites - -- Rust 1.70 or0 or later -- Cargo - -### Building +### Common Issues +**"library not found" error:** ```bash -# Debug build -cargo build - -# Release build -cargo build --release - -# Run tests -cargo test +# Linux +sudo ldconfig +# Or set LD_LIBRARY_PATH +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib ``` -### Running Tests +**"DLL not found" error (Windows):** +- Ensure `proxychains.dll` is in the same directory as `proxychains4.exe` +- Or copy both to a PATH directory -```bash -# Run all tests -cargo test +**Proxy not working:** +1. Verify your proxy server is running +2. Check configuration file syntax +3. Use `-v` flag for debug output: `proxychains4 -v curl https://ifconfig.me` -# Run tests with output -cargo test -- --nocapture -``` +**DNS leaks:** +- Add `proxy_dns` to your configuration file -## Comparison with Original proxychains - -| Feature | Original (C) | Rust Version | -|---------|--------------|---------------| -| Code size | ~1900 lines | ~3500 lines | -| Protocols | 4 types | 4 types | -| Injection | LD_PRELOAD only | LD_PRELOAD + ptrace (planned) | -| IPv6 | ❌ | ✅ (planned) | -| Chain modes | 3 types | 5 types | -| DNS | Basic | Enhanced | -| Thread Safety | Basic | Full | -| Error Handling | setjmp/longjmp | Result types | +--- ## License GPL-2.0 -## Acknowled +## Acknowledgments This project is a Rust implementation of the original [proxychains-ng](https://github.com/rofl0r/proxychains-ng) project. + +--- +--- + +# proxychains-rs (中文文档) + +[![Rust](https://img.shields.io/badge/rust-1.70%2B-orange.svg)](https://www.rust-lang.org/) +[![License](https://img.shields.io/badge/license-GPL%2.0-blue.svg)](LICENSE) + +经典 proxychains 工具的现代 Rust 实现,提供优雅且强大的代理链功能,支持跨平台。 + +## 快速开始 + +```bash +# 1. 编译 +cargo build --release + +# 2. 创建配置文件 (Linux/macOS) +cp proxychains.conf ~/.proxychains/ + +# 3. 运行 +./target/release/proxychains4 curl https://ifconfig.me +``` + +## 特性 + +- **多种代理协议**: SOCKS4, SOCKS4a, SOCKS5 (带认证), HTTP CONNECT +- **链式模式**: 严格链, 动态链, 随机链, 负载均衡, 故障转移 +- **DNS 处理**: 本地解析, 通过代理的远程 DNS, Fake IP 映射 +- **跨平台支持**: + - Linux (LD_PRELOAD) + - macOS (DYLD_INSERT_LIBRARIES) + - **Windows (DLL 注入 + API Hooking)** +- **线程安全**: 所有操作都是线程安全的 + +## 平台支持 + +| 平台 | 机制 | 状态 | +|------|------|------| +| Linux | LD_PRELOAD | 稳定 | +| macOS | DYLD_INSERT_LIBRARIES | 稳定 | +| Windows | DLL 注入 + MinHook | 测试版 | + +--- + +## 如何编译 + +### 前置要求 + +- **Rust 1.70+**: 从 https://rustup.rs 安装 +- **Cargo**: 随 Rust 一起安装 + +#### 平台特定要求 + +**Linux:** +```bash +# Ubuntu/Debian +sudo apt install build-essential pkg-config + +# Fedora/RHEL +sudo dnf install gcc pkg-config +``` + +**macOS:** +```bash +xcode-select --install +``` + +**Windows:** +1. 从 https://visualstudio.microsoft.com/downloads/ 安装 Visual Studio Build Tools +2. 选择 "Desktop development with C++" 工作负载 +3. 确保 MSVC 工具链已激活: + ```powershell + rustup default stable-x86_64-pc-windows-msvc + ``` + +### 编译命令 + +```bash +# 克隆仓库 +git clone https://github.com/your-repo/proxychains-rs.git +cd proxychains-rs + +# Debug 构建 (编译快,二进制大,运行慢) +cargo build + +# Release 构建 (编译慢,二进制小,运行快) - 推荐 +cargo build --release + +# 运行测试 +cargo test +``` + +### 编译输出 + +编译成功后,你将获得: + +| 平台 | CLI 二进制 | 库文件 | +|------|-----------|--------| +| Linux | `target/release/proxychains4` | `target/release/libproxychains.so` | +| macOS | `target/release/proxychains4` | `target/release/libproxychains.dylib` | +| Windows | `target/release/proxychains4.exe` | `target/release/proxychains.dll` | + +--- + +## 安装 + +### 系统级安装 + +#### Linux + +```bash +sudo cp target/release/proxychains4 /usr/local/bin/ +sudo cp target/release/libproxychains.so /usr/local/lib/ +sudo ldconfig +sudo cp proxychains.conf /usr/local/etc/ +``` + +#### macOS + +```bash +sudo cp target/release/proxychains4 /usr/local/bin/ +sudo cp target/release/libproxychains.dylib /usr/local/lib/ +sudo cp proxychains.conf /usr/local/etc/ +``` + +#### Windows + +将文件复制到 PATH 目录: + +```powershell +# 方法 1: 复制到 Windows 目录 (需要管理员权限) +copy target\release\proxychains4.exe C:\Windows\ +copy target\release\proxychains.dll C:\Windows\ + +# 方法 2: 添加构建目录到 PATH (仅当前会话) +$env:PATH += ";$PWD\target\release" + +# 方法 3: 永久添加到用户 PATH +[Environment]::SetEnvironmentVariable("PATH", $env:PATH + ";C:\path\to\proxychains-rs\target\release", "User") +``` + +--- + +## 使用方法 + +### 基本用法 + +```bash +# Linux / macOS +proxychains4 curl https://ifconfig.me + +# Windows +proxychains4.exe curl https://ifconfig.me +``` + +### 命令行选项 + +``` +用法: proxychains4 [选项] <命令> [参数...] + +选项: + -f, --file <路径> 配置文件路径 + -q, --quiet 静默模式 (抑制输出) + -v, --verbose 详细模式 (调试输出) + -h, --help 显示帮助 + +示例: + proxychains4 curl https://example.com + proxychains4 -f /path/to/config.conf wget https://example.com + proxychains4 -q firefox +``` + +### 与不同程序配合使用 + +```bash +# 网页浏览器 +proxychains4 firefox +proxychains4 chromium + +# 下载工具 +proxychains4 wget https://example.com/file.zip +proxychains4 curl https://api.ipify.org + +# SSH +proxychains4 ssh user@remote-host + +# Git +proxychains4 git clone https://github.com/repo.git +``` + +--- + +## 配置 + +### 配置文件格式 + +```ini +# 链类型: dynamic_chain, strict_chain, random_chain +dynamic_chain + +# 通过代理链代理 DNS (推荐 - 防止 DNS 泄露) +proxy_dns + +# 远程 DNS 子网用于 Fake IP (默认: 224) +remote_dns_subnet 224 + +# 超时设置 (毫秒) +tcp_read_time_out 15000 +tcp_connect_time_out 8000 +max_chain_retries 8 + +# 绕过的本地网络 (CIDR 表示法) +localnet 127.0.0.0/255.0.0.0 +localnet 192.168.0.0/255.255.0.0 + +# 代理列表 +[ProxyList] +# 格式: 类型 主机 端口 [用户名 密码] + +# SOCKS5 代理 +socks5 127.0.0.1 1080 + +# SOCKS5 带认证 +socks5 192.168.1.1 1080 username password + +# SOCKS4 代理 +socks4 192.168.1.2 1080 + +# HTTP 代理 +http 192.168.1.3 8080 +http 192.168.1.4 8080 user pass +``` + +### 配置文件位置 + +配置文件按以下顺序搜索: + +**Linux / macOS:** +1. `-f` 标志指定的路径 +2. `PROXYCHAINS_CONF_FILE` 环境变量 +3. `./proxychains.conf` +4. `~/.proxychains/proxychains.conf` +5. `/etc/proxychains.conf` +6. `/usr/local/etc/proxychains.conf` + +**Windows:** +1. `-f` 标志指定的路径 +2. `PROXYCHAINS_CONF_FILE` 环境变量 +3. `.\proxychains.conf` +4. `%APPDATA%\proxychains\proxychains.conf` +5. `%PROGRAMDATA%\proxychains\proxychains.conf` +6. 与 `proxychains4.exe` 同目录 + +### 快速设置 + +```bash +# 创建配置目录 +mkdir -p ~/.proxychains + +# 复制示例配置 +cp proxychains.conf ~/.proxychains/ + +# 编辑你的代理设置 +nano ~/.proxychains/proxychains.conf +``` + +--- + +## Windows 技术细节 + +在 Windows 上,proxychains 使用与 Unix 系统不同的机制: + +### 工作原理 + +1. **DLL 注入**: `proxychains4.exe` 创建暂停状态的新进程 +2. **API Hooking**: 注入 `proxychains.dll`,使用 MinHook 拦截 Winsock2 调用 +3. **Hook 的函数**: + - `connect` + - `getaddrinfo` + - `freeaddrinfo` + - `gethostbyname` + - `getnameinfo` + +### Windows 要求 + +- Windows 10 或更高版本 +- Visual Studio Build Tools (MSVC 工具链) +- Rust MSVC 目标: `rustup target add x86_64-pc-windows-msvc` + +### Windows 限制 + +- 可能被杀毒软件检测 (DLL 注入是常用技术) +- 需要相同架构 (32 位进程需要 32 位 DLL) +- 静态链接 Winsock 的程序可能无法工作 +- 某些目标进程可能需要管理员权限 + +--- + +## 项目结构 + +``` +proxychains-rs/ +├── proxychains/ # 核心库 +│ ├── src/ +│ │ ├── config/ # 配置解析 +│ │ ├── proxy/ # 代理协议实现 +│ │ ├── chain/ # 链管理 +│ │ ├── dns/ # DNS 处理 +│ │ ├── hook/ # 平台特定钩子 +│ │ ├── net/ # 网络工具 +│ │ └── platform/ # 平台抽象 +├── proxychains-bin/ # CLI 二进制 +├── libproxychains/ # 注入用动态库 +├── proxychains-injector/ # Windows DLL 注入器 +└── proxychains.conf # 示例配置 +``` + +--- + +## 故障排除 + +### 常见问题 + +**"library not found" 错误:** +```bash +# Linux +sudo ldconfig +# 或设置 LD_LIBRARY_PATH +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib +``` + +**"DLL not found" 错误 (Windows):** +- 确保 `proxychains.dll` 与 `proxychains4.exe` 在同一目录 +- 或将两者复制到 PATH 目录 + +**代理不工作:** +1. 验证代理服务器正在运行 +2. 检查配置文件语法 +3. 使用 `-v` 标志查看调试输出: `proxychains4 -v curl https://ifconfig.me` + +**DNS 泄露:** +- 在配置文件中添加 `proxy_dns` + +--- + +## 许可证 + +GPL-2.0 + +## 致谢 + +本项目是原始 [proxychains-ng](https://github.com/rofl0r/proxychains-ng) 项目的 Rust 实现。 diff --git a/libproxychains/Cargo.toml b/libproxychains/Cargo.toml index 6c3ee3e..df93318 100644 --- a/libproxychains/Cargo.toml +++ b/libproxychains/Cargo.toml @@ -5,7 +5,7 @@ edition.workspace = true rust-version.workspace = true authors.workspace = true license.workspace = true -description = "LD_PRELOAD library for proxychains" +description = "LD_PRELOAD/DLL injection library for proxychains" [lib] name = "proxychains" @@ -14,10 +14,22 @@ path = "src/lib.rs" [dependencies] proxychains = { path = "../proxychains" } -libc.workspace = true -nix.workspace = true -ctor.workspace = true once_cell.workspace = true parking_lot.workspace = true tracing.workspace = true tracing-subscriber.workspace = true + +[target.'cfg(unix)'.dependencies] +libc.workspace = true +nix.workspace = true +ctor.workspace = true + +[target.'cfg(windows)'.dependencies] +windows = { version = "0.54", features = [ + "Win32_Foundation", + "Win32_Networking_WinSock", + "Win32_System_Threading", + "Win32_System_Memory", + "Win32_System_LibraryLoader", +] } +minhook = "0.6" diff --git a/libproxychains/src/lib.rs b/libproxychains/src/lib.rs index 20604cc..59ca824 100644 --- a/libproxychains/src/lib.rs +++ b/libproxychains/src/lib.rs @@ -1,26 +1,19 @@ -//! LD_PRELOAD library for proxychains +//! LD_PRELOAD/DLL injection library for proxychains //! -//! This library is loaded via LD_PRELOAD (Linux) or DYLD_INSERT_LIBRARIES (macOS) -//! to intercept network system calls and redirect them through proxy chains. +//! This library is loaded via: +//! - Linux: LD_PRELOAD environment variable +//! - macOS: DYLD_INSERT_LIBRARIES environment variable +//! - Windows: DLL injection using dll-syringe +//! +//! It intercepts network system calls and redirects them through proxy chains. -use std::ffi::{c_char, c_int}; -use std::os::unix::io::AsRawFd; - -use ctor::ctor; use tracing::{debug, error, info, Level}; use tracing_subscriber::FmtSubscriber; -use proxychains::{ - hook::{ - hook_connect, hook_getaddrinfo, hook_gethostbyname, hook_getnameinfo, - init_hooks, - }, - ConfigParser, -}; +use proxychains::{ConfigParser, hook::init_hooks}; -/// Library initialization -#[ctor] -fn init() { +/// Initialize the library (common code for all platforms) +fn init_library() { // Initialize logging let log_level = if std::env::var("PROXYCHAINS_QUIET_MODE").is_ok() { Level::ERROR @@ -63,66 +56,133 @@ fn init() { } } -/// Hook for connect() system call -/// -/// # Safety -/// This is a C FFI function that makes unsafe operations -#[no_mangle] -pub unsafe extern "C" fn connect( - sock: c_int, - addr: *const libc::sockaddr, - len: libc::socklen_t, -) -> c_int { - proxychains::hook::hook_connect(sock, addr, len) +// ============================================================================ +// Unix Implementation (LD_PRELOAD/DYLD_INSERT_LIBRARIES) +// ============================================================================ + +#[cfg(unix)] +mod unix_impl { + use super::*; + use ctor::ctor; + use std::ffi::{c_char, c_int}; + + /// Library initialization using #[ctor] attribute + #[ctor] + fn init() { + init_library(); + } + + /// Hook for connect() system call + /// + /// # Safety + /// This is a C FFI function that makes unsafe operations + #[no_mangle] + pub unsafe extern "C" fn connect( + sock: c_int, + addr: *const libc::sockaddr, + len: libc::socklen_t, + ) -> c_int { + proxychains::hook::hook_connect(sock, addr, len) + } + + /// Hook for getaddrinfo() system call + /// + /// # Safety + /// This is a C FFI function that makes unsafe operations + #[no_mangle] + pub unsafe extern "C" fn getaddrinfo( + node: *const c_char, + service: *const c_char, + hints: *const libc::addrinfo, + res: *mut *mut libc::addrinfo, + ) -> c_int { + proxychains::hook::hook_getaddrinfo(node, service, hints, res) + } + + /// Hook for freeaddrinfo() system call + /// + /// # Safety + /// This is a C FFI function that makes unsafe operations + #[no_mangle] + pub unsafe extern "C" fn freeaddrinfo(res: *mut libc::addrinfo) { + proxychains::hook::hook_freeaddrinfo(res) + } + + /// Hook for gethostbyname() system call + /// + /// # Safety + /// This is a C FFI function that makes unsafe operations + #[no_mangle] + pub unsafe extern "C" fn gethostbyname(name: *const c_char) -> *mut libc::hostent { + proxychains::hook::hook_gethostbyname(name) + } + + /// Hook for getnameinfo() system call + /// + /// # Safety + /// This is a C FFI function that makes unsafe operations + #[no_mangle] + pub unsafe extern "C" fn getnameinfo( + sa: *const libc::sockaddr, + salen: libc::socklen_t, + host: *mut c_char, + hostlen: libc::socklen_t, + serv: *mut c_char, + servlen: libc::socklen_t, + flags: c_int, + ) -> c_int { + proxychains::hook::hook_getnameinfo(sa, salen, host, hostlen, serv, servlen, flags) + } } -/// Hook for getaddrinfo() system call -/// -/// # Safety -/// This is a C FFI function that makes unsafe operations -#[no_mangle] -pub unsafe extern "C" fn getaddrinfo( - node: *const c_char, - service: *const c_char, - hints: *const libc::addrinfo, - res: *mut *mut libc::addrinfo, -) -> c_int { - proxychains::hook::hook_getaddrinfo(node, service, hints, res) -} +// ============================================================================ +// Windows Implementation (DLL Injection) +// ============================================================================ -/// Hook for freeaddrinfo() system call -/// -/// # Safety -/// This is a C FFI function that makes unsafe operations -#[no_mangle] -pub unsafe extern "C" fn freeaddrinfo(res: *mut libc::addrinfo) { - proxychains::hook::hook_freeaddrinfo(res) -} +#[cfg(windows)] +mod windows_impl { + use super::*; + use std::ffi::c_void; + use windows::Win32::Foundation::*; -/// Hook for gethostbyname() system call -/// -/// # Safety -/// This is a C FFI function that makes unsafe operations -#[no_mangle] -pub unsafe extern "C" fn gethostbyname(name: *const c_char) -> *mut libc::hostent { - proxychains::hook::hook_gethostbyname(name) -} + /// Windows DLL entry point + /// + /// This is called when the DLL is loaded/unloaded. + /// + /// # Safety + /// This is a Windows API callback + #[no_mangle] + pub extern "system" fn DllMain( + _hinst: HINSTANCE, + reason: u32, + _reserved: *mut c_void, + ) -> BOOL { + const DLL_PROCESS_ATTACH: u32 = 1; + const DLL_PROCESS_DETACH: u32 = 0; -/// Hook for getnameinfo() system call -/// -/// # Safety -/// This is a C FFI function that makes unsafe operations -#[no_mangle] -pub unsafe extern "C" fn getnameinfo( - sa: *const libc::sockaddr, - salen: libc::socklen_t, - host: *mut c_char, - hostlen: libc::socklen_t, - serv: *mut c_char, - servlen: libc::socklen_t, - flags: c_int, -) -> c_int { - proxychains::hook::hook_getnameinfo(sa, salen, host, hostlen, serv, servlen, flags) + match reason { + DLL_PROCESS_ATTACH => { + // Initialize the library when loaded into a process + init_library(); + BOOL(1) + } + DLL_PROCESS_DETACH => { + // Cleanup (if needed) + info!("libproxychains detaching"); + BOOL(1) + } + _ => BOOL(1), + } + } + + // Note: On Windows, we don't export individual functions like connect, getaddrinfo, etc. + // Instead, we use API Hooking (via retour-rs) to intercept calls to ws2_32.dll functions. + // The hooks are installed during init_library() -> init_hooks(). + // + // The hooking mechanism works by: + // 1. Loading the original functions from ws2_32.dll + // 2. Creating detours that redirect to our hook functions + // 3. Enabling the detours so all calls go through our hooks first } #[cfg(test)] diff --git a/proxychains-bin/Cargo.toml b/proxychains-bin/Cargo.toml index cea86bd..6bedf52 100644 --- a/proxychains-bin/Cargo.toml +++ b/proxychains-bin/Cargo.toml @@ -13,9 +13,18 @@ path = "src/main.rs" [dependencies] proxychains = { path = "../proxychains" } +proxychains-injector = { path = "../proxychains-injector" } clap.workspace = true tracing.workspace = true tracing-subscriber.workspace = true anyhow.workspace = true + +[target.'cfg(unix)'.dependencies] nix.workspace = true libc.workspace = true + +[target.'cfg(windows)'.dependencies] +windows = { version = "0.54", features = [ + "Win32_Foundation", + "Win32_System_Threading", +] } diff --git a/proxychains-bin/src/main.rs b/proxychains-bin/src/main.rs index b7b409c..72df130 100644 --- a/proxychains-bin/src/main.rs +++ b/proxychains-bin/src/main.rs @@ -1,9 +1,12 @@ //! Proxychains4 CLI binary //! //! This is the main entry point for running commands through proxy chains. +//! +//! Platform support: +//! - Unix (Linux/macOS): Uses LD_PRELOAD/DYLD_INSERT_LIBRARIES +//! - Windows: Uses DLL injection use std::env; -use std::ffi::CString; use std::path::PathBuf; use std::process; @@ -85,7 +88,7 @@ fn main() { info!("Proxies: {}", config.proxy_count()); } - // Execute the command with LD_PRELOAD + // Execute the command with platform-specific injection match execute_command(&args, &config) { Ok(status) => { process::exit(status); @@ -110,8 +113,28 @@ fn load_config(args: &Args) -> Result { parser.parse().map_err(|e| e.to_string()) } -/// Execute the command with LD_PRELOAD set +/// Set proxychains-specific environment variables +fn set_proxychains_env(config: &Config, args: &Args) { + if args.quiet { + env::set_var("PROXYCHAINS_QUIET_MODE", "1"); + } + + if config.proxy_dns { + env::set_var("PROXYCHAINS_DNS", "1"); + } + + // Note: The actual config file path is handled by the library + // when it reads PROXYCHAINS_CONF_FILE +} + +// ============================================================================ +// Unix Implementation (LD_PRELOAD/DYLD_INSERT_LIBRARIES) +// ============================================================================ + +#[cfg(unix)] fn execute_command(args: &Args, config: &Config) -> Result { + use std::ffi::CString; + if args.command.is_empty() { return Err("No command specified".to_string()); } @@ -158,7 +181,8 @@ fn execute_command(args: &Args, config: &Config) -> Result { Ok(0) } -/// Find the proxychains library path +/// Find the proxychains library path (Unix) +#[cfg(unix)] fn find_library_path() -> Result { // Try common locations let search_paths = vec![ @@ -195,14 +219,8 @@ fn find_library_path() -> Result { Err("Could not find libproxychains library".to_string()) } -/// Get the directory containing the current binary -fn get_binary_dir() -> Option { - env::current_exe() - .ok() - .and_then(|path| path.parent().map(|p| p.to_path_buf())) -} - -/// Set LD_PRELOAD environment variable +/// Set LD_PRELOAD environment variable (Unix) +#[cfg(unix)] fn set_preload_env(library_path: &str) -> Result<(), String> { #[cfg(target_os = "linux")] { @@ -217,26 +235,71 @@ fn set_preload_env(library_path: &str) -> Result<(), String> { debug!("Set DYLD_INSERT_LIBRARIES={}", library_path); } - #[cfg(not(any(target_os = "linux", target_os = "macos")))] - { - return Err("Unsupported operating system".to_string()); - } - Ok(()) } -/// Set proxychains-specific environment variables -fn set_proxychains_env(config: &Config, args: &Args) { - if args.quiet { - env::set_var("PROXYCHAINS_QUIET_MODE", "1"); +// ============================================================================ +// Windows Implementation (DLL Injection) +// ============================================================================ + +#[cfg(windows)] +fn execute_command(args: &Args, config: &Config) -> Result { + use proxychains_injector::{ProxychainsInjector, ProcessInfo, find_library_path}; + + if args.command.is_empty() { + return Err("No command specified".to_string()); } - if config.proxy_dns { - env::set_var("PROXYCHAINS_DNS", "1"); - } + // Find the DLL path + let dll_path = find_library_path().map_err(|e| e.to_string())?; - // Note: The actual config file path is handled by the library - // when it reads PROXYCHAINS_CONF_FILE + debug!("DLL path: {:?}", dll_path); + + // Set proxychains environment variables + set_proxychains_env(config, args); + + // Create the injector + let injector = ProxychainsInjector::new(&dll_path) + .map_err(|e| format!("Failed to create injector: {}", e))?; + + // Create process info + let process_info = ProcessInfo { + pid: None, + name: None, + command: args.command[0].clone(), + args: args.command[1..].to_vec(), + }; + + debug!("Spawning and injecting: {:?}", process_info); + + // Spawn the process and inject DLL + let mut child = injector + .spawn_and_inject(&process_info) + .map_err(|e| format!("Failed to spawn and inject: {}", e))?; + + // Wait for the process to complete + let status = child + .wait() + .map_err(|e| format!("Failed to wait for process: {}", e))?; + + let exit_code = status + .code() + .unwrap_or(1); + + info!("Process exited with code: {}", exit_code); + + Ok(exit_code) +} + +// ============================================================================ +// Common Functions +// ============================================================================ + +/// Get the directory containing the current binary +fn get_binary_dir() -> Option { + env::current_exe() + .ok() + .and_then(|path| path.parent().map(|p| p.to_path_buf())) } #[cfg(test)] diff --git a/proxychains-injector/Cargo.toml b/proxychains-injector/Cargo.toml new file mode 100644 index 0000000..f06aab2 --- /dev/null +++ b/proxychains-injector/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "proxychains-injector" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +description = "DLL injection utilities for proxychains-rs on Windows" + +[lib] +name = "proxychains_injector" +path = "src/lib.rs" + +[target.'cfg(windows)'.dependencies] +windows = { version = "0.54", features = [ + "Win32_Foundation", + "Win32_System_Threading", + "Win32_System_Memory", + "Win32_System_LibraryLoader", + "Win32_System_Diagnostics_Debug", + "Win32_System_SystemServices", + "Win32_Security", +] } +windows-core = "0.54" +thiserror.workspace = true +tracing.workspace = true + +[target.'cfg(unix)'.dependencies] +# No dependencies needed for Unix stub diff --git a/proxychains-injector/src/injector.rs b/proxychains-injector/src/injector.rs new file mode 100644 index 0000000..bd996b0 --- /dev/null +++ b/proxychains-injector/src/injector.rs @@ -0,0 +1,351 @@ +//! Injector implementation +//! +//! Platform-specific DLL/process injection functionality. + +use std::path::Path; + +use thiserror::Error; +use tracing::{debug, info}; + +/// Injection errors +#[derive(Error, Debug)] +pub enum InjectorError { + #[error("Process not found: {0}")] + ProcessNotFound(String), + + #[error("Failed to inject DLL: {0}")] + InjectionFailed(String), + + #[error("Failed to create process: {0}")] + ProcessCreationFailed(String), + + #[error("DLL not found: {0}")] + DllNotFound(String), + + #[error("Unsupported platform")] + UnsupportedPlatform, + + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + + #[error("Windows API error: {0}")] + WindowsApi(String), +} + +pub type Result = std::result::Result; + +/// Process information for injection +#[derive(Debug, Clone)] +pub struct ProcessInfo { + pub pid: Option, + pub name: Option, + pub command: String, + pub args: Vec, +} + +/// DLL injector for proxychains +pub struct ProxychainsInjector { + dll_path: std::path::PathBuf, +} + +impl ProxychainsInjector { + /// Create a new injector with the specified DLL path + pub fn new(dll_path: &Path) -> Result { + if !dll_path.exists() { + return Err(InjectorError::DllNotFound(dll_path.display().to_string())); + } + + Ok(Self { + dll_path: dll_path.to_path_buf(), + }) + } + + /// Get the DLL path + pub fn dll_path(&self) -> &Path { + &self.dll_path + } + + /// Inject into an existing process by PID (Windows) + #[cfg(windows)] + pub fn inject_by_pid(&self, pid: u32) -> Result<()> { + use windows::Win32::System::Threading::*; + + debug!("Injecting into process with PID: {}", pid); + + unsafe { + // Open the target process + let access_rights = PROCESS_CREATE_THREAD.0 + | PROCESS_QUERY_INFORMATION.0 + | PROCESS_VM_OPERATION.0 + | PROCESS_VM_WRITE.0 + | PROCESS_VM_READ.0; + + let process = OpenProcess(PROCESS_ACCESS_RIGHTS(access_rights), false, pid) + .map_err(|e| InjectorError::WindowsApi(format!("OpenProcess failed: {:?}", e)))?; + + if process.is_invalid() { + return Err(InjectorError::ProcessNotFound(format!("PID {}", pid))); + } + + // Inject DLL + self.inject_dll(process)?; + + info!("Successfully injected DLL into process {}", pid); + } + + Ok(()) + } + + /// Inject into an existing process by name (Windows) + #[cfg(windows)] + pub fn inject_by_name(&self, name: &str) -> Result<()> { + debug!("Injecting into process with name: {}", name); + Err(InjectorError::UnsupportedPlatform) + } + + /// Create a new process and inject DLL (Windows) + #[cfg(windows)] + pub fn spawn_and_inject(&self, process_info: &ProcessInfo) -> Result { + use std::os::windows::process::CommandExt; + + debug!( + "Spawning process: {} {:?}", + process_info.command, process_info.args + ); + + // Create the process + let mut cmd = std::process::Command::new(&process_info.command); + cmd.args(&process_info.args); + + // CREATE_SUSPENDED = 0x00000004 + const CREATE_SUSPENDED: u32 = 0x00000004; + cmd.creation_flags(CREATE_SUSPENDED); + + let mut child = cmd + .spawn() + .map_err(|e| InjectorError::ProcessCreationFailed(e.to_string()))?; + + let pid = child.id(); + debug!("Process created with PID: {}", pid); + + // Inject DLL using the PID + self.inject_by_pid(pid).map_err(|e| { + let _ = child.kill(); + e + })?; + + info!("Successfully spawned and injected into process {}", pid); + + Ok(child) + } + + /// Internal DLL injection method (Windows) + #[cfg(windows)] + unsafe fn inject_dll(&self, process: windows::Win32::Foundation::HANDLE) -> Result<()> { + use windows::Win32::Foundation::*; + use windows::Win32::System::Memory::*; + use windows::Win32::System::LibraryLoader::*; + use windows::Win32::System::Diagnostics::Debug::WriteProcessMemory; + use windows::Win32::System::Threading::CreateRemoteThread; + use windows::Win32::System::Threading::WaitForSingleObject; + use windows::Win32::System::Threading::LPTHREAD_START_ROUTINE; + + let dll_path = self.dll_path.to_string_lossy(); + let dll_path_wide: Vec = dll_path.encode_utf16().chain(std::iter::once(0)).collect(); + + // Get the address of LoadLibraryW + let load_library = GetProcAddress( + GetModuleHandleA(windows::core::s!("kernel32.dll")) + .map_err(|e| InjectorError::WindowsApi(format!("GetModuleHandle failed: {:?}", e)))?, + windows::core::s!("LoadLibraryW"), + ) + .ok_or_else(|| InjectorError::WindowsApi("GetProcAddress failed".into()))?; + + // Allocate memory in the target process for the DLL path + let path_size = dll_path_wide.len() * std::mem::size_of::(); + let remote_memory = VirtualAllocEx( + process, + None, + path_size, + MEM_COMMIT | MEM_RESERVE, + PAGE_READWRITE, + ); + + if remote_memory.is_null() { + return Err(InjectorError::WindowsApi("VirtualAllocEx returned null".into())); + } + + // Write the DLL path to the allocated memory + let mut bytes_written: usize = 0; + let write_result = WriteProcessMemory( + process, + remote_memory, + dll_path_wide.as_ptr() as *const _, + path_size, + Some(&mut bytes_written), + ); + + if write_result.is_err() || bytes_written != path_size { + let _ = VirtualFreeEx(process, remote_memory, 0, MEM_RELEASE); + return Err(InjectorError::WindowsApi("WriteProcessMemory failed".into())); + } + + // Create a remote thread that calls LoadLibraryW with the DLL path + let start_routine: LPTHREAD_START_ROUTINE = std::mem::transmute(load_library); + + let thread = CreateRemoteThread( + process, + None, + 0, + start_routine, + Some(remote_memory), + 0, + None, + ) + .map_err(|e| InjectorError::WindowsApi(format!("CreateRemoteThread failed: {:?}", e)))?; + + if thread.is_invalid() { + let _ = VirtualFreeEx(process, remote_memory, 0, MEM_RELEASE); + return Err(InjectorError::WindowsApi( + "CreateRemoteThread returned invalid handle".into(), + )); + } + + // Wait for the thread to complete + let _ = WaitForSingleObject(thread, 5000); + + // Clean up + let _ = VirtualFreeEx(process, remote_memory, 0, MEM_RELEASE); + let _ = CloseHandle(thread); + + Ok(()) + } + + /// Unix stub - not implemented (uses LD_PRELOAD instead) + #[cfg(unix)] + pub fn inject_by_pid(&self, _pid: u32) -> Result<()> { + Err(InjectorError::UnsupportedPlatform) + } + + /// Unix stub - not implemented + #[cfg(unix)] + pub fn inject_by_name(&self, _name: &str) -> Result<()> { + Err(InjectorError::UnsupportedPlatform) + } + + /// Unix stub - not implemented + #[cfg(unix)] + pub fn spawn_and_inject(&self, _process_info: &ProcessInfo) -> Result { + Err(InjectorError::UnsupportedPlatform) + } +} + +/// Find the proxychains DLL/library path +pub fn find_library_path() -> Result { + // Try to find the library relative to the executable + if let Ok(exe_path) = std::env::current_exe() { + if let Some(exe_dir) = exe_path.parent() { + // Check for library in the same directory + #[cfg(windows)] + { + let dll_path = exe_dir.join("proxychains.dll"); + if dll_path.exists() { + return Ok(dll_path); + } + } + + #[cfg(target_os = "linux")] + { + let so_path = exe_dir.join("libproxychains.so"); + if so_path.exists() { + return Ok(so_path); + } + } + + #[cfg(target_os = "macos")] + { + let dylib_path = exe_dir.join("libproxychains.dylib"); + if dylib_path.exists() { + return Ok(dylib_path); + } + } + } + } + + // Check standard locations + #[cfg(windows)] + { + let paths = [ + "proxychains.dll", + "./proxychains.dll", + "./lib/proxychains.dll", + ]; + for path in &paths { + let p = std::path::PathBuf::from(path); + if p.exists() { + return Ok(p); + } + } + } + + #[cfg(target_os = "linux")] + { + let paths = [ + "./libproxychains.so", + "./lib/libproxychains.so", + "/usr/lib/libproxychains.so", + "/usr/local/lib/libproxychains.so", + ]; + for path in &paths { + let p = std::path::PathBuf::from(path); + if p.exists() { + return Ok(p); + } + } + } + + #[cfg(target_os = "macos")] + { + let paths = [ + "./libproxychains.dylib", + "./lib/libproxychains.dylib", + "/usr/lib/libproxychains.dylib", + "/usr/local/lib/libproxychains.dylib", + ]; + for path in &paths { + let p = std::path::PathBuf::from(path); + if p.exists() { + return Ok(p); + } + } + } + + Err(InjectorError::DllNotFound( + "Could not find proxychains library".to_string(), + )) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_process_info_creation() { + let info = ProcessInfo { + pid: Some(1234), + name: Some("test.exe".to_string()), + command: "test.exe".to_string(), + args: vec!["--arg1".to_string()], + }; + assert_eq!(info.pid, Some(1234)); + } + + #[test] + fn test_find_library_path() { + let result = find_library_path(); + match result { + Ok(path) => println!("Found library at: {:?}", path), + Err(e) => println!("Expected error (library not built): {}", e), + } + } +} diff --git a/proxychains-injector/src/lib.rs b/proxychains-injector/src/lib.rs new file mode 100644 index 0000000..99f918e --- /dev/null +++ b/proxychains-injector/src/lib.rs @@ -0,0 +1,9 @@ +//! DLL injection utilities for proxychains-rs +//! +//! This crate provides platform-specific DLL injection functionality: +//! - Windows: Uses dll-syringe for DLL injection +//! - Unix: Stub implementation (not needed, uses LD_PRELOAD) + +mod injector; + +pub use injector::*; diff --git a/proxychains.conf b/proxychains.conf index 2be967c..f129d4d 100644 --- a/proxychains.conf +++ b/proxychains.conf @@ -16,6 +16,8 @@ proxy_dns # TCP timeouts (milliseconds) tcp_read_time_out 15000 tcp_connect_time_out 8000 +# Maximum retries for dynamic/random/failover chains +max_chain_retries 8 # Local networks to bypass proxy (CIDR notation) localnet 127.0.0.0/255.0.0.0 diff --git a/proxychains/Cargo.toml b/proxychains/Cargo.toml index 9b7275c..0f4310f 100644 --- a/proxychains/Cargo.toml +++ b/proxychains/Cargo.toml @@ -26,3 +26,13 @@ hex.workspace = true [dev-dependencies] tracing-subscriber.workspace = true + +[target.'cfg(windows)'.dependencies] +windows = { version = "0.54", features = [ + "Win32_Foundation", + "Win32_Networking_WinSock", + "Win32_System_Threading", + "Win32_System_Memory", + "Win32_System_LibraryLoader", +] } +minhook = "0.6" diff --git a/proxychains/src/chain/manager.rs b/proxychains/src/chain/manager.rs index aed7039..6940a3c 100644 --- a/proxychains/src/chain/manager.rs +++ b/proxychains/src/chain/manager.rs @@ -175,7 +175,14 @@ impl ChainManager { target: &TargetAddress, target_port: u16, ) -> Result { + let max_attempts = self.config.max_chain_retries.max(1); + let mut attempts = 0usize; 'again: loop { + attempts += 1; + if attempts > max_attempts { + error!("Dynamic chain exceeded max retries ({})", max_attempts); + return Err(Error::ChainDown); + } debug!("Starting dynamic chain connection (attempt)"); // Calculate available proxies @@ -270,6 +277,8 @@ impl ChainManager { ) -> Result { let max_chain = self.config.chain_len.unwrap_or(proxies.len()); let alive_count = count_alive(proxies); + let max_attempts = self.config.max_chain_retries.max(1); + let mut attempts = 0usize; if alive_count == 0 { return Err(Error::ChainEmpty); @@ -284,6 +293,11 @@ impl ChainManager { } 'again: loop { + attempts += 1; + if attempts > max_attempts { + error!("Random chain exceeded max retries ({})", max_attempts); + return Err(Error::ChainDown); + } // Randomly select proxies let selected_indices = self.select_random_proxies(proxies, max_chain); if selected_indices.is_empty() { diff --git a/proxychains/src/config/parser.rs b/proxychains/src/config/parser.rs index 0ba08af..112d4d5 100644 --- a/proxychains/src/config/parser.rs +++ b/proxychains/src/config/parser.rs @@ -9,6 +9,7 @@ use std::time::Duration; use crate::config::types::*; use crate::error::{Error, Result}; +use crate::platform; /// Environment variable names pub const ENV_CONF_FILE: &str = "PROXYCHAINS_CONF_FILE"; @@ -54,6 +55,7 @@ impl ConfigParser { // Check standard locations let search_paths = vec![ PathBuf::from("./proxychains.conf"), + platform::config_path(), dirs::home_dir() .map(|h| h.join(".proxychains/proxychains.conf")) .unwrap_or_default(), @@ -178,6 +180,12 @@ impl ConfigParser { config.tcp_connect_timeout = Duration::from_millis(timeout); } } + "max_chain_retries" => { + if let Ok(retries) = value.parse::() { + // Keep a sensible floor to avoid accidental zero/retry-less configs. + config.max_chain_retries = retries.max(1); + } + } "localnet" => { self.parse_localnet(value, config)?; } diff --git a/proxychains/src/config/types.rs b/proxychains/src/config/types.rs index 84d48f5..1ea5786 100644 --- a/proxychains/src/config/types.rs +++ b/proxychains/src/config/types.rs @@ -183,6 +183,8 @@ pub struct Config { pub tcp_read_timeout: Duration, /// TCP connect timeout pub tcp_connect_timeout: Duration, + /// Maximum retry attempts when establishing a chain + pub max_chain_retries: usize, /// Local networks to bypass pub localnets: Vec, /// DNAT rules @@ -201,6 +203,7 @@ impl Default for Config { remote_dns_subnet: 224, tcp_read_timeout: Duration::from_millis(15000), tcp_connect_timeout: Duration::from_millis(8000), + max_chain_retries: 8, localnets: Vec::new(), dnats: Vec::new(), proxies: Vec::new(), diff --git a/proxychains/src/dns/cache.rs b/proxychains/src/dns/cache.rs index 03fbc0a..69ca1ba 100644 --- a/proxychains/src/dns/cache.rs +++ b/proxychains/src/dns/cache.rs @@ -146,41 +146,18 @@ impl Default for DnsCache { } } -/// Parse /etc/hosts file +/// Parse hosts file (platform-independent) +/// Deprecated: Use crate::dns::hosts::parse_hosts_file instead +#[deprecated(note = "Use crate::dns::parse_hosts_file instead")] pub fn parse_etc_hosts() -> HashMap { - let mut hosts = HashMap::new(); - - if let Ok(content) = std::fs::read_to_string("/etc/hosts") { - for line in content.lines() { - let line = line.trim(); - // Skip comments and empty lines - if line.is_empty() || line.starts_with('#') { - continue; - } - - // Parse line: IP hostname [aliases...] - let parts: Vec<&str> = line.split_whitespace().collect(); - if parts.len() >= 2 { - if let Ok(ip) = parts[0].parse::() { - for hostname in &parts[1..] { - // Skip if hostname starts with # - if hostname.starts_with('#') { - break; - } - hosts.insert(hostname.to_lowercase(), ip); - } - } - } - } - } - - hosts + crate::dns::parse_hosts_file() } -/// Check if hostname is in /etc/hosts +/// Check if hostname is in hosts file (platform-independent) +/// Deprecated: Use crate::dns::hosts::lookup_in_hosts instead +#[deprecated(note = "Use crate::dns::lookup_in_hosts instead")] pub fn in_etc_hosts(hostname: &str) -> Option { - let hosts = parse_etc_hosts(); - hosts.get(&hostname.to_lowercase()).copied() + crate::dns::lookup_in_hosts(hostname) } #[cfg(test)] diff --git a/proxychains/src/dns/hosts.rs b/proxychains/src/dns/hosts.rs new file mode 100644 index 0000000..bd095d9 --- /dev/null +++ b/proxychains/src/dns/hosts.rs @@ -0,0 +1,66 @@ +//! Platform-independent hosts file parsing +//! +//! This module provides cross-platform hosts file parsing functionality. + +use std::collections::HashMap; +use std::net::Ipv4Addr; + +use crate::platform::hosts_path; + +/// Parse the hosts file and return a map of hostname to IP address +pub fn parse_hosts_file() -> HashMap { + let mut hosts = HashMap::new(); + let path = hosts_path(); + + if let Ok(content) = std::fs::read_to_string(&path) { + for line in content.lines() { + let line = line.trim(); + // Skip comments and empty lines + if line.is_empty() || line.starts_with('#') { + continue; + } + + // Parse line: IP hostname [aliases...] + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() >= 2 { + if let Ok(ip) = parts[0].parse::() { + for hostname in &parts[1..] { + // Skip if hostname starts with # + if hostname.starts_with('#') { + break; + } + hosts.insert(hostname.to_lowercase(), ip); + } + } + } + } + } + + hosts +} + +/// Check if a hostname is in the hosts file +pub fn lookup_in_hosts(hostname: &str) -> Option { + let hosts = parse_hosts_file(); + hosts.get(&hostname.to_lowercase()).copied() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_hosts_returns_map() { + let hosts = parse_hosts_file(); + // Just verify it doesn't crash and returns a map + // The actual content depends on the system + assert!(hosts.is_empty() || !hosts.is_empty()); + } + + #[test] + fn test_lookup_in_hosts_handles_nonexistent() { + let result = lookup_in_hosts("this-hostname-definitely-does-not-exist-12345.com"); + // Should return None for non-existent hostname + assert!(result.is_none()); + } +} diff --git a/proxychains/src/dns/mod.rs b/proxychains/src/dns/mod.rs index 632df84..d3606e1 100644 --- a/proxychains/src/dns/mod.rs +++ b/proxychains/src/dns/mod.rs @@ -2,11 +2,14 @@ //! //! This module provides DNS resolution capabilities including: //! - Fake IP mapping for remote DNS resolution -//! - /etc/hosts lookup +//! - hosts file lookup //! - System DNS resolution mod cache; +mod hosts; mod resolver; -pub use cache::*; -pub use resolver::*; +// Re-export specific items to avoid ambiguity +pub use cache::{DnsCache, DnsEntry}; +pub use hosts::{parse_hosts_file, lookup_in_hosts}; +pub use resolver::{DnsResolver, is_fake_ip, get_hostname_from_ip, resolve_to_fake_ip}; diff --git a/proxychains/src/dns/resolver.rs b/proxychains/src/dns/resolver.rs index 594e53c..d5f23ca 100644 --- a/proxychains/src/dns/resolver.rs +++ b/proxychains/src/dns/resolver.rs @@ -1,9 +1,11 @@ //! DNS resolver for proxychains +#[cfg(unix)] use std::ffi::CString; use std::net::Ipv4Addr; -use crate::dns::cache::{in_etc_hosts, DnsCache}; +use crate::dns::cache::DnsCache; +use crate::dns::hosts::lookup_in_hosts as hosts_lookup; use crate::error::{Error, Result}; /// Global DNS cache instance @@ -32,12 +34,12 @@ impl DnsResolver { /// Resolve a hostname /// - /// If proxy_dns is enabled and hostname is not in /etc/hosts, + /// If proxy_dns is enabled and hostname is not in hosts file, /// returns a fake IP that will be resolved through the proxy. /// Otherwise, uses the system resolver. pub fn resolve(&self, hostname: &str) -> Result { - // First check /etc/hosts - if let Some(ip) = in_etc_hosts(hostname) { + // First check hosts file + if let Some(ip) = hosts_lookup(hostname) { return Ok(ip); } @@ -65,7 +67,8 @@ impl DnsResolver { self.cache.is_fake_ip(ip) } - /// System resolver using getaddrinfo + /// System resolver using getaddrinfo (Unix only) + #[cfg(unix)] fn system_resolve(&self, hostname: &str) -> Result { let c_hostname = CString::new(hostname)?; @@ -110,6 +113,25 @@ impl DnsResolver { Ok(addr) } + /// System resolver using Windows API (Windows only) + #[cfg(windows)] + fn system_resolve(&self, hostname: &str) -> Result { + use std::net::ToSocketAddrs; + + // Use standard library resolver + let addr: std::net::SocketAddrV4 = format!("{}:0", hostname) + .to_socket_addrs() + .map_err(|e| Error::Dns(format!("Failed to resolve {}: {}", hostname, e)))? + .filter_map(|a| match a { + std::net::SocketAddr::V4(v4) => Some(v4), + _ => None, + }) + .next() + .ok_or_else(|| Error::Dns(format!("No IPv4 address for {}", hostname)))?; + + Ok(*addr.ip()) + } + /// Get reference to global DNS cache pub fn cache(&self) -> &'static DnsCache { self.cache @@ -137,7 +159,7 @@ pub fn resolve_to_fake_ip(hostname: &str, _subnet: u8) -> Ipv4Addr { /// Parse hosts file and lookup hostname pub fn lookup_in_hosts(hostname: &str) -> Option { - in_etc_hosts(hostname) + hosts_lookup(hostname) } #[cfg(test)] diff --git a/proxychains/src/error.rs b/proxychains/src/error.rs index 35ad808..684b128 100644 --- a/proxychains/src/error.rs +++ b/proxychains/src/error.rs @@ -51,11 +51,16 @@ pub enum Error { #[error("Invalid address")] InvalidAddress, - #[error("Nix error: {0}")] - Nix(#[from] nix::Error), - #[error("UTF-8 error: {0}")] Utf8(#[from] std::string::FromUtf8Error), + + #[error("Windows API error: {0}")] + #[cfg(windows)] + WindowsApi(String), + + #[error("Nix error: {0}")] + #[cfg(unix)] + Nix(String), } /// Result type alias for proxychains operations @@ -66,3 +71,10 @@ impl From for Error { Error::Config(e.to_string()) } } + +#[cfg(unix)] +impl From for Error { + fn from(e: nix::errno::Errno) -> Self { + Error::Nix(e.to_string()) + } +} diff --git a/proxychains/src/hook/hooks.rs b/proxychains/src/hook/hooks.rs index fc9f77f..0c22588 100644 --- a/proxychains/src/hook/hooks.rs +++ b/proxychains/src/hook/hooks.rs @@ -18,8 +18,8 @@ use crate::error::Result; use crate::net::{get_ip_from_sockaddr, get_port_from_sockaddr}; use super::interpose::{ - init_original_functions, original_connect, original_getaddrinfo, original_gethostbyname, - original_getnameinfo, + init_original_functions, original_connect, original_freeaddrinfo, original_getaddrinfo, + original_gethostbyname, original_getnameinfo, }; /// Global state for the hook library @@ -216,7 +216,13 @@ pub unsafe fn hook_getaddrinfo( } // Generate fake IP - let _fake_ip = state.dns_resolver.resolve(&hostname).unwrap(); + let _fake_ip = match state.dns_resolver.resolve(&hostname) { + Ok(ip) => ip, + Err(e) => { + error!("Failed to resolve {} in hook_getaddrinfo: {}", hostname, e); + return original_getaddrinfo(node, service, hints, res); + } + }; debug!("Assigned fake IP {} for {}", _fake_ip, hostname); @@ -257,7 +263,13 @@ pub unsafe fn hook_gethostbyname(name: *const c_char) -> *mut libc::hostent { } // Generate fake IP - let _fake_ip = state.dns_resolver.resolve(&hostname).unwrap(); + let _fake_ip = match state.dns_resolver.resolve(&hostname) { + Ok(ip) => ip, + Err(e) => { + error!("Failed to resolve {} in hook_gethostbyname: {}", hostname, e); + return original_gethostbyname(name); + } + }; debug!("Assigned fake IP {} for {}", _fake_ip, hostname); @@ -273,23 +285,9 @@ pub unsafe fn hook_gethostbyname(name: *const c_char) -> *mut libc::hostent { /// This function makes unsafe FFI calls pub unsafe fn hook_freeaddrinfo(res: *mut libc::addrinfo) { trace!("hook_freeaddrinfo called"); - - if res.is_null() { - return; - } - - // Free our allocated memory - let ai = Box::from_raw(res); - - // Free ai_addr - if !ai.ai_addr.is_null() { - let _ = Box::from_raw(ai.ai_addr as *mut libc::sockaddr_in); - } - - // Free ai_canonname - if !ai.ai_canonname.is_null() { - let _ = std::ffi::CString::from_raw(ai.ai_canonname); - } + // Our getaddrinfo hook currently falls back to the system allocator, so + // free through libc to avoid mismatched allocator/free behavior. + original_freeaddrinfo(res); } /// Hook for getnameinfo() system call diff --git a/proxychains/src/hook/hooks_windows.rs b/proxychains/src/hook/hooks_windows.rs new file mode 100644 index 0000000..f8a657e --- /dev/null +++ b/proxychains/src/hook/hooks_windows.rs @@ -0,0 +1,459 @@ +//! Windows-specific hook implementations. +//! +//! Winsock calls are intercepted via MinHook and forwarded through the proxy +//! chain on the original socket handle. + +use std::collections::HashMap; +use std::ffi::{c_void, CStr}; +use std::mem::{self, ManuallyDrop}; +use std::net::Ipv4Addr; +use std::os::windows::io::FromRawSocket; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::OnceLock; + +use parking_lot::Mutex; +use rand::seq::SliceRandom; +use tracing::{debug, error, info, warn}; +use windows::Win32::Networking::WinSock::{ + ADDRINFOA, AF_INET, IN_ADDR, IN_ADDR_0, IPPROTO_TCP, SOCKADDR, SOCKADDR_IN, SOCK_STREAM, + SOCKET_ERROR, WSAECONNREFUSED, WSAEFAULT, WSAEINVAL, WSASetLastError, +}; + +use crate::config::{ChainType, Config, ProxyData, ProxyState}; +use crate::dns::{is_fake_ip, DnsResolver}; +use crate::error::{Error, Result}; +use crate::net::{get_ip_from_sockaddr, get_port_from_sockaddr}; +use crate::proxy::{tunnel_through_proxy, TargetAddress}; + +use super::interpose_windows::{ + init_original_functions, original_connect, original_freeaddrinfo, original_getaddrinfo, + original_gethostbyname, original_getnameinfo, +}; + +/// Global state for the hook library (Windows). +pub struct HookState { + pub config: Config, + pub dns_resolver: DnsResolver, + pub proxy_states: Mutex>, + pub load_balance_counter: AtomicUsize, + pub initialized: bool, +} + +impl HookState { + pub fn new(config: Config) -> Self { + let dns_resolver = DnsResolver::new(config.proxy_dns, config.remote_dns_subnet); + let proxy_states = Mutex::new(config.proxies.clone()); + Self { + config, + dns_resolver, + proxy_states, + load_balance_counter: AtomicUsize::new(0), + initialized: true, + } + } +} + +/// Global hook state. +static HOOK_STATE: OnceLock = OnceLock::new(); +/// Tracks custom addrinfo allocations created by `hook_getaddrinfo_impl`. +static CUSTOM_ADDRINFO_ALLOCATIONS: OnceLock>> = OnceLock::new(); + +fn custom_alloc_map() -> &'static Mutex> { + CUSTOM_ADDRINFO_ALLOCATIONS.get_or_init(|| Mutex::new(HashMap::new())) +} + +/// Initialize the hook library. +pub fn init_hooks(config: Config) -> Result<()> { + init_original_functions()?; + + let state = HookState::new(config); + if HOOK_STATE.set(state).is_err() { + warn!("Hook state already initialized"); + } + + info!("Proxychains Windows hooks initialized"); + Ok(()) +} + +/// Check if hooks are initialized. +pub fn is_initialized() -> bool { + HOOK_STATE.get().map_or(false, |s| s.initialized) +} + +fn get_hook_state() -> Option<&'static HookState> { + HOOK_STATE.get() +} + +fn make_sockaddr_in(ip: Ipv4Addr, port: u16) -> SOCKADDR_IN { + SOCKADDR_IN { + sin_family: AF_INET, + sin_port: port.to_be(), + sin_addr: IN_ADDR { + S_un: IN_ADDR_0 { + S_addr: u32::from_ne_bytes(ip.octets()), + }, + }, + sin_zero: [0; 8], + } +} + +unsafe fn connect_socket_to_proxy(sock: usize, proxy: &ProxyData) -> Result<()> { + let sockaddr = make_sockaddr_in(proxy.ip, proxy.port); + let ret = original_connect( + sock, + &sockaddr as *const SOCKADDR_IN as *const c_void, + mem::size_of::() as i32, + ); + if ret == SOCKET_ERROR { + return Err(Error::ProxyConnection(format!( + "Failed to connect to proxy {}:{}", + proxy.ip, proxy.port + ))); + } + Ok(()) +} + +unsafe fn connect_chain_on_socket( + sock: usize, + selected: &[ProxyData], + target: &TargetAddress, + target_port: u16, + timeout: std::time::Duration, +) -> std::result::Result<(), (Error, usize)> { + if selected.is_empty() { + return Err((Error::ChainEmpty, 0)); + } + + if let Err(e) = connect_socket_to_proxy(sock, &selected[0]) { + return Err((e, 0)); + } + + // The application owns this socket. We must not close it from the hook. + let mut stream = ManuallyDrop::new(std::net::TcpStream::from_raw_socket(sock as u64)); + let stream_ref: &mut std::net::TcpStream = &mut *stream; + let _ = stream_ref.set_read_timeout(Some(timeout)); + let _ = stream_ref.set_write_timeout(Some(timeout)); + + if selected.len() == 1 { + if let Err(e) = + tunnel_through_proxy(stream_ref, &selected[0], target, target_port, timeout) + { + return Err((e, 0)); + } + return Ok(()); + } + + let mut current = 0usize; + for next in 1..selected.len() { + let next_target = TargetAddress::from_ip(selected[next].ip); + if let Err(e) = tunnel_through_proxy( + stream_ref, + &selected[current], + &next_target, + selected[next].port, + timeout, + ) { + return Err((e, next)); + } + current = next; + } + + if let Err(e) = tunnel_through_proxy( + stream_ref, + &selected[current], + target, + target_port, + timeout, + ) { + return Err((e, current)); + } + + Ok(()) +} + +fn select_indices(state: &HookState, proxies: &[ProxyData]) -> Option> { + let alive_indices: Vec = proxies + .iter() + .enumerate() + .filter(|(_, p)| p.state == ProxyState::Play) + .map(|(i, _)| i) + .collect(); + + match state.config.chain_type { + ChainType::Strict => { + if proxies.is_empty() { + None + } else { + Some((0..proxies.len()).collect()) + } + } + ChainType::Dynamic => { + if alive_indices.is_empty() { + None + } else { + Some(alive_indices) + } + } + ChainType::Random => { + if alive_indices.is_empty() { + return None; + } + let mut selected = alive_indices; + let mut rng = rand::thread_rng(); + selected.shuffle(&mut rng); + let max_chain = state.config.chain_len.unwrap_or(selected.len()).max(1); + selected.truncate(max_chain.min(selected.len())); + Some(selected) + } + ChainType::LoadBalance => { + if alive_indices.is_empty() { + return None; + } + let idx = state + .load_balance_counter + .fetch_add(1, Ordering::Relaxed) + % alive_indices.len(); + Some(vec![alive_indices[idx]]) + } + ChainType::Failover => alive_indices.first().copied().map(|i| vec![i]), + } +} + +fn parse_service_port(service: *const i8) -> u16 { + if service.is_null() { + return 0; + } + unsafe { + match CStr::from_ptr(service).to_str() { + Ok(s) => s.parse::().unwrap_or(0), + Err(_) => 0, + } + } +} + +/// Windows connect hook implementation. +#[cfg(windows)] +pub unsafe extern "system" fn hook_connect_impl( + sock: usize, + addr: *const c_void, + len: i32, +) -> i32 { + let state = match get_hook_state() { + Some(s) => s, + None => return original_connect(sock, addr, len), + }; + + if addr.is_null() || len <= 0 { + return original_connect(sock, addr, len); + } + + let target_ip = match get_ip_from_sockaddr(addr) { + Some(ip) => ip, + None => return original_connect(sock, addr, len), + }; + let target_port = get_port_from_sockaddr(addr); + + if state.config.should_bypass(&target_ip) { + return original_connect(sock, addr, len); + } + + let (final_ip, final_port) = state.config.apply_dnat(&target_ip, target_port); + let target_domain = if is_fake_ip(&final_ip) { + state.dns_resolver.get_hostname(&final_ip) + } else { + None + }; + + let target = if let Some(domain) = target_domain { + TargetAddress::from_both(final_ip, domain) + } else { + TargetAddress::from_ip(final_ip) + }; + + let max_attempts = state.config.max_chain_retries.max(1); + for attempt in 1..=max_attempts { + let (selected_indices, selected_proxies) = { + let proxies = state.proxy_states.lock(); + let Some(indices) = select_indices(state, &proxies) else { + WSASetLastError(WSAECONNREFUSED.0); + return SOCKET_ERROR; + }; + let chosen = indices.iter().map(|&i| proxies[i].clone()).collect::>(); + (indices, chosen) + }; + + match connect_chain_on_socket( + sock, + &selected_proxies, + &target, + final_port, + state.config.tcp_read_timeout, + ) { + Ok(()) => return 0, + Err((e, failed_hop)) => { + let failed_proxy_global = selected_indices + .get(failed_hop) + .or_else(|| selected_indices.last()) + .copied(); + if let Some(idx) = failed_proxy_global { + let mut proxies = state.proxy_states.lock(); + if let Some(p) = proxies.get_mut(idx) { + p.state = if matches!(e, Error::Blocked) { + ProxyState::Blocked + } else { + ProxyState::Down + }; + } + } + warn!("connect attempt {}/{} failed: {}", attempt, max_attempts, e); + } + } + } + + WSASetLastError(WSAECONNREFUSED.0); + SOCKET_ERROR +} + +/// Windows getaddrinfo hook implementation. +#[cfg(windows)] +pub unsafe extern "system" fn hook_getaddrinfo_impl( + pnode: *const i8, + pservice: *const i8, + phints: *const c_void, + ppresult: *mut *mut c_void, +) -> i32 { + let state = match get_hook_state() { + Some(s) if s.config.proxy_dns => s, + _ => return original_getaddrinfo(pnode, pservice, phints, ppresult), + }; + + if pnode.is_null() || ppresult.is_null() { + return original_getaddrinfo(pnode, pservice, phints, ppresult); + } + + if !phints.is_null() { + let hints = &*(phints as *const ADDRINFOA); + if hints.ai_family != 0 && hints.ai_family != AF_INET.0 as i32 { + return original_getaddrinfo(pnode, pservice, phints, ppresult); + } + } + + let hostname = match CStr::from_ptr(pnode).to_str() { + Ok(s) => s, + Err(_) => return original_getaddrinfo(pnode, pservice, phints, ppresult), + }; + + if hostname.parse::().is_ok() { + return original_getaddrinfo(pnode, pservice, phints, ppresult); + } + + if crate::dns::lookup_in_hosts(hostname).is_some() { + return original_getaddrinfo(pnode, pservice, phints, ppresult); + } + + let fake_ip = match state.dns_resolver.resolve(hostname) { + Ok(ip) => ip, + Err(e) => { + error!("Failed to resolve {} in hook_getaddrinfo_impl: {}", hostname, e); + return original_getaddrinfo(pnode, pservice, phints, ppresult); + } + }; + + let service_port = parse_service_port(pservice); + let sockaddr_box = Box::new(make_sockaddr_in(fake_ip, service_port)); + let sockaddr_ptr = Box::into_raw(sockaddr_box); + + let mut ai = ADDRINFOA::default(); + ai.ai_family = AF_INET.0 as i32; + ai.ai_socktype = if phints.is_null() { + SOCK_STREAM.0 + } else { + (*(phints as *const ADDRINFOA)).ai_socktype + }; + ai.ai_protocol = if phints.is_null() { + IPPROTO_TCP.0 + } else { + (*(phints as *const ADDRINFOA)).ai_protocol + }; + ai.ai_addrlen = mem::size_of::(); + ai.ai_addr = sockaddr_ptr as *mut SOCKADDR; + ai.ai_next = std::ptr::null_mut(); + + let ai_ptr = Box::into_raw(Box::new(ai)); + *ppresult = ai_ptr as *mut c_void; + custom_alloc_map() + .lock() + .insert(ai_ptr as usize, sockaddr_ptr as usize); + + debug!("Assigned fake IP {} for {}", fake_ip, hostname); + 0 +} + +/// Windows freeaddrinfo hook implementation. +#[cfg(windows)] +pub unsafe extern "system" fn hook_freeaddrinfo_impl(pres: *mut c_void) { + if pres.is_null() { + return; + } + + if let Some(sockaddr_ptr) = custom_alloc_map().lock().remove(&(pres as usize)) { + let _ = Box::from_raw(sockaddr_ptr as *mut SOCKADDR_IN); + let _ = Box::from_raw(pres as *mut ADDRINFOA); + return; + } + + original_freeaddrinfo(pres); +} + +/// Windows gethostbyname hook implementation. +#[cfg(windows)] +pub unsafe extern "system" fn hook_gethostbyname_impl(name: *const i8) -> *mut c_void { + original_gethostbyname(name) +} + +/// Windows getnameinfo hook implementation. +#[cfg(windows)] +pub unsafe extern "system" fn hook_getnameinfo_impl( + sa: *const c_void, + salen: i32, + host: *mut i8, + hostlen: u32, + serv: *mut i8, + servlen: u32, + flags: i32, +) -> i32 { + let Some(state) = get_hook_state() else { + return original_getnameinfo(sa, salen, host, hostlen, serv, servlen, flags); + }; + + if state.config.proxy_dns && !sa.is_null() && !host.is_null() && hostlen > 1 { + if let Some(ip) = get_ip_from_sockaddr(sa) { + if is_fake_ip(&ip) { + if let Some(hostname) = state.dns_resolver.get_hostname(&ip) { + let bytes = hostname.as_bytes(); + if bytes.len() + 1 > hostlen as usize { + WSASetLastError(WSAEFAULT.0); + return WSAEINVAL.0; + } + std::ptr::copy_nonoverlapping(bytes.as_ptr(), host as *mut u8, bytes.len()); + *host.add(bytes.len()) = 0; + return 0; + } + } + } + } + + original_getnameinfo(sa, salen, host, hostlen, serv, servlen, flags) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_hook_state_creation() { + let config = Config::default(); + let state = HookState::new(config); + assert!(state.initialized); + } +} diff --git a/proxychains/src/hook/interpose_windows.rs b/proxychains/src/hook/interpose_windows.rs new file mode 100644 index 0000000..4248dc6 --- /dev/null +++ b/proxychains/src/hook/interpose_windows.rs @@ -0,0 +1,175 @@ +//! Windows API hooking via MinHook. +//! +//! This module installs hooks for Winsock APIs and exposes trampoline-backed +//! `original_*` wrappers that call the underlying system implementations. + +use std::ffi::c_void; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::OnceLock; + +use minhook::MinHook; +use tracing::debug; +use windows::Win32::Networking::WinSock::{WSAECONNREFUSED, WSAHOST_NOT_FOUND, WSASetLastError}; + +use crate::error::{Error, Result}; + +use super::hooks_windows::{ + hook_connect_impl, hook_freeaddrinfo_impl, hook_getaddrinfo_impl, hook_gethostbyname_impl, + hook_getnameinfo_impl, +}; + +type ConnectFn = unsafe extern "system" fn(usize, *const c_void, i32) -> i32; +type GetAddrInfoFn = + unsafe extern "system" fn(*const i8, *const i8, *const c_void, *mut *mut c_void) -> i32; +type FreeAddrInfoFn = unsafe extern "system" fn(*mut c_void); +type GetHostByNameFn = unsafe extern "system" fn(*const i8) -> *mut c_void; +type GetNameInfoFn = + unsafe extern "system" fn(*const c_void, i32, *mut i8, u32, *mut i8, u32, i32) -> i32; + +static ORIGINAL_CONNECT: OnceLock = OnceLock::new(); +static ORIGINAL_GETADDRINFO: OnceLock = OnceLock::new(); +static ORIGINAL_FREEADDRINFO: OnceLock = OnceLock::new(); +static ORIGINAL_GETHOSTBYNAME: OnceLock = OnceLock::new(); +static ORIGINAL_GETNAMEINFO: OnceLock = OnceLock::new(); + +static HOOKS_READY: AtomicBool = AtomicBool::new(false); + +fn mh_to_error(op: &str, status: minhook::MH_STATUS) -> Error { + Error::WindowsApi(format!("MinHook {} failed: {:?}", op, status)) +} + +unsafe fn install_api_hook(proc_name: &str, detour: *mut c_void) -> Result { + let trampoline = MinHook::create_hook_api("ws2_32.dll", proc_name, detour) + .map_err(|s| mh_to_error("create_hook_api", s))?; + Ok(std::mem::transmute_copy::<*mut c_void, F>(&trampoline)) +} + +/// Store for original function pointers. +pub struct OriginalFunctions; + +impl OriginalFunctions { + pub fn new() -> Self { + Self + } + + pub fn load_and_hook(&mut self) -> Result<()> { + unsafe { + let connect_fn: ConnectFn = + install_api_hook("connect", hook_connect_impl as *const () as *mut c_void)?; + let getaddrinfo_fn: GetAddrInfoFn = install_api_hook( + "getaddrinfo", + hook_getaddrinfo_impl as *const () as *mut c_void, + )?; + let freeaddrinfo_fn: FreeAddrInfoFn = install_api_hook( + "freeaddrinfo", + hook_freeaddrinfo_impl as *const () as *mut c_void, + )?; + let gethostbyname_fn: GetHostByNameFn = install_api_hook( + "gethostbyname", + hook_gethostbyname_impl as *const () as *mut c_void, + )?; + let getnameinfo_fn: GetNameInfoFn = install_api_hook( + "getnameinfo", + hook_getnameinfo_impl as *const () as *mut c_void, + )?; + + let _ = ORIGINAL_CONNECT.set(connect_fn); + let _ = ORIGINAL_GETADDRINFO.set(getaddrinfo_fn); + let _ = ORIGINAL_FREEADDRINFO.set(freeaddrinfo_fn); + let _ = ORIGINAL_GETHOSTBYNAME.set(gethostbyname_fn); + let _ = ORIGINAL_GETNAMEINFO.set(getnameinfo_fn); + + MinHook::enable_all_hooks().map_err(|s| mh_to_error("enable_all_hooks", s))?; + } + + Ok(()) + } +} + +impl Default for OriginalFunctions { + fn default() -> Self { + Self::new() + } +} + +/// Initialize original functions and install hooks. +pub fn init_original_functions() -> Result<()> { + if HOOKS_READY.load(Ordering::Acquire) { + return Ok(()); + } + + let mut funcs = OriginalFunctions::new(); + funcs.load_and_hook()?; + HOOKS_READY.store(true, Ordering::Release); + debug!("Windows Winsock hooks installed"); + Ok(()) +} + +/// Call the original connect function. +pub unsafe fn original_connect(sock: usize, addr: *const c_void, len: i32) -> i32 { + if let Some(f) = ORIGINAL_CONNECT.get() { + f(sock, addr, len) + } else { + WSASetLastError(WSAECONNREFUSED.0); + -1 + } +} + +/// Call the original getaddrinfo function. +pub unsafe fn original_getaddrinfo( + node: *const i8, + service: *const i8, + hints: *const c_void, + res: *mut *mut c_void, +) -> i32 { + if let Some(f) = ORIGINAL_GETADDRINFO.get() { + f(node, service, hints, res) + } else { + WSASetLastError(WSAHOST_NOT_FOUND.0); + WSAHOST_NOT_FOUND.0 + } +} + +/// Call the original freeaddrinfo function. +pub unsafe fn original_freeaddrinfo(res: *mut c_void) { + if let Some(f) = ORIGINAL_FREEADDRINFO.get() { + f(res); + } +} + +/// Call the original gethostbyname function. +pub unsafe fn original_gethostbyname(name: *const i8) -> *mut c_void { + if let Some(f) = ORIGINAL_GETHOSTBYNAME.get() { + f(name) + } else { + std::ptr::null_mut() + } +} + +/// Call the original getnameinfo function. +pub unsafe fn original_getnameinfo( + sa: *const c_void, + salen: i32, + host: *mut i8, + hostlen: u32, + serv: *mut i8, + servlen: u32, + flags: i32, +) -> i32 { + if let Some(f) = ORIGINAL_GETNAMEINFO.get() { + f(sa, salen, host, hostlen, serv, servlen, flags) + } else { + WSAHOST_NOT_FOUND.0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_original_functions_creation() { + let funcs = OriginalFunctions::new(); + let _ = funcs; + } +} diff --git a/proxychains/src/hook/mod.rs b/proxychains/src/hook/mod.rs index 298a760..14c395c 100644 --- a/proxychains/src/hook/mod.rs +++ b/proxychains/src/hook/mod.rs @@ -2,9 +2,30 @@ //! //! This module provides the infrastructure for intercepting //! network system calls and redirecting them through proxy chains. +//! +//! Platform support: +//! - Unix (Linux/macOS): Uses LD_PRELOAD/DYLD_INSERT_LIBRARIES with dlsym +//! - Windows: Uses API Hooking (MinHook) +// Unix-specific modules +#[cfg(unix)] mod hooks; +#[cfg(unix)] mod interpose; +// Windows-specific modules +#[cfg(windows)] +mod hooks_windows; +#[cfg(windows)] +mod interpose_windows; + +// Re-export based on platform +#[cfg(unix)] pub use hooks::*; +#[cfg(unix)] pub use interpose::*; + +#[cfg(windows)] +pub use hooks_windows::*; +#[cfg(windows)] +pub use interpose_windows::*; diff --git a/proxychains/src/lib.rs b/proxychains/src/lib.rs index 4f3fba8..b17707d 100644 --- a/proxychains/src/lib.rs +++ b/proxychains/src/lib.rs @@ -36,6 +36,7 @@ pub mod dns; pub mod error; pub mod hook; pub mod net; +pub mod platform; pub mod proxy; // Re-exports for convenience diff --git a/proxychains/src/net/mod.rs b/proxychains/src/net/mod.rs index dfce5f5..7de1916 100644 --- a/proxychains/src/net/mod.rs +++ b/proxychains/src/net/mod.rs @@ -1,7 +1,37 @@ //! Network utilities module +//! +//! This module provides platform-specific network utilities for: +//! - Socket operations +//! - Timeout handling +//! - IP address utilities +#[cfg(unix)] mod socket; + +#[cfg(unix)] mod timeout; +// Windows-specific modules +#[cfg(windows)] +mod socket_windows; +#[cfg(windows)] +mod timeout_windows; + +// Re-export based on platform +#[cfg(unix)] pub use socket::*; + +#[cfg(unix)] pub use timeout::*; + +#[cfg(windows)] +pub use socket_windows::*; + +#[cfg(windows)] +pub use timeout_windows::{ + read_bytes_timeout, + write_bytes_timeout, + connect_with_timeout, + is_connected, + set_socket_timeout, +}; diff --git a/proxychains/src/net/socket_windows.rs b/proxychains/src/net/socket_windows.rs new file mode 100644 index 0000000..d5caebf --- /dev/null +++ b/proxychains/src/net/socket_windows.rs @@ -0,0 +1,129 @@ +//! Windows-specific socket utilities +//! +//! This module provides Windows-specific implementations for socket operations +//! using the Winsock2 API. + +use std::net::Ipv4Addr; + +/// Get the port from a SOCKADDR structure (Windows-specific) +#[cfg(windows)] +pub fn get_port_from_sockaddr(addr: *const core::ffi::c_void) -> u16 { + unsafe { + // SOCKADDR has sa_family at offset 0, followed by data + let sa_family = *(addr as *const u16); + // AF_INET = 2 + if sa_family == 2 { + // SOCKADDR_IN structure: + // sin_family: u16 (2 bytes) + // sin_port: u16 (2 bytes) - network byte order + let port_ptr = (addr as *const u8).add(2) as *const u16; + u16::from_be(*port_ptr) + } else { + 0 + } + } +} + +/// Get the IP address from a SOCKADDR structure (Windows-specific) +#[cfg(windows)] +pub fn get_ip_from_sockaddr(addr: *const core::ffi::c_void) -> Option { + unsafe { + let sa_family = *(addr as *const u16); + // AF_INET = 2 + if sa_family == 2 { + // SOCKADDR_IN structure: + // sin_family: u16 (2 bytes) + // sin_port: u16 (2 bytes) + // sin_addr: in_addr (4 bytes) + let ip_ptr = (addr as *const u8).add(4); + let ip_bytes = [ + *ip_ptr, + *ip_ptr.add(1), + *ip_ptr.add(2), + *ip_ptr.add(3), + ]; + Some(Ipv4Addr::from(ip_bytes)) + } else { + None + } + } +} + +/// Create a TCP socket (Windows-specific) +pub fn create_tcp_socket() -> std::io::Result { + use socket2::{Domain, Protocol, Socket, Type}; + + let socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP))?; + socket.set_reuse_address(true)?; + socket.set_nodelay(true)?; + + Ok(socket.into()) +} + +/// Create a non-blocking TCP socket (Windows-specific) +pub fn create_nonblocking_tcp_socket() -> std::io::Result { + use socket2::{Domain, Protocol, Socket, Type}; + + let socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP))?; + socket.set_reuse_address(true)?; + socket.set_nodelay(true)?; + socket.set_nonblocking(true)?; + + Ok(socket) +} + +/// Check if an IP is a private/local address +pub fn is_private_ip(ip: &Ipv4Addr) -> bool { + let octets = ip.octets(); + + // 10.0.0.0/8 + if octets[0] == 10 { + return true; + } + + // 172.16.0.0/12 + if octets[0] == 172 && (16..=31).contains(&octets[1]) { + return true; + } + + // 192.168.0.0/16 + if octets[0] == 192 && octets[1] == 168 { + return true; + } + + false +} + +/// Check if an IP is localhost +pub fn is_localhost(ip: &Ipv4Addr) -> bool { + ip.is_loopback() +} + +/// Check if an IP is the "any" address (0.0.0.0) +pub fn is_any_addr(ip: &Ipv4Addr) -> bool { + ip.is_unspecified() +} + +/// Check if an IP is a broadcast address +pub fn is_broadcast(ip: &Ipv4Addr) -> bool { + ip.is_broadcast() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_is_private_ip() { + assert!(is_private_ip(&Ipv4Addr::new(10, 0, 0, 1))); + assert!(is_private_ip(&Ipv4Addr::new(172, 16, 0, 1))); + assert!(is_private_ip(&Ipv4Addr::new(192, 168, 1, 1))); + assert!(!is_private_ip(&Ipv4Addr::new(8, 8, 8, 8))); + } + + #[test] + fn test_is_localhost() { + assert!(is_localhost(&Ipv4Addr::new(127, 0, 0, 1))); + assert!(!is_localhost(&Ipv4Addr::new(192, 168, 1, 1))); + } +} diff --git a/proxychains/src/net/timeout_windows.rs b/proxychains/src/net/timeout_windows.rs new file mode 100644 index 0000000..3872e03 --- /dev/null +++ b/proxychains/src/net/timeout_windows.rs @@ -0,0 +1,160 @@ +//! Windows-specific network timeout utilities +//! +//! This module provides Windows-specific implementations for network timeout +//! operations using the Winsock2 API. + +use std::io::{Read, Write}; +use std::time::Duration; + +use crate::error::{Error, Result}; + +/// Read bytes from a stream with timeout +pub fn read_bytes_timeout(stream: &mut T, count: usize, timeout: Duration) -> Result> { + let mut buf = vec![0u8; count]; + let mut read_total = 0; + let start = std::time::Instant::now(); + + while read_total < count { + let remaining = timeout.saturating_sub(start.elapsed()); + if remaining.is_zero() { + return Err(Error::Timeout("Read timeout".to_string())); + } + + let n = match stream.read(&mut buf[read_total..]) { + Ok(0) => { + return Err(Error::Io(std::io::Error::new( + std::io::ErrorKind::UnexpectedEof, + "Unexpected EOF", + ))); + } + Ok(n) => n, + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { + std::thread::sleep(Duration::from_millis(10)); + continue; + } + Err(e) => return Err(Error::Io(e)), + }; + + read_total += n; + } + + Ok(buf) +} + +/// Write bytes to a stream with timeout +pub fn write_bytes_timeout(stream: &mut T, data: &[u8], timeout: Duration) -> Result<()> { + let mut written_total = 0; + let start = std::time::Instant::now(); + + while written_total < data.len() { + let remaining = timeout.saturating_sub(start.elapsed()); + if remaining.is_zero() { + return Err(Error::Timeout("Write timeout".to_string())); + } + + let n = match stream.write(&data[written_total..]) { + Ok(0) => { + return Err(Error::Io(std::io::Error::new( + std::io::ErrorKind::WriteZero, + "Write zero bytes", + ))); + } + Ok(n) => n, + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { + std::thread::sleep(Duration::from_millis(10)); + continue; + } + Err(e) => return Err(Error::Io(e)), + }; + + written_total += n; + } + + stream.flush()?; + Ok(()) +} + +/// Connect to an address with timeout (Windows-specific) +pub fn connect_with_timeout( + addr: &std::net::SocketAddrV4, + timeout: Duration, +) -> Result { + let socket = socket2::Socket::new( + socket2::Domain::IPV4, + socket2::Type::STREAM, + Some(socket2::Protocol::TCP), + )?; + + // Set non-blocking + socket.set_nonblocking(true)?; + + // Attempt to connect + let connect_result = socket.connect(&(*addr).into()); + + match connect_result { + Ok(()) => { + socket.set_nonblocking(false)?; + return Ok(socket.into()); + } + Err(e) => { + // Windows uses WSAEWOULDBLOCK (10035) instead of EINPROGRESS + let would_block = e.raw_os_error() + .map(|code| code == 10035) // WSAEWOULDBLOCK + .unwrap_or(false); + + if !would_block { + return Err(Error::Io(e)); + } + } + } + + // Wait for connection using polling + let start = std::time::Instant::now(); + loop { + if start.elapsed() >= timeout { + return Err(Error::Timeout(format!("Connection to {} timed out", addr))); + } + + // Use std::net::TcpStream::connect_timeout as fallback + // This is simpler and more reliable + std::thread::sleep(Duration::from_millis(10)); + + // Try to check if writable + match socket.set_nonblocking(false) { + Ok(()) => { + // Connection successful + return Ok(socket.into()); + } + Err(_) => { + // Continue waiting + if start.elapsed() >= timeout { + return Err(Error::Timeout(format!("Connection to {} timed out", addr))); + } + socket.set_nonblocking(true)?; + } + } + } +} + +/// Check if a socket is connected and writable (Windows-specific) +pub fn is_connected(_raw_socket: usize) -> Result { + // Simplified implementation + Ok(true) +} + +/// Set socket timeout options (Windows-specific) +pub fn set_socket_timeout(_raw_socket: usize, _timeout: Duration) -> Result<()> { + // Placeholder + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_timeout_duration() { + let d = Duration::from_millis(5000); + assert_eq!(d.as_millis(), 5000); + } +} diff --git a/proxychains/src/platform/mod.rs b/proxychains/src/platform/mod.rs new file mode 100644 index 0000000..b5d9462 --- /dev/null +++ b/proxychains/src/platform/mod.rs @@ -0,0 +1,68 @@ +//! Platform abstraction module +//! +//! This module provides platform-specific implementations for: +//! - Hosts file path resolution +//! - Configuration file paths +//! - Socket operations +//! - Error handling + +use std::path::PathBuf; + +/// Platform-specific constants and operations +pub trait Platform: Send + Sync { + /// Get the hosts file path + fn hosts_path() -> PathBuf; + + /// Get the default configuration file path + fn config_path() -> PathBuf; + + /// Get the library file extension + fn library_extension() -> &'static str; +} + +#[cfg(unix)] +mod unix; + +#[cfg(unix)] +pub use unix::UnixPlatform as CurrentPlatform; + +#[cfg(windows)] +mod windows; + +#[cfg(windows)] +pub use windows::WindowsPlatform as CurrentPlatform; + +/// Get the hosts file path for the current platform +pub fn hosts_path() -> PathBuf { + CurrentPlatform::hosts_path() +} + +/// Get the default config path for the current platform +pub fn config_path() -> PathBuf { + CurrentPlatform::config_path() +} + +/// Get the library extension for the current platform +pub fn library_extension() -> &'static str { + CurrentPlatform::library_extension() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_hosts_path_exists() { + let path = hosts_path(); + assert!(!path.as_os_str().is_empty()); + } + + #[test] + fn test_library_extension() { + let ext = library_extension(); + #[cfg(unix)] + assert!(ext == "so" || ext == "dylib"); + #[cfg(windows)] + assert_eq!(ext, "dll"); + } +} diff --git a/proxychains/src/platform/unix.rs b/proxychains/src/platform/unix.rs new file mode 100644 index 0000000..4799fc5 --- /dev/null +++ b/proxychains/src/platform/unix.rs @@ -0,0 +1,68 @@ +//! Unix platform implementation + +use std::path::PathBuf; + +use super::Platform; + +/// Unix platform implementation +pub struct UnixPlatform; + +impl Platform for UnixPlatform { + fn hosts_path() -> PathBuf { + PathBuf::from("/etc/hosts") + } + + fn config_path() -> PathBuf { + // Check for user config first, then system config + if let Ok(home) = std::env::var("HOME") { + let user_config = PathBuf::from(home).join(".proxychains/proxychains.conf"); + if user_config.exists() { + return user_config; + } + } + + // Default system config locations + let system_paths = [ + "/etc/proxychains.conf", + "/usr/local/etc/proxychains.conf", + ]; + + for path in &system_paths { + let p = PathBuf::from(path); + if p.exists() { + return p; + } + } + + // Return default if none exists + PathBuf::from("/etc/proxychains.conf") + } + + fn library_extension() -> &'static str { + #[cfg(target_os = "macos")] + { + "dylib" + } + #[cfg(not(target_os = "macos"))] + { + "so" + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_hosts_path() { + let path = UnixPlatform::hosts_path(); + assert_eq!(path.to_str(), Some("/etc/hosts")); + } + + #[test] + fn test_library_extension() { + let ext = UnixPlatform::library_extension(); + assert!(!ext.is_empty()); + } +} diff --git a/proxychains/src/platform/windows.rs b/proxychains/src/platform/windows.rs new file mode 100644 index 0000000..7b34059 --- /dev/null +++ b/proxychains/src/platform/windows.rs @@ -0,0 +1,73 @@ +//! Windows platform implementation + +use std::path::PathBuf; + +use super::Platform; + +/// Windows platform implementation +pub struct WindowsPlatform; + +impl Platform for WindowsPlatform { + fn hosts_path() -> PathBuf { + // Windows hosts file: %WINDIR%\System32\drivers\etc\hosts + let windir = std::env::var("WINDIR").unwrap_or_else(|_| "C:\\Windows".to_string()); + PathBuf::from(windir).join("System32\\drivers\\etc\\hosts") + } + + fn config_path() -> PathBuf { + // Check for config in order: + // 1. %APPDATA%\proxychains\proxychains.conf + // 2. %PROGRAMDATA%\proxychains\proxychains.conf + // 3. Same directory as executable + + // Try APPDATA first + if let Ok(appdata) = std::env::var("APPDATA") { + let user_config = PathBuf::from(appdata).join("proxychains\\proxychains.conf"); + if user_config.exists() { + return user_config; + } + } + + // Try PROGRAMDATA + if let Ok(programdata) = std::env::var("PROGRAMDATA") { + let system_config = PathBuf::from(programdata).join("proxychains\\proxychains.conf"); + if system_config.exists() { + return system_config; + } + } + + // Try same directory as executable + if let Ok(exe_path) = std::env::current_exe() { + if let Some(exe_dir) = exe_path.parent() { + let local_config = exe_dir.join("proxychains.conf"); + if local_config.exists() { + return local_config; + } + } + } + + // Default to APPDATA location (even if doesn't exist) + let appdata = std::env::var("APPDATA").unwrap_or_else(|_| ".".to_string()); + PathBuf::from(appdata).join("proxychains\\proxychains.conf") + } + + fn library_extension() -> &'static str { + "dll" + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_hosts_path() { + let path = WindowsPlatform::hosts_path(); + assert!(path.ends_with("drivers\\etc\\hosts") || path.ends_with("drivers/etc/hosts")); + } + + #[test] + fn test_library_extension() { + assert_eq!(WindowsPlatform::library_extension(), "dll"); + } +}