Hide keyboard shortcuts

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

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

467

468

469

470

471

472

473

474

475

476

477

478

479

480

481

482

483

484

485

486

487

488

489

490

491

492

493

494

495

496

497

498

499

500

501

502

503

504

505

506

507

508

509

510

511

512

513

514

515

516

517

518

519

520

521

522

523

524

525

526

527

528

529

530

531

532

533

534

535

536

537

538

539

540

541

542

543

544

545

546

547

548

549

550

551

552

553

554

555

556

557

558

559

560

561

562

563

564

565

566

567

568

569

570

571

572

573

574

575

576

577

578

579

580

581

582

583

584

585

586

587

588

589

590

591

592

593

594

595

596

597

598

599

600

601

602

603

604

605

606

607

608

609

610

611

612

613

614

615

616

617

618

619

620

621

622

623

624

625

626

627

628

629

630

631

632

633

634

635

636

637

638

639

640

641

642

643

644

645

646

647

648

649

650

651

652

653

654

655

656

657

658

659

660

661

662

663

664

665

666

667

668

669

670

671

672

673

674

675

676

677

678

679

680

681

682

683

684

685

686

687

688

689

690

691

692

693

694

695

696

697

698

699

700

701

702

703

704

705

706

707

708

709

710

711

712

713

714

715

716

717

718

719

720

721

722

723

724

725

726

727

728

729

730

731

732

733

734

735

736

737

738

739

740

741

742

743

744

745

746

747

748

749

750

751

752

753

754

755

756

757

758

759

760

761

762

763

764

765

766

767

768

769

770

771

772

773

774

775

776

777

778

779

780

781

782

783

784

785

786

787

788

789

790

791

792

793

794

795

796

797

798

799

800

801

802

803

804

805

806

807

808

809

810

811

812

813

814

815

816

817

818

819

820

821

822

823

824

825

826

827

828

829

830

831

832

833

834

835

836

837

838

839

840

841

842

843

844

845

846

847

848

849

850

851

852

853

854

855

856

857

858

859

860

861

862

863

864

865

866

867

868

869

870

871

872

873

874

875

876

877

878

879

880

881

882

883

884

885

886

887

888

889

890

891

892

893

894

895

896

897

898

899

900

901

902

903

904

905

906

907

908

909

910

911

912

913

914

915

916

917

918

919

920

921

922

923

924

925

926

927

928

929

930

931

932

933

934

935

936

937

938

939

940

941

942

943

944

945

946

947

948

949

950

951

952

953

954

955

956

957

958

959

960

961

962

963

964

965

966

967

968

969

970

971

972

973

974

975

976

977

978

979

980

981

982

983

984

985

986

987

988

989

990

991

992

993

994

995

996

997

998

999

1000

1001

1002

1003

1004

1005

# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 

# 

# This software is provided under under a slightly modified version 

# of the Apache Software License. See the accompanying LICENSE file 

# for more information. 

# 

 

 

# -*- mode: python; tab-width: 4 -*- 

# 

# Copyright (C) 2001 Michael Teo <michaelteo@bigfoot.com> 

# nmb.py - NetBIOS library 

# 

# This software is provided 'as-is', without any express or implied warranty.  

# In no event will the author be held liable for any damages arising from the  

# use of this software. 

# 

# Permission is granted to anyone to use this software for any purpose,  

# including commercial applications, and to alter it and redistribute it  

# freely, subject to the following restrictions: 

# 

# 1. The origin of this software must not be misrepresented; you must not  

# claim that you wrote the original software. If you use this software  

# in a product, an acknowledgment in the product documentation would be 

# appreciated but is not required. 

# 

# 2. Altered source versions must be plainly marked as such, and must not be  

# misrepresented as being the original software. 

# 

# 3. This notice cannot be removed or altered from any source distribution. 

# 

# Altered source done by Alberto Solino (@agsolino) 

 

from __future__ import division 

from __future__ import print_function 

from __future__ import absolute_import 

import errno 

import re 

import select 

import socket 

import string 

import time 

import random 

from struct import pack, unpack 

from six import byte2int, indexbytes, b 

 

from impacket.structure import Structure 

 

# Our random number generator 

try: 

rand = random.SystemRandom() 

except NotImplementedError: 

rand = random 

pass 

 

 

################################################################################ 

# CONSTANTS 

################################################################################ 

# Taken from socket module reference 

INADDR_ANY = '0.0.0.0' 

BROADCAST_ADDR = '<broadcast>' 

 

# Default port for NetBIOS name service 

NETBIOS_NS_PORT = 137 

# Default port for NetBIOS session service 

NETBIOS_SESSION_PORT = 139 

 

# Default port for SMB session service 

SMB_SESSION_PORT = 445 

 

# Owner Node Type Constants 

NODE_B = 0x0000 

NODE_P = 0x2000 

NODE_M = 0x4000 

NODE_RESERVED = 0x6000 

NODE_GROUP = 0x8000 

NODE_UNIQUE = 0x0 

 

# Name Type Constants 

TYPE_UNKNOWN = 0x01 

TYPE_WORKSTATION = 0x00 

TYPE_CLIENT = 0x03 

TYPE_SERVER = 0x20 

TYPE_DOMAIN_MASTER = 0x1B 

TYPE_DOMAIN_CONTROLLER = 0x1C 

TYPE_MASTER_BROWSER = 0x1D 

TYPE_BROWSER = 0x1E 

TYPE_NETDDE = 0x1F 

TYPE_STATUS = 0x21 

 

# Opcodes values 

OPCODE_QUERY = 0 

OPCODE_REGISTRATION = 0x5 << 11 

OPCODE_RELEASE = 0x6 << 11 

OPCODE_WACK = 0x7 << 11 

OPCODE_REFRESH = 0x8 << 11 

OPCODE_REQUEST = 0 << 11 

OPCODE_RESPONSE = 0x10 << 11 

 

# NM_FLAGS 

NM_FLAGS_BROADCAST = 0x1 << 4 

NM_FLAGS_UNICAST = 0 << 4 

NM_FLAGS_RA = 0x8 << 4 

NM_FLAGS_RD = 0x10 << 4 

NM_FLAGS_TC = 0x20 << 4 

NM_FLAGS_AA = 0x40 << 4 

 

# QUESTION_TYPE 

QUESTION_TYPE_NB = 0x20 # NetBIOS general Name Service Resource Record 

QUESTION_TYPE_NBSTAT = 0x21 # NetBIOS NODE STATUS Resource Record 

# QUESTION_CLASS 

QUESTION_CLASS_IN = 0x1 # Internet class 

 

# RESOURCE RECORD RR_TYPE field definitions 

RR_TYPE_A = 0x1 # IP address Resource Record 

RR_TYPE_NS = 0x2 # Name Server Resource Record 

RR_TYPE_NULL = 0xA # NULL Resource Record 

RR_TYPE_NB = 0x20 # NetBIOS general Name Service Resource Record 

RR_TYPE_NBSTAT = 0x21 # NetBIOS NODE STATUS Resource Record 

 

# RESOURCE RECORD RR_CLASS field definitions 

RR_CLASS_IN = 1 # Internet class 

 

# RCODE values 

RCODE_FMT_ERR = 0x1 # Format Error. Request was invalidly formatted. 

RCODE_SRV_ERR = 0x2 # Server failure. Problem with NBNS, cannot process name. 

RCODE_IMP_ERR = 0x4 # Unsupported request error. Allowable only for challenging NBNS when gets an Update type 

# registration request. 

RCODE_RFS_ERR = 0x5 # Refused error. For policy reasons server will not register this name from this host. 

RCODE_ACT_ERR = 0x6 # Active error. Name is owned by another node. 

RCODE_CFT_ERR = 0x7 # Name in conflict error. A UNIQUE name is owned by more than one node. 

 

# NAME_FLAGS 

NAME_FLAGS_PRM = 0x0200 # Permanent Name Flag. If one (1) then entry is for the permanent node name. Flag is zero 

# (0) for all other names. 

NAME_FLAGS_ACT = 0x0400 # Active Name Flag. All entries have this flag set to one (1). 

NAME_FLAG_CNF = 0x0800 # Conflict Flag. If one (1) then name on this node is in conflict. 

NAME_FLAG_DRG = 0x1000 # Deregister Flag. If one (1) then this name is in the process of being deleted. 

 

# NB_FLAGS 

NB_FLAGS_ONT_B = 0 

NB_FLAGS_ONT_P = 1 << 13 

NB_FLAGS_ONT_M = 2 << 13 

NB_FLAGS_G = 1 << 15 

 

NAME_TYPES = {TYPE_UNKNOWN: 'Unknown', TYPE_WORKSTATION: 'Workstation', TYPE_CLIENT: 'Client', 

TYPE_SERVER: 'Server', TYPE_DOMAIN_MASTER: 'Domain Master', TYPE_DOMAIN_CONTROLLER: 'Domain Controller', 

TYPE_MASTER_BROWSER: 'Master Browser', TYPE_BROWSER: 'Browser Server', TYPE_NETDDE: 'NetDDE Server', 

TYPE_STATUS: 'Status'} 

 

# NetBIOS Session Types 

NETBIOS_SESSION_MESSAGE = 0x0 

NETBIOS_SESSION_REQUEST = 0x81 

NETBIOS_SESSION_POSITIVE_RESPONSE = 0x82 

NETBIOS_SESSION_NEGATIVE_RESPONSE = 0x83 

NETBIOS_SESSION_RETARGET_RESPONSE = 0x84 

NETBIOS_SESSION_KEEP_ALIVE = 0x85 

 

################################################################################ 

# HELPERS 

################################################################################ 

def encode_name(name, nametype, scope): 

# ToDo: Rewrite this simpler, we're using less than written 

""" 

Perform first and second level encoding of name as specified in RFC 1001 (Section 4) 

 

:param string name: the name to encode 

:param integer nametype: the name type constants 

:param string scope: the name's scope  

 

:return string/bytes: the encoded name. 

""" 

if name == '*': 

name += '\0' * 15 

elif len(name) > 15: 

name = name[:15] + chr(nametype) 

else: 

name = name.ljust(15) + chr(nametype) 

 

encoded_name = chr(len(name) * 2) + re.sub('.', _do_first_level_encoding, name) 

 

try: 

184 ↛ 185,   184 ↛ 1882 missed branches: 1) line 184 didn't jump to line 185, because the condition on line 184 was never true, 2) line 184 didn't jump to line 188, because the condition on line 184 was never false if isinstance(encoded_name, unicode): 

encoded_name = encoded_name.encode('utf-8') 

except NameError: 

pass 

188 ↛ 189line 188 didn't jump to line 189, because the condition on line 188 was never true if scope: 

encoded_scope = '' 

for s in scope.split('.'): 

encoded_scope = encoded_scope + chr(len(s)) + s 

 

return b(encoded_name + encoded_scope) + b'\0' 

else: 

return b(encoded_name) + b'\0' 

 

# Internal method for use in encode_name() 

def _do_first_level_encoding(m): 

s = ord(m.group(0)) 

return string.ascii_uppercase[s >> 4] + string.ascii_uppercase[s & 0x0f] 

 

def decode_name(name): 

# ToDo: Rewrite this simpler, we're using less than written 

""" 

Perform first and second level decoding of name as specified in RFC 1001 (Section 4) 

 

:param string/bytes name: the name to decode 

 

:return string: the decoded name. 

""" 

 

name_length = ord(name[0:1]) 

assert name_length == 32 

 

decoded_name = re.sub('..', _do_first_level_decoding, name[1:33].decode('utf-8')) 

216 ↛ 219line 216 didn't jump to line 219, because the condition on line 216 was never false if name[33:34] == b'\0': 

return 34, decoded_name, '' 

else: 

decoded_domain = '' 

offset = 34 

while 1: 

domain_length = byte2int(name[offset:offset+1]) 

if domain_length == 0: 

break 

decoded_domain = '.' + name[offset:offset + domain_length].decode('utf-8') 

offset += domain_length 

return offset + 1, decoded_name, decoded_domain 

 

def _do_first_level_decoding(m): 

s = m.group(0) 

return chr(((ord(s[0]) - ord('A')) << 4) | (ord(s[1]) - ord('A'))) 

 

ERRCLASS_QUERY = 0x00 

ERRCLASS_SESSION = 0xf0 

ERRCLASS_OS = 0xff 

 

QUERY_ERRORS = {0x01: 'Format Error. Request was invalidly formatted', 

0x02: 'Server failure. Problem with NBNS, cannot process name.', 

0x03: 'Name does not exist', 

0x04: 'Unsupported request error. Allowable only for challenging NBNS when gets an Update type registration request.', 

0x05: 'Refused error. For policy reasons server will not register this name from this host.', 

0x06: 'Active error. Name is owned by another node.', 

0x07: 'Name in conflict error. A UNIQUE name is owned by more than one node.', 

 

} 

 

SESSION_ERRORS = {0x80: 'Not listening on called name', 

0x81: 'Not listening for calling name', 

0x82: 'Called name not present', 

0x83: 'Sufficient resources', 

0x8f: 'Unspecified error' 

} 

 

class NetBIOSError(Exception): 

def __init__(self, error_message='', error_class=None, error_code=None): 

self.error_class = error_class 

self.error_code = error_code 

self.error_msg = error_message 

 

def get_error_code(self): 

return self.error 

 

def getErrorCode(self): 

return self.get_error_code() 

 

def get_error_string(self): 

return str(self) 

 

def getErrorString(self): 

return str(self) 

 

def __str__(self): 

if self.error_code is not None: 

if self.error_code in QUERY_ERRORS: 

return '%s-%s(%s)' % (self.error_msg, QUERY_ERRORS[self.error_code], self.error_code) 

elif self.error_code in SESSION_ERRORS: 

return '%s-%s(%s)' % (self.error_msg, SESSION_ERRORS[self.error_code], self.error_code) 

else: 

return '%s(%s)' % (self.error_msg, self.error_code) 

else: 

return '%s' % self.error_msg 

 

class NetBIOSTimeout(Exception): 

def __init__(self, message = 'The NETBIOS connection with the remote host timed out.'): 

Exception.__init__(self, message) 

 

################################################################################ 

# 4.2 NAME SERVER PACKETS 

################################################################################ 

class NBNSResourceRecord(Structure): 

structure = ( 

('RR_NAME','z=\x00'), 

('RR_TYPE','>H=0'), 

('RR_CLASS','>H=0'), 

('TTL','>L=0'), 

('RDLENGTH','>H-RDATA'), 

('RDATA',':=""'), 

) 

 

class NBNodeStatusResponse(NBNSResourceRecord): 

def __init__(self, data = 0): 

NBNSResourceRecord.__init__(self, data) 

self.mac = b'00-00-00-00-00-00' 

self.num_names = unpack('B', self['RDATA'][:1])[0] 

self.entries = list() 

data = self['RDATA'][1:] 

for _ in range(self.num_names): 

entry = NODE_NAME_ENTRY(data) 

data = data[len(entry):] 

self.entries.append(entry) 

self.statistics = STATISTICS(data) 

self.set_mac_in_hexa(self.statistics['UNIT_ID']) 

 

def set_mac_in_hexa(self, data): 

data_aux = u'' 

for d in bytearray(data): 

if data_aux == '': 

data_aux = '%02x' % d 

else: 

data_aux += '-%02x' % d 

self.mac = data_aux.upper() 

 

def get_mac(self): 

return self.mac 

 

def rawData(self): 

res = pack('!B', self.num_names ) 

for i in range(0, self.num_names): 

res += self.entries[i].getData() 

 

class NBPositiveNameQueryResponse(NBNSResourceRecord): 

def __init__(self, data = 0): 

NBNSResourceRecord.__init__(self, data) 

self.entries = [ ] 

rdata = self['RDATA'] 

while len(rdata) > 0: 

entry = ADDR_ENTRY(rdata) 

rdata = rdata[len(entry):] 

self.entries.append(socket.inet_ntoa(entry['NB_ADDRESS'])) 

 

# 4.2.1. GENERAL FORMAT OF NAME SERVICE PACKETS 

class NAME_SERVICE_PACKET(Structure): 

commonHdr = ( 

('NAME_TRN_ID','>H=0'), 

('FLAGS','>H=0'), 

('QDCOUNT','>H=0'), 

('ANCOUNT','>H=0'), 

('NSCOUNT','>H=0'), 

('ARCOUNT','>H=0'), 

) 

structure = ( 

('ANSWERS',':'), 

) 

 

# 4.2.1.2. QUESTION SECTION 

class QUESTION_ENTRY(Structure): 

commonHdr = ( 

('QUESTION_NAME','z'), 

('QUESTION_TYPE','>H=0'), 

('QUESTION_CLASS','>H=0'), 

) 

 

# 4.2.1.3. RESOURCE RECORD 

class RESOURCE_RECORD(Structure): 

structure = ( 

('RR_NAME','z=\x00'), 

('RR_TYPE','>H=0'), 

('RR_CLASS','>H=0'), 

('TTL','>L=0'), 

('RDLENGTH','>H-RDATA'), 

('RDATA',':=""'), 

) 

 

# 4.2.2. NAME REGISTRATION REQUEST 

class NAME_REGISTRATION_REQUEST(NAME_SERVICE_PACKET): 

structure = ( 

('QUESTION_NAME', ':'), 

('QUESTION_TYPE', '>H=0'), 

('QUESTION_CLASS', '>H=0'), 

('RR_NAME',':', ), 

('RR_TYPE', '>H=0'), 

('RR_CLASS','>H=0'), 

('TTL', '>L=0'), 

('RDLENGTH', '>H=6'), 

('NB_FLAGS', '>H=0'), 

('NB_ADDRESS', '4s=b""'), 

) 

def __init__(self, data=None): 

NAME_SERVICE_PACKET.__init__(self,data) 

self['FLAGS'] = OPCODE_REQUEST | NM_FLAGS_RD | OPCODE_REGISTRATION 

self['QDCOUNT'] = 1 

self['ANCOUNT'] = 0 

self['NSCOUNT'] = 0 

self['ARCOUNT'] = 1 

 

self['QUESTION_TYPE'] = QUESTION_TYPE_NB 

self['QUESTION_CLASS'] = QUESTION_CLASS_IN 

 

self['RR_TYPE'] = RR_TYPE_NB 

self['RR_CLASS'] = RR_CLASS_IN 

 

# 4.2.3. NAME OVERWRITE REQUEST & DEMAND 

class NAME_OVERWRITE_REQUEST(NAME_REGISTRATION_REQUEST): 

def __init__(self, data=None): 

NAME_REGISTRATION_REQUEST.__init__(self,data) 

self['FLAGS'] = OPCODE_REQUEST | OPCODE_REGISTRATION 

self['QDCOUNT'] = 1 

self['ANCOUNT'] = 0 

self['NSCOUNT'] = 0 

self['ARCOUNT'] = 1 

 

# 4.2.4. NAME REFRESH REQUEST 

class NAME_REFRESH_REQUEST(NAME_REGISTRATION_REQUEST): 

def __init__(self, data=None): 

NAME_REGISTRATION_REQUEST.__init__(self,data) 

self['FLAGS'] = OPCODE_REFRESH | 0x1 

self['QDCOUNT'] = 1 

self['ANCOUNT'] = 0 

self['NSCOUNT'] = 0 

self['ARCOUNT'] = 1 

 

# 4.2.5. POSITIVE NAME REGISTRATION RESPONSE 

# 4.2.6. NEGATIVE NAME REGISTRATION RESPONSE 

# 4.2.7. END-NODE CHALLENGE REGISTRATION RESPONSE 

class NAME_REGISTRATION_RESPONSE(NAME_REGISTRATION_REQUEST): 

def __init__(self, data=None): 

NAME_REGISTRATION_REQUEST.__init__(self,data) 

 

# 4.2.8. NAME CONFLICT DEMAND 

class NAME_CONFLICT_DEMAND(NAME_REGISTRATION_REQUEST): 

def __init__(self, data=None): 

NAME_REGISTRATION_REQUEST.__init__(self,data) 

 

# ToDo: 4.2.9. NAME RELEASE REQUEST & DEMAND 

# ToDo: 4.2.10. POSITIVE NAME RELEASE RESPONSE 

# ToDo: 4.2.11. NEGATIVE NAME RELEASE RESPONSE 

 

# 4.2.12. NAME QUERY REQUEST 

class NAME_QUERY_REQUEST(NAME_SERVICE_PACKET): 

structure = ( 

('QUESTION_NAME', ':'), 

('QUESTION_TYPE', '>H=0'), 

('QUESTION_CLASS', '>H=0'), 

) 

def __init__(self, data=None): 

NAME_SERVICE_PACKET.__init__(self,data) 

self['FLAGS'] = OPCODE_REQUEST | OPCODE_REGISTRATION | NM_FLAGS_RD 

self['RCODE'] = 0 

self['QDCOUNT'] = 1 

self['ANCOUNT'] = 0 

self['NSCOUNT'] = 0 

self['ARCOUNT'] = 0 

 

self['QUESTION_TYPE'] = QUESTION_TYPE_NB 

self['QUESTION_CLASS'] = QUESTION_CLASS_IN 

 

# 4.2.13. POSITIVE NAME QUERY RESPONSE 

class ADDR_ENTRY(Structure): 

structure = ( 

('NB_FLAGS', '>H=0'), 

('NB_ADDRESS', '4s=b""'), 

) 

 

# ToDo: 4.2.15. REDIRECT NAME QUERY RESPONSE 

# ToDo: 4.2.16. WAIT FOR ACKNOWLEDGEMENT (WACK) RESPONSE 

 

# 4.2.17. NODE STATUS REQUEST 

class NODE_STATUS_REQUEST(NAME_QUERY_REQUEST): 

def __init__(self, data=None): 

NAME_QUERY_REQUEST.__init__(self,data) 

 

self['FLAGS'] = 0 

self['QUESTION_TYPE'] = QUESTION_TYPE_NBSTAT 

 

# 4.2.18. NODE STATUS RESPONSE 

class NODE_NAME_ENTRY(Structure): 

structure = ( 

('NAME','15s=b""'), 

('TYPE','B=0'), 

('NAME_FLAGS','>H'), 

) 

 

class STATISTICS(Structure): 

structure = ( 

('UNIT_ID','6s=b""'), 

('JUMPERS','B'), 

('TEST_RESULT','B'), 

('VERSION_NUMBER','>H'), 

('PERIOD_OF_STATISTICS','>H'), 

('NUMBER_OF_CRCs','>H'), 

('NUMBER_ALIGNMENT_ERRORS','>H'), 

('NUMBER_OF_COLLISIONS','>H'), 

('NUMBER_SEND_ABORTS','>H'), 

('NUMBER_GOOD_SENDS','>L'), 

('NUMBER_GOOD_RECEIVES','>L'), 

('NUMBER_RETRANSMITS','>H'), 

('NUMBER_NO_RESOURCE_CONDITIONS','>H'), 

('NUMBER_FREE_COMMAND_BLOCKS','>H'), 

('TOTAL_NUMBER_COMMAND_BLOCKS','>H'), 

('MAX_TOTAL_NUMBER_COMMAND_BLOCKS','>H'), 

('NUMBER_PENDING_SESSIONS','>H'), 

('MAX_NUMBER_PENDING_SESSIONS','>H'), 

('MAX_TOTAL_SESSIONS_POSSIBLE','>H'), 

('SESSION_DATA_PACKET_SIZE','>H'), 

) 

 

class NetBIOS: 

# Creates a NetBIOS instance without specifying any default NetBIOS domain nameserver. 

# All queries will be sent through the servport. 

def __init__(self, servport = NETBIOS_NS_PORT): 

self.__servport = NETBIOS_NS_PORT 

self.__nameserver = None 

self.__broadcastaddr = BROADCAST_ADDR 

self.mac = b'00-00-00-00-00-00' 

 

def _setup_connection(self, dstaddr, timeout=None): 

port = rand.randint(10000, 60000) 

af, socktype, proto, _canonname, _sa = socket.getaddrinfo(dstaddr, port, socket.AF_INET, socket.SOCK_DGRAM)[0] 

s = socket.socket(af, socktype, proto) 

has_bind = 1 

for _i in range(0, 10): 

# We try to bind to a port for 10 tries 

try: 

s.bind((INADDR_ANY, rand.randint(10000, 60000))) 

s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) 

has_bind = 1 

except socket.error: 

pass 

529 ↛ 530line 529 didn't jump to line 530, because the condition on line 529 was never true if not has_bind: 

raise NetBIOSError('Cannot bind to a good UDP port', ERRCLASS_OS, errno.EAGAIN) 

self.__sock = s 

 

def send(self, request, destaddr, timeout): 

self._setup_connection(destaddr) 

 

tries = 3 

while 1: 

try: 

self.__sock.sendto(request.getData(), 0, (destaddr, self.__servport)) 

ready, _, _ = select.select([self.__sock.fileno()], [], [], timeout) 

if not ready: 

if tries: 

# Retry again until tries == 0 

tries -= 1 

else: 

raise NetBIOSTimeout 

else: 

try: 

data, _ = self.__sock.recvfrom(65536, 0) 

except Exception as e: 

raise NetBIOSError("recvfrom error: %s" % str(e)) 

self.__sock.close() 

res = NAME_SERVICE_PACKET(data) 

554 ↛ 538line 554 didn't jump to line 538, because the condition on line 554 was never false if res['NAME_TRN_ID'] == request['NAME_TRN_ID']: 

555 ↛ 556line 555 didn't jump to line 556, because the condition on line 555 was never true if (res['FLAGS'] & 0xf) > 0: 

raise NetBIOSError('Negative response', ERRCLASS_QUERY, res['FLAGS'] & 0xf) 

return res 

558 ↛ 559line 558 didn't jump to line 559, because the exception caught by line 558 didn't happen except select.error as ex: 

if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN: 

raise NetBIOSError('Error occurs while waiting for response', ERRCLASS_OS, ex[0]) 

except socket.error as ex: 

raise NetBIOSError('Connection error: %s' % str(ex)) 

 

# Set the default NetBIOS domain nameserver. 

def set_nameserver(self, nameserver): 

self.__nameserver = nameserver 

 

# Return the default NetBIOS domain nameserver, or None if none is specified. 

def get_nameserver(self): 

return self.__nameserver 

 

# Set the broadcast address to be used for query. 

def set_broadcastaddr(self, broadcastaddr): 

self.__broadcastaddr = broadcastaddr 

 

# Return the broadcast address to be used, or BROADCAST_ADDR if default broadcast address is used.  

def get_broadcastaddr(self): 

return self.__broadcastaddr 

 

# Returns a NBPositiveNameQueryResponse instance containing the host information for nbname. 

# If a NetBIOS domain nameserver has been specified, it will be used for the query. 

# Otherwise, the query is broadcasted on the broadcast address. 

def gethostbyname(self, nbname, qtype = TYPE_WORKSTATION, scope = None, timeout = 1): 

resp = self.name_query_request(nbname, self.__nameserver, qtype, scope, timeout) 

return resp 

 

# Returns a list of NBNodeEntry instances containing node status information for nbname. 

# If destaddr contains an IP address, then this will become an unicast query on the destaddr. 

# Raises NetBIOSTimeout if timeout (in secs) is reached. 

# Raises NetBIOSError for other errors 

def getnodestatus(self, nbname, destaddr = None, type = TYPE_WORKSTATION, scope = None, timeout = 1): 

592 ↛ 595line 592 didn't jump to line 595, because the condition on line 592 was never false if destaddr: 

return self.node_status_request(nbname, destaddr, type, scope, timeout) 

else: 

return self.node_status_request(nbname, self.__nameserver, type, scope, timeout) 

 

def getnetbiosname(self, ip): 

entries = self.getnodestatus('*',ip) 

entries = [x for x in entries if x['TYPE'] == TYPE_SERVER] 

return entries[0]['NAME'].strip().decode('latin-1') 

 

def getmacaddress(self): 

return self.mac 

 

def name_registration_request(self, nbname, destaddr, qtype, scope, nb_flags=0, nb_address='0.0.0.0'): 

netbios_name = nbname.upper() 

qn_label = encode_name(netbios_name, qtype, scope) 

 

p = NAME_REGISTRATION_REQUEST() 

p['NAME_TRN_ID'] = rand.randint(1, 32000) 

p['QUESTION_NAME'] = qn_label[:-1] + b'\x00' 

p['RR_NAME'] = qn_label[:-1] + b'\x00' 

p['TTL'] = 0xffff 

p['NB_FLAGS'] = nb_flags 

p['NB_ADDRESS'] = socket.inet_aton(nb_address) 

616 ↛ 617line 616 didn't jump to line 617, because the condition on line 616 was never true if not destaddr: 

p['FLAGS'] |= NM_FLAGS_BROADCAST 

destaddr = self.__broadcastaddr 

 

res = self.send(p, destaddr, 1) 

return res 

 

def name_query_request(self, nbname, destaddr = None, qtype = TYPE_SERVER, scope = None, timeout = 1): 

netbios_name = nbname.upper() 

qn_label = encode_name(netbios_name, qtype, scope) 

 

p = NAME_QUERY_REQUEST() 

p['NAME_TRN_ID'] = rand.randint(1, 32000) 

p['QUESTION_NAME'] = qn_label[:-1] + b'\x00' 

p['FLAGS'] = NM_FLAGS_RD 

631 ↛ 632line 631 didn't jump to line 632, because the condition on line 631 was never true if not destaddr: 

p['FLAGS'] |= NM_FLAGS_BROADCAST 

 

destaddr = self.__broadcastaddr 

 

res = self.send(p, destaddr, timeout) 

return NBPositiveNameQueryResponse(res['ANSWERS']) 

 

def node_status_request(self, nbname, destaddr, type, scope, timeout): 

netbios_name = nbname.upper() 

qn_label = encode_name(netbios_name, type, scope) 

p = NODE_STATUS_REQUEST() 

p['NAME_TRN_ID'] = rand.randint(1, 32000) 

p['QUESTION_NAME'] = qn_label[:-1] + b'\x00' 

 

646 ↛ 647line 646 didn't jump to line 647, because the condition on line 646 was never true if not destaddr: 

p['FLAGS'] = NM_FLAGS_BROADCAST 

destaddr = self.__broadcastaddr 

 

res = self.send(p, destaddr, timeout) 

answ = NBNodeStatusResponse(res['ANSWERS']) 

self.mac = answ.get_mac() 

return answ.entries 

 

################################################################################ 

# 4.2 SESSION SERVICE PACKETS 

################################################################################ 

 

class NetBIOSSessionPacket: 

def __init__(self, data=0): 

self.type = 0x0 

self.flags = 0x0 

self.length = 0x0 

if data == 0: 

self._trailer = b'' 

else: 

try: 

self.type = indexbytes(data,0) 

if self.type == NETBIOS_SESSION_MESSAGE: 

self.length = indexbytes(data,1) << 16 | (unpack('!H', data[2:4])[0]) 

else: 

self.flags = data[1] 

self.length = unpack('!H', data[2:4])[0] 

 

self._trailer = data[4:] 

except: 

raise NetBIOSError('Wrong packet format ') 

 

def set_type(self, type): 

self.type = type 

 

def get_type(self): 

return self.type 

 

def rawData(self): 

if self.type == NETBIOS_SESSION_MESSAGE: 

data = pack('!BBH', self.type, self.length >> 16, self.length & 0xFFFF) + self._trailer 

else: 

data = pack('!BBH', self.type, self.flags, self.length) + self._trailer 

return data 

 

def set_trailer(self, data): 

self._trailer = data 

self.length = len(data) 

 

def get_length(self): 

return self.length 

 

def get_trailer(self): 

return self._trailer 

 

class NetBIOSSession: 

def __init__(self, myname, remote_name, remote_host, remote_type=TYPE_SERVER, sess_port=NETBIOS_SESSION_PORT, 

timeout=None, local_type=TYPE_WORKSTATION, sock=None): 

""" 

 

:param unicode myname: My local NetBIOS name 

:param unicode remote_name: Remote NetBIOS name 

:param unicode remote_host: Remote IP Address 

:param integer remote_type: NetBIOS Host type 

:param integer sess_port: Session port to connect (139,445) 

:param integer timeout: Timeout for connection 

:param integer local_type: My Local Host Type 

:param socket sock: Socket for already established connection 

""" 

716 ↛ 717line 716 didn't jump to line 717, because the condition on line 716 was never true if len(myname) > 15: 

self.__myname = myname[:15].upper() 

else: 

self.__myname = myname.upper() 

self.__local_type = local_type 

 

assert remote_name 

# if destination port SMB_SESSION_PORT and remote name *SMBSERVER, we're changing it to its IP address 

# helping solving the client mistake ;) 

725 ↛ 726line 725 didn't jump to line 726, because the condition on line 725 was never true if remote_name == '*SMBSERVER' and sess_port == SMB_SESSION_PORT: 

remote_name = remote_host 

 

# If remote name is *SMBSERVER let's try to query its name.. if can't be guessed, continue and hope for the best 

 

730 ↛ 731line 730 didn't jump to line 731, because the condition on line 730 was never true if remote_name == '*SMBSERVER': 

nb = NetBIOS() 

try: 

res = nb.getnetbiosname(remote_host) 

except: 

res = None 

pass 

 

if res is not None: 

remote_name = res 

 

741 ↛ 742line 741 didn't jump to line 742, because the condition on line 741 was never true if len(remote_name) > 15: 

self.__remote_name = remote_name[:15].upper() 

else: 

self.__remote_name = remote_name.upper() 

self.__remote_type = remote_type 

self.__remote_host = remote_host 

 

748 ↛ 750line 748 didn't jump to line 750, because the condition on line 748 was never true if sock is not None: 

# We are acting as a server 

self._sock = sock 

else: 

self._sock = self._setup_connection((remote_host, sess_port), timeout) 

 

if sess_port == NETBIOS_SESSION_PORT: 

self._request_session(remote_type, local_type, timeout) 

 

def _request_session(self, remote_type, local_type, timeout): 

raise NotImplementedError('Not Implemented!') 

 

def _setup_connection(self, peer, timeout=None): 

raise NotImplementedError('Not Implemented!') 

 

def get_myname(self): 

return self.__myname 

 

def get_mytype(self): 

return self.__local_type 

 

def get_remote_host(self): 

return self.__remote_host 

 

def get_remote_name(self): 

return self.__remote_name 

 

def get_remote_type(self): 

return self.__remote_type 

 

def close(self): 

self._sock.close() 

 

def get_socket(self): 

return self._sock 

 

class NetBIOSUDPSessionPacket(Structure): 

TYPE_DIRECT_UNIQUE = 16 

TYPE_DIRECT_GROUP = 17 

 

FLAGS_MORE_FRAGMENTS = 1 

FLAGS_FIRST_FRAGMENT = 2 

FLAGS_B_NODE = 0 

 

structure = ( 

('Type','B=16'), # Direct Unique Datagram 

('Flags','B=2'), # FLAGS_FIRST_FRAGMENT 

('ID','<H'), 

('_SourceIP','>L'), 

('SourceIP','"'), 

('SourcePort','>H=138'), 

('DataLegth','>H-Data'), 

('Offset','>H=0'), 

('SourceName','z'), 

('DestinationName','z'), 

('Data',':'), 

) 

 

def getData(self): 

addr = self['SourceIP'].split('.') 

addr = [int(x) for x in addr] 

addr = (((addr[0] << 8) + addr[1] << 8) + addr[2] << 8) + addr[3] 

self['_SourceIP'] = addr 

return Structure.getData(self) 

 

def get_trailer(self): 

return self['Data'] 

 

class NetBIOSUDPSession(NetBIOSSession): 

def _setup_connection(self, peer, timeout=None): 

af, socktype, proto, canonname, sa = socket.getaddrinfo(peer[0], peer[1], 0, socket.SOCK_DGRAM)[0] 

sock = socket.socket(af, socktype, proto) 

sock.connect(sa) 

 

sock = socket.socket(af, socktype, proto) 

sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 

sock.bind((INADDR_ANY, 138)) 

self.peer = peer 

return sock 

 

def _request_session(self, remote_type, local_type, timeout = None): 

pass 

 

def next_id(self): 

if hasattr(self, '__dgram_id'): 

answer = self.__dgram_id 

else: 

self.__dgram_id = rand.randint(1,65535) 

answer = self.__dgram_id 

self.__dgram_id += 1 

return answer 

 

def send_packet(self, data): 

# Yes... I know... 

self._sock.connect(self.peer) 

 

p = NetBIOSUDPSessionPacket() 

p['ID'] = self.next_id() 

p['SourceIP'] = self._sock.getsockname()[0] 

p['SourceName'] = encode_name(self.get_myname(), self.get_mytype(), '')[:-1] 

p['DestinationName'] = encode_name(self.get_remote_name(), self.get_remote_type(), '')[:-1] 

p['Data'] = data 

 

self._sock.sendto(str(p), self.peer) 

self._sock.close() 

 

self._sock = self._setup_connection(self.peer) 

 

def recv_packet(self, timeout = None): 

# The next loop is a workaround for a bigger problem: 

# When data reaches higher layers, the lower headers are lost, 

# and with them, for example, the source IP. Hence, SMB users 

# can't know where packets are coming from... we need a better 

# solution, right now, we will filter everything except packets 

# coming from the remote_host specified in __init__() 

 

while 1: 

data, peer = self._sock.recvfrom(8192) 

# print "peer: %r self.peer: %r" % (peer, self.peer) 

if peer == self.peer: 

break 

 

return NetBIOSUDPSessionPacket(data) 

 

class NetBIOSTCPSession(NetBIOSSession): 

def __init__(self, myname, remote_name, remote_host, remote_type=TYPE_SERVER, sess_port=NETBIOS_SESSION_PORT, 

timeout=None, local_type=TYPE_WORKSTATION, sock=None, select_poll=False): 

""" 

 

:param unicode myname: My local NetBIOS name 

:param unicode remote_name: Remote NetBIOS name 

:param unicode remote_host: Remote IP Address 

:param integer remote_type: NetBIOS Host type 

:param integer sess_port: Session port to connect (139,445) 

:param integer timeout: Timeout for connection 

:param integer local_type: My Local Host Type 

:param socket sock: Socket for already established connection 

:param boolean select_poll: Type of polling mechanism 

""" 

self.__select_poll = select_poll 

888 ↛ 889line 888 didn't jump to line 889, because the condition on line 888 was never true if self.__select_poll: 

self.read_function = self.polling_read 

else: 

self.read_function = self.non_polling_read 

NetBIOSSession.__init__(self, myname, remote_name, remote_host, remote_type=remote_type, sess_port=sess_port, 

timeout=timeout, local_type=local_type, sock=sock) 

 

def _setup_connection(self, peer, timeout=None): 

try: 

af, socktype, proto, canonname, sa = socket.getaddrinfo(peer[0], peer[1], 0, socket.SOCK_STREAM)[0] 

sock = socket.socket(af, socktype, proto) 

oldtimeout = sock.gettimeout() 

sock.settimeout(timeout) 

sock.connect(sa) 

sock.settimeout(oldtimeout) 

except socket.error as e: 

raise socket.error("Connection error (%s:%s)" % (peer[0], peer[1]), e) 

return sock 

 

def send_packet(self, data): 

p = NetBIOSSessionPacket() 

p.set_type(NETBIOS_SESSION_MESSAGE) 

p.set_trailer(data) 

self._sock.sendall(p.rawData()) 

 

def recv_packet(self, timeout = None): 

data = self.__read(timeout) 

return NetBIOSSessionPacket(data) 

 

def _request_session(self, remote_type, local_type, timeout = None): 

p = NetBIOSSessionPacket() 

remote_name = encode_name(self.get_remote_name(), remote_type, '') 

myname = encode_name(self.get_myname(), local_type, '') 

p.set_type(NETBIOS_SESSION_REQUEST) 

p.set_trailer(remote_name + myname) 

 

self._sock.sendall(p.rawData()) 

while 1: 

p = self.recv_packet(timeout) 

927 ↛ 928line 927 didn't jump to line 928, because the condition on line 927 was never true if p.get_type() == NETBIOS_SESSION_NEGATIVE_RESPONSE: 

raise NetBIOSError('Cannot request session (Called Name:%s)' % self.get_remote_name()) 

929 ↛ 933line 929 didn't jump to line 933, because the condition on line 929 was never false elif p.get_type() == NETBIOS_SESSION_POSITIVE_RESPONSE: 

break 

else: 

# Ignore all other messages, most probably keepalive messages 

pass 

 

def polling_read(self, read_length, timeout): 

data = b'' 

if timeout is None: 

timeout = 3600 

 

time_left = timeout 

CHUNK_TIME = 0.025 

bytes_left = read_length 

 

while bytes_left > 0: 

try: 

ready, _, _ = select.select([self._sock.fileno()], [], [], 0) 

 

if not ready: 

if time_left <= 0: 

raise NetBIOSTimeout 

else: 

time.sleep(CHUNK_TIME) 

time_left -= CHUNK_TIME 

continue 

 

received = self._sock.recv(bytes_left) 

if len(received) == 0: 

raise NetBIOSError('Error while reading from remote', ERRCLASS_OS, None) 

 

data = data + received 

bytes_left = read_length - len(data) 

except select.error as ex: 

if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN: 

raise NetBIOSError('Error occurs while reading from remote', ERRCLASS_OS, ex[0]) 

 

return bytes(data) 

 

def non_polling_read(self, read_length, timeout): 

data = b'' 

970 ↛ 971line 970 didn't jump to line 971, because the condition on line 970 was never true if timeout is None: 

timeout = 3600 

 

start_time = time.time() 

bytes_left = read_length 

 

while bytes_left > 0: 

try: 

ready, _, _ = select.select([self._sock.fileno()], [], [], timeout) 

 

980 ↛ 981line 980 didn't jump to line 981, because the condition on line 980 was never true if not ready or (time.time() - start_time) > timeout: 

raise NetBIOSTimeout 

 

received = self._sock.recv(bytes_left) 

984 ↛ 985line 984 didn't jump to line 985, because the condition on line 984 was never true if len(received) == 0: 

raise NetBIOSError('Error while reading from remote', ERRCLASS_OS, None) 

 

data = data + received 

bytes_left = read_length - len(data) 

except select.error as ex: 

990 ↛ 976,   990 ↛ 9912 missed branches: 1) line 990 didn't jump to line 976, because the condition on line 990 was never false, 2) line 990 didn't jump to line 991, because the condition on line 990 was never true if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN: 

raise NetBIOSError('Error occurs while reading from remote', ERRCLASS_OS, ex[0]) 

 

return bytes(data) 

 

def __read(self, timeout = None): 

data = self.read_function(4, timeout) 

type, flags, length = unpack('>ccH', data) 

if ord(type) == NETBIOS_SESSION_MESSAGE: 

length |= ord(flags) << 16 

else: 

1001 ↛ 1002line 1001 didn't jump to line 1002, because the condition on line 1001 was never true if ord(flags) & 0x01: 

length |= 0x10000 

data2 = self.read_function(length, timeout) 

 

return data + data2