Coverage for /root/GitHubProjects/impacket/impacket/structure.py : 82%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Impacket - Collection of Python classes for working with network protocols.
2#
3# SECUREAUTH LABS. Copyright (C) 2020 SecureAuth Corporation. All rights reserved.
4#
5# This software is provided under a slightly modified version
6# of the Apache Software License. See the accompanying LICENSE file
7# for more information.
8#
10from __future__ import division
11from __future__ import print_function
12from struct import pack, unpack, calcsize
13from six import b, PY3
15class Structure:
16 """ sublcasses can define commonHdr and/or structure.
17 each of them is an tuple of either two: (fieldName, format) or three: (fieldName, ':', class) fields.
18 [it can't be a dictionary, because order is important]
20 where format specifies how the data in the field will be converted to/from bytes (string)
21 class is the class to use when unpacking ':' fields.
23 each field can only contain one value (or an array of values for *)
24 i.e. struct.pack('Hl',1,2) is valid, but format specifier 'Hl' is not (you must use 2 dfferent fields)
26 format specifiers:
27 specifiers from module pack can be used with the same format
28 see struct.__doc__ (pack/unpack is finally called)
29 x [padding byte]
30 c [character]
31 b [signed byte]
32 B [unsigned byte]
33 h [signed short]
34 H [unsigned short]
35 l [signed long]
36 L [unsigned long]
37 i [signed integer]
38 I [unsigned integer]
39 q [signed long long (quad)]
40 Q [unsigned long long (quad)]
41 s [string (array of chars), must be preceded with length in format specifier, padded with zeros]
42 p [pascal string (includes byte count), must be preceded with length in format specifier, padded with zeros]
43 f [float]
44 d [double]
45 = [native byte ordering, size and alignment]
46 @ [native byte ordering, standard size and alignment]
47 ! [network byte ordering]
48 < [little endian]
49 > [big endian]
51 usual printf like specifiers can be used (if started with %)
52 [not recommended, there is no way to unpack this]
54 %08x will output an 8 bytes hex
55 %s will output a string
56 %s\\x00 will output a NUL terminated string
57 %d%d will output 2 decimal digits (against the very same specification of Structure)
58 ...
60 some additional format specifiers:
61 : just copy the bytes from the field into the output string (input may be string, other structure, or anything responding to __str__()) (for unpacking, all what's left is returned)
62 z same as :, but adds a NUL byte at the end (asciiz) (for unpacking the first NUL byte is used as terminator) [asciiz string]
63 u same as z, but adds two NUL bytes at the end (after padding to an even size with NULs). (same for unpacking) [unicode string]
64 w DCE-RPC/NDR string (it's a macro for [ '<L=(len(field)+1)/2','"\\x00\\x00\\x00\\x00','<L=(len(field)+1)/2',':' ]
65 ?-field length of field named 'field', formatted as specified with ? ('?' may be '!H' for example). The input value overrides the real length
66 ?1*?2 array of elements. Each formatted as '?2', the number of elements in the array is stored as specified by '?1' (?1 is optional, or can also be a constant (number), for unpacking)
67 'xxxx literal xxxx (field's value doesn't change the output. quotes must not be closed or escaped)
68 "xxxx literal xxxx (field's value doesn't change the output. quotes must not be closed or escaped)
69 _ will not pack the field. Accepts a third argument, which is an unpack code. See _Test_UnpackCode for an example
70 ?=packcode will evaluate packcode in the context of the structure, and pack the result as specified by ?. Unpacking is made plain
71 ?&fieldname "Address of field fieldname".
72 For packing it will simply pack the id() of fieldname. Or use 0 if fieldname doesn't exists.
73 For unpacking, it's used to know weather fieldname has to be unpacked or not, i.e. by adding a & field you turn another field (fieldname) in an optional field.
75 """
76 commonHdr = ()
77 structure = ()
78 debug = 0
80 def __init__(self, data = None, alignment = 0):
81 if not hasattr(self, 'alignment'): 81 ↛ 84line 81 didn't jump to line 84, because the condition on line 81 was never false
82 self.alignment = alignment
84 self.fields = {}
85 self.rawData = data
86 if data is not None:
87 self.fromString(data)
88 else:
89 self.data = None
91 @classmethod
92 def fromFile(self, file):
93 answer = self()
94 answer.fromString(file.read(len(answer)))
95 return answer
97 def setAlignment(self, alignment):
98 self.alignment = alignment
100 def setData(self, data):
101 self.data = data
103 def packField(self, fieldName, format = None):
104 if self.debug: 104 ↛ 105line 104 didn't jump to line 105, because the condition on line 104 was never true
105 print("packField( %s | %s )" % (fieldName, format))
107 if format is None: 107 ↛ 108line 107 didn't jump to line 108, because the condition on line 107 was never true
108 format = self.formatForField(fieldName)
110 if fieldName in self.fields:
111 ans = self.pack(format, self.fields[fieldName], field = fieldName)
112 else:
113 ans = self.pack(format, None, field = fieldName)
115 if self.debug: 115 ↛ 116line 115 didn't jump to line 116, because the condition on line 115 was never true
116 print("\tanswer %r" % ans)
118 return ans
120 def getData(self):
121 if self.data is not None: 121 ↛ 122line 121 didn't jump to line 122, because the condition on line 121 was never true
122 return self.data
123 data = bytes()
124 for field in self.commonHdr+self.structure:
125 try:
126 data += self.packField(field[0], field[1])
127 except Exception as e:
128 if field[0] in self.fields:
129 e.args += ("When packing field '%s | %s | %r' in %s" % (field[0], field[1], self[field[0]], self.__class__),)
130 else:
131 e.args += ("When packing field '%s | %s' in %s" % (field[0], field[1], self.__class__),)
132 raise
133 if self.alignment:
134 if len(data) % self.alignment:
135 data += (b'\x00'*self.alignment)[:-(len(data) % self.alignment)]
137 #if len(data) % self.alignment: data += ('\x00'*self.alignment)[:-(len(data) % self.alignment)]
138 return data
140 def fromString(self, data):
141 self.rawData = data
142 for field in self.commonHdr+self.structure:
143 if self.debug: 143 ↛ 144line 143 didn't jump to line 144, because the condition on line 143 was never true
144 print("fromString( %s | %s | %r )" % (field[0], field[1], data))
145 size = self.calcUnpackSize(field[1], data, field[0])
146 if self.debug: 146 ↛ 147line 146 didn't jump to line 147, because the condition on line 146 was never true
147 print(" size = %d" % size)
148 dataClassOrCode = b
149 if len(field) > 2:
150 dataClassOrCode = field[2]
151 try:
152 self[field[0]] = self.unpack(field[1], data[:size], dataClassOrCode = dataClassOrCode, field = field[0])
153 except Exception as e:
154 e.args += ("When unpacking field '%s | %s | %r[:%d]'" % (field[0], field[1], data, size),)
155 raise
157 size = self.calcPackSize(field[1], self[field[0]], field[0])
158 if self.alignment and size % self.alignment:
159 size += self.alignment - (size % self.alignment)
160 data = data[size:]
162 return self
164 def __setitem__(self, key, value):
165 self.fields[key] = value
166 self.data = None # force recompute
168 def __getitem__(self, key):
169 return self.fields[key]
171 def __delitem__(self, key):
172 del self.fields[key]
174 def __str__(self):
175 return self.getData()
177 def __len__(self):
178 # XXX: improve
179 return len(self.getData())
181 def pack(self, format, data, field = None):
182 if self.debug: 182 ↛ 183line 182 didn't jump to line 183, because the condition on line 182 was never true
183 print(" pack( %s | %r | %s)" % (format, data, field))
185 if field:
186 addressField = self.findAddressFieldFor(field)
187 if (addressField is not None) and (data is None):
188 return b''
190 # void specifier
191 if format[:1] == '_':
192 return b''
194 # quote specifier
195 if format[:1] == "'" or format[:1] == '"':
196 return b(format[1:])
198 # code specifier
199 two = format.split('=')
200 if len(two) >= 2:
201 try:
202 return self.pack(two[0], data)
203 except:
204 fields = {'self':self}
205 fields.update(self.fields)
206 return self.pack(two[0], eval(two[1], {}, fields))
208 # address specifier
209 two = format.split('&')
210 if len(two) == 2:
211 try:
212 return self.pack(two[0], data)
213 except:
214 if (two[1] in self.fields) and (self[two[1]] is not None):
215 return self.pack(two[0], id(self[two[1]]) & ((1<<(calcsize(two[0])*8))-1) )
216 else:
217 return self.pack(two[0], 0)
219 # length specifier
220 two = format.split('-')
221 if len(two) == 2:
222 try:
223 return self.pack(two[0],data)
224 except:
225 return self.pack(two[0], self.calcPackFieldSize(two[1]))
227 # array specifier
228 two = format.split('*')
229 if len(two) == 2:
230 answer = bytes()
231 for each in data:
232 answer += self.pack(two[1], each)
233 if two[0]:
234 if two[0].isdigit(): 234 ↛ 235line 234 didn't jump to line 235, because the condition on line 234 was never true
235 if int(two[0]) != len(data):
236 raise Exception("Array field has a constant size, and it doesn't match the actual value")
237 else:
238 return self.pack(two[0], len(data))+answer
239 return answer
241 # "printf" string specifier
242 if format[:1] == '%': 242 ↛ 244line 242 didn't jump to line 244, because the condition on line 242 was never true
243 # format string like specifier
244 return b(format % data)
246 # asciiz specifier
247 if format[:1] == 'z':
248 if isinstance(data,bytes): 248 ↛ 249line 248 didn't jump to line 249, because the condition on line 248 was never true
249 return data + b('\0')
250 return bytes(b(data)+b('\0'))
252 # unicode specifier
253 if format[:1] == 'u':
254 return bytes(data+b('\0\0') + (len(data) & 1 and b('\0') or b''))
256 # DCE-RPC/NDR string specifier
257 if format[:1] == 'w':
258 if len(data) == 0: 258 ↛ 259line 258 didn't jump to line 259, because the condition on line 258 was never true
259 data = b('\0\0')
260 elif len(data) % 2:
261 data = b(data) + b('\0')
262 l = pack('<L', len(data)//2)
263 return b''.join([l, l, b('\0\0\0\0'), data])
265 if data is None:
266 raise Exception("Trying to pack None")
268 # literal specifier
269 if format[:1] == ':':
270 if isinstance(data, Structure):
271 return data.getData()
272 # If we have an object that can serialize itself, go ahead
273 elif hasattr(data, "getData"): 273 ↛ 274line 273 didn't jump to line 274, because the condition on line 273 was never true
274 return data.getData()
275 elif isinstance(data, int): 275 ↛ 276line 275 didn't jump to line 276, because the condition on line 275 was never true
276 return bytes(data)
277 elif isinstance(data, bytes) is not True:
278 return bytes(b(data))
279 else:
280 return data
282 if format[-1:] == 's':
283 # Let's be sure we send the right type
284 if isinstance(data, bytes) or isinstance(data, bytearray):
285 return pack(format, data)
286 else:
287 return pack(format, b(data))
289 # struct like specifier
290 return pack(format, data)
292 def unpack(self, format, data, dataClassOrCode = b, field = None):
293 if self.debug: 293 ↛ 294line 293 didn't jump to line 294, because the condition on line 293 was never true
294 print(" unpack( %s | %r )" % (format, data))
296 if field:
297 addressField = self.findAddressFieldFor(field)
298 if addressField is not None:
299 if not self[addressField]:
300 return
302 # void specifier
303 if format[:1] == '_':
304 if dataClassOrCode != b:
305 fields = {'self':self, 'inputDataLeft':data}
306 fields.update(self.fields)
307 return eval(dataClassOrCode, {}, fields)
308 else:
309 return None
311 # quote specifier
312 if format[:1] == "'" or format[:1] == '"':
313 answer = format[1:]
314 if b(answer) != data:
315 raise Exception("Unpacked data doesn't match constant value '%r' should be '%r'" % (data, answer))
316 return answer
318 # address specifier
319 two = format.split('&')
320 if len(two) == 2:
321 return self.unpack(two[0],data)
323 # code specifier
324 two = format.split('=')
325 if len(two) >= 2:
326 return self.unpack(two[0],data)
328 # length specifier
329 two = format.split('-')
330 if len(two) == 2:
331 return self.unpack(two[0],data)
333 # array specifier
334 two = format.split('*')
335 if len(two) == 2:
336 answer = []
337 sofar = 0
338 if two[0].isdigit(): 338 ↛ 339line 338 didn't jump to line 339, because the condition on line 338 was never true
339 number = int(two[0])
340 elif two[0]:
341 sofar += self.calcUnpackSize(two[0], data)
342 number = self.unpack(two[0], data[:sofar])
343 else:
344 number = -1
346 while number and sofar < len(data):
347 nsofar = sofar + self.calcUnpackSize(two[1],data[sofar:])
348 answer.append(self.unpack(two[1], data[sofar:nsofar], dataClassOrCode))
349 number -= 1
350 sofar = nsofar
351 return answer
353 # "printf" string specifier
354 if format[:1] == '%': 354 ↛ 356line 354 didn't jump to line 356, because the condition on line 354 was never true
355 # format string like specifier
356 return format % data
358 # asciiz specifier
359 if format == 'z':
360 if data[-1:] != b('\x00'):
361 raise Exception("%s 'z' field is not NUL terminated: %r" % (field, data))
362 if PY3: 362 ↛ 365line 362 didn't jump to line 365, because the condition on line 362 was never false
363 return data[:-1].decode('latin-1')
364 else:
365 return data[:-1]
367 # unicode specifier
368 if format == 'u':
369 if data[-2:] != b('\x00\x00'): 369 ↛ 370line 369 didn't jump to line 370, because the condition on line 369 was never true
370 raise Exception("%s 'u' field is not NUL-NUL terminated: %r" % (field, data))
371 return data[:-2] # remove trailing NUL
373 # DCE-RPC/NDR string specifier
374 if format == 'w':
375 l = unpack('<L', data[:4])[0]
376 return data[12:12+l*2]
378 # literal specifier
379 if format == ':':
380 if isinstance(data, bytes) and dataClassOrCode is b:
381 return data
382 return dataClassOrCode(data)
384 # struct like specifier
385 return unpack(format, data)[0]
387 def calcPackSize(self, format, data, field = None):
388# # print " calcPackSize %s:%r" % (format, data)
389 if field:
390 addressField = self.findAddressFieldFor(field)
391 if addressField is not None:
392 if not self[addressField]:
393 return 0
395 # void specifier
396 if format[:1] == '_':
397 return 0
399 # quote specifier
400 if format[:1] == "'" or format[:1] == '"':
401 return len(format)-1
403 # address specifier
404 two = format.split('&')
405 if len(two) == 2:
406 return self.calcPackSize(two[0], data)
408 # code specifier
409 two = format.split('=')
410 if len(two) >= 2:
411 return self.calcPackSize(two[0], data)
413 # length specifier
414 two = format.split('-')
415 if len(two) == 2:
416 return self.calcPackSize(two[0], data)
418 # array specifier
419 two = format.split('*')
420 if len(two) == 2:
421 answer = 0
422 if two[0].isdigit(): 422 ↛ 423line 422 didn't jump to line 423, because the condition on line 422 was never true
423 if int(two[0]) != len(data):
424 raise Exception("Array field has a constant size, and it doesn't match the actual value")
425 elif two[0]:
426 answer += self.calcPackSize(two[0], len(data))
428 for each in data:
429 answer += self.calcPackSize(two[1], each)
430 return answer
432 # "printf" string specifier
433 if format[:1] == '%': 433 ↛ 435line 433 didn't jump to line 435, because the condition on line 433 was never true
434 # format string like specifier
435 return len(format % data)
437 # asciiz specifier
438 if format[:1] == 'z':
439 return len(data)+1
441 # asciiz specifier
442 if format[:1] == 'u':
443 l = len(data)
444 return l + (l & 1 and 3 or 2)
446 # DCE-RPC/NDR string specifier
447 if format[:1] == 'w':
448 l = len(data)
449 return 12+l+l % 2
451 # literal specifier
452 if format[:1] == ':':
453 return len(data)
455 # struct like specifier
456 return calcsize(format)
458 def calcUnpackSize(self, format, data, field = None):
459 if self.debug: 459 ↛ 460line 459 didn't jump to line 460, because the condition on line 459 was never true
460 print(" calcUnpackSize( %s | %s | %r)" % (field, format, data))
462 # void specifier
463 if format[:1] == '_':
464 return 0
466 addressField = self.findAddressFieldFor(field)
467 if addressField is not None:
468 if not self[addressField]:
469 return 0
471 try:
472 lengthField = self.findLengthFieldFor(field)
473 return int(self[lengthField])
474 except Exception:
475 pass
477 # XXX: Try to match to actual values, raise if no match
479 # quote specifier
480 if format[:1] == "'" or format[:1] == '"':
481 return len(format)-1
483 # address specifier
484 two = format.split('&')
485 if len(two) == 2:
486 return self.calcUnpackSize(two[0], data)
488 # code specifier
489 two = format.split('=')
490 if len(two) >= 2:
491 return self.calcUnpackSize(two[0], data)
493 # length specifier
494 two = format.split('-')
495 if len(two) == 2:
496 return self.calcUnpackSize(two[0], data)
498 # array specifier
499 two = format.split('*')
500 if len(two) == 2:
501 answer = 0
502 if two[0]:
503 if two[0].isdigit(): 503 ↛ 504line 503 didn't jump to line 504, because the condition on line 503 was never true
504 number = int(two[0])
505 else:
506 answer += self.calcUnpackSize(two[0], data)
507 number = self.unpack(two[0], data[:answer])
509 while number:
510 number -= 1
511 answer += self.calcUnpackSize(two[1], data[answer:])
512 else:
513 while answer < len(data):
514 answer += self.calcUnpackSize(two[1], data[answer:])
515 return answer
517 # "printf" string specifier
518 if format[:1] == '%': 518 ↛ 519line 518 didn't jump to line 519, because the condition on line 518 was never true
519 raise Exception("Can't guess the size of a printf like specifier for unpacking")
521 # asciiz specifier
522 if format[:1] == 'z':
523 return data.index(b('\x00'))+1
525 # asciiz specifier
526 if format[:1] == 'u':
527 l = data.index(b('\x00\x00'))
528 return l + (l & 1 and 3 or 2)
530 # DCE-RPC/NDR string specifier
531 if format[:1] == 'w':
532 l = unpack('<L', data[:4])[0]
533 return 12+l*2
535 # literal specifier
536 if format[:1] == ':':
537 return len(data)
539 # struct like specifier
540 return calcsize(format)
542 def calcPackFieldSize(self, fieldName, format = None):
543 if format is None: 543 ↛ 546line 543 didn't jump to line 546, because the condition on line 543 was never false
544 format = self.formatForField(fieldName)
546 return self.calcPackSize(format, self[fieldName])
548 def formatForField(self, fieldName):
549 for field in self.commonHdr+self.structure: 549 ↛ 552line 549 didn't jump to line 552, because the loop on line 549 didn't complete
550 if field[0] == fieldName:
551 return field[1]
552 raise Exception("Field %s not found" % fieldName)
554 def findAddressFieldFor(self, fieldName):
555 descriptor = '&%s' % fieldName
556 l = len(descriptor)
557 for field in self.commonHdr+self.structure:
558 if field[1][-l:] == descriptor:
559 return field[0]
560 return None
562 def findLengthFieldFor(self, fieldName):
563 descriptor = '-%s' % fieldName
564 l = len(descriptor)
565 for field in self.commonHdr+self.structure:
566 if field[1][-l:] == descriptor:
567 return field[0]
568 return None
570 def zeroValue(self, format):
571 two = format.split('*')
572 if len(two) == 2:
573 if two[0].isdigit():
574 return (self.zeroValue(two[1]),)*int(two[0])
576 if not format.find('*') == -1:
577 return ()
578 if 's' in format:
579 return b''
580 if format in ['z',':','u']:
581 return b''
582 if format == 'w':
583 return b('\x00\x00')
585 return 0
587 def clear(self):
588 for field in self.commonHdr + self.structure:
589 self[field[0]] = self.zeroValue(field[1])
591 def dump(self, msg = None, indent = 0):
592 if msg is None:
593 msg = self.__class__.__name__
594 ind = ' '*indent
595 print("\n%s" % msg)
596 fixedFields = []
597 for field in self.commonHdr+self.structure:
598 i = field[0]
599 if i in self.fields: 599 ↛ 597line 599 didn't jump to line 597, because the condition on line 599 was never false
600 fixedFields.append(i)
601 if isinstance(self[i], Structure):
602 self[i].dump('%s%s:{' % (ind,i), indent = indent + 4)
603 print("%s}" % ind)
604 else:
605 print("%s%s: {%r}" % (ind,i,self[i]))
606 # Do we have remaining fields not defined in the structures? let's
607 # print them
608 remainingFields = list(set(self.fields) - set(fixedFields))
609 for i in remainingFields:
610 if isinstance(self[i], Structure): 610 ↛ 611line 610 didn't jump to line 611, because the condition on line 610 was never true
611 self[i].dump('%s%s:{' % (ind,i), indent = indent + 4)
612 print("%s}" % ind)
613 else:
614 print("%s%s: {%r}" % (ind,i,self[i]))
616def pretty_print(x):
617 if chr(x) in '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ ':
618 return chr(x)
619 else:
620 return u'.'
622def hexdump(data, indent = ''):
623 if data is None: 623 ↛ 624line 623 didn't jump to line 624, because the condition on line 623 was never true
624 return
625 if isinstance(data, int): 625 ↛ 626line 625 didn't jump to line 626, because the condition on line 625 was never true
626 data = str(data).encode('utf-8')
627 x=bytearray(data)
628 strLen = len(x)
629 i = 0
630 while i < strLen:
631 line = " %s%04x " % (indent, i)
632 for j in range(16):
633 if i+j < strLen:
634 line += "%02X " % x[i+j]
635 else:
636 line += u" "
637 if j%16 == 7:
638 line += " "
639 line += " "
640 line += ''.join(pretty_print(x) for x in x[i:i+16] )
641 print (line)
642 i += 16
644def parse_bitmask(dict, value):
645 ret = ''
647 for i in range(0, 31):
648 flag = 1 << i
650 if value & flag == 0:
651 continue
653 if flag in dict:
654 ret += '%s | ' % dict[flag]
655 else:
656 ret += "0x%.8X | " % flag
658 if len(ret) == 0:
659 return '0'
660 else:
661 return ret[:-3]