diff --git a/main.go b/main.go index 8cc5942a..1b4a3497 100644 --- a/main.go +++ b/main.go @@ -47,6 +47,7 @@ func main() { sess.Register(modules.NewHttpServer(sess)) sess.Register(modules.NewHttpProxy(sess)) sess.Register(modules.NewHttpsProxy(sess)) + sess.Register(modules.NewTcpProxy(sess)) sess.Register(modules.NewRestAPI(sess)) sess.Register(modules.NewWOL(sess)) sess.Register(modules.NewWiFiRecon(sess)) diff --git a/modules/http_proxy_script_builtins.go b/modules/base_proxy_script.go similarity index 51% rename from modules/http_proxy_script_builtins.go rename to modules/base_proxy_script.go index fc86ca9c..4cee92d8 100644 --- a/modules/http_proxy_script_builtins.go +++ b/modules/base_proxy_script.go @@ -2,8 +2,10 @@ package modules import ( "io/ioutil" + "sync" "github.com/bettercap/bettercap/log" + "github.com/bettercap/bettercap/session" "github.com/robertkrimen/otto" ) @@ -15,7 +17,68 @@ func errOtto(format string, args ...interface{}) otto.Value { return nullOtto } -// define functions available to proxy scripts +type ProxyScript struct { + sync.Mutex + + Path string + Source string + VM *otto.Otto + + sess *session.Session + cbCacheLock *sync.Mutex + cbCache map[string]bool +} + +func LoadProxyScriptSource(path, source string, sess *session.Session) (err error, s *ProxyScript) { + s = &ProxyScript{ + Path: path, + Source: source, + VM: otto.New(), + sess: sess, + cbCacheLock: &sync.Mutex{}, + cbCache: make(map[string]bool), + } + + // this will define callbacks and global objects + _, err = s.VM.Run(s.Source) + if err != nil { + return + } + + // define session pointer + err = s.VM.Set("env", sess.Env.Data) + if err != nil { + log.Error("Error while defining environment: %s", err) + return + } + + err = s.defineBuiltins() + if err != nil { + log.Error("Error while defining builtin functions: %s", err) + return + } + + // run onLoad if defined + if s.hasCallback("onLoad") { + _, err = s.VM.Run("onLoad()") + if err != nil { + log.Error("Error while executing onLoad callback: %s", err) + return + } + } + + return +} + +func LoadProxyScript(path string, sess *session.Session) (err error, s *ProxyScript) { + raw, err := ioutil.ReadFile(path) + if err != nil { + return + } + + return LoadProxyScriptSource(path, string(raw), sess) +} + func (s *ProxyScript) defineBuiltins() error { // used to read a file ... doh s.VM.Set("readFile", func(call otto.FunctionCall) otto.Value { @@ -76,3 +139,23 @@ func (s *ProxyScript) defineBuiltins() error { return nil } + +func (s *ProxyScript) hasCallback(name string) bool { + s.cbCacheLock.Lock() + defer s.cbCacheLock.Unlock() + + // check the cache + has, found := s.cbCache[name] + if found == false { + // check the VM + cb, err := s.VM.Get(name) + if err == nil && cb.IsFunction() { + has = true + } else { + has = false + } + s.cbCache[name] = has + } + + return has +} diff --git a/modules/http_proxy_base.go b/modules/http_proxy_base.go index 429e231b..1d03efa3 100644 --- a/modules/http_proxy_base.go +++ b/modules/http_proxy_base.go @@ -36,7 +36,7 @@ type HTTPProxy struct { Server *http.Server Redirection *firewall.Redirection Proxy *goproxy.ProxyHttpServer - Script *ProxyScript + Script *HttpProxyScript CertFile string KeyFile string @@ -147,7 +147,7 @@ func (p *HTTPProxy) Configure(address string, proxyPort int, httpPort int, scrip p.Address = address if scriptPath != "" { - if err, p.Script = LoadProxyScript(scriptPath, p.sess); err != nil { + if err, p.Script = LoadHttpProxyScript(scriptPath, p.sess); err != nil { return err } else { log.Debug("Proxy script %s loaded.", scriptPath) diff --git a/modules/http_proxy_script.go b/modules/http_proxy_script.go index 9fd9df65..7342a3da 100644 --- a/modules/http_proxy_script.go +++ b/modules/http_proxy_script.go @@ -3,7 +3,6 @@ package modules import ( "io/ioutil" "net/http" - "sync" "github.com/bettercap/bettercap/log" "github.com/bettercap/bettercap/session" @@ -11,59 +10,22 @@ import ( "github.com/robertkrimen/otto" ) -type ProxyScript struct { - sync.Mutex - - Path string - Source string - VM *otto.Otto - - sess *session.Session +type HttpProxyScript struct { + *ProxyScript onRequestScript *otto.Script onResponseScript *otto.Script - cbCacheLock *sync.Mutex - cbCache map[string]bool } -func LoadProxyScriptSource(path, source string, sess *session.Session) (err error, s *ProxyScript) { - s = &ProxyScript{ - Path: path, - Source: source, - VM: otto.New(), +func LoadHttpProxyScriptSource(path, source string, sess *session.Session) (err error, s *HttpProxyScript) { + err, ps := LoadProxyScriptSource(path, source, sess) + if err != nil { + return + } - sess: sess, + s = &HttpProxyScript{ + ProxyScript: ps, onRequestScript: nil, onResponseScript: nil, - cbCacheLock: &sync.Mutex{}, - cbCache: make(map[string]bool), - } - - // this will define callbacks and global objects - _, err = s.VM.Run(s.Source) - if err != nil { - return - } - - // define session pointer - err = s.VM.Set("env", sess.Env.Data) - if err != nil { - log.Error("Error while defining environment: %s", err) - return - } - - err = s.defineBuiltins() - if err != nil { - log.Error("Error while defining builtin functions: %s", err) - return - } - - // run onLoad if defined - if s.hasCallback("onLoad") { - _, err = s.VM.Run("onLoad()") - if err != nil { - log.Error("Error while executing onLoad callback: %s", err) - return - } } // compile call to onRequest if defined @@ -87,7 +49,7 @@ func LoadProxyScriptSource(path, source string, sess *session.Session) (err erro return } -func LoadProxyScript(path string, sess *session.Session) (err error, s *ProxyScript) { +func LoadHttpProxyScript(path string, sess *session.Session) (err error, s *HttpProxyScript) { log.Info("Loading proxy script %s ...", path) raw, err := ioutil.ReadFile(path) @@ -95,30 +57,10 @@ func LoadProxyScript(path string, sess *session.Session) (err error, s *ProxyScr return } - return LoadProxyScriptSource(path, string(raw), sess) + return LoadHttpProxyScriptSource(path, string(raw), sess) } -func (s *ProxyScript) hasCallback(name string) bool { - s.cbCacheLock.Lock() - defer s.cbCacheLock.Unlock() - - // check the cache - has, found := s.cbCache[name] - if found == false { - // check the VM - cb, err := s.VM.Get(name) - if err == nil && cb.IsFunction() { - has = true - } else { - has = false - } - s.cbCache[name] = has - } - - return has -} - -func (s *ProxyScript) doRequestDefines(req *http.Request) (err error, jsres *JSResponse) { +func (s *HttpProxyScript) doRequestDefines(req *http.Request) (err error, jsres *JSResponse) { // convert request and define empty response to be optionally filled jsreq := NewJSRequest(req) if err = s.VM.Set("req", &jsreq); err != nil { @@ -134,7 +76,7 @@ func (s *ProxyScript) doRequestDefines(req *http.Request) (err error, jsres *JSR return } -func (s *ProxyScript) doResponseDefines(res *http.Response) (err error, jsres *JSResponse) { +func (s *HttpProxyScript) doResponseDefines(res *http.Response) (err error, jsres *JSResponse) { // convert both request and response jsreq := NewJSRequest(res.Request) if err = s.VM.Set("req", jsreq); err != nil { @@ -151,7 +93,7 @@ func (s *ProxyScript) doResponseDefines(res *http.Response) (err error, jsres *J return } -func (s *ProxyScript) OnRequest(req *http.Request) *JSResponse { +func (s *HttpProxyScript) OnRequest(req *http.Request) *JSResponse { if s.onRequestScript != nil { s.Lock() defer s.Unlock() @@ -177,7 +119,7 @@ func (s *ProxyScript) OnRequest(req *http.Request) *JSResponse { return nil } -func (s *ProxyScript) OnResponse(res *http.Response) *JSResponse { +func (s *HttpProxyScript) OnResponse(res *http.Response) *JSResponse { if s.onResponseScript != nil { s.Lock() defer s.Unlock() diff --git a/modules/http_proxy_script_test.go b/modules/http_proxy_script_test.go index ca88c8b0..ca0610ba 100644 --- a/modules/http_proxy_script_test.go +++ b/modules/http_proxy_script_test.go @@ -8,11 +8,11 @@ import ( "github.com/bettercap/bettercap/session" ) -func getScript(src string) *ProxyScript { +func getScript(src string) *HttpProxyScript { sess := session.Session{} sess.Env = session.NewEnvironment(&sess) - err, script := LoadProxyScriptSource("", src, &sess) + err, script := LoadHttpProxyScriptSource("", src, &sess) if err != nil { log.Fatal("%s", err) } diff --git a/modules/tcp_proxy.go b/modules/tcp_proxy.go new file mode 100644 index 00000000..2bceb8c2 --- /dev/null +++ b/modules/tcp_proxy.go @@ -0,0 +1,214 @@ +package modules + +import ( + "fmt" + "io" + "net" + "sync" + + "github.com/bettercap/bettercap/firewall" + "github.com/bettercap/bettercap/log" + "github.com/bettercap/bettercap/session" +) + +type TcpProxy struct { + session.SessionModule + Redirection *firewall.Redirection + localAddr *net.TCPAddr + remoteAddr *net.TCPAddr + listener *net.TCPListener + script *TcpProxyScript +} + +func NewTcpProxy(s *session.Session) *TcpProxy { + p := &TcpProxy{ + SessionModule: session.NewSessionModule("tcp.proxy", s), + } + + p.AddParam(session.NewIntParameter("tcp.port", + "443", + "Remote port to redirect when the TCP proxy is activated.")) + + p.AddParam(session.NewStringParameter("tcp.address", + "", + session.IPv4Validator, + "Remote address of the TCP proxy.")) + + p.AddParam(session.NewStringParameter("tcp.proxy.address", + session.ParamIfaceAddress, + session.IPv4Validator, + "Address to bind the TCP proxy to.")) + + p.AddParam(session.NewIntParameter("tcp.proxy.port", + "8443", + "Port to bind the TCP proxy to.")) + + p.AddParam(session.NewStringParameter("tcp.proxy.script", + "", + "", + "Path of a TCP proxy JS script.")) + + p.AddHandler(session.NewModuleHandler("tcp.proxy on", "", + "Start HTTP proxy.", + func(args []string) error { + return p.Start() + })) + + p.AddHandler(session.NewModuleHandler("tcp.proxy off", "", + "Stop HTTP proxy.", + func(args []string) error { + return p.Stop() + })) + + return p +} + +func (p *TcpProxy) Name() string { + return "tcp.proxy" +} + +func (p *TcpProxy) Description() string { + return "A full featured TCP proxy, all TCP traffic to a given remote address and port will be redirected to it." +} + +func (p *TcpProxy) Author() string { + return "Simone Margaritelli " +} + +func (p *TcpProxy) Configure() error { + var err error + var port int + var proxyPort int + var address string + var proxyAddress string + var scriptPath string + + if err, address = p.StringParam("tcp.address"); err != nil { + return err + } else if err, proxyAddress = p.StringParam("tcp.proxy.address"); err != nil { + return err + } else if err, proxyPort = p.IntParam("tcp.proxy.port"); err != nil { + return err + } else if err, port = p.IntParam("tcp.port"); err != nil { + return err + } else if err, scriptPath = p.StringParam("tcp.proxy.script"); err != nil { + return err + } else if p.localAddr, err = net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", proxyAddress, proxyPort)); err != nil { + return err + } else if p.remoteAddr, err = net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", address, port)); err != nil { + return err + } else if p.listener, err = net.ListenTCP("tcp", p.localAddr); err != nil { + return err + } + + if scriptPath != "" { + if err, p.script = LoadTcpProxyScript(scriptPath, p.Session); err != nil { + return err + } else { + log.Debug("TCP proxy script %s loaded.", scriptPath) + } + } + + if p.Session.Firewall.IsForwardingEnabled() == false { + log.Info("Enabling forwarding.") + p.Session.Firewall.EnableForwarding(true) + } + + p.Redirection = firewall.NewRedirection(p.Session.Interface.Name(), + "TCP", + port, + proxyAddress, + proxyPort) + + p.Redirection.SrcAddress = address + + if err := p.Session.Firewall.EnableRedirection(p.Redirection, true); err != nil { + return err + } + + log.Debug("Applied redirection %s", p.Redirection.String()) + + return nil +} + +func (p *TcpProxy) doPipe(from, to net.Addr, src, 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" { + log.Warning("Read failed: %s", err) + } + return + } + b := buff[:n] + + ret := p.script.OnData(from, to, b) + if ret != nil { + nret := len(ret) + log.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 { + log.Warning("Write failed: %s", err) + return + } + + log.Debug("%s -> %s : %d bytes", from.String(), to.String(), n) + } +} + +func (p *TcpProxy) handleConnection(c *net.TCPConn) { + defer c.Close() + + log.Info("TCP proxy got a connection from %s", c.RemoteAddr().String()) + + remote, err := net.DialTCP("tcp", nil, p.remoteAddr) + if err != nil { + log.Warning("Error while connecting to remote %s: %s", p.remoteAddr.String(), err) + return + } + defer remote.Close() + + wg := sync.WaitGroup{} + wg.Add(2) + + // start pipeing + go p.doPipe(c.RemoteAddr(), p.remoteAddr, c, remote, &wg) + go p.doPipe(p.remoteAddr, c.RemoteAddr(), remote, c, &wg) + + wg.Wait() +} + +func (p *TcpProxy) Start() error { + if p.Running() == true { + return session.ErrAlreadyStarted + } else if err := p.Configure(); err != nil { + return err + } + + return p.SetRunning(true, func() { + log.Info("TCP proxy started ( x -> %s -> %s )", p.localAddr, p.remoteAddr) + + for p.Running() { + conn, err := p.listener.AcceptTCP() + if err != nil { + log.Warning("Error while accepting TCP connection: %s", err) + continue + } + + go p.handleConnection(conn) + } + }) +} + +func (p *TcpProxy) Stop() error { + return p.SetRunning(false, func() { + p.listener.Close() + }) +} diff --git a/modules/tcp_proxy_script.go b/modules/tcp_proxy_script.go new file mode 100644 index 00000000..7a59a162 --- /dev/null +++ b/modules/tcp_proxy_script.go @@ -0,0 +1,92 @@ +package modules + +import ( + "io/ioutil" + "net" + "strings" + + "github.com/bettercap/bettercap/log" + "github.com/bettercap/bettercap/session" + + "github.com/robertkrimen/otto" +) + +type TcpProxyScript struct { + *ProxyScript + onDataScript *otto.Script +} + +func LoadTcpProxyScriptSource(path, source string, sess *session.Session) (err error, s *TcpProxyScript) { + err, ps := LoadProxyScriptSource(path, source, sess) + if err != nil { + return + } + + s = &TcpProxyScript{ + ProxyScript: ps, + onDataScript: nil, + } + + if s.hasCallback("onData") { + s.onDataScript, err = s.VM.Compile("", "onData(from, to, data)") + if err != nil { + log.Error("Error while compiling onData callback: %s", err) + return + } + } + + return +} + +func LoadTcpProxyScript(path string, sess *session.Session) (err error, s *TcpProxyScript) { + log.Info("Loading TCP proxy script %s ...", path) + + raw, err := ioutil.ReadFile(path) + if err != nil { + return + } + + return LoadTcpProxyScriptSource(path, string(raw), sess) +} + +func (s *TcpProxyScript) doDefines(from, to net.Addr, data []byte) (err error) { + addrFrom := strings.Split(from.String(), ":")[0] + addrTo := strings.Split(to.String(), ":")[0] + + if err = s.VM.Set("from", addrFrom); err != nil { + log.Error("Error while defining from: %s", err) + return + } else if err = s.VM.Set("to", addrTo); err != nil { + log.Error("Error while defining to: %s", err) + return + } else if err = s.VM.Set("data", string(data)); err != nil { + log.Error("Error while defining data: %s", err) + return + } + return +} + +func (s *TcpProxyScript) OnData(from, to net.Addr, data []byte) []byte { + if s.onDataScript != nil { + s.Lock() + defer s.Unlock() + + err := s.doDefines(from, to, data) + if err != nil { + log.Error("Error while running bootstrap definitions: %s", err) + return nil + } + + ret, err := s.VM.Run(s.onDataScript) + if err != nil { + log.Error("Error while executing onData callback: %s", err) + return nil + } + + if ret.IsUndefined() == false && ret.IsString() { + return []byte(ret.String()) + } + } + + return nil +}