ruby_smb 1.0.5 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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