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

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

## 415 statements

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

10 from __future__ import division

11 from __future__ import print_function

12 from struct import pack, unpack, calcsize

13 from six import b, PY3

14

15 class Structure:

16 """ sublcasses can define commonHdr and/or structure.

17 each of them is an tuple of either two: (fieldName, format) or three: (fieldName, ':', class) fields.

18 [it can't be a dictionary, because order is important]

19

20 where format specifies how the data in the field will be converted to/from bytes (string)

21 class is the class to use when unpacking ':' fields.

22

23 each field can only contain one value (or an array of values for *)

24 i.e. struct.pack('Hl',1,2) is valid, but format specifier 'Hl' is not (you must use 2 dfferent fields)

25

26 format specifiers:

27 specifiers from module pack can be used with the same format

28 see struct.__doc__ (pack/unpack is finally called)

29 x [padding byte]

30 c [character]

31 b [signed byte]

32 B [unsigned byte]

33 h [signed short]

34 H [unsigned short]

35 l [signed long]

36 L [unsigned long]

37 i [signed integer]

38 I [unsigned integer]

39 q [signed long long (quad)]

40 Q [unsigned long long (quad)]

41 s [string (array of chars), must be preceded with length in format specifier, padded with zeros]

42 p [pascal string (includes byte count), must be preceded with length in format specifier, padded with zeros]

43 f [float]

44 d [double]

45 = [native byte ordering, size and alignment]

46 @ [native byte ordering, standard size and alignment]

47 ! [network byte ordering]

48 < [little endian]

49 > [big endian]

50

51 usual printf like specifiers can be used (if started with %)

52 [not recommended, there is no way to unpack this]

53

54 %08x will output an 8 bytes hex

55 %s will output a string

56 %s\\x00 will output a NUL terminated string

57 %d%d will output 2 decimal digits (against the very same specification of Structure)

58 ...

59

60 some additional format specifiers:

61 : just copy the bytes from the field into the output string (input may be string, other structure, or anything responding to __str__()) (for unpacking, all what's left is returned)

62 z same as :, but adds a NUL byte at the end (asciiz) (for unpacking the first NUL byte is used as terminator) [asciiz string]

63 u same as z, but adds two NUL bytes at the end (after padding to an even size with NULs). (same for unpacking) [unicode string]

64 w DCE-RPC/NDR string (it's a macro for [ '<L=(len(field)+1)/2','"\\x00\\x00\\x00\\x00','<L=(len(field)+1)/2',':' ]

65 ?-field length of field named 'field', formatted as specified with ? ('?' may be '!H' for example). The input value overrides the real length

66 ?1*?2 array of elements. Each formatted as '?2', the number of elements in the array is stored as specified by '?1' (?1 is optional, or can also be a constant (number), for unpacking)

67 'xxxx literal xxxx (field's value doesn't change the output. quotes must not be closed or escaped)

68 "xxxx literal xxxx (field's value doesn't change the output. quotes must not be closed or escaped)

69 _ will not pack the field. Accepts a third argument, which is an unpack code. See _Test_UnpackCode for an example

70 ?=packcode will evaluate packcode in the context of the structure, and pack the result as specified by ?. Unpacking is made plain

71 ?&fieldname "Address of field fieldname".

72 For packing it will simply pack the id() of fieldname. Or use 0 if fieldname doesn't exists.

73 For unpacking, it's used to know weather fieldname has to be unpacked or not, i.e. by adding a & field you turn another field (fieldname) in an optional field.

74

75 """

76 commonHdr = ()

77 structure = ()

78 debug = 0

79

80 def __init__(self, data = None, alignment = 0):

81 if not hasattr(self, 'alignment'): 81 ↛ 84line 81 didn't jump to line 84, because the condition on line 81 was never false

82 self.alignment = alignment

83

84 self.fields = {}

85 self.rawData = data

86 if data is not None:

87 self.fromString(data)

88 else:

89 self.data = None

90

91 @classmethod

92 def fromFile(self, file):

93 answer = self()

94 answer.fromString(file.read(len(answer)))

95 return answer

96

97 def setAlignment(self, alignment):

98 self.alignment = alignment

99

100 def setData(self, data):

101 self.data = data

102

103 def packField(self, fieldName, format = None):

104 if self.debug: 104 ↛ 105line 104 didn't jump to line 105, because the condition on line 104 was never true

105 print("packField( %s | %s )" % (fieldName, format))

106

107 if format is None: 107 ↛ 108line 107 didn't jump to line 108, because the condition on line 107 was never true

108 format = self.formatForField(fieldName)

109

110 if fieldName in self.fields:

111 ans = self.pack(format, self.fields[fieldName], field = fieldName)

112 else:

113 ans = self.pack(format, None, field = fieldName)

114

115 if self.debug: 115 ↛ 116line 115 didn't jump to line 116, because the condition on line 115 was never true

116 print("\tanswer %r" % ans)

117

118 return ans

119

120 def getData(self):

121 if self.data is not None: 121 ↛ 122line 121 didn't jump to line 122, because the condition on line 121 was never true

122 return self.data

123 data = bytes()

124 for field in self.commonHdr+self.structure:

125 try:

126 data += self.packField(field[0], field[1])

127 except Exception as e:

128 if field[0] in self.fields:

129 e.args += ("When packing field '%s | %s | %r' in %s" % (field[0], field[1], self[field[0]], self.__class__),)

130 else:

131 e.args += ("When packing field '%s | %s' in %s" % (field[0], field[1], self.__class__),)

132 raise

133 if self.alignment:

134 if len(data) % self.alignment:

135 data += (b'\x00'*self.alignment)[:-(len(data) % self.alignment)]

136

137 #if len(data) % self.alignment: data += ('\x00'*self.alignment)[:-(len(data) % self.alignment)]

138 return data

139

140 def fromString(self, data):

141 self.rawData = data

142 for field in self.commonHdr+self.structure:

143 if self.debug: 143 ↛ 144line 143 didn't jump to line 144, because the condition on line 143 was never true

144 print("fromString( %s | %s | %r )" % (field[0], field[1], data))

145 size = self.calcUnpackSize(field[1], data, field[0])

146 if self.debug: 146 ↛ 147line 146 didn't jump to line 147, because the condition on line 146 was never true

147 print(" size = %d" % size)

148 dataClassOrCode = b

149 if len(field) > 2:

150 dataClassOrCode = field[2]

151 try:

152 self[field[0]] = self.unpack(field[1], data[:size], dataClassOrCode = dataClassOrCode, field = field[0])

153 except Exception as e:

154 e.args += ("When unpacking field '%s | %s | %r[:%d]'" % (field[0], field[1], data, size),)

155 raise

156

157 size = self.calcPackSize(field[1], self[field[0]], field[0])

158 if self.alignment and size % self.alignment:

159 size += self.alignment - (size % self.alignment)

160 data = data[size:]

161

162 return self

163

164 def __setitem__(self, key, value):

165 self.fields[key] = value

166 self.data = None # force recompute

167

168 def __getitem__(self, key):

169 return self.fields[key]

170

171 def __delitem__(self, key):

172 del self.fields[key]

173

174 def __str__(self):

175 return self.getData()

176

177 def __len__(self):

178 # XXX: improve

179 return len(self.getData())

180

181 def pack(self, format, data, field = None):

182 if self.debug: 182 ↛ 183line 182 didn't jump to line 183, because the condition on line 182 was never true

183 print(" pack( %s | %r | %s)" % (format, data, field))

184

185 if field:

186 addressField = self.findAddressFieldFor(field)

187 if (addressField is not None) and (data is None):

188 return b''

189

190 # void specifier

191 if format[:1] == '_':

192 return b''

193

194 # quote specifier

195 if format[:1] == "'" or format[:1] == '"':

196 return b(format[1:])

197

198 # code specifier

199 two = format.split('=')

200 if len(two) >= 2:

201 try:

202 return self.pack(two[0], data)

203 except:

204 fields = {'self':self}

205 fields.update(self.fields)

206 return self.pack(two[0], eval(two[1], {}, fields))

207

208 # address specifier

209 two = format.split('&')

210 if len(two) == 2:

211 try:

212 return self.pack(two[0], data)

213 except:

214 if (two[1] in self.fields) and (self[two[1]] is not None):

215 return self.pack(two[0], id(self[two[1]]) & ((1<<(calcsize(two[0])*8))-1) )

216 else:

217 return self.pack(two[0], 0)

218

219 # length specifier

220 two = format.split('-')

221 if len(two) == 2:

222 try:

223 return self.pack(two[0],data)

224 except:

225 return self.pack(two[0], self.calcPackFieldSize(two[1]))

226

227 # array specifier

228 two = format.split('*')

229 if len(two) == 2:

230 answer = bytes()

231 for each in data:

232 answer += self.pack(two[1], each)

233 if two[0]:

234 if two[0].isdigit(): 234 ↛ 235line 234 didn't jump to line 235, because the condition on line 234 was never true

235 if int(two[0]) != len(data):

236 raise Exception("Array field has a constant size, and it doesn't match the actual value")

237 else:

238 return self.pack(two[0], len(data))+answer

239 return answer

240

241 # "printf" string specifier

242 if format[:1] == '%': 242 ↛ 244line 242 didn't jump to line 244, because the condition on line 242 was never true

243 # format string like specifier

244 return b(format % data)

245

246 # asciiz specifier

247 if format[:1] == 'z':

248 if isinstance(data,bytes): 248 ↛ 249line 248 didn't jump to line 249, because the condition on line 248 was never true

249 return data + b('\0')

250 return bytes(b(data)+b('\0'))

251

252 # unicode specifier

253 if format[:1] == 'u':

254 return bytes(data+b('\0\0') + (len(data) & 1 and b('\0') or b''))

255

256 # DCE-RPC/NDR string specifier

257 if format[:1] == 'w':

258 if len(data) == 0: 258 ↛ 259line 258 didn't jump to line 259, because the condition on line 258 was never true

259 data = b('\0\0')

260 elif len(data) % 2:

261 data = b(data) + b('\0')

262 l = pack('<L', len(data)//2)

263 return b''.join([l, l, b('\0\0\0\0'), data])

264

265 if data is None:

266 raise Exception("Trying to pack None")

267

268 # literal specifier

269 if format[:1] == ':':

270 if isinstance(data, Structure):

271 return data.getData()

272 # If we have an object that can serialize itself, go ahead

273 elif hasattr(data, "getData"): 273 ↛ 274line 273 didn't jump to line 274, because the condition on line 273 was never true

274 return data.getData()

275 elif isinstance(data, int): 275 ↛ 276line 275 didn't jump to line 276, because the condition on line 275 was never true

276 return bytes(data)

277 elif isinstance(data, bytes) is not True:

278 return bytes(b(data))

279 else:

280 return data

281

282 if format[-1:] == 's':

283 # Let's be sure we send the right type

284 if isinstance(data, bytes) or isinstance(data, bytearray):

285 return pack(format, data)

286 else:

287 return pack(format, b(data))

288

289 # struct like specifier

290 return pack(format, data)

291

292 def unpack(self, format, data, dataClassOrCode = b, field = None):

293 if self.debug: 293 ↛ 294line 293 didn't jump to line 294, because the condition on line 293 was never true

294 print(" unpack( %s | %r )" % (format, data))

295

296 if field:

297 addressField = self.findAddressFieldFor(field)

298 if addressField is not None:

299 if not self[addressField]:

300 return

301

302 # void specifier

303 if format[:1] == '_':

304 if dataClassOrCode != b:

305 fields = {'self':self, 'inputDataLeft':data}

306 fields.update(self.fields)

307 return eval(dataClassOrCode, {}, fields)

308 else:

309 return None

310

311 # quote specifier

312 if format[:1] == "'" or format[:1] == '"':

313 answer = format[1:]

314 if b(answer) != data:

315 raise Exception("Unpacked data doesn't match constant value '%r' should be '%r'" % (data, answer))

316 return answer

317

318 # address specifier

319 two = format.split('&')

320 if len(two) == 2:

321 return self.unpack(two[0],data)

322

323 # code specifier

324 two = format.split('=')

325 if len(two) >= 2:

326 return self.unpack(two[0],data)

327

328 # length specifier

329 two = format.split('-')

330 if len(two) == 2:

331 return self.unpack(two[0],data)

332

333 # array specifier

334 two = format.split('*')

335 if len(two) == 2:

336 answer = []

337 sofar = 0

338 if two[0].isdigit(): 338 ↛ 339line 338 didn't jump to line 339, because the condition on line 338 was never true

339 number = int(two[0])

340 elif two[0]:

341 sofar += self.calcUnpackSize(two[0], data)

342 number = self.unpack(two[0], data[:sofar])

343 else:

344 number = -1

345

346 while number and sofar < len(data):

347 nsofar = sofar + self.calcUnpackSize(two[1],data[sofar:])

348 answer.append(self.unpack(two[1], data[sofar: nsofar], dataClassOrCode))

349 number -= 1

350 sofar = nsofar

351 return answer

352

353 # "printf" string specifier

354 if format[:1] == '%': 354 ↛ 356line 354 didn't jump to line 356, because the condition on line 354 was never true

355 # format string like specifier

356 return format % data

357

358 # asciiz specifier

359 if format == 'z':

360 if data[-1:] != b('\x00'):

361 raise Exception("%s 'z' field is not NUL terminated: %r" % (field, data))

362 if PY3: 362 ↛ 365line 362 didn't jump to line 365, because the condition on line 362 was never false

363 return data[:-1].decode('latin-1')

364 else:

365 return data[:-1]

366

367 # unicode specifier

368 if format == 'u':

369 if data[-2:] != b('\x00\x00'): 369 ↛ 370line 369 didn't jump to line 370, because the condition on line 369 was never true

370 raise Exception("%s 'u' field is not NUL-NUL terminated: %r" % (field, data))

371 return data[:-2] # remove trailing NUL

372

373 # DCE-RPC/NDR string specifier

374 if format == 'w':

375 l = unpack('<L', data[:4])[0]

376 return data[12:12+l*2]

377

378 # literal specifier

379 if format == ':':

380 if isinstance(data, bytes) and dataClassOrCode is b:

381 return data

382 return dataClassOrCode(data)

383

384 # struct like specifier

385 return unpack(format, data)[0]

386

387 def calcPackSize(self, format, data, field = None):

388# # print " calcPackSize %s:%r" % (format, data)

389 if field:

390 addressField = self.findAddressFieldFor(field)

391 if addressField is not None:

392 if not self[addressField]:

393 return 0

394

395 # void specifier

396 if format[:1] == '_':

397 return 0

398

399 # quote specifier

400 if format[:1] == "'" or format[:1] == '"':

401 return len(format)-1

402

403 # address specifier

404 two = format.split('&')

405 if len(two) == 2:

406 return self.calcPackSize(two[0], data)

407

408 # code specifier

409 two = format.split('=')

410 if len(two) >= 2:

411 return self.calcPackSize(two[0], data)

412

413 # length specifier

414 two = format.split('-')

415 if len(two) == 2:

416 return self.calcPackSize(two[0], data)

417

418 # array specifier

419 two = format.split('*')

420 if len(two) == 2:

421 answer = 0

422 if two[0].isdigit(): 422 ↛ 423line 422 didn't jump to line 423, because the condition on line 422 was never true

423 if int(two[0]) != len(data):

424 raise Exception("Array field has a constant size, and it doesn't match the actual value")

425 elif two[0]:

426 answer += self.calcPackSize(two[0], len(data))

427

428 for each in data:

429 answer += self.calcPackSize(two[1], each)

430 return answer

431

432 # "printf" string specifier

433 if format[:1] == '%': 433 ↛ 435line 433 didn't jump to line 435, because the condition on line 433 was never true

434 # format string like specifier

435 return len(format % data)

436

437 # asciiz specifier

438 if format[:1] == 'z':

439 return len(data)+1

440

441 # asciiz specifier

442 if format[:1] == 'u':

443 l = len(data)

444 return l + (l & 1 and 3 or 2)

445

446 # DCE-RPC/NDR string specifier

447 if format[:1] == 'w':

448 l = len(data)

449 return 12+l+l % 2

450

451 # literal specifier

452 if format[:1] == ':':

453 return len(data)

454

455 # struct like specifier

456 return calcsize(format)

457

458 def calcUnpackSize(self, format, data, field = None):

459 if self.debug: 459 ↛ 460line 459 didn't jump to line 460, because the condition on line 459 was never true

460 print(" calcUnpackSize( %s | %s | %r)" % (field, format, data))

461

462 # void specifier

463 if format[:1] == '_':

464 return 0

465

466 addressField = self.findAddressFieldFor(field)

467 if addressField is not None:

468 if not self[addressField]:

469 return 0

470

471 try:

472 lengthField = self.findLengthFieldFor(field)

473 return int(self[lengthField])

474 except Exception:

475 pass

476

477 # XXX: Try to match to actual values, raise if no match

478

479 # quote specifier

480 if format[:1] == "'" or format[:1] == '"':

481 return len(format)-1

482

483 # address specifier

484 two = format.split('&')

485 if len(two) == 2:

486 return self.calcUnpackSize(two[0], data)

487

488 # code specifier

489 two = format.split('=')

490 if len(two) >= 2:

491 return self.calcUnpackSize(two[0], data)

492

493 # length specifier

494 two = format.split('-')

495 if len(two) == 2:

496 return self.calcUnpackSize(two[0], data)

497

498 # array specifier

499 two = format.split('*')

500 if len(two) == 2:

501 answer = 0

502 if two[0]:

503 if two[0].isdigit(): 503 ↛ 504line 503 didn't jump to line 504, because the condition on line 503 was never true

504 number = int(two[0])

505 else:

506 answer += self.calcUnpackSize(two[0], data)

507 number = self.unpack(two[0], data[:answer])

508

509 while number:

510 number -= 1

511 answer += self.calcUnpackSize(two[1], data[answer:])

512 else:

513 while answer < len(data):

514 answer += self.calcUnpackSize(two[1], data[answer:])

515 return answer

516

517 # "printf" string specifier

518 if format[:1] == '%': 518 ↛ 519line 518 didn't jump to line 519, because the condition on line 518 was never true

519 raise Exception("Can't guess the size of a printf like specifier for unpacking")

520

521 # asciiz specifier

522 if format[:1] == 'z':

523 return data.index(b('\x00'))+1

524

525 # asciiz specifier

526 if format[:1] == 'u':

527 l = data.index(b('\x00\x00'))

528 return l + (l & 1 and 3 or 2)

529

530 # DCE-RPC/NDR string specifier

531 if format[:1] == 'w':

532 l = unpack('<L', data[:4])[0]

533 return 12+l*2

534

535 # literal specifier

536 if format[:1] == ':':

537 return len(data)

538

539 # struct like specifier

540 return calcsize(format)

541

542 def calcPackFieldSize(self, fieldName, format = None):

543 if format is None: 543 ↛ 546line 543 didn't jump to line 546, because the condition on line 543 was never false

544 format = self.formatForField(fieldName)

545

546 return self.calcPackSize(format, self[fieldName])

547

548 def formatForField(self, fieldName):

549 for field in self.commonHdr+self.structure: 549 ↛ 552line 549 didn't jump to line 552, because the loop on line 549 didn't complete

550 if field[0] == fieldName:

551 return field[1]

552 raise Exception("Field %s not found" % fieldName)

553

554 def findAddressFieldFor(self, fieldName):

555 descriptor = '&%s' % fieldName

556 l = len(descriptor)

557 for field in self.commonHdr+self.structure:

558 if field[1][-l:] == descriptor:

559 return field[0]

560 return None

561

562 def findLengthFieldFor(self, fieldName):

563 descriptor = '-%s' % fieldName

564 l = len(descriptor)

565 for field in self.commonHdr+self.structure:

566 if field[1][-l:] == descriptor:

567 return field[0]

568 return None

569

570 def zeroValue(self, format):

571 two = format.split('*')

572 if len(two) == 2:

573 if two[0].isdigit():

574 return (self.zeroValue(two[1]),)*int(two[0])

575

576 if not format.find('*') == -1:

577 return ()

578 if 's' in format:

579 return b''

580 if format in ['z',':','u']:

581 return b''

582 if format == 'w':

583 return b('\x00\x00')

584

585 return 0

586

587 def clear(self):

588 for field in self.commonHdr + self.structure:

589 self[field[0]] = self.zeroValue(field[1])

590

591 def dump(self, msg = None, indent = 0):

592 if msg is None:

593 msg = self.__class__.__name__

594 ind = ' '*indent

595 print("\n%s" % msg)

596 fixedFields = []

597 for field in self.commonHdr+self.structure:

598 i = field[0]

599 if i in self.fields: 599 ↛ 597line 599 didn't jump to line 597, because the condition on line 599 was never false

600 fixedFields.append(i)

601 if isinstance(self[i], Structure):

602 self[i].dump('%s%s:{' % (ind,i), indent = indent + 4)

603 print("%s}" % ind)

604 else:

605 print("%s%s: {%r}" % (ind,i,self[i]))

606 # Do we have remaining fields not defined in the structures? let's

607 # print them

608 remainingFields = list(set(self.fields) - set(fixedFields))

609 for i in remainingFields:

610 if isinstance(self[i], Structure): 610 ↛ 611line 610 didn't jump to line 611, because the condition on line 610 was never true

611 self[i].dump('%s%s:{' % (ind,i), indent = indent + 4)

612 print("%s}" % ind)

613 else:

614 print("%s%s: {%r}" % (ind,i,self[i]))

615

616 def pretty_print(x):

617 if chr(x) in '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ ':

618 return chr(x)

619 else:

620 return u'.'

621

622 def hexdump(data, indent = ''):

623 if data is None: 623 ↛ 624line 623 didn't jump to line 624, because the condition on line 623 was never true

624 return

625 if isinstance(data, int): 625 ↛ 626line 625 didn't jump to line 626, because the condition on line 625 was never true

626 data = str(data).encode('utf-8')

627 x=bytearray(data)

628 strLen = len(x)

629 i = 0

630 while i < strLen:

631 line = " %s%04x " % (indent, i)

632 for j in range(16):

633 if i+j < strLen:

634 line += "%02X " % x[i+j]

635 else:

636 line += u" "

637 if j%16 == 7:

638 line += " "

639 line += " "

640 line += ''.join(pretty_print(x) for x in x[i: i+16] )

641 print (line)

642 i += 16

643

644 def parse_bitmask(dict, value):

645 ret = ''

646

647 for i in range(0, 31):

648 flag = 1 << i

649

650 if value & flag == 0:

651 continue

652

653 if flag in dict:

654 ret += '%s | ' % dict[flag]

655 else:

656 ret += "0x%.8X | " % flag

657

658 if len(ret) == 0:

659 return '0'

660 else:

661 return ret[:-3]