new: implemented tcp.proxy (closes #33)

This commit is contained in:
evilsocket 2018-02-23 09:27:19 +01:00
parent 0f8be49beb
commit 129f87f8f9
7 changed files with 410 additions and 78 deletions

View file

@ -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))

View file

@ -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
}

View file

@ -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)

View file

@ -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()

View file

@ -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)
}

214
modules/tcp_proxy.go Normal file
View file

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

View file

@ -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
}