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)