Commit 68a95efd authored by Sophie Brun's avatar Sophie Brun

New upstream version 2.23

parent 13d6cf85
......@@ -19,11 +19,11 @@
[[projects]]
branch = "master"
digest = "1:7e4a9eaa42cb3b4cd48bd0cd2072a029fe25a47af20cfac529ea8c365f8559ac"
digest = "1:3a4202162c0d85f2a5fed5675bd35e6686c8b379c63e40ef105bf3e7d76d97f5"
name = "github.com/antchfx/xpath"
packages = ["."]
pruneopts = "UT"
revision = "c8489ed3251e7d55ec2b7f18a2bc3a9a7222f0af"
revision = "ce1d48779e67a1ddfb380995fe532b2e0015919c"
[[projects]]
branch = "master"
......@@ -58,6 +58,14 @@
revision = "62c6fe6193755f722b8b8788aa7357be55a50ff1"
version = "v1.4"
[[projects]]
branch = "master"
digest = "1:c8bc90a7d67587dda6b8a90e570c411874fa01117eb383527c5e36d4fae5158a"
name = "github.com/bettercap/recording"
packages = ["."]
pruneopts = "UT"
revision = "1396b95921b3cc1cb1cee3280c7f6be6c7f06b06"
[[projects]]
branch = "master"
digest = "1:8efd09ca363b01b7dca5baf091d65473df5f08f107b7c3fcd93c605189e031ed"
......@@ -209,16 +217,16 @@
version = "v0.1.1"
[[projects]]
digest = "1:3bb9c8451d199650bfd303e0068d86f135952fead374ad87c09a9b8a2cc4bd7c"
digest = "1:e150b5fafbd7607e2d638e4e5cf43aa4100124e5593385147b0a74e2733d8b0d"
name = "github.com/mattn/go-isatty"
packages = ["."]
pruneopts = "UT"
revision = "369ecd8cea9851e459abb67eb171853e3986591e"
version = "v0.0.6"
revision = "c2a7a6ca930a4cd0bc33a3f298eb71960732a3a7"
version = "v0.0.7"
[[projects]]
branch = "master"
digest = "1:0e2c7e1de0daaa759dac2b7feb90fdc944f1d23a6e0c8c20502cb635bcd2aaba"
digest = "1:b2a92f887089bda5ea0b5c885d6e785581756da3e24ed86df5566c60721a70ec"
name = "github.com/mdlayher/dhcp6"
packages = [
".",
......@@ -226,15 +234,15 @@
"internal/buffer",
]
pruneopts = "UT"
revision = "775147d26a880f77f6e06d1a28fbeb2ec4fec013"
revision = "2a67805d7d0b0bad6c1103058981afdea583b459"
[[projects]]
branch = "master"
digest = "1:34fe44dd2bbe5723068e0a7a266847965a88297d383fe611e0358e556d82de09"
digest = "1:34bd596081cc218eb9f14ddcc712015bed5b9b84917c6279640397f534565e72"
name = "github.com/mdlayher/raw"
packages = ["."]
pruneopts = "UT"
revision = "480b93709cce56651807d3fdeb260a5a7c4e2d5f"
revision = "b0647ab7d8b35d5b8b734462042628e5af82f7f4"
[[projects]]
branch = "master"
......@@ -290,15 +298,15 @@
name = "golang.org/x/net"
packages = ["bpf"]
pruneopts = "UT"
revision = "16b79f2e4e95ea23b2bf9903c9809ff7b013ce85"
revision = "74de082e2cca95839e88aa0aeee5aadf6ce7710f"
[[projects]]
branch = "master"
digest = "1:a2bc6cb1f4e1d0b512e1d47d392ead580006b5bdade6ffde16271759fe609b34"
digest = "1:cd9a35e005d99871df9dc2354bc9dd8f09d186e2efa6affae6593f5ef93876b0"
name = "golang.org/x/sys"
packages = ["unix"]
pruneopts = "UT"
revision = "b6889370fb1098ed892bd3400d189bb6a3355813"
revision = "baf5eb976a8cd65845293cd814ea151018552292"
[[projects]]
digest = "1:9935525a8c49b8434a0b0a54e1980e94a6fae73aaff45c5d33ba8dff69de123e"
......@@ -320,6 +328,7 @@
"github.com/bettercap/gatt",
"github.com/bettercap/nrf24",
"github.com/bettercap/readline",
"github.com/bettercap/recording",
"github.com/chifflier/nfqueue-go/nfqueue",
"github.com/dustin/go-humanize",
"github.com/elazarl/goproxy",
......@@ -337,11 +346,11 @@
"github.com/google/gopacket/layers",
"github.com/google/gopacket/pcap",
"github.com/google/gopacket/pcapgo",
"github.com/google/gousb",
"github.com/gorilla/mux",
"github.com/gorilla/websocket",
"github.com/inconshreveable/go-vhost",
"github.com/jpillora/go-tld",
"github.com/kr/binarydist",
"github.com/malfunkt/iprange",
"github.com/mdlayher/dhcp6",
"github.com/mdlayher/dhcp6/dhcp6opts",
......
......@@ -10,6 +10,10 @@
name = "github.com/bettercap/readline"
version = "1.4.0"
[[constraint]]
branch = "master"
name = "github.com/bettercap/recording"
[[constraint]]
branch = "master"
name = "github.com/chifflier/nfqueue-go"
......@@ -73,7 +77,3 @@
[prune]
go-tests = true
unused-packages = true
[[constraint]]
name = "github.com/kr/binarydist"
version = "0.1.0"
......@@ -31,7 +31,7 @@ While the first version (up to 1.6.2) of bettercap was implemented in Ruby and o
This ground-up rewrite offered several advantages:
* bettercap can now be distributed as a **single binary** with very few dependencies, for basically **any OS and any architecture**.
* 1.x proxies, altough highly optimized and event based, **[used to bottleneck the entire network](https://en.wikipedia.org/wiki/Global_interpreter_lock)** when performing a MITM attack, while the new version adds almost no overhead.
* 1.x proxies, although highly optimized and event based, **[used to bottleneck the entire network](https://en.wikipedia.org/wiki/Global_interpreter_lock)** when performing a MITM attack, while the new version adds almost no overhead.
* Due to such **performance and functional limitations**, most of the features that the 2.x version is offering were simply impossible to implement properly (read as: without killing the entire network ... or your computer).
For this reason, **any version prior to 2.x is considered deprecated** and any type of support has been dropped in favor of the new implementation. An archived copy of the legacy documentation is [available here](https://www.bettercap.org/legacy/), however **it is strongly suggested to upgrade**.
......
......@@ -2,7 +2,7 @@ package core
const (
Name = "bettercap"
Version = "2.22"
Version = "2.23"
Author = "Simone 'evilsocket' Margaritelli"
Website = "https://bettercap.org/"
)
......@@ -10,6 +10,8 @@ import (
"github.com/bettercap/bettercap/session"
"github.com/bettercap/bettercap/tls"
"github.com/bettercap/recording"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
......@@ -35,7 +37,7 @@ type RestAPI struct {
replaying bool
recordFileName string
recordWait *sync.WaitGroup
record *Record
record *recording.Archive
recStarted time.Time
recStopped time.Time
}
......
......@@ -95,7 +95,7 @@ func (mod *RestAPI) patchFrame(buf []byte) (frame map[string]interface{}, err er
func (mod *RestAPI) showSession(w http.ResponseWriter, r *http.Request) {
if mod.replaying {
if !mod.record.Session.Over() {
from := mod.record.Session.CurFrame() - 1
from := mod.record.Session.Index() - 1
q := r.URL.Query()
vals := q["from"]
if len(vals) > 0 {
......@@ -106,12 +106,12 @@ func (mod *RestAPI) showSession(w http.ResponseWriter, r *http.Request) {
mod.record.Session.SetFrom(from)
mod.Debug("replaying session %d of %d from %s",
mod.record.Session.CurFrame(),
mod.record.Session.Index(),
mod.record.Session.Frames(),
mod.recordFileName)
mod.State.Store("rec_frames", mod.record.Session.Frames())
mod.State.Store("rec_cur_frame", mod.record.Session.CurFrame())
mod.State.Store("rec_cur_frame", mod.record.Session.Index())
buf := mod.record.Session.Next()
if frame, err := mod.patchFrame(buf); err != nil {
......@@ -254,7 +254,7 @@ func (mod *RestAPI) showEvents(w http.ResponseWriter, r *http.Request) {
if mod.replaying {
if !mod.record.Events.Over() {
from := mod.record.Events.CurFrame() - 1
from := mod.record.Events.Index() - 1
vals := q["from"]
if len(vals) > 0 {
if n, err := strconv.Atoi(vals[0]); err == nil {
......@@ -264,7 +264,7 @@ func (mod *RestAPI) showEvents(w http.ResponseWriter, r *http.Request) {
mod.record.Events.SetFrom(from)
mod.Debug("replaying events %d of %d from %s",
mod.record.Events.CurFrame(),
mod.record.Events.Index(),
mod.record.Events.Frames(),
mod.recordFileName)
......
......@@ -7,6 +7,8 @@ import (
"fmt"
"time"
"github.com/bettercap/recording"
"github.com/evilsocket/islazy/fs"
)
......@@ -45,7 +47,7 @@ func (mod *RestAPI) recorder() {
mod.recTime = 0
mod.recording = true
mod.replaying = false
mod.record = NewRecord(mod.recordFileName, &mod.SessionModule)
mod.record = recording.New(mod.recordFileName)
mod.Info("started recording to %s (clock %s) ...", mod.recordFileName, clock)
......
......@@ -5,6 +5,8 @@ import (
"fmt"
"time"
"github.com/bettercap/recording"
"github.com/evilsocket/islazy/fs"
)
......@@ -38,7 +40,10 @@ func (mod *RestAPI) startReplay(filename string) (err error) {
mod.Info("loading %s ...", mod.recordFileName)
start := time.Now()
if mod.record, err = LoadRecord(mod.recordFileName, &mod.SessionModule); err != nil {
mod.record, err = recording.Load(mod.recordFileName, func(progress float64, done int, total int) {
mod.State.Store("load_progress", progress)
})
if err != nil {
return err
}
loadedIn := time.Since(start)
......
package api_rest
import (
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"sync"
"time"
"github.com/bettercap/bettercap/session"
"github.com/evilsocket/islazy/fs"
"github.com/kr/binarydist"
)
type patch []byte
type frame []byte
type progressCallback func(done int)
type RecordEntry struct {
sync.Mutex
Data []byte `json:"data"`
Cur []byte `json:"-"`
States []patch `json:"states"`
NumStates int `json:"-"`
CurState int `json:"-"`
frames []frame
progress progressCallback
}
func NewRecordEntry(progress progressCallback) *RecordEntry {
return &RecordEntry{
Data: nil,
Cur: nil,
States: make([]patch, 0),
NumStates: 0,
CurState: 0,
frames: nil,
progress: progress,
}
}
func (e *RecordEntry) AddState(state []byte) error {
e.Lock()
defer e.Unlock()
// set reference state
if e.Data == nil {
e.Data = state
} else {
// create a patch
oldReader := bytes.NewReader(e.Cur)
newReader := bytes.NewReader(state)
writer := new(bytes.Buffer)
if err := binarydist.Diff(oldReader, newReader, writer); err != nil {
return err
}
e.States = append(e.States, patch(writer.Bytes()))
e.NumStates++
e.CurState = 0
}
e.Cur = state
return nil
}
func (e *RecordEntry) Reset() {
e.Lock()
defer e.Unlock()
e.Cur = e.Data
e.NumStates = len(e.States)
e.CurState = 0
}
func (e *RecordEntry) Compile() error {
e.Lock()
defer e.Unlock()
// reset the state
e.Cur = e.Data
e.NumStates = len(e.States)
e.CurState = 0
e.frames = make([]frame, e.NumStates+1)
// first is the master frame
e.frames[0] = frame(e.Data)
// precompute frames so they can be accessed by index
for i := 0; i < e.NumStates; i++ {
patch := e.States[i]
oldReader := bytes.NewReader(e.Cur)
patchReader := bytes.NewReader(patch)
newWriter := new(bytes.Buffer)
if err := binarydist.Patch(oldReader, newWriter, patchReader); err != nil {
return err
}
e.Cur = newWriter.Bytes()
e.frames[i+1] = e.Cur
e.progress(1)
}
e.progress(1)
return nil
}
func (e *RecordEntry) Frames() int {
e.Lock()
defer e.Unlock()
// master + sub states
return e.NumStates + 1
}
func (e *RecordEntry) CurFrame() int {
e.Lock()
defer e.Unlock()
return e.CurState + 1
}
func (e *RecordEntry) SetFrom(from int) {
e.Lock()
defer e.Unlock()
e.CurState = from
}
func (e *RecordEntry) Over() bool {
e.Lock()
defer e.Unlock()
return e.CurState > e.NumStates
}
func (e *RecordEntry) Next() []byte {
e.Lock()
defer e.Unlock()
cur := e.CurState
e.CurState++
return e.frames[cur]
}
func (e *RecordEntry) TimeOf(idx int) time.Time {
e.Lock()
defer e.Unlock()
buf := e.frames[idx]
frame := make(map[string]interface{})
if err := json.Unmarshal(buf, &frame); err != nil {
fmt.Printf("%v\n", err)
return time.Time{}
} else if tm, err := time.Parse(time.RFC3339, frame["polled_at"].(string)); err != nil {
fmt.Printf("%v\n", err)
return time.Time{}
} else {
return tm
}
}
func (e *RecordEntry) StartedAt() time.Time {
return e.TimeOf(0)
}
func (e *RecordEntry) StoppedAt() time.Time {
return e.TimeOf(e.NumStates)
}
func (e *RecordEntry) Duration() time.Duration {
return e.StoppedAt().Sub(e.StartedAt())
}
// the Record object represents a recorded session
type Record struct {
sync.Mutex
mod *session.SessionModule `json:"-"`
fileName string `json:"-"`
done int `json:"-"`
total int `json:"-"`
progress float64 `json:"-"`
Session *RecordEntry `json:"session"`
Events *RecordEntry `json:"events"`
}
func NewRecord(fileName string, mod *session.SessionModule) *Record {
r := &Record{
fileName: fileName,
mod: mod,
}
r.Session = NewRecordEntry(r.onProgress)
r.Events = NewRecordEntry(r.onProgress)
return r
}
func (r *Record) onProgress(done int) {
r.done += done
r.progress = float64(r.done) / float64(r.total) * 100.0
r.mod.State.Store("load_progress", r.progress)
}
func LoadRecord(fileName string, mod *session.SessionModule) (*Record, error) {
if !fs.Exists(fileName) {
return nil, fmt.Errorf("%s does not exist", fileName)
}
compressed, err := ioutil.ReadFile(fileName)
if err != nil {
return nil, fmt.Errorf("error while reading %s: %s", fileName, err)
}
decompress, err := gzip.NewReader(bytes.NewReader(compressed))
if err != nil {
return nil, fmt.Errorf("error while reading gzip file %s: %s", fileName, err)
}
defer decompress.Close()
raw, err := ioutil.ReadAll(decompress)
if err != nil {
return nil, fmt.Errorf("error while decompressing %s: %s", fileName, err)
}
rec := &Record{}
decoder := json.NewDecoder(bytes.NewReader(raw))
if err = decoder.Decode(rec); err != nil {
return nil, fmt.Errorf("error while parsing %s: %s", fileName, err)
}
rec.fileName = fileName
rec.mod = mod
rec.Session.NumStates = len(rec.Session.States)
rec.Session.progress = rec.onProgress
rec.Events.NumStates = len(rec.Events.States)
rec.Events.progress = rec.onProgress
rec.done = 0
rec.total = rec.Session.NumStates + rec.Events.NumStates + 2
rec.progress = 0.0
// reset state and precompute frames
if err = rec.Session.Compile(); err != nil {
return nil, err
} else if err = rec.Events.Compile(); err != nil {
return nil, err
}
return rec, nil
}
func (r *Record) NewState(session []byte, events []byte) error {
if err := r.Session.AddState(session); err != nil {
return err
} else if err := r.Events.AddState(events); err != nil {
return err
}
return r.Flush()
}
func (r *Record) save() error {
buf := new(bytes.Buffer)
encoder := json.NewEncoder(buf)
if err := encoder.Encode(r); err != nil {
return err
}
data := buf.Bytes()
compressed := new(bytes.Buffer)
compress := gzip.NewWriter(compressed)
if _, err := compress.Write(data); err != nil {
return err
} else if err = compress.Flush(); err != nil {
return err
} else if err = compress.Close(); err != nil {
return err
}
return ioutil.WriteFile(r.fileName, compressed.Bytes(), os.ModePerm)
}
func (r *Record) Flush() error {
r.Lock()
defer r.Unlock()
return r.save()
}
......@@ -38,6 +38,8 @@ func NewArpSpoofer(s *session.Session) *ArpSpoofer {
waitGroup: &sync.WaitGroup{},
}
mod.SessionModule.Requires("net.recon")
mod.AddParam(session.NewStringParameter("arp.spoof.targets", session.ParamSubnet, "", "Comma separated list of IP addresses, MAC addresses or aliases to spoof, also supports nmap style IP ranges."))
mod.AddParam(session.NewStringParameter("arp.spoof.whitelist", "", "", "Comma separated list of IP addresses, MAC addresses or aliases to skip while spoofing."))
......
......@@ -42,6 +42,8 @@ func NewDHCP6Spoofer(s *session.Session) *DHCP6Spoofer {
waitGroup: &sync.WaitGroup{},
}
mod.SessionModule.Requires("net.recon")
mod.AddParam(session.NewStringParameter("dhcp6.spoof.domains",
"microsoft.com, google.com, facebook.com, apple.com, twitter.com",
``,
......
......@@ -34,6 +34,8 @@ func NewDNSSpoofer(s *session.Session) *DNSSpoofer {
waitGroup: &sync.WaitGroup{},
}
mod.SessionModule.Requires("net.recon")
mod.AddParam(session.NewStringParameter("dns.spoof.hosts",
"",
"",
......
......@@ -8,10 +8,12 @@ import (
"net/url"
"regexp"
"strings"
"github.com/bettercap/bettercap/session"
)
type JSRequest struct {
Client string
Client map[string]string
Method string
Version string
Scheme string
......@@ -43,8 +45,16 @@ func NewJSRequest(req *http.Request) *JSRequest {
}
}
client_ip := strings.Split(req.RemoteAddr, ":")[0]
client_mac := ""
client_alias := ""
if endpoint := session.I.Lan.GetByIp(client_ip); endpoint != nil {
client_mac = endpoint.HwAddress
client_alias = endpoint.Alias
}
jreq := &JSRequest{
Client: strings.Split(req.RemoteAddr, ":")[0],
Client: map[string]string{"IP": client_ip, "MAC": client_mac, "Alias": client_alias},
Method: req.Method,
Version: fmt.Sprintf("%d.%d", req.ProtoMajor, req.ProtoMinor),
Scheme: req.URL.Scheme,
......@@ -63,7 +73,7 @@ func NewJSRequest(req *http.Request) *JSRequest {
}
func (j *JSRequest) NewHash() string {
hash := fmt.Sprintf("%s.%s.%s.%s.%s.%s.%s.%s.%s", j.Client, j.Method, j.Version, j.Scheme, j.Hostname, j.Path, j.Query, j.ContentType, j.Headers)
hash := fmt.Sprintf("%s.%s.%s.%s.%s.%s.%s.%s.%s", j.Client["IP"], j.Method, j.Version, j.Scheme, j.Hostname, j.Path, j.Query, j.ContentType, j.Headers)
hash += "." + j.Body
return hash
}
......
......@@ -30,6 +30,8 @@ func NewProber(s *session.Session) *Prober {
waitGroup: &sync.WaitGroup{},
}
mod.SessionModule.Requires("net.recon")
mod.AddParam(session.NewBoolParameter("net.probe.nbns",
"true",
"Enable NetBIOS name service discovery probes."))
......
......@@ -98,7 +98,14 @@ func (mod *Discovery) getRow(e *network.Endpoint, withMeta bool) [][]string {
metas := []string{}
e.Meta.Each(func(name string, value interface{}) {
metas = append(metas, fmt.Sprintf("%s:%s", tui.Green(name), tui.Yellow(value.(string))))
s := ""
if sv, ok := value.(string); ok {
s = sv
} else {
s = fmt.Sprintf("%+v", value)
}
metas = append(metas, fmt.Sprintf("%s:%s", tui.Green(name), tui.Yellow(s)))
})
sort.Strings(metas)
......
......@@ -29,6 +29,8 @@ func NewSniffer(s *session.Session) *Sniffer {
Stats: nil,
}
mod.SessionModule.Requires("net.recon")
mod.AddParam(session.NewBoolParameter("net.sniff.verbose",
"false",
"If true, every captured and parsed packet will be sent to the events.stream for displaying, otherwise only the ones parsed at the application layer (sni, http, etc)."))
......
......@@ -4,6 +4,7 @@ import (
"strings"
"github.com/bettercap/bettercap/packets"
"github.com/bettercap/bettercap/session"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
......@@ -44,6 +45,14 @@ func mdnsParser(ip *layers.IPv4, pkt gopacket.Packet, udp *layers.UDP) bool {
}
for hostname, ips := range m {
for _, ip := range ips {
if endpoint := session.I.Lan.GetByIp(ip); endpoint != nil {
endpoint.OnMeta(map[string]string{
"mdns:hostname": hostname,
})
}
}
NewSnifferEvent(
pkt.Metadata().Timestamp,
"mdns",
......
......@@ -10,6 +10,13 @@ import (
"github.com/google/gopacket/layers"
)
type OpenPort struct {
Proto string `json:"proto"`
Banner string `json:"banner"`
Service string `json:"service"`
Port int `json:"port"`
}
func (mod *SynScanner) isAddressInRange(ip net.IP) bool {
for _, a := range mod.addresses {
if a.Equal(ip) {
......@@ -53,8 +60,16 @@ func (mod *SynScanner) onPacket(pkt gopacket.Packet) {
}
if host != nil {
ports := host.Meta.GetIntsWith("tcp-ports", port, true)
host.Meta.SetInts("tcp-ports", ports)
ports := host.Meta.GetOr("ports", map[int]OpenPort{}).(map[int]OpenPort)
if _, found := ports[port]; !found {
ports[port] = OpenPort{