1 import struct
2 from Crypto.Cipher import AES
3
4
5
6
7 DOT154_FCF_TYPE_MASK = 0x0007
8 DOT154_FCF_SEC_EN = 0x0008
9 DOT154_FCF_FRAME_PND = 0x0010
10 DOT154_FCF_ACK_REQ = 0x0020
11 DOT154_FCF_INTRA_PAN = 0x0040
12 DOT154_FCF_DADDR_MASK = 0x0C00
13 DOT154_FCF_VERSION_MASK = 0x3000
14 DOT154_FCF_SADDR_MASK = 0xC000
15
16
17 DOT154_FCF_TYPE_MASK_SHIFT = 0
18 DOT154_FCF_DADDR_MASK_SHIFT = 10
19 DOT154_FCF_VERSION_MASK_SHIFT = 12
20 DOT154_FCF_SADDR_MASK_SHIFT = 14
21
22
23 DOT154_FCF_ADDR_NONE = 0x0000
24 DOT154_FCF_ADDR_SHORT = 0x0002
25 DOT154_FCF_ADDR_EXT = 0x0003
26
27 DOT154_FCF_TYPE_BEACON = 0
28 DOT154_FCF_TYPE_DATA = 1
29 DOT154_FCF_TYPE_ACK = 2
30 DOT154_FCF_TYPE_MACCMD = 3
31
32 DOT154_CRYPT_NONE = 0x00
33 DOT154_CRYPT_MIC32 = 0x01
34 DOT154_CRYPT_MIC64 = 0x02
35 DOT154_CRYPT_MIC128 = 0x03
36 DOT154_CRYPT_ENC = 0x04
37 DOT154_CRYPT_ENC_MIC32 = 0x05
38 DOT154_CRYPT_ENC_MIC64 = 0x06
39 DOT154_CRYPT_ENC_MIC128 = 0x07
40
43 '''
44 Instantiates the Dot154PacketParser class.
45 '''
46
47
48 self.__crypt_blockcntr = 1
49 self.__crypt_A_i = []
50 return
51
53 '''
54 Used for AES-CTR mode after populating self.__crypt_A_i
55 Don't call this directly. Just don't.
56 '''
57 retindex = self.__crypt_blockcntr
58 self.__crypt_blockcntr += 1
59 return self.__crypt_A_i[retindex]
60
62 '''
63 Decrypts the specified packet. Returns empty string if the packet is
64 not encrypted, or if decryption MIC validation fails.
65
66 @type packet: String
67 @param packet: Packet contents.
68 @type key: String
69 @param key: Key contents.
70 @rtype: String
71 @return: Decrypted packet contents, empty string if not encrypted or if
72 decryped MIC fails validation.
73 '''
74
75
76 encpayload = packet[-self.payloadlen(packet):]
77
78 if ord(encpayload[0]) != DOT154_CRYPT_ENC_MIC64:
79 raise Exception("Unsupported security level in packet: 0x%02x." % ord(encpayload[0]))
80
81 if len(key) != 16:
82 raise Exception("Invalid key length (%d)." % len(key))
83
84
85
86 if self.payloadlen(packet) < 15:
87 raise Exception("Payload length too short (%d)." % self.payloadlen(packet))
88
89 nonce = self.nonce(packet)
90
91
92 c = encpayload[-9:]
93
94
95
96
97 C = c[0:-8]
98 U = c[-8:]
99
100
101 cipherText = C + ("\x00" * (16 - len(C)%16))
102
103
104
105 flags = "\x01"
106
107
108
109
110
111 self.__crypt_A_i = []
112 for i in xrange(0, (1+1+(len(C)/16))):
113 self.__crypt_A_i.append(flags + nonce + struct.pack(">H",i))
114
115
116 self.__crypt_blockcntr = 1
117 crypt = AES.new(key, AES.MODE_CTR, counter=self.__crypt_counter)
118 plainText = crypt.decrypt(cipherText)[0:len(C)]
119
120
121 crypt = AES.new(key, AES.MODE_CBC)
122 S_0 = crypt.encrypt(self.__crypt_A_i[0])
123
124
125 T_obs = []
126 for i in xrange(0,len(S_0[0:8])):
127 T_obs.append((ord(S_0[i]) ^ ord(U[i])))
128
129
130 T_obs = ''.join(struct.pack("B",i) for i in T_obs)
131
132
133
134
135 hdrlen = self.hdrlen(packet)
136 a = packet[0:hdrlen] + packet[hdrlen:hdrlen+6]
137
138
139 addAuthData = struct.pack(">H",len(a)) + a
140
141
142 addAuthData += ("\x00" * (16 - len(addAuthData)%16))
143
144
145
146 plainTextPadded = plainText + ("\x00" * (16 - len(plainText)%16))
147
148 authData = addAuthData + plainTextPadded
149
150
151 B = "\x59" + nonce + "\x00\x01" + authData
152
153
154 iv = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
155 for i in xrange(0, len(B)/16):
156 crypt = AES.new(key, AES.MODE_CBC, iv)
157 Bn = B[i*16:(i*16)+16]
158 iv = crypt.encrypt(Bn)
159 T_calc = iv[0:8]
160
161
162 if T_obs == T_calc:
163 return plainText
164 else:
165 return ""
166
167
169 '''
170 Chops up the specified packet contents into a list of fields. Does
171 not attempt to re-order the field values for parsing. ''.join(X) will
172 reassemble original packet string. Fields which may or may not be
173 present (such as the Source PAN field) are empty if they are not
174 present, keeping the list elements consistent, as follows:
175 FCF | Seq# | DPAN | DA | SPAN | SA | [Beacon Data] | PHY Payload
176
177 If the packet is a beacon frame, the Beacon Data field will be populated
178 as a list element in the format:
179
180 Superframe Spec | GTS Fields | Pending Addr Counts | Proto ID | Stack Profile/Profile Version | Device Capabilities | Ext PAN ID | TX Offset | Update ID
181
182 An exception is raised if the packet contents are too short to
183 decode.
184
185 @type packet: String
186 @param packet: Packet contents.
187 @rtype: list
188 @return: Chopped contents of the 802.15.4 packet into list elements.
189 '''
190 pktchop = ['', '', '', '', '', '', [], '']
191
192 pktchop[0] = packet[0:2]
193
194
195 pktchop[1] = packet[2]
196
197
198 fcf = struct.unpack("<H",pktchop[0])[0]
199
200
201 if (fcf & DOT154_FCF_TYPE_MASK) == DOT154_FCF_TYPE_BEACON:
202
203 beacondata = ["", "", "", "", "", "", "", "", "", ""]
204
205 try:
206
207
208 pktchop[4] = packet[3:5]
209 pktchop[5] = packet[5:7]
210 offset = 7
211
212
213 beacondata[0] = packet[offset:offset+2]
214 offset+=2
215
216
217 beacondata[1] = packet[offset]
218 offset+=1
219
220
221 beacondata[2] = packet[offset]
222 offset+=1
223
224
225 beacondata[3] = packet[offset]
226 offset+=1
227
228
229 beacondata[4] = packet[offset]
230 offset+=1
231
232
233 beacondata[5] = packet[offset]
234 offset+=1
235
236
237 beacondata[6] = packet[offset:offset+8]
238 offset+=8
239
240
241 beacondata[7] = packet[offset:offset+3]
242 offset+=3
243
244
245 beacondata[8] = packet[offset]
246 offset+=1
247
248 except:
249 pass
250
251 pktchop[6] = beacondata
252
253 else:
254
255
256
257 pktchop[2] = packet[3:5]
258 offset = 5
259
260
261 daddr_mask = (fcf & DOT154_FCF_DADDR_MASK) >> 10
262 if daddr_mask == DOT154_FCF_ADDR_EXT:
263 pktchop[3] = packet[offset:offset+8]
264 offset+=8
265 elif daddr_mask == DOT154_FCF_ADDR_SHORT:
266 pktchop[3] = packet[offset:offset+2]
267 offset+=2
268
269
270 if (fcf & DOT154_FCF_INTRA_PAN) == 0:
271 pktchop[4] = packet[offset:offset+2]
272 offset+=2
273
274
275 saddr_mask = (fcf & DOT154_FCF_SADDR_MASK) >> 14
276 if daddr_mask == DOT154_FCF_ADDR_EXT:
277 pktchop[5] = packet[offset:offset+8]
278 offset+=8
279 elif daddr_mask == DOT154_FCF_ADDR_SHORT:
280 pktchop[5] = packet[offset:offset+2]
281 offset+=2
282
283
284 if offset < len(packet):
285 pktchop[7] = packet[offset:]
286
287 return pktchop
288
289
291 '''
292 Returns the length of the 802.15.4 header.
293 @type packet: String
294 @param packet: Packet contents to evaluate for header length.
295 @rtype: Int
296 @return: Length of the 802.15.4 header.
297 '''
298
299
300
301
302 if (len(packet) < 9):
303 raise Exception("Packet too small, %d bytes." % len(packet))
304
305
306 plen = 9
307
308
309 fcf = struct.unpack("<H",packet[0:2])[0]
310
311
312 if (fcf & DOT154_FCF_DADDR_MASK) >> 10 == DOT154_FCF_ADDR_EXT:
313 plen += 6
314
315
316 if (fcf & DOT154_FCF_SADDR_MASK) >> 14 == DOT154_FCF_ADDR_EXT:
317 plen += 6
318
319
320 if (fcf & DOT154_FCF_INTRA_PAN) == 0:
321 plen += 2
322
323 return plen
324
326 '''
327 Returns the length of the 802.15.4 payload.
328 @type packet: String
329 @param packet: Packet contents to evaluate for header length.
330 @rtype: Int
331 @return: Length of the 802.15.4 payload.
332 '''
333 return len(packet) - self.hdrlen(packet)
334
335 - def nonce(self, packet):
336 '''
337 Returns the nonce of the 802.15.4 packet. Returns empty string for
338 unencrypted frames.
339 @type packet: String
340 @param packet: Packet contents to evaluate for nonce.
341 @rtype: String
342 @return: Nonce, empty when the frame is not encrypted.
343 '''
344
345
346 fcf = struct.unpack("<H",packet[0:2])[0]
347
348 if (fcf & DOT154_FCF_SEC_EN) == 0:
349
350 return ""
351
352
353 pchop = self.pktchop(packet)
354
355
356 noncep1 = pchop[5][::-1]
357
358
359 encpayload = packet[-self.payloadlen(packet):]
360
361
362 noncep3 = encpayload[0]
363
364
365 noncep2 = encpayload[1:5][::-1]
366
367 return noncep1 + noncep2 + noncep3
368