package main import ( "bytes" "context" "crypto/tls" "crypto/x509" "database/sql" "encoding/pem" "flag" "fmt" "io" "log" "net" "net/http" "net/url" "os" "os/exec" "os/signal" "path/filepath" "runtime" "strings" "syscall" "time" "github.com/elazarl/goproxy" _ "github.com/mattn/go-sqlite3" // cgo 版(极致性能) "golang.org/x/sys/windows/registry" ) type Config struct { DomainsOfInterest []string `toml:"domains_of_interest"` DictFile string `toml:"dict_file"` Proxy struct { Port int `toml:"port"` } `toml:"proxy"` Dump struct { OutputDir string `toml:"output_dir"` DOIDir string `toml:"DOI_dir"` Enabled bool `toml:"Enabled"` } `toml:"dump"` ASR struct { ReplacePercentage int `toml:"replace_percentage"` } `toml:"asr"` } type UserData struct { TxTime time.Time RxTime time.Time RequestBody []byte ModifiedBody []byte ResponseBody []byte } type ProxyServer struct { config *Config tlsConfig *tls.Config proxy *goproxy.ProxyHttpServer server *http.Server originalProxy string dumper *Batcher verbose bool debugMode bool } func main() { // Parse command line flags var testConnectivity = flag.Bool("test", false, "Test proxy connectivity") var verbose = flag.Bool("v", false, "Verbose mode - log more information") var debugMode = flag.Bool("d", false, "Debug mode - dump modified requests/responses") flag.Parse() // Set console to UTF-8 on Windows to prevent garbled text if runtime.GOOS == "windows" { setConsoleUTF8() } fmt.Println("MITM Proxy Server v2.0") // Load configuration config, err := loadConfig("config.toml") if err != nil { log.Fatalf("Failed to load configuration: %v", err) } log.Println(config.String()) fmt.Printf("Reading dictionary from %s...\n", config.DictFile) dict, err := NewChineseDict(config.DictFile) if err != nil { log.Fatalf("Failed to load dictionary: %v", err) } Dict = dict fmt.Printf("Dictionary loaded successfully, size=%d\n", Dict.GetCharacterCount()) fmt.Println("Starting MITM proxy server...") // Create output directories if err := os.MkdirAll(config.Dump.OutputDir, 0755); err != nil { log.Fatalf("Failed to create output directory: %v", err) } if config.Dump.DOIDir != "" { if err := os.MkdirAll(config.Dump.DOIDir, 0755); err != nil { log.Fatalf("Failed to create DOI directory: %v", err) } } // create db var batcher *Batcher = nil if config.Dump.Enabled { filename := filepath.Join(config.Dump.OutputDir, time.Now().Format("20060102.150405.000000000")+".db") db, err := sql.Open("sqlite3", "file:"+filename+"?_busy_timeout=5000&_journal_mode=WAL") if err != nil { panic(err) } defer db.Close() b, err := NewBatcher(db, 1000, // maxRows:一次最多 1000 行 2<<20, // maxBytes:~2MB 触发 200*time.Millisecond, // flushEvery:最多等 200ms ) if err != nil { panic(err) } batcher = b defer batcher.Close() } // Create proxy server proxy, err := NewProxyServer(config, *verbose, *debugMode, batcher) 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") } // 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("\nShutting down proxy server...") proxy.Shutdown() fmt.Println("Proxy server closed") if batcher != nil { if err := batcher.Err(); err != nil { fmt.Println("background error:", err) } } } func loadConfig(filename string) (*Config, error) { return parseConfig(filename) } func NewProxyServer(config *Config, verbose bool, debugMode bool, batcher *Batcher) (*ProxyServer, error) { // Load hardcoded P12 certificate for MITM tlsConfig, err := loadHardcodedCertificate() if err != nil { return nil, fmt.Errorf("failed to load certificate: %v", err) } // Create goproxy instance goProxy := goproxy.NewProxyHttpServer() goProxy.Verbose = verbose ps := &ProxyServer{ config: config, tlsConfig: tlsConfig, proxy: goProxy, verbose: verbose, debugMode: debugMode, dumper: batcher, } // Configure MITM for HTTPS traffic ps.setupMITM() // Setup request/response handlers ps.setupHandlers() // Create HTTP server ps.server = &http.Server{ Addr: fmt.Sprintf(":%d", config.Proxy.Port), Handler: ps.proxy, ErrorLog: log.New(os.Stdout, "GOPROXY-SERVER: ", log.LstdFlags), } return ps, nil } // setupMITM configures HTTPS MITM using goproxy func (p *ProxyServer) setupMITM() { // Set the CA certificate for MITM if len(p.tlsConfig.Certificates) > 0 { cert := p.tlsConfig.Certificates[0] goproxy.GoproxyCa = cert goproxy.OkConnect = &goproxy.ConnectAction{Action: goproxy.ConnectMitm, TLSConfig: goproxy.TLSConfigFromCA(&cert)} } // Enable MITM for all HTTPS connections p.proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm) } // setupHandlers configures request/response handlers for logging and filtering func (p *ProxyServer) setupHandlers() { // Add health check endpoint p.proxy.OnRequest().DoFunc(func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { if r.Host == "127.0.0.1:"+fmt.Sprintf("%d", p.config.Proxy.Port) && r.URL.Path == "/proxy-health" { return r, goproxy.NewResponse(r, goproxy.ContentTypeText, http.StatusOK, "MITM Proxy is running") } return r, nil }) // Log all HTTP requests and capture request body p.proxy.OnRequest().DoFunc(func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { if !((p.isDomainOfInterest(r.Host) && p.debugMode) || p.config.Dump.Enabled) { return r, nil } // Read request body once and recreate it for both dumping and forwarding if r.Body != nil { reqBody, err := io.ReadAll(r.Body) if err != nil { log.Printf("Failed to read request body: %v", err) return r, nil } r.Body.Close() var newReqBody []byte = nil if p.isDomainOfInterest(r.Host) { newReqBody, err = asrResultObfuscate(r, reqBody, p.config.ASR.ReplacePercentage) if err != nil && err.Error() != "not an asr request" { log.Printf("Failed to obfuscate request body: %v", err) } if !p.debugMode && err == nil { log.Println("[INFO] ASR Request Body Modified") } } if newReqBody != nil { r.Body = io.NopCloser(bytes.NewReader(newReqBody)) r.ContentLength = int64(len(newReqBody)) } else { // Recreate the request body so it can be forwarded to the server r.Body = io.NopCloser(bytes.NewReader(reqBody)) r.ContentLength = int64(len(reqBody)) } // Store request body in context for later use in response handler ctx.UserData = UserData{ TxTime: time.Now(), RequestBody: reqBody, ModifiedBody: newReqBody, } } else { ctx.UserData = UserData{ TxTime: time.Now(), } } return r, nil }) // Log all HTTP responses and dump traffic p.proxy.OnResponse().DoFunc(func(r *http.Response, ctx *goproxy.ProxyCtx) *http.Response { if !p.debugMode && !p.config.Dump.Enabled { return r } // Get request body from context (if available) var userData UserData if ctx.UserData != nil { if u, ok := ctx.UserData.(UserData); ok { u.RxTime = time.Now() userData = u } else { // There is no userdata, which mean the traffic should not be captured return r } } if p.verbose || p.isDomainOfInterest(ctx.Req.Host) { fmt.Printf( "[%s][INFO][Interest=%v] HTTP Response: %s %s\n", time.Now().Format("20060102T15:04:05.000000"), p.isDomainOfInterest(ctx.Req.Host), r.Status, ctx.Req.URL.String(), ) } if r != nil { // Read response body once and recreate it for both dumping and returning if r.Body != nil { respBody, err := io.ReadAll(r.Body) if err != nil { log.Printf("Failed to read response body: %v", err) return r } r.Body.Close() // Recreate the response body so it can be returned to client r.Body = io.NopCloser(bytes.NewReader(respBody)) r.ContentLength = int64(len(respBody)) userData.ResponseBody = respBody } } // Dump traffic to file with both request and response bodies // Only dump if verbose mode is enabled OR if the request was modified if p.debugMode { p.dumpHTTPTrafficWithBodies(ctx.Req, r, userData) } if p.config.Dump.Enabled { p.dumpHTTPTrafficWithBodiesToSQL(ctx.Req, r, userData) } return r }) } 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) } // dumpHTTPTrafficWithBodies dumps HTTP request and response with both bodies to file // It now should only be called in debug mode. Logging the traffic that is modified func (p *ProxyServer) dumpHTTPTrafficWithBodies(req *http.Request, resp *http.Response, userData UserData) { file, err := os.Create(p.getFilePath(req)) 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) // Write all request headers 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(userData.RequestBody) > 0 { fmt.Fprintf(file, "%s\n", string(userData.RequestBody)) } if userData.ModifiedBody != nil { fmt.Fprintf(file, "\n=== MODIFIED REQUEST BODY ===\n") fmt.Fprintf(file, "%s\n", string(userData.ModifiedBody)) } // Write response information if resp != nil { fmt.Fprintf(file, "\n=== RESPONSE ===\n") fmt.Fprintf(file, "%s %s\n", resp.Proto, resp.Status) // Write all response headers for name, values := range resp.Header { for _, value := range values { fmt.Fprintf(file, "%s: %s\n", name, value) } } fmt.Fprintf(file, "\n") // Write response body if len(userData.ResponseBody) > 0 { fmt.Fprintf(file, "%s\n", string(userData.ResponseBody)) } } } // dumpHTTPTrafficWithBodiesToSQL logs the traffic to SQLite DB. It only writes what is actually sent, // which mean, if a request is modified, then it will write the modified request. func (p *ProxyServer) dumpHTTPTrafficWithBodiesToSQL(req *http.Request, resp *http.Response, userData UserData) { record := LogRow{ TSns: time.Now().UnixNano(), TxTime: userData.TxTime, Proto: req.Proto, Method: req.Method, URL: req.URL.String(), Modified: userData.ModifiedBody != nil, RxTime: time.Now(), } if record.Modified { record.TxBody = string(userData.ModifiedBody) } else { record.TxBody = string(userData.RequestBody) } // Write all request headers for name, values := range req.Header { for _, value := range values { record.TxHeader += fmt.Sprintf("%s: %s\n", name, value) } } // Write response information if resp != nil { record.Status = resp.StatusCode // Write all response headers for name, values := range resp.Header { for _, value := range values { record.RxHeader += fmt.Sprintf("%s: %s\n", name, value) } } record.RxBody = string(userData.ResponseBody) } if err := p.dumper.Write(record); err != nil { log.Printf("Failed to write record: %v", err) } } func (p *ProxyServer) getFilePath(req *http.Request) string { timestamp := time.Now().Format("20060102.150405.000000000") filename := fmt.Sprintf("%s_%s.txt", timestamp, sanitizeFilename(req.URL.String())) if p.isDomainOfInterest(req.Host) { return filepath.Join(p.config.Dump.DOIDir, filename) } return filepath.Join(p.config.Dump.OutputDir, filename) } // Keep the isDomainOfInterest method as it's still needed 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 } // sanitizeFilename replaces unsafe characters for file names func sanitizeFilename(filename string) string { // Replace unsafe characters replacer := strings.NewReplacer( ":", "_", "/", "_", "\\", "_", "*", "_", "?", "_", "\"", "_", "<", "_", ">", "_", "|", "_", ) s := replacer.Replace(filename) if len(s) > 80 { return s[:80] } return s } 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", ""); 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() } // 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 }