ruby_smb 2.0.4 → 2.0.9

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 (51) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.github/workflows/verify.yml +56 -0
  5. data/README.md +0 -1
  6. data/examples/delete_file.rb +0 -0
  7. data/examples/net_share_enum_all.rb +0 -0
  8. data/examples/pipes.rb +0 -0
  9. data/examples/rename_file.rb +0 -0
  10. data/lib/ruby_smb.rb +3 -2
  11. data/lib/ruby_smb/client.rb +25 -21
  12. data/lib/ruby_smb/client/negotiation.rb +10 -12
  13. data/lib/ruby_smb/compression.rb +7 -0
  14. data/lib/ruby_smb/compression/lznt1.rb +164 -0
  15. data/lib/ruby_smb/dcerpc.rb +3 -1
  16. data/lib/ruby_smb/dcerpc/ndr.rb +97 -0
  17. data/lib/ruby_smb/dcerpc/netlogon.rb +101 -0
  18. data/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_request.rb +37 -0
  19. data/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_response.rb +26 -0
  20. data/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_request.rb +37 -0
  21. data/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_response.rb +23 -0
  22. data/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_request.rb +32 -0
  23. data/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_response.rb +24 -0
  24. data/lib/ruby_smb/dcerpc/request.rb +6 -0
  25. data/lib/ruby_smb/dispatcher/socket.rb +1 -1
  26. data/lib/ruby_smb/smb1/file.rb +1 -1
  27. data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +0 -1
  28. data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +0 -1
  29. data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +1 -2
  30. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +1 -13
  31. data/lib/ruby_smb/smb1/pipe.rb +4 -2
  32. data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +4 -0
  33. data/lib/ruby_smb/smb2/pipe.rb +6 -4
  34. data/lib/ruby_smb/version.rb +1 -1
  35. data/spec/lib/ruby_smb/client_spec.rb +17 -7
  36. data/spec/lib/ruby_smb/compression/lznt1_spec.rb +32 -0
  37. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_request_spec.rb +69 -0
  38. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_response_spec.rb +53 -0
  39. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_request_spec.rb +69 -0
  40. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_response_spec.rb +37 -0
  41. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_request_spec.rb +45 -0
  42. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_response_spec.rb +37 -0
  43. data/spec/lib/ruby_smb/smb1/file_spec.rb +1 -1
  44. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +0 -1
  45. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +0 -1
  46. data/spec/lib/ruby_smb/smb1/packet/trans2/open2_response_spec.rb +0 -5
  47. data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_response_spec.rb +0 -6
  48. data/spec/spec_helper.rb +1 -1
  49. metadata +42 -19
  50. metadata.gz.sig +0 -0
  51. data/.travis.yml +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43f9f98936e870ad767348eb90c66f17eaf5e56c62b7792e13502c860bf8c4f1
4
- data.tar.gz: b57d0bdee72f2fde7ee700771f85cd0f442f3ca5be70b4564b1598df05474860
3
+ metadata.gz: 6c56b48b8782a1622a0d9586244a82a4527868e20519a5ab7339a4b679909288
4
+ data.tar.gz: b5aef9f1775e56661b9248567f5a2d254a7b64e67b7252cc742a66b392ab45a4
5
5
  SHA512:
6
- metadata.gz: '08c1bea7f46add92b4bf6746ec3794a7845a0d0dccb8a8d338d7b3fd07133b670b5509bfd1a71fa10dcc11059bcd01b38a0da664c9fe7b86aa8d58738d0215e8'
7
- data.tar.gz: 69efcb218b522cd233a593ccd281786732f76c717349f44c776a1a7f9790a697b46ccc63b60ab01b31b58ab38487c2d0c3ff3db7c99f55dd7fda469c1ecc5d90
6
+ metadata.gz: 1cf475d6429f701a37557668a1baba47ff241109c99c5a133e3f0c89dbdfc6eda8658952485b8e71eed8a6815541a2d729a093b96230907e2c58f9a63170851d
7
+ data.tar.gz: d5189ce906e257765049e08a6c60d360c5c6bbc389c40d51a3d18c882e68375578cadf74b5b1b892a2b8bee72d3b2ffdc3384259ce0d4213f6d98d076cfcd21e
checksums.yaml.gz.sig CHANGED
Binary file
data.tar.gz.sig CHANGED
Binary file
@@ -0,0 +1,56 @@
1
+ name: Verify
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - '*'
7
+ pull_request:
8
+ branches:
9
+ - '*'
10
+
11
+ jobs:
12
+ test:
13
+ runs-on: ubuntu-16.04
14
+ timeout-minutes: 40
15
+
16
+ strategy:
17
+ fail-fast: true
18
+ matrix:
19
+ ruby:
20
+ - 2.5
21
+ - 2.6
22
+ - 2.7
23
+ test_cmd:
24
+ - bundle exec rspec
25
+
26
+ env:
27
+ RAILS_ENV: test
28
+
29
+ name: Ruby ${{ matrix.ruby }} - ${{ matrix.test_cmd }}
30
+ steps:
31
+ - name: Checkout code
32
+ uses: actions/checkout@v2
33
+
34
+ - uses: actions/setup-ruby@v1
35
+ with:
36
+ ruby-version: ${{ matrix.ruby }}
37
+
38
+ - name: Setup bundler
39
+ run: |
40
+ gem install bundler
41
+ - uses: actions/cache@v2
42
+ with:
43
+ path: vendor/bundle
44
+ key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
45
+ restore-keys: |
46
+ ${{ runner.os }}-gems-
47
+ - name: Bundle install
48
+ run: |
49
+ bundle config path vendor/bundle
50
+ bundle install --jobs 4 --retry 3
51
+ - name: ${{ matrix.test_cmd }}
52
+ run: |
53
+ echo "${CMD}"
54
+ bash -c "${CMD}"
55
+ env:
56
+ CMD: ${{ matrix.test_cmd }}
data/README.md CHANGED
@@ -1,6 +1,5 @@
1
1
  # RubySMB
2
2
 
3
- [![Build Status](https://travis-ci.org/rapid7/ruby_smb.svg?branch=master)](https://travis-ci.org/rapid7/ruby_smb)
4
3
  [![Code Climate](https://codeclimate.com/github/rapid7/ruby_smb.png)](https://codeclimate.com/github/rapid7/ruby_smb)
5
4
  [![Coverage Status](https://coveralls.io/repos/github/rapid7/ruby_smb/badge.svg?branch=master)](https://coveralls.io/github/rapid7/ruby_smb?branch=master)
6
5
 
File without changes
File without changes
data/examples/pipes.rb CHANGED
File without changes
File without changes
data/lib/ruby_smb.rb CHANGED
@@ -8,8 +8,8 @@ require 'windows_error'
8
8
  require 'windows_error/nt_status'
9
9
  # A packet parsing and manipulation library for the SMB1 and SMB2 protocols
10
10
  #
11
- # [[MS-SMB] Server Mesage Block (SMB) Protocol Version 1](https://msdn.microsoft.com/en-us/library/cc246482.aspx)
12
- # [[MS-SMB2] Server Mesage Block (SMB) Protocol Versions 2 and 3](https://msdn.microsoft.com/en-us/library/cc246482.aspx)
11
+ # [[MS-SMB] Server Message Block (SMB) Protocol Version 1](https://msdn.microsoft.com/en-us/library/cc246482.aspx)
12
+ # [[MS-SMB2] Server Message Block (SMB) Protocol Versions 2 and 3](https://msdn.microsoft.com/en-us/library/cc246482.aspx)
13
13
  module RubySMB
14
14
  require 'ruby_smb/error'
15
15
  require 'ruby_smb/dispositions'
@@ -26,4 +26,5 @@ module RubySMB
26
26
  require 'ruby_smb/smb1'
27
27
  require 'ruby_smb/client'
28
28
  require 'ruby_smb/crypto'
29
+ require 'ruby_smb/compression'
29
30
  end
@@ -277,7 +277,8 @@ module RubySMB
277
277
  # @param smb1 [Boolean] whether or not to enable SMB1 support
278
278
  # @param smb2 [Boolean] whether or not to enable SMB2 support
279
279
  # @param smb3 [Boolean] whether or not to enable SMB3 support
280
- def initialize(dispatcher, smb1: true, smb2: true, smb3: true, username:, password:, domain: '.', local_workstation: 'WORKSTATION', always_encrypt: true)
280
+ def initialize(dispatcher, smb1: true, smb2: true, smb3: true, username:, password:, domain: '.',
281
+ local_workstation: 'WORKSTATION', always_encrypt: true, ntlm_flags: default_flags)
281
282
  raise ArgumentError, 'No Dispatcher provided' unless dispatcher.is_a? RubySMB::Dispatcher::Base
282
283
  if smb1 == false && smb2 == false && smb3 == false
283
284
  raise ArgumentError, 'You must enable at least one Protocol'
@@ -296,7 +297,7 @@ module RubySMB
296
297
  @smb3 = smb3
297
298
  @username = username.encode('utf-8') || ''.encode('utf-8')
298
299
  @max_buffer_size = MAX_BUFFER_SIZE
299
- # These sizes will be modifed during negotiation
300
+ # These sizes will be modified during negotiation
300
301
  @server_max_buffer_size = SERVER_MAX_BUFFER_SIZE
301
302
  @server_max_read_size = RubySMB::SMB2::File::MAX_PACKET_SIZE
302
303
  @server_max_write_size = RubySMB::SMB2::File::MAX_PACKET_SIZE
@@ -306,18 +307,12 @@ module RubySMB
306
307
  # SMB 3.x options
307
308
  @session_encrypt_data = always_encrypt
308
309
 
309
- negotiate_version_flag = 0x02000000
310
- flags = Net::NTLM::Client::DEFAULT_FLAGS |
311
- Net::NTLM::FLAGS[:TARGET_INFO] |
312
- negotiate_version_flag ^
313
- Net::NTLM::FLAGS[:OEM]
314
-
315
310
  @ntlm_client = Net::NTLM::Client.new(
316
311
  @username,
317
312
  @password,
318
313
  workstation: @local_workstation,
319
314
  domain: @domain,
320
- flags: flags
315
+ flags: ntlm_flags
321
316
  )
322
317
 
323
318
  @tree_connects = []
@@ -368,31 +363,28 @@ module RubySMB
368
363
 
369
364
  # Performs protocol negotiation and session setup. It defaults to using
370
365
  # the credentials supplied during initialization, but can take a new set of credentials if needed.
371
- def login(username: self.username, password: self.password, domain: self.domain, local_workstation: self.local_workstation)
366
+ def login(username: self.username, password: self.password,
367
+ domain: self.domain, local_workstation: self.local_workstation,
368
+ ntlm_flags: default_flags)
372
369
  negotiate
373
- session_setup(username, password, domain, true,
374
- local_workstation: local_workstation)
370
+ session_setup(username, password, domain,
371
+ local_workstation: local_workstation,
372
+ ntlm_flags: ntlm_flags)
375
373
  end
376
374
 
377
375
  def session_setup(user, pass, domain, do_recv=true,
378
- local_workstation: self.local_workstation)
376
+ local_workstation: self.local_workstation, ntlm_flags: default_flags)
379
377
  @domain = domain
380
378
  @local_workstation = local_workstation
381
379
  @password = pass.encode('utf-8') || ''.encode('utf-8')
382
380
  @username = user.encode('utf-8') || ''.encode('utf-8')
383
381
 
384
- negotiate_version_flag = 0x02000000
385
- flags = Net::NTLM::Client::DEFAULT_FLAGS |
386
- Net::NTLM::FLAGS[:TARGET_INFO] |
387
- negotiate_version_flag ^
388
- Net::NTLM::FLAGS[:OEM]
389
-
390
382
  @ntlm_client = Net::NTLM::Client.new(
391
383
  @username,
392
384
  @password,
393
385
  workstation: @local_workstation,
394
386
  domain: @domain,
395
- flags: flags
387
+ flags: ntlm_flags
396
388
  )
397
389
 
398
390
  authenticate
@@ -431,7 +423,7 @@ module RubySMB
431
423
  end
432
424
 
433
425
  # Sends a packet and receives the raw response through the Dispatcher.
434
- # It will also sign the packet if neccessary.
426
+ # It will also sign the packet if necessary.
435
427
  #
436
428
  # @param packet [RubySMB::GenericPacket] the request to be sent
437
429
  # @param encrypt [Boolean] true if encryption has to be enabled for this transaction
@@ -654,5 +646,17 @@ module RubySMB
654
646
  @preauth_integrity_hash_value + data.to_binary_s
655
647
  )
656
648
  end
649
+
650
+ private
651
+
652
+ def default_flags
653
+ negotiate_version_flag = 0x02000000
654
+ flags = Net::NTLM::Client::DEFAULT_FLAGS |
655
+ Net::NTLM::FLAGS[:TARGET_INFO] |
656
+ negotiate_version_flag ^
657
+ Net::NTLM::FLAGS[:OEM]
658
+
659
+ flags
660
+ end
657
661
  end
658
662
  end
@@ -57,15 +57,20 @@ module RubySMB
57
57
 
58
58
  # Takes the raw response data from the server and tries
59
59
  # parse it into a valid Response packet object.
60
- # This method currently assumes that all SMB1 will use Extended Security.
61
60
  #
62
61
  # @param raw_data [String] the raw binary response from the server
63
62
  # @return [RubySMB::SMB1::Packet::NegotiateResponseExtended] when the response is an SMB1 Extended Security Negotiate Response Packet
63
+ # @return [RubySMB::SMB1::Packet::NegotiateResponse] when the response is an SMB1 Negotiate Response Packet
64
64
  # @return [RubySMB::SMB2::Packet::NegotiateResponse] when the response is an SMB2 Negotiate Response Packet
65
65
  def negotiate_response(raw_data)
66
66
  response = nil
67
67
  if smb1
68
68
  packet = RubySMB::SMB1::Packet::NegotiateResponseExtended.read raw_data
69
+
70
+ unless packet.valid?
71
+ packet = RubySMB::SMB1::Packet::NegotiateResponse.read raw_data
72
+ end
73
+
69
74
  response = packet if packet.valid?
70
75
  end
71
76
  if (smb2 || smb3) && response.nil?
@@ -74,17 +79,10 @@ module RubySMB
74
79
  end
75
80
  if response.nil?
76
81
  if packet.packet_smb_version == 'SMB1'
77
- extended_security = if packet.is_a? RubySMB::SMB1::Packet::NegotiateResponseExtended
78
- packet.parameter_block.capabilities.extended_security
79
- else
80
- "n/a"
81
- end
82
82
  raise RubySMB::Error::InvalidPacket.new(
83
83
  expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
84
- expected_cmd: RubySMB::SMB1::Packet::NegotiateResponseExtended::COMMAND,
85
- expected_custom: "extended_security=1",
86
- packet: packet,
87
- received_custom: "extended_security=#{extended_security}"
84
+ expected_cmd: RubySMB::SMB1::Packet::NegotiateResponse::COMMAND,
85
+ packet: packet
88
86
  )
89
87
  elsif packet.packet_smb_version == 'SMB2'
90
88
  raise RubySMB::Error::InvalidPacket.new(
@@ -275,8 +273,8 @@ module RubySMB
275
273
  context_type: RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
276
274
  )
277
275
  # Adding all possible compression algorithm even if we don't support
278
- # them yet. This will force the server to disclose the support
279
- # algorithms in the repsonse.
276
+ # them yet. This will force the server to disclose the supported
277
+ # algorithms in the response.
280
278
  nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZNT1
281
279
  nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77
282
280
  nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman
@@ -0,0 +1,7 @@
1
+ module RubySMB
2
+ module Compression
3
+
4
+ require 'ruby_smb/compression/lznt1'
5
+
6
+ end
7
+ end
@@ -0,0 +1,164 @@
1
+ module RubySMB
2
+ module Compression
3
+ module LZNT1
4
+ def self.compress(buf, chunk_size: 0x1000)
5
+ out = ''
6
+ until buf.empty?
7
+ chunk = buf[0...chunk_size]
8
+ compressed = compress_chunk(chunk)
9
+ # chunk is compressed
10
+ if compressed.length < chunk.length
11
+ out << [0xb000 | (compressed.length - 1)].pack('v')
12
+ out << compressed
13
+ else
14
+ out << [0x3000 | (chunk.length - 1)].pack('v')
15
+ out << chunk
16
+ end
17
+ buf = buf[chunk_size..-1]
18
+ break if buf.nil?
19
+ end
20
+
21
+ out
22
+ end
23
+
24
+ def self.compress_chunk(chunk)
25
+ blob = chunk
26
+ out = ''
27
+ pow2 = 0x10
28
+ l_mask3 = 0x1002
29
+ o_shift = 12
30
+ until blob.empty?
31
+ bits = 0
32
+ tmp = ''
33
+ i = -1
34
+ loop do
35
+ i += 1
36
+ bits >>= 1
37
+ while pow2 < (chunk.length - blob.length)
38
+ pow2 <<= 1
39
+ l_mask3 = (l_mask3 >> 1) + 1
40
+ o_shift -= 1
41
+ end
42
+
43
+ max_len = [blob.length, l_mask3].min
44
+ offset, length = find(chunk[0...(chunk.length - blob.length)], blob, max_len)
45
+
46
+ # try to find more compressed pattern
47
+ _offset2, length2 = find(chunk[0...chunk.length - blob.length + 1], blob[1..-1], max_len)
48
+
49
+ length = 0 if length < length2
50
+
51
+ if length > 0
52
+ symbol = ((offset - 1) << o_shift) | (length - 3)
53
+ tmp << [symbol].pack('v')
54
+ # set the highest bit
55
+ bits |= 0x80
56
+ blob = blob[length..-1]
57
+ else
58
+ tmp += blob[0]
59
+ blob = blob[1..-1]
60
+ end
61
+
62
+ break if blob.empty? || i == 7
63
+ end
64
+
65
+ out << [bits >> (7 - i)].pack('C')
66
+ out << tmp
67
+ end
68
+
69
+ out
70
+ end
71
+
72
+ def self.decompress(buf, length_check: true)
73
+ out = ''
74
+ until buf.empty?
75
+ header = buf.unpack1('v')
76
+ length = (header & 0xfff) + 1
77
+ raise EncodingError, 'invalid chunk length' if length_check && length > (buf.length - 2)
78
+
79
+ chunk = buf[2...length + 2]
80
+ out << if header & 0x8000 == 0
81
+ chunk
82
+ else
83
+ decompress_chunk(chunk)
84
+ end
85
+ buf = buf[length + 2..-1]
86
+ end
87
+
88
+ out
89
+ end
90
+
91
+ def self.decompress_chunk(chunk)
92
+ out = ''
93
+ until chunk.empty?
94
+ flags = chunk[0].unpack1('C')
95
+ chunk = chunk[1..-1]
96
+ 8.times do |i|
97
+ if (flags >> i & 1) == 0
98
+ out << chunk[0]
99
+ chunk = chunk[1..-1]
100
+ else
101
+ flag = chunk.unpack1('v')
102
+ pos = out.length - 1
103
+ l_mask = 0xfff
104
+ o_shift = 12
105
+ while pos >= 0x10
106
+ l_mask >>= 1
107
+ o_shift -= 1
108
+ pos >>= 1
109
+ end
110
+
111
+ length = (flag & l_mask) + 3
112
+ offset = (flag >> o_shift) + 1
113
+
114
+ if length >= offset
115
+ out_offset = out[-offset..-1]
116
+ tmp = out_offset * (0xfff / out_offset.length + 1)
117
+ out << tmp[0...length]
118
+ else
119
+ out << out[-offset..-offset + length - 1]
120
+ end
121
+ chunk = chunk[2..-1]
122
+ end
123
+ break if chunk.empty?
124
+ end
125
+ end
126
+
127
+ out
128
+ end
129
+
130
+ class << self
131
+ private
132
+
133
+ def find(src, target, max_len)
134
+ result_offset = 0
135
+ result_length = 0
136
+ 1.upto(max_len - 1) do |i|
137
+ offset = src.rindex(target[0...i])
138
+ next if offset.nil?
139
+
140
+ tmp_offset = src.length - offset
141
+ tmp_length = i
142
+ if tmp_offset == tmp_length
143
+ src_offset = src[offset..-1]
144
+ tmp = src_offset * (0xfff / src_offset.length + 1)
145
+ i.upto(max_len) do |j|
146
+ offset = tmp.rindex(target[0...j])
147
+ break if offset.nil?
148
+
149
+ tmp_length = j
150
+ end
151
+ end
152
+
153
+ if tmp_length > result_length
154
+ result_offset = tmp_offset
155
+ result_length = tmp_length
156
+ end
157
+ end
158
+
159
+ result_length < 3 ? [0, 0] : [result_offset, result_length]
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -13,14 +13,16 @@ module RubySMB
13
13
  require 'ruby_smb/dcerpc/rpc_security_attributes'
14
14
  require 'ruby_smb/dcerpc/pdu_header'
15
15
  require 'ruby_smb/dcerpc/srvsvc'
16
- require 'ruby_smb/dcerpc/winreg'
17
16
  require 'ruby_smb/dcerpc/svcctl'
17
+ require 'ruby_smb/dcerpc/winreg'
18
+ require 'ruby_smb/dcerpc/netlogon'
18
19
  require 'ruby_smb/dcerpc/request'
19
20
  require 'ruby_smb/dcerpc/response'
20
21
  require 'ruby_smb/dcerpc/bind'
21
22
  require 'ruby_smb/dcerpc/bind_ack'
22
23
 
23
24
 
25
+
24
26
  # Bind to the remote server interface endpoint.
25
27
  #
26
28
  # @param options [Hash] the options to pass to the Bind request packet. At least, :endpoint must but provided with an existing Dcerpc class