mirror of
https://github.com/bettercap/bettercap
synced 2025-07-05 20:42:09 -07:00
yeah i should have done this before, i know
This commit is contained in:
commit
0091ffdbb3
33 changed files with 25678 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
*.cap
|
||||||
|
bettercap
|
||||||
|
net/oui_compiled.go
|
31
Makefile
Normal file
31
Makefile
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
TARGET=bettercap
|
||||||
|
BUILD_DATE=`date +%Y-%m-%d\ %H:%M`
|
||||||
|
BUILD_FILE=core/build.go
|
||||||
|
|
||||||
|
all: build
|
||||||
|
@echo "@ Done"
|
||||||
|
@echo -n "\n"
|
||||||
|
|
||||||
|
build: build_file
|
||||||
|
@echo "@ Building ..."
|
||||||
|
@go build $(FLAGS) -o $(TARGET) .
|
||||||
|
|
||||||
|
build_file: resources
|
||||||
|
@rm -f $(BUILD_FILE)
|
||||||
|
@echo "package core" > $(BUILD_FILE)
|
||||||
|
@echo "const (" >> $(BUILD_FILE)
|
||||||
|
@echo " BuildDate = \"$(BUILD_DATE)\"" >> $(BUILD_FILE)
|
||||||
|
@echo ")" >> $(BUILD_FILE)
|
||||||
|
|
||||||
|
resources:
|
||||||
|
@echo "@ Compiling resources into go files ..."
|
||||||
|
@go-bindata -o net/oui_compiled.go -pkg net net/oui.dat
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@rm -rf $(TARGET) net/oui_compiled.go
|
||||||
|
|
||||||
|
clear_arp:
|
||||||
|
@ip -s -s neigh flush all
|
||||||
|
|
||||||
|
bcast_ping:
|
||||||
|
@ping -b 255.255.255.255
|
8
core/banner.go
Normal file
8
core/banner.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
const (
|
||||||
|
Name = "bettercap"
|
||||||
|
Version = "2.0.0a"
|
||||||
|
Author = "Simone 'evilsocket' Margaritelli"
|
||||||
|
Website = "https://bettercap.org/"
|
||||||
|
)
|
4
core/build.go
Normal file
4
core/build.go
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
package core
|
||||||
|
const (
|
||||||
|
BuildDate = "2017-11-16 19:24"
|
||||||
|
)
|
25
core/core.go
Normal file
25
core/core.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/op/go-logging"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = logging.MustGetLogger("mitm")
|
||||||
|
|
||||||
|
func Exec(executable string, args []string) (string, error) {
|
||||||
|
path, err := exec.LookPath(executable)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf(DIM+"Exec( '%s %s' )"+RESET+"\n", path, strings.Join(args, " "))
|
||||||
|
raw, err := exec.Command(path, args...).CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf(" err=%s out='%s'\n", err, raw)
|
||||||
|
return "", err
|
||||||
|
} else {
|
||||||
|
return strings.Trim(string(raw), "\r\n\t "), nil
|
||||||
|
}
|
||||||
|
}
|
25
core/options.go
Normal file
25
core/options.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import "flag"
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
InterfaceName *string
|
||||||
|
Caplet *string
|
||||||
|
Debug *bool
|
||||||
|
Silent *bool
|
||||||
|
NoHistory *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseOptions() (Options, error) {
|
||||||
|
o := Options{
|
||||||
|
InterfaceName: flag.String("iface", "", "Network interface to bind to."),
|
||||||
|
Caplet: flag.String("caplet", "", "Read commands from this file instead of goin into interactive mode."),
|
||||||
|
Debug: flag.Bool("debug", false, "Print debug messages."),
|
||||||
|
Silent: flag.Bool("silent", false, "Suppress all logs which are not errors."),
|
||||||
|
NoHistory: flag.Bool("no-history", false, "Disable history file."),
|
||||||
|
}
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
return o, nil
|
||||||
|
}
|
36
core/swag.go
Normal file
36
core/swag.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
// https://misc.flogisoft.com/bash/tip_colors_and_formatting
|
||||||
|
const (
|
||||||
|
BOLD = "\033[1m"
|
||||||
|
DIM = "\033[2m"
|
||||||
|
|
||||||
|
RED = "\033[31m"
|
||||||
|
GREEN = "\033[32m"
|
||||||
|
YELLOW = "\033[33m"
|
||||||
|
|
||||||
|
RESET = "\033[0m"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ON = GREEN + "✔" + RESET
|
||||||
|
const OFF = RED + "✘" + RESET
|
||||||
|
|
||||||
|
func Bold(s string) string {
|
||||||
|
return BOLD + s + RESET
|
||||||
|
}
|
||||||
|
|
||||||
|
func Dim(s string) string {
|
||||||
|
return DIM + s + RESET
|
||||||
|
}
|
||||||
|
|
||||||
|
func Red(s string) string {
|
||||||
|
return RED + s + RESET
|
||||||
|
}
|
||||||
|
|
||||||
|
func Green(s string) string {
|
||||||
|
return GREEN + s + RESET
|
||||||
|
}
|
||||||
|
|
||||||
|
func Yellow(s string) string {
|
||||||
|
return YELLOW + s + RESET
|
||||||
|
}
|
10
firewall/firewall.go
Normal file
10
firewall/firewall.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package firewall
|
||||||
|
|
||||||
|
type FirewallManager interface {
|
||||||
|
IsForwardingEnabled() bool
|
||||||
|
EnableForwarding(enabled bool) error
|
||||||
|
EnableIcmpBcast(enabled bool) error
|
||||||
|
EnableSendRedirects(enabled bool) error
|
||||||
|
EnableRedirection(r *Redirection, enabled bool) error
|
||||||
|
Restore()
|
||||||
|
}
|
178
firewall/firewall_linux.go
Normal file
178
firewall/firewall_linux.go
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
package firewall
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/evilsocket/bettercap/core"
|
||||||
|
"github.com/op/go-logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LinuxFirewall struct {
|
||||||
|
forwarding bool
|
||||||
|
redirections map[string]*Redirection
|
||||||
|
}
|
||||||
|
|
||||||
|
var log = logging.MustGetLogger("mitm")
|
||||||
|
|
||||||
|
const (
|
||||||
|
IPV4ForwardingFile = "/proc/sys/net/ipv4/ip_forward"
|
||||||
|
IPV4ICMPBcastFile = "/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts"
|
||||||
|
IPV4SendRedirectsFile = "/proc/sys/net/ipv4/conf/all/send_redirects"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Make() FirewallManager {
|
||||||
|
firewall := &LinuxFirewall{
|
||||||
|
forwarding: false,
|
||||||
|
redirections: make(map[string]*Redirection, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
firewall.forwarding = firewall.IsForwardingEnabled()
|
||||||
|
|
||||||
|
log.Debugf("Created Linux Firewall ( forwarding=%v )\n", firewall.forwarding)
|
||||||
|
|
||||||
|
return firewall
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f LinuxFirewall) enableFeature(filename string, enable bool) error {
|
||||||
|
var value string
|
||||||
|
if enable {
|
||||||
|
value = "1"
|
||||||
|
} else {
|
||||||
|
value = "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
fd, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
|
||||||
|
if _, err = fd.WriteString(value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f LinuxFirewall) IsForwardingEnabled() bool {
|
||||||
|
|
||||||
|
if out, err := ioutil.ReadFile(IPV4ForwardingFile); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return strings.Trim(string(out), "\r\n\t ") == "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f LinuxFirewall) EnableForwarding(enabled bool) error {
|
||||||
|
return f.enableFeature(IPV4ForwardingFile, enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f LinuxFirewall) EnableIcmpBcast(enabled bool) error {
|
||||||
|
return f.enableFeature(IPV4ICMPBcastFile, enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f LinuxFirewall) EnableSendRedirects(enabled bool) error {
|
||||||
|
return f.enableFeature(IPV4SendRedirectsFile, enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *LinuxFirewall) EnableRedirection(r *Redirection, enabled bool) error {
|
||||||
|
var opts []string
|
||||||
|
|
||||||
|
rkey := r.String()
|
||||||
|
_, found := f.redirections[rkey]
|
||||||
|
|
||||||
|
if enabled == true {
|
||||||
|
if found == true {
|
||||||
|
return fmt.Errorf("Redirection '%s' already enabled.", rkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Enabling redirection %s\n", rkey)
|
||||||
|
|
||||||
|
f.redirections[rkey] = r
|
||||||
|
|
||||||
|
// accept all
|
||||||
|
if _, err := core.Exec("iptables", []string{"-P", "FORWARD", "ACCEPT"}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.SrcAddress == "" {
|
||||||
|
opts = []string{
|
||||||
|
"-t", "nat",
|
||||||
|
"-A", "PREROUTING",
|
||||||
|
"-i", r.Interface,
|
||||||
|
"-p", r.Protocol,
|
||||||
|
"--dport", fmt.Sprintf("%d", r.SrcPort),
|
||||||
|
"-j", "DNAT",
|
||||||
|
"--to", fmt.Sprintf("%s:%d", r.DstAddress, r.DstPort),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
opts = []string{
|
||||||
|
"-t", "nat",
|
||||||
|
"-A", "PREROUTING",
|
||||||
|
"-i", r.Interface,
|
||||||
|
"-p", r.Protocol,
|
||||||
|
"-d", r.SrcAddress,
|
||||||
|
"--dport", fmt.Sprintf("%d", r.SrcPort),
|
||||||
|
"-j", "DNAT",
|
||||||
|
"--to", fmt.Sprintf("%s:%d", r.DstAddress, r.DstPort),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := core.Exec("iptables", opts); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if found == false {
|
||||||
|
log.Debugf("Did not remove redirection '%s' as it was already removed.\n", r.String())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Disabling redirection %s\n", r.String())
|
||||||
|
|
||||||
|
delete(f.redirections, r.String())
|
||||||
|
|
||||||
|
if r.SrcAddress == "" {
|
||||||
|
opts = []string{
|
||||||
|
"-t", "nat",
|
||||||
|
"-D", "PREROUTING",
|
||||||
|
"-i", r.Interface,
|
||||||
|
"-p", r.Protocol,
|
||||||
|
"--dport", fmt.Sprintf("%d", r.SrcPort),
|
||||||
|
"-j", "DNAT",
|
||||||
|
"--to", fmt.Sprintf("%s:%d", r.DstAddress, r.DstPort),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
opts = []string{
|
||||||
|
"-t", "nat",
|
||||||
|
"-D", "PREROUTING",
|
||||||
|
"-i", r.Interface,
|
||||||
|
"-p", r.Protocol,
|
||||||
|
"-d", r.SrcAddress,
|
||||||
|
"--dport", fmt.Sprintf("%d", r.SrcPort),
|
||||||
|
"-j", "DNAT",
|
||||||
|
"--to", fmt.Sprintf("%s:%d", r.DstAddress, r.DstPort),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := core.Exec("iptables", opts); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f LinuxFirewall) Restore() {
|
||||||
|
log.Debugf("Restoring firewall state.\n")
|
||||||
|
for _, r := range f.redirections {
|
||||||
|
if err := f.EnableRedirection(r, false); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := f.EnableForwarding(f.forwarding); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
27
firewall/redirection.go
Normal file
27
firewall/redirection.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package firewall
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type Redirection struct {
|
||||||
|
Interface string
|
||||||
|
Protocol string
|
||||||
|
SrcAddress string
|
||||||
|
SrcPort int
|
||||||
|
DstAddress string
|
||||||
|
DstPort int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRedirection(iface string, proto string, port_from int, addr_to string, port_to int) *Redirection {
|
||||||
|
return &Redirection{
|
||||||
|
Interface: iface,
|
||||||
|
Protocol: proto,
|
||||||
|
SrcAddress: "",
|
||||||
|
SrcPort: port_from,
|
||||||
|
DstAddress: addr_to,
|
||||||
|
DstPort: port_to,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Redirection) String() string {
|
||||||
|
return fmt.Sprintf("[%s] (%s) %s:%d -> %s:%d", r.Interface, r.Protocol, r.SrcAddress, r.SrcPort, r.DstAddress, r.DstPort)
|
||||||
|
}
|
57
main.go
Normal file
57
main.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/op/go-logging"
|
||||||
|
|
||||||
|
"github.com/evilsocket/bettercap/core"
|
||||||
|
"github.com/evilsocket/bettercap/session"
|
||||||
|
"github.com/evilsocket/bettercap/session/modules"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sess *session.Session
|
||||||
|
var log = logging.MustGetLogger("mitm")
|
||||||
|
var err error
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if sess, err = session.New(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Starting %s v%s\n", core.Name, core.Version)
|
||||||
|
log.Infof("Build: date=%s os=%s arch=%s\n", core.BuildDate, runtime.GOOS, runtime.GOARCH)
|
||||||
|
|
||||||
|
sess.Register(session_modules.NewProber(sess))
|
||||||
|
sess.Register(session_modules.NewDiscovery(sess))
|
||||||
|
sess.Register(session_modules.NewArpSpoofer(sess))
|
||||||
|
sess.Register(session_modules.NewSniffer(sess))
|
||||||
|
sess.Register(session_modules.NewHttpProxy(sess))
|
||||||
|
|
||||||
|
if err = sess.Start(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer sess.Close()
|
||||||
|
|
||||||
|
if *sess.Options.Caplet != "" {
|
||||||
|
if err = sess.RunCaplet(*sess.Options.Caplet); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for sess.Active {
|
||||||
|
line, err := sess.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if line == "" || line[0] == '#' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = sess.Run(line); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
90
net/arp.go
Normal file
90
net/arp.go
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
package net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/op/go-logging"
|
||||||
|
|
||||||
|
"github.com/evilsocket/bettercap/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ArpTable map[string]string
|
||||||
|
|
||||||
|
var (
|
||||||
|
log = logging.MustGetLogger("mitm")
|
||||||
|
arp_parsed = false
|
||||||
|
arp_lock = &sync.Mutex{}
|
||||||
|
arp_table = make(ArpTable)
|
||||||
|
)
|
||||||
|
|
||||||
|
var ArpTableParser = regexp.MustCompile("^[^\\d\\.]+([\\d\\.]+).+\\s+([a-f0-9:]{17}).+\\s+(.+)$")
|
||||||
|
var ArpTableTokens = 4
|
||||||
|
|
||||||
|
func ArpDiff(current, before ArpTable) ArpTable {
|
||||||
|
diff := make(ArpTable)
|
||||||
|
for ip, mac := range current {
|
||||||
|
_, found := before[ip]
|
||||||
|
if !found {
|
||||||
|
diff[ip] = mac
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return diff
|
||||||
|
}
|
||||||
|
|
||||||
|
func ArpLookup(iface string, address string, refresh bool) (string, error) {
|
||||||
|
// Refresh ARP table if first run or if a force refresh has been instructed.
|
||||||
|
if ArpParsed() == false || refresh == true {
|
||||||
|
if _, err := ArpUpdate(iface); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup the hardware address of this ip.
|
||||||
|
if mac, found := arp_table[address]; found == true {
|
||||||
|
return mac, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("Could not find mac for %s", address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ArpParsed() bool {
|
||||||
|
arp_lock.Lock()
|
||||||
|
defer arp_lock.Unlock()
|
||||||
|
return arp_parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
func ArpUpdate(iface string) (ArpTable, error) {
|
||||||
|
arp_lock.Lock()
|
||||||
|
defer arp_lock.Unlock()
|
||||||
|
|
||||||
|
// Signal we parsed the ARP table at least once.
|
||||||
|
arp_parsed = true
|
||||||
|
|
||||||
|
// Run "arp -an" and parse the output.
|
||||||
|
output, err := core.Exec("arp", []string{"-a", "-n"})
|
||||||
|
if err != nil {
|
||||||
|
return arp_table, err
|
||||||
|
}
|
||||||
|
|
||||||
|
new_table := make(ArpTable)
|
||||||
|
for _, line := range strings.Split(output, "\n") {
|
||||||
|
m := ArpTableParser.FindStringSubmatch(line)
|
||||||
|
if len(m) == ArpTableTokens {
|
||||||
|
address := m[1]
|
||||||
|
mac := m[2]
|
||||||
|
ifname := m[3]
|
||||||
|
|
||||||
|
if ifname == iface {
|
||||||
|
new_table[address] = mac
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
arp_table = new_table
|
||||||
|
|
||||||
|
return arp_table, nil
|
||||||
|
}
|
82
net/endpoint.go
Normal file
82
net/endpoint.go
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/evilsocket/bettercap/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Endpoint struct {
|
||||||
|
IP net.IP
|
||||||
|
HW net.HardwareAddr
|
||||||
|
IpAddress string
|
||||||
|
SubnetBits uint32
|
||||||
|
IpAddressUint32 uint32
|
||||||
|
HwAddress string
|
||||||
|
Hostname string
|
||||||
|
Vendor string
|
||||||
|
}
|
||||||
|
|
||||||
|
type OnHostResolvedAction func(e *Endpoint)
|
||||||
|
|
||||||
|
func NewEndpointNoResolve(ip, mac, name string, bits uint32) *Endpoint {
|
||||||
|
hw, err := net.ParseMAC(mac)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e := &Endpoint{
|
||||||
|
IP: net.ParseIP(ip),
|
||||||
|
HW: hw,
|
||||||
|
IpAddress: ip,
|
||||||
|
SubnetBits: bits,
|
||||||
|
IpAddressUint32: binary.BigEndian.Uint32(net.ParseIP(ip)[12:16]),
|
||||||
|
HwAddress: mac,
|
||||||
|
Hostname: name,
|
||||||
|
Vendor: OuiLookup(mac),
|
||||||
|
}
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEndpoint(ip, mac string) *Endpoint {
|
||||||
|
e := NewEndpointNoResolve(ip, mac, "", 0)
|
||||||
|
|
||||||
|
// start resolver goroutine
|
||||||
|
go func() {
|
||||||
|
if names, err := net.LookupAddr(e.IpAddress); err == nil {
|
||||||
|
e.Hostname = names[0]
|
||||||
|
log.Debugf("Endpoint %s is now known as %s\n", e.IpAddress, core.Green(e.Hostname))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Endpoint) Name() string {
|
||||||
|
return t.Hostname
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Endpoint) CIDR() string {
|
||||||
|
shift := 32 - t.SubnetBits
|
||||||
|
address := t.IpAddressUint32
|
||||||
|
ip := make(net.IP, 4)
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(ip, (address>>shift)<<shift)
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s/%d", ip.String(), t.SubnetBits)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Endpoint) String() string {
|
||||||
|
if t.HwAddress == "" {
|
||||||
|
return t.IpAddress
|
||||||
|
} else if t.Vendor == "" {
|
||||||
|
return fmt.Sprintf("%s : %s", t.IpAddress, t.HwAddress)
|
||||||
|
} else if t.Hostname == "" {
|
||||||
|
return fmt.Sprintf("%s : %s ( %s )", t.IpAddress, t.HwAddress, t.Vendor)
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("%s : %s ( %s ) - "+core.BOLD+t.Hostname+core.RESET, t.IpAddress, t.HwAddress, t.Vendor)
|
||||||
|
}
|
||||||
|
}
|
98
net/net.go
Normal file
98
net/net.go
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
package net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/evilsocket/bettercap/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
var IPv4RouteParser = regexp.MustCompile("^([\\d\\.]+)\\s+([\\d\\.]+)\\s+([\\d\\.]+)\\s+([A-Z]+)\\s+\\d+\\s+\\d+\\s+\\d+\\s+(.+)$")
|
||||||
|
var IPv4RouteTokens = 6
|
||||||
|
|
||||||
|
func FindInterface(name string) (*Endpoint, error) {
|
||||||
|
ifaces, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iface := range ifaces {
|
||||||
|
mac := iface.HardwareAddr.String()
|
||||||
|
addrs, err := iface.Addrs()
|
||||||
|
// is interface active?
|
||||||
|
if err == nil && len(addrs) > 0 {
|
||||||
|
if (name == "" && iface.Name != "lo") || iface.Name == name {
|
||||||
|
var e *Endpoint = nil
|
||||||
|
// For every address of the interface.
|
||||||
|
for _, addr := range addrs {
|
||||||
|
ip := addr.String()
|
||||||
|
// Make sure this is an IPv4 address.
|
||||||
|
if m, _ := regexp.MatchString("^[0-9\\.]+/?\\d*$", ip); m == true {
|
||||||
|
if strings.IndexRune(ip, '/') == -1 {
|
||||||
|
// plain ip
|
||||||
|
e = NewEndpointNoResolve(ip, mac, iface.Name, 0)
|
||||||
|
} else {
|
||||||
|
// ip/bits
|
||||||
|
parts := strings.Split(ip, "/")
|
||||||
|
ip_part := parts[0]
|
||||||
|
bits, err := strconv.Atoi(parts[1])
|
||||||
|
if err == nil {
|
||||||
|
e = NewEndpointNoResolve(ip_part, mac, iface.Name, uint32(bits))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if e != nil {
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if name == "" {
|
||||||
|
return nil, fmt.Errorf("Could not find default network interface.")
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("Could not find interface '%s'.", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindGateway(iface *Endpoint) (*Endpoint, error) {
|
||||||
|
output, err := core.Exec("route", []string{"-n", "-A", "inet"})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, line := range strings.Split(output, "\n") {
|
||||||
|
m := IPv4RouteParser.FindStringSubmatch(line)
|
||||||
|
if len(m) == IPv4RouteTokens {
|
||||||
|
// destination := m[1]
|
||||||
|
// mask := m[3]
|
||||||
|
flags := m[4]
|
||||||
|
ifname := m[5]
|
||||||
|
|
||||||
|
if ifname == iface.Name() && flags == "UG" {
|
||||||
|
gateway := m[2]
|
||||||
|
// log.Debugf("Gateway ip is %s", gateway)
|
||||||
|
if gateway == iface.IpAddress {
|
||||||
|
// log.Debug("Gateway == Interface")
|
||||||
|
return iface, nil
|
||||||
|
} else {
|
||||||
|
// we have the address, now we need its mac
|
||||||
|
mac, err := ArpLookup(iface.Name(), gateway, false)
|
||||||
|
if err == nil {
|
||||||
|
// log.Debugf("Gateway mac is %s", mac)
|
||||||
|
return NewEndpoint(gateway, mac), nil
|
||||||
|
} else {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("Could not detect the gateway.")
|
||||||
|
}
|
22930
net/oui.dat
Normal file
22930
net/oui.dat
Normal file
File diff suppressed because it is too large
Load diff
54
net/oui.go
Normal file
54
net/oui.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
oui = make(map[string]string)
|
||||||
|
)
|
||||||
|
|
||||||
|
func OuiInit() {
|
||||||
|
bytes, err := Asset("net/oui.dat")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := string(bytes)
|
||||||
|
lines := strings.Split(data, "\n")
|
||||||
|
|
||||||
|
for lineno, line := range lines {
|
||||||
|
line = strings.Trim(line, " \n\r\t")
|
||||||
|
if len(line) == 0 || line[0] == '#' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(line, " ", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
log.Warningf("Skipping line %d '%s'\n", lineno+1, line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := strings.ToLower(strings.Trim(parts[0], " \n\r\t"))
|
||||||
|
vendor := strings.Trim(parts[1], " \n\r\t")
|
||||||
|
|
||||||
|
oui[prefix] = vendor
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Loaded %d vendors signatures.\n", len(oui))
|
||||||
|
}
|
||||||
|
|
||||||
|
func OuiLookup(mac string) string {
|
||||||
|
octects := strings.Split(mac, ":")
|
||||||
|
if len(octects) > 3 {
|
||||||
|
prefix := octects[0] + octects[1] + octects[2]
|
||||||
|
|
||||||
|
if vendor, found := oui[prefix]; found == true {
|
||||||
|
return vendor
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Warningf("Unexpected mac '%s' in net.OuiLookup\n", mac)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "???"
|
||||||
|
}
|
40
packets/arp.go
Normal file
40
packets/arp.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package packets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/gopacket/layers"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewARPTo(from net.IP, from_hw net.HardwareAddr, to net.IP, to_hw net.HardwareAddr, req uint16) (layers.Ethernet, layers.ARP) {
|
||||||
|
eth := layers.Ethernet{
|
||||||
|
SrcMAC: from_hw,
|
||||||
|
DstMAC: net.HardwareAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||||
|
EthernetType: layers.EthernetTypeARP,
|
||||||
|
}
|
||||||
|
arp := layers.ARP{
|
||||||
|
AddrType: layers.LinkTypeEthernet,
|
||||||
|
Protocol: layers.EthernetTypeIPv4,
|
||||||
|
HwAddressSize: 6,
|
||||||
|
ProtAddressSize: 4,
|
||||||
|
Operation: req,
|
||||||
|
SourceHwAddress: from_hw,
|
||||||
|
SourceProtAddress: from.To4(),
|
||||||
|
DstHwAddress: to_hw,
|
||||||
|
DstProtAddress: to.To4(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return eth, arp
|
||||||
|
}
|
||||||
|
func NewARP(from net.IP, from_hw net.HardwareAddr, to net.IP, req uint16) (layers.Ethernet, layers.ARP) {
|
||||||
|
return NewARPTo(from, from_hw, to, []byte{0, 0, 0, 0, 0, 0}, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewARPRequest(from net.IP, from_hw net.HardwareAddr, to net.IP) (error, []byte) {
|
||||||
|
eth, arp := NewARP(from, from_hw, to, layers.ARPRequest)
|
||||||
|
return Serialize(ð, &arp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewARPReply(from net.IP, from_hw net.HardwareAddr, to net.IP, to_hw net.HardwareAddr) (error, []byte) {
|
||||||
|
eth, arp := NewARPTo(from, from_hw, to, to_hw, layers.ARPReply)
|
||||||
|
return Serialize(ð, &arp)
|
||||||
|
}
|
58
packets/queue.go
Normal file
58
packets/queue.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package packets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/gopacket/pcap"
|
||||||
|
"github.com/op/go-logging"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = logging.MustGetLogger("mitm")
|
||||||
|
|
||||||
|
type Queue struct {
|
||||||
|
iface string
|
||||||
|
handle *pcap.Handle
|
||||||
|
lock *sync.Mutex
|
||||||
|
active bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewQueue(iface string) (*Queue, error) {
|
||||||
|
log.Debugf("Creating packet queue for interface %s.\n", iface)
|
||||||
|
var err error
|
||||||
|
|
||||||
|
q := &Queue{
|
||||||
|
iface: iface,
|
||||||
|
handle: nil,
|
||||||
|
lock: &sync.Mutex{},
|
||||||
|
active: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
q.handle, err = pcap.OpenLive(iface, 65536, true, pcap.BlockForever)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return q, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queue) Send(raw []byte) error {
|
||||||
|
q.lock.Lock()
|
||||||
|
defer q.lock.Unlock()
|
||||||
|
|
||||||
|
log.Debugf("Sending %d bytes to packet queue.\n", len(raw))
|
||||||
|
if q.active {
|
||||||
|
return q.handle.WritePacketData(raw)
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Packet queue is not active.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queue) Stop() {
|
||||||
|
q.lock.Lock()
|
||||||
|
defer q.lock.Unlock()
|
||||||
|
|
||||||
|
log.Debugf("Stopping packet queue.\n")
|
||||||
|
|
||||||
|
q.handle.Close()
|
||||||
|
q.active = false
|
||||||
|
}
|
20
packets/serialize.go
Normal file
20
packets/serialize.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package packets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/gopacket"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Serialize(layers ...gopacket.SerializableLayer) (error, []byte) {
|
||||||
|
// Set up buffer and options for serialization.
|
||||||
|
buf := gopacket.NewSerializeBuffer()
|
||||||
|
opts := gopacket.SerializeOptions{
|
||||||
|
FixLengths: true,
|
||||||
|
ComputeChecksums: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gopacket.SerializeLayers(buf, opts, layers...); err != nil {
|
||||||
|
return err, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, buf.Bytes()
|
||||||
|
}
|
32
packets/udp.go
Normal file
32
packets/udp.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package packets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/gopacket/layers"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewUDPProbe(from net.IP, from_hw net.HardwareAddr, to net.IP, port int) (error, []byte) {
|
||||||
|
eth := layers.Ethernet{
|
||||||
|
SrcMAC: from_hw,
|
||||||
|
DstMAC: net.HardwareAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||||
|
EthernetType: layers.EthernetTypeIPv4,
|
||||||
|
}
|
||||||
|
|
||||||
|
ip4 := layers.IPv4{
|
||||||
|
Protocol: layers.IPProtocolUDP,
|
||||||
|
Version: 4,
|
||||||
|
TTL: 64,
|
||||||
|
SrcIP: from,
|
||||||
|
DstIP: to,
|
||||||
|
}
|
||||||
|
|
||||||
|
udp := layers.UDP{
|
||||||
|
SrcPort: layers.UDPPort(12345),
|
||||||
|
DstPort: layers.UDPPort(port),
|
||||||
|
}
|
||||||
|
udp.Payload = []byte{0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef}
|
||||||
|
|
||||||
|
udp.SetNetworkLayerForChecksum(&ip4)
|
||||||
|
|
||||||
|
return Serialize(ð, &ip4, &udp)
|
||||||
|
}
|
28
session/command_handler.go
Normal file
28
session/command_handler.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package session
|
||||||
|
|
||||||
|
import "regexp"
|
||||||
|
|
||||||
|
type CommandHandler struct {
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
Parser *regexp.Regexp
|
||||||
|
Exec func(args []string, s *Session) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCommandHandler(name string, expr string, desc string, exec func(args []string, s *Session) error) CommandHandler {
|
||||||
|
return CommandHandler{
|
||||||
|
Name: name,
|
||||||
|
Description: desc,
|
||||||
|
Parser: regexp.MustCompile(expr),
|
||||||
|
Exec: exec,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CommandHandler) Parse(line string) (bool, []string) {
|
||||||
|
result := h.Parser.FindStringSubmatch(line)
|
||||||
|
if len(result) == h.Parser.NumSubexp()+1 {
|
||||||
|
return true, result[1:len(result)]
|
||||||
|
} else {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
82
session/environment.go
Normal file
82
session/environment.go
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package session
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Environment struct {
|
||||||
|
Padding int
|
||||||
|
storage map[string]string
|
||||||
|
lock *sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEnvironment() *Environment {
|
||||||
|
env := &Environment{
|
||||||
|
Padding: 0,
|
||||||
|
storage: make(map[string]string),
|
||||||
|
lock: &sync.Mutex{},
|
||||||
|
}
|
||||||
|
|
||||||
|
return env
|
||||||
|
}
|
||||||
|
|
||||||
|
func (env *Environment) Has(name string) bool {
|
||||||
|
env.lock.Lock()
|
||||||
|
defer env.lock.Unlock()
|
||||||
|
|
||||||
|
_, found := env.storage[name]
|
||||||
|
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
func (env *Environment) Set(name, value string) string {
|
||||||
|
env.lock.Lock()
|
||||||
|
defer env.lock.Unlock()
|
||||||
|
|
||||||
|
old, _ := env.storage[name]
|
||||||
|
env.storage[name] = value
|
||||||
|
|
||||||
|
if len(name) > env.Padding {
|
||||||
|
env.Padding = len(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return old
|
||||||
|
}
|
||||||
|
|
||||||
|
func (env *Environment) Get(name string) (bool, string) {
|
||||||
|
env.lock.Lock()
|
||||||
|
defer env.lock.Unlock()
|
||||||
|
|
||||||
|
if value, found := env.storage[name]; found == true {
|
||||||
|
return true, value
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (env *Environment) GetInt(name string) (error, int) {
|
||||||
|
if found, value := env.Get(name); found == true {
|
||||||
|
if i, err := strconv.Atoi(value); err == nil {
|
||||||
|
return nil, i
|
||||||
|
} else {
|
||||||
|
return err, 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Not found."), 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (env *Environment) Sorted() []string {
|
||||||
|
env.lock.Lock()
|
||||||
|
defer env.lock.Unlock()
|
||||||
|
|
||||||
|
var keys []string
|
||||||
|
for k := range env.storage {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
return keys
|
||||||
|
}
|
77
session/module.go
Normal file
77
session/module.go
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
package session
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
type Module interface {
|
||||||
|
Name() string
|
||||||
|
Description() string
|
||||||
|
Author() string
|
||||||
|
Handlers() []ModuleHandler
|
||||||
|
Parameters() map[string]*ModuleParam
|
||||||
|
|
||||||
|
Running() bool
|
||||||
|
Start() error
|
||||||
|
Stop() error
|
||||||
|
|
||||||
|
OnSessionStarted(s *Session)
|
||||||
|
OnSessionEnded(s *Session)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SessionModule struct {
|
||||||
|
Session *Session
|
||||||
|
Started bool
|
||||||
|
StatusLock *sync.Mutex
|
||||||
|
|
||||||
|
handlers []ModuleHandler
|
||||||
|
params map[string]*ModuleParam
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSessionModule(s *Session) SessionModule {
|
||||||
|
m := SessionModule{
|
||||||
|
Session: s,
|
||||||
|
Started: false,
|
||||||
|
StatusLock: &sync.Mutex{},
|
||||||
|
|
||||||
|
handlers: make([]ModuleHandler, 0),
|
||||||
|
params: make(map[string]*ModuleParam),
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SessionModule) Handlers() []ModuleHandler {
|
||||||
|
return m.handlers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SessionModule) Parameters() map[string]*ModuleParam {
|
||||||
|
return m.params
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SessionModule) Param(name string) *ModuleParam {
|
||||||
|
return m.params[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SessionModule) AddHandler(h ModuleHandler) {
|
||||||
|
m.handlers = append(m.handlers, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SessionModule) AddParam(p *ModuleParam) {
|
||||||
|
m.params[p.Name] = p
|
||||||
|
p.Register(m.Session)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SessionModule) Running() bool {
|
||||||
|
m.StatusLock.Lock()
|
||||||
|
defer m.StatusLock.Unlock()
|
||||||
|
return m.Started
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SessionModule) SetRunning(running bool) {
|
||||||
|
m.StatusLock.Lock()
|
||||||
|
defer m.StatusLock.Unlock()
|
||||||
|
m.Started = running
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SessionModule) OnSessionStarted(s *Session) {
|
||||||
|
|
||||||
|
}
|
37
session/module_handler.go
Normal file
37
session/module_handler.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package session
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/evilsocket/bettercap/core"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ModuleHandler struct {
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
Parser *regexp.Regexp
|
||||||
|
Exec func(args []string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewModuleHandler(name string, expr string, desc string, exec func(args []string) error) ModuleHandler {
|
||||||
|
return ModuleHandler{
|
||||||
|
Name: name,
|
||||||
|
Description: desc,
|
||||||
|
Parser: regexp.MustCompile(expr),
|
||||||
|
Exec: exec,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ModuleHandler) Help(padding int) string {
|
||||||
|
return fmt.Sprintf(" "+core.Bold("%"+strconv.Itoa(padding)+"s")+" : %s\n", h.Name, h.Description)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ModuleHandler) Parse(line string) (bool, []string) {
|
||||||
|
result := h.Parser.FindStringSubmatch(line)
|
||||||
|
if len(result) == h.Parser.NumSubexp()+1 {
|
||||||
|
return true, result[1:len(result)]
|
||||||
|
} else {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
105
session/module_param.go
Normal file
105
session/module_param.go
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
package session
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/evilsocket/bettercap/core"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ParamType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
STRING ParamType = iota
|
||||||
|
BOOL = iota
|
||||||
|
INT = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
type ModuleParam struct {
|
||||||
|
Name string
|
||||||
|
Type ParamType
|
||||||
|
Value string
|
||||||
|
Description string
|
||||||
|
|
||||||
|
Validator *regexp.Regexp
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewModuleParameter(name string, def_value string, t ParamType, validator string, desc string) *ModuleParam {
|
||||||
|
p := &ModuleParam{
|
||||||
|
Name: name,
|
||||||
|
Type: t,
|
||||||
|
Description: desc,
|
||||||
|
Value: def_value,
|
||||||
|
Validator: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
if validator != "" {
|
||||||
|
p.Validator = regexp.MustCompile(validator)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStringParameter(name string, def_value string, validator string, desc string) *ModuleParam {
|
||||||
|
return NewModuleParameter(name, def_value, STRING, validator, desc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBoolParameter(name string, def_value string, validator string, desc string) *ModuleParam {
|
||||||
|
return NewModuleParameter(name, def_value, BOOL, validator, desc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIntParameter(name string, def_value string, validator string, desc string) *ModuleParam {
|
||||||
|
return NewModuleParameter(name, def_value, INT, validator, desc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ModuleParam) Validate(value string) (error, interface{}) {
|
||||||
|
if p.Validator != nil {
|
||||||
|
if p.Validator.MatchString(value) == false {
|
||||||
|
return fmt.Errorf("Parameter value '%s' does not match validator '%s'.", value, p.Validator.String()), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Type == STRING {
|
||||||
|
return nil, value
|
||||||
|
} else if p.Type == BOOL {
|
||||||
|
lvalue := strings.ToLower(value)
|
||||||
|
if lvalue == "true" {
|
||||||
|
return nil, true
|
||||||
|
} else if lvalue == "false" {
|
||||||
|
return nil, false
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Can't typecast '%s' to boolean.", value), nil
|
||||||
|
}
|
||||||
|
} else if p.Type == INT {
|
||||||
|
i, err := strconv.Atoi(value)
|
||||||
|
return err, i
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Unhandled module parameter type %d.", p.Type), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ModuleParam) Get(s *Session) (error, interface{}) {
|
||||||
|
var v string
|
||||||
|
var found bool
|
||||||
|
var obj interface{}
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if found, v = s.Env.Get(p.Name); found == false {
|
||||||
|
v = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
err, obj = p.Validate(v)
|
||||||
|
return err, obj
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ModuleParam) Help(padding int) string {
|
||||||
|
return fmt.Sprintf(" "+core.YELLOW+"%"+strconv.Itoa(padding)+"s"+core.RESET+
|
||||||
|
" : "+
|
||||||
|
"%s "+core.DIM+"(default=%s"+core.RESET+")\n", p.Name, p.Description, p.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ModuleParam) Register(s *Session) {
|
||||||
|
s.Env.Set(p.Name, p.Value)
|
||||||
|
}
|
BIN
session/modules/.discovery.go.swp
Normal file
BIN
session/modules/.discovery.go.swp
Normal file
Binary file not shown.
214
session/modules/arp_spoof.go
Normal file
214
session/modules/arp_spoof.go
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
package session_modules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
network "github.com/evilsocket/bettercap/net"
|
||||||
|
"github.com/evilsocket/bettercap/packets"
|
||||||
|
"github.com/evilsocket/bettercap/session"
|
||||||
|
"github.com/malfunkt/iprange"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ArpSpoofer struct {
|
||||||
|
session.SessionModule
|
||||||
|
Done chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewArpSpoofer(s *session.Session) *ArpSpoofer {
|
||||||
|
p := &ArpSpoofer{
|
||||||
|
SessionModule: session.NewSessionModule(s),
|
||||||
|
Done: make(chan bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
p.AddParam(session.NewStringParameter("arp.spoof.targets", "<entire subnet>", "", "IP addresses to spoof."))
|
||||||
|
|
||||||
|
p.AddHandler(session.NewModuleHandler("arp.spoof (on|off)", "^arp\\.spoof\\s+(on|off)$",
|
||||||
|
"Start/stop ARP spoofer.",
|
||||||
|
func(args []string) error {
|
||||||
|
if args[0] == "on" {
|
||||||
|
return p.Start()
|
||||||
|
} else {
|
||||||
|
return p.Stop()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ArpSpoofer) OnSessionStarted(s *session.Session) {
|
||||||
|
// refresh the subnet after session has been created
|
||||||
|
s.Env.Set("arp.spoof.targets", s.Interface.CIDR())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ArpSpoofer) OnSessionEnded(s *session.Session) {
|
||||||
|
if p.Running() {
|
||||||
|
p.Stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ArpSpoofer) Name() string {
|
||||||
|
return "ARP Spoofer"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ArpSpoofer) Description() string {
|
||||||
|
return "Keep spoofing selected hosts on the network."
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ArpSpoofer) Author() string {
|
||||||
|
return "Simone Margaritelli <evilsocket@protonmail.com>"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ArpSpoofer) shouldSpoof(ip net.IP) bool {
|
||||||
|
addr := ip.String()
|
||||||
|
if ip.IsLoopback() == true {
|
||||||
|
return false
|
||||||
|
} else if addr == p.Session.Interface.IpAddress {
|
||||||
|
return false
|
||||||
|
} else if addr == p.Session.Gateway.IpAddress {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ArpSpoofer) getMAC(ip net.IP, probe bool) (net.HardwareAddr, error) {
|
||||||
|
var mac string
|
||||||
|
var hw net.HardwareAddr
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// do we have this ip mac address?
|
||||||
|
mac, err = network.ArpLookup(p.Session.Interface.Name(), ip.String(), false)
|
||||||
|
if err != nil && probe == true {
|
||||||
|
from := p.Session.Interface.IP
|
||||||
|
from_hw := p.Session.Interface.HW
|
||||||
|
|
||||||
|
if err, probe := packets.NewUDPProbe(from, from_hw, ip, 139); err != nil {
|
||||||
|
log.Errorf("Error while creating UDP probe packet for %s: %s\n", ip.String(), err)
|
||||||
|
} else {
|
||||||
|
p.Session.Queue.Send(probe)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
|
mac, err = network.ArpLookup(p.Session.Interface.Name(), ip.String(), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mac == "" {
|
||||||
|
return nil, fmt.Errorf("Could not find hardware address for %s.", ip.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
hw, err = net.ParseMAC(mac)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error while parsing hardware address '%s' for %s: %s", mac, ip.String(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return hw, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ArpSpoofer) sendArp(addresses []net.IP, saddr net.IP, smac net.HardwareAddr, check_running bool, probe bool) {
|
||||||
|
for _, ip := range addresses {
|
||||||
|
if p.shouldSpoof(ip) == false {
|
||||||
|
log.Debugf("Skipping address %s from ARP spoofing.\n", ip)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// do we have this ip mac address?
|
||||||
|
hw, err := p.getMAC(ip, probe)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("Error while looking up hardware address for %s: %s\n", ip.String(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err, pkt := packets.NewARPReply(saddr, smac, ip, hw); err != nil {
|
||||||
|
log.Errorf("Error while creating ARP spoof packet for %s: %s\n", ip.String(), err)
|
||||||
|
} else {
|
||||||
|
log.Debugf("Sending %d bytes of ARP packet to %s:%s.\n", len(pkt), ip.String(), hw.String())
|
||||||
|
p.Session.Queue.Send(pkt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if check_running && p.Running() == false {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ArpSpoofer) unSpoof() error {
|
||||||
|
var targets string
|
||||||
|
|
||||||
|
if err, v := p.Param("arp.spoof.targets").Get(p.Session); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
targets = v.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := iprange.Parse(targets)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error while parsing arp.spoof.targets variable '%s': %s.", targets, err)
|
||||||
|
}
|
||||||
|
addresses := list.Expand()
|
||||||
|
|
||||||
|
from := p.Session.Gateway.IP
|
||||||
|
from_hw := p.Session.Gateway.HW
|
||||||
|
|
||||||
|
log.Infof("Restoring ARP cache of %d targets (%s).\n", len(addresses), targets)
|
||||||
|
|
||||||
|
p.sendArp(addresses, from, from_hw, false, false)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ArpSpoofer) Start() error {
|
||||||
|
if p.Running() == false {
|
||||||
|
var targets string
|
||||||
|
|
||||||
|
if err, v := p.Param("arp.spoof.targets").Get(p.Session); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
targets = v.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := iprange.Parse(targets)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error while parsing arp.spoof.targets variable '%s': %s.", targets, err)
|
||||||
|
}
|
||||||
|
addresses := list.Expand()
|
||||||
|
|
||||||
|
p.SetRunning(true)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
|
||||||
|
from := p.Session.Gateway.IP
|
||||||
|
from_hw := p.Session.Interface.HW
|
||||||
|
|
||||||
|
log.Infof("ARP spoofer started, probing %d targets (%s).\n", len(addresses), targets)
|
||||||
|
|
||||||
|
for p.Running() {
|
||||||
|
p.sendArp(addresses, from, from_hw, true, false)
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Done <- true
|
||||||
|
log.Info("ARP spoofer stopped.\n")
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("ARP spoofer already started.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ArpSpoofer) Stop() error {
|
||||||
|
if p.Running() == true {
|
||||||
|
p.SetRunning(false)
|
||||||
|
|
||||||
|
log.Info("Waiting for ARP spoofer to stop ...\n")
|
||||||
|
|
||||||
|
<-p.Done
|
||||||
|
|
||||||
|
p.unSpoof()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("ARP spoofer already stopped.")
|
||||||
|
}
|
||||||
|
}
|
264
session/modules/http_proxy.go
Normal file
264
session/modules/http_proxy.go
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
package session_modules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/op/go-logging"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/elazarl/goproxy"
|
||||||
|
"github.com/elazarl/goproxy/ext/html"
|
||||||
|
|
||||||
|
"github.com/evilsocket/bettercap/firewall"
|
||||||
|
"github.com/evilsocket/bettercap/session"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = logging.MustGetLogger("mitm")
|
||||||
|
|
||||||
|
type ProxyFilter struct {
|
||||||
|
Type string
|
||||||
|
Expression string
|
||||||
|
Replace string
|
||||||
|
Compiled *regexp.Regexp
|
||||||
|
}
|
||||||
|
|
||||||
|
func tokenize(s string, sep byte, n int) (error, []string) {
|
||||||
|
filtered := make([]string, 0)
|
||||||
|
tokens := strings.Split(s, string(sep))
|
||||||
|
|
||||||
|
for _, t := range tokens {
|
||||||
|
if t != "" {
|
||||||
|
filtered = append(filtered, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(filtered) != n {
|
||||||
|
return fmt.Errorf("Could not split '%s' by '%s'.", s, string(sep)), filtered
|
||||||
|
} else {
|
||||||
|
return nil, filtered
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProxyFilter(type_, expression string) (error, *ProxyFilter) {
|
||||||
|
err, tokens := tokenize(expression, expression[0], 2)
|
||||||
|
if err != nil {
|
||||||
|
return err, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := &ProxyFilter{
|
||||||
|
Type: type_,
|
||||||
|
Expression: tokens[0],
|
||||||
|
Replace: tokens[1],
|
||||||
|
Compiled: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter.Compiled, err = regexp.Compile(filter.Expression); err != nil {
|
||||||
|
return err, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, filter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ProxyFilter) Process(req *http.Request, response_body string) string {
|
||||||
|
orig := response_body
|
||||||
|
filtered := f.Compiled.ReplaceAllString(orig, f.Replace)
|
||||||
|
|
||||||
|
// TODO: this sucks
|
||||||
|
if orig != filtered {
|
||||||
|
log.Infof("%s > Applied %s-filtering to %d of response body.", req.RemoteAddr, f.Type, len(filtered))
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
|
|
||||||
|
type HttpProxy struct {
|
||||||
|
session.SessionModule
|
||||||
|
|
||||||
|
address string
|
||||||
|
redirection *firewall.Redirection
|
||||||
|
server http.Server
|
||||||
|
proxy *goproxy.ProxyHttpServer
|
||||||
|
|
||||||
|
pre_filter *ProxyFilter
|
||||||
|
post_filter *ProxyFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHttpProxy(s *session.Session) *HttpProxy {
|
||||||
|
p := &HttpProxy{
|
||||||
|
SessionModule: session.NewSessionModule(s),
|
||||||
|
proxy: goproxy.NewProxyHttpServer(),
|
||||||
|
address: "",
|
||||||
|
redirection: nil,
|
||||||
|
pre_filter: nil,
|
||||||
|
post_filter: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
p.AddParam(session.NewIntParameter("http.port", "80", "", "HTTP port to redirect when the proxy is activated."))
|
||||||
|
p.AddParam(session.NewIntParameter("http.proxy.port", "8080", "", "Port to bind the HTTP proxy to."))
|
||||||
|
p.AddParam(session.NewStringParameter("http.proxy.address", "<interface address>", `^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$`, "Address to bind the HTTP proxy to."))
|
||||||
|
p.AddParam(session.NewStringParameter("http.proxy.post.filter", "", "", "SED like syntax to replace things in the response ( example |</head>|<script src='...'></script></head>| )."))
|
||||||
|
|
||||||
|
p.AddHandler(session.NewModuleHandler("http.proxy (on|off)", "^http\\.proxy (on|off)$",
|
||||||
|
"Start/stop HTTP proxy.",
|
||||||
|
func(args []string) error {
|
||||||
|
if args[0] == "on" {
|
||||||
|
return p.Start()
|
||||||
|
} else {
|
||||||
|
return p.Stop()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
p.proxy.NonproxyHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if p.doProxy(req) == true {
|
||||||
|
req.URL.Scheme = "http"
|
||||||
|
req.URL.Host = req.Host
|
||||||
|
|
||||||
|
// TODO: p.pre_filter.Process
|
||||||
|
|
||||||
|
p.proxy.ServeHTTP(w, req)
|
||||||
|
} else {
|
||||||
|
log.Infof("Skipping %s\n", req.Host)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
p.proxy.OnResponse(goproxy_html.IsHtml).Do(goproxy_html.HandleString(func(body string, ctx *goproxy.ProxyCtx) string {
|
||||||
|
if p.post_filter != nil {
|
||||||
|
body = p.post_filter.Process(ctx.Req, body)
|
||||||
|
}
|
||||||
|
return body
|
||||||
|
}))
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p HttpProxy) Name() string {
|
||||||
|
return "HTTP Proxy"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p HttpProxy) Description() string {
|
||||||
|
return "A full featured HTTP proxy that can be used to inject malicious contents into webpages, all HTTP traffic will be redirected to it."
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p HttpProxy) Author() string {
|
||||||
|
return "Simone Margaritelli <evilsocket@protonmail.com>"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p HttpProxy) OnSessionStarted(s *session.Session) {
|
||||||
|
// refresh the address after session has been created
|
||||||
|
s.Env.Set("http.proxy.address", s.Interface.IpAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p HttpProxy) OnSessionEnded(s *session.Session) {
|
||||||
|
if p.Running() {
|
||||||
|
p.Stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HttpProxy) Start() error {
|
||||||
|
var http_port int
|
||||||
|
var proxy_port int
|
||||||
|
|
||||||
|
if p.Running() == true {
|
||||||
|
return fmt.Errorf("HTTP proxy already started.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err, v := p.Param("http.proxy.address").Get(p.Session); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
p.address = v.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err, v := p.Param("http.proxy.port").Get(p.Session); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
proxy_port = v.(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err, v := p.Param("http.port").Get(p.Session); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
http_port = v.(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.post_filter = nil
|
||||||
|
if err, v := p.Param("http.proxy.post.filter").Get(p.Session); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
expression := v.(string)
|
||||||
|
if expression != "" {
|
||||||
|
if err, p.post_filter = NewProxyFilter("post", expression); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
log.Debug("Proxy POST filter set to '%s'.", expression)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Session.Firewall.IsForwardingEnabled() == false {
|
||||||
|
p.Session.Firewall.EnableForwarding(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.redirection = firewall.NewRedirection(p.Session.Interface.Name(),
|
||||||
|
"TCP",
|
||||||
|
http_port,
|
||||||
|
p.address,
|
||||||
|
proxy_port)
|
||||||
|
|
||||||
|
if err := p.Session.Firewall.EnableRedirection(p.redirection, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
address := fmt.Sprintf("%s:%d", p.address, proxy_port)
|
||||||
|
log.Infof("Starting proxy on %s.\n", address)
|
||||||
|
|
||||||
|
p.server = http.Server{Addr: address, Handler: p.proxy}
|
||||||
|
go func() {
|
||||||
|
p.SetRunning(true)
|
||||||
|
if err := p.server.ListenAndServe(); err != nil {
|
||||||
|
p.SetRunning(false)
|
||||||
|
log.Warning(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HttpProxy) Stop() error {
|
||||||
|
if p.Running() == true {
|
||||||
|
p.SetRunning(false)
|
||||||
|
p.server.Shutdown(nil)
|
||||||
|
log.Info("HTTP proxy stopped.\n")
|
||||||
|
if p.redirection != nil {
|
||||||
|
if err := p.Session.Firewall.EnableRedirection(p.redirection, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.redirection = nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("HTTP proxy stopped.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HttpProxy) doProxy(req *http.Request) bool {
|
||||||
|
blacklist := []string{
|
||||||
|
"localhost",
|
||||||
|
"127.0.0.1",
|
||||||
|
p.address,
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Host == "" {
|
||||||
|
log.Errorf("Got request with empty host: %v\n", req)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, blacklisted := range blacklist {
|
||||||
|
if strings.HasPrefix(req.Host, blacklisted) {
|
||||||
|
log.Errorf("Got request with blacklisted host: %s\n", req.Host)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
138
session/modules/net_probe.go
Normal file
138
session/modules/net_probe.go
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
package session_modules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
// "github.com/evilsocket/bettercap/packets"
|
||||||
|
"github.com/evilsocket/bettercap/session"
|
||||||
|
"github.com/malfunkt/iprange"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Prober struct {
|
||||||
|
session.SessionModule
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProber(s *session.Session) *Prober {
|
||||||
|
p := &Prober{
|
||||||
|
SessionModule: session.NewSessionModule(s),
|
||||||
|
}
|
||||||
|
|
||||||
|
p.AddParam(session.NewIntParameter("net.probe.throttle", "10", "", "If greater than 0, probe packets will be throttled by this value in milliseconds."))
|
||||||
|
|
||||||
|
p.AddHandler(session.NewModuleHandler("net.probe (on|off)", "^net\\.probe\\s+(on|off)$",
|
||||||
|
"Start/stop network hosts probing in background.",
|
||||||
|
func(args []string) error {
|
||||||
|
if args[0] == "on" {
|
||||||
|
return p.Start()
|
||||||
|
} else {
|
||||||
|
return p.Stop()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Prober) Name() string {
|
||||||
|
return "Network Prober"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Prober) Description() string {
|
||||||
|
return "Keep probing for new hosts on the network by sending dummy UDP packets to every possible IP on the subnet."
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Prober) Author() string {
|
||||||
|
return "Simone Margaritelli <evilsocket@protonmail.com>"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Prober) shouldProbe(ip net.IP) bool {
|
||||||
|
addr := ip.String()
|
||||||
|
if ip.IsLoopback() == true {
|
||||||
|
return false
|
||||||
|
} else if addr == p.Session.Interface.IpAddress {
|
||||||
|
return false
|
||||||
|
} else if addr == p.Session.Gateway.IpAddress {
|
||||||
|
return false
|
||||||
|
} else if p.Session.Targets.Has(addr) == true {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Prober) OnSessionEnded(s *session.Session) {
|
||||||
|
if p.Running() {
|
||||||
|
p.Stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Prober) sendUDP(from net.IP, from_hw net.HardwareAddr, ip net.IP) {
|
||||||
|
name := fmt.Sprintf("%s:137", ip)
|
||||||
|
if addr, err := net.ResolveUDPAddr("udp", name); err != nil {
|
||||||
|
log.Errorf("Could not resolve %s.", name)
|
||||||
|
} else if con, err := net.DialUDP("udp", nil, addr); err != nil {
|
||||||
|
log.Errorf("Could not dial %s.", name)
|
||||||
|
} else {
|
||||||
|
// log.Debugf("UDP connection to %s enstablished.\n", name)
|
||||||
|
defer con.Close()
|
||||||
|
con.Write([]byte{0xde, 0xad, 0xbe, 0xef})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Prober) Start() error {
|
||||||
|
if p.Running() == false {
|
||||||
|
throttle := int(0)
|
||||||
|
if err, v := p.Param("net.probe.throttle").Get(p.Session); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
throttle = v.(int)
|
||||||
|
log.Debugf("Throttling packets of %d ms.\n", throttle)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.SetRunning(true)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
list, err := iprange.Parse(p.Session.Interface.CIDR())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
from := p.Session.Interface.IP
|
||||||
|
from_hw := p.Session.Interface.HW
|
||||||
|
addresses := list.Expand()
|
||||||
|
|
||||||
|
log.Infof("Network prober started, probing %d possible addresses.\n", len(addresses))
|
||||||
|
|
||||||
|
for p.Running() {
|
||||||
|
for _, ip := range addresses {
|
||||||
|
if p.shouldProbe(ip) == false {
|
||||||
|
log.Debugf("Skipping address %s from UDP probing.\n", ip)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
p.sendUDP(from, from_hw, ip)
|
||||||
|
|
||||||
|
if throttle > 0 {
|
||||||
|
time.Sleep(time.Duration(throttle) * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Network prober stopped.\n")
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Network prober already started.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Prober) Stop() error {
|
||||||
|
if p.Running() == true {
|
||||||
|
p.SetRunning(false)
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Network prober already stopped.")
|
||||||
|
}
|
||||||
|
}
|
132
session/modules/net_recon.go
Normal file
132
session/modules/net_recon.go
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
package session_modules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/evilsocket/bettercap/net"
|
||||||
|
"github.com/evilsocket/bettercap/session"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Discovery struct {
|
||||||
|
session.SessionModule
|
||||||
|
|
||||||
|
refresh int
|
||||||
|
before net.ArpTable
|
||||||
|
current net.ArpTable
|
||||||
|
quit chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDiscovery(s *session.Session) *Discovery {
|
||||||
|
d := &Discovery{
|
||||||
|
SessionModule: session.NewSessionModule(s),
|
||||||
|
|
||||||
|
refresh: 1,
|
||||||
|
before: nil,
|
||||||
|
current: nil,
|
||||||
|
quit: make(chan bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
d.AddHandler(session.NewModuleHandler("net.recon (on|off)", "^net\\.recon\\s+(on|off)$",
|
||||||
|
"Start/stop network hosts discovery in background.",
|
||||||
|
func(args []string) error {
|
||||||
|
if args[0] == "on" {
|
||||||
|
return d.Start()
|
||||||
|
} else {
|
||||||
|
return d.Stop()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
d.AddHandler(session.NewModuleHandler("net.show", "^net\\.show$",
|
||||||
|
"Show current hosts list.",
|
||||||
|
func(args []string) error {
|
||||||
|
return d.Show()
|
||||||
|
}))
|
||||||
|
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Discovery) Name() string {
|
||||||
|
return "Network Recon"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Discovery) Description() string {
|
||||||
|
return "Read periodically the ARP cache in order to monitor for new hosts on the network."
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Discovery) Author() string {
|
||||||
|
return "Simone Margaritelli <evilsocket@protonmail.com>"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Discovery) OnSessionEnded(s *session.Session) {
|
||||||
|
if d.Running() {
|
||||||
|
d.Stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Discovery) Start() error {
|
||||||
|
if d.Running() == false {
|
||||||
|
d.SetRunning(true)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
log.Info("Network discovery started.\n")
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-time.After(time.Duration(d.refresh) * time.Second):
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if d.current, err = net.ArpUpdate(d.Session.Interface.Name()); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var new net.ArpTable = make(net.ArpTable)
|
||||||
|
var rem net.ArpTable = make(net.ArpTable)
|
||||||
|
|
||||||
|
if d.before != nil {
|
||||||
|
new = net.ArpDiff(d.current, d.before)
|
||||||
|
rem = net.ArpDiff(d.before, d.current)
|
||||||
|
} else {
|
||||||
|
new = d.current
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(new) > 0 || len(rem) > 0 {
|
||||||
|
// refresh target pool
|
||||||
|
for ip, mac := range new {
|
||||||
|
d.Session.Targets.AddIfNotExist(ip, mac)
|
||||||
|
}
|
||||||
|
|
||||||
|
for ip, mac := range rem {
|
||||||
|
d.Session.Targets.Remove(ip, mac)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.before = d.current
|
||||||
|
|
||||||
|
case <-d.quit:
|
||||||
|
log.Info("Network discovery stopped.\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Network discovery already started.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Discovery) Show() error {
|
||||||
|
d.Session.Targets.Dump()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Discovery) Stop() error {
|
||||||
|
if d.Running() == true {
|
||||||
|
d.SetRunning(false)
|
||||||
|
d.quit <- true
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Network discovery already stopped.")
|
||||||
|
}
|
||||||
|
}
|
275
session/modules/net_sniff.go
Normal file
275
session/modules/net_sniff.go
Normal file
|
@ -0,0 +1,275 @@
|
||||||
|
package session_modules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/evilsocket/bettercap/core"
|
||||||
|
"github.com/evilsocket/bettercap/session"
|
||||||
|
"github.com/google/gopacket"
|
||||||
|
"github.com/google/gopacket/layers"
|
||||||
|
"github.com/google/gopacket/pcap"
|
||||||
|
"github.com/google/gopacket/pcapgo"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SnifferContext struct {
|
||||||
|
Handle *pcap.Handle
|
||||||
|
DumpLocal bool
|
||||||
|
Verbose bool
|
||||||
|
Filter string
|
||||||
|
Expression string
|
||||||
|
Compiled *regexp.Regexp
|
||||||
|
Output string
|
||||||
|
OutputFile *os.File
|
||||||
|
OutputWriter *pcapgo.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSnifferContext() *SnifferContext {
|
||||||
|
return &SnifferContext{
|
||||||
|
Handle: nil,
|
||||||
|
DumpLocal: false,
|
||||||
|
Verbose: true,
|
||||||
|
Filter: "",
|
||||||
|
Expression: "",
|
||||||
|
Compiled: nil,
|
||||||
|
Output: "",
|
||||||
|
OutputFile: nil,
|
||||||
|
OutputWriter: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
no = core.Red("no")
|
||||||
|
yes = core.Green("yes")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *SnifferContext) Log() {
|
||||||
|
log.Info("\n")
|
||||||
|
|
||||||
|
if c.DumpLocal {
|
||||||
|
log.Info(" Skip local packets : " + no)
|
||||||
|
} else {
|
||||||
|
log.Info(" Skip local packets : " + yes)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Verbose {
|
||||||
|
log.Info(" Verbose : " + yes)
|
||||||
|
} else {
|
||||||
|
log.Info(" Verbose : " + no)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Filter != "" {
|
||||||
|
log.Info(" BPF Filter : '" + core.Yellow(c.Filter) + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Expression != "" {
|
||||||
|
log.Info(" Regular expression : '" + core.Yellow(c.Expression) + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Output != "" {
|
||||||
|
log.Info(" File output : '" + core.Yellow(c.Output) + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SnifferContext) Close() {
|
||||||
|
if c.Handle != nil {
|
||||||
|
c.Handle.Close()
|
||||||
|
c.Handle = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.OutputFile != nil {
|
||||||
|
c.OutputFile.Close()
|
||||||
|
c.OutputFile = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Sniffer struct {
|
||||||
|
session.SessionModule
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSniffer(s *session.Session) *Sniffer {
|
||||||
|
sniff := &Sniffer{
|
||||||
|
SessionModule: session.NewSessionModule(s),
|
||||||
|
}
|
||||||
|
|
||||||
|
sniff.AddParam(session.NewBoolParameter("net.sniffer.verbose", "true", "", "Print captured packets to screen."))
|
||||||
|
sniff.AddParam(session.NewBoolParameter("net.sniffer.local", "false", "", "If true it will consider packets from/to this computer, otherwise it will skip them."))
|
||||||
|
sniff.AddParam(session.NewStringParameter("net.sniffer.filter", "not arp", "", "BPF filter for the sniffer."))
|
||||||
|
sniff.AddParam(session.NewStringParameter("net.sniffer.regexp", "", "", "If filled, only packets matching this regular expression will be considered."))
|
||||||
|
sniff.AddParam(session.NewStringParameter("net.sniffer.output", "", "", "If set, the sniffer will write captured packets to this file."))
|
||||||
|
|
||||||
|
sniff.AddHandler(session.NewModuleHandler("net.sniffer (on|off)", "^net\\.sniffer\\s+(on|off)$",
|
||||||
|
"Start/stop network sniffer in background.",
|
||||||
|
func(args []string) error {
|
||||||
|
if args[0] == "on" {
|
||||||
|
return sniff.Start()
|
||||||
|
} else {
|
||||||
|
return sniff.Stop()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return sniff
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Sniffer) Name() string {
|
||||||
|
return "Network Sniffer"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Sniffer) Description() string {
|
||||||
|
return "Sniff packets from the network."
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Sniffer) Author() string {
|
||||||
|
return "Simone Margaritelli <evilsocket@protonmail.com>"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sn Sniffer) OnSessionEnded(s *session.Session) {
|
||||||
|
if sn.Running() {
|
||||||
|
sn.Stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func same(a, b net.HardwareAddr) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, v := range a {
|
||||||
|
if b[idx] != v {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Sniffer) isLocalPacket(packet gopacket.Packet) bool {
|
||||||
|
local_hw := s.Session.Interface.HW
|
||||||
|
eth := packet.Layer(layers.LayerTypeEthernet)
|
||||||
|
if eth != nil {
|
||||||
|
eth_packet, _ := eth.(*layers.Ethernet)
|
||||||
|
if same(eth_packet.SrcMAC, local_hw) || same(eth_packet.DstMAC, local_hw) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Sniffer) GetContext() (error, *SnifferContext) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
ctx := NewSnifferContext()
|
||||||
|
|
||||||
|
if ctx.Handle, err = pcap.OpenLive(s.Session.Interface.Name(), 65536, true, pcap.BlockForever); err != nil {
|
||||||
|
return err, ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
if err, v := s.Param("net.sniffer.verbose").Get(s.Session); err != nil {
|
||||||
|
return err, ctx
|
||||||
|
} else {
|
||||||
|
ctx.Verbose = v.(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err, v := s.Param("net.sniffer.local").Get(s.Session); err != nil {
|
||||||
|
return err, ctx
|
||||||
|
} else {
|
||||||
|
ctx.DumpLocal = v.(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err, v := s.Param("net.sniffer.filter").Get(s.Session); err != nil {
|
||||||
|
return err, ctx
|
||||||
|
} else {
|
||||||
|
if ctx.Filter = v.(string); ctx.Filter != "" {
|
||||||
|
err = ctx.Handle.SetBPFFilter(ctx.Filter)
|
||||||
|
if err != nil {
|
||||||
|
return err, ctx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err, v := s.Param("net.sniffer.regexp").Get(s.Session); err != nil {
|
||||||
|
return err, ctx
|
||||||
|
} else {
|
||||||
|
if ctx.Expression = v.(string); ctx.Expression != "" {
|
||||||
|
if ctx.Compiled, err = regexp.Compile(ctx.Expression); err != nil {
|
||||||
|
return err, ctx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err, v := s.Param("net.sniffer.output").Get(s.Session); err != nil {
|
||||||
|
return err, ctx
|
||||||
|
} else {
|
||||||
|
if ctx.Output = v.(string); ctx.Output != "" {
|
||||||
|
if ctx.OutputFile, err = os.Create(ctx.Output); err != nil {
|
||||||
|
return err, ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.OutputWriter = pcapgo.NewWriter(ctx.OutputFile)
|
||||||
|
ctx.OutputWriter.WriteFileHeader(65536, layers.LinkTypeEthernet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Sniffer) Start() error {
|
||||||
|
if s.Running() == false {
|
||||||
|
var err error
|
||||||
|
var ctx *SnifferContext
|
||||||
|
|
||||||
|
if err, ctx = s.GetContext(); err != nil {
|
||||||
|
if ctx != nil {
|
||||||
|
ctx.Close()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.SetRunning(true)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer ctx.Close()
|
||||||
|
|
||||||
|
log.Info("Network sniffer started.\n")
|
||||||
|
ctx.Log()
|
||||||
|
|
||||||
|
src := gopacket.NewPacketSource(ctx.Handle, ctx.Handle.LinkType())
|
||||||
|
for packet := range src.Packets() {
|
||||||
|
if s.Running() == false {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.DumpLocal == true || s.isLocalPacket(packet) == false {
|
||||||
|
data := packet.Data()
|
||||||
|
if ctx.Compiled == nil || ctx.Compiled.Match(data) == true {
|
||||||
|
if ctx.Verbose {
|
||||||
|
fmt.Println(packet.Dump())
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.OutputWriter != nil {
|
||||||
|
ctx.OutputWriter.WritePacket(packet.Metadata().CaptureInfo, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Network sniffer stopped.\n")
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Network sniffer already started.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Sniffer) Stop() error {
|
||||||
|
if s.Running() == true {
|
||||||
|
s.SetRunning(false)
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Network sniffer already stopped.")
|
||||||
|
}
|
||||||
|
}
|
408
session/session.go
Normal file
408
session/session.go
Normal file
|
@ -0,0 +1,408 @@
|
||||||
|
package session
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"os/user"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/chzyer/readline"
|
||||||
|
"github.com/op/go-logging"
|
||||||
|
|
||||||
|
"github.com/evilsocket/bettercap/core"
|
||||||
|
"github.com/evilsocket/bettercap/firewall"
|
||||||
|
"github.com/evilsocket/bettercap/net"
|
||||||
|
"github.com/evilsocket/bettercap/packets"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
Options core.Options
|
||||||
|
Interface *net.Endpoint
|
||||||
|
Gateway *net.Endpoint
|
||||||
|
Firewall firewall.FirewallManager
|
||||||
|
Env *Environment
|
||||||
|
Targets *Targets
|
||||||
|
Queue *packets.Queue
|
||||||
|
Input *readline.Instance
|
||||||
|
Active bool
|
||||||
|
|
||||||
|
// Watcher *discovery.Watcher
|
||||||
|
CoreHandlers []CommandHandler
|
||||||
|
Modules []Module
|
||||||
|
HelpPadding int
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() (*Session, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
s := &Session{
|
||||||
|
Env: NewEnvironment(),
|
||||||
|
Active: false,
|
||||||
|
Queue: nil,
|
||||||
|
|
||||||
|
CoreHandlers: make([]CommandHandler, 0),
|
||||||
|
Modules: make([]Module, 0),
|
||||||
|
HelpPadding: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Options, err = core.ParseOptions(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if u, err := user.Current(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if u.Uid != "0" {
|
||||||
|
return nil, fmt.Errorf("This software must run as root.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup logging
|
||||||
|
if *s.Options.Debug == true {
|
||||||
|
logging.SetLevel(logging.DEBUG, "")
|
||||||
|
} else if *s.Options.Silent == true {
|
||||||
|
logging.SetLevel(logging.ERROR, "")
|
||||||
|
} else {
|
||||||
|
logging.SetLevel(logging.INFO, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.registerCoreHandlers()
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) registerCoreHandlers() {
|
||||||
|
s.CoreHandlers = append(s.CoreHandlers, NewCommandHandler("help", "^(help|\\?)$",
|
||||||
|
"Display list of available commands.",
|
||||||
|
func(args []string, s *Session) error {
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("Basic commands:\n\n")
|
||||||
|
for _, h := range s.CoreHandlers {
|
||||||
|
fmt.Printf(" "+core.Bold("%"+strconv.Itoa(s.HelpPadding)+"s")+" : %s\n", h.Name, h.Description)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(s.Modules, func(i, j int) bool {
|
||||||
|
return s.Modules[i].Name() < s.Modules[j].Name()
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, m := range s.Modules {
|
||||||
|
fmt.Println()
|
||||||
|
status := ""
|
||||||
|
if m.Running() {
|
||||||
|
status = core.Green("active")
|
||||||
|
} else {
|
||||||
|
status = core.Red("not active")
|
||||||
|
}
|
||||||
|
fmt.Printf("%s [%s]\n", m.Name(), status)
|
||||||
|
fmt.Println(core.Dim(m.Description()) + "\n")
|
||||||
|
for _, h := range m.Handlers() {
|
||||||
|
fmt.Printf(h.Help(s.HelpPadding))
|
||||||
|
}
|
||||||
|
|
||||||
|
params := m.Parameters()
|
||||||
|
if len(params) > 0 {
|
||||||
|
fmt.Printf("\n Parameters\n\n")
|
||||||
|
for _, p := range params {
|
||||||
|
fmt.Printf(p.Help(s.HelpPadding))
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
|
||||||
|
s.CoreHandlers = append(s.CoreHandlers, NewCommandHandler("active", "^active$",
|
||||||
|
"Show information about active modules.",
|
||||||
|
func(args []string, s *Session) error {
|
||||||
|
for _, m := range s.Modules {
|
||||||
|
if m.Running() == false {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Printf("[%s] %s (%s)\n", core.Green("active"), m.Name(), core.Dim(m.Description()))
|
||||||
|
params := m.Parameters()
|
||||||
|
if len(params) > 0 {
|
||||||
|
for _, p := range params {
|
||||||
|
_, p.Value = s.Env.Get(p.Name)
|
||||||
|
fmt.Printf(" %s: '%s'\n", p.Name, core.Yellow(p.Value))
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
|
||||||
|
s.CoreHandlers = append(s.CoreHandlers, NewCommandHandler("exit", "^(q|quit|e|exit)$",
|
||||||
|
"Close the session and exit.",
|
||||||
|
func(args []string, s *Session) error {
|
||||||
|
s.Active = false
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
|
||||||
|
s.CoreHandlers = append(s.CoreHandlers, NewCommandHandler("sleep SECONDS", "^sleep\\s+(\\d+)$",
|
||||||
|
"Sleep for the given amount of seconds.",
|
||||||
|
func(args []string, s *Session) error {
|
||||||
|
if secs, err := strconv.Atoi(args[0]); err == nil {
|
||||||
|
time.Sleep(time.Duration(secs) * time.Second)
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
s.CoreHandlers = append(s.CoreHandlers, NewCommandHandler("get NAME", "^get\\s+(.+)",
|
||||||
|
"Get the value of variable NAME, use * for all.",
|
||||||
|
func(args []string, s *Session) error {
|
||||||
|
key := args[0]
|
||||||
|
if key == "*" {
|
||||||
|
prev_ns := ""
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
for _, k := range s.Env.Sorted() {
|
||||||
|
ns := ""
|
||||||
|
toks := strings.Split(k, ".")
|
||||||
|
if len(toks) > 0 {
|
||||||
|
ns = toks[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if ns != prev_ns {
|
||||||
|
fmt.Println()
|
||||||
|
prev_ns = ns
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf(" %"+strconv.Itoa(s.Env.Padding)+"s: '%s'\n", k, s.Env.storage[k])
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
} else if found, value := s.Env.Get(key); found == true {
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf(" %s: '%s'\n", key, value)
|
||||||
|
fmt.Println()
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("%s not found", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
|
||||||
|
s.CoreHandlers = append(s.CoreHandlers, NewCommandHandler("set NAME VALUE", "^set\\s+([^\\s]+)\\s+(.+)",
|
||||||
|
"Set the VALUE of variable NAME.",
|
||||||
|
func(args []string, s *Session) error {
|
||||||
|
key := args[0]
|
||||||
|
value := args[1]
|
||||||
|
|
||||||
|
if value == "\"\"" {
|
||||||
|
value = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Env.Set(key, value)
|
||||||
|
fmt.Printf(" %s => '%s'\n", core.Green(key), core.Yellow(value))
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) setupInput() error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
pcompleters := make([]readline.PrefixCompleterInterface, 0)
|
||||||
|
for _, h := range s.CoreHandlers {
|
||||||
|
pcompleters = append(pcompleters, readline.PcItem(h.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range s.Modules {
|
||||||
|
for _, h := range m.Handlers() {
|
||||||
|
pcompleters = append(pcompleters, readline.PcItem(h.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
history := ""
|
||||||
|
if *s.Options.NoHistory == false {
|
||||||
|
history = "bettercap.history"
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := readline.Config{
|
||||||
|
HistoryFile: history,
|
||||||
|
InterruptPrompt: "^C",
|
||||||
|
EOFPrompt: "exit",
|
||||||
|
HistorySearchFold: true,
|
||||||
|
AutoComplete: readline.NewPrefixCompleter(pcompleters...),
|
||||||
|
FuncFilterInputRune: func(r rune) (rune, bool) {
|
||||||
|
switch r {
|
||||||
|
// block CtrlZ feature
|
||||||
|
case readline.CharCtrlZ:
|
||||||
|
return r, false
|
||||||
|
}
|
||||||
|
return r, true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Input, err = readline.NewEx(&cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// now that we have the readline instance, we can set logging to its
|
||||||
|
// console writer so the whole thing gets correctly updated when something
|
||||||
|
// is logged to screen (it won't overlap with the prompt).
|
||||||
|
log_be := logging.NewLogBackend(s.Input.Stderr(), "", 0)
|
||||||
|
log_level := logging.AddModuleLevel(log_be)
|
||||||
|
if *s.Options.Debug == true {
|
||||||
|
log_level.SetLevel(logging.DEBUG, "")
|
||||||
|
} else if *s.Options.Silent == true {
|
||||||
|
log_level.SetLevel(logging.ERROR, "")
|
||||||
|
} else {
|
||||||
|
log_level.SetLevel(logging.INFO, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
logging.SetBackend(log_level)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) Close() {
|
||||||
|
for _, m := range s.Modules {
|
||||||
|
m.OnSessionEnded(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Firewall.Restore()
|
||||||
|
s.Queue.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) Register(mod Module) error {
|
||||||
|
s.Modules = append(s.Modules, mod)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) Start() error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
net.OuiInit()
|
||||||
|
|
||||||
|
if s.Interface, err = net.FindInterface(*s.Options.InterfaceName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Env.Set("iface.name", s.Interface.Name())
|
||||||
|
s.Env.Set("iface.address", s.Interface.IpAddress)
|
||||||
|
s.Env.Set("iface.mac", s.Interface.HwAddress)
|
||||||
|
|
||||||
|
if s.Queue, err = packets.NewQueue(s.Interface.Name()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("[%s%s%s] %s\n", core.GREEN, s.Interface.Name(), core.RESET, s.Interface)
|
||||||
|
log.Debugf("[%ssubnet%s] %s\n", core.GREEN, core.RESET, s.Interface.CIDR())
|
||||||
|
|
||||||
|
if s.Gateway, err = net.FindGateway(s.Interface); err != nil {
|
||||||
|
log.Warningf("%s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Gateway == nil || s.Gateway.IpAddress == s.Interface.IpAddress {
|
||||||
|
s.Gateway = s.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Env.Set("gateway.address", s.Gateway.IpAddress)
|
||||||
|
s.Env.Set("gateway.mac", s.Gateway.HwAddress)
|
||||||
|
|
||||||
|
log.Debugf("[%sgateway%s] %s\n", core.GREEN, core.RESET, s.Gateway)
|
||||||
|
|
||||||
|
s.Targets = NewTargets(s.Interface, s.Gateway)
|
||||||
|
s.Firewall = firewall.Make()
|
||||||
|
|
||||||
|
if err := s.setupInput(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, h := range s.CoreHandlers {
|
||||||
|
if len(h.Name) > s.HelpPadding {
|
||||||
|
s.HelpPadding = len(h.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, m := range s.Modules {
|
||||||
|
for _, h := range m.Handlers() {
|
||||||
|
if len(h.Name) > s.HelpPadding {
|
||||||
|
s.HelpPadding = len(h.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range m.Parameters() {
|
||||||
|
if len(p.Name) > s.HelpPadding {
|
||||||
|
s.HelpPadding = len(p.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(c, os.Interrupt)
|
||||||
|
signal.Notify(c, syscall.SIGTERM)
|
||||||
|
go func() {
|
||||||
|
<-c
|
||||||
|
fmt.Println()
|
||||||
|
log.Warning("Got SIGTERM ...")
|
||||||
|
s.Close()
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
|
||||||
|
s.Active = true
|
||||||
|
|
||||||
|
for _, m := range s.Modules {
|
||||||
|
m.OnSessionStarted(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) ReadLine() (string, error) {
|
||||||
|
s.Input.SetPrompt(core.GREEN + s.Interface.IpAddress + core.RESET + "» ")
|
||||||
|
s.Input.Refresh()
|
||||||
|
return s.Input.Readline()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) RunCaplet(filename string) error {
|
||||||
|
log.Infof("Reading from caplet %s ...\n", filename)
|
||||||
|
|
||||||
|
input, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer input.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(input)
|
||||||
|
scanner.Split(bufio.ScanLines)
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if line == "" || line[0] == '#' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = s.Run(line); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) Run(line string) error {
|
||||||
|
line = strings.TrimRight(line, " ")
|
||||||
|
for _, h := range s.CoreHandlers {
|
||||||
|
if parsed, args := h.Parse(line); parsed == true {
|
||||||
|
return h.Exec(args, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range s.Modules {
|
||||||
|
for _, h := range m.Handlers() {
|
||||||
|
if parsed, args := h.Parse(line); parsed == true {
|
||||||
|
return h.Exec(args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Unknown command %s%s%s, type %shelp%s for the help menu.", core.BOLD, line, core.RESET, core.BOLD, core.RESET)
|
||||||
|
}
|
110
session/targets.go
Normal file
110
session/targets.go
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
package session
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/op/go-logging"
|
||||||
|
|
||||||
|
"github.com/evilsocket/bettercap/core"
|
||||||
|
"github.com/evilsocket/bettercap/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = logging.MustGetLogger("mitm")
|
||||||
|
|
||||||
|
type Targets struct {
|
||||||
|
Interface *net.Endpoint
|
||||||
|
Gateway *net.Endpoint
|
||||||
|
Targets map[string]*net.Endpoint
|
||||||
|
lock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTargets(iface, gateway *net.Endpoint) *Targets {
|
||||||
|
return &Targets{
|
||||||
|
Interface: iface,
|
||||||
|
Gateway: gateway,
|
||||||
|
Targets: make(map[string]*net.Endpoint),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp *Targets) Remove(ip, mac string) {
|
||||||
|
tp.lock.Lock()
|
||||||
|
defer tp.lock.Unlock()
|
||||||
|
|
||||||
|
if e, found := tp.Targets[mac]; found {
|
||||||
|
log.Infof("[%slost%s] %s\n", core.RED, core.RESET, e)
|
||||||
|
delete(tp.Targets, mac)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp *Targets) shouldIgnore(ip string) bool {
|
||||||
|
return (ip == tp.Interface.IpAddress || ip == tp.Gateway.IpAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp *Targets) Has(ip string) bool {
|
||||||
|
tp.lock.Lock()
|
||||||
|
defer tp.lock.Unlock()
|
||||||
|
|
||||||
|
for _, e := range tp.Targets {
|
||||||
|
if e.IpAddress == ip {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp *Targets) AddIfNotExist(ip, mac string) {
|
||||||
|
tp.lock.Lock()
|
||||||
|
defer tp.lock.Unlock()
|
||||||
|
|
||||||
|
if tp.shouldIgnore(ip) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if t, found := tp.Targets[mac]; found {
|
||||||
|
t.IpAddress = ip
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e := net.NewEndpoint(ip, mac)
|
||||||
|
log.Infof("[%snew%s] %s\n", core.GREEN, core.RESET, e)
|
||||||
|
tp.Targets[mac] = e
|
||||||
|
}
|
||||||
|
|
||||||
|
type tSorter []*net.Endpoint
|
||||||
|
|
||||||
|
func (a tSorter) Len() int { return len(a) }
|
||||||
|
func (a tSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a tSorter) Less(i, j int) bool { return a[i].IpAddressUint32 < a[j].IpAddressUint32 }
|
||||||
|
|
||||||
|
func (tp *Targets) Dump() {
|
||||||
|
tp.lock.Lock()
|
||||||
|
defer tp.lock.Unlock()
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf(" " + core.GREEN + "interface" + core.RESET + "\n\n")
|
||||||
|
fmt.Printf(" " + tp.Interface.String() + "\n")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf(" " + core.GREEN + "gateway" + core.RESET + "\n\n")
|
||||||
|
fmt.Printf(" " + tp.Gateway.String() + "\n")
|
||||||
|
|
||||||
|
if len(tp.Targets) > 0 {
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf(" " + core.GREEN + "hosts" + core.RESET + "\n\n")
|
||||||
|
targets := make([]*net.Endpoint, 0, len(tp.Targets))
|
||||||
|
for _, t := range tp.Targets {
|
||||||
|
targets = append(targets, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(tSorter(targets))
|
||||||
|
|
||||||
|
for _, t := range targets {
|
||||||
|
fmt.Println(" " + t.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue