ruby_smb 1.0.3 → 2.0.1

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 (200) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.travis.yml +3 -2
  5. data/Gemfile +6 -2
  6. data/README.md +35 -47
  7. data/examples/enum_registry_key.rb +28 -0
  8. data/examples/enum_registry_values.rb +30 -0
  9. data/examples/negotiate.rb +51 -8
  10. data/examples/pipes.rb +2 -1
  11. data/examples/read_file_encryption.rb +56 -0
  12. data/examples/read_registry_key_value.rb +32 -0
  13. data/lib/ruby_smb.rb +4 -1
  14. data/lib/ruby_smb/client.rb +233 -22
  15. data/lib/ruby_smb/client/authentication.rb +70 -33
  16. data/lib/ruby_smb/client/echo.rb +20 -2
  17. data/lib/ruby_smb/client/encryption.rb +62 -0
  18. data/lib/ruby_smb/client/negotiation.rb +172 -24
  19. data/lib/ruby_smb/client/signing.rb +19 -0
  20. data/lib/ruby_smb/client/tree_connect.rb +24 -18
  21. data/lib/ruby_smb/client/utils.rb +8 -7
  22. data/lib/ruby_smb/client/winreg.rb +46 -0
  23. data/lib/ruby_smb/crypto.rb +30 -0
  24. data/lib/ruby_smb/dcerpc.rb +38 -0
  25. data/lib/ruby_smb/dcerpc/bind.rb +2 -2
  26. data/lib/ruby_smb/dcerpc/bind_ack.rb +2 -2
  27. data/lib/ruby_smb/dcerpc/error.rb +3 -0
  28. data/lib/ruby_smb/dcerpc/ndr.rb +95 -16
  29. data/lib/ruby_smb/dcerpc/pdu_header.rb +1 -1
  30. data/lib/ruby_smb/dcerpc/request.rb +28 -9
  31. data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +35 -0
  32. data/lib/ruby_smb/dcerpc/srvsvc.rb +10 -0
  33. data/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all.rb +9 -0
  34. data/lib/ruby_smb/dcerpc/winreg.rb +340 -0
  35. data/lib/ruby_smb/dcerpc/winreg/close_key_request.rb +24 -0
  36. data/lib/ruby_smb/dcerpc/winreg/close_key_response.rb +27 -0
  37. data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +45 -0
  38. data/lib/ruby_smb/dcerpc/winreg/enum_key_response.rb +42 -0
  39. data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +39 -0
  40. data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +36 -0
  41. data/lib/ruby_smb/dcerpc/winreg/open_key_request.rb +34 -0
  42. data/lib/ruby_smb/dcerpc/winreg/open_key_response.rb +25 -0
  43. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +43 -0
  44. data/lib/ruby_smb/dcerpc/winreg/open_root_key_response.rb +35 -0
  45. data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +27 -0
  46. data/lib/ruby_smb/dcerpc/winreg/query_info_key_response.rb +40 -0
  47. data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +39 -0
  48. data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +57 -0
  49. data/lib/ruby_smb/dcerpc/winreg/regsam.rb +40 -0
  50. data/lib/ruby_smb/dispatcher/socket.rb +4 -3
  51. data/lib/ruby_smb/error.rb +68 -2
  52. data/lib/ruby_smb/generic_packet.rb +33 -4
  53. data/lib/ruby_smb/smb1/commands.rb +1 -1
  54. data/lib/ruby_smb/smb1/file.rb +66 -15
  55. data/lib/ruby_smb/smb1/packet/close_request.rb +2 -5
  56. data/lib/ruby_smb/smb1/packet/close_response.rb +2 -1
  57. data/lib/ruby_smb/smb1/packet/echo_request.rb +2 -4
  58. data/lib/ruby_smb/smb1/packet/echo_response.rb +2 -1
  59. data/lib/ruby_smb/smb1/packet/empty_packet.rb +10 -1
  60. data/lib/ruby_smb/smb1/packet/logoff_request.rb +2 -4
  61. data/lib/ruby_smb/smb1/packet/logoff_response.rb +2 -1
  62. data/lib/ruby_smb/smb1/packet/negotiate_request.rb +2 -5
  63. data/lib/ruby_smb/smb1/packet/negotiate_response.rb +3 -7
  64. data/lib/ruby_smb/smb1/packet/negotiate_response_extended.rb +4 -4
  65. data/lib/ruby_smb/smb1/packet/nt_create_andx_request.rb +2 -4
  66. data/lib/ruby_smb/smb1/packet/nt_create_andx_response.rb +2 -1
  67. data/lib/ruby_smb/smb1/packet/nt_trans/create_request.rb +2 -1
  68. data/lib/ruby_smb/smb1/packet/nt_trans/create_response.rb +2 -1
  69. data/lib/ruby_smb/smb1/packet/nt_trans/request.rb +2 -4
  70. data/lib/ruby_smb/smb1/packet/nt_trans/response.rb +2 -1
  71. data/lib/ruby_smb/smb1/packet/read_andx_request.rb +2 -5
  72. data/lib/ruby_smb/smb1/packet/read_andx_response.rb +2 -1
  73. data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +2 -1
  74. data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +3 -2
  75. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +2 -5
  76. data/lib/ruby_smb/smb1/packet/session_setup_response.rb +3 -2
  77. data/lib/ruby_smb/smb1/packet/trans/peek_nmpipe_request.rb +0 -1
  78. data/lib/ruby_smb/smb1/packet/trans/peek_nmpipe_response.rb +3 -2
  79. data/lib/ruby_smb/smb1/packet/trans/request.rb +2 -5
  80. data/lib/ruby_smb/smb1/packet/trans/response.rb +2 -1
  81. data/lib/ruby_smb/smb1/packet/trans/transact_nmpipe_request.rb +1 -1
  82. data/lib/ruby_smb/smb1/packet/trans/transact_nmpipe_response.rb +1 -1
  83. data/lib/ruby_smb/smb1/packet/trans2/find_first2_request.rb +2 -1
  84. data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +8 -2
  85. data/lib/ruby_smb/smb1/packet/trans2/find_next2_request.rb +2 -1
  86. data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +8 -2
  87. data/lib/ruby_smb/smb1/packet/trans2/open2_request.rb +2 -1
  88. data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +2 -1
  89. data/lib/ruby_smb/smb1/packet/trans2/request.rb +2 -4
  90. data/lib/ruby_smb/smb1/packet/trans2/request_secondary.rb +2 -4
  91. data/lib/ruby_smb/smb1/packet/trans2/response.rb +2 -1
  92. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_request.rb +2 -1
  93. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +2 -1
  94. data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +2 -4
  95. data/lib/ruby_smb/smb1/packet/tree_connect_response.rb +13 -3
  96. data/lib/ruby_smb/smb1/packet/tree_disconnect_request.rb +2 -4
  97. data/lib/ruby_smb/smb1/packet/tree_disconnect_response.rb +2 -1
  98. data/lib/ruby_smb/smb1/packet/write_andx_request.rb +3 -6
  99. data/lib/ruby_smb/smb1/packet/write_andx_response.rb +2 -1
  100. data/lib/ruby_smb/smb1/pipe.rb +87 -6
  101. data/lib/ruby_smb/smb1/tree.rb +50 -3
  102. data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
  103. data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
  104. data/lib/ruby_smb/smb2/file.rb +103 -25
  105. data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
  106. data/lib/ruby_smb/smb2/packet.rb +2 -0
  107. data/lib/ruby_smb/smb2/packet/close_request.rb +2 -4
  108. data/lib/ruby_smb/smb2/packet/close_response.rb +2 -1
  109. data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
  110. data/lib/ruby_smb/smb2/packet/create_request.rb +2 -4
  111. data/lib/ruby_smb/smb2/packet/create_response.rb +2 -1
  112. data/lib/ruby_smb/smb2/packet/echo_request.rb +2 -4
  113. data/lib/ruby_smb/smb2/packet/echo_response.rb +2 -1
  114. data/lib/ruby_smb/smb2/packet/error_packet.rb +15 -3
  115. data/lib/ruby_smb/smb2/packet/ioctl_request.rb +2 -5
  116. data/lib/ruby_smb/smb2/packet/ioctl_response.rb +2 -1
  117. data/lib/ruby_smb/smb2/packet/logoff_request.rb +2 -4
  118. data/lib/ruby_smb/smb2/packet/logoff_response.rb +2 -1
  119. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -17
  120. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +52 -5
  121. data/lib/ruby_smb/smb2/packet/query_directory_request.rb +2 -4
  122. data/lib/ruby_smb/smb2/packet/query_directory_response.rb +8 -2
  123. data/lib/ruby_smb/smb2/packet/read_request.rb +2 -4
  124. data/lib/ruby_smb/smb2/packet/read_response.rb +2 -1
  125. data/lib/ruby_smb/smb2/packet/session_setup_request.rb +2 -5
  126. data/lib/ruby_smb/smb2/packet/session_setup_response.rb +2 -1
  127. data/lib/ruby_smb/smb2/packet/set_info_request.rb +2 -4
  128. data/lib/ruby_smb/smb2/packet/set_info_response.rb +2 -1
  129. data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
  130. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +93 -10
  131. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +10 -22
  132. data/lib/ruby_smb/smb2/packet/tree_disconnect_request.rb +2 -4
  133. data/lib/ruby_smb/smb2/packet/tree_disconnect_response.rb +2 -1
  134. data/lib/ruby_smb/smb2/packet/write_request.rb +2 -4
  135. data/lib/ruby_smb/smb2/packet/write_response.rb +2 -1
  136. data/lib/ruby_smb/smb2/pipe.rb +86 -12
  137. data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
  138. data/lib/ruby_smb/smb2/tree.rb +65 -21
  139. data/lib/ruby_smb/version.rb +1 -1
  140. data/ruby_smb.gemspec +5 -3
  141. data/spec/lib/ruby_smb/client_spec.rb +1612 -108
  142. data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
  143. data/spec/lib/ruby_smb/dcerpc/bind_ack_spec.rb +2 -2
  144. data/spec/lib/ruby_smb/dcerpc/bind_spec.rb +2 -2
  145. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +410 -0
  146. data/spec/lib/ruby_smb/dcerpc/request_spec.rb +50 -7
  147. data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +98 -0
  148. data/spec/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all_spec.rb +13 -0
  149. data/spec/lib/ruby_smb/dcerpc/srvsvc_spec.rb +60 -0
  150. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_request_spec.rb +28 -0
  151. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_response_spec.rb +36 -0
  152. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +108 -0
  153. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_response_spec.rb +97 -0
  154. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +94 -0
  155. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +82 -0
  156. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_request_spec.rb +74 -0
  157. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_response_spec.rb +35 -0
  158. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +90 -0
  159. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_response_spec.rb +38 -0
  160. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +39 -0
  161. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_response_spec.rb +113 -0
  162. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +88 -0
  163. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +150 -0
  164. data/spec/lib/ruby_smb/dcerpc/winreg/regsam_spec.rb +32 -0
  165. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +710 -0
  166. data/spec/lib/ruby_smb/dcerpc_spec.rb +81 -0
  167. data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +2 -2
  168. data/spec/lib/ruby_smb/error_spec.rb +59 -0
  169. data/spec/lib/ruby_smb/generic_packet_spec.rb +52 -4
  170. data/spec/lib/ruby_smb/smb1/file_spec.rb +191 -2
  171. data/spec/lib/ruby_smb/smb1/packet/empty_packet_spec.rb +68 -0
  172. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
  173. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
  174. data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
  175. data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
  176. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +11 -2
  177. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +11 -2
  178. data/spec/lib/ruby_smb/smb1/packet/tree_connect_response_spec.rb +40 -0
  179. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +272 -149
  180. data/spec/lib/ruby_smb/smb1/tree_spec.rb +44 -7
  181. data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
  182. data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
  183. data/spec/lib/ruby_smb/smb2/file_spec.rb +323 -6
  184. data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
  185. data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
  186. data/spec/lib/ruby_smb/smb2/packet/error_packet_spec.rb +78 -0
  187. data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
  188. data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
  189. data/spec/lib/ruby_smb/smb2/packet/query_directory_response_spec.rb +8 -0
  190. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
  191. data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
  192. data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -22
  193. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +286 -149
  194. data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
  195. data/spec/lib/ruby_smb/smb2/tree_spec.rb +261 -2
  196. metadata +191 -83
  197. metadata.gz.sig +0 -0
  198. data/lib/ruby_smb/smb1/dcerpc.rb +0 -67
  199. data/lib/ruby_smb/smb2/dcerpc.rb +0 -70
  200. data/spec/lib/ruby_smb/smb1/packet/error_packet_spec.rb +0 -37
@@ -9,6 +9,7 @@ RSpec.describe RubySMB::SMB2::Packet::TreeConnectRequest do
9
9
  it { is_expected.to respond_to :path_offset }
10
10
  it { is_expected.to respond_to :path_length }
11
11
  it { is_expected.to respond_to :path }
12
+ it { is_expected.to respond_to :tree_connect_request_extension }
12
13
 
13
14
  it 'is little endian' do
14
15
  expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
@@ -30,18 +31,347 @@ RSpec.describe RubySMB::SMB2::Packet::TreeConnectRequest do
30
31
  end
31
32
  end
32
33
 
33
- describe '#encode_path' do
34
- let(:path) { '\\192.168.1.1\\example' }
35
- let(:encoded_path) { path.encode('utf-16le').force_encoding('binary') }
34
+ describe '#structure_size' do
35
+ it 'should be a 16-bit unsigned integer' do
36
+ expect(packet.structure_size).to be_a BinData::Uint16le
37
+ end
38
+
39
+ it 'should have a default value of 9 as per the SMB2 spec' do
40
+ expect(packet.structure_size).to eq 9
41
+ end
42
+ end
43
+
44
+ describe '#flags' do
45
+ it 'should be a 16-bit unsigned integer' do
46
+ expect(packet.flags).to be_a BinData::Uint16le
47
+ end
48
+ end
49
+
50
+ describe '#path_offset' do
51
+ it 'should be a 16-bit unsigned integer' do
52
+ expect(packet.path_offset).to be_a BinData::Uint16le
53
+ end
54
+
55
+ context 'when flags is set to SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT' do
56
+ it 'should be set to the offset, in bytes, of the full share path name from the beginning of the packet header' do
57
+ packet.flags = described_class::SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT
58
+ expect(packet.path_offset).to eq(88)
59
+ end
60
+ end
61
+
62
+ context 'when flags is not set to SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT' do
63
+ it 'should be set to the offset, in bytes, of the full share path name from the beginning of the packet header' do
64
+ expect(packet.path_offset).to eq(72)
65
+ end
66
+ end
67
+ end
68
+
69
+ describe '#path_length' do
70
+ let(:path) { '\\\\server\\path' }
71
+
72
+ it 'should be a 16-bit unsigned integer' do
73
+ expect(packet.path_length).to be_a BinData::Uint16le
74
+ end
75
+
76
+ context 'when flags is set to SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT' do
77
+ it 'should be the length of the full share path name (unicode) in bytes' do
78
+ packet.flags = described_class::SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT
79
+ packet.tree_connect_request_extension.path = path
80
+ expect(packet.path_length).to eq(path.length * 2)
81
+ end
82
+ end
83
+
84
+ context 'when flags is not set to SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT' do
85
+ it 'should be the length of the full share path name (unicode) in bytes' do
86
+ packet.path = path
87
+ expect(packet.path_length).to eq(path.length * 2)
88
+ end
89
+ end
90
+ end
91
+
92
+ describe '#path' do
93
+ it 'should be a unicode string' do
94
+ expect(packet.path).to be_a RubySMB::Field::String16
95
+ end
96
+
97
+ it 'exists if #flags is not set to SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT' do
98
+ packet.flags = described_class::SMB2_TREE_CONNECT_FLAG_REDIRECT_TO_OWNER
99
+ expect(packet.path?).to be true
100
+ end
101
+ end
102
+
103
+ describe '#tree_connect_request_extension' do
104
+ it 'is a TreeConnectRequestExtension structure' do
105
+ expect(packet.tree_connect_request_extension).to be_a RubySMB::SMB2::Packet::TreeConnectRequestExtension
106
+ end
107
+
108
+ it 'exists if #flags is set to SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT' do
109
+ packet.flags = described_class::SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT
110
+ expect(packet.tree_connect_request_extension?).to be true
111
+ end
112
+ end
113
+
114
+ it 'reads binary data as expected' do
115
+ data = described_class.new
116
+ expect(described_class.read(data.to_binary_s)).to eq(data)
117
+ data = described_class.new(flags: described_class::SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT)
118
+ data.tree_connect_request_extension.tree_connect_contexts << RubySMB::SMB2::Packet::TreeConnectContext.new(context_type: 1)
119
+ expect(described_class.read(data.to_binary_s)).to eq(data)
120
+ end
121
+ end
122
+
123
+ RSpec.describe RubySMB::SMB2::Packet::TreeConnectRequestExtension do
124
+ subject(:packet) { described_class.new }
125
+
126
+ it { is_expected.to respond_to :tree_connect_context_offset }
127
+ it { is_expected.to respond_to :tree_connect_context_count }
128
+ it { is_expected.to respond_to :reserved }
129
+ it { is_expected.to respond_to :path }
130
+ it { is_expected.to respond_to :tree_connect_contexts }
131
+
132
+ it 'is little endian' do
133
+ expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
134
+ end
135
+
136
+ describe '#tree_connect_context_offset' do
137
+ it 'is a 32-bit unsigned integer' do
138
+ expect(packet.tree_connect_context_offset).to be_a BinData::Uint32le
139
+ end
140
+
141
+ it 'is the offset from the start of the SMB2 TREE_CONNECT request of an array of tree connect contexts' do
142
+ tc = RubySMB::SMB2::Packet::TreeConnectRequest.new(
143
+ flags: RubySMB::SMB2::Packet::TreeConnectRequest::SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT
144
+ )
145
+ expect(tc.tree_connect_request_extension.tree_connect_context_offset).to eq(16)
146
+ end
147
+ end
148
+
149
+ describe '#tree_connect_context_count' do
150
+ it 'should be a 16-bit unsigned integer' do
151
+ expect(packet.tree_connect_context_count).to be_a BinData::Uint16le
152
+ end
153
+
154
+ it 'should be the #tree_connect_contexts size' do
155
+ packet.tree_connect_contexts << RubySMB::SMB2::Packet::TreeConnectContext.new(context_type: 1)
156
+ packet.tree_connect_contexts << RubySMB::SMB2::Packet::TreeConnectContext.new(context_type: 1)
157
+ expect(packet.tree_connect_context_count).to eq(2)
158
+ end
159
+ end
160
+
161
+ describe '#reserved' do
162
+ it 'should be a binary string' do
163
+ expect(packet.reserved).to be_a BinData::String
164
+ end
165
+
166
+ it 'is 10-bytes long' do
167
+ expect(packet.reserved.length).to eq(10)
168
+ end
169
+ end
170
+
171
+ describe '#path' do
172
+ it 'should be a unicode string' do
173
+ expect(packet.path).to be_a RubySMB::Field::String16
174
+ end
175
+ end
176
+
177
+ describe '#tree_connect_contexts' do
178
+ it 'is an Array field' do
179
+ expect(packet.tree_connect_contexts).to be_a BinData::Array
180
+ end
181
+
182
+ it 'has #tree_connect_context_count elements' do
183
+ packet.tree_connect_context_count = 3
184
+ expect(packet.tree_connect_contexts.size).to eq(3)
185
+ end
186
+ end
187
+
188
+ it 'reads binary data as expected' do
189
+ data = described_class.new
190
+ expect(described_class.read(data.to_binary_s)).to eq(data)
191
+ data.tree_connect_contexts << RubySMB::SMB2::Packet::TreeConnectContext.new(context_type: 1)
192
+ data.tree_connect_contexts << RubySMB::SMB2::Packet::TreeConnectContext.new(context_type: 1)
193
+ expect(described_class.read(data.to_binary_s)).to eq(data)
194
+ end
195
+ end
196
+
197
+ RSpec.describe RubySMB::SMB2::Packet::TreeConnectContext do
198
+ subject(:packet) do
199
+ described_class.new(
200
+ context_type: described_class::SMB2_REMOTED_IDENTITY_TREE_CONNECT_CONTEXT_ID
201
+ )
202
+ end
203
+
204
+ it { is_expected.to respond_to :context_type }
205
+ it { is_expected.to respond_to :data_length }
206
+ it { is_expected.to respond_to :reserved }
207
+ it { is_expected.to respond_to :data }
208
+
209
+ it 'is little endian' do
210
+ expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
211
+ end
212
+
213
+ describe '#context_type' do
214
+ it 'should be a 16-bit unsigned integer' do
215
+ expect(packet.context_type).to be_a BinData::Uint16le
216
+ end
217
+ end
218
+
219
+ describe '#data_length' do
220
+ it 'should be a 16-bit unsigned integer' do
221
+ expect(packet.data_length).to be_a BinData::Uint16le
222
+ end
223
+
224
+ it 'is the length, in bytes, of the Data field' do
225
+ expect(packet.data_length).to eq(packet.data.to_binary_s.size)
226
+ end
227
+ end
228
+
229
+ describe '#reserved' do
230
+ it 'is a 32-bit unsigned integer' do
231
+ expect(packet.reserved).to be_a BinData::Uint32le
232
+ end
233
+ end
234
+
235
+ describe '#data' do
236
+ it 'is a BinData Choice' do
237
+ expect(packet.data).to be_a BinData::Choice
238
+ end
239
+
240
+ it 'contains the structure defined by #context_type' do
241
+ expect(packet.data).to eq(RubySMB::SMB2::Packet::RemotedIdentityTreeConnectContext.new)
242
+ end
243
+ end
244
+
245
+ it 'reads binary data as expected' do
246
+ data = described_class.new(context_type: 1)
247
+ expect(described_class.read(data.to_binary_s)).to eq(data)
248
+ end
249
+ end
250
+
251
+ RSpec.describe RubySMB::SMB2::Packet::RemotedIdentityTreeConnectContext do
252
+ subject(:packet) { described_class.new }
253
+
254
+ it { is_expected.to respond_to :ticket_type }
255
+ it { is_expected.to respond_to :ticket_size }
256
+ it { is_expected.to respond_to :user }
257
+ it { is_expected.to respond_to :user_name }
258
+ it { is_expected.to respond_to :domain }
259
+ it { is_expected.to respond_to :groups }
260
+ it { is_expected.to respond_to :restricted_groups }
261
+ it { is_expected.to respond_to :privileges }
262
+ it { is_expected.to respond_to :primary_group }
263
+ it { is_expected.to respond_to :owner }
264
+ it { is_expected.to respond_to :default_dacl }
265
+ it { is_expected.to respond_to :device_groups }
266
+ it { is_expected.to respond_to :user_claims }
267
+ it { is_expected.to respond_to :device_claims }
268
+ it { is_expected.to respond_to :ticket_info }
269
+
270
+ it 'is little endian' do
271
+ expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
272
+ end
273
+
274
+ describe '#ticket_type' do
275
+ it 'should be a 16-bit unsigned integer' do
276
+ expect(packet.ticket_type).to be_a BinData::Uint16le
277
+ end
278
+
279
+ it 'should be 1' do
280
+ expect(packet.ticket_type).to eq(1)
281
+ end
282
+ end
283
+
284
+ describe '#ticket_size' do
285
+ it 'should be a 16-bit unsigned integer' do
286
+ expect(packet.ticket_size).to be_a BinData::Uint16le
287
+ end
288
+
289
+ it 'is the total size of this structure' do
290
+ packet.ticket_info = 'Ticket Info'
291
+ expect(packet.ticket_size).to eq(packet.num_bytes)
292
+ end
293
+ end
294
+
295
+ describe '#user' do
296
+ it 'should be a 16-bit unsigned integer' do
297
+ expect(packet.user).to be_a BinData::Uint16le
298
+ end
299
+ end
300
+
301
+ describe '#user_name' do
302
+ it 'should be a 16-bit unsigned integer' do
303
+ expect(packet.user_name).to be_a BinData::Uint16le
304
+ end
305
+ end
36
306
 
37
- it 'sets the path string to a UTF-16LE version of the supplied string' do
38
- packet.encode_path(path)
39
- expect(packet.path).to eq encoded_path
307
+ describe '#domain' do
308
+ it 'should be a 16-bit unsigned integer' do
309
+ expect(packet.domain).to be_a BinData::Uint16le
40
310
  end
311
+ end
41
312
 
42
- it 'updates the #path_length' do
43
- packet.encode_path(path)
44
- expect(packet.path_length).to eq encoded_path.length
313
+ describe '#groups' do
314
+ it 'should be a 16-bit unsigned integer' do
315
+ expect(packet.groups).to be_a BinData::Uint16le
45
316
  end
46
317
  end
318
+
319
+ describe '#restricted_groups' do
320
+ it 'should be a 16-bit unsigned integer' do
321
+ expect(packet.restricted_groups).to be_a BinData::Uint16le
322
+ end
323
+ end
324
+
325
+ describe '#privileges' do
326
+ it 'should be a 16-bit unsigned integer' do
327
+ expect(packet.privileges).to be_a BinData::Uint16le
328
+ end
329
+ end
330
+
331
+ describe '#primary_group' do
332
+ it 'should be a 16-bit unsigned integer' do
333
+ expect(packet.primary_group).to be_a BinData::Uint16le
334
+ end
335
+ end
336
+
337
+ describe '#owner' do
338
+ it 'should be a 16-bit unsigned integer' do
339
+ expect(packet.owner).to be_a BinData::Uint16le
340
+ end
341
+ end
342
+
343
+ describe '#default_dacl' do
344
+ it 'should be a 16-bit unsigned integer' do
345
+ expect(packet.default_dacl).to be_a BinData::Uint16le
346
+ end
347
+ end
348
+
349
+ describe '#device_groups' do
350
+ it 'should be a 16-bit unsigned integer' do
351
+ expect(packet.device_groups).to be_a BinData::Uint16le
352
+ end
353
+ end
354
+
355
+ describe '#user_claims' do
356
+ it 'should be a 16-bit unsigned integer' do
357
+ expect(packet.user_claims).to be_a BinData::Uint16le
358
+ end
359
+ end
360
+
361
+ describe '#device_claims' do
362
+ it 'should be a 16-bit unsigned integer' do
363
+ expect(packet.device_claims).to be_a BinData::Uint16le
364
+ end
365
+ end
366
+
367
+ describe '#ticket_info' do
368
+ it 'should be string' do
369
+ expect(packet.ticket_info).to be_a BinData::String
370
+ end
371
+ end
372
+
373
+ it 'reads binary data as expected' do
374
+ data = described_class.new(ticket_info: 'ticket info')
375
+ expect(described_class.read(data.to_binary_s)).to eq(data)
376
+ end
47
377
  end
@@ -30,27 +30,8 @@ RSpec.describe RubySMB::SMB2::Packet::TreeConnectResponse do
30
30
  end
31
31
  end
32
32
 
33
- describe '#is_directory?' do
34
- it 'returns true if #share_type is 0x01' do
35
- packet.share_type = 0x01
36
- expect(packet.is_directory?).to be true
37
- end
38
-
39
- it 'returns false if #share_type is not 0x01' do
40
- packet.share_type = 0x02
41
- expect(packet.is_directory?).to be false
42
- end
43
- end
44
-
45
- describe '#access_rights' do
46
- it 'is a DirectoryAccessMask if the Tree is a directory' do
47
- allow(packet).to receive(:is_directory?).and_return(true)
48
- expect(packet.access_rights).to be_a RubySMB::SMB2::BitField::DirectoryAccessMask
49
- end
50
-
51
- it 'is a FileAccessMask if the Tree is not a directory' do
52
- allow(packet).to receive(:is_directory?).and_return(false)
53
- expect(packet.access_rights).to be_a RubySMB::SMB2::BitField::FileAccessMask
54
- end
33
+ it 'reads binary data as expected' do
34
+ data = described_class.new
35
+ expect(described_class.read(data.to_binary_s)).to eq(data)
55
36
  end
56
37
  end
@@ -27,14 +27,80 @@ RSpec.describe RubySMB::SMB2::Pipe do
27
27
  last_write: time
28
28
  )
29
29
  }
30
-
31
30
  let(:ioctl_response) {
32
31
  packet = RubySMB::SMB2::Packet::IoctlResponse.new
33
32
  packet.buffer = "\x03\x00\x00\x00" + "\x10\x20\x30\x40" + "\x00\x00\x00\x00" + "\x00\x00\x00\x00"
34
33
  packet
35
34
  }
35
+ let(:filename) { 'msf-pipe' }
36
+
37
+ subject(:pipe) { described_class.new(name: filename, response: create_response, tree: tree) }
38
+
39
+ describe '#peek' do
40
+ let(:request) { RubySMB::SMB2::Packet::IoctlRequest.new }
41
+ let(:raw_response) { double('Raw response') }
42
+ let(:response) { double('Response') }
43
+
44
+ before :example do
45
+ allow(RubySMB::SMB2::Packet::IoctlRequest).to receive(:new).and_return(request)
46
+ allow(client).to receive(:send_recv).and_return(raw_response)
47
+ allow(RubySMB::SMB2::Packet::IoctlResponse).to receive(:read).and_return(response)
48
+ allow(response).to receive(:valid?).and_return(true)
49
+ allow(response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_SUCCESS)
50
+ end
51
+
52
+ it 'creates a IoctlRequest'do
53
+ expect(RubySMB::SMB2::Packet::IoctlRequest).to receive(:new)
54
+ pipe.peek
55
+ end
56
+
57
+ it 'sets the request #ctl_code field' do
58
+ expect(request).to receive(:ctl_code=).with(RubySMB::Fscc::ControlCodes::FSCTL_PIPE_PEEK)
59
+ pipe.peek
60
+ end
61
+
62
+ it 'sets the request #is_fsctl flag to true' do
63
+ pipe.peek
64
+ expect(request.flags.is_fsctl).to eq 1
65
+ end
66
+
67
+ it 'sets the request #max_output_response field to the expected value' do
68
+ pipe.peek(peek_size: 10)
69
+ expect(request.max_output_response).to eq(16 + 10)
70
+ end
71
+
72
+ it 'calls #set_header_fields' do
73
+ expect(pipe).to receive(:set_header_fields).with(request)
74
+ pipe.peek
75
+ end
76
+
77
+ it 'calls Client #send_recv' do
78
+ expect(client).to receive(:send_recv).with(request)
79
+ pipe.peek
80
+ end
81
+
82
+ it 'parses the response as a SMB2 IoctlResponse packet' do
83
+ expect(RubySMB::SMB2::Packet::IoctlResponse).to receive(:read).with(raw_response)
84
+ pipe.peek
85
+ end
86
+
87
+ it 'raises an InvalidPacket exception if the response is not valid' do
88
+ allow(response).to receive(:valid?).and_return(false)
89
+ smb2_header = double('SMB2 Header')
90
+ allow(response).to receive(:smb2_header).and_return(smb2_header)
91
+ allow(smb2_header).to receive_messages(:protocol => nil, :command => nil)
92
+ expect { pipe.peek }.to raise_error(RubySMB::Error::InvalidPacket)
93
+ end
94
+
95
+ it 'raises an UnexpectedStatusCode exception if the response status code is not STATUS_SUCCESS or STATUS_BUFFER_OVERFLOW' do
96
+ allow(response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_OBJECT_NAME_NOT_FOUND)
97
+ expect { pipe.peek }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
98
+ end
36
99
 
37
- subject(:pipe) { described_class.new(name: 'msf-pipe', response: create_response, tree: tree) }
100
+ it 'returns the expected response' do
101
+ expect(pipe.peek).to eq(response)
102
+ end
103
+ end
38
104
 
39
105
  describe '#peek_available' do
40
106
  it 'reads the correct number of bytes available' do
@@ -61,206 +127,277 @@ RSpec.describe RubySMB::SMB2::Pipe do
61
127
  end
62
128
  end
63
129
 
64
- context 'with DCERPC' do
65
- describe '#net_share_enum_all' do
66
- let(:host) { '1.2.3.4' }
67
- let(:dcerpc_response) { RubySMB::Dcerpc::Response.new }
68
-
69
- before :example do
70
- allow(pipe).to receive(:bind)
71
- allow(pipe).to receive(:request).and_return(dcerpc_response)
72
- allow(RubySMB::Dcerpc::Srvsvc::NetShareEnumAll).to receive(:parse_response).and_return([])
130
+ describe '#initialize' do
131
+ context 'when name is not provided' do
132
+ it 'raises an ArgumentError' do
133
+ expect {
134
+ described_class.new(tree: tree, response: create_response, name: nil)
135
+ }.to raise_error(ArgumentError)
73
136
  end
137
+ end
74
138
 
75
- it 'calls #bind with the expected arguments' do
76
- expect(pipe).to receive(:bind).with(endpoint: RubySMB::Dcerpc::Srvsvc)
77
- pipe.net_share_enum_all(host)
78
- end
139
+ it 'calls the superclass with the expected arguments' do
140
+ expect(pipe.tree).to eq(tree)
141
+ expect(pipe.name).to eq(filename)
142
+ expect(pipe.attributes).to eq(create_response.file_attributes)
143
+ expect(pipe.guid).to eq(create_response.file_id)
144
+ expect(pipe.last_access).to eq(create_response.last_access.to_datetime)
145
+ expect(pipe.last_change).to eq(create_response.last_change.to_datetime)
146
+ expect(pipe.last_write).to eq(create_response.last_write.to_datetime)
147
+ expect(pipe.size).to eq(create_response.end_of_file)
148
+ expect(pipe.size_on_disk).to eq(create_response.allocation_size)
149
+ end
79
150
 
80
- it 'calls #request with the expected arguments' do
81
- expect(pipe).to receive(:request).with(RubySMB::Dcerpc::Srvsvc::NET_SHARE_ENUM_ALL, host: host)
82
- pipe.net_share_enum_all(host)
151
+ context 'with \'srvsvc\' filename' do
152
+ it 'extends Srvsvc class' do
153
+ pipe = described_class.new(tree: tree, response: create_response, name: 'srvsvc')
154
+ expect(pipe.respond_to?(:net_share_enum_all)).to be true
83
155
  end
156
+ end
84
157
 
85
- it 'parse the response with NetShareEnumAll #parse_response method' do
86
- stub = 'ABCD'
87
- dcerpc_response.alloc_hint = stub.size
88
- dcerpc_response.stub = stub
89
- expect(RubySMB::Dcerpc::Srvsvc::NetShareEnumAll).to receive(:parse_response).with(stub)
90
- pipe.net_share_enum_all(host)
158
+ context 'with \'winreg\' filename' do
159
+ it 'extends Winreg class' do
160
+ pipe = described_class.new(tree: tree, response: create_response, name: 'winreg')
161
+ expect(pipe.respond_to?(:has_registry_key?)).to be true
91
162
  end
163
+ end
164
+ end
92
165
 
93
- it 'returns the remote shares' do
94
- shares = [
95
- ["C$", "DISK", "Default share"],
96
- ["Shared", "DISK", ""],
97
- ["IPC$", "IPC", "Remote IPC"],
98
- ["ADMIN$", "DISK", "Remote Admin"]
99
- ]
100
- output = [
101
- {:name=>"C$", :type=>"DISK", :comment=>"Default share"},
102
- {:name=>"Shared", :type=>"DISK", :comment=>""},
103
- {:name=>"IPC$", :type=>"IPC", :comment=>"Remote IPC"},
104
- {:name=>"ADMIN$", :type=>"DISK", :comment=>"Remote Admin"},
105
- ]
106
- allow(RubySMB::Dcerpc::Srvsvc::NetShareEnumAll).to receive(:parse_response).and_return(shares)
107
- expect(pipe.net_share_enum_all(host)).to eq(output)
108
- end
166
+ describe '#dcerpc_request' do
167
+ let(:options) { { host: '1.2.3.4' } }
168
+ let(:stub_packet ) { RubySMB::Dcerpc::Winreg::OpenKeyRequest.new }
169
+ let(:dcerpc_request) { double('DCERPC Request') }
170
+ let(:request_stub) { double('Request stub') }
171
+ before :example do
172
+ allow(RubySMB::Dcerpc::Request).to receive(:new).and_return(dcerpc_request)
173
+ allow(dcerpc_request).to receive(:stub).and_return(request_stub)
174
+ allow(request_stub).to receive(:read)
175
+ allow(pipe).to receive(:ioctl_send_recv)
109
176
  end
110
177
 
111
- describe '#bind' do
112
- let(:options) { { endpoint: RubySMB::Dcerpc::Srvsvc } }
113
- let(:bind_packet) { RubySMB::Dcerpc::Bind.new(options) }
114
- let(:bind_ack_packet) { RubySMB::Dcerpc::BindAck.new }
178
+ it 'creates a Request packet with the expected arguments' do
179
+ pipe.dcerpc_request(stub_packet, options)
180
+ expect(options).to eq( { host: '1.2.3.4', endpoint: 'Winreg' })
181
+ expect(RubySMB::Dcerpc::Request).to have_received(:new).with({ opnum: stub_packet.opnum }, options)
182
+ end
115
183
 
116
- before :example do
117
- allow(RubySMB::Dcerpc::Bind).to receive(:new).and_return(bind_packet)
118
- allow(pipe).to receive(:write)
119
- allow(pipe).to receive(:read)
120
- bind_ack_packet.p_result_list.n_results = 1
121
- bind_ack_packet.p_result_list.p_results[0].result = RubySMB::Dcerpc::BindAck::ACCEPTANCE
122
- allow(RubySMB::Dcerpc::BindAck).to receive(:read).and_return(bind_ack_packet)
123
- end
184
+ it 'sets DCERPC request stub to the stub packet passed as argument' do
185
+ pipe.dcerpc_request(stub_packet, options)
186
+ expect(request_stub).to have_received(:read).with(stub_packet.to_binary_s)
187
+ end
124
188
 
125
- it 'creates a Bind packet' do
126
- expect(RubySMB::Dcerpc::Bind).to receive(:new).with(options).and_return(bind_packet)
127
- pipe.bind(options)
128
- end
189
+ it 'calls #ioctl_send_recv with the expected arguments' do
190
+ pipe.dcerpc_request(stub_packet, options)
191
+ expect(pipe).to have_received(:ioctl_send_recv).with(dcerpc_request, options)
192
+ end
193
+ end
129
194
 
130
- it 'writes to the named pipe' do
131
- expect(pipe).to receive(:write).with(data: bind_packet.to_binary_s)
132
- pipe.bind(options)
133
- end
195
+ describe '#ioctl_send_recv' do
196
+ let(:ioctl_request_packet) { double('IoctlRequest') }
197
+ let(:flags) { double('Flags') }
198
+ let(:dcerpc_request) { double('DCERPC Request') }
199
+ let(:binary_dcerpc_request) { double('Binary DCERPC Request') }
200
+ let(:options) { { host: '1.2.3.4' } }
201
+ let(:ioctl_raw_response) { double('IOCTL raw response') }
202
+ let(:ioctl_response) { double('IOCTL response') }
203
+ let(:raw_data) { double('Raw data') }
204
+ let(:dcerpc_response) { double('DCERPC Response') }
205
+ let(:result) { 'Result' }
206
+ before :example do
207
+ allow(RubySMB::SMB2::Packet::IoctlRequest).to receive(:new).and_return(ioctl_request_packet)
208
+ allow(pipe).to receive(:set_header_fields).and_return(ioctl_request_packet)
209
+ allow(ioctl_request_packet).to receive_messages(
210
+ :ctl_code= => nil,
211
+ :flags => flags,
212
+ :buffer= => nil
213
+ )
214
+ allow(flags).to receive(:is_fsctl=)
215
+ allow(dcerpc_request).to receive(:to_binary_s).and_return(binary_dcerpc_request)
216
+ allow(client).to receive(:send_recv).and_return(ioctl_raw_response)
217
+ allow(RubySMB::SMB2::Packet::IoctlResponse).to receive(:read).and_return(ioctl_response)
218
+ allow(ioctl_response).to receive_messages(
219
+ :valid? => true,
220
+ :status_code => WindowsError::NTStatus::STATUS_SUCCESS,
221
+ :output_data => raw_data
222
+ )
223
+ allow(RubySMB::Dcerpc::Response).to receive(:read).and_return(dcerpc_response)
224
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :ptype => RubySMB::Dcerpc::PTypes::RESPONSE)
225
+ allow(dcerpc_response).to receive(:stub).and_return(result)
226
+ end
227
+
228
+ it 'creates an IoctlRequest packet' do
229
+ pipe.ioctl_send_recv(dcerpc_request, options)
230
+ expect(RubySMB::SMB2::Packet::IoctlRequest).to have_received(:new).with(options)
231
+ end
232
+
233
+ it 'calls #set_header_fields' do
234
+ pipe.ioctl_send_recv(dcerpc_request, options)
235
+ expect(pipe).to have_received(:set_header_fields).with(ioctl_request_packet)
236
+ end
237
+
238
+ it 'sets the expected properties on the request packet' do
239
+ pipe.ioctl_send_recv(dcerpc_request, options)
240
+ expect(ioctl_request_packet).to have_received(:ctl_code=).with(0x11C017)
241
+ expect(flags).to have_received(:is_fsctl=).with(0x1)
242
+ expect(ioctl_request_packet).to have_received(:buffer=).with(binary_dcerpc_request)
243
+ end
244
+
245
+ it 'sends the expected request' do
246
+ pipe.ioctl_send_recv(dcerpc_request, options)
247
+ expect(client).to have_received(:send_recv).with(ioctl_request_packet)
248
+ end
249
+
250
+ it 'creates an IoctlResponse packet from the response' do
251
+ pipe.ioctl_send_recv(dcerpc_request, options)
252
+ expect(RubySMB::SMB2::Packet::IoctlResponse).to have_received(:read).with(ioctl_raw_response)
253
+ end
134
254
 
135
- it 'reads the socket' do
136
- expect(pipe).to receive(:read)
137
- pipe.bind(options)
255
+ context 'when the response is not an IoctlResponse packet' do
256
+ it 'raises an InvalidPacket exception' do
257
+ allow(ioctl_response).to receive_message_chain(:smb2_header, :protocol)
258
+ allow(ioctl_response).to receive_message_chain(:smb2_header, :command)
259
+ allow(ioctl_response).to receive(:valid?).and_return(false)
260
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Error::InvalidPacket)
138
261
  end
262
+ end
139
263
 
140
- it 'creates a BindAck packet from the response' do
141
- raw_response = RubySMB::Dcerpc::BindAck.new.to_binary_s
142
- allow(pipe).to receive(:read).and_return(raw_response)
143
- expect(RubySMB::Dcerpc::BindAck).to receive(:read).with(raw_response).and_return(bind_ack_packet)
144
- pipe.bind(options)
264
+ context 'when the response status code is not STATUS_SUCCESS or STATUS_BUFFER_OVERFLOW' do
265
+ it 'raises an UnexpectedStatusCode exception' do
266
+ allow(ioctl_response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_INVALID_HANDLE)
267
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
145
268
  end
269
+ end
146
270
 
147
- it 'raises the expected exception when an invalid packet is received' do
148
- allow(RubySMB::Dcerpc::BindAck).to receive(:read).and_raise(IOError)
149
- expect { pipe.bind(options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
271
+ context 'when the response status code is STATUS_SUCCESS' do
272
+ it 'does not raise any exception' do
273
+ expect { pipe.ioctl_send_recv(dcerpc_request, options)}.not_to raise_error
150
274
  end
151
275
 
152
- it 'raises the expected exception when it is not a BindAck packet' do
153
- response = RubySMB::Dcerpc::Bind.new
154
- allow(RubySMB::Dcerpc::BindAck).to receive(:read).and_return(response)
155
- expect { pipe.bind(options) }.to raise_error(RubySMB::Dcerpc::Error::BindError)
276
+ it 'creates a DCERPC Response packet from the response' do
277
+ pipe.ioctl_send_recv(dcerpc_request, options)
278
+ expect(RubySMB::Dcerpc::Response).to have_received(:read).with(raw_data)
156
279
  end
157
280
 
158
- it 'raises an exception when no result is returned' do
159
- bind_ack_packet.p_result_list.n_results = 0
160
- expect { pipe.bind(options) }.to raise_error(RubySMB::Dcerpc::Error::BindError)
281
+ context 'when an IOError occurs while parsing the DCERPC response' do
282
+ it 'raises an InvalidPacket exception' do
283
+ allow(RubySMB::Dcerpc::Response).to receive(:read).and_raise(IOError)
284
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
285
+ end
161
286
  end
162
287
 
163
- it 'raises an exception when result is not ACCEPTANCE' do
164
- bind_ack_packet.p_result_list.p_results[0].result = RubySMB::Dcerpc::BindAck::USER_REJECTION
165
- expect { pipe.bind(options) }.to raise_error(RubySMB::Dcerpc::Error::BindError)
288
+ context 'when the response is not a DCERPC Response packet' do
289
+ it 'raises an InvalidPacket exception' do
290
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :ptype => RubySMB::Dcerpc::PTypes::FAULT)
291
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
292
+ end
166
293
  end
167
294
 
168
- it 'returns the expected BindAck packet' do
169
- expect(pipe.bind(options)).to eq(bind_ack_packet)
295
+ it 'returns the expected stub data' do
296
+ expect(pipe.ioctl_send_recv(dcerpc_request, options)).to eq(result)
170
297
  end
171
298
  end
172
299
 
173
- describe '#request' do
174
- let(:options) { { host: '1.2.3.4' } }
175
- let(:opnum) { RubySMB::Dcerpc::Srvsvc::NET_SHARE_ENUM_ALL }
176
- let(:req_packet) { RubySMB::Dcerpc::Request.new({ :opnum => opnum }, options) }
177
- let(:ioctl_response) { RubySMB::SMB2::Packet::IoctlResponse.new }
178
- let(:res_packet) { RubySMB::Dcerpc::Response.new }
179
-
300
+ context 'when the response status code is STATUS_BUFFER_OVERFLOW' do
301
+ let(:data_count) { 100 }
302
+ let(:added_raw_data) { double('Added raw data') }
180
303
  before :example do
181
- allow(RubySMB::Dcerpc::Request).to receive(:new).and_return(req_packet)
182
- allow(pipe).to receive(:ioctl_send_recv).and_return(ioctl_response)
183
- allow(RubySMB::Dcerpc::Response).to receive(:read).and_return(res_packet)
304
+ allow(ioctl_response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW)
305
+ allow(ioctl_response).to receive(:output_count).and_return(data_count)
306
+ allow(pipe).to receive(:read).and_return(added_raw_data)
307
+ allow(raw_data).to receive(:<<)
308
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :pfc_flags, :first_frag => 1)
309
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :pfc_flags, :last_frag => 1)
184
310
  end
185
311
 
186
- it 'creates a Request packet' do
187
- expect(RubySMB::Dcerpc::Request).to receive(:new).and_return(req_packet)
188
- pipe.request(opnum, options)
312
+ it 'does not raise any exception' do
313
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.not_to raise_error
189
314
  end
190
315
 
191
- it 'calls #ioctl_send_recv' do
192
- expect(pipe).to receive(:ioctl_send_recv).with(req_packet, options)
193
- pipe.request(opnum, options)
316
+ it 'reads the expected number of bytes and concatenate it the first response raw data' do
317
+ pipe.ioctl_send_recv(dcerpc_request, options)
318
+ expect(pipe).to have_received(:read).with(bytes: tree.client.max_buffer_size - data_count)
319
+ expect(raw_data).to have_received(:<<).with(added_raw_data)
194
320
  end
195
321
 
196
- it 'creates a DCERPC Response packet from the response' do
197
- expect(RubySMB::Dcerpc::Response).to receive(:read).with(ioctl_response.output_data)
198
- pipe.request(opnum, options)
322
+ it 'creates a DCERPC Response packet from the updated raw data' do
323
+ pipe.ioctl_send_recv(dcerpc_request, options)
324
+ expect(RubySMB::Dcerpc::Response).to have_received(:read).with(raw_data)
199
325
  end
200
326
 
201
- it 'raises the expected exception when an invalid packet is received' do
202
- allow(RubySMB::Dcerpc::Response).to receive(:read).and_raise(IOError)
203
- expect { pipe.request(opnum, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
327
+ context 'when an IOError occurs while parsing the DCERPC response' do
328
+ it 'raises an InvalidPacket exception' do
329
+ allow(RubySMB::Dcerpc::Response).to receive(:read).and_raise(IOError)
330
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
331
+ end
204
332
  end
205
333
 
206
- it 'raises the expected exception when it is not a BindAck packet' do
207
- response = RubySMB::Dcerpc::Request.new
208
- allow(RubySMB::Dcerpc::Response).to receive(:read).and_return(response)
209
- expect { pipe.request(opnum, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
334
+ context 'when the response is not a DCERPC Response packet' do
335
+ it 'raises an InvalidPacket exception' do
336
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :ptype => RubySMB::Dcerpc::PTypes::FAULT)
337
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
338
+ end
210
339
  end
211
340
 
212
- it 'returns the expected DCERPC Response' do
213
- expect(pipe.request(opnum, options)).to eq(res_packet)
341
+ context 'when the response is not the first fragment' do
342
+ it 'raises an InvalidPacket exception' do
343
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :pfc_flags, :first_frag => 0)
344
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
345
+ end
214
346
  end
215
- end
216
347
 
217
- describe '#ioctl_send_recv' do
218
- let(:action) { RubySMB::Dcerpc::Request.new({ :opnum => RubySMB::Dcerpc::Srvsvc::NET_SHARE_ENUM_ALL }, host: '1.2.3.4') }
219
- let(:options) { {} }
220
- let(:ioctl_request) { RubySMB::SMB2::Packet::IoctlRequest.new(options) }
221
- let(:ioctl_response) { RubySMB::SMB2::Packet::IoctlResponse.new }
348
+ context 'when the response is the last fragment' do
349
+ it 'only reads the pipe once' do
350
+ pipe.ioctl_send_recv(dcerpc_request, options)
351
+ expect(RubySMB::Dcerpc::Response).to have_received(:read).once
352
+ end
222
353
 
223
- before :example do
224
- allow(client).to receive(:send_recv).and_return(ioctl_response.to_binary_s)
354
+ it 'returns the expected stub data' do
355
+ expect(pipe.ioctl_send_recv(dcerpc_request, options)).to eq(result)
356
+ end
225
357
  end
226
358
 
227
- it 'calls #set_header_fields' do
228
- expect(pipe).to receive(:set_header_fields).with(ioctl_request).and_call_original
229
- pipe.ioctl_send_recv(action, options)
230
- end
359
+ context 'when the response is not the last fragment' do
360
+ let(:raw_data2) { double('Raw data #2') }
361
+ let(:dcerpc_response2) { double('DCERPC Response #2') }
362
+ let(:result2) { 'Result #2' }
363
+ before :example do
364
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :pfc_flags, :last_frag => 0)
365
+ allow(pipe).to receive(:read).with(bytes: tree.client.max_buffer_size).and_return(raw_data2)
366
+ allow(RubySMB::Dcerpc::Response).to receive(:read).with(raw_data2).and_return(dcerpc_response2)
367
+ allow(dcerpc_response2).to receive_message_chain(:pdu_header, :ptype => RubySMB::Dcerpc::PTypes::RESPONSE)
368
+ allow(dcerpc_response2).to receive_message_chain(:pdu_header, :pfc_flags, :last_frag => 1)
369
+ allow(dcerpc_response2).to receive(:stub).and_return(result2)
370
+ end
231
371
 
232
- it 'calls Client #send_recv with the expected request' do
233
- expect(client).to receive(:send_recv) do |req|
234
- expect(req.ctl_code).to eq(0x0011C017)
235
- expect(req.flags.is_fsctl).to eq(0x00000001)
236
- expect(req.buffer).to eq(action.to_binary_s)
237
- ioctl_response.to_binary_s
372
+ it 'reads the expected number of bytes' do
373
+ pipe.ioctl_send_recv(dcerpc_request, options)
374
+ expect(pipe).to have_received(:read).with(bytes: tree.client.max_buffer_size)
238
375
  end
239
- pipe.ioctl_send_recv(action, options)
240
- end
241
376
 
242
- it 'creates a IoctlResponse packet from the response' do
243
- expect(RubySMB::SMB2::Packet::IoctlResponse).to receive(:read).with(ioctl_response.to_binary_s).and_call_original
244
- pipe.ioctl_send_recv(action, options)
245
- end
377
+ it 'creates a DCERPC Response packet from the new raw data' do
378
+ pipe.ioctl_send_recv(dcerpc_request, options)
379
+ expect(RubySMB::Dcerpc::Response).to have_received(:read).with(raw_data2)
380
+ end
246
381
 
247
- it 'raises the expected exception when it is not a IoctlResponse packet' do
248
- response = RubySMB::SMB2::Packet::LogoffResponse.new
249
- allow(RubySMB::SMB2::Packet::IoctlResponse).to receive(:read).and_return(response)
250
- expect { pipe.ioctl_send_recv(action, options) }.to raise_error(RubySMB::Error::InvalidPacket)
251
- end
382
+ context 'when an IOError occurs while parsing the new DCERPC response' do
383
+ it 'raises an InvalidPacket exception' do
384
+ allow(RubySMB::Dcerpc::Response).to receive(:read).with(raw_data2).and_raise(IOError)
385
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
386
+ end
387
+ end
252
388
 
253
- it 'raises the expected exception when the status code is not STATUS_SUCCESS' do
254
- ioctl_response_packet = RubySMB::SMB2::Packet::IoctlResponse.new
255
- ioctl_response_packet.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE.value
256
- allow(RubySMB::SMB2::Packet::IoctlResponse).to receive(:read).with(ioctl_response.to_binary_s).and_return(ioctl_response_packet)
257
- expect { pipe.ioctl_send_recv(action, options) }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
258
- end
389
+ context 'when the new response is not a DCERPC Response packet' do
390
+ it 'raises an InvalidPacket exception' do
391
+ allow(dcerpc_response2).to receive_message_chain(:pdu_header, :ptype => RubySMB::Dcerpc::PTypes::FAULT)
392
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
393
+ end
394
+ end
259
395
 
260
- it 'returns the expected DCERPC Response' do
261
- expect(pipe.ioctl_send_recv(action, options)).to eq(ioctl_response)
396
+ it 'returns the expected stub data' do
397
+ expect(pipe.ioctl_send_recv(dcerpc_request, options)).to eq(result)
398
+ end
262
399
  end
263
400
  end
264
401
  end
265
- end
266
402
 
403
+ end