ruby_smb 3.3.18 → 3.3.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ruby_smb/client/authentication.rb +53 -0
  3. data/lib/ruby_smb/client/negotiation.rb +10 -2
  4. data/lib/ruby_smb/client/tree_connect.rb +8 -1
  5. data/lib/ruby_smb/client.rb +16 -5
  6. data/lib/ruby_smb/rap/net_share_enum.rb +166 -0
  7. data/lib/ruby_smb/rap.rb +10 -0
  8. data/lib/ruby_smb/smb1/commands.rb +1 -0
  9. data/lib/ruby_smb/smb1/packet/negotiate_response.rb +11 -0
  10. data/lib/ruby_smb/smb1/packet/open_andx_request.rb +39 -0
  11. data/lib/ruby_smb/smb1/packet/open_andx_response.rb +40 -0
  12. data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +2 -2
  13. data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +11 -0
  14. data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +53 -13
  15. data/lib/ruby_smb/smb1/packet/trans2/find_information_level/find_info_standard.rb +39 -0
  16. data/lib/ruby_smb/smb1/packet/trans2/find_information_level.rb +1 -0
  17. data/lib/ruby_smb/smb1/packet/trans2/win9x_framing.rb +68 -0
  18. data/lib/ruby_smb/smb1/packet/trans2.rb +1 -0
  19. data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +1 -1
  20. data/lib/ruby_smb/smb1/packet/tree_connect_response.rb +10 -1
  21. data/lib/ruby_smb/smb1/packet.rb +2 -0
  22. data/lib/ruby_smb/smb1/pipe.rb +2 -0
  23. data/lib/ruby_smb/smb1/tree.rb +113 -9
  24. data/lib/ruby_smb/version.rb +1 -1
  25. data/lib/ruby_smb.rb +1 -0
  26. data/spec/lib/ruby_smb/client_spec.rb +2 -1
  27. data/spec/lib/ruby_smb/rap/net_share_enum_spec.rb +185 -0
  28. data/spec/lib/ruby_smb/smb1/packet/trans2/win9x_framing_spec.rb +113 -0
  29. data/spec/lib/ruby_smb/smb1/tree_spec.rb +188 -2
  30. metadata +12 -2
@@ -457,6 +457,129 @@ RSpec.describe RubySMB::SMB1::Tree do
457
457
  end
458
458
  end
459
459
  end
460
+
461
+ context 'with SMB_INFO_STANDARD (LANMAN 2.0 / Win9x)' do
462
+ let(:info_standard) { RubySMB::SMB1::Packet::Trans2::FindInformationLevel::FindInfoStandard }
463
+
464
+ def build_info_standard_entry(name:, size: 0, attrs: 0x20, pad_after: false)
465
+ entry = info_standard.new
466
+ entry.data_size = size
467
+ entry.allocation_size = size
468
+ entry.file_attributes = attrs
469
+ entry.file_name = name
470
+ entry.file_name_length = name.bytesize
471
+ pad_after ? entry.to_binary_s + "\x00" : entry.to_binary_s
472
+ end
473
+
474
+ def build_find_first2_raw(blob, status: 0)
475
+ packet = RubySMB::SMB1::Packet::Trans2::FindFirst2Response.new
476
+ packet.smb_header.nt_status = status
477
+ packet.data_block.trans2_parameters.eos = 1
478
+ packet.data_block.trans2_data.buffer = blob
479
+ packet.to_binary_s
480
+ end
481
+
482
+ before :each do
483
+ # Undo the default FindFirst2Response stubs from the outer #list block
484
+ allow(RubySMB::SMB1::Packet::Trans2::FindFirst2Request).to receive(:new).and_call_original
485
+ allow(RubySMB::SMB1::Packet::Trans2::FindFirst2Response).to receive(:read).and_call_original
486
+ end
487
+
488
+ it 'parses sequential SMB_INFO_STANDARD entries separated by a null pad' do
489
+ # Win9x servers insert a trailing null byte between entries.
490
+ blob = build_info_standard_entry(name: 'foo.txt', size: 100, pad_after: true) +
491
+ build_info_standard_entry(name: 'barbaz', size: 200)
492
+ allow(client).to receive(:send_recv).and_return(build_find_first2_raw(blob))
493
+
494
+ results = tree.list(type: info_standard)
495
+
496
+ expect(results.length).to eq 2
497
+ expect(results[0].file_name).to eq 'foo.txt'
498
+ expect(results[0].data_size).to eq 100
499
+ expect(results[1].file_name).to eq 'barbaz'
500
+ expect(results[1].data_size).to eq 200
501
+ end
502
+
503
+ it 'parses a single SMB_INFO_STANDARD entry without trailing padding' do
504
+ blob = build_info_standard_entry(name: 'only.txt', size: 42)
505
+ allow(client).to receive(:send_recv).and_return(build_find_first2_raw(blob))
506
+
507
+ results = tree.list(type: info_standard)
508
+
509
+ expect(results.length).to eq 1
510
+ expect(results[0].file_name).to eq 'only.txt'
511
+ expect(results[0].data_size).to eq 42
512
+ end
513
+
514
+ it 'stops when an entry has a zero file_name_length' do
515
+ entry = build_info_standard_entry(name: 'first.txt', pad_after: true)
516
+ zero = "\x00" * 23
517
+ blob = entry + zero
518
+ allow(client).to receive(:send_recv).and_return(build_find_first2_raw(blob))
519
+
520
+ results = tree.list(type: info_standard)
521
+
522
+ expect(results.map(&:file_name)).to eq(['first.txt'])
523
+ end
524
+
525
+ it 'raises UnexpectedStatusCode when the SMB status is not success' do
526
+ allow(client).to receive(:send_recv).and_return(
527
+ build_find_first2_raw('', status: WindowsError::NTStatus::STATUS_ACCESS_DENIED.value)
528
+ )
529
+ expect { tree.list(type: info_standard) }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
530
+ end
531
+
532
+ it 'turns unicode off and raises search_count to 255 for SMB_INFO_STANDARD' do
533
+ allow(client).to receive(:send_recv) do |packet|
534
+ expect(packet.smb_header.flags2.unicode).to eq 0
535
+ expect(packet.data_block.trans2_parameters.search_count).to eq 255
536
+ build_find_first2_raw('')
537
+ end
538
+ tree.list(type: info_standard)
539
+ end
540
+
541
+ it 'returns an empty array when the data blob is empty' do
542
+ allow(client).to receive(:send_recv).and_return(build_find_first2_raw(''))
543
+ expect(tree.list(type: info_standard)).to eq([])
544
+ end
545
+
546
+ context 'against a Win9x-era server that omits the trans2 4-byte alignment pad' do
547
+ # Wire layout: word_count=10 (no setup section), parameter_offset=55
548
+ # points right after byte_count (no pad1), data_offset=66 points after
549
+ # trans2_parameters + 1-byte pad2. BinData's Trans2::DataBlock inserts
550
+ # its usual pad1 on read, so trans2_data.buffer arrives (data_count -
551
+ # pad1_length) bytes short and #results would otherwise see 0 entries.
552
+ # The Tree#list workaround detects the mismatch and re-slices the
553
+ # buffer from the server-reported data_offset.
554
+ def build_win9x_find_first2_raw
555
+ data_count = 87
556
+ parameter_offset = 55
557
+ data_offset = 66
558
+ smb_header = "\xffSMB\x32".b + "\x00".b * 4 + "\x98".b + "\x03\x60".b + ("\x00".b * 20)
559
+ param_block = [10, data_count, 0, 10, parameter_offset, 0,
560
+ data_count, data_offset, 0, 0].pack('v*')
561
+ trans2_params = [0x0300, 3, 1, 0, 74].pack('v*')
562
+ entry1 = "\x98\x5c\x38\x70\x98\x5c\x00\x00\x98\x5c\x39\x70" \
563
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x01".b + '.'
564
+ entry2 = "\x98\x5c\x38\x70\x98\x5c\x00\x00\x98\x5c\x39\x70" \
565
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x02".b + '..'
566
+ entry3 = "\x98\x5c\x40\x70\x98\x5c\x00\x00\x98\x5c\x4c\x70" \
567
+ "\x16\x00\x00\x00\x16\x00\x00\x00\x20\x00\x0c".b + 'FLAG.TXT.txt'
568
+ trans2_data = entry1 + "\x00".b + entry2 + "\x00".b + entry3 + "\x00".b
569
+ raise "data_count mismatch" unless trans2_data.bytesize == data_count
570
+ byte_count_value = 10 + 1 + data_count # 0 pad1 + params + 1 pad2 + data
571
+ smb_header + [10].pack('C') + param_block +
572
+ [byte_count_value].pack('v') + trans2_params + "\x00".b + trans2_data
573
+ end
574
+
575
+ it 'parses the entries that BinData would otherwise drop due to missing pad1' do
576
+ allow(client).to receive(:send_recv).and_return(build_win9x_find_first2_raw)
577
+ results = tree.list(type: info_standard)
578
+ expect(results.map { |r| r.file_name.to_s }).to eq(['.', '..', 'FLAG.TXT.txt'])
579
+ expect(results.last.data_size).to eq 22
580
+ end
581
+ end
582
+ end
460
583
  end
461
584
 
462
585
  describe '#set_header_fields' do
@@ -492,8 +615,71 @@ RSpec.describe RubySMB::SMB1::Tree do
492
615
  expect(modified_request.parameter_block.max_parameter_count).to eq 10
493
616
  end
494
617
 
495
- it 'sets #max_data_count to 16,384' do
496
- expect(modified_request.parameter_block.max_data_count).to eq 16_384
618
+ it 'sets #max_data_count to the minimum of 16,384 and server_max_buffer_size' do
619
+ expect(modified_request.parameter_block.max_data_count).to eq(
620
+ [16_384, client.server_max_buffer_size].min
621
+ )
622
+ end
623
+ end
624
+
625
+ describe '#open_file (SMB_COM_OPEN_ANDX fallback)' do
626
+ # Win9x and other LAN-Manager-era servers don't advertise the NT SMBs
627
+ # capability, so #_open dispatches to #_open_andx instead of NT_CREATE_ANDX.
628
+ let(:open_andx_response) do
629
+ packet = RubySMB::SMB1::Packet::OpenAndxResponse.new
630
+ packet.smb_header.nt_status = 0
631
+ packet.parameter_block.fid = 0x4242
632
+ packet.parameter_block.file_data_size = 1234
633
+ packet.parameter_block.resource_type = RubySMB::SMB1::ResourceType::DISK
634
+ packet
635
+ end
636
+
637
+ before :example do
638
+ client.server_supports_nt_smbs = false
639
+ end
640
+
641
+ it 'builds the OPEN_ANDX request without raising NoMethodError on bit-field assignment' do
642
+ allow(client).to receive(:send_recv).and_return(open_andx_response.to_binary_s)
643
+ expect { tree.open_file(filename: 'HELLO.TXT') }.not_to raise_error
644
+ end
645
+
646
+ it 'serializes search_attributes / file_attributes as SMB_FILE_ATTRIBUTES bit-fields' do
647
+ sent = nil
648
+ allow(client).to receive(:send_recv) do |req|
649
+ sent = req
650
+ open_andx_response.to_binary_s
651
+ end
652
+ tree.open_file(filename: 'HELLO.TXT')
653
+
654
+ # 0x0016 = directory | system | hidden in the SMB_FILE_ATTRIBUTES search half.
655
+ expect(sent.parameter_block.search_attributes.directory).to eq 1
656
+ expect(sent.parameter_block.search_attributes.system).to eq 1
657
+ expect(sent.parameter_block.search_attributes.hidden).to eq 1
658
+ expect(sent.parameter_block.search_attributes.to_binary_s).to eq([0x0016].pack('v'))
659
+
660
+ # Read-only open: file_attributes mask is zeroed.
661
+ expect(sent.parameter_block.file_attributes.to_binary_s).to eq([0x0000].pack('v'))
662
+ end
663
+
664
+ it 'sets the SMB_FILE_ATTRIBUTE_ARCHIVE bit when opened for write' do
665
+ allow(client).to receive(:send_recv).and_return(open_andx_response.to_binary_s)
666
+ sent = nil
667
+ allow(client).to receive(:send_recv) do |req|
668
+ sent = req
669
+ open_andx_response.to_binary_s
670
+ end
671
+ tree.open_file(filename: 'HELLO.TXT', write: true)
672
+
673
+ expect(sent.parameter_block.file_attributes.to_binary_s).to eq([0x0020].pack('v'))
674
+ expect(sent.parameter_block.file_attributes.archive).to eq 1
675
+ end
676
+
677
+ it 'returns a File handle whose FID and size come from the OPEN_ANDX response' do
678
+ allow(client).to receive(:send_recv).and_return(open_andx_response.to_binary_s)
679
+ file = tree.open_file(filename: 'HELLO.TXT')
680
+ expect(file).to be_a(RubySMB::SMB1::File)
681
+ expect(file.fid).to eq 0x4242
682
+ expect(file.size).to eq 1234
497
683
  end
498
684
  end
499
685
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_smb
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.18
4
+ version: 3.3.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Metasploit Hackers
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2026-04-22 00:00:00.000000000 Z
16
+ date: 2026-04-27 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: redcarpet
@@ -465,6 +465,8 @@ files:
465
465
  - lib/ruby_smb/ntlm.rb
466
466
  - lib/ruby_smb/ntlm/client.rb
467
467
  - lib/ruby_smb/peer_info.rb
468
+ - lib/ruby_smb/rap.rb
469
+ - lib/ruby_smb/rap/net_share_enum.rb
468
470
  - lib/ruby_smb/server.rb
469
471
  - lib/ruby_smb/server/cli.rb
470
472
  - lib/ruby_smb/server/server_client.rb
@@ -536,6 +538,8 @@ files:
536
538
  - lib/ruby_smb/smb1/packet/nt_trans/request.rb
537
539
  - lib/ruby_smb/smb1/packet/nt_trans/response.rb
538
540
  - lib/ruby_smb/smb1/packet/nt_trans/subcommands.rb
541
+ - lib/ruby_smb/smb1/packet/open_andx_request.rb
542
+ - lib/ruby_smb/smb1/packet/open_andx_response.rb
539
543
  - lib/ruby_smb/smb1/packet/read_andx_request.rb
540
544
  - lib/ruby_smb/smb1/packet/read_andx_response.rb
541
545
  - lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb
@@ -558,6 +562,7 @@ files:
558
562
  - lib/ruby_smb/smb1/packet/trans2/find_information_level.rb
559
563
  - lib/ruby_smb/smb1/packet/trans2/find_information_level/find_file_both_directory_info.rb
560
564
  - lib/ruby_smb/smb1/packet/trans2/find_information_level/find_file_full_directory_info.rb
565
+ - lib/ruby_smb/smb1/packet/trans2/find_information_level/find_info_standard.rb
561
566
  - lib/ruby_smb/smb1/packet/trans2/find_next2_request.rb
562
567
  - lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb
563
568
  - lib/ruby_smb/smb1/packet/trans2/open2_request.rb
@@ -579,6 +584,7 @@ files:
579
584
  - lib/ruby_smb/smb1/packet/trans2/set_file_information_request.rb
580
585
  - lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb
581
586
  - lib/ruby_smb/smb1/packet/trans2/subcommands.rb
587
+ - lib/ruby_smb/smb1/packet/trans2/win9x_framing.rb
582
588
  - lib/ruby_smb/smb1/packet/tree_connect_request.rb
583
589
  - lib/ruby_smb/smb1/packet/tree_connect_response.rb
584
590
  - lib/ruby_smb/smb1/packet/tree_disconnect_request.rb
@@ -836,6 +842,7 @@ files:
836
842
  - spec/lib/ruby_smb/nbss/session_request_spec.rb
837
843
  - spec/lib/ruby_smb/ntlm/client/session_spec.rb
838
844
  - spec/lib/ruby_smb/ntlm/client_spec.rb
845
+ - spec/lib/ruby_smb/rap/net_share_enum_spec.rb
839
846
  - spec/lib/ruby_smb/server/server_client_spec.rb
840
847
  - spec/lib/ruby_smb/server/session_spec.rb
841
848
  - spec/lib/ruby_smb/server/share/provider/disk_spec.rb
@@ -915,6 +922,7 @@ files:
915
922
  - spec/lib/ruby_smb/smb1/packet/trans2/response_spec.rb
916
923
  - spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_request_spec.rb
917
924
  - spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_response_spec.rb
925
+ - spec/lib/ruby_smb/smb1/packet/trans2/win9x_framing_spec.rb
918
926
  - spec/lib/ruby_smb/smb1/packet/tree_connect_request_spec.rb
919
927
  - spec/lib/ruby_smb/smb1/packet/tree_connect_response_spec.rb
920
928
  - spec/lib/ruby_smb/smb1/packet/tree_disconnect_request_spec.rb
@@ -1187,6 +1195,7 @@ test_files:
1187
1195
  - spec/lib/ruby_smb/nbss/session_request_spec.rb
1188
1196
  - spec/lib/ruby_smb/ntlm/client/session_spec.rb
1189
1197
  - spec/lib/ruby_smb/ntlm/client_spec.rb
1198
+ - spec/lib/ruby_smb/rap/net_share_enum_spec.rb
1190
1199
  - spec/lib/ruby_smb/server/server_client_spec.rb
1191
1200
  - spec/lib/ruby_smb/server/session_spec.rb
1192
1201
  - spec/lib/ruby_smb/server/share/provider/disk_spec.rb
@@ -1266,6 +1275,7 @@ test_files:
1266
1275
  - spec/lib/ruby_smb/smb1/packet/trans2/response_spec.rb
1267
1276
  - spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_request_spec.rb
1268
1277
  - spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_response_spec.rb
1278
+ - spec/lib/ruby_smb/smb1/packet/trans2/win9x_framing_spec.rb
1269
1279
  - spec/lib/ruby_smb/smb1/packet/tree_connect_request_spec.rb
1270
1280
  - spec/lib/ruby_smb/smb1/packet/tree_connect_response_spec.rb
1271
1281
  - spec/lib/ruby_smb/smb1/packet/tree_disconnect_request_spec.rb