From aab4830e7a330e36dd71c81ba16a8063bd037be4 Mon Sep 17 00:00:00 2001 From: shadowy-pycoder <35629483+shadowy-pycoder@users.noreply.github.com> Date: Mon, 21 Jul 2025 06:34:10 +0300 Subject: [PATCH] added arp spoofer --- README.md | 28 +++++++-- cmd/gohpts/cli.go | 17 ++---- go.mod | 8 ++- go.sum | 17 +++++- gohpts.go | 142 ++++++++++++++++++++++++++++++---------------- version.go | 2 +- 6 files changed, 146 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index e4cfd8d..eb4828d 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,9 @@ Specify http server in proxy configuration of Postman - **Traffic sniffing**\ Proxy is able to parse HTTP headers and TLS handshake metadata +- **ARP spoofing**\ + Proxy entire subnets with ARP spoofing approach + - **DNS Leak Protection**\ DNS resolution occurs on SOCKS5 server side. @@ -98,7 +101,7 @@ You can download the binary for your platform from [Releases](https://github.com Example: ```shell -HPTS_RELEASE=v1.8.5; 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.9.0; 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): @@ -165,7 +168,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) + -arpspoof Enable ARP spoof proxy for selected targets (Example: "targets 10.0.0.1,10.0.0.5-10,192.168.1.*,192.168.10.0/24;fullduplex false;debug true") -mark Set mark for each packet sent through transparent proxy (Default: redirect 0, tproxy 100) ``` @@ -483,13 +486,28 @@ fi ### ARP spoofing -`GoHPTS` can be used with tools like [Bettercap](https://github.com/bettercap/bettercap) to proxy ARP spoofed traffic. +`GoHPTS` has in-built ARP spoofer that can be used to make all TCP talking devices of your LAN to use proxy server to connect to the Internet. +This is achieved by adding `-arpspoof` flag with couple of parameters, separated by semicolon. + +Example: + +```shell +ssh remote -D 1080 -Nf +sudo env PATH=$PATH gohpts -d -T 8888 -M tproxy -sniff -body -auto -mark 100 -arpspoof "targets 192.168.10.0/24;fullduplex true;debug true" +``` + +Proxy will scan for devices in subnet `192.168.10.0/24` and send them ARP packets to pretend to be a gateway, if `fullduplex` is true, +proxy will send ARP packets to gateway as well to make it believe our proxy has each IP on the subnet. + +After proxy is stopped with `Ctrl+C`, it will automatically unspoof all targets. + +`GoHPTS` can also be used with tools like [Bettercap](https://github.com/bettercap/bettercap) to proxy ARP spoofed traffic. -Run the proxy with `-arp` flag +Run the proxy: ```shell ssh remote -D 1080 -Nf -sudo env PATH=$PATH gohpts -d -T 8888 -M tproxy -sniff -body -auto -mark 100 -arp +sudo env PATH=$PATH gohpts -d -T 8888 -M tproxy -sniff -body -auto -mark 100 ``` Run `bettercap` with this command (see [documentation](https://www.bettercap.org/)): diff --git a/cmd/gohpts/cli.go b/cmd/gohpts/cli.go index 444e7ae..43102a8 100644 --- a/cmd/gohpts/cli.go +++ b/cmd/gohpts/cli.go @@ -61,7 +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) + -arpspoof Enable ARP spoof proxy for selected targets (Example: "targets 10.0.0.1,10.0.0.5-10,192.168.1.*,192.168.10.0/24;fullduplex false;debug true") -mark Set mark for each packet sent through transparent proxy (Default: redirect 0, tproxy 100) ` @@ -114,11 +114,11 @@ 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.ARPSpoof, + "arpspoof", + "", + "Enable ARP spoof proxy for selected targets", ) } flags.StringVar(&conf.LogFilePath, "logfile", "", "Log file path (Default: stdout)") @@ -179,11 +179,6 @@ 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/go.mod b/go.mod index ef79865..d0c5ea3 100644 --- a/go.mod +++ b/go.mod @@ -7,13 +7,19 @@ require ( github.com/google/uuid v1.6.0 github.com/rs/zerolog v1.34.0 github.com/shadowy-pycoder/colors v0.0.1 - github.com/shadowy-pycoder/mshark v0.0.6 + github.com/shadowy-pycoder/mshark v0.0.7 golang.org/x/net v0.40.0 golang.org/x/sys v0.33.0 golang.org/x/term v0.32.0 ) require ( + github.com/josharian/native v1.1.0 // indirect + github.com/malfunkt/iprange v0.9.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mdlayher/packet v1.1.2 // indirect + github.com/mdlayher/socket v0.4.1 // indirect + github.com/pkg/errors v0.9.1 // indirect + golang.org/x/sync v0.16.0 // indirect ) diff --git a/go.sum b/go.sum index bbf4cf1..1f7e8dd 100644 --- a/go.sum +++ b/go.sum @@ -4,13 +4,24 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= +github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/malfunkt/iprange v0.9.0 h1:VCs0PKLUPotNVQTpVNszsut4lP7OCGNBwX+lOYBrnVQ= +github.com/malfunkt/iprange v0.9.0/go.mod h1:TRGqO/f95gh3LOndUGTL46+W0GXA91WTqyZ0Quwvt4U= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY= +github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4= +github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= +github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -19,12 +30,14 @@ github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= github.com/shadowy-pycoder/colors v0.0.1 h1:weCj/YIOupqy4BSP8KuVzr20fC+cuAv/tArz7bhhkP4= github.com/shadowy-pycoder/colors v0.0.1/go.mod h1:lkrJS1PY2oVigNLTT6pkbF7B/v0YcU2LD5PZnss1Q4U= -github.com/shadowy-pycoder/mshark v0.0.6 h1:XmIoj9+uHEwc8RmiPT2iMYEdTvyiJ+zBTRBhIkx9JTg= -github.com/shadowy-pycoder/mshark v0.0.6/go.mod h1:Txx0p8JxYOGd+0V+6N9MeCUGtGdfHAATWE8KB1nd7H0= +github.com/shadowy-pycoder/mshark v0.0.7 h1:iuCLxKXh0HhukrZOQIbXjGneTTOZSPIS0mkIBSJm/4U= +github.com/shadowy-pycoder/mshark v0.0.7/go.mod h1:FqbHFdsx0zMnrZZH0+oPzaFcleP4O+tUWv8i5gxo87k= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/gohpts.go b/gohpts.go index 69b2e64..195d6c4 100644 --- a/gohpts.go +++ b/gohpts.go @@ -34,6 +34,7 @@ import ( "github.com/google/uuid" "github.com/rs/zerolog" "github.com/shadowy-pycoder/colors" + "github.com/shadowy-pycoder/mshark/arpspoof" "github.com/shadowy-pycoder/mshark/layers" "golang.org/x/net/proxy" ) @@ -66,6 +67,7 @@ var ( credsPattern = regexp.MustCompile( `(?i)(?:"|')?(username|user|login|email|password|pass|pwd)(?:"|')?\s*[:=]\s*(?:"|')?([^\s"'&]+)`, ) + macPattern = regexp.MustCompile(`(?i)([a-z0-9_]+_[0-9a-f]{2}(?::[0-9a-f]{2}){2}|(?:[0-9a-f]{2}[:-]){5}[0-9a-f]{2})`) ) // Hop-by-hop headers @@ -97,7 +99,7 @@ type Config struct { TProxyMode string Auto bool Mark uint - ARP bool + ARPSpoof string LogFilePath string Debug bool JSON bool @@ -121,7 +123,7 @@ type proxyapp struct { tproxyMode string auto bool mark uint - arp bool + arpspoofer *arpspoof.ARPSpoofer user string pass string proxychain chain @@ -1396,52 +1398,48 @@ 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?") - } + cmdClearForward := 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 + `) + cmdClearForward.Stdout = os.Stdout + cmdClearForward.Stderr = os.Stderr + if err := cmdClearForward.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") + } + cmdForwardFilter := 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)) + cmdForwardFilter.Stdout = os.Stdout + cmdForwardFilter.Stderr = os.Stderr + if err := cmdForwardFilter.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?") - } + 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 { @@ -1481,6 +1479,9 @@ func (p *proxyapp) Run() { quit := make(chan os.Signal, 1) p.closeConn = make(chan bool) signal.Notify(quit, os.Interrupt) + if p.arpspoofer != nil { + go p.arpspoofer.Start() + } var tproxyServer *tproxyServer if p.tproxyAddr != "" { tproxyServer = newTproxyServer(p) @@ -1508,6 +1509,12 @@ func (p *proxyapp) Run() { if p.httpServer != nil { go func() { <-quit + if p.arpspoofer != nil { + err := p.arpspoofer.Stop() + if err != nil { + p.logger.Error().Err(err).Msg("Failed stopping arp spoofer") + } + } if p.auto { err := p.clearRedirectRules(output) if err != nil { @@ -1550,6 +1557,12 @@ func (p *proxyapp) Run() { } else { go func() { <-quit + if p.arpspoofer != nil { + err := p.arpspoofer.Stop() + if err != nil { + p.logger.Error().Err(err).Msg("Failed stopping arp spoofer") + } + } if p.auto { err := p.clearRedirectRules(output) if err != nil { @@ -1706,6 +1719,9 @@ func New(conf *Config) *proxyapp { result = domainPattern.ReplaceAllStringFunc(result, func(match string) string { return colors.Yellow(match).String() }) + result = macPattern.ReplaceAllStringFunc(result, func(match string) string { + return colors.Yellow(match).String() + }) return result } @@ -1819,11 +1835,41 @@ 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") + if conf.ARPSpoof != "" { + if runtime.GOOS != "linux" { + p.logger.Fatal().Msg("ARP spoof setup is available only for linux system") + } + if !p.auto { + p.logger.Warn().Msg("ARP spoof setup requires iptables configuration") + } + asc := &arpspoof.ARPSpoofConfig{Logger: p.logger} + errMsg := `Failed parsing arp options. Example: "targets 10.0.0.1,10.0.0.5-10,192.168.1.*,192.168.10.0/24;fullduplex false;debug true"` + for opt := range strings.SplitSeq(strings.ToLower(conf.ARPSpoof), ";") { + keyval := strings.SplitN(strings.Trim(opt, " "), " ", 2) + if len(keyval) < 2 { + p.logger.Fatal().Msg(errMsg) + } + key := keyval[0] + val := keyval[1] + switch key { + case "targets": + asc.Targets = val + case "fullduplex": + if val == "true" { + asc.FullDuplex = true + } + case "debug": + if val == "true" { + asc.Debug = true + } + default: + p.logger.Fatal().Msg(errMsg) + } + } + p.arpspoofer, err = arpspoof.NewARPSpoofer(asc) + if err != nil { + p.logger.Fatal().Err(err).Msg("Failed creating arp spoofer") + } } var addrHTTP, addrSOCKS, certFile, keyFile string if conf.ServerConfPath != "" { diff --git a/version.go b/version.go index a0240c4..1edd6b6 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package gohpts -const Version string = "gohpts v1.8.5" +const Version string = "gohpts v1.9.0"