yeah i should have done this before, i know

This commit is contained in:
evilsocket 2017-11-17 14:49:59 +01:00
commit 0091ffdbb3
33 changed files with 25678 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
*.cap
bettercap
net/oui_compiled.go

31
Makefile Normal file
View 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
View 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
View file

@ -0,0 +1,4 @@
package core
const (
BuildDate = "2017-11-16 19:24"
)

25
core/core.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

54
net/oui.go Normal file
View 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
View 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(&eth, &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(&eth, &arp)
}

58
packets/queue.go Normal file
View 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
View 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
View 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(&eth, &ip4, &udp)
}

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

Binary file not shown.

View 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.")
}
}

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

View 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.")
}
}

View 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.")
}
}

View 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
View 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
View 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()
}