source: scapy/layers/dot11.py @ 1236:47d832a95492

Revision 1236:47d832a95492, 19.2 KB checked in by Phil <phil@…>, 3 years ago (diff)

Added support PPI (Per-Packet Information) header. Decoding is partial but sufficient to reach next layer.

Line 
1## This file is part of Scapy
2## See http://www.secdev.org/projects/scapy for more informations
3## Copyright (C) Philippe Biondi <phil@secdev.org>
4## This program is published under a GPLv2 license
5
6"""
7Wireless LAN according to IEEE 802.11.
8"""
9
10import re,struct
11
12from scapy.packet import *
13from scapy.fields import *
14from scapy.plist import PacketList
15from scapy.layers.l2 import *
16
17
18try:
19    from Crypto.Cipher import ARC4
20except ImportError:
21    log_loading.info("Can't import python Crypto lib. Won't be able to decrypt WEP.")
22
23
24### Fields
25
26class Dot11AddrMACField(MACField):
27    def is_applicable(self, pkt):
28        return 1
29    def addfield(self, pkt, s, val):
30        if self.is_applicable(pkt):
31            return MACField.addfield(self, pkt, s, val)
32        else:
33            return s       
34    def getfield(self, pkt, s):
35        if self.is_applicable(pkt):
36            return MACField.getfield(self, pkt, s)
37        else:
38            return s,None
39
40class Dot11Addr2MACField(Dot11AddrMACField):
41    def is_applicable(self, pkt):
42        if pkt.type == 1:
43            return pkt.subtype in [ 0xb, 0xa, 0xe, 0xf] # RTS, PS-Poll, CF-End, CF-End+CF-Ack
44        return 1
45
46class Dot11Addr3MACField(Dot11AddrMACField):
47    def is_applicable(self, pkt):
48        if pkt.type in [0,2]:
49            return 1
50        return 0
51
52class Dot11Addr4MACField(Dot11AddrMACField):
53    def is_applicable(self, pkt):
54        if pkt.type == 2:
55            if pkt.FCfield & 0x3 == 0x3: # To-DS and From-DS are set
56                return 1
57        return 0
58   
59
60### Layers
61
62
63class PrismHeader(Packet):
64    """ iwpriv wlan0 monitor 3 """
65    name = "Prism header"
66    fields_desc = [ LEIntField("msgcode",68),
67                    LEIntField("len",144),
68                    StrFixedLenField("dev","",16),
69                    LEIntField("hosttime_did",0),
70                  LEShortField("hosttime_status",0),
71                  LEShortField("hosttime_len",0),
72                    LEIntField("hosttime",0),
73                    LEIntField("mactime_did",0),
74                  LEShortField("mactime_status",0),
75                  LEShortField("mactime_len",0),
76                    LEIntField("mactime",0),
77                    LEIntField("channel_did",0),
78                  LEShortField("channel_status",0),
79                  LEShortField("channel_len",0),
80                    LEIntField("channel",0),
81                    LEIntField("rssi_did",0),
82                  LEShortField("rssi_status",0),
83                  LEShortField("rssi_len",0),
84                    LEIntField("rssi",0),
85                    LEIntField("sq_did",0),
86                  LEShortField("sq_status",0),
87                  LEShortField("sq_len",0),
88                    LEIntField("sq",0),
89                    LEIntField("signal_did",0),
90                  LEShortField("signal_status",0),
91                  LEShortField("signal_len",0),
92              LESignedIntField("signal",0),
93                    LEIntField("noise_did",0),
94                  LEShortField("noise_status",0),
95                  LEShortField("noise_len",0),
96                    LEIntField("noise",0),
97                    LEIntField("rate_did",0),
98                  LEShortField("rate_status",0),
99                  LEShortField("rate_len",0),
100                    LEIntField("rate",0),
101                    LEIntField("istx_did",0),
102                  LEShortField("istx_status",0),
103                  LEShortField("istx_len",0),
104                    LEIntField("istx",0),
105                    LEIntField("frmlen_did",0),
106                  LEShortField("frmlen_status",0),
107                  LEShortField("frmlen_len",0),
108                    LEIntField("frmlen",0),
109                    ]
110    def answers(self, other):
111        if isinstance(other, PrismHeader):
112            return self.payload.answers(other.payload)
113        else:
114            return self.payload.answers(other)
115
116class RadioTap(Packet):
117    name = "RadioTap dummy"
118    fields_desc = [ ByteField('version', 0),
119                    ByteField('pad', 0),
120                    FieldLenField('len', None, 'notdecoded', '<H', adjust=lambda pkt,x:x+8),
121                    FlagsField('present', None, -32, ['TSFT','Flags','Rate','Channel','FHSS','dBm_AntSignal',
122                                                     'dBm_AntNoise','Lock_Quality','TX_Attenuation','dB_TX_Attenuation',
123                                                      'dBm_TX_Power', 'Antenna', 'dB_AntSignal', 'dB_AntNoise',
124                                                     'b14', 'b15','b16','b17','b18','b19','b20','b21','b22','b23',
125                                                     'b24','b25','b26','b27','b28','b29','b30','Ext']),
126                    StrLenField('notdecoded', "", length_from= lambda pkt:pkt.len-8) ]
127
128class PPI(Packet):
129    name = "Per-Packet Information header (partial)"
130    fields_desc = [ ByteField("version", 0),
131                    ByteField("flags", 0),
132                    FieldLenField("len", None, fmt="<H", length_of="fields", adjust=lambda pkt,x:x+8),
133                    LEIntField("dlt", 0),
134                    StrLenField("notdecoded", "", length_from = lambda pkt:pkt.len-8)
135                    ]
136
137
138
139class Dot11SCField(LEShortField):
140    def is_applicable(self, pkt):
141        return pkt.type != 1 # control frame
142    def addfield(self, pkt, s, val):
143        if self.is_applicable(pkt):
144            return LEShortField.addfield(self, pkt, s, val)
145        else:
146            return s
147    def getfield(self, pkt, s):
148        if self.is_applicable(pkt):
149            return LEShortField.getfield(self, pkt, s)
150        else:
151            return s,None
152
153class Dot11(Packet):
154    name = "802.11"
155    fields_desc = [
156                    BitField("subtype", 0, 4),
157                    BitEnumField("type", 0, 2, ["Management", "Control", "Data", "Reserved"]),
158                    BitField("proto", 0, 2),
159                    FlagsField("FCfield", 0, 8, ["to-DS", "from-DS", "MF", "retry", "pw-mgt", "MD", "wep", "order"]),
160                    ShortField("ID",0),
161                    MACField("addr1", ETHER_ANY),
162                    Dot11Addr2MACField("addr2", ETHER_ANY),
163                    Dot11Addr3MACField("addr3", ETHER_ANY),
164                    Dot11SCField("SC", 0),
165                    Dot11Addr4MACField("addr4", ETHER_ANY)
166                    ]
167    def mysummary(self):
168        return self.sprintf("802.11 %Dot11.type% %Dot11.subtype% %Dot11.addr2% > %Dot11.addr1%")
169    def guess_payload_class(self, payload):
170        if self.type == 0x02 and (self.subtype >= 0x08 and self.subtype <=0xF and self.subtype != 0xD):
171            return Dot11QoS
172        elif self.FCfield & 0x40:
173            return Dot11WEP
174        else:
175            return Packet.guess_payload_class(self, payload)
176    def answers(self, other):
177        if isinstance(other,Dot11):
178            if self.type == 0: # management
179                if self.addr1.lower() != other.addr2.lower(): # check resp DA w/ req SA
180                    return 0
181                if (other.subtype,self.subtype) in [(0,1),(2,3),(4,5)]:
182                    return 1
183                if self.subtype == other.subtype == 11: # auth
184                    return self.payload.answers(other.payload)
185            elif self.type == 1: # control
186                return 0
187            elif self.type == 2: # data
188                return self.payload.answers(other.payload)
189            elif self.type == 3: # reserved
190                return 0
191        return 0
192    def unwep(self, key=None, warn=1):
193        if self.FCfield & 0x40 == 0:
194            if warn:
195                warning("No WEP to remove")
196            return
197        if  isinstance(self.payload.payload, NoPayload):
198            if key or conf.wepkey:
199                self.payload.decrypt(key)
200            if isinstance(self.payload.payload, NoPayload):
201                if warn:
202                    warning("Dot11 can't be decrypted. Check conf.wepkey.")
203                return
204        self.FCfield &= ~0x40
205        self.payload=self.payload.payload
206
207
208class Dot11QoS(Packet):
209    name = "802.11 QoS"
210    fields_desc = [ BitField("TID",None,4),
211                    BitField("EOSP",None,1),
212                    BitField("Ack Policy",None,2),
213                    BitField("Reserved",None,1),
214                    ByteField("TXOP",None) ]
215    def guess_payload_class(self, payload):
216        if isinstance(self.underlayer, Dot11):
217            if self.underlayer.FCfield & 0x40:
218                return Dot11WEP
219        return Packet.guess_payload_class(self, payload)
220
221
222capability_list = [ "res8", "res9", "short-slot", "res11",
223                    "res12", "DSSS-OFDM", "res14", "res15",
224                   "ESS", "IBSS", "CFP", "CFP-req",
225                   "privacy", "short-preamble", "PBCC", "agility"]
226
227reason_code = {0:"reserved",1:"unspec", 2:"auth-expired",
228               3:"deauth-ST-leaving",
229               4:"inactivity", 5:"AP-full", 6:"class2-from-nonauth",
230               7:"class3-from-nonass", 8:"disas-ST-leaving",
231               9:"ST-not-auth"}
232
233status_code = {0:"success", 1:"failure", 10:"cannot-support-all-cap",
234               11:"inexist-asso", 12:"asso-denied", 13:"algo-unsupported",
235               14:"bad-seq-num", 15:"challenge-failure",
236               16:"timeout", 17:"AP-full",18:"rate-unsupported" }
237
238class Dot11Beacon(Packet):
239    name = "802.11 Beacon"
240    fields_desc = [ LELongField("timestamp", 0),
241                    LEShortField("beacon_interval", 0x0064),
242                    FlagsField("cap", 0, 16, capability_list) ]
243   
244
245class Dot11Elt(Packet):
246    name = "802.11 Information Element"
247    fields_desc = [ ByteEnumField("ID", 0, {0:"SSID", 1:"Rates", 2: "FHset", 3:"DSset", 4:"CFset", 5:"TIM", 6:"IBSSset", 16:"challenge",
248                                            42:"ERPinfo", 46:"QoS Capability", 47:"ERPinfo", 48:"RSNinfo", 50:"ESRates",221:"vendor",68:"reserved"}),
249                    FieldLenField("len", None, "info", "B"),
250                    StrLenField("info", "", length_from=lambda x:x.len) ]
251    def mysummary(self):
252        if self.ID == 0:
253            return "SSID=%s"%repr(self.info),[Dot11]
254        else:
255            return ""
256
257class Dot11ATIM(Packet):
258    name = "802.11 ATIM"
259
260class Dot11Disas(Packet):
261    name = "802.11 Disassociation"
262    fields_desc = [ LEShortEnumField("reason", 1, reason_code) ]
263
264class Dot11AssoReq(Packet):
265    name = "802.11 Association Request"
266    fields_desc = [ FlagsField("cap", 0, 16, capability_list),
267                    LEShortField("listen_interval", 0x00c8) ]
268
269
270class Dot11AssoResp(Packet):
271    name = "802.11 Association Response"
272    fields_desc = [ FlagsField("cap", 0, 16, capability_list),
273                    LEShortField("status", 0),
274                    LEShortField("AID", 0) ]
275
276class Dot11ReassoReq(Packet):
277    name = "802.11 Reassociation Request"
278    fields_desc = [ FlagsField("cap", 0, 16, capability_list),
279                    LEShortField("listen_interval", 0x00c8),
280                    MACField("current_AP", ETHER_ANY) ]
281
282
283class Dot11ReassoResp(Dot11AssoResp):
284    name = "802.11 Reassociation Response"
285
286class Dot11ProbeReq(Packet):
287    name = "802.11 Probe Request"
288   
289class Dot11ProbeResp(Packet):
290    name = "802.11 Probe Response"
291    fields_desc = [ LELongField("timestamp", 0),
292                    LEShortField("beacon_interval", 0x0064),
293                    FlagsField("cap", 0, 16, capability_list) ]
294   
295class Dot11Auth(Packet):
296    name = "802.11 Authentication"
297    fields_desc = [ LEShortEnumField("algo", 0, ["open", "sharedkey"]),
298                    LEShortField("seqnum", 0),
299                    LEShortEnumField("status", 0, status_code) ]
300    def answers(self, other):
301        if self.seqnum == other.seqnum+1:
302            return 1
303        return 0
304
305class Dot11Deauth(Packet):
306    name = "802.11 Deauthentication"
307    fields_desc = [ LEShortEnumField("reason", 1, reason_code) ]
308
309
310
311class Dot11WEP(Packet):
312    name = "802.11 WEP packet"
313    fields_desc = [ StrFixedLenField("iv", "\0\0\0", 3),
314                    ByteField("keyid", 0),
315                    StrField("wepdata",None,remain=4),
316                    IntField("icv",None) ]
317
318    def post_dissect(self, s):
319#        self.icv, = struct.unpack("!I",self.wepdata[-4:])
320#        self.wepdata = self.wepdata[:-4]
321        self.decrypt()
322
323    def build_payload(self):
324        if self.wepdata is None:
325            return Packet.build_payload(self)
326        return ""
327
328    def post_build(self, p, pay):
329        if self.wepdata is None:
330            key = conf.wepkey
331            if key:
332                if self.icv is None:
333                    pay += struct.pack("<I",crc32(pay))
334                    icv = ""
335                else:
336                    icv = p[4:8]
337                c = ARC4.new(self.iv+key)
338                p = p[:4]+c.encrypt(pay)+icv
339            else:
340                warning("No WEP key set (conf.wepkey).. strange results expected..")
341        return p
342           
343
344    def decrypt(self,key=None):
345        if key is None:
346            key = conf.wepkey
347        if key:
348            c = ARC4.new(self.iv+key)
349            self.add_payload(LLC(c.decrypt(self.wepdata)))
350                   
351
352bind_layers( PrismHeader,   Dot11,         )
353bind_layers( RadioTap,      Dot11,         )
354bind_layers( PPI,           Dot11,         dlt=105)
355bind_layers( Dot11,         LLC,           type=2)
356bind_layers( Dot11QoS,      LLC,           )
357bind_layers( Dot11,         Dot11AssoReq,    subtype=0, type=0)
358bind_layers( Dot11,         Dot11AssoResp,   subtype=1, type=0)
359bind_layers( Dot11,         Dot11ReassoReq,  subtype=2, type=0)
360bind_layers( Dot11,         Dot11ReassoResp, subtype=3, type=0)
361bind_layers( Dot11,         Dot11ProbeReq,   subtype=4, type=0)
362bind_layers( Dot11,         Dot11ProbeResp,  subtype=5, type=0)
363bind_layers( Dot11,         Dot11Beacon,     subtype=8, type=0)
364bind_layers( Dot11,         Dot11ATIM,       subtype=9, type=0)
365bind_layers( Dot11,         Dot11Disas,      subtype=10, type=0)
366bind_layers( Dot11,         Dot11Auth,       subtype=11, type=0)
367bind_layers( Dot11,         Dot11Deauth,     subtype=12, type=0)
368bind_layers( Dot11Beacon,     Dot11Elt,    )
369bind_layers( Dot11AssoReq,    Dot11Elt,    )
370bind_layers( Dot11AssoResp,   Dot11Elt,    )
371bind_layers( Dot11ReassoReq,  Dot11Elt,    )
372bind_layers( Dot11ReassoResp, Dot11Elt,    )
373bind_layers( Dot11ProbeReq,   Dot11Elt,    )
374bind_layers( Dot11ProbeResp,  Dot11Elt,    )
375bind_layers( Dot11Auth,       Dot11Elt,    )
376bind_layers( Dot11Elt,        Dot11Elt,    )
377
378
379conf.l2types.register(105, Dot11)
380conf.l2types.register_num2layer(801, Dot11)
381conf.l2types.register(119, PrismHeader)
382conf.l2types.register_num2layer(802, PrismHeader)
383conf.l2types.register(127, RadioTap)
384conf.l2types.register(0xc0, PPI)
385conf.l2types.register_num2layer(803, RadioTap)
386
387
388class WiFi_am(AnsweringMachine):
389    """Before using this, initialize "iffrom" and "ifto" interfaces:
390iwconfig iffrom mode monitor
391iwpriv orig_ifto hostapd 1
392ifconfig ifto up
393note: if ifto=wlan0ap then orig_ifto=wlan0
394note: ifto and iffrom must be set on the same channel
395ex:
396ifconfig eth1 up
397iwconfig eth1 mode monitor
398iwconfig eth1 channel 11
399iwpriv wlan0 hostapd 1
400ifconfig wlan0ap up
401iwconfig wlan0 channel 11
402iwconfig wlan0 essid dontexist
403iwconfig wlan0 mode managed
404"""
405    function_name = "airpwn"
406    filter = None
407   
408    def parse_options(self, iffrom, ifto, replace, pattern="", ignorepattern=""):
409        self.iffrom = iffrom
410        self.ifto = ifto
411        ptrn = re.compile(pattern)
412        iptrn = re.compile(ignorepattern)
413       
414    def is_request(self, pkt):
415        if not isinstance(pkt,Dot11):
416            return 0
417        if not pkt.FCfield & 1:
418            return 0
419        if not pkt.haslayer(TCP):
420            return 0
421        ip = pkt.getlayer(IP)
422        tcp = pkt.getlayer(TCP)
423        pay = str(tcp.payload)
424        if not self.ptrn.match(pay):
425            return 0
426        if self.iptrn.match(pay):
427            return 0
428
429    def make_reply(self, p):
430        ip = p.getlayer(IP)
431        tcp = p.getlayer(TCP)
432        pay = str(tcp.payload)
433        del(p.payload.payload.payload)
434        p.FCfield="from-DS"
435        p.addr1,p.addr2 = p.addr2,p.addr1
436        p /= IP(src=ip.dst,dst=ip.src)
437        p /= TCP(sport=tcp.dport, dport=tcp.sport,
438                 seq=tcp.ack, ack=tcp.seq+len(pay),
439                 flags="PA")
440        q = p.copy()
441        p /= self.replace
442        q.ID += 1
443        q.getlayer(TCP).flags="RA"
444        q.getlayer(TCP).seq+=len(replace)
445        return [p,q]
446   
447    def print_reply(self):
448        print p.sprintf("Sent %IP.src%:%IP.sport% > %IP.dst%:%TCP.dport%")
449
450    def send_reply(self, reply):
451        sendp(reply, iface=self.ifto, **self.optsend)
452
453    def sniff(self):
454        sniff(iface=self.iffrom, **self.optsniff)
455
456
457
458plst=[]
459def get_toDS():
460    global plst
461    while 1:
462        p,=sniff(iface="eth1",count=1)
463        if not isinstance(p,Dot11):
464            continue
465        if p.FCfield & 1:
466            plst.append(p)
467            print "."
468
469
470#    if not ifto.endswith("ap"):
471#        print "iwpriv %s hostapd 1" % ifto
472#        os.system("iwpriv %s hostapd 1" % ifto)
473#        ifto += "ap"
474#       
475#    os.system("iwconfig %s mode monitor" % iffrom)
476#   
477
478def airpwn(iffrom, ifto, replace, pattern="", ignorepattern=""):
479    """Before using this, initialize "iffrom" and "ifto" interfaces:
480iwconfig iffrom mode monitor
481iwpriv orig_ifto hostapd 1
482ifconfig ifto up
483note: if ifto=wlan0ap then orig_ifto=wlan0
484note: ifto and iffrom must be set on the same channel
485ex:
486ifconfig eth1 up
487iwconfig eth1 mode monitor
488iwconfig eth1 channel 11
489iwpriv wlan0 hostapd 1
490ifconfig wlan0ap up
491iwconfig wlan0 channel 11
492iwconfig wlan0 essid dontexist
493iwconfig wlan0 mode managed
494"""
495   
496    ptrn = re.compile(pattern)
497    iptrn = re.compile(ignorepattern)
498    def do_airpwn(p, ifto=ifto, replace=replace, ptrn=ptrn, iptrn=iptrn):
499        if not isinstance(p,Dot11):
500            return
501        if not p.FCfield & 1:
502            return
503        if not p.haslayer(TCP):
504            return
505        ip = p.getlayer(IP)
506        tcp = p.getlayer(TCP)
507        pay = str(tcp.payload)
508#        print "got tcp"
509        if not ptrn.match(pay):
510            return
511#        print "match 1"
512        if iptrn.match(pay):
513            return
514#        print "match 2"
515        del(p.payload.payload.payload)
516        p.FCfield="from-DS"
517        p.addr1,p.addr2 = p.addr2,p.addr1
518        q = p.copy()
519        p /= IP(src=ip.dst,dst=ip.src)
520        p /= TCP(sport=tcp.dport, dport=tcp.sport,
521                 seq=tcp.ack, ack=tcp.seq+len(pay),
522                 flags="PA")
523        q = p.copy()
524        p /= replace
525        q.ID += 1
526        q.getlayer(TCP).flags="RA"
527        q.getlayer(TCP).seq+=len(replace)
528       
529        sendp([p,q], iface=ifto, verbose=0)
530#        print "send",repr(p)       
531#        print "send",repr(q)
532        print p.sprintf("Sent %IP.src%:%IP.sport% > %IP.dst%:%TCP.dport%")
533
534    sniff(iface=iffrom,prn=do_airpwn)
535
536           
537       
538conf.stats_dot11_protocols += [Dot11WEP, Dot11Beacon, ]
539
540
541       
542
543
544class Dot11PacketList(PacketList):
545    def __init__(self, res=None, name="Dot11List", stats=None):
546        if stats is None:
547            stats = conf.stats_dot11_protocols
548
549        PacketList.__init__(self, res, name, stats)
550    def toEthernet(self):
551        data = map(lambda x:x.getlayer(Dot11), filter(lambda x : x.haslayer(Dot11) and x.type == 2, self.res))
552        r2 = []
553        for p in data:
554            q = p.copy()
555            q.unwep()
556            r2.append(Ether()/q.payload.payload.payload) #Dot11/LLC/SNAP/IP
557        return PacketList(r2,name="Ether from %s"%self.listname)
558       
559       
Note: See TracBrowser for help on using the repository browser.