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

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