ruby_smb 1.0.5 → 1.1.0

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 (77) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.travis.yml +4 -1
  5. data/README.md +35 -47
  6. data/examples/enum_registry_key.rb +28 -0
  7. data/examples/enum_registry_values.rb +30 -0
  8. data/examples/pipes.rb +2 -1
  9. data/examples/read_registry_key_value.rb +32 -0
  10. data/lib/ruby_smb.rb +0 -1
  11. data/lib/ruby_smb/client.rb +2 -0
  12. data/lib/ruby_smb/client/winreg.rb +46 -0
  13. data/lib/ruby_smb/dcerpc.rb +38 -0
  14. data/lib/ruby_smb/dcerpc/bind.rb +2 -2
  15. data/lib/ruby_smb/dcerpc/bind_ack.rb +2 -2
  16. data/lib/ruby_smb/dcerpc/error.rb +3 -0
  17. data/lib/ruby_smb/dcerpc/ndr.rb +95 -16
  18. data/lib/ruby_smb/dcerpc/pdu_header.rb +1 -1
  19. data/lib/ruby_smb/dcerpc/request.rb +28 -9
  20. data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +35 -0
  21. data/lib/ruby_smb/dcerpc/srvsvc.rb +10 -0
  22. data/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all.rb +9 -0
  23. data/lib/ruby_smb/dcerpc/winreg.rb +340 -0
  24. data/lib/ruby_smb/dcerpc/winreg/close_key_request.rb +24 -0
  25. data/lib/ruby_smb/dcerpc/winreg/close_key_response.rb +27 -0
  26. data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +45 -0
  27. data/lib/ruby_smb/dcerpc/winreg/enum_key_response.rb +42 -0
  28. data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +39 -0
  29. data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +36 -0
  30. data/lib/ruby_smb/dcerpc/winreg/open_key_request.rb +34 -0
  31. data/lib/ruby_smb/dcerpc/winreg/open_key_response.rb +25 -0
  32. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +43 -0
  33. data/lib/ruby_smb/dcerpc/winreg/open_root_key_response.rb +35 -0
  34. data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +27 -0
  35. data/lib/ruby_smb/dcerpc/winreg/query_info_key_response.rb +40 -0
  36. data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +39 -0
  37. data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +57 -0
  38. data/lib/ruby_smb/dcerpc/winreg/regsam.rb +40 -0
  39. data/lib/ruby_smb/smb1/file.rb +2 -0
  40. data/lib/ruby_smb/smb1/pipe.rb +78 -2
  41. data/lib/ruby_smb/smb2/packet/error_packet.rb +2 -4
  42. data/lib/ruby_smb/smb2/pipe.rb +89 -2
  43. data/lib/ruby_smb/version.rb +1 -1
  44. data/ruby_smb.gemspec +3 -3
  45. data/spec/lib/ruby_smb/client_spec.rb +148 -0
  46. data/spec/lib/ruby_smb/dcerpc/bind_ack_spec.rb +2 -2
  47. data/spec/lib/ruby_smb/dcerpc/bind_spec.rb +2 -2
  48. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +410 -0
  49. data/spec/lib/ruby_smb/dcerpc/request_spec.rb +50 -7
  50. data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +98 -0
  51. data/spec/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all_spec.rb +13 -0
  52. data/spec/lib/ruby_smb/dcerpc/srvsvc_spec.rb +60 -0
  53. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_request_spec.rb +28 -0
  54. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_response_spec.rb +36 -0
  55. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +108 -0
  56. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_response_spec.rb +97 -0
  57. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +94 -0
  58. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +82 -0
  59. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_request_spec.rb +74 -0
  60. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_response_spec.rb +35 -0
  61. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +90 -0
  62. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_response_spec.rb +38 -0
  63. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +39 -0
  64. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_response_spec.rb +113 -0
  65. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +88 -0
  66. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +150 -0
  67. data/spec/lib/ruby_smb/dcerpc/winreg/regsam_spec.rb +32 -0
  68. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +710 -0
  69. data/spec/lib/ruby_smb/dcerpc_spec.rb +81 -0
  70. data/spec/lib/ruby_smb/smb1/file_spec.rb +9 -1
  71. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +210 -148
  72. data/spec/lib/ruby_smb/smb2/packet/error_packet_spec.rb +3 -24
  73. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +256 -145
  74. metadata +66 -9
  75. metadata.gz.sig +0 -0
  76. data/lib/ruby_smb/smb1/dcerpc.rb +0 -72
  77. data/lib/ruby_smb/smb2/dcerpc.rb +0 -75
@@ -0,0 +1,57 @@
1
+ module RubySMB
2
+ module Dcerpc
3
+ module Winreg
4
+
5
+ # This class represents a BaseRegQueryValue Response Packet as defined in
6
+ # [3.1.5.17 BaseRegQueryValue (Opnum 17)](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rrp/8bc10aa3-2f91-44e8-aa33-b3263c49ab9d)
7
+ class QueryValueResponse < BinData::Record
8
+ attr_reader :opnum
9
+
10
+ endian :little
11
+
12
+ ndr_lp_dword :lp_type
13
+ ndr_lp_byte :lp_data
14
+ string :pad, length: -> { pad_length }
15
+ ndr_lp_dword :lpcb_data
16
+ ndr_lp_dword :lpcb_len
17
+ uint32 :error_status
18
+
19
+ def initialize_instance
20
+ super
21
+ @opnum = REG_QUERY_VALUE
22
+ end
23
+
24
+ # Determines the correct length for the padding in front of
25
+ # #lpcb_data. It should always force a 4-byte alignment.
26
+ def pad_length
27
+ offset = (lp_data.abs_offset + lp_data.to_binary_s.length) % 4
28
+ (4 - offset) % 4
29
+ end
30
+
31
+ # Returns the data portion of the registry value formatted according to its type:
32
+ # [3.1.1.5 Values](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rrp/3d64dbea-f016-4373-8cac-e43bf343837d)
33
+ def data
34
+ bytes = lp_data.bytes.to_a.pack('C*')
35
+ case lp_type
36
+ when 1,2
37
+ bytes.force_encoding('utf-16le').strip
38
+ when 3
39
+ bytes
40
+ when 4
41
+ bytes.unpack('V').first
42
+ when 5
43
+ bytes.unpack('N').first
44
+ when 7
45
+ str = bytes.force_encoding('utf-16le')
46
+ str.split("\0".encode('utf-16le'))
47
+ when 11
48
+ bytes.unpack('Q<').first
49
+ else
50
+ ""
51
+ end
52
+ end
53
+
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,40 @@
1
+ module RubySMB
2
+ module Dcerpc
3
+ module Winreg
4
+
5
+ # This class represents a REGSAM structure as defined in
6
+ # [2.2.3 REGSAM](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rrp/fefbc801-b141-4bb1-9dcb-bf366da3ae7e)
7
+ # [2.4.3 ACCESS_MASK](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/7a53f60e-e730-4dfe-bbe9-b21b62eb790b)
8
+ class Regsam < BinData::Record
9
+ endian :little
10
+ bit2 :reserved, label: 'Reserved Space'
11
+ bit1 :key_create_link, label: 'Key Create Link'
12
+ bit1 :key_notify, label: 'Key Notify'
13
+ bit1 :key_enumerate_sub_keys, label: 'Key Enumerate Sub Keys'
14
+ bit1 :key_create_sub_key, label: 'Key Create Sub Key'
15
+ bit1 :key_set_value, label: 'Key Set Value'
16
+ bit1 :key_query_value, label: 'Key Query Value'
17
+ # byte boundary
18
+ bit6 :reserved2, label: 'Reserved Space'
19
+ bit1 :key_wow64_32key, label: 'Key Wow64 32key'
20
+ bit1 :key_wow64_64key, label: 'Key Wow64 64key'
21
+ # byte boundary
22
+ bit3 :reserved3, label: 'Reserved Space'
23
+ bit1 :synchronize, label: 'Synchronize'
24
+ bit1 :write_owner, label: 'Write Owner'
25
+ bit1 :write_dac, label: 'Write DAC'
26
+ bit1 :read_control, label: 'Read Control'
27
+ bit1 :delete_access, label: 'Delete'
28
+ # byte boundary
29
+ bit1 :generic_read, label: 'Generic Read'
30
+ bit1 :generic_write, label: 'Generic Write'
31
+ bit1 :generic_execute, label: 'Generic Execute'
32
+ bit1 :generic_all, label: 'Generic All'
33
+ bit2 :reserved4, label: 'Reserved Space'
34
+ bit1 :maximum, label: 'Maximum Allowed'
35
+ bit1 :system_security, label: 'System Security'
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -156,6 +156,8 @@ module RubySMB
156
156
  def read_packet(read_length: 0, offset: 0)
157
157
  read_request = set_header_fields(RubySMB::SMB1::Packet::ReadAndxRequest.new)
158
158
  read_request.parameter_block.max_count_of_bytes_to_return = read_length
159
+ read_request.parameter_block.min_count_of_bytes_to_return = read_length
160
+ read_request.parameter_block.remaining = read_length
159
161
  read_request.parameter_block.offset = offset
160
162
  read_request
161
163
  end
@@ -3,9 +3,9 @@ module RubySMB
3
3
  # Represents a pipe on the Remote server that we can perform
4
4
  # various I/O operations on.
5
5
  class Pipe < File
6
- require 'ruby_smb/smb1/dcerpc'
6
+ require 'ruby_smb/dcerpc'
7
7
 
8
- include RubySMB::SMB1::Dcerpc
8
+ include RubySMB::Dcerpc
9
9
 
10
10
  # Reference: https://msdn.microsoft.com/en-us/library/ee441883.aspx
11
11
  STATUS_DISCONNECTED = 0x0001
@@ -13,6 +13,17 @@ module RubySMB
13
13
  STATUS_OK = 0x0003
14
14
  STATUS_CLOSED = 0x0004
15
15
 
16
+ def initialize(tree:, response:, name:)
17
+ raise ArgumentError, 'No Name Provided' if name.nil?
18
+ case name
19
+ when 'srvsvc'
20
+ extend RubySMB::Dcerpc::Srvsvc
21
+ when 'winreg'
22
+ extend RubySMB::Dcerpc::Winreg
23
+ end
24
+ super(tree: tree, response: response, name: name)
25
+ end
26
+
16
27
  # Performs a peek operation on the named pipe
17
28
  #
18
29
  # @param peek_size [Integer] Amount of data to peek
@@ -68,6 +79,71 @@ module RubySMB
68
79
  state == STATUS_OK
69
80
  end
70
81
 
82
+ # Send a DCERPC request with the provided stub packet.
83
+ #
84
+ # @params stub_packet [#opnum] the stub packet to add to the DCERPC request
85
+ # @return [String] the raw DCERPC response stub
86
+ # @raise [RubySMB::Error::InvalidPacket] if the response is not valid
87
+ # @raise [RubySMB::Error::UnexpectedStatusCode] if the response status code is different than STATUS_SUCCESS or STATUS_BUFFER_OVERFLOW
88
+ def dcerpc_request(stub_packet, options={})
89
+ options.merge!(endpoint: stub_packet.class.name.split('::').at(-2))
90
+ dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum: stub_packet.opnum }, options)
91
+ dcerpc_request.stub.read(stub_packet.to_binary_s)
92
+ trans_nmpipe_request = RubySMB::SMB1::Packet::Trans::TransactNmpipeRequest.new(options)
93
+ @tree.set_header_fields(trans_nmpipe_request)
94
+ trans_nmpipe_request.set_fid(@fid)
95
+ trans_nmpipe_request.data_block.trans_data.write_data = dcerpc_request.to_binary_s
96
+
97
+ trans_nmpipe_raw_response = @tree.client.send_recv(trans_nmpipe_request)
98
+ trans_nmpipe_response = RubySMB::SMB1::Packet::Trans::TransactNmpipeResponse.read(trans_nmpipe_raw_response)
99
+ unless trans_nmpipe_response.valid?
100
+ raise RubySMB::Error::InvalidPacket.new(
101
+ expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
102
+ expected_cmd: RubySMB::SMB1::Packet::Trans::TransactNmpipeResponse::COMMAND,
103
+ received_proto: trans_nmpipe_response.smb_header.protocol,
104
+ received_cmd: trans_nmpipe_response.smb_header.command
105
+ )
106
+ end
107
+ unless [WindowsError::NTStatus::STATUS_SUCCESS,
108
+ WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW].include?(trans_nmpipe_response.status_code)
109
+ raise RubySMB::Error::UnexpectedStatusCode, trans_nmpipe_response.status_code.name
110
+ end
111
+
112
+ raw_data = trans_nmpipe_response.data_block.trans_data.read_data.to_binary_s
113
+ if trans_nmpipe_response.status_code == WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW
114
+ raw_data << read(bytes: @tree.client.max_buffer_size - trans_nmpipe_response.parameter_block.data_count)
115
+ dcerpc_response = dcerpc_response_from_raw_response(raw_data)
116
+ unless dcerpc_response.pdu_header.pfc_flags.first_frag == 1
117
+ raise RubySMB::Dcerpc::Error::InvalidPacket, "Not the first fragment"
118
+ end
119
+ stub_data = dcerpc_response.stub.to_s
120
+
121
+ loop do
122
+ break if dcerpc_response.pdu_header.pfc_flags.last_frag == 1
123
+ raw_data = read(bytes: @tree.client.max_buffer_size)
124
+ dcerpc_response = dcerpc_response_from_raw_response(raw_data)
125
+ stub_data << dcerpc_response.stub.to_s
126
+ end
127
+ stub_data
128
+ else
129
+ dcerpc_response = dcerpc_response_from_raw_response(raw_data)
130
+ dcerpc_response.stub.to_s
131
+ end
132
+ end
133
+
134
+
135
+ private
136
+
137
+ def dcerpc_response_from_raw_response(raw_data)
138
+ dcerpc_response = RubySMB::Dcerpc::Response.read(raw_data)
139
+ unless dcerpc_response.pdu_header.ptype == RubySMB::Dcerpc::PTypes::RESPONSE
140
+ raise RubySMB::Dcerpc::Error::InvalidPacket, "Not a Response packet"
141
+ end
142
+ dcerpc_response
143
+ rescue IOError
144
+ raise RubySMB::Dcerpc::Error::InvalidPacket, "Error reading the DCERPC response"
145
+ end
146
+
71
147
  end
72
148
  end
73
149
  end
@@ -11,10 +11,8 @@ module RubySMB
11
11
  uint16 :structure_size, label: 'Structure Size', initial_value: 9
12
12
  uint8 :error_context_count, label: 'ErrorContextCount'
13
13
  uint8 :reserved
14
- uint32 :byte_count, label: 'Byte Count of ErrorData',
15
- initial_value: -> { error_data.num_bytes == 1 ? 0 : error_data.num_bytes }
16
- string :error_data, label: 'Error Data', initial_value: "\x00",
17
- read_length: -> { byte_count == 0 ? 1 : byte_count }
14
+ uint32 :byte_count, label: 'Byte Count of ErrorData'
15
+ string :error_data, label: 'Error Data', read_length: -> { byte_count }
18
16
 
19
17
  def valid?
20
18
  return smb2_header.protocol == RubySMB::SMB2::SMB2_PROTOCOL_ID &&
@@ -3,13 +3,24 @@ module RubySMB
3
3
  # Represents a pipe on the Remote server that we can perform
4
4
  # various I/O operations on.
5
5
  class Pipe < File
6
- require 'ruby_smb/smb2/dcerpc'
6
+ require 'ruby_smb/dcerpc'
7
7
 
8
- include RubySMB::SMB2::Dcerpc
8
+ include RubySMB::Dcerpc
9
9
 
10
10
  STATUS_CONNECTED = 0x00000003
11
11
  STATUS_CLOSING = 0x00000004
12
12
 
13
+ def initialize(tree:, response:, name:)
14
+ raise ArgumentError, 'No Name Provided' if name.nil?
15
+ case name
16
+ when 'srvsvc'
17
+ extend RubySMB::Dcerpc::Srvsvc
18
+ when 'winreg'
19
+ extend RubySMB::Dcerpc::Winreg
20
+ end
21
+ super(tree: tree, response: response, name: name)
22
+ end
23
+
13
24
  # Performs a peek operation on the named pipe
14
25
  #
15
26
  # @param peek_size [Integer] Amount of data to peek
@@ -67,6 +78,82 @@ module RubySMB
67
78
  state == STATUS_CONNECTED
68
79
  end
69
80
 
81
+ def dcerpc_request(stub_packet, options={})
82
+ options.merge!(endpoint: stub_packet.class.name.split('::').at(-2))
83
+ dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum: stub_packet.opnum }, options)
84
+ dcerpc_request.stub.read(stub_packet.to_binary_s)
85
+ ioctl_send_recv(dcerpc_request, options)
86
+ end
87
+
88
+ def ioctl_send_recv(action, options={})
89
+ request = set_header_fields(RubySMB::SMB2::Packet::IoctlRequest.new(options))
90
+ request.ctl_code = 0x0011C017
91
+ request.flags.is_fsctl = 0x00000001
92
+ request.buffer = action.to_binary_s
93
+
94
+ ioctl_raw_response = @tree.client.send_recv(request)
95
+ ioctl_response = RubySMB::SMB2::Packet::IoctlResponse.read(ioctl_raw_response)
96
+ unless ioctl_response.valid?
97
+ raise RubySMB::Error::InvalidPacket.new(
98
+ expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
99
+ expected_cmd: RubySMB::SMB2::Packet::IoctlRequest::COMMAND,
100
+ received_proto: ioctl_response.smb2_header.protocol,
101
+ received_cmd: ioctl_response.smb2_header.command
102
+ )
103
+ end
104
+ # TODO: improve the handling of STATUS_PENDING responses
105
+ if ioctl_response.status_code == WindowsError::NTStatus::STATUS_PENDING
106
+ sleep 1
107
+ ioctl_raw_response = @tree.client.dispatcher.recv_packet
108
+ ioctl_response = RubySMB::SMB2::Packet::IoctlResponse.read(ioctl_raw_response)
109
+ unless ioctl_response.valid?
110
+ raise RubySMB::Error::InvalidPacket.new(
111
+ expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
112
+ expected_cmd: RubySMB::SMB2::Packet::IoctlRequest::COMMAND,
113
+ received_proto: ioctl_response.smb2_header.protocol,
114
+ received_cmd: ioctl_response.smb2_header.command
115
+ )
116
+ end
117
+ elsif ![WindowsError::NTStatus::STATUS_SUCCESS,
118
+ WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW].include?(ioctl_response.status_code)
119
+ raise RubySMB::Error::UnexpectedStatusCode, ioctl_response.status_code.name
120
+ end
121
+
122
+ raw_data = ioctl_response.output_data
123
+ if ioctl_response.status_code == WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW
124
+ raw_data << read(bytes: @tree.client.max_buffer_size - ioctl_response.output_count)
125
+ dcerpc_response = dcerpc_response_from_raw_response(raw_data)
126
+ unless dcerpc_response.pdu_header.pfc_flags.first_frag == 1
127
+ raise RubySMB::Dcerpc::Error::InvalidPacket, "Not the first fragment"
128
+ end
129
+ stub_data = dcerpc_response.stub.to_s
130
+
131
+ loop do
132
+ break if dcerpc_response.pdu_header.pfc_flags.last_frag == 1
133
+ raw_data = read(bytes: @tree.client.max_buffer_size)
134
+ dcerpc_response = dcerpc_response_from_raw_response(raw_data)
135
+ stub_data << dcerpc_response.stub.to_s
136
+ end
137
+ stub_data
138
+ else
139
+ dcerpc_response = dcerpc_response_from_raw_response(raw_data)
140
+ dcerpc_response.stub.to_s
141
+ end
142
+ end
143
+
144
+
145
+ private
146
+
147
+ def dcerpc_response_from_raw_response(raw_data)
148
+ dcerpc_response = RubySMB::Dcerpc::Response.read(raw_data)
149
+ unless dcerpc_response.pdu_header.ptype == RubySMB::Dcerpc::PTypes::RESPONSE
150
+ raise RubySMB::Dcerpc::Error::InvalidPacket, "Not a Response packet"
151
+ end
152
+ dcerpc_response
153
+ rescue IOError
154
+ raise RubySMB::Dcerpc::Error::InvalidPacket, "Error reading the DCERPC response"
155
+ end
156
+
70
157
  end
71
158
  end
72
159
  end
@@ -1,3 +1,3 @@
1
1
  module RubySMB
2
- VERSION = '1.0.5'.freeze
2
+ VERSION = '1.1.0'.freeze
3
3
  end
@@ -6,8 +6,8 @@ require 'ruby_smb/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = 'ruby_smb'
8
8
  spec.version = RubySMB::VERSION
9
- spec.authors = ['David Maloney', 'James Lee', 'Dev Mohanty', 'Christophe De La Fuente']
10
- spec.email = ['DMaloney@rapid7.com', 'egypt@metasploit.com', 'dev_mohanty@rapid7.com', 'paristvinternet-github@yahoo.com']
9
+ spec.authors = ['Metasploit Hackers', 'David Maloney', 'James Lee', 'Dev Mohanty', 'Christophe De La Fuente']
10
+ spec.email = ['msfdev@metasploit.com']
11
11
  spec.summary = 'A pure Ruby implementation of the SMB Protocol Family'
12
12
  spec.description = ''
13
13
  spec.homepage = 'https://github.com/rapid7/ruby_smb'
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
26
26
  spec.platform = Gem::Platform::RUBY
27
27
  end
28
28
 
29
- spec.required_ruby_version = '>= 2.2.0'
29
+ spec.required_ruby_version = '>= 2.3.0'
30
30
 
31
31
  spec.add_development_dependency 'bundler'
32
32
  spec.add_development_dependency 'fivemat'
@@ -1325,5 +1325,153 @@ RSpec.describe RubySMB::Client do
1325
1325
  end
1326
1326
  end
1327
1327
 
1328
+ context 'Winreg' do
1329
+ describe '#connect_to_winreg' do
1330
+ let(:host) { '1.2.3.4' }
1331
+ let(:share) { "\\\\#{host}\\IPC$" }
1332
+ let(:ipc_tree) { double('IPC$ tree') }
1333
+ let(:named_pipe) { double('Named pipe') }
1334
+ before :example do
1335
+ allow(ipc_tree).to receive_messages(
1336
+ :share => share,
1337
+ :open_file => named_pipe
1338
+ )
1339
+ allow(client).to receive(:tree_connect).and_return(ipc_tree)
1340
+ end
1341
+
1342
+ context 'when the client is already connected to the IPC$ share' do
1343
+ before :example do
1344
+ client.tree_connects << ipc_tree
1345
+ allow(ipc_tree).to receive(:share).and_return(share)
1346
+ end
1347
+
1348
+ it 'does not connect to the already connected tree' do
1349
+ client.connect_to_winreg(host)
1350
+ expect(client).to_not have_received(:tree_connect)
1351
+ end
1352
+ end
1353
+
1354
+ it 'calls #tree_connect' do
1355
+ client.connect_to_winreg(host)
1356
+ expect(client).to have_received(:tree_connect).with(share)
1357
+ end
1358
+
1359
+ it 'open \'winreg\' file on the IPC$ Tree' do
1360
+ client.connect_to_winreg(host)
1361
+ expect(ipc_tree).to have_received(:open_file).with(filename: "winreg", write: true, read: true)
1362
+ end
1363
+
1364
+ it 'returns the expected opened named pipe' do
1365
+ expect(client.connect_to_winreg(host)).to eq(named_pipe)
1366
+ end
1367
+
1368
+ context 'when a block is given' do
1369
+ before :example do
1370
+ allow(named_pipe).to receive(:close)
1371
+ end
1372
+
1373
+ it 'yields the expected named_pipe' do
1374
+ client.connect_to_winreg(host) do |np|
1375
+ expect(np).to eq(named_pipe)
1376
+ end
1377
+ end
1378
+
1379
+ it 'closes the named pipe' do
1380
+ client.connect_to_winreg(host) { |np| }
1381
+ expect(named_pipe).to have_received(:close)
1382
+ end
1383
+
1384
+ it 'returns the block return value' do
1385
+ result = double('Result')
1386
+ expect(client.connect_to_winreg(host) { |np| result }).to eq(result)
1387
+ end
1388
+ end
1389
+ end
1390
+
1391
+ describe '#has_registry_key?' do
1392
+ let(:host) { '1.2.3.4' }
1393
+ let(:key) { 'HKLM\\Registry\\Key' }
1394
+ let(:named_pipe) { double('Named pipe') }
1395
+ let(:result) { double('Result') }
1396
+ before :example do
1397
+ allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
1398
+ allow(named_pipe).to receive(:has_registry_key?).and_return(result)
1399
+ end
1400
+
1401
+ it 'calls #connect_to_winreg to wrap the main logic around' do
1402
+ client.has_registry_key?(host, key)
1403
+ expect(client).to have_received(:connect_to_winreg).with(host)
1404
+ end
1405
+
1406
+ it 'calls Pipe #has_registry_key?' do
1407
+ client.has_registry_key?(host, key)
1408
+ expect(named_pipe).to have_received(:has_registry_key?).with(key)
1409
+ end
1410
+ end
1411
+
1412
+ describe '#read_registry_key_value' do
1413
+ let(:host) { '1.2.3.4' }
1414
+ let(:key) { 'HKLM\\Registry\\Key' }
1415
+ let(:value_name) { 'Value' }
1416
+ let(:named_pipe) { double('Named pipe') }
1417
+ let(:result) { double('Result') }
1418
+ before :example do
1419
+ allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
1420
+ allow(named_pipe).to receive(:read_registry_key_value).and_return(result)
1421
+ end
1422
+
1423
+ it 'calls #connect_to_winreg to wrap the main logic around' do
1424
+ client.read_registry_key_value(host, key, value_name)
1425
+ expect(client).to have_received(:connect_to_winreg).with(host)
1426
+ end
1427
+
1428
+ it 'calls Pipe #read_registry_key_value' do
1429
+ client.read_registry_key_value(host, key, value_name)
1430
+ expect(named_pipe).to have_received(:read_registry_key_value).with(key, value_name)
1431
+ end
1432
+ end
1433
+
1434
+ describe '#enum_registry_key' do
1435
+ let(:host) { '1.2.3.4' }
1436
+ let(:key) { 'HKLM\\Registry\\Key' }
1437
+ let(:named_pipe) { double('Named pipe') }
1438
+ let(:result) { double('Result') }
1439
+ before :example do
1440
+ allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
1441
+ allow(named_pipe).to receive(:enum_registry_key).and_return(result)
1442
+ end
1443
+
1444
+ it 'calls #connect_to_winreg to wrap the main logic around' do
1445
+ client.enum_registry_key(host, key)
1446
+ expect(client).to have_received(:connect_to_winreg).with(host)
1447
+ end
1448
+
1449
+ it 'calls Pipe #enum_registry_key' do
1450
+ client.enum_registry_key(host, key)
1451
+ expect(named_pipe).to have_received(:enum_registry_key).with(key)
1452
+ end
1453
+ end
1454
+
1455
+ describe '#enum_registry_values' do
1456
+ let(:host) { '1.2.3.4' }
1457
+ let(:key) { 'HKLM\\Registry\\Key' }
1458
+ let(:named_pipe) { double('Named pipe') }
1459
+ let(:result) { double('Result') }
1460
+ before :example do
1461
+ allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
1462
+ allow(named_pipe).to receive(:enum_registry_values).and_return(result)
1463
+ end
1464
+
1465
+ it 'calls #connect_to_winreg to wrap the main logic around' do
1466
+ client.enum_registry_values(host, key)
1467
+ expect(client).to have_received(:connect_to_winreg).with(host)
1468
+ end
1469
+
1470
+ it 'calls Pipe #enum_registry_values' do
1471
+ client.enum_registry_values(host, key)
1472
+ expect(named_pipe).to have_received(:enum_registry_values).with(key)
1473
+ end
1474
+ end
1475
+ end
1328
1476
  end
1329
1477