bettercap/modules/tcp_proxy/tcp_proxy.go
2024-08-09 18:19:21 +02:00

257 lines
6.3 KiB
Go

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 <evilsocket@gmail.com>"
}
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()
})
}