package tcp_proxy import ( "fmt" "io" "net" "sync" "github.com/bettercap/bettercap/v2/firewall" "github.com/bettercap/bettercap/v2/session" "github.com/robertkrimen/otto" ) type TcpProxy struct { session.SessionModule Redirection *firewall.Redirection localAddr *net.TCPAddr remoteAddr *net.TCPAddr tunnelAddr *net.TCPAddr listener *net.TCPListener script *TcpProxyScript } func NewTcpProxy(s *session.Session) *TcpProxy { mod := &TcpProxy{ SessionModule: session.NewSessionModule("tcp.proxy", s), } mod.AddParam(session.NewIntParameter("tcp.port", "443", "Remote port to redirect when the TCP proxy is activated.")) mod.AddParam(session.NewStringParameter("tcp.address", "", session.IPv4Validator, "Remote address of the TCP proxy.")) mod.AddParam(session.NewStringParameter("tcp.proxy.address", session.ParamIfaceAddress, session.IPv4Validator, "Address to bind the TCP proxy to.")) mod.AddParam(session.NewIntParameter("tcp.proxy.port", "8443", "Port to bind the TCP proxy to.")) mod.AddParam(session.NewStringParameter("tcp.proxy.script", "", "", "Path of a TCP proxy JS script.")) mod.AddParam(session.NewStringParameter("tcp.tunnel.address", "", "", "Address to redirect the TCP tunnel to (optional).")) mod.AddParam(session.NewIntParameter("tcp.tunnel.port", "0", "Port to redirect the TCP tunnel to (optional).")) mod.AddHandler(session.NewModuleHandler("tcp.proxy on", "", "Start TCP proxy.", func(args []string) error { return mod.Start() })) mod.AddHandler(session.NewModuleHandler("tcp.proxy off", "", "Stop TCP proxy.", func(args []string) error { return mod.Stop() })) return mod } func (mod *TcpProxy) Name() string { return "tcp.proxy" } func (mod *TcpProxy) Description() string { return "A full featured TCP proxy and tunnel, all TCP traffic to a given remote address and port will be redirected to it." } func (mod *TcpProxy) Author() string { return "Simone Margaritelli " } func (mod *TcpProxy) Configure() error { var err error var port int var proxyPort int var address string var proxyAddress string var scriptPath string var tunnelAddress string var tunnelPort int if mod.Running() { return session.ErrAlreadyStarted(mod.Name()) } else if err, address = mod.StringParam("tcp.address"); err != nil { return err } else if err, proxyAddress = mod.StringParam("tcp.proxy.address"); err != nil { return err } else if err, proxyPort = mod.IntParam("tcp.proxy.port"); err != nil { return err } else if err, port = mod.IntParam("tcp.port"); err != nil { return err } else if err, tunnelAddress = mod.StringParam("tcp.tunnel.address"); err != nil { return err } else if err, tunnelPort = mod.IntParam("tcp.tunnel.port"); err != nil { return err } else if err, scriptPath = mod.StringParam("tcp.proxy.script"); err != nil { return err } else if mod.localAddr, err = net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", proxyAddress, proxyPort)); err != nil { return err } else if mod.remoteAddr, err = net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", address, port)); err != nil { return err } else if mod.tunnelAddr, err = net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", tunnelAddress, tunnelPort)); err != nil { return err } else if mod.listener, err = net.ListenTCP("tcp", mod.localAddr); err != nil { return err } if scriptPath != "" { if err, mod.script = LoadTcpProxyScript(scriptPath, mod.Session); err != nil { return err } else { mod.Debug("script %s loaded.", scriptPath) } } if !mod.Session.Firewall.IsForwardingEnabled() { mod.Info("enabling forwarding.") mod.Session.Firewall.EnableForwarding(true) } mod.Redirection = firewall.NewRedirection(mod.Session.Interface.Name(), "TCP", port, proxyAddress, proxyPort) mod.Redirection.SrcAddress = address if err := mod.Session.Firewall.EnableRedirection(mod.Redirection, true); err != nil { return err } mod.Debug("applied redirection %s", mod.Redirection.String()) return nil } func (mod *TcpProxy) doPipe(from, to net.Addr, src *net.TCPConn, dst io.ReadWriter, wg *sync.WaitGroup) { defer wg.Done() buff := make([]byte, 0xffff) for { n, err := src.Read(buff) if err != nil { if err.Error() != "EOF" { mod.Warning("read failed: %s", err) } return } b := buff[:n] if mod.script != nil { ret := mod.script.OnData(from, to, b, func(call otto.FunctionCall) otto.Value { mod.Debug("onData dropCallback called") src.Close() return otto.Value{} }) if ret != nil { nret := len(ret) mod.Info("overriding %d bytes of data from %s to %s with %d bytes of new data.", n, from.String(), to.String(), nret) b = make([]byte, nret) copy(b, ret) } } n, err = dst.Write(b) if err != nil { mod.Warning("write failed: %s", err) return } mod.Debug("%s -> %s : %d bytes", from.String(), to.String(), n) } } func (mod *TcpProxy) handleConnection(c *net.TCPConn) { defer c.Close() mod.Info("got a connection from %s", c.RemoteAddr().String()) // tcp tunnel enabled if mod.tunnelAddr.IP.To4() != nil { mod.Info("tcp tunnel started ( %s -> %s )", mod.remoteAddr.String(), mod.tunnelAddr.String()) mod.remoteAddr = mod.tunnelAddr } remote, err := net.DialTCP("tcp", nil, mod.remoteAddr) if err != nil { mod.Warning("error while connecting to remote %s: %s", mod.remoteAddr.String(), err) return } defer remote.Close() wg := sync.WaitGroup{} wg.Add(2) // start pipeing go mod.doPipe(c.RemoteAddr(), mod.remoteAddr, c, remote, &wg) go mod.doPipe(mod.remoteAddr, c.RemoteAddr(), remote, c, &wg) wg.Wait() } func (mod *TcpProxy) Start() error { if err := mod.Configure(); err != nil { return err } return mod.SetRunning(true, func() { mod.Info("started ( x -> %s -> %s )", mod.localAddr.String(), mod.remoteAddr.String()) for mod.Running() { conn, err := mod.listener.AcceptTCP() if err != nil { mod.Warning("error while accepting TCP connection: %s", err) continue } go mod.handleConnection(conn) } }) } func (mod *TcpProxy) Stop() error { if mod.Redirection != nil { mod.Debug("disabling redirection %s", mod.Redirection.String()) if err := mod.Session.Firewall.EnableRedirection(mod.Redirection, false); err != nil { return err } mod.Redirection = nil } return mod.SetRunning(false, func() { mod.listener.Close() }) }