Coverage for /root/GitHubProjects/impacket/impacket/dcerpc/v5/ndr.py : 83%

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#
9# Description:
10# [C706] Transfer NDR Syntax implementation
11#
12# Author:
13# Alberto Solino (@agsolino)
14#
15# ToDo:
16# [X] Unions and rest of the structured types
17# [ ] Documentation for this library, especially the support for Arrays
18#
19from __future__ import division
20from __future__ import print_function
21import random
22import inspect
23from struct import pack, unpack_from, calcsize
24from six import with_metaclass, PY3
26from impacket import LOG
27from impacket.dcerpc.v5.enum import Enum
28from impacket.uuid import uuidtup_to_bin
30# Something important to have in mind:
31# Diagrams do not depict the specified alignment gaps, which can appear in the octet stream
32# before an item (see Section 14.2.2 on page 620.)
33# Where necessary, an alignment gap, consisting of octets of unspecified value, *precedes* the
34# representation of a primitive. The gap is of the smallest size sufficient to align the primitive
36class NDR(object):
37 """
38 This will be the base class for all DCERPC NDR Types and represents a NDR Primitive Type
39 """
40 referent = ()
41 commonHdr = ()
42 commonHdr64 = ()
43 structure = ()
44 structure64 = ()
45 align = 4
46 item = None
47 _isNDR64 = False
49 def __init__(self, data = None, isNDR64 = False):
50 object.__init__(self)
51 self._isNDR64 = isNDR64
52 self.fields = {}
54 if isNDR64 is True:
55 if self.commonHdr64 != ():
56 self.commonHdr = self.commonHdr64
57 if self.structure64 != ():
58 self.structure = self.structure64
59 if hasattr(self, 'align64'):
60 self.align = self.align64
62 for fieldName, fieldTypeOrClass in self.commonHdr+self.structure+self.referent:
63 if self.isNDR(fieldTypeOrClass):
64 self.fields[fieldName] = fieldTypeOrClass(isNDR64 = self._isNDR64)
65 elif fieldTypeOrClass == ':':
66 self.fields[fieldName] = b''
67 elif len(fieldTypeOrClass.split('=')) == 2:
68 try:
69 self.fields[fieldName] = eval(fieldTypeOrClass.split('=')[1])
70 except:
71 self.fields[fieldName] = None
72 else:
73 self.fields[fieldName] = []
75 if data is not None:
76 self.fromString(data)
78 def changeTransferSyntax(self, newSyntax):
79 NDR64Syntax = uuidtup_to_bin(('71710533-BEBA-4937-8319-B5DBEF9CCC36', '1.0'))
80 if newSyntax == NDR64Syntax: 80 ↛ 108line 80 didn't jump to line 108, because the condition on line 80 was never false
81 if self._isNDR64 is False:
82 # Ok, let's change everything
83 self._isNDR64 = True
84 for fieldName in list(self.fields.keys()):
85 if isinstance(self.fields[fieldName], NDR):
86 self.fields[fieldName].changeTransferSyntax(newSyntax)
87 # Finally, I change myself
88 if self.commonHdr64 != ():
89 self.commonHdr = self.commonHdr64
90 if self.structure64 != ():
91 self.structure = self.structure64
92 if hasattr(self, 'align64'):
93 self.align = self.align64
94 # And check whether the changes changed the data types
95 # if so, I need to instantiate the new ones and copy the
96 # old values
97 for fieldName, fieldTypeOrClass in self.commonHdr+self.structure+self.referent:
98 if isinstance(self.fields[fieldName], NDR):
99 if fieldTypeOrClass != self.fields[fieldName].__class__ and isinstance(self.fields[fieldName], NDRPOINTERNULL) is False:
100 backupData = self[fieldName]
101 self.fields[fieldName] = fieldTypeOrClass(isNDR64 = self._isNDR64)
102 if 'Data' in self.fields[fieldName].fields: 102 ↛ 105line 102 didn't jump to line 105, because the condition on line 102 was never false
103 self.fields[fieldName].fields['Data'] = backupData
104 else:
105 self[fieldName] = backupData
107 else:
108 if self._isNDR64 is True:
109 # Ok, nothing for now
110 raise Exception('Shouldn\'t be here')
112 def __setitem__(self, key, value):
113 if isinstance(value, NDRPOINTERNULL):
114 value = NDRPOINTERNULL(isNDR64 = self._isNDR64)
115 if isinstance(self.fields[key], NDRPOINTER):
116 self.fields[key] = value
117 elif 'Data' in self.fields[key].fields: 117 ↛ exitline 117 didn't return from function '__setitem__', because the condition on line 117 was never false
118 if isinstance(self.fields[key].fields['Data'], NDRPOINTER):
119 self.fields[key].fields['Data'] = value
120 elif isinstance(value, NDR):
121 # It's not a null pointer, ok. Another NDR type, but it
122 # must be the same same as the iteam already in place
123 if self.fields[key].__class__.__name__ == value.__class__.__name__:
124 self.fields[key] = value
125 elif isinstance(self.fields[key]['Data'], NDR):
126 if self.fields[key]['Data'].__class__.__name__ == value.__class__.__name__: 126 ↛ 129line 126 didn't jump to line 129, because the condition on line 126 was never false
127 self.fields[key]['Data'] = value
128 else:
129 LOG.error("Can't setitem with class specified, should be %s" % self.fields[key]['Data'].__class__.__name__)
130 else:
131 LOG.error("Can't setitem with class specified, should be %s" % self.fields[key].__class__.__name__)
132 elif isinstance(self.fields[key], NDR):
133 self.fields[key]['Data'] = value
134 else:
135 self.fields[key] = value
137 def __getitem__(self, key):
138 if isinstance(self.fields[key], NDR):
139 if 'Data' in self.fields[key].fields:
140 return self.fields[key]['Data']
141 return self.fields[key]
143 def __str__(self):
144 return self.getData()
146 def __len__(self):
147 # XXX: improve
148 return len(self.getData())
150 def getDataLen(self, data, offset=0):
151 return len(data) - offset
153 @staticmethod
154 def isNDR(field):
155 if inspect.isclass(field):
156 myClass = field
157 if issubclass(myClass, NDR): 157 ↛ 159line 157 didn't jump to line 159, because the condition on line 157 was never false
158 return True
159 return False
161 def dumpRaw(self, msg = None, indent = 0):
162 if msg is None:
163 msg = self.__class__.__name__
164 ind = ' '*indent
165 print("\n%s" % msg)
166 for field in self.commonHdr+self.structure+self.referent:
167 i = field[0]
168 if i in self.fields: 168 ↛ 166line 168 didn't jump to line 166, because the condition on line 168 was never false
169 if isinstance(self.fields[i], NDR):
170 self.fields[i].dumpRaw('%s%s:{' % (ind,i), indent = indent + 4)
171 print("%s}" % ind)
173 elif isinstance(self.fields[i], list):
174 print("%s[" % ind)
175 for num,j in enumerate(self.fields[i]):
176 if isinstance(j, NDR):
177 j.dumpRaw('%s%s:' % (ind,i), indent = indent + 4)
178 print("%s," % ind)
179 else:
180 print("%s%s: {%r}," % (ind, i, j))
181 print("%s]" % ind)
183 else:
184 print("%s%s: {%r}" % (ind,i,self[i]))
186 def dump(self, msg = None, indent = 0):
187 if msg is None:
188 msg = self.__class__.__name__
189 ind = ' '*indent
190 if msg != '':
191 print("%s" % msg, end=' ')
192 for fieldName, fieldType in self.commonHdr+self.structure+self.referent:
193 if fieldName in self.fields: 193 ↛ 192line 193 didn't jump to line 192, because the condition on line 193 was never false
194 if isinstance(self.fields[fieldName], NDR):
195 self.fields[fieldName].dump('\n%s%-31s' % (ind, fieldName+':'), indent = indent + 4),
196 else:
197 print(" %r" % (self[fieldName]), end=' ')
199 def getAlignment(self):
200 return self.align
202 @staticmethod
203 def calculatePad(fieldType, soFar):
204 if isinstance(fieldType, str):
205 try:
206 alignment = calcsize(fieldType.split('=')[0])
207 except:
208 alignment = 0
209 else:
210 alignment = 0
212 if alignment > 0:
213 pad = (alignment - (soFar % alignment)) % alignment
214 else:
215 pad = 0
217 return pad
219 def getData(self, soFar = 0):
220 data = b''
221 for fieldName, fieldTypeOrClass in self.commonHdr+self.structure:
222 try:
223 # Alignment of Primitive Types
225 # NDR enforces NDR alignment of primitive data; that is, any primitive of size n
226 # octets is aligned at a octet stream index that is a multiple of n.
227 # (In this version of NDR, n is one of {1, 2, 4, 8}.) An octet stream index indicates
228 # the number of an octet in an octet stream when octets are numbered, beginning with 0,
229 # from the first octet in the stream. Where necessary, an alignment gap, consisting of
230 # octets of unspecified value, precedes the representation of a primitive. The gap is
231 # of the smallest size sufficient to align the primitive.
232 pad = self.calculatePad(fieldTypeOrClass, soFar)
233 if pad > 0:
234 soFar += pad
235 data += b'\xbf'*pad
237 res = self.pack(fieldName, fieldTypeOrClass, soFar)
239 data += res
240 soFar += len(res)
241 except Exception as e:
242 LOG.error(str(e))
243 LOG.error("Error packing field '%s | %s' in %s" % (fieldName, fieldTypeOrClass, self.__class__))
244 raise
246 return data
248 def fromString(self, data, offset=0):
249 offset0 = offset
250 for fieldName, fieldTypeOrClass in self.commonHdr+self.structure:
251 try:
252 # Alignment of Primitive Types
254 # NDR enforces NDR alignment of primitive data; that is, any primitive of size n
255 # octets is aligned at a octet stream index that is a multiple of n.
256 # (In this version of NDR, n is one of {1, 2, 4, 8}.) An octet stream index indicates
257 # the number of an octet in an octet stream when octets are numbered, beginning with 0,
258 # from the first octet in the stream. Where necessary, an alignment gap, consisting of
259 # octets of unspecified value, precedes the representation of a primitive. The gap is
260 # of the smallest size sufficient to align the primitive.
261 offset += self.calculatePad(fieldTypeOrClass, offset)
263 offset += self.unpack(fieldName, fieldTypeOrClass, data, offset)
264 except Exception as e:
265 LOG.error(str(e))
266 LOG.error("Error unpacking field '%s | %s | %r'" % (fieldName, fieldTypeOrClass, data[offset:offset+256]))
267 raise
268 return offset - offset0
270 def pack(self, fieldName, fieldTypeOrClass, soFar = 0):
271 if isinstance(self.fields[fieldName], NDR):
272 return self.fields[fieldName].getData(soFar)
274 data = self.fields[fieldName]
275 # void specifier
276 if fieldTypeOrClass[:1] == '_': 276 ↛ 277line 276 didn't jump to line 277, because the condition on line 276 was never true
277 return b''
279 # code specifier
280 two = fieldTypeOrClass.split('=')
281 if len(two) >= 2:
282 try:
283 return self.pack(fieldName, two[0], soFar)
284 except:
285 self.fields[fieldName] = eval(two[1], {}, self.fields)
286 return self.pack(fieldName, two[0], soFar)
288 if data is None:
289 raise Exception('Trying to pack None')
291 # literal specifier
292 if fieldTypeOrClass[:1] == ':':
293 if hasattr(data, 'getData'):
294 return data.getData()
295 return data
297 # struct like specifier
298 return pack(fieldTypeOrClass, data)
300 def unpack(self, fieldName, fieldTypeOrClass, data, offset=0):
301 if isinstance(self.fields[fieldName], NDR):
302 return self.fields[fieldName].fromString(data, offset)
304 # code specifier
305 two = fieldTypeOrClass.split('=')
306 if len(two) >= 2:
307 return self.unpack(fieldName, two[0], data, offset)
309 # literal specifier
310 if fieldTypeOrClass == ':':
311 if isinstance(fieldTypeOrClass, NDR): 311 ↛ 312line 311 didn't jump to line 312, because the condition on line 311 was never true
312 return self.fields[fieldName].fromString(data, offset)
313 else:
314 dataLen = self.getDataLen(data, offset)
315 self.fields[fieldName] = data[offset:offset+dataLen]
316 return dataLen
318 # struct like specifier
319 self.fields[fieldName] = unpack_from(fieldTypeOrClass, data, offset)[0]
321 return calcsize(fieldTypeOrClass)
323 def calcPackSize(self, fieldTypeOrClass, data):
324 if isinstance(fieldTypeOrClass, str) is False: 324 ↛ 325line 324 didn't jump to line 325, because the condition on line 324 was never true
325 return len(data)
327 # code specifier
328 two = fieldTypeOrClass.split('=')
329 if len(two) >= 2:
330 return self.calcPackSize(two[0], data)
332 # literal specifier
333 if fieldTypeOrClass[:1] == ':':
334 return len(data)
336 # struct like specifier
337 return calcsize(fieldTypeOrClass)
339 def calcUnPackSize(self, fieldTypeOrClass, data, offset=0):
340 if isinstance(fieldTypeOrClass, str) is False:
341 return len(data) - offset
343 # code specifier
344 two = fieldTypeOrClass.split('=')
345 if len(two) >= 2:
346 return self.calcUnPackSize(two[0], data, offset)
348 # array specifier
349 two = fieldTypeOrClass.split('*')
350 if len(two) == 2:
351 return len(data) - offset
353 # literal specifier
354 if fieldTypeOrClass[:1] == ':':
355 return len(data) - offset
357 # struct like specifier
358 return calcsize(fieldTypeOrClass)
360# NDR Primitives
361class NDRSMALL(NDR):
362 align = 1
363 structure = (
364 ('Data', 'b=0'),
365 )
367class NDRUSMALL(NDR):
368 align = 1
369 structure = (
370 ('Data', 'B=0'),
371 )
373class NDRBOOLEAN(NDRSMALL):
374 def dump(self, msg = None, indent = 0):
375 if msg is None:
376 msg = self.__class__.__name__
377 if msg != '':
378 print(msg, end=' ')
380 if self['Data'] > 0:
381 print(" TRUE")
382 else:
383 print(" FALSE")
385class NDRCHAR(NDR):
386 align = 1
387 structure = (
388 ('Data', 'c'),
389 )
391class NDRSHORT(NDR):
392 align = 2
393 structure = (
394 ('Data', '<h=0'),
395 )
397class NDRUSHORT(NDR):
398 align = 2
399 structure = (
400 ('Data', '<H=0'),
401 )
403class NDRLONG(NDR):
404 align = 4
405 structure = (
406 ('Data', '<l=0'),
407 )
409class NDRULONG(NDR):
410 align = 4
411 structure = (
412 ('Data', '<L=0'),
413 )
415class NDRHYPER(NDR):
416 align = 8
417 structure = (
418 ('Data', '<q=0'),
419 )
421class NDRUHYPER(NDR):
422 align = 8
423 structure = (
424 ('Data', '<Q=0'),
425 )
427class NDRFLOAT(NDR):
428 align = 4
429 structure = (
430 ('Data', '<f=0'),
431 )
433class NDRDOUBLEFLOAT(NDR):
434 align = 8
435 structure = (
436 ('Data', '<d=0'),
437 )
439class EnumType(type):
440 def __getattr__(self, attr):
441 return self.enumItems[attr].value
443class NDRENUM(with_metaclass(EnumType, NDR)):
444 align = 2
445 align64 = 4
446 structure = (
447 ('Data', '<H'),
448 )
450 # 2.2.5.2 NDR64 Simple Data Types
451 # NDR64 supports all simple types defined by NDR (as specified in [C706] section 14.2)
452 # with the same alignment requirements except for enumerated types, which MUST be
453 # represented as signed long integers (4 octets) in NDR64.
454 structure64 = (
455 ('Data', '<L'),
456 )
457 # enum MUST be an python enum (see enum.py)
458 class enumItems(Enum):
459 pass
461 def __setitem__(self, key, value):
462 if isinstance(value, Enum): 462 ↛ 463line 462 didn't jump to line 463, because the condition on line 462 was never true
463 self['Data'] = value.value
464 else:
465 return NDR.__setitem__(self,key,value)
467 def dump(self, msg = None, indent = 0):
468 if msg is None: 468 ↛ 469line 468 didn't jump to line 469, because the condition on line 468 was never true
469 msg = self.__class__.__name__
470 if msg != '': 470 ↛ 473line 470 didn't jump to line 473, because the condition on line 470 was never false
471 print(msg, end=' ')
473 print(" %s" % self.enumItems(self.fields['Data']).name, end=' ')
475# NDR Constructed Types (arrays, strings, structures, unions, variant structures, pipes and pointers)
476class NDRCONSTRUCTEDTYPE(NDR):
477 @staticmethod
478 def isPointer(field):
479 if inspect.isclass(field): 479 ↛ 483line 479 didn't jump to line 483, because the condition on line 479 was never false
480 myClass = field
481 if issubclass(myClass, NDRPOINTER):
482 return True
483 return False
485 @staticmethod
486 def isUnion(field):
487 if inspect.isclass(field): 487 ↛ 491line 487 didn't jump to line 491, because the condition on line 487 was never false
488 myClass = field
489 if issubclass(myClass, NDRUNION):
490 return True
491 return False
493 def getDataReferents(self, soFar = 0):
494 data = b''
495 for fieldName, fieldTypeOrClass in self.commonHdr+self.structure:
496 if isinstance(self.fields[fieldName], NDRCONSTRUCTEDTYPE):
497 data += self.fields[fieldName].getDataReferents(len(data)+soFar)
498 data += self.fields[fieldName].getDataReferent(len(data)+soFar)
499 return data
501 def getDataReferent(self, soFar=0):
502 data = b''
503 soFar0 = soFar
504 if hasattr(self,'referent') is False: 504 ↛ 505line 504 didn't jump to line 505, because the condition on line 504 was never true
505 return b''
507 if 'ReferentID' in self.fields:
508 if self['ReferentID'] == 0:
509 return b''
511 for fieldName, fieldTypeOrClass in self.referent:
512 try:
513 if isinstance(self.fields[fieldName], NDRUniConformantArray) or isinstance(self.fields[fieldName], NDRUniConformantVaryingArray):
514 # So we have an array, first item in the structure must be the array size, although we
515 # will need to build it later.
516 if self._isNDR64:
517 arrayItemSize = 8
518 arrayPackStr = '<Q'
519 else:
520 arrayItemSize = 4
521 arrayPackStr = '<L'
523 # The size information is itself aligned according to the alignment rules for
524 # primitive data types. (See Section 14.2.2 on page 620.) The data of the constructed
525 # type is then aligned according to the alignment rules for the constructed type.
526 # In other words, the size information precedes the structure and is aligned
527 # independently of the structure alignment.
528 # We need to check whether we need padding or not
529 pad0 = (arrayItemSize - (soFar % arrayItemSize)) % arrayItemSize
530 if pad0 > 0:
531 soFar += pad0
532 arrayPadding = b'\xef'*pad0
533 else:
534 arrayPadding = b''
535 # And now, let's pretend we put the item in
536 soFar += arrayItemSize
537 data = self.fields[fieldName].getData(soFar)
538 data = arrayPadding + pack(arrayPackStr, self.getArrayMaximumSize(fieldName)) + data
539 else:
540 pad = self.calculatePad(fieldTypeOrClass, soFar)
541 if pad > 0: 541 ↛ 542line 541 didn't jump to line 542, because the condition on line 541 was never true
542 soFar += pad
543 data += b'\xcc'*pad
545 data += self.pack(fieldName, fieldTypeOrClass, soFar)
547 # Any referent information to pack?
548 if isinstance(self.fields[fieldName], NDRCONSTRUCTEDTYPE): 548 ↛ 551line 548 didn't jump to line 551, because the condition on line 548 was never false
549 data += self.fields[fieldName].getDataReferents(soFar0 + len(data))
550 data += self.fields[fieldName].getDataReferent(soFar0 + len(data))
551 soFar = soFar0 + len(data)
553 except Exception as e:
554 LOG.error(str(e))
555 LOG.error("Error packing field '%s | %s' in %s" % (fieldName, fieldTypeOrClass, self.__class__))
556 raise
558 return data
560 def calcPackSize(self, fieldTypeOrClass, data):
561 if isinstance(fieldTypeOrClass, str) is False: 561 ↛ 562line 561 didn't jump to line 562, because the condition on line 561 was never true
562 return len(data)
564 # array specifier
565 two = fieldTypeOrClass.split('*')
566 if len(two) == 2: 566 ↛ 567line 566 didn't jump to line 567, because the condition on line 566 was never true
567 answer = 0
568 for each in data:
569 if self.isNDR(self.item):
570 item = ':'
571 else:
572 item = self.item
573 answer += self.calcPackSize(item, each)
574 return answer
575 else:
576 return NDR.calcPackSize(self, fieldTypeOrClass, data)
578 def getArrayMaximumSize(self, fieldName):
579 if self.fields[fieldName].fields['MaximumCount'] is not None and self.fields[fieldName].fields['MaximumCount'] > 0:
580 return self.fields[fieldName].fields['MaximumCount']
581 else:
582 return self.fields[fieldName].getArraySize()
584 def getArraySize(self, fieldName, data, offset=0):
585 if self._isNDR64:
586 arrayItemSize = 8
587 arrayUnPackStr = '<Q'
588 else:
589 arrayItemSize = 4
590 arrayUnPackStr = '<L'
592 pad = (arrayItemSize - (offset % arrayItemSize)) % arrayItemSize
593 offset += pad
595 if isinstance(self.fields[fieldName], NDRUniConformantArray):
596 # Array Size is at the very beginning
597 arraySize = unpack_from(arrayUnPackStr, data, offset)[0]
598 elif isinstance(self.fields[fieldName], NDRUniConformantVaryingArray): 598 ↛ 608line 598 didn't jump to line 608, because the condition on line 598 was never false
599 # NDRUniConformantVaryingArray Array
600 # Unpack the Maximum Count
601 maximumCount = unpack_from(arrayUnPackStr, data, offset)[0]
602 # Let's store the Maximum Count for later use
603 self.fields[fieldName].fields['MaximumCount'] = maximumCount
604 # Unpack the Actual Count
605 arraySize = unpack_from(arrayUnPackStr, data, offset+arrayItemSize*2)[0]
606 else:
607 # NDRUniVaryingArray Array
608 arraySize = unpack_from(arrayUnPackStr, data, offset+arrayItemSize)[0]
610 return arraySize, arrayItemSize+pad
612 def fromStringReferents(self, data, offset=0):
613 offset0 = offset
614 for fieldName, fieldTypeOrClass in self.commonHdr+self.structure:
615 if isinstance(self.fields[fieldName], NDRCONSTRUCTEDTYPE):
616 offset += self.fields[fieldName].fromStringReferents(data, offset)
617 offset += self.fields[fieldName].fromStringReferent(data, offset)
618 return offset - offset0
620 def fromStringReferent(self, data, offset=0):
621 if hasattr(self, 'referent') is not True: 621 ↛ 622line 621 didn't jump to line 622, because the condition on line 621 was never true
622 return 0
624 offset0 = offset
626 if 'ReferentID' in self.fields:
627 if self['ReferentID'] == 0:
628 # NULL Pointer, there's no referent for it
629 return 0
631 for fieldName, fieldTypeOrClass in self.referent:
632 try:
633 if isinstance(self.fields[fieldName], NDRUniConformantArray) or isinstance(self.fields[fieldName], NDRUniConformantVaryingArray):
634 # Get the array size
635 arraySize, advanceStream = self.getArraySize(fieldName, data, offset)
636 offset += advanceStream
638 # Let's tell the array how many items are available
639 self.fields[fieldName].setArraySize(arraySize)
640 size = self.fields[fieldName].fromString(data, offset)
641 else:
642 # ToDo: Align only if not NDR
643 offset += self.calculatePad(fieldTypeOrClass, offset)
645 size = self.unpack(fieldName, fieldTypeOrClass, data, offset)
647 if isinstance(self.fields[fieldName], NDRCONSTRUCTEDTYPE): 647 ↛ 650line 647 didn't jump to line 650, because the condition on line 647 was never false
648 size += self.fields[fieldName].fromStringReferents(data, offset+size)
649 size += self.fields[fieldName].fromStringReferent(data, offset+size)
650 offset += size
651 except Exception as e:
652 LOG.error(str(e))
653 LOG.error("Error unpacking field '%s | %s | %r'" % (fieldName, fieldTypeOrClass, data[offset:offset+256]))
654 raise
656 return offset-offset0
658 def calcUnPackSize(self, fieldTypeOrClass, data, offset=0):
659 if isinstance(fieldTypeOrClass, str) is False:
660 return len(data) - offset
662 two = fieldTypeOrClass.split('*')
663 if len(two) == 2:
664 return len(data) - offset
665 else:
666 return NDR.calcUnPackSize(self, fieldTypeOrClass, data, offset)
668# Uni-dimensional Fixed Arrays
669class NDRArray(NDRCONSTRUCTEDTYPE):
670 def dump(self, msg = None, indent = 0):
671 if msg is None: 671 ↛ 672line 671 didn't jump to line 672, because the condition on line 671 was never true
672 msg = self.__class__.__name__
673 ind = ' '*indent
674 if msg != '':
675 print(msg, end=' ')
677 if isinstance(self['Data'], list):
678 print("\n%s[" % ind)
679 ind += ' '*4
680 for num,j in enumerate(self.fields['Data']):
681 if isinstance(j, NDR):
682 j.dump('%s' % ind, indent = indent + 4),
683 print(",")
684 else:
685 print("%s %r," % (ind,j))
686 print("%s]" % ind[:-4], end=' ')
687 else:
688 print(" %r" % self['Data'], end=' ')
690 def setArraySize(self, size):
691 self.arraySize = size
693 def getArraySize(self):
694 return self.arraySize
696 def changeTransferSyntax(self, newSyntax):
697 # Here we gotta go over each item in the array and change the TS
698 # Only if the item type is NDR
699 if hasattr(self, 'item') and self.item is not None:
700 if self.isNDR(self.item):
701 for item in self.fields['Data']:
702 item.changeTransferSyntax(newSyntax)
703 return NDRCONSTRUCTEDTYPE.changeTransferSyntax(self, newSyntax)
705 def getAlignment(self):
706 # Array alignment is the largest alignment of the array element type and
707 # the size information type, if any.
708 align = 0
709 # And now the item
710 if hasattr(self, "item") and self.item is not None:
711 if self.isNDR(self.item):
712 tmpAlign = self.item().getAlignment()
713 else:
714 tmpAlign = self.calcPackSize(self.item, b'')
715 if tmpAlign > align: 715 ↛ 717line 715 didn't jump to line 717, because the condition on line 715 was never false
716 align = tmpAlign
717 return align
719 def getData(self, soFar = 0):
720 data = b''
721 soFar0 = soFar
722 for fieldName, fieldTypeOrClass in self.structure:
723 try:
724 if self.isNDR(fieldTypeOrClass) is False: 724 ↛ 732line 724 didn't jump to line 732, because the condition on line 724 was never false
725 # If the item is not NDR (e.g. ('MaximumCount', '<L=len(Data)'))
726 # we have to align it
727 pad = self.calculatePad(fieldTypeOrClass, soFar)
728 if pad > 0: 728 ↛ 729line 728 didn't jump to line 729, because the condition on line 728 was never true
729 soFar += pad
730 data += b'\xca'*pad
732 res = self.pack(fieldName, fieldTypeOrClass, soFar)
733 data += res
734 soFar = soFar0 + len(data)
735 except Exception as e:
736 LOG.error(str(e))
737 LOG.error("Error packing field '%s | %s' in %s" % (fieldName, fieldTypeOrClass, self.__class__))
738 raise
740 return data
742 def pack(self, fieldName, fieldTypeOrClass, soFar = 0):
743 # array specifier
744 two = fieldTypeOrClass.split('*')
745 if len(two) == 2:
746 answer = b''
747 if self.isNDR(self.item):
748 item = ':'
749 dataClass = self.item
750 self.fields['_tmpItem'] = dataClass(isNDR64=self._isNDR64)
751 else:
752 item = self.item
753 dataClass = None
754 self.fields['_tmpItem'] = item
756 for each in (self.fields[fieldName]):
757 pad = self.calculatePad(self.item, len(answer)+soFar)
758 if pad > 0: 758 ↛ 759line 758 didn't jump to line 759, because the condition on line 758 was never true
759 answer += b'\xdd' * pad
760 if dataClass is None:
761 if item == 'c' and PY3 and isinstance(each, int): 761 ↛ 763line 761 didn't jump to line 763, because the condition on line 761 was never true
762 # Special case when dealing with PY3, here we have an integer we need to convert
763 each = bytes([each])
764 answer += pack(item, each)
765 else:
766 answer += each.getData(len(answer)+soFar)
768 if dataClass is not None:
769 for each in self.fields[fieldName]:
770 if isinstance(each, NDRCONSTRUCTEDTYPE):
771 answer += each.getDataReferents(len(answer)+soFar)
772 answer += each.getDataReferent(len(answer)+soFar)
774 del(self.fields['_tmpItem'])
775 if isinstance(self, NDRUniConformantArray) or isinstance(self, NDRUniConformantVaryingArray):
776 # First field points to a field with the amount of items
777 self.setArraySize(len(self.fields[fieldName]))
778 else:
779 self.fields[two[1]] = len(self.fields[fieldName])
781 return answer
782 else:
783 return NDRCONSTRUCTEDTYPE.pack(self, fieldName, fieldTypeOrClass, soFar)
785 def fromString(self, data, offset=0):
786 offset0 = offset
787 for fieldName, fieldTypeOrClass in self.commonHdr+self.structure:
788 try:
789 if self.isNDR(fieldTypeOrClass) is False: 789 ↛ 794line 789 didn't jump to line 794, because the condition on line 789 was never false
790 # If the item is not NDR (e.g. ('MaximumCount', '<L=len(Data)'))
791 # we have to align it
792 offset += self.calculatePad(fieldTypeOrClass, offset)
794 size = self.unpack(fieldName, fieldTypeOrClass, data, offset)
795 offset += size
796 except Exception as e:
797 LOG.error(str(e))
798 LOG.error("Error unpacking field '%s | %s | %r'" % (fieldName, fieldTypeOrClass, data[offset:offset+256]))
799 raise
800 return offset - offset0
802 def unpack(self, fieldName, fieldTypeOrClass, data, offset=0):
803 # array specifier
804 two = fieldTypeOrClass.split('*')
805 answer = []
806 soFarItems = 0
807 offset0 = offset
808 if len(two) == 2:
809 if isinstance(self, NDRUniConformantArray):
810 # First field points to a field with the amount of items
811 numItems = self.getArraySize()
812 elif isinstance(self, NDRUniConformantVaryingArray):
813 # In this case we have the MaximumCount but it could be different from the ActualCount.
814 # Let's make the unpack figure this out.
815 #self.fields['MaximumCount'] = self.getArraySize()
816 numItems = self[two[1]]
817 else:
818 numItems = self[two[1]]
820 # The item type is determined by self.item
821 if self.isNDR(self.item):
822 item = ':'
823 dataClassOrCode = self.item
824 self.fields['_tmpItem'] = dataClassOrCode(isNDR64=self._isNDR64)
825 else:
826 item = self.item
827 dataClassOrCode = None
828 self.fields['_tmpItem'] = item
830 nsofar = 0
831 while numItems and soFarItems < len(data) - offset:
832 pad = self.calculatePad(self.item, soFarItems+offset)
833 if pad > 0: 833 ↛ 834line 833 didn't jump to line 834, because the condition on line 833 was never true
834 soFarItems +=pad
835 if dataClassOrCode is None:
836 nsofar = soFarItems + calcsize(item)
837 answer.append(unpack_from(item, data, offset+soFarItems)[0])
838 else:
839 itemn = dataClassOrCode(isNDR64=self._isNDR64)
840 size = itemn.fromString(data, offset+soFarItems)
841 answer.append(itemn)
842 nsofar += size + pad
843 numItems -= 1
844 soFarItems = nsofar
846 if dataClassOrCode is not None and isinstance(dataClassOrCode(), NDRCONSTRUCTEDTYPE):
847 # We gotta go over again, asking for the referents
848 answer2 = []
849 for itemn in answer:
850 size = itemn.fromStringReferents(data, soFarItems+offset)
851 soFarItems += size
852 size = itemn.fromStringReferent(data, soFarItems+offset)
853 soFarItems += size
854 answer2.append(itemn)
855 answer = answer2
856 del answer2
858 del(self.fields['_tmpItem'])
860 self.fields[fieldName] = answer
861 return soFarItems + offset - offset0
862 else:
863 return NDRCONSTRUCTEDTYPE.unpack(self, fieldName, fieldTypeOrClass, data, offset)
865class NDRUniFixedArray(NDRArray):
866 structure = (
867 ('Data',':'),
868 )
870# Uni-dimensional Conformant Arrays
871class NDRUniConformantArray(NDRArray):
872 item = 'c'
873 structure = (
874 #('MaximumCount', '<L=len(Data)'),
875 ('Data', '*MaximumCount'),
876 )
878 structure64 = (
879 #('MaximumCount', '<Q=len(Data)'),
880 ('Data', '*MaximumCount'),
881 )
883 def __init__(self, data = None, isNDR64 = False):
884 NDRArray.__init__(self, data, isNDR64)
885 # Let's store the hidden MaximumCount field
886 self.fields['MaximumCount'] = 0
888 def __setitem__(self, key, value):
889 self.fields['MaximumCount'] = None
890 return NDRArray.__setitem__(self, key, value)
893# Uni-dimensional Varying Arrays
894class NDRUniVaryingArray(NDRArray):
895 item = 'c'
896 structure = (
897 ('Offset','<L=0'),
898 ('ActualCount','<L=len(Data)'),
899 ('Data','*ActualCount'),
900 )
901 structure64 = (
902 ('Offset','<Q=0'),
903 ('ActualCount','<Q=len(Data)'),
904 ('Data','*ActualCount'),
905 )
907 def __setitem__(self, key, value):
908 self.fields['ActualCount'] = None
909 return NDRArray.__setitem__(self, key, value)
911# Uni-dimensional Conformant-varying Arrays
912class NDRUniConformantVaryingArray(NDRArray):
913 item = 'c'
914 commonHdr = (
915 #('MaximumCount', '<L=len(Data)'),
916 ('Offset','<L=0'),
917 ('ActualCount','<L=len(Data)'),
918 )
919 commonHdr64 = (
920 #('MaximumCount', '<Q=len(Data)'),
921 ('Offset','<Q=0'),
922 ('ActualCount','<Q=len(Data)'),
923 )
925 structure = (
926 ('Data','*ActualCount'),
927 )
929 def __init__(self, data = None, isNDR64 = False):
930 NDRArray.__init__(self, data, isNDR64)
931 # Let's store the hidden MaximumCount field
932 self.fields['MaximumCount'] = 0
934 def __setitem__(self, key, value):
935 self.fields['MaximumCount'] = None
936 self.fields['ActualCount'] = None
937 return NDRArray.__setitem__(self, key, value)
939 def getData(self, soFar = 0):
940 data = b''
941 soFar0 = soFar
942 for fieldName, fieldTypeOrClass in self.commonHdr+self.structure:
943 try:
944 pad = self.calculatePad(fieldTypeOrClass, soFar)
945 if pad > 0: 945 ↛ 946line 945 didn't jump to line 946, because the condition on line 945 was never true
946 soFar += pad
947 data += b'\xcb'*pad
949 res = self.pack(fieldName, fieldTypeOrClass, soFar)
950 data += res
951 soFar = soFar0 + len(data)
952 except Exception as e:
953 LOG.error(str(e))
954 LOG.error("Error packing field '%s | %s' in %s" % (fieldName, fieldTypeOrClass, self.__class__))
955 raise
957 return data
959# Multidimensional arrays not implemented for now
961# Varying Strings
962class NDRVaryingString(NDRUniVaryingArray):
963 def getData(self, soFar = 0):
964 # The last element of a string is a terminator of the same size as the other elements.
965 # If the string element size is one octet, the terminator is a NULL character.
966 # The terminator for a string of multi-byte characters is the array element zero (0).
967 if self["Data"][-1:] != b'\x00': 967 ↛ 972line 967 didn't jump to line 972, because the condition on line 967 was never false
968 if PY3 and isinstance(self["Data"],list) is False: 968 ↛ 969line 968 didn't jump to line 969, because the condition on line 968 was never true
969 self["Data"] = self["Data"] + b'\x00'
970 else:
971 self["Data"] = b''.join(self["Data"]) + b'\x00'
972 return NDRUniVaryingArray.getData(self, soFar)
974 def fromString(self, data, offset = 0):
975 ret = NDRUniVaryingArray.fromString(self, data, offset)
976 # Let's take out the last item
977 self["Data"] = self["Data"][:-1]
978 return ret
980# Conformant and Varying Strings
981class NDRConformantVaryingString(NDRUniConformantVaryingArray):
982 pass
984# Structures
985# Structures Containing a Conformant Array
986# Structures Containing a Conformant and Varying Array
987class NDRSTRUCT(NDRCONSTRUCTEDTYPE):
988 def getData(self, soFar = 0):
989 data = b''
990 arrayPadding = b''
991 soFar0 = soFar
992 # 14.3.7.1 Structures Containing a Conformant Array
993 # A structure can contain a conformant array only as its last member.
994 # In the NDR representation of a structure that contains a conformant array,
995 # the unsigned long integers that give maximum element counts for dimensions of the array
996 # are moved to the beginning of the structure, and the array elements appear in place at
997 # the end of the structure.
998 # 14.3.7.2 Structures Containing a Conformant and Varying Array
999 # A structure can contain a conformant and varying array only as its last member.
1000 # In the NDR representation of a structure that contains a conformant and varying array,
1001 # the maximum counts for dimensions of the array are moved to the beginning of the structure,
1002 # but the offsets and actual counts remain in place at the end of the structure,
1003 # immediately preceding the array elements
1004 lastItem = (self.commonHdr+self.structure)[-1][0]
1005 if isinstance(self.fields[lastItem], NDRUniConformantArray) or isinstance(self.fields[lastItem], NDRUniConformantVaryingArray):
1006 # So we have an array, first item in the structure must be the array size, although we
1007 # will need to build it later.
1008 if self._isNDR64:
1009 arrayItemSize = 8
1010 arrayPackStr = '<Q'
1011 else:
1012 arrayItemSize = 4
1013 arrayPackStr = '<L'
1015 # The size information is itself aligned according to the alignment rules for
1016 # primitive data types. (See Section 14.2.2 on page 620.) The data of the constructed
1017 # type is then aligned according to the alignment rules for the constructed type.
1018 # In other words, the size information precedes the structure and is aligned
1019 # independently of the structure alignment.
1020 # We need to check whether we need padding or not
1021 pad0 = (arrayItemSize - (soFar % arrayItemSize)) % arrayItemSize
1022 if pad0 > 0:
1023 soFar += pad0
1024 arrayPadding = b'\xee'*pad0
1025 else:
1026 arrayPadding = b''
1027 # And now, let's pretend we put the item in
1028 soFar += arrayItemSize
1029 else:
1030 arrayItemSize = 0
1032 # Now we need to align the structure
1033 # The alignment of a structure in the octet stream is the largest of the alignments of the fields it
1034 # contains. These fields may also be constructed types. The same alignment rules apply
1035 # recursively to nested constructed types.
1036 alignment = self.getAlignment()
1038 if alignment > 0:
1039 pad = (alignment - (soFar % alignment)) % alignment
1040 if pad > 0:
1041 soFar += pad
1042 data += b'\xAB'*pad
1044 for fieldName, fieldTypeOrClass in self.commonHdr+self.structure:
1045 try:
1046 if isinstance(self.fields[fieldName], NDRUniConformantArray) or isinstance(self.fields[fieldName], NDRUniConformantVaryingArray):
1047 res = self.fields[fieldName].getData(soFar)
1048 if isinstance(self, NDRPOINTER):
1049 pointerData = data[:arrayItemSize]
1050 data = data[arrayItemSize:]
1051 data = pointerData + arrayPadding + pack(arrayPackStr ,self.getArrayMaximumSize(fieldName)) + data
1052 else:
1053 data = arrayPadding + pack(arrayPackStr, self.getArrayMaximumSize(fieldName)) + data
1054 arrayPadding = b''
1055 arrayItemSize = 0
1056 else:
1057 res = self.pack(fieldName, fieldTypeOrClass, soFar)
1058 data += res
1059 soFar = soFar0 + len(data) + len(arrayPadding) + arrayItemSize
1060 except Exception as e:
1061 LOG.error(str(e))
1062 LOG.error("Error packing field '%s | %s' in %s" % (fieldName, fieldTypeOrClass, self.__class__))
1063 raise
1065 # 2.2.5.3.4.1 Structure with Trailing Gap
1066 # NDR64 represents a structure as an ordered sequence of representations of the
1067 # structure members. The trailing gap from the last nonconformant and nonvarying
1068 # field to the alignment of the structure MUST be represented as a trailing pad.
1069 # The size of the structure MUST be a multiple of its alignment.
1070 # See the following figure.
1072 # 4.8 Example of Structure with Trailing Gap in NDR64
1073 # This example shows a structure with a trailing gap in NDR64.
1074 # typedef struct _StructWithPad
1075 # {
1076 # long l;
1077 # short s;
1078 # } StructWithPad;
1079 # The size of the structure in the octet stream MUST contain a 2-byte trailing
1080 # gap to make its size 8, a multiple of the structure's alignment, 4.
1081# if self._isNDR64 is True:
1082# # ToDo add trailing gap here
1083# if alignment > 0:
1084# pad = (alignment - (soFar % alignment)) % alignment
1085# if pad > 0:
1086# soFar += pad
1087# data += '\xcd'*pad
1088# print self.__class__ , alignment, pad, hex(soFar)
1089 return data
1091 def fromString(self, data, offset = 0 ):
1092 offset0 = offset
1093 # 14.3.7.1 Structures Containing a Conformant Array
1094 # A structure can contain a conformant array only as its last member.
1095 # In the NDR representation of a structure that contains a conformant array,
1096 # the unsigned long integers that give maximum element counts for dimensions of the array
1097 # are moved to the beginning of the structure, and the array elements appear in place at
1098 # the end of the structure.
1099 # 14.3.7.2 Structures Containing a Conformant and Varying Array
1100 # A structure can contain a conformant and varying array only as its last member.
1101 # In the NDR representation of a structure that contains a conformant and varying array,
1102 # the maximum counts for dimensions of the array are moved to the beginning of the structure,
1103 # but the offsets and actual counts remain in place at the end of the structure,
1104 # immediately preceding the array elements
1105 lastItem = (self.commonHdr+self.structure)[-1][0]
1107 # If it's a pointer, let's parse it here because
1108 # we are going to parse the next MaximumCount field(s) manually
1109 # when it's a Conformant or Conformant and Varying array
1110 if isinstance(self, NDRPOINTER):
1111 structureFields = self.structure
1113 alignment = self.getAlignment()
1114 if alignment > 0: 1114 ↛ 1117line 1114 didn't jump to line 1117, because the condition on line 1114 was never false
1115 offset += (alignment - (offset % alignment)) % alignment
1117 for fieldName, fieldTypeOrClass in self.commonHdr:
1118 offset += self.unpack(fieldName, fieldTypeOrClass, data, offset)
1119 else:
1120 structureFields = self.commonHdr+self.structure
1122 if isinstance(self.fields[lastItem], NDRUniConformantArray) or isinstance(self.fields[lastItem], NDRUniConformantVaryingArray):
1123 # So we have an array, first item in the structure must be the array size, although we
1124 # will need to build it later.
1125 if self._isNDR64:
1126 arrayItemSize = 8
1127 arrayUnPackStr = '<Q'
1128 else:
1129 arrayItemSize = 4
1130 arrayUnPackStr = '<L'
1132 # The size information is itself aligned according to the alignment rules for
1133 # primitive data types. (See Section 14.2.2 on page 620.) The data of the constructed
1134 # type is then aligned according to the alignment rules for the constructed type.
1135 # In other words, the size information precedes the structure and is aligned
1136 # independently of the structure alignment.
1137 # We need to check whether we need padding or not
1138 offset += (arrayItemSize - (offset % arrayItemSize)) % arrayItemSize
1140 # And let's extract the array size for later use
1141 if isinstance(self.fields[lastItem], NDRUniConformantArray):
1142 # NDRUniConformantArray
1143 arraySize = unpack_from(arrayUnPackStr, data, offset)[0]
1144 self.fields[lastItem].setArraySize(arraySize)
1145 else:
1146 # NDRUniConformantVaryingArray
1147 maximumCount = unpack_from(arrayUnPackStr, data, offset)[0]
1148 self.fields[lastItem].fields['MaximumCount'] = maximumCount
1150 offset += arrayItemSize
1152 # Now we need to align the structure
1153 # The alignment of a structure in the octet stream is the largest of the alignments of the fields it
1154 # contains. These fields may also be constructed types. The same alignment rules apply
1155 # recursively to nested constructed types.
1156 alignment = self.getAlignment()
1157 if alignment > 0:
1158 offset += (alignment - (offset % alignment)) % alignment
1160 for fieldName, fieldTypeOrClass in structureFields:
1161 try:
1162 offset += self.unpack(fieldName, fieldTypeOrClass, data, offset)
1163 except Exception as e:
1164 LOG.error(str(e))
1165 LOG.error("Error unpacking field '%s | %s | %r'" % (fieldName, fieldTypeOrClass, data[offset:offset+256]))
1166 raise
1168 return offset - offset0
1170 def getAlignment(self):
1171 # Alignment of Constructed Types
1172 #
1173 # NDR enforces NDR alignment of structured data. As with primitive data types, an alignment, n, is determined
1174 # for the structure. Where necessary, an alignment gap of octets of unspecified value precedes the data in
1175 # the NDR octet stream. This gap is the smallest size sufficient to align the first field of the structure
1176 # on an NDR octet stream index of n.
1178 # The rules for calculating the alignment of constructed types are as follows:
1180 # 1) If a conformant structure-that is, a conformant or conformant varying array, or a structure containing
1181 # a conformant or conformant varying array-is embedded in the constructed type, and is the outermost
1182 # structure-that is, is not contained in another structure-then the size information from the contained
1183 # conformant structure is positioned so that it precedes both the containing constructed type and any
1184 # alignment gap for the constructed type. (See Section 14.3.7 for information about structures containing
1185 # arrays.) The size information is itself aligned according to the alignment rules for primitive data
1186 # types. (See Section 14.2.2 on page 620.) The data of the constructed type is then aligned according to
1187 # the alignment rules for the constructed type. In other words, the size information precedes the structure
1188 # and is aligned independently of the structure alignment.
1190 # 2) The alignment of a structure in the octet stream is the largest of the alignments of the fields it
1191 # contains. These fields may also be constructed types. The same alignment rules apply recursively to nested
1192 # constructed types.
1194 align = 0
1195 for fieldName, fieldTypeOrClass in self.commonHdr+self.structure+self.referent:
1196 if isinstance(self.fields[fieldName], NDR):
1197 tmpAlign = self.fields[fieldName].getAlignment()
1198 else:
1199 tmpAlign = self.calcPackSize(fieldTypeOrClass, b'')
1200 if tmpAlign > align:
1201 align = tmpAlign
1202 return align
1204# Unions
1205class NDRUNION(NDRCONSTRUCTEDTYPE):
1206 commonHdr = (
1207 ('tag', NDRUSHORT),
1208 )
1209 commonHdr64 = (
1210 ('tag', NDRULONG),
1211 )
1213 union = {
1214 # For example
1215 #1: ('pStatusChangeParam1', PSERVICE_NOTIFY_STATUS_CHANGE_PARAMS_1),
1216 #2: ('pStatusChangeParams', PSERVICE_NOTIFY_STATUS_CHANGE_PARAMS_2),
1217 }
1218 def __init__(self, data = None, isNDR64=False, topLevel = False):
1219 #ret = NDR.__init__(self,None, isNDR64=isNDR64)
1220 self.topLevel = topLevel
1221 self._isNDR64 = isNDR64
1222 self.fields = {}
1224 if isNDR64 is True:
1225 if self.commonHdr64 != (): 1225 ↛ 1227line 1225 didn't jump to line 1227, because the condition on line 1225 was never false
1226 self.commonHdr = self.commonHdr64
1227 if self.structure64 != (): 1227 ↛ 1228line 1227 didn't jump to line 1228, because the condition on line 1227 was never true
1228 self.structure = self.structure64
1229 if hasattr(self, 'align64'): 1229 ↛ 1230line 1229 didn't jump to line 1230, because the condition on line 1229 was never true
1230 self.align = self.align64
1232 for fieldName, fieldTypeOrClass in self.commonHdr+self.structure+self.referent:
1233 if self.isNDR(fieldTypeOrClass): 1233 ↛ 1240line 1233 didn't jump to line 1240, because the condition on line 1233 was never false
1234 if self.isPointer(fieldTypeOrClass):
1235 self.fields[fieldName] = fieldTypeOrClass(isNDR64 = self._isNDR64, topLevel = topLevel)
1236 elif self.isUnion(fieldTypeOrClass): 1236 ↛ 1237line 1236 didn't jump to line 1237, because the condition on line 1236 was never true
1237 self.fields[fieldName] = fieldTypeOrClass(isNDR64 = self._isNDR64, topLevel = topLevel)
1238 else:
1239 self.fields[fieldName] = fieldTypeOrClass(isNDR64 = self._isNDR64)
1240 elif fieldTypeOrClass == ':':
1241 self.fields[fieldName] = None
1242 elif len(fieldTypeOrClass.split('=')) == 2:
1243 try:
1244 self.fields[fieldName] = eval(fieldTypeOrClass.split('=')[1])
1245 except:
1246 self.fields[fieldName] = None
1247 else:
1248 self.fields[fieldName] = 0
1250 if data is not None: 1250 ↛ 1251line 1250 didn't jump to line 1251, because the condition on line 1250 was never true
1251 self.fromString(data)
1253 def __setitem__(self, key, value):
1254 if key == 'tag':
1255 # We're writing the tag, we now should set the right item for the structure
1256 self.structure = ()
1257 if value in self.union: 1257 ↛ 1264line 1257 didn't jump to line 1264, because the condition on line 1257 was never false
1258 self.structure = (self.union[value]),
1259 # Init again the structure
1260 self.__init__(None, isNDR64=self._isNDR64, topLevel = self.topLevel)
1261 self.fields['tag']['Data'] = value
1262 else:
1263 # Let's see if we have a default value
1264 if 'default' in self.union:
1265 if self.union['default'] is None:
1266 self.structure = ()
1267 else:
1268 self.structure = (self.union['default']),
1269 # Init again the structure
1270 self.__init__(None, isNDR64=self._isNDR64, topLevel = self.topLevel)
1271 self.fields['tag']['Data'] = 0xffff
1272 else:
1273 raise Exception("Unknown tag %d for union!" % value)
1274 else:
1275 return NDRCONSTRUCTEDTYPE.__setitem__(self,key,value)
1277 def getData(self, soFar = 0):
1278 data = b''
1279 soFar0 = soFar
1281 # Let's align ourselves
1282 alignment = self.getAlignment()
1283 if alignment > 0: 1283 ↛ 1286line 1283 didn't jump to line 1286, because the condition on line 1283 was never false
1284 pad = (alignment - (soFar % alignment)) % alignment
1285 else:
1286 pad = 0
1287 if pad > 0:
1288 soFar += pad
1289 data += b'\xbc'*pad
1291 for fieldName, fieldTypeOrClass in self.commonHdr:
1292 try:
1293 pad = self.calculatePad(fieldTypeOrClass, soFar)
1294 if pad > 0: 1294 ↛ 1295line 1294 didn't jump to line 1295, because the condition on line 1294 was never true
1295 soFar += pad
1296 data += b'\xbb'*pad
1298 res = self.pack(fieldName, fieldTypeOrClass, soFar)
1299 data += res
1300 soFar = soFar0 + len(data)
1301 except Exception as e:
1302 LOG.error(str(e))
1303 LOG.error("Error packing field '%s | %s' in %s" % (fieldName, fieldTypeOrClass, self.__class__))
1304 raise
1306 # WARNING
1307 # Now we need to align what's coming next.
1308 # This doesn't come from the documentation but from seeing the packets in the wire
1309 # for some reason, even if the next field is a SHORT, it should be aligned to
1310 # a DWORD, or HYPER if NDR64.
1311 if self._isNDR64:
1312 align = 8
1313 else:
1314 if hasattr(self, 'notAlign'): 1314 ↛ 1315line 1314 didn't jump to line 1315, because the condition on line 1314 was never true
1315 align = 1
1316 else:
1317 align = 4
1319 pad = (align - (soFar % align)) % align
1320 if pad > 0:
1321 data += b'\xbd'*pad
1322 soFar += pad
1324 if self.structure == (): 1324 ↛ 1325line 1324 didn't jump to line 1325, because the condition on line 1324 was never true
1325 return data
1327 for fieldName, fieldTypeOrClass in self.structure:
1328 try:
1329 pad = self.calculatePad(fieldTypeOrClass, soFar)
1330 if pad > 0: 1330 ↛ 1331line 1330 didn't jump to line 1331, because the condition on line 1330 was never true
1331 soFar += pad
1332 data += b'\xbe'*pad
1334 res = self.pack(fieldName, fieldTypeOrClass, soFar)
1335 data += res
1336 soFar = soFar0 + len(data)
1337 except Exception as e:
1338 LOG.error(str(e))
1339 LOG.error("Error packing field '%s | %s' in %s" % (fieldName, fieldTypeOrClass, self.__class__))
1340 raise
1342 return data
1344 def fromString(self, data, offset=0):
1345 offset0 = offset
1346 # Let's align ourselves
1347 alignment = self.getAlignment()
1348 if alignment > 0: 1348 ↛ 1351line 1348 didn't jump to line 1351, because the condition on line 1348 was never false
1349 pad = (alignment - (offset % alignment)) % alignment
1350 else:
1351 pad = 0
1352 if pad > 0:
1353 offset += pad
1355 if len(data)-offset > 4: 1355 ↛ 1376line 1355 didn't jump to line 1376, because the condition on line 1355 was never false
1356 # First off, let's see what the tag is:
1357 # We need to know the tag type and unpack it
1358 tagtype = self.commonHdr[0][1].structure[0][1].split('=')[0]
1359 tag = unpack_from(tagtype, data, offset)[0]
1360 if tag in self.union:
1361 self.structure = (self.union[tag]),
1362 self.__init__(None, isNDR64=self._isNDR64, topLevel = self.topLevel)
1363 else:
1364 # Let's see if we have a default value
1365 if 'default' in self.union: 1365 ↛ 1366line 1365 didn't jump to line 1366, because the condition on line 1365 was never true
1366 if self.union['default'] is None:
1367 self.structure = ()
1368 else:
1369 self.structure = (self.union['default']),
1370 # Init again the structure
1371 self.__init__(None, isNDR64=self._isNDR64, topLevel = self.topLevel)
1372 self.fields['tag']['Data'] = 0xffff
1373 else:
1374 raise Exception("Unknown tag %d for union!" % tag)
1376 for fieldName, fieldTypeOrClass in self.commonHdr:
1377 try:
1378 offset += self.calculatePad(fieldTypeOrClass, offset)
1379 offset += self.unpack(fieldName, fieldTypeOrClass, data, offset)
1380 except Exception as e:
1381 LOG.error(str(e))
1382 LOG.error("Error unpacking field '%s | %s | %r'" % (fieldName, fieldTypeOrClass, data[offset:offset+256]))
1383 raise
1385 # WARNING
1386 # Now we need to align what's coming next.
1387 # This doesn't come from the documentation but from seeing the packets in the wire
1388 # for some reason, even if the next field is a SHORT, it should be aligned to
1389 # a DWORD, or HYPER if NDR64.
1390 if self._isNDR64:
1391 align = 8
1392 else:
1393 if hasattr(self, 'notAlign'): 1393 ↛ 1394line 1393 didn't jump to line 1394, because the condition on line 1393 was never true
1394 align = 1
1395 else:
1396 align = 4
1398 offset += (align - (offset % align)) % align
1400 if self.structure == (): 1400 ↛ 1401line 1400 didn't jump to line 1401, because the condition on line 1400 was never true
1401 return offset-offset0
1403 for fieldName, fieldTypeOrClass in self.structure:
1404 try:
1405 offset += self.calculatePad(fieldTypeOrClass, offset)
1406 offset += self.unpack(fieldName, fieldTypeOrClass, data, offset)
1407 except Exception as e:
1408 LOG.error(str(e))
1409 LOG.error("Error unpacking field '%s | %s | %r'" % (fieldName, fieldTypeOrClass, data[offset:offset+256]))
1410 raise
1412 return offset - offset0
1414 def getAlignment(self):
1415 # Union alignment is the largest alignment of the union discriminator
1416 # and all of the union arms.
1417 # WRONG, I'm calculating it just with the tag, if I do it with the
1418 # arms I get bad stub data. Something wrong I'm doing or the standard
1419 # is wrong (most probably it's me :s )
1420 align = 0
1421 if self._isNDR64:
1422 fields = self.commonHdr+self.structure
1423 else:
1424 fields = self.commonHdr
1425 for fieldName, fieldTypeOrClass in fields:
1426 if isinstance(self.fields[fieldName], NDR): 1426 ↛ 1429line 1426 didn't jump to line 1429, because the condition on line 1426 was never false
1427 tmpAlign = self.fields[fieldName].getAlignment()
1428 else:
1429 tmpAlign = self.calcPackSize(fieldTypeOrClass, b'')
1430 if tmpAlign > align:
1431 align = tmpAlign
1433 if self._isNDR64:
1434 for fieldName, fieldTypeOrClass in self.union.values():
1435 tmpAlign = fieldTypeOrClass(isNDR64 = self._isNDR64).getAlignment()
1436 if tmpAlign > align:
1437 align = tmpAlign
1438 return align
1440# Pipes not implemented for now
1442# Pointers
1443class NDRPOINTERNULL(NDR):
1444 align = 4
1445 align64 = 8
1446 structure = (
1447 ('Data', '<L=0'),
1448 )
1449 structure64 = (
1450 ('Data', '<Q=0'),
1451 )
1453 def dump(self, msg = None, indent = 0):
1454 if msg is None: 1454 ↛ 1455line 1454 didn't jump to line 1455, because the condition on line 1454 was never true
1455 msg = self.__class__.__name__
1456 if msg != '': 1456 ↛ 1459line 1456 didn't jump to line 1459, because the condition on line 1456 was never false
1457 print("%s" % msg, end=' ')
1458 # Here we just print NULL
1459 print(" NULL", end=' ')
1461NULL = NDRPOINTERNULL()
1463class NDRPOINTER(NDRSTRUCT):
1464 align = 4
1465 align64 = 8
1466 commonHdr = (
1467 ('ReferentID','<L=0xff'),
1468 )
1469 commonHdr64 = (
1470 ('ReferentID','<Q=0xff'),
1471 )
1473 referent = (
1474 # This is the representation of the Referent
1475 ('Data',':'),
1476 )
1477 def __init__(self, data = None, isNDR64=False, topLevel = False):
1478 NDRSTRUCT.__init__(self,None, isNDR64=isNDR64)
1479 # If we are being called from a NDRCALL, it's a TopLevelPointer,
1480 # if not, it's a embeeded pointer.
1481 # It is *very* important, for every subclass of NDRPointer
1482 # you have to declare the referent in the referent variable
1483 # Not in the structure one!
1484 if topLevel is True:
1485 self.structure = self.referent
1486 self.referent = ()
1488 if data is None: 1488 ↛ 1491line 1488 didn't jump to line 1491, because the condition on line 1488 was never false
1489 self.fields['ReferentID'] = random.randint(1,65535)
1490 else:
1491 self.fromString(data)
1493 def __setitem__(self, key, value):
1494 if (key in self.fields) is False:
1495 # Key not found.. let's send it to the referent to handle, maybe it's there
1496 return self.fields['Data'].__setitem__(key,value)
1497 else:
1498 return NDRSTRUCT.__setitem__(self,key,value)
1500 def __getitem__(self, key):
1501 if key in self.fields:
1502 if isinstance(self.fields[key], NDR):
1503 if 'Data' in self.fields[key].fields:
1504 return self.fields[key]['Data']
1505 return self.fields[key]
1506 else:
1507 # Key not found, let's send it to the referent, maybe it's there
1508 return self.fields['Data'].__getitem__(key)
1510 def getData(self, soFar = 0):
1511 # First of all we need to align ourselves
1512 data = b''
1513 pad = self.calculatePad(self.commonHdr[0][1], soFar)
1514 if pad > 0:
1515 soFar += pad
1516 data = b'\xaa'*pad
1517 # If we have a ReferentID == 0, means there's no data
1518 if self.fields['ReferentID'] == 0:
1519 if len(self.referent) > 0:
1520 self['Data'] = b''
1521 else:
1522 if self._isNDR64 is True: 1522 ↛ 1523line 1522 didn't jump to line 1523, because the condition on line 1522 was never true
1523 return data+b'\x00'*8
1524 else:
1525 return data+b'\x00'*4
1527 return data + NDRSTRUCT.getData(self, soFar)
1529 def fromString(self, data, offset=0):
1530 # First of all we need to align ourselves
1531 pad = self.calculatePad(self.commonHdr[0][1], offset)
1532 offset += pad
1534 # Do we have a Referent ID == 0?
1535 if self._isNDR64 is True:
1536 unpackStr = '<Q'
1537 else:
1538 unpackStr = '<L'
1540 if unpack_from(unpackStr, data, offset)[0] == 0:
1541 # Let's save the value
1542 self['ReferentID'] = 0
1543 self.fields['Data'] = b''
1544 if self._isNDR64 is True:
1545 return pad + 8
1546 else:
1547 return pad + 4
1548 else:
1549 retVal = NDRSTRUCT.fromString(self, data, offset)
1550 return retVal + pad
1552 def dump(self, msg = None, indent = 0):
1553 if msg is None: 1553 ↛ 1554line 1553 didn't jump to line 1554, because the condition on line 1553 was never true
1554 msg = self.__class__.__name__
1555 if msg != '':
1556 print("%s" % msg, end=' ')
1557 # Here we just print the referent
1558 if isinstance(self.fields['Data'], NDR):
1559 self.fields['Data'].dump('', indent = indent)
1560 else:
1561 if self['ReferentID'] == 0: 1561 ↛ 1564line 1561 didn't jump to line 1564, because the condition on line 1561 was never false
1562 print(" NULL", end=' ')
1563 else:
1564 print(" %r" % (self['Data']), end=' ')
1566 def getAlignment(self):
1567 if self._isNDR64 is True:
1568 return 8
1569 else:
1570 return 4
1573# Embedded Reference Pointers not implemented for now
1575################################################################################
1576# Common RPC Data Types
1578class PNDRUniConformantVaryingArray(NDRPOINTER):
1579 referent = (
1580 ('Data', NDRUniConformantVaryingArray),
1581 )
1583class PNDRUniConformantArray(NDRPOINTER):
1584 referent = (
1585 ('Data', NDRUniConformantArray),
1586 )
1587 def __init__(self, data = None, isNDR64 = False, topLevel = False):
1588 NDRPOINTER.__init__(self,data,isNDR64,topLevel)
1590class NDRCALL(NDRCONSTRUCTEDTYPE):
1591 # This represents a group of NDR instances that conforms an NDR Call.
1592 # The only different between a regular NDR instance is a NDR call must
1593 # represent the referents when building the final octet stream
1594 referent = ()
1595 commonHdr = ()
1596 commonHdr64 = ()
1597 structure = ()
1598 structure64 = ()
1599 align = 4
1600 def __init__(self, data = None, isNDR64 = False):
1601 self._isNDR64 = isNDR64
1602 self.fields = {}
1604 if isNDR64 is True:
1605 if self.commonHdr64 != (): 1605 ↛ 1606line 1605 didn't jump to line 1606, because the condition on line 1605 was never true
1606 self.commonHdr = self.commonHdr64
1607 if self.structure64 != (): 1607 ↛ 1608line 1607 didn't jump to line 1608, because the condition on line 1607 was never true
1608 self.structure = self.structure64
1609 if hasattr(self, 'align64'): 1609 ↛ 1610line 1609 didn't jump to line 1610, because the condition on line 1609 was never true
1610 self.align = self.align64
1612 for fieldName, fieldTypeOrClass in self.commonHdr+self.structure+self.referent:
1613 if self.isNDR(fieldTypeOrClass): 1613 ↛ 1620line 1613 didn't jump to line 1620, because the condition on line 1613 was never false
1614 if self.isPointer(fieldTypeOrClass):
1615 self.fields[fieldName] = fieldTypeOrClass(isNDR64 = self._isNDR64, topLevel = True)
1616 elif self.isUnion(fieldTypeOrClass):
1617 self.fields[fieldName] = fieldTypeOrClass(isNDR64 = self._isNDR64, topLevel = True)
1618 else:
1619 self.fields[fieldName] = fieldTypeOrClass(isNDR64 = self._isNDR64)
1620 elif fieldTypeOrClass == ':':
1621 self.fields[fieldName] = None
1622 elif len(fieldTypeOrClass.split('=')) == 2:
1623 try:
1624 self.fields[fieldName] = eval(fieldTypeOrClass.split('=')[1])
1625 except:
1626 self.fields[fieldName] = None
1627 else:
1628 self.fields[fieldName] = 0
1630 if data is not None:
1631 self.fromString(data)
1633 def dump(self, msg = None, indent = 0):
1634 NDRCONSTRUCTEDTYPE.dump(self, msg, indent)
1635 print('\n\n')
1637 def getData(self, soFar = 0):
1638 data = b''
1639 soFar0 = soFar
1640 for fieldName, fieldTypeOrClass in self.commonHdr+self.structure:
1641 try:
1642 pad = self.calculatePad(fieldTypeOrClass, soFar)
1643 if pad > 0: 1643 ↛ 1644line 1643 didn't jump to line 1644, because the condition on line 1643 was never true
1644 soFar += pad
1645 data += b'\xab'*pad
1647 # Are we dealing with an array?
1648 if isinstance(self.fields[fieldName], NDRUniConformantArray) or isinstance(self.fields[fieldName],
1649 NDRUniConformantVaryingArray):
1650 # Align size item
1651 if self._isNDR64:
1652 pad = (8 - (soFar % 8)) % 8
1653 else:
1654 pad = (4 - (soFar % 4)) % 4
1655 # Pack the item
1656 res = self.pack(fieldName, fieldTypeOrClass, soFar+pad)
1657 # Yes, get the array size
1658 arraySize = self.getArrayMaximumSize(fieldName)
1659 if self._isNDR64:
1660 pad = (8 - (soFar % 8)) % 8
1661 data += b'\xce'*pad + pack('<Q', arraySize) + res
1662 else:
1663 pad = (4 - (soFar % 4)) % 4
1664 data += b'\xce'*pad + pack('<L', arraySize) + res
1665 else:
1666 data += self.pack(fieldName, fieldTypeOrClass, soFar)
1668 soFar = soFar0 + len(data)
1669 # Any referent information to pack?
1670 # I'm still not sure whether this should go after processing
1671 # all the fields at the call level.
1672 # Guess we'll figure it out testing.
1673 if isinstance(self.fields[fieldName], NDRCONSTRUCTEDTYPE):
1674 data += self.fields[fieldName].getDataReferents(soFar)
1675 soFar = soFar0 + len(data)
1676 data += self.fields[fieldName].getDataReferent(soFar)
1677 soFar = soFar0 + len(data)
1678 except Exception as e:
1679 LOG.error(str(e))
1680 LOG.error("Error packing field '%s | %s' in %s" % (fieldName, fieldTypeOrClass, self.__class__))
1681 raise
1683 return data
1685 def fromString(self, data, offset=0):
1686 offset0 = offset
1687 for fieldName, fieldTypeOrClass in self.commonHdr+self.structure:
1688 try:
1689 # Are we dealing with an array?
1690 if isinstance(self.fields[fieldName], NDRUniConformantArray) or isinstance(self.fields[fieldName],
1691 NDRUniConformantVaryingArray):
1692 # Yes, get the array size
1693 arraySize, advanceStream = self.getArraySize(fieldName, data, offset)
1694 self.fields[fieldName].setArraySize(arraySize)
1695 offset += advanceStream
1697 size = self.unpack(fieldName, fieldTypeOrClass, data, offset)
1699 # Any referent information to unpack?
1700 if isinstance(self.fields[fieldName], NDRCONSTRUCTEDTYPE):
1701 size += self.fields[fieldName].fromStringReferents(data, offset+size)
1702 size += self.fields[fieldName].fromStringReferent(data, offset+size)
1703 offset += size
1704 except Exception as e:
1705 LOG.error(str(e))
1706 LOG.error("Error unpacking field '%s | %s | %r'" % (fieldName, fieldTypeOrClass, data[offset:offset+256]))
1707 raise
1709 return offset - offset0
1711# Top Level Struct == NDRCALL
1712NDRTLSTRUCT = NDRCALL
1714class UNKNOWNDATA(NDR):
1715 align = 1
1716 structure = (
1717 ('Data', ':'),
1718 )