mitm/main.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
}