1145 lines
33 KiB
Go
1145 lines
33 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/pem"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"math/big"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"golang.org/x/sys/windows/registry"
|
|
)
|
|
|
|
type Config struct {
|
|
DomainsOfInterest []string
|
|
Proxy struct {
|
|
Port int
|
|
}
|
|
Dump struct {
|
|
OutputDir string
|
|
}
|
|
}
|
|
|
|
type ProxyServer struct {
|
|
config *Config
|
|
tlsConfig *tls.Config
|
|
server *http.Server
|
|
originalProxy string
|
|
}
|
|
|
|
// testMode can be set via build flags for UTF-8 testing
|
|
var testMode string
|
|
|
|
func main() {
|
|
// Parse command line flags
|
|
var testConnectivity = flag.Bool("test", false, "Test proxy connectivity")
|
|
flag.Parse()
|
|
|
|
// Set console to UTF-8 on Windows to prevent garbled text
|
|
if runtime.GOOS == "windows" {
|
|
setConsoleUTF8()
|
|
}
|
|
|
|
fmt.Println("Starting MITM proxy server...")
|
|
|
|
// If in test mode, output UTF-8 test characters and exit
|
|
if testMode == "true" {
|
|
fmt.Println("========================================")
|
|
fmt.Println("UTF-8 Encoding Test")
|
|
fmt.Println("========================================")
|
|
fmt.Println("Testing Unicode characters:")
|
|
fmt.Println("English: Hello World")
|
|
fmt.Println("Chinese: 你好世界")
|
|
fmt.Println("Japanese: こんにちは世界")
|
|
fmt.Println("Korean: 안녕하세요 세계")
|
|
fmt.Println("Symbols: ✓ ✗ ★ ♥ ◆ ▲")
|
|
fmt.Println("")
|
|
fmt.Println("If you can see the above characters correctly,")
|
|
fmt.Println("UTF-8 encoding is working properly in the MITM proxy.")
|
|
fmt.Println("========================================")
|
|
return
|
|
}
|
|
|
|
// Load configuration
|
|
config, err := loadConfig("config.toml")
|
|
if err != nil {
|
|
log.Fatalf("Failed to load configuration: %v", err)
|
|
}
|
|
|
|
// Create output directory
|
|
if err := os.MkdirAll(config.Dump.OutputDir, 0755); err != nil {
|
|
log.Fatalf("Failed to create output directory: %v", err)
|
|
}
|
|
|
|
// Create proxy server
|
|
proxy, err := NewProxyServer(config)
|
|
if err != nil {
|
|
log.Fatalf("Failed to create proxy server: %v", err)
|
|
}
|
|
|
|
// Install CA certificate
|
|
if err := proxy.installCACert(); err != nil {
|
|
log.Printf("Failed to install CA certificate: %v", err)
|
|
} else {
|
|
fmt.Println("✓ CA certificate installed successfully")
|
|
}
|
|
|
|
// Set system proxy
|
|
if err := proxy.setSystemProxy(); err != nil {
|
|
log.Fatalf("Failed to set system proxy: %v", err)
|
|
}
|
|
fmt.Printf("✓ System proxy set to 127.0.0.1:%d\n", config.Proxy.Port)
|
|
|
|
// Verify proxy settings were applied
|
|
if err := proxy.verifyProxySettings(); err != nil {
|
|
fmt.Printf("⚠️ Warning: Could not verify proxy settings: %v\n", err)
|
|
} else {
|
|
fmt.Printf("✓ Proxy settings verified in Windows registry\n")
|
|
}
|
|
|
|
// Show current proxy configuration for debugging
|
|
proxy.showProxyConfiguration()
|
|
|
|
// Start proxy server
|
|
serverStarted := make(chan error, 1)
|
|
go func() {
|
|
fmt.Printf("🚀 Starting proxy server on port %d...\n", config.Proxy.Port)
|
|
if err := proxy.Start(); err != nil && err != http.ErrServerClosed {
|
|
serverStarted <- err
|
|
return
|
|
}
|
|
serverStarted <- nil
|
|
}()
|
|
|
|
// Wait for server to start or fail
|
|
fmt.Printf("⏳ Waiting for server to start...\n")
|
|
select {
|
|
case err := <-serverStarted:
|
|
if err != nil {
|
|
log.Fatalf("❌ Failed to start proxy server: %v", err)
|
|
}
|
|
fmt.Printf("✅ Proxy server successfully started on port %d\n", config.Proxy.Port)
|
|
case <-time.After(5 * time.Second):
|
|
fmt.Printf("✅ Proxy server appears to be starting (no immediate errors)\n")
|
|
}
|
|
|
|
// Test basic connectivity first
|
|
fmt.Printf("🔍 Testing basic proxy connectivity...\n")
|
|
if err := testBasicConnectivity(config.Proxy.Port); err != nil {
|
|
fmt.Printf("⚠️ Basic connectivity test failed: %v\n", err)
|
|
fmt.Printf("💡 Try running diagnose_proxy.bat for detailed diagnostics\n")
|
|
} else {
|
|
fmt.Printf("✅ Basic proxy connectivity test passed\n")
|
|
}
|
|
|
|
// Run full connectivity test if requested
|
|
if *testConnectivity {
|
|
proxyAddr := fmt.Sprintf("127.0.0.1:%d", config.Proxy.Port)
|
|
testProxyConnectivity(proxyAddr)
|
|
}
|
|
|
|
// Wait for interrupt signal
|
|
sigChan := make(chan os.Signal, 1)
|
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
|
<-sigChan
|
|
|
|
fmt.Println("\n🛑 Shutting down proxy server...")
|
|
proxy.Shutdown()
|
|
fmt.Println("✓ Proxy server closed")
|
|
}
|
|
|
|
func loadConfig(filename string) (*Config, error) {
|
|
return parseConfig(filename)
|
|
}
|
|
|
|
func NewProxyServer(config *Config) (*ProxyServer, error) {
|
|
// Load hardcoded P12 certificate
|
|
tlsConfig, err := loadHardcodedCertificate()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load certificate: %v", err)
|
|
}
|
|
|
|
proxy := &ProxyServer{
|
|
config: config,
|
|
tlsConfig: tlsConfig,
|
|
}
|
|
|
|
// Create HTTP server with enhanced logging
|
|
// IMPORTANT: Use a custom handler that properly handles CONNECT requests
|
|
proxy.server = &http.Server{
|
|
Addr: fmt.Sprintf(":%d", config.Proxy.Port),
|
|
Handler: proxy, // Use the proxy itself as the handler
|
|
// Add some server configuration for better debugging
|
|
ErrorLog: log.New(os.Stdout, "HTTP-SERVER: ", log.LstdFlags),
|
|
}
|
|
|
|
return proxy, nil
|
|
}
|
|
|
|
func (p *ProxyServer) Start() error {
|
|
fmt.Printf("🔧 Starting HTTP server on %s\n", p.server.Addr)
|
|
|
|
// Test if port is available
|
|
listener, err := net.Listen("tcp", p.server.Addr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to bind to port %s: %v", p.server.Addr, err)
|
|
}
|
|
|
|
fmt.Printf("✅ Successfully bound to port %s\n", p.server.Addr)
|
|
|
|
// Start serving with our listener
|
|
return p.server.Serve(listener)
|
|
}
|
|
|
|
func (p *ProxyServer) Shutdown() {
|
|
// Restore system proxy settings
|
|
if err := p.restoreSystemProxy(); err != nil {
|
|
log.Printf("Failed to restore system proxy: %v", err)
|
|
} else {
|
|
fmt.Println("✓ System proxy restored")
|
|
}
|
|
|
|
// Close HTTP server
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
p.server.Shutdown(ctx)
|
|
}
|
|
|
|
func (p *ProxyServer) createTLSConfigForHost(host string) *tls.Config {
|
|
// Create a TLS config that generates certificates on-demand
|
|
config := &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
// Generate a certificate specifically for this host
|
|
return p.generateCertificateForHost(info.ServerName)
|
|
},
|
|
}
|
|
return config
|
|
}
|
|
|
|
// generateCertificateForHost creates a certificate for a specific hostname
|
|
func (p *ProxyServer) generateCertificateForHost(hostname string) (*tls.Certificate, error) {
|
|
// Use the CA certificate and key from our base config
|
|
if len(p.tlsConfig.Certificates) == 0 {
|
|
return nil, fmt.Errorf("no CA certificate available")
|
|
}
|
|
|
|
caCert := p.tlsConfig.Certificates[0]
|
|
|
|
// Parse the CA certificate
|
|
caCertParsed, err := x509.ParseCertificate(caCert.Certificate[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse CA certificate: %v", err)
|
|
}
|
|
|
|
// Generate a new private key for the host certificate
|
|
hostPrivateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate host private key: %v", err)
|
|
}
|
|
|
|
// Create certificate template for the specific host
|
|
template := x509.Certificate{
|
|
SerialNumber: big.NewInt(time.Now().UnixNano()),
|
|
Subject: pkix.Name{
|
|
Organization: []string{"MITM Proxy"},
|
|
Country: []string{"US"},
|
|
CommonName: hostname,
|
|
},
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().Add(365 * 24 * time.Hour),
|
|
// Proper key usage for server certificate
|
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{
|
|
x509.ExtKeyUsageServerAuth,
|
|
},
|
|
BasicConstraintsValid: true,
|
|
IsCA: false,
|
|
}
|
|
|
|
// Add the hostname to DNS names
|
|
template.DNSNames = []string{hostname}
|
|
|
|
// If hostname is an IP address, add it to IP addresses
|
|
if ip := net.ParseIP(hostname); ip != nil {
|
|
template.IPAddresses = []net.IP{ip}
|
|
}
|
|
|
|
// Generate the certificate signed by our CA
|
|
certDER, err := x509.CreateCertificate(rand.Reader, &template, caCertParsed, &hostPrivateKey.PublicKey, caCert.PrivateKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create certificate for %s: %v", hostname, err)
|
|
}
|
|
|
|
// Create the TLS certificate
|
|
cert := &tls.Certificate{
|
|
Certificate: [][]byte{certDER},
|
|
PrivateKey: hostPrivateKey,
|
|
}
|
|
|
|
return cert, nil
|
|
}
|
|
|
|
// ServeHTTP implements http.Handler interface to properly handle all requests including CONNECT
|
|
func (p *ProxyServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
// Handle health check endpoint
|
|
if r.URL.Path == "/proxy-health" {
|
|
fmt.Printf("🏥 Health check request from %s\n", r.RemoteAddr)
|
|
w.Header().Set("Content-Type", "text/plain")
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("MITM Proxy is running"))
|
|
return
|
|
}
|
|
|
|
// Handle all other requests through our main handler
|
|
p.handleHTTP(w, r)
|
|
}
|
|
|
|
func (p *ProxyServer) handleHTTP(w http.ResponseWriter, r *http.Request) {
|
|
// Log all incoming requests for debugging with timestamp
|
|
timestamp := time.Now().Format("15:04:05")
|
|
fmt.Printf("[%s] 📥 INCOMING: %s %s %s from %s\n", timestamp, r.Method, r.Host, r.URL.Path, r.RemoteAddr)
|
|
fmt.Printf("[%s] 📋 Headers: %d headers received\n", timestamp, len(r.Header))
|
|
|
|
// Log ALL headers for debugging proxy issues
|
|
for name, values := range r.Header {
|
|
for _, value := range values {
|
|
fmt.Printf("[%s] 📋 Header: %s: %s\n", timestamp, name, value)
|
|
}
|
|
}
|
|
|
|
// Log the complete URL being requested
|
|
fmt.Printf("[%s] 🌐 Full URL: %s\n", timestamp, r.URL.String())
|
|
|
|
// Check if this is actually an HTTPS request being sent as HTTP
|
|
if r.URL.Scheme == "https" {
|
|
fmt.Printf("[%s] ⚠️ HTTPS URL received as HTTP request - browser may not be using CONNECT\n", timestamp)
|
|
}
|
|
|
|
if r.Method == http.MethodConnect {
|
|
fmt.Printf("[%s] 🔐 Processing HTTPS CONNECT for %s\n", timestamp, r.Host)
|
|
p.handleHTTPS(w, r)
|
|
return
|
|
}
|
|
|
|
// Handle HTTP request
|
|
fmt.Printf("[%s] 🌐 Processing HTTP request for %s\n", timestamp, r.Host)
|
|
p.handleHTTPRequest(w, r)
|
|
}
|
|
|
|
func (p *ProxyServer) handleHTTPS(w http.ResponseWriter, r *http.Request) {
|
|
timestamp := time.Now().Format("15:04:05")
|
|
fmt.Printf("[%s] 🔐 HTTPS CONNECT: Starting for %s from %s\n", timestamp, r.Host, r.RemoteAddr)
|
|
|
|
// Get the hijacker before sending any response
|
|
hijacker, ok := w.(http.Hijacker)
|
|
if !ok {
|
|
fmt.Printf("[%s] ❌ HTTPS CONNECT: Hijacking not supported for %s\n", timestamp, r.Host)
|
|
http.Error(w, "Hijacking not supported", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
fmt.Printf("[%s] ✅ HTTPS CONNECT: Hijacker obtained for %s\n", timestamp, r.Host)
|
|
|
|
// Hijack the connection first
|
|
clientConn, _, err := hijacker.Hijack()
|
|
if err != nil {
|
|
fmt.Printf("[%s] ❌ HTTPS CONNECT: Hijack failed for %s: %v\n", timestamp, r.Host, err)
|
|
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
defer func() {
|
|
fmt.Printf("[%s] 🔌 HTTPS CONNECT: Closing client connection for %s\n", timestamp, r.Host)
|
|
clientConn.Close()
|
|
}()
|
|
fmt.Printf("[%s] ✅ HTTPS CONNECT: Connection hijacked for %s\n", timestamp, r.Host)
|
|
|
|
// Send 200 Connection established response manually
|
|
connectResponse := "HTTP/1.1 200 Connection established\r\n\r\n"
|
|
written, err := clientConn.Write([]byte(connectResponse))
|
|
if err != nil {
|
|
fmt.Printf("[%s] ❌ HTTPS CONNECT: Failed to send response to %s: %v\n", timestamp, r.Host, err)
|
|
return
|
|
}
|
|
fmt.Printf("[%s] ✅ HTTPS CONNECT: Sent %d bytes response to %s\n", timestamp, written, r.Host)
|
|
|
|
// Give the client a moment to process the CONNECT response
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Try MITM first - if it fails, fall back to transparent proxy
|
|
fmt.Printf("[%s] 🔍 HTTPS CONNECT: Attempting MITM for %s\n", timestamp, r.Host)
|
|
if !p.attemptMITM(clientConn, r.Host) {
|
|
fmt.Printf("[%s] ⚠️ HTTPS CONNECT: MITM failed for %s, falling back to transparent proxy\n", timestamp, r.Host)
|
|
p.handleHTTPSTransparent(clientConn, r.Host)
|
|
}
|
|
}
|
|
|
|
// attemptMITM tries to perform MITM decryption, returns true if successful
|
|
func (p *ProxyServer) attemptMITM(clientConn net.Conn, host string) bool {
|
|
timestamp := time.Now().Format("15:04:05")
|
|
fmt.Printf("[%s] 🔍 MITM: Attempting MITM for %s\n", timestamp, host)
|
|
|
|
// First establish connection to target server
|
|
fmt.Printf("[%s] 🌐 MITM: Connecting to target %s\n", timestamp, host)
|
|
destConn, err := tls.Dial("tcp", host, &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
})
|
|
if err != nil {
|
|
fmt.Printf("[%s] ❌ MITM: Cannot connect to target %s with TLS: %v\n", timestamp, host, err)
|
|
return false
|
|
}
|
|
defer destConn.Close()
|
|
fmt.Printf("[%s] ✅ MITM: Connected to target %s\n", timestamp, host)
|
|
|
|
// Create TLS config for client connection
|
|
fmt.Printf("[%s] 🔧 MITM: Creating TLS config for client connection to %s\n", timestamp, host)
|
|
tlsConfig := p.createTLSConfigForHost(host)
|
|
|
|
// Wrap client connection with our certificate
|
|
fmt.Printf("[%s] 🔐 MITM: Wrapping client connection with our certificate for %s\n", timestamp, host)
|
|
tlsClientConn := tls.Server(clientConn, tlsConfig)
|
|
defer tlsClientConn.Close()
|
|
|
|
// Set handshake timeout
|
|
fmt.Printf("[%s] ⏰ MITM: Setting TLS handshake timeout for %s\n", timestamp, host)
|
|
tlsClientConn.SetDeadline(time.Now().Add(10 * time.Second))
|
|
|
|
// Try TLS handshake with client
|
|
fmt.Printf("[%s] 🤝 MITM: Starting TLS handshake with client for %s\n", timestamp, host)
|
|
if err := tlsClientConn.Handshake(); err != nil {
|
|
fmt.Printf("[%s] ❌ MITM: TLS handshake failed for %s: %v\n", timestamp, host, err)
|
|
return false
|
|
}
|
|
|
|
// Clear deadline after successful handshake
|
|
tlsClientConn.SetDeadline(time.Time{})
|
|
fmt.Printf("[%s] ✅ MITM: TLS handshake successful for %s - decrypting traffic\n", timestamp, host)
|
|
|
|
// Create channels for bidirectional copying
|
|
done := make(chan struct{}, 2)
|
|
|
|
// Copy data bidirectionally with decryption
|
|
go func() {
|
|
defer func() {
|
|
fmt.Printf("[%s] 📤 MITM: Response copying finished for %s\n", timestamp, host)
|
|
done <- struct{}{}
|
|
}()
|
|
p.copyAndDumpDecrypted(destConn, tlsClientConn, host, "response")
|
|
}()
|
|
|
|
go func() {
|
|
defer func() {
|
|
fmt.Printf("[%s] 📥 MITM: Request copying finished for %s\n", timestamp, host)
|
|
done <- struct{}{}
|
|
}()
|
|
p.copyAndDumpDecrypted(tlsClientConn, destConn, host, "request")
|
|
}()
|
|
|
|
// Wait for one direction to close
|
|
<-done
|
|
fmt.Printf("[%s] 🔚 MITM: Connection to %s closed\n", timestamp, host)
|
|
return true
|
|
}
|
|
|
|
// handleHTTPSTransparent handles transparent HTTPS proxy (when TLS decryption fails)
|
|
func (p *ProxyServer) handleHTTPSTransparent(clientConn net.Conn, host string) {
|
|
timestamp := time.Now().Format("15:04:05")
|
|
fmt.Printf("[%s] 🔄 TRANSPARENT: Starting transparent proxy for %s\n", timestamp, host)
|
|
|
|
// Set connection timeout
|
|
clientConn.SetDeadline(time.Now().Add(30 * time.Second))
|
|
defer clientConn.SetDeadline(time.Time{})
|
|
|
|
// Establish connection to target server
|
|
fmt.Printf("[%s] 🌐 TRANSPARENT: Connecting to target %s\n", timestamp, host)
|
|
destConn, err := net.DialTimeout("tcp", host, 10*time.Second)
|
|
if err != nil {
|
|
fmt.Printf("[%s] ❌ TRANSPARENT: Failed to connect to target server %s: %v\n", timestamp, host, err)
|
|
return
|
|
}
|
|
defer destConn.Close()
|
|
fmt.Printf("[%s] ✅ TRANSPARENT: Connected to target %s\n", timestamp, host)
|
|
|
|
// Set timeout for destination connection
|
|
destConn.SetDeadline(time.Now().Add(30 * time.Second))
|
|
defer destConn.SetDeadline(time.Time{})
|
|
|
|
fmt.Printf("[%s] 🔗 TRANSPARENT: Established transparent proxy connection to %s\n", timestamp, host)
|
|
|
|
// Create channels to handle connection closing
|
|
done := make(chan struct{}, 2)
|
|
|
|
// Transparently proxy encrypted data
|
|
go func() {
|
|
defer func() {
|
|
fmt.Printf("[%s] 📤 TRANSPARENT: Response copying finished for %s\n", timestamp, host)
|
|
done <- struct{}{}
|
|
}()
|
|
p.copyAndDump(destConn, clientConn, host, "response")
|
|
}()
|
|
|
|
go func() {
|
|
defer func() {
|
|
fmt.Printf("[%s] 📥 TRANSPARENT: Request copying finished for %s\n", timestamp, host)
|
|
done <- struct{}{}
|
|
}()
|
|
p.copyAndDump(clientConn, destConn, host, "request")
|
|
}()
|
|
|
|
// Wait for one direction to close, then close both connections
|
|
<-done
|
|
fmt.Printf("[%s] 🔚 TRANSPARENT: Proxy connection to %s closed\n", timestamp, host)
|
|
}
|
|
|
|
func (p *ProxyServer) handleHTTPRequest(w http.ResponseWriter, r *http.Request) {
|
|
// Build the complete target URL
|
|
targetURL := r.URL
|
|
if targetURL.Scheme == "" {
|
|
targetURL.Scheme = "http"
|
|
}
|
|
if targetURL.Host == "" {
|
|
targetURL.Host = r.Host
|
|
}
|
|
|
|
// Read request body for dumping
|
|
var reqBody []byte
|
|
if r.Body != nil {
|
|
var err error
|
|
reqBody, err = io.ReadAll(r.Body)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
r.Body = io.NopCloser(bytes.NewReader(reqBody))
|
|
}
|
|
|
|
// Create new request with exact same properties
|
|
proxyReq, err := http.NewRequest(r.Method, targetURL.String(), bytes.NewReader(reqBody))
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Copy ALL headers exactly as-is (completely transparent)
|
|
proxyReq.Header = make(http.Header)
|
|
for name, values := range r.Header {
|
|
proxyReq.Header[name] = values
|
|
}
|
|
|
|
// Create a completely transparent HTTP client
|
|
client := &http.Client{
|
|
Transport: &http.Transport{
|
|
TLSClientConfig: &tls.Config{
|
|
InsecureSkipVerify: true, // For MITM purposes
|
|
},
|
|
DisableKeepAlives: false,
|
|
DisableCompression: true, // Keep original compression
|
|
MaxIdleConns: 100,
|
|
IdleConnTimeout: 90 * time.Second,
|
|
TLSHandshakeTimeout: 10 * time.Second,
|
|
ExpectContinueTimeout: 1 * time.Second,
|
|
},
|
|
// IMPORTANT: Don't interfere with redirects - let client handle them
|
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
|
return http.ErrUseLastResponse
|
|
},
|
|
Timeout: 30 * time.Second,
|
|
}
|
|
|
|
// Make the request
|
|
resp, err := client.Do(proxyReq)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// Read response body for dumping
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Dump traffic for inspection
|
|
p.dumpTraffic(r, reqBody, respBody, r.Host)
|
|
|
|
// Print traffic if domain is of interest
|
|
if p.isDomainOfInterest(r.Host) {
|
|
p.printTraffic(r, reqBody, respBody)
|
|
}
|
|
|
|
// Copy ALL response headers exactly as-is (completely transparent)
|
|
for name, values := range resp.Header {
|
|
w.Header()[name] = values
|
|
}
|
|
|
|
// Write the exact response status and body
|
|
w.WriteHeader(resp.StatusCode)
|
|
w.Write(respBody)
|
|
}
|
|
|
|
func (p *ProxyServer) copyAndDump(dst io.Writer, src io.Reader, host, direction string) {
|
|
buffer := make([]byte, 32768) // Increased buffer size
|
|
var allData []byte
|
|
totalBytes := 0
|
|
|
|
for {
|
|
n, err := src.Read(buffer)
|
|
if n > 0 {
|
|
data := buffer[:n]
|
|
written, writeErr := dst.Write(data)
|
|
if writeErr != nil {
|
|
fmt.Printf("Write error in %s direction for %s: %v\n", direction, host, writeErr)
|
|
break
|
|
}
|
|
if written != n {
|
|
fmt.Printf("Incomplete write in %s direction for %s: wrote %d of %d bytes\n", direction, host, written, n)
|
|
}
|
|
allData = append(allData, data...)
|
|
totalBytes += n
|
|
}
|
|
if err != nil {
|
|
if err != io.EOF {
|
|
fmt.Printf("Read error in %s direction for %s: %v\n", direction, host, err)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
// Dump encrypted HTTPS traffic
|
|
if len(allData) > 0 {
|
|
p.dumpHTTPSTraffic(host, direction+"_encrypted", allData)
|
|
|
|
// If domain is of interest, print to stdout
|
|
if p.isDomainOfInterest(host) {
|
|
fmt.Printf("\n=== HTTPS %s Encrypted Traffic: %s ===\n", direction, host)
|
|
fmt.Printf("Data length: %d bytes\n", totalBytes)
|
|
// Display first 256 bytes of encrypted data (hexadecimal format)
|
|
displayLength := len(allData)
|
|
if displayLength > 256 {
|
|
displayLength = 256
|
|
}
|
|
fmt.Printf("Encrypted data content (first %d bytes): %x\n", displayLength, allData[:displayLength])
|
|
fmt.Println("==========================================")
|
|
}
|
|
}
|
|
}
|
|
|
|
// copyAndDumpDecrypted copies and dumps decrypted data
|
|
func (p *ProxyServer) copyAndDumpDecrypted(dst io.Writer, src io.Reader, host, direction string) {
|
|
buffer := make([]byte, 32768) // Increased buffer size
|
|
var allData []byte
|
|
totalBytes := 0
|
|
|
|
for {
|
|
n, err := src.Read(buffer)
|
|
if n > 0 {
|
|
data := buffer[:n]
|
|
written, writeErr := dst.Write(data)
|
|
if writeErr != nil {
|
|
fmt.Printf("Write error in decrypted %s direction for %s: %v\n", direction, host, writeErr)
|
|
break
|
|
}
|
|
if written != n {
|
|
fmt.Printf("Incomplete write in decrypted %s direction for %s: wrote %d of %d bytes\n", direction, host, written, n)
|
|
}
|
|
allData = append(allData, data...)
|
|
totalBytes += n
|
|
}
|
|
if err != nil {
|
|
if err != io.EOF {
|
|
fmt.Printf("Read error in decrypted %s direction for %s: %v\n", direction, host, err)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
// Dump decrypted HTTPS traffic
|
|
if len(allData) > 0 {
|
|
p.dumpHTTPSTraffic(host, direction+"_decrypted", allData)
|
|
|
|
// If domain is of interest, print to stdout
|
|
if p.isDomainOfInterest(host) {
|
|
fmt.Printf("\n=== HTTPS %s Decrypted Traffic: %s ===\n", direction, host)
|
|
fmt.Printf("Data length: %d bytes\n", totalBytes)
|
|
// Display complete decrypted data (limit to first 2KB for readability)
|
|
displayLength := len(allData)
|
|
if displayLength > 2048 {
|
|
displayLength = 2048
|
|
}
|
|
fmt.Printf("Decrypted data content (first %d bytes): %s\n", displayLength, string(allData[:displayLength]))
|
|
fmt.Println("==========================================")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *ProxyServer) dumpTraffic(req *http.Request, reqBody, respBody []byte, host string) {
|
|
timestamp := time.Now().Format("20060102_150405")
|
|
filename := fmt.Sprintf("%s_%s.txt", timestamp, sanitizeFilename(host))
|
|
filepath := filepath.Join(p.config.Dump.OutputDir, filename)
|
|
|
|
file, err := os.Create(filepath)
|
|
if err != nil {
|
|
log.Printf("Failed to create dump file: %v", err)
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
// Write request information
|
|
fmt.Fprintf(file, "=== REQUEST ===\n")
|
|
fmt.Fprintf(file, "%s %s %s\n", req.Method, req.URL.String(), req.Proto)
|
|
fmt.Fprintf(file, "Host: %s\n", req.Host)
|
|
for name, values := range req.Header {
|
|
for _, value := range values {
|
|
fmt.Fprintf(file, "%s: %s\n", name, value)
|
|
}
|
|
}
|
|
fmt.Fprintf(file, "\n")
|
|
|
|
// Write request body
|
|
if len(reqBody) > 0 {
|
|
fmt.Fprintf(file, "%s\n", string(reqBody))
|
|
}
|
|
|
|
// Write response information
|
|
fmt.Fprintf(file, "\n=== RESPONSE ===\n")
|
|
fmt.Fprintf(file, "%s\n", string(respBody))
|
|
}
|
|
|
|
func (p *ProxyServer) dumpHTTPSTraffic(host, direction string, data []byte) {
|
|
timestamp := time.Now().Format("20060102_150405")
|
|
filename := fmt.Sprintf("%s_%s_%s.bin", timestamp, sanitizeFilename(host), direction)
|
|
filepath := filepath.Join(p.config.Dump.OutputDir, filename)
|
|
|
|
file, err := os.Create(filepath)
|
|
if err != nil {
|
|
log.Printf("Failed to create HTTPS dump file: %v", err)
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
file.Write(data)
|
|
}
|
|
|
|
func (p *ProxyServer) isDomainOfInterest(host string) bool {
|
|
// Remove port number
|
|
if colonIndex := strings.Index(host, ":"); colonIndex != -1 {
|
|
host = host[:colonIndex]
|
|
}
|
|
|
|
for _, domain := range p.config.DomainsOfInterest {
|
|
if strings.Contains(host, domain) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (p *ProxyServer) printTraffic(req *http.Request, reqBody, respBody []byte) {
|
|
fmt.Printf("\n=== Traffic from Domain of Interest: %s ===\n", req.Host)
|
|
fmt.Printf("Request: %s %s\n", req.Method, req.URL.String())
|
|
|
|
// Print request headers
|
|
fmt.Println("Request Headers:")
|
|
for name, values := range req.Header {
|
|
for _, value := range values {
|
|
fmt.Printf(" %s: %s\n", name, value)
|
|
}
|
|
}
|
|
|
|
// Print complete request body
|
|
if len(reqBody) > 0 {
|
|
fmt.Printf("Request Body (%d bytes): %s\n", len(reqBody), string(reqBody))
|
|
}
|
|
|
|
// Print complete response body
|
|
if len(respBody) > 0 {
|
|
fmt.Printf("Response Body (%d bytes): %s\n", len(respBody), string(respBody))
|
|
}
|
|
fmt.Println("==========================================")
|
|
}
|
|
|
|
func sanitizeFilename(filename string) string {
|
|
// Replace unsafe characters
|
|
replacer := strings.NewReplacer(
|
|
":", "_",
|
|
"/", "_",
|
|
"\\", "_",
|
|
"*", "_",
|
|
"?", "_",
|
|
"\"", "_",
|
|
"<", "_",
|
|
">", "_",
|
|
"|", "_",
|
|
)
|
|
return replacer.Replace(filename)
|
|
}
|
|
|
|
func (p *ProxyServer) setSystemProxy() error {
|
|
// Get current proxy settings
|
|
k, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Internet Settings`, registry.QUERY_VALUE)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer k.Close()
|
|
|
|
// Read current ProxyServer settings
|
|
currentProxy, _, err := k.GetStringValue("ProxyServer")
|
|
if err == nil {
|
|
p.originalProxy = currentProxy
|
|
}
|
|
|
|
// Set new proxy
|
|
k, err = registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Internet Settings`, registry.SET_VALUE)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer k.Close()
|
|
|
|
proxyAddr := fmt.Sprintf("127.0.0.1:%d", p.config.Proxy.Port)
|
|
|
|
// Enable proxy
|
|
if err := k.SetDWordValue("ProxyEnable", 1); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Set proxy server address for BOTH HTTP and HTTPS
|
|
// Format: "http=proxy:port;https=proxy:port" or just "proxy:port" for both
|
|
if err := k.SetStringValue("ProxyServer", proxyAddr); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Ensure no proxy bypass for local addresses
|
|
if err := k.SetStringValue("ProxyOverride", "<local>"); err != nil {
|
|
// This is not critical, continue anyway
|
|
fmt.Printf("Warning: Could not set ProxyOverride: %v\n", err)
|
|
}
|
|
|
|
// Refresh IE settings and notify system
|
|
return p.refreshIESettings()
|
|
}
|
|
|
|
func (p *ProxyServer) restoreSystemProxy() error {
|
|
k, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Internet Settings`, registry.SET_VALUE)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer k.Close()
|
|
|
|
// Disable proxy
|
|
if err := k.SetDWordValue("ProxyEnable", 0); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Restore original proxy settings
|
|
if p.originalProxy != "" {
|
|
if err := k.SetStringValue("ProxyServer", p.originalProxy); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Refresh IE settings
|
|
return p.refreshIESettings()
|
|
}
|
|
|
|
func (p *ProxyServer) refreshIESettings() error {
|
|
// Multiple methods to ensure Windows recognizes the proxy change
|
|
|
|
// Method 1: Reset WinHTTP proxy
|
|
cmd1 := exec.Command("cmd", "/c", "netsh winhttp reset proxy")
|
|
if err := cmd1.Run(); err != nil {
|
|
fmt.Printf("Warning: netsh winhttp reset failed: %v\n", err)
|
|
}
|
|
|
|
// Method 2: Import IE proxy settings to WinHTTP
|
|
cmd2 := exec.Command("cmd", "/c", "netsh winhttp import proxy source=ie")
|
|
if err := cmd2.Run(); err != nil {
|
|
fmt.Printf("Warning: netsh winhttp import failed: %v\n", err)
|
|
}
|
|
|
|
// Method 3: Force refresh Internet Options
|
|
cmd3 := exec.Command("cmd", "/c", "rundll32.exe inetcpl.cpl,ClearMyTracksByProcess 8")
|
|
if err := cmd3.Run(); err != nil {
|
|
fmt.Printf("Warning: Internet Options refresh failed: %v\n", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *ProxyServer) verifyProxySettings() error {
|
|
k, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Internet Settings`, registry.QUERY_VALUE)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open registry key: %v", err)
|
|
}
|
|
defer k.Close()
|
|
|
|
// Check if proxy is enabled
|
|
proxyEnable, _, err := k.GetIntegerValue("ProxyEnable")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read ProxyEnable: %v", err)
|
|
}
|
|
if proxyEnable != 1 {
|
|
return fmt.Errorf("proxy is not enabled (ProxyEnable = %d)", proxyEnable)
|
|
}
|
|
|
|
// Check proxy server setting
|
|
proxyServer, _, err := k.GetStringValue("ProxyServer")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read ProxyServer: %v", err)
|
|
}
|
|
|
|
expectedProxy := fmt.Sprintf("127.0.0.1:%d", p.config.Proxy.Port)
|
|
if proxyServer != expectedProxy {
|
|
return fmt.Errorf("proxy server mismatch: expected %s, got %s", expectedProxy, proxyServer)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *ProxyServer) showProxyConfiguration() {
|
|
fmt.Printf("🔍 Current Proxy Configuration:\n")
|
|
|
|
// Show Windows proxy settings
|
|
k, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Internet Settings`, registry.QUERY_VALUE)
|
|
if err != nil {
|
|
fmt.Printf(" ❌ Could not read proxy settings: %v\n", err)
|
|
return
|
|
}
|
|
defer k.Close()
|
|
|
|
if proxyEnable, _, err := k.GetIntegerValue("ProxyEnable"); err == nil {
|
|
fmt.Printf(" ProxyEnable: %d\n", proxyEnable)
|
|
}
|
|
|
|
if proxyServer, _, err := k.GetStringValue("ProxyServer"); err == nil {
|
|
fmt.Printf(" ProxyServer: %s\n", proxyServer)
|
|
}
|
|
|
|
if proxyOverride, _, err := k.GetStringValue("ProxyOverride"); err == nil {
|
|
fmt.Printf(" ProxyOverride: %s\n", proxyOverride)
|
|
}
|
|
|
|
// Show WinHTTP proxy settings
|
|
fmt.Printf(" WinHTTP Proxy Settings:\n")
|
|
cmd := exec.Command("cmd", "/c", "netsh winhttp show proxy")
|
|
if output, err := cmd.CombinedOutput(); err == nil {
|
|
fmt.Printf(" %s\n", string(output))
|
|
} else {
|
|
fmt.Printf(" ❌ Could not get WinHTTP proxy settings: %v\n", err)
|
|
}
|
|
}
|
|
|
|
func (p *ProxyServer) installCACert() error {
|
|
// Use hardcoded CA certificate data
|
|
certData := []byte(getHardcodedCACert())
|
|
|
|
// Parse certificate
|
|
var cert *x509.Certificate
|
|
var err error
|
|
|
|
// Try parsing DER format directly
|
|
cert, err = x509.ParseCertificate(certData)
|
|
if err != nil {
|
|
// Try parsing PEM format
|
|
block, _ := pem.Decode(certData)
|
|
if block == nil {
|
|
return fmt.Errorf("unable to parse certificate file")
|
|
}
|
|
cert, err = x509.ParseCertificate(block.Bytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Write certificate to temporary file
|
|
tempFile, err := os.CreateTemp("", "ca_cert_*.crt")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tempFileName := tempFile.Name()
|
|
defer os.Remove(tempFileName)
|
|
|
|
if err := pem.Encode(tempFile, &pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: cert.Raw,
|
|
}); err != nil {
|
|
tempFile.Close()
|
|
return err
|
|
}
|
|
|
|
// Close the file before certutil accesses it to avoid sharing violation
|
|
tempFile.Close()
|
|
|
|
// Use certutil to install certificate to trusted root certificate authorities
|
|
cmd := exec.Command("certutil", "-addstore", "-f", "Root", tempFileName)
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to install certificate: %v, output: %s", err, string(output))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// setConsoleUTF8 sets the Windows console to UTF-8 code page to prevent garbled text
|
|
func setConsoleUTF8() {
|
|
// Set console input and output code page to UTF-8 (65001)
|
|
cmd := exec.Command("cmd", "/c", "chcp 65001 >nul")
|
|
cmd.Run()
|
|
}
|
|
|
|
// testBasicConnectivity tests if the proxy server is responding to basic HTTP requests
|
|
func testBasicConnectivity(port int) error {
|
|
client := &http.Client{
|
|
Timeout: 5 * time.Second,
|
|
}
|
|
|
|
healthURL := fmt.Sprintf("http://127.0.0.1:%d/proxy-health", port)
|
|
resp, err := client.Get(healthURL)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to connect to proxy health endpoint: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("proxy health check returned status %d", resp.StatusCode)
|
|
}
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read health check response: %v", err)
|
|
}
|
|
|
|
if !strings.Contains(string(body), "MITM Proxy is running") {
|
|
return fmt.Errorf("unexpected health check response: %s", string(body))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// testProxyConnectivity tests the proxy with simple HTTP and HTTPS requests
|
|
func testProxyConnectivity(proxyAddr string) {
|
|
fmt.Println("🧪 Testing proxy connectivity...")
|
|
|
|
// Create proxy URL
|
|
proxyURL, err := url.Parse(fmt.Sprintf("http://%s", proxyAddr))
|
|
if err != nil {
|
|
fmt.Printf("❌ Failed to parse proxy URL: %v\n", err)
|
|
return
|
|
}
|
|
|
|
// Create HTTP client with proxy
|
|
transport := &http.Transport{
|
|
Proxy: http.ProxyURL(proxyURL),
|
|
TLSClientConfig: &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
},
|
|
}
|
|
client := &http.Client{
|
|
Transport: transport,
|
|
Timeout: 30 * time.Second,
|
|
}
|
|
|
|
// Test HTTP request
|
|
fmt.Println("🌐 Testing HTTP request...")
|
|
testHTTPRequest(client, "http://httpbin.org/ip")
|
|
|
|
// Test HTTPS request
|
|
fmt.Println("🔒 Testing HTTPS request...")
|
|
testHTTPSRequest(client, "https://httpbin.org/ip")
|
|
|
|
// Test redirects (should be transparent)
|
|
fmt.Println("🔄 Testing HTTP redirect...")
|
|
testRedirectTransparency(client, "http://httpbin.org/redirect/1")
|
|
|
|
// Test domain that might cause issues
|
|
fmt.Println("🌍 Testing Google...")
|
|
testHTTPSRequest(client, "https://www.google.com")
|
|
}
|
|
|
|
func testHTTPRequest(client *http.Client, testURL string) {
|
|
resp, err := client.Get(testURL)
|
|
if err != nil {
|
|
fmt.Printf("❌ HTTP test failed: %v\n", err)
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
fmt.Printf("❌ Failed to read HTTP response: %v\n", err)
|
|
return
|
|
}
|
|
|
|
fmt.Printf("✅ HTTP test successful: %s\n", resp.Status)
|
|
fmt.Printf(" Response: %s\n", string(body)[:min(len(body), 200)])
|
|
}
|
|
|
|
func testHTTPSRequest(client *http.Client, testURL string) {
|
|
resp, err := client.Get(testURL)
|
|
if err != nil {
|
|
fmt.Printf("❌ HTTPS test failed: %v\n", err)
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
fmt.Printf("❌ Failed to read HTTPS response: %v\n", err)
|
|
return
|
|
}
|
|
|
|
fmt.Printf("✅ HTTPS test successful: %s\n", resp.Status)
|
|
fmt.Printf(" Response: %s\n", string(body)[:min(len(body), 200)])
|
|
}
|
|
|
|
func testRedirectTransparency(client *http.Client, testURL string) {
|
|
// Create a client that doesn't follow redirects to test transparency
|
|
testClient := &http.Client{
|
|
Transport: client.Transport,
|
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
|
return http.ErrUseLastResponse
|
|
},
|
|
Timeout: client.Timeout,
|
|
}
|
|
|
|
resp, err := testClient.Get(testURL)
|
|
if err != nil {
|
|
fmt.Printf("❌ Redirect transparency test failed: %v\n", err)
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode >= 300 && resp.StatusCode < 400 {
|
|
location := resp.Header.Get("Location")
|
|
fmt.Printf("✅ Redirect transparency test successful: %s -> %s\n", resp.Status, location)
|
|
} else {
|
|
fmt.Printf("⚠️ Expected redirect, got: %s (this may be normal)\n", resp.Status)
|
|
}
|
|
}
|
|
|
|
func min(a, b int) int {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|