ruby_smb 2.0.9 → 2.0.13

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 (228) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/verify.yml +5 -15
  4. data/examples/auth_capture.rb +71 -0
  5. data/examples/dump_secrets_from_sid.rb +207 -0
  6. data/examples/enum_domain_users.rb +75 -0
  7. data/examples/get_computer_info.rb +42 -0
  8. data/examples/query_service_status.rb +42 -4
  9. data/lib/ruby_smb/client/negotiation.rb +1 -1
  10. data/lib/ruby_smb/client.rb +10 -20
  11. data/lib/ruby_smb/dcerpc/bind.rb +28 -20
  12. data/lib/ruby_smb/dcerpc/bind_ack.rb +29 -28
  13. data/lib/ruby_smb/dcerpc/client.rb +542 -0
  14. data/lib/ruby_smb/dcerpc/drsr/drs_bind_request.rb +24 -0
  15. data/lib/ruby_smb/dcerpc/drsr/drs_bind_response.rb +26 -0
  16. data/lib/ruby_smb/dcerpc/drsr/drs_crack_names_request.rb +57 -0
  17. data/lib/ruby_smb/dcerpc/drsr/drs_crack_names_response.rb +76 -0
  18. data/lib/ruby_smb/dcerpc/drsr/drs_domain_controller_info_request.rb +46 -0
  19. data/lib/ruby_smb/dcerpc/drsr/drs_domain_controller_info_response.rb +168 -0
  20. data/lib/ruby_smb/dcerpc/drsr/drs_extensions.rb +56 -0
  21. data/lib/ruby_smb/dcerpc/drsr/drs_get_nc_changes_request.rb +121 -0
  22. data/lib/ruby_smb/dcerpc/drsr/drs_get_nc_changes_response.rb +118 -0
  23. data/lib/ruby_smb/dcerpc/drsr/drs_unbind_request.rb +24 -0
  24. data/lib/ruby_smb/dcerpc/drsr/drs_unbind_response.rb +26 -0
  25. data/lib/ruby_smb/dcerpc/drsr.rb +909 -0
  26. data/lib/ruby_smb/dcerpc/epm/epm_ept_map_request.rb +26 -0
  27. data/lib/ruby_smb/dcerpc/epm/epm_ept_map_response.rb +25 -0
  28. data/lib/ruby_smb/dcerpc/epm/epm_twrt.rb +211 -0
  29. data/lib/ruby_smb/dcerpc/epm.rb +75 -0
  30. data/lib/ruby_smb/dcerpc/error.rb +17 -0
  31. data/lib/ruby_smb/dcerpc/ndr.rb +1159 -297
  32. data/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_request.rb +3 -13
  33. data/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_response.rb +3 -3
  34. data/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_request.rb +3 -13
  35. data/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_response.rb +1 -1
  36. data/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_request.rb +3 -11
  37. data/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_response.rb +1 -1
  38. data/lib/ruby_smb/dcerpc/netlogon.rb +5 -4
  39. data/lib/ruby_smb/dcerpc/p_syntax_id_t.rb +4 -3
  40. data/lib/ruby_smb/dcerpc/pdu_header.rb +7 -7
  41. data/lib/ruby_smb/dcerpc/ptypes.rb +1 -0
  42. data/lib/ruby_smb/dcerpc/request.rb +79 -32
  43. data/lib/ruby_smb/dcerpc/response.rb +45 -10
  44. data/lib/ruby_smb/dcerpc/rpc_auth3.rb +28 -0
  45. data/lib/ruby_smb/dcerpc/rpc_security_attributes.rb +11 -11
  46. data/lib/ruby_smb/dcerpc/rrp_rpc_unicode_string.rb +118 -0
  47. data/lib/ruby_smb/dcerpc/samr/rpc_sid.rb +150 -0
  48. data/lib/ruby_smb/dcerpc/samr/samr_close_handle_request.rb +23 -0
  49. data/lib/ruby_smb/dcerpc/samr/samr_close_handle_response.rb +24 -0
  50. data/lib/ruby_smb/dcerpc/samr/samr_connect_request.rb +32 -0
  51. data/lib/ruby_smb/dcerpc/samr/samr_connect_response.rb +23 -0
  52. data/lib/ruby_smb/dcerpc/samr/samr_enumerate_users_in_domain_request.rb +26 -0
  53. data/lib/ruby_smb/dcerpc/samr/samr_enumerate_users_in_domain_response.rb +55 -0
  54. data/lib/ruby_smb/dcerpc/samr/samr_get_alias_membership_request.rb +48 -0
  55. data/lib/ruby_smb/dcerpc/samr/samr_get_alias_membership_response.rb +38 -0
  56. data/lib/ruby_smb/dcerpc/samr/samr_get_groups_for_user_request.rb +23 -0
  57. data/lib/ruby_smb/dcerpc/samr/samr_get_groups_for_user_response.rb +48 -0
  58. data/lib/ruby_smb/dcerpc/samr/samr_lookup_domain_in_sam_server_request.rb +24 -0
  59. data/lib/ruby_smb/dcerpc/samr/samr_lookup_domain_in_sam_server_response.rb +25 -0
  60. data/lib/ruby_smb/dcerpc/samr/samr_open_domain_request.rb +27 -0
  61. data/lib/ruby_smb/dcerpc/samr/samr_open_domain_response.rb +24 -0
  62. data/lib/ruby_smb/dcerpc/samr/samr_open_user_request.rb +26 -0
  63. data/lib/ruby_smb/dcerpc/samr/samr_open_user_response.rb +24 -0
  64. data/lib/ruby_smb/dcerpc/samr/samr_rid_to_sid_request.rb +23 -0
  65. data/lib/ruby_smb/dcerpc/samr/samr_rid_to_sid_response.rb +23 -0
  66. data/lib/ruby_smb/dcerpc/samr.rb +613 -0
  67. data/lib/ruby_smb/dcerpc/sec_trailer.rb +26 -0
  68. data/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all.rb +56 -79
  69. data/lib/ruby_smb/dcerpc/srvsvc.rb +27 -4
  70. data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request.rb +13 -25
  71. data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response.rb +2 -2
  72. data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response.rb +1 -1
  73. data/lib/ruby_smb/dcerpc/svcctl/control_service_request.rb +1 -1
  74. data/lib/ruby_smb/dcerpc/svcctl/control_service_response.rb +1 -1
  75. data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request.rb +4 -14
  76. data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response.rb +1 -1
  77. data/lib/ruby_smb/dcerpc/svcctl/open_service_w_request.rb +3 -11
  78. data/lib/ruby_smb/dcerpc/svcctl/open_service_w_response.rb +1 -1
  79. data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request.rb +1 -1
  80. data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response.rb +12 -11
  81. data/lib/ruby_smb/dcerpc/svcctl/query_service_status_response.rb +1 -1
  82. data/lib/ruby_smb/dcerpc/svcctl/service_status.rb +9 -8
  83. data/lib/ruby_smb/dcerpc/svcctl/start_service_w_request.rb +3 -3
  84. data/lib/ruby_smb/dcerpc/svcctl/start_service_w_response.rb +1 -1
  85. data/lib/ruby_smb/dcerpc/svcctl.rb +1 -3
  86. data/lib/ruby_smb/dcerpc/uuid.rb +3 -0
  87. data/lib/ruby_smb/dcerpc/winreg/close_key_response.rb +2 -2
  88. data/lib/ruby_smb/dcerpc/winreg/create_key_request.rb +2 -13
  89. data/lib/ruby_smb/dcerpc/winreg/create_key_response.rb +3 -3
  90. data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +3 -20
  91. data/lib/ruby_smb/dcerpc/winreg/enum_key_response.rb +3 -20
  92. data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +5 -14
  93. data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +5 -14
  94. data/lib/ruby_smb/dcerpc/winreg/open_key_request.rb +1 -9
  95. data/lib/ruby_smb/dcerpc/winreg/open_key_response.rb +4 -3
  96. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +5 -6
  97. data/lib/ruby_smb/dcerpc/winreg/open_root_key_response.rb +2 -2
  98. data/lib/ruby_smb/dcerpc/winreg/query_info_key_response.rb +9 -18
  99. data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +4 -14
  100. data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +7 -15
  101. data/lib/ruby_smb/dcerpc/winreg/regsam.rb +3 -1
  102. data/lib/ruby_smb/dcerpc/winreg/save_key_request.rb +0 -9
  103. data/lib/ruby_smb/dcerpc/winreg/save_key_response.rb +1 -1
  104. data/lib/ruby_smb/dcerpc/winreg.rb +10 -14
  105. data/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_get_info_request.rb +26 -0
  106. data/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_get_info_response.rb +88 -0
  107. data/lib/ruby_smb/dcerpc/wkssvc.rb +65 -0
  108. data/lib/ruby_smb/dcerpc.rb +41 -11
  109. data/lib/ruby_smb/dialect.rb +45 -0
  110. data/lib/ruby_smb/dispatcher/base.rb +1 -1
  111. data/lib/ruby_smb/field/file_time.rb +1 -1
  112. data/lib/ruby_smb/field/string16.rb +5 -1
  113. data/lib/ruby_smb/gss/provider/authenticator.rb +42 -0
  114. data/lib/ruby_smb/gss/provider/ntlm.rb +303 -0
  115. data/lib/ruby_smb/gss/provider.rb +35 -0
  116. data/lib/ruby_smb/gss.rb +56 -63
  117. data/lib/ruby_smb/ntlm.rb +61 -0
  118. data/lib/ruby_smb/server/server_client/negotiation.rb +156 -0
  119. data/lib/ruby_smb/server/server_client/session_setup.rb +82 -0
  120. data/lib/ruby_smb/server/server_client.rb +162 -0
  121. data/lib/ruby_smb/server.rb +54 -0
  122. data/lib/ruby_smb/signing.rb +59 -0
  123. data/lib/ruby_smb/smb1/packet/negotiate_response.rb +11 -11
  124. data/lib/ruby_smb/smb1/packet/negotiate_response_extended.rb +1 -1
  125. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
  126. data/lib/ruby_smb/smb1/pipe.rb +4 -0
  127. data/lib/ruby_smb/smb1/tree.rb +1 -1
  128. data/lib/ruby_smb/smb2/negotiate_context.rb +18 -2
  129. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +9 -0
  130. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +0 -1
  131. data/lib/ruby_smb/smb2/packet/session_setup_response.rb +2 -2
  132. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +1 -1
  133. data/lib/ruby_smb/smb2/pipe.rb +4 -0
  134. data/lib/ruby_smb/smb2/tree.rb +1 -1
  135. data/lib/ruby_smb/smb2.rb +3 -1
  136. data/lib/ruby_smb/version.rb +1 -1
  137. data/lib/ruby_smb.rb +2 -1
  138. data/spec/lib/ruby_smb/client_spec.rb +8 -11
  139. data/spec/lib/ruby_smb/dcerpc/bind_ack_spec.rb +69 -41
  140. data/spec/lib/ruby_smb/dcerpc/bind_spec.rb +75 -21
  141. data/spec/lib/ruby_smb/dcerpc/client_spec.rb +714 -0
  142. data/spec/lib/ruby_smb/dcerpc/drsr_spec.rb +2169 -0
  143. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +3792 -1373
  144. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_request_spec.rb +4 -4
  145. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_request_spec.rb +4 -4
  146. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_request_spec.rb +2 -2
  147. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_response_spec.rb +2 -2
  148. data/spec/lib/ruby_smb/dcerpc/p_syntax_id_t_spec.rb +18 -4
  149. data/spec/lib/ruby_smb/dcerpc/pdu_header_spec.rb +27 -1
  150. data/spec/lib/ruby_smb/dcerpc/request_spec.rb +76 -11
  151. data/spec/lib/ruby_smb/dcerpc/response_spec.rb +99 -9
  152. data/spec/lib/ruby_smb/dcerpc/rpc_auth3_spec.rb +75 -0
  153. data/spec/lib/ruby_smb/dcerpc/rpc_security_attributes_spec.rb +29 -28
  154. data/spec/lib/ruby_smb/dcerpc/rrp_rpc_unicode_string_spec.rb +340 -0
  155. data/spec/lib/ruby_smb/dcerpc/samr/rpc_sid_spec.rb +116 -0
  156. data/spec/lib/ruby_smb/dcerpc/samr/samr_close_handle_request_spec.rb +40 -0
  157. data/spec/lib/ruby_smb/dcerpc/samr/samr_close_handle_response_spec.rb +48 -0
  158. data/spec/lib/ruby_smb/dcerpc/samr/samr_connect_request_spec.rb +56 -0
  159. data/spec/lib/ruby_smb/dcerpc/samr/samr_connect_response_spec.rb +47 -0
  160. data/spec/lib/ruby_smb/dcerpc/samr/samr_enumerate_users_in_domain_request_spec.rb +63 -0
  161. data/spec/lib/ruby_smb/dcerpc/samr/samr_enumerate_users_in_domain_response_spec.rb +265 -0
  162. data/spec/lib/ruby_smb/dcerpc/samr/samr_lookup_domain_in_sam_server_request_spec.rb +52 -0
  163. data/spec/lib/ruby_smb/dcerpc/samr/samr_lookup_domain_in_sam_server_response_spec.rb +36 -0
  164. data/spec/lib/ruby_smb/dcerpc/samr/samr_open_domain_request_spec.rb +56 -0
  165. data/spec/lib/ruby_smb/dcerpc/samr/samr_open_domain_response_spec.rb +48 -0
  166. data/spec/lib/ruby_smb/dcerpc/samr/samr_rid_to_sid_request_spec.rb +48 -0
  167. data/spec/lib/ruby_smb/dcerpc/samr/samr_rid_to_sid_response_spec.rb +42 -0
  168. data/spec/lib/ruby_smb/dcerpc/samr_spec.rb +420 -0
  169. data/spec/lib/ruby_smb/dcerpc/sec_trailer_spec.rb +92 -0
  170. data/spec/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all_spec.rb +149 -110
  171. data/spec/lib/ruby_smb/dcerpc/srvsvc_spec.rb +21 -17
  172. data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request_spec.rb +56 -79
  173. data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response_spec.rb +4 -4
  174. data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response_spec.rb +2 -2
  175. data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_request_spec.rb +2 -2
  176. data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_response_spec.rb +2 -2
  177. data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request_spec.rb +19 -29
  178. data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response_spec.rb +2 -2
  179. data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_request_spec.rb +9 -15
  180. data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_response_spec.rb +2 -2
  181. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request_spec.rb +2 -2
  182. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response_spec.rb +22 -22
  183. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_response_spec.rb +2 -2
  184. data/spec/lib/ruby_smb/dcerpc/svcctl/service_status_spec.rb +18 -14
  185. data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_request_spec.rb +5 -4
  186. data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_response_spec.rb +2 -2
  187. data/spec/lib/ruby_smb/dcerpc/svcctl_spec.rb +1 -5
  188. data/spec/lib/ruby_smb/dcerpc/uuid_spec.rb +15 -23
  189. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_response_spec.rb +2 -2
  190. data/spec/lib/ruby_smb/dcerpc/winreg/create_key_request_spec.rb +4 -41
  191. data/spec/lib/ruby_smb/dcerpc/winreg/create_key_response_spec.rb +4 -4
  192. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +4 -52
  193. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_response_spec.rb +4 -56
  194. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +10 -34
  195. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +10 -34
  196. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_request_spec.rb +2 -26
  197. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_response_spec.rb +2 -2
  198. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +17 -25
  199. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_response_spec.rb +2 -2
  200. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_response_spec.rb +20 -44
  201. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +8 -32
  202. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +10 -22
  203. data/spec/lib/ruby_smb/dcerpc/winreg/regsam_spec.rb +4 -0
  204. data/spec/lib/ruby_smb/dcerpc/winreg/save_key_request_spec.rb +0 -12
  205. data/spec/lib/ruby_smb/dcerpc/winreg/save_key_response_spec.rb +2 -2
  206. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +18 -47
  207. data/spec/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_get_info_request_spec.rb +43 -0
  208. data/spec/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_get_info_response_spec.rb +410 -0
  209. data/spec/lib/ruby_smb/dcerpc/wkssvc_spec.rb +70 -0
  210. data/spec/lib/ruby_smb/field/string16_spec.rb +22 -0
  211. data/spec/lib/ruby_smb/gss/provider/ntlm/account_spec.rb +32 -0
  212. data/spec/lib/ruby_smb/gss/provider/ntlm/authenticator_spec.rb +101 -0
  213. data/spec/lib/ruby_smb/gss/provider/ntlm/os_version_spec.rb +32 -0
  214. data/spec/lib/ruby_smb/gss/provider/ntlm_spec.rb +113 -0
  215. data/spec/lib/ruby_smb/server/server_client_spec.rb +156 -0
  216. data/spec/lib/ruby_smb/server_spec.rb +32 -0
  217. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +18 -37
  218. data/spec/lib/ruby_smb/smb1/tree_spec.rb +4 -4
  219. data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +2 -2
  220. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +18 -16
  221. data/spec/lib/ruby_smb/smb2/tree_spec.rb +5 -5
  222. data/spec/support/bin_helper.rb +9 -0
  223. data.tar.gz.sig +2 -1
  224. metadata +119 -6
  225. metadata.gz.sig +0 -0
  226. data/lib/ruby_smb/client/signing.rb +0 -64
  227. data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +0 -38
  228. data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +0 -135
@@ -0,0 +1,714 @@
1
+ require 'ruby_smb/dcerpc/client'
2
+
3
+ RSpec.describe RubySMB::Dcerpc::Client do
4
+ it 'includes the RubySMB::Dcerpc::Epm class' do
5
+ expect(described_class < RubySMB::Dcerpc::Epm).to be true
6
+ end
7
+
8
+ let(:host) { '1.2.3.4' }
9
+ let(:endpoint) { RubySMB::Dcerpc::Samr }
10
+
11
+ subject(:client) { described_class.new(host, endpoint) }
12
+ subject(:auth_client) { described_class.new(host, endpoint, username: 'testuser', password: '1234') }
13
+
14
+ it { is_expected.to respond_to :domain }
15
+ it { is_expected.to respond_to :local_workstation }
16
+ it { is_expected.to respond_to :ntlm_client }
17
+ it { is_expected.to respond_to :username }
18
+ it { is_expected.to respond_to :password }
19
+ it { is_expected.to respond_to :default_name }
20
+ it { is_expected.to respond_to :default_domain }
21
+ it { is_expected.to respond_to :dns_host_name }
22
+ it { is_expected.to respond_to :dns_domain_name }
23
+ it { is_expected.to respond_to :dns_tree_name }
24
+ it { is_expected.to respond_to :os_version }
25
+ it { is_expected.to respond_to :max_buffer_size }
26
+ it { is_expected.to respond_to :tcp_socket }
27
+
28
+ describe '#auth_ctx_id_base' do
29
+ it 'is randomly generated' do
30
+ expect(
31
+ 20.times.map do
32
+ described_class.new(host, endpoint).instance_variable_get(:@auth_ctx_id_base)
33
+ end.uniq.size
34
+ ).to eq(20)
35
+ end
36
+ end
37
+
38
+ describe '#ntlm_client' do
39
+ context 'without a username and a password' do
40
+ it 'does not instantiate a net::ntlm::client' do
41
+ expect(client.ntlm_client).to be nil
42
+ end
43
+ end
44
+
45
+ context 'with a username and a password' do
46
+ it 'instantiates a Net::NTLM::Client' do
47
+ expect(auth_client.ntlm_client).to be_a Net::NTLM::Client
48
+ end
49
+ end
50
+ end
51
+
52
+ describe '#connect' do
53
+ let(:tcp_socket) { double('TcpSocket') }
54
+ before :example do
55
+ allow(TCPSocket).to receive(:new).and_return(tcp_socket)
56
+ end
57
+
58
+ it 'does nothing if a TcpSocket has been provided already' do
59
+ client.tcp_socket = double('TcpSocket')
60
+ expect(client.connect).to be nil
61
+ expect(TCPSocket).to_not have_received(:new)
62
+ end
63
+
64
+ context 'without initial TcpSocket' do
65
+ context 'when a TCP port is provided' do
66
+ it 'connects to this port' do
67
+ expect(client.connect(port: 123)).to eq(tcp_socket)
68
+ expect(TCPSocket).to have_received(:new).with(host, 123)
69
+ end
70
+ end
71
+
72
+ context 'without TCP port' do
73
+ let(:host_port) { {host: '0.9.8.7', port: 999} }
74
+ let(:epm_tcp_socket) { double('EPM TcpSocket') }
75
+ before :example do
76
+ allow(TCPSocket).to receive(:new).with(host, 135).and_return(epm_tcp_socket)
77
+ allow(client).to receive(:bind)
78
+ allow(client).to receive(:get_host_port_from_ept_mapper).and_return(host_port)
79
+ allow(epm_tcp_socket).to receive(:close)
80
+ end
81
+
82
+ it 'connects to port 135' do
83
+ client.connect
84
+ expect(TCPSocket).to have_received(:new).with(host, 135)
85
+ end
86
+
87
+ it 'binds to the Endpoint Mapper endpoint' do
88
+ client.connect
89
+ expect(client).to have_received(:bind).with(endpoint: RubySMB::Dcerpc::Epm)
90
+ end
91
+
92
+ it 'gets host and port information from the Endpoint Mapper' do
93
+ client.connect
94
+ expect(client).to have_received(:get_host_port_from_ept_mapper).with(
95
+ uuid: endpoint::UUID,
96
+ maj_ver: endpoint::VER_MAJOR,
97
+ min_ver: endpoint::VER_MINOR
98
+ )
99
+ end
100
+
101
+ it 'closes the EPM socket' do
102
+ client.connect
103
+ expect(epm_tcp_socket).to have_received(:close)
104
+ end
105
+
106
+ it 'connects to the endpoint and returns the socket' do
107
+ expect(client.connect).to eq(tcp_socket)
108
+ expect(TCPSocket).to have_received(:new).with(host, 999)
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ describe '#close' do
115
+ let(:tcp_socket) { double('TcpSocket') }
116
+ before :example do
117
+ client.tcp_socket = tcp_socket
118
+ allow(tcp_socket).to receive(:close)
119
+ end
120
+
121
+ it 'closes the socket' do
122
+ allow(tcp_socket).to receive(:closed?).and_return(false)
123
+ client.close
124
+ expect(tcp_socket).to have_received(:close)
125
+ end
126
+
127
+ it 'does not closes the socket if it is already closed' do
128
+ allow(tcp_socket).to receive(:closed?).and_return(true)
129
+ client.close
130
+ expect(tcp_socket).to_not have_received(:close)
131
+ end
132
+ end
133
+
134
+ context 'with authentication' do
135
+ describe '#add_auth_verifier' do
136
+ let(:req) { RubySMB::Dcerpc::Bind.new }
137
+ let(:auth_type) { RubySMB::Dcerpc::RPC_C_AUTHN_WINNT }
138
+ let(:auth_level) { 0 }
139
+ let(:auth) { 'serialized auth value' }
140
+
141
+ it 'sets #auth_value field to the expected value' do
142
+ auth_client.add_auth_verifier(req, auth, auth_type, auth_level)
143
+ expect(req.auth_value).to eq(auth)
144
+ end
145
+
146
+ it 'sets PDUHeader #auth_length field to the expected value' do
147
+ auth_client.add_auth_verifier(req, auth, auth_type, auth_level)
148
+ expect(req.pdu_header.auth_length).to eq(auth.length)
149
+ end
150
+
151
+ it 'sets #sec_trailer field to the expected value' do
152
+ auth_client.add_auth_verifier(req, auth, auth_type, auth_level)
153
+ expect(req.sec_trailer.auth_type).to eq(auth_type)
154
+ expect(req.sec_trailer.auth_level).to eq(auth_level)
155
+ expect(req.sec_trailer.auth_context_id).to eq(auth_client.instance_variable_get(:@auth_ctx_id_base))
156
+ end
157
+ end
158
+
159
+ describe '#process_ntlm_type2' do
160
+ let(:type2_message) do
161
+ '4e544c4d53535000020000000a000a0038000000358289e2e7acb2d8d2f0e12100000000000000'\
162
+ '00aa00aa00420000000602f0230000000f4d0059004c004100420002000a004d0059004c004100'\
163
+ '420001001c00570049004e002d004400500030004d00310042004300370036003800040016006d'\
164
+ '0079006c00610062002e006c006f00630061006c0003003400570049004e002d00440050003000'\
165
+ '4d003100420043003700360038002e006d0079006c00610062002e006c006f00630061006c0005'\
166
+ '0016006d0079006c00610062002e006c006f00630061006c0007000800c0ff2034bd95d7010000'\
167
+ '0000'.unhexlify
168
+ end
169
+
170
+ it 'returns the expected type3 message' do
171
+ expect(auth_client.process_ntlm_type2(type2_message)).to start_with("NTLMSSP\x00\x03\x00\x00\x00")
172
+ end
173
+
174
+ it 'stores the session key' do
175
+ auth_client.process_ntlm_type2(type2_message)
176
+ expect(auth_client.instance_variable_get(:@session_key).size).to eq(16)
177
+ end
178
+
179
+ it 'stores the target information' do
180
+ expect(auth_client).to receive(:store_target_info)
181
+ auth_client.process_ntlm_type2(type2_message)
182
+ end
183
+
184
+ it 'stores the OS version' do
185
+ auth_client.process_ntlm_type2(type2_message)
186
+ expect(auth_client.os_version).to eq('6.2.9200')
187
+ end
188
+ end
189
+
190
+ describe '#send_auth3' do
191
+ let(:auth) { 'NTLMSSP security blob' }
192
+ let(:bindack) { RubySMB::Dcerpc::BindAck.new(auth_value: auth) }
193
+ let(:auth_type) { RubySMB::Dcerpc::RPC_C_AUTHN_WINNT }
194
+ let(:auth_level) { 0 }
195
+ let(:auth3) { double('Type3 message') }
196
+ let(:rpc_auth3) { RubySMB::Dcerpc::RpcAuth3.new }
197
+ before :example do
198
+ allow(auth_client).to receive(:process_ntlm_type2).and_return(auth3)
199
+ allow(RubySMB::Dcerpc::RpcAuth3).to receive(:new).and_return(rpc_auth3)
200
+ allow(auth_client).to receive(:add_auth_verifier)
201
+ allow(auth_client).to receive(:send_packet)
202
+ end
203
+
204
+ it 'add an auth verifier to the RpcAuth3 packet' do
205
+ auth_client.send_auth3(bindack, auth_type, auth_level)
206
+ expect(auth_client).to have_received(:add_auth_verifier).with(rpc_auth3, auth3, auth_type, auth_level)
207
+ end
208
+
209
+ it 'sets the PDUHeader #call_id to the expected value' do
210
+ auth_client.instance_variable_set(:@call_id, 56)
211
+ auth_client.send_auth3(bindack, auth_type, auth_level)
212
+ expect(rpc_auth3.pdu_header.call_id).to eq(56)
213
+ end
214
+
215
+ it 'sends the RpcAuth3 packet' do
216
+ auth_client.send_auth3(bindack, auth_type, auth_level)
217
+ expect(auth_client).to have_received(:send_packet).with(rpc_auth3)
218
+ end
219
+
220
+ it 'increments #call_id' do
221
+ auth_client.send_auth3(bindack, auth_type, auth_level)
222
+ expect(auth_client.instance_variable_get(:@call_id)).to eq(2)
223
+ end
224
+
225
+ context 'with RPC_C_AUTHN_WINNT auth_type' do
226
+ it 'processes NTLM type2 message' do
227
+ auth_client.send_auth3(bindack, auth_type, auth_level)
228
+ expect(auth_client).to have_received(:process_ntlm_type2).with(auth)
229
+ end
230
+ end
231
+ end
232
+ end
233
+
234
+ describe '#bind' do
235
+ let(:bind_req) { RubySMB::Dcerpc::Bind.new(endpoint: endpoint) }
236
+ let(:bindack_response) do
237
+ RubySMB::Dcerpc::BindAck.new({
238
+ p_result_list: {
239
+ p_results: [{
240
+ result: RubySMB::Dcerpc::BindAck::ACCEPTANCE
241
+ }]
242
+ }
243
+ })
244
+ end
245
+ before :example do
246
+ allow(RubySMB::Dcerpc::Bind).to receive(:new).and_return(bind_req)
247
+ allow(client).to receive(:send_packet)
248
+ allow(client).to receive(:recv_struct).and_return(bindack_response)
249
+ end
250
+
251
+ it 'sets the expected call_id value on the Bind request' do
252
+ client.instance_variable_set(:@call_id, 56)
253
+ client.bind
254
+ expect(bind_req.pdu_header.call_id).to eq(56)
255
+ end
256
+
257
+ it 'sends the expected Bind packet' do
258
+ client.bind
259
+ expect(client).to have_received(:send_packet).with(bind_req)
260
+ end
261
+
262
+ context 'without presentation context in the response' do
263
+ let(:bindack_response) { RubySMB::Dcerpc::BindAck.new }
264
+
265
+ it 'raises a BindError exception' do
266
+ expect { client.bind }.to raise_error(RubySMB::Dcerpc::Error::BindError)
267
+ end
268
+ end
269
+
270
+ context 'with a response that refused the presentation context' do
271
+ let(:bindack_response) do
272
+ RubySMB::Dcerpc::BindAck.new({
273
+ p_result_list: {
274
+ p_results: [{
275
+ result: RubySMB::Dcerpc::BindAck::PROVIDER_REJECTION
276
+ }]
277
+ }
278
+ })
279
+ end
280
+
281
+ it 'raises a BindError exception' do
282
+ expect { client.bind }.to raise_error(RubySMB::Dcerpc::Error::BindError)
283
+ end
284
+ end
285
+
286
+ it 'sets @max_buffer_size from the response value' do
287
+ bindack_response.max_xmit_frag = 12345
288
+ client.bind
289
+ expect(client.max_buffer_size).to eq(12345)
290
+ end
291
+
292
+ it 'sets @call_id from the response value' do
293
+ bindack_response.pdu_header.call_id = 45
294
+ client.bind
295
+ expect(client.instance_variable_get(:@call_id)).to eq(45)
296
+ end
297
+
298
+ context 'with authentication' do
299
+ subject(:client) { described_class.new(host, endpoint, username: 'testuser', password: '1234') }
300
+
301
+ context 'with RPC_C_AUTHN_WINNT auth_type' do
302
+ let(:kwargs) do {
303
+ auth_level: RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_DEFAULT,
304
+ auth_type: RubySMB::Dcerpc::RPC_C_AUTHN_WINNT
305
+ }
306
+ end
307
+ let(:type1_message) { double('type1 message') }
308
+ let(:auth) { double('auth') }
309
+ before :example do
310
+ allow(client.ntlm_client).to receive(:init_context).and_return(type1_message)
311
+ allow(type1_message).to receive(:serialize).and_return(auth)
312
+ allow(client).to receive(:add_auth_verifier)
313
+ allow(client).to receive(:send_auth3)
314
+ end
315
+
316
+ it 'raises an exception if the NTLM client is not initialized' do
317
+ client.ntlm_client = nil
318
+ expect {client.bind(**kwargs)}.to raise_error(ArgumentError)
319
+ end
320
+
321
+ it 'adds the auth verifier with a NTLM type1 message' do
322
+ client.bind(**kwargs)
323
+ expect(client).to have_received(:add_auth_verifier).with(bind_req, auth, kwargs[:auth_type], kwargs[:auth_level])
324
+ end
325
+
326
+ it 'sends an auth3 request' do
327
+ client.bind(**kwargs)
328
+ expect(client).to have_received(:send_auth3).with(bindack_response, kwargs[:auth_type], kwargs[:auth_level])
329
+ end
330
+ end
331
+ end
332
+ end
333
+
334
+ describe '#store_target_info' do
335
+ let(:target_info_str) do
336
+ '02001400540045005300540044004f004d00410049004e000100100054004500530054004e0041'\
337
+ '004d00450004002000740065007300740064006f006d00610069006e002e006c006f0063006100'\
338
+ '6c000300320074006500730074006e0061006d0065002e00740065007300740064006f006d0061'\
339
+ '0069006e002e006c006f00630061006c0005002000740065007300740066006f00720065007300'\
340
+ '74002e006c006f00630061006c0007000800513777014668d30100000000'.unhexlify
341
+ end
342
+
343
+ it 'creates a Net::NTLM::TargetInfo object from the target_info string' do
344
+ expect(Net::NTLM::TargetInfo).to receive(:new).with(target_info_str).and_call_original
345
+ client.store_target_info(target_info_str)
346
+ end
347
+
348
+ it 'sets the expected Client\'s attribute' do
349
+ client.store_target_info(target_info_str)
350
+ expect(client.default_name).to eq 'TESTNAME'
351
+ expect(client.default_domain).to eq 'TESTDOMAIN'
352
+ expect(client.dns_host_name).to eq 'testname.testdomain.local'
353
+ expect(client.dns_domain_name).to eq 'testdomain.local'
354
+ expect(client.dns_tree_name).to eq 'testforest.local'
355
+ end
356
+
357
+ it 'stores the strings with UTF-8 encoding' do
358
+ client.store_target_info(target_info_str)
359
+ expect(client.default_name.encoding.name).to eq 'UTF-8'
360
+ expect(client.default_domain.encoding.name).to eq 'UTF-8'
361
+ expect(client.dns_host_name.encoding.name).to eq 'UTF-8'
362
+ expect(client.dns_domain_name.encoding.name).to eq 'UTF-8'
363
+ expect(client.dns_tree_name.encoding.name).to eq 'UTF-8'
364
+ end
365
+ end
366
+
367
+ describe '#extract_os_version' do
368
+ it 'returns the expected version number' do
369
+ expect(client.extract_os_version("\x06\x00q\x17\x00\x00\x00\x0F")).to eq '6.0.6001'
370
+ end
371
+ end
372
+
373
+ describe '#set_integrity_privacy' do
374
+ let(:dcerpc_req) do
375
+ RubySMB::Dcerpc::Request.new(
376
+ { opnum: RubySMB::Dcerpc::Winreg::REG_ENUM_KEY },
377
+ { endpoint: 'Winreg' }
378
+ )
379
+ end
380
+ let(:auth_level) { RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_PRIVACY }
381
+ let(:auth_type) { RubySMB::Dcerpc::RPC_C_AUTHN_WINNT }
382
+ let(:session) { double('NTLM Session') }
383
+ let(:encrypted_stub) { 'Encrypted Stub'.b }
384
+ let(:auth_pad) { "\x00" * dcerpc_req.sec_trailer.auth_pad_length.to_i }
385
+ let(:signature) { 'Signature'.b }
386
+ before :example do
387
+ allow(auth_client.ntlm_client).to receive(:session).and_return(session)
388
+ # Make sure the encrypted stub includes the correct pad to make sure sec_trailer is 16-bytes aligned
389
+ allow(session).to receive(:seal_message).and_return(encrypted_stub + auth_pad)
390
+ allow(session).to receive(:sign_message).and_return(signature)
391
+ end
392
+
393
+ it 'sets the sec_trailer to the correct values' do
394
+ auth_client.set_integrity_privacy(dcerpc_req, auth_level: auth_level, auth_type: auth_type)
395
+ expect(dcerpc_req.sec_trailer.auth_type).to eq(auth_type)
396
+ expect(dcerpc_req.sec_trailer.auth_level).to eq(auth_level)
397
+ expect(dcerpc_req.sec_trailer.auth_context_id).to eq(auth_client.instance_variable_get(:@auth_ctx_id_base))
398
+ end
399
+
400
+ it 'sets the PDU header auth_length field to the signature size' do
401
+ auth_client.set_integrity_privacy(dcerpc_req, auth_level: auth_level, auth_type: auth_type)
402
+ expect(dcerpc_req.pdu_header.auth_length).to eq(signature.size)
403
+ end
404
+
405
+ it 'signs the correct message' do
406
+ plain_stub = dcerpc_req.stub.to_binary_s
407
+ auth_client.set_integrity_privacy(dcerpc_req, auth_level: auth_level, auth_type: auth_type)
408
+ expect(dcerpc_req.auth_value).to eq(signature)
409
+ expect(dcerpc_req.pdu_header.auth_length).to eq(signature.size)
410
+
411
+ # Restore the request structure to what it was before signing:
412
+ # First, remove the signature but keep the PDU header auth_length value
413
+ dcerpc_req.auth_value = ''
414
+ dcerpc_req.pdu_header.auth_length = 16
415
+ # Restore the plaintext stub
416
+ dcerpc_req.stub = plain_stub
417
+ # Restore the original fragment length: packet.size + original_signature.size
418
+ dcerpc_req.pdu_header.frag_length = dcerpc_req.num_bytes + 16
419
+ expect(session).to have_received(:sign_message).with(dcerpc_req.to_binary_s)
420
+ end
421
+
422
+ context 'without NTLM EXTENDED_SECURITY flag' do
423
+ it 'signs the correct message' do
424
+ flags = auth_client.ntlm_client.flags ^ RubySMB::NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY]
425
+ allow(auth_client.ntlm_client).to receive(:flags).and_return(flags)
426
+ plain_stub = dcerpc_req.stub.to_binary_s
427
+ auth_client.set_integrity_privacy(dcerpc_req, auth_level: auth_level, auth_type: auth_type)
428
+ expect(dcerpc_req.auth_value).to eq(signature)
429
+ expect(dcerpc_req.pdu_header.auth_length).to eq(signature.size)
430
+ expect(session).to have_received(:sign_message).with(plain_stub + auth_pad)
431
+ end
432
+ end
433
+
434
+ it 'encrypts the stub' do
435
+ plain_stub = dcerpc_req.stub.to_binary_s + dcerpc_req.auth_pad.to_binary_s
436
+ auth_client.set_integrity_privacy(dcerpc_req, auth_level: auth_level, auth_type: auth_type)
437
+ expect(dcerpc_req.stub).to eq(encrypted_stub)
438
+ expect(session).to have_received(:seal_message).with(plain_stub)
439
+ end
440
+
441
+ context 'without RPC_C_AUTHN_LEVEL_PKT_PRIVACY auth_level' do
442
+ it 'does not encrypt the stub' do
443
+ plain_stub = dcerpc_req.stub.to_binary_s
444
+ auth_client.set_integrity_privacy(dcerpc_req, auth_level: RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, auth_type: auth_type)
445
+ expect(dcerpc_req.stub.to_binary_s).to eq(plain_stub)
446
+ expect(session).to_not have_received(:seal_message)
447
+ end
448
+ end
449
+
450
+ context 'with an unsupported auth_level' do
451
+ it 'raises an Argument exception' do
452
+ expect { auth_client.set_integrity_privacy(dcerpc_req, auth_level: auth_level, auth_type: 88) }.to raise_error(ArgumentError)
453
+ end
454
+ end
455
+ end
456
+
457
+ describe '#dcerpc_request' do
458
+ let(:request_stub) { RubySMB::Dcerpc::Winreg::EnumKeyRequest.new(lp_name: 'Test Name Request') }
459
+ let(:dcerpc_req) do
460
+ RubySMB::Dcerpc::Request.new(
461
+ { opnum: RubySMB::Dcerpc::Winreg::REG_ENUM_KEY },
462
+ { endpoint: endpoint }
463
+ )
464
+ end
465
+ let(:response_stub) { RubySMB::Dcerpc::Winreg::EnumKeyResponse.new(lp_name: 'Test Name Response') }
466
+ let(:dcerpc_res) { RubySMB::Dcerpc::Response.new(stub: response_stub.to_binary_s) }
467
+ before :example do
468
+ allow(RubySMB::Dcerpc::Request).to receive(:new).and_return(dcerpc_req)
469
+ allow(client).to receive(:send_packet)
470
+ allow(client).to receive(:recv_struct).and_return(dcerpc_res)
471
+ end
472
+
473
+ it 'returns the correct response stub' do
474
+ expect(client.dcerpc_request(request_stub)).to eq(response_stub.to_binary_s)
475
+ end
476
+
477
+ it 'sends the correct Request packet' do
478
+ client.dcerpc_request(request_stub)
479
+ expect(client).to have_received(:send_packet).with(dcerpc_req)
480
+ end
481
+
482
+ it 'receives the correct packet' do
483
+ client.dcerpc_request(request_stub)
484
+ expect(client).to have_received(:recv_struct).with(RubySMB::Dcerpc::Response)
485
+ end
486
+
487
+ context 'when the first packet is not the first fragment' do
488
+ it 'raises an InvalidPacket exception' do
489
+ dcerpc_res.pdu_header.pfc_flags.first_frag = 0
490
+ expect { client.dcerpc_request(request_stub) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
491
+ end
492
+ end
493
+
494
+ context 'when requiring privacy' do
495
+ let(:auth_level) { RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_PRIVACY }
496
+ let(:auth_type) { RubySMB::Dcerpc::RPC_C_AUTHN_WINNT }
497
+ before :example do
498
+ allow(client).to receive(:set_integrity_privacy)
499
+ allow(client).to receive(:handle_integrity_privacy)
500
+ end
501
+
502
+ it 'sets integrity and privacy' do
503
+ client.dcerpc_request(request_stub, auth_level: auth_level, auth_type: auth_type)
504
+ expect(client).to have_received(:set_integrity_privacy).with(dcerpc_req, auth_level: auth_level, auth_type: auth_type)
505
+ end
506
+
507
+ it 'processes the integrity and privacy information from the response' do
508
+ client.dcerpc_request(request_stub, auth_level: auth_level, auth_type: auth_type)
509
+ expect(client).to have_received(:handle_integrity_privacy).with(dcerpc_res, auth_level: auth_level, auth_type: auth_type)
510
+ end
511
+ end
512
+
513
+ context 'with a fragmented response' do
514
+ let(:nb_of_fragments) { 3 }
515
+ before :example do
516
+ dcerpc_res.pdu_header.pfc_flags.last_frag = 0
517
+ count = 0
518
+ allow(client).to receive(:recv_struct) do
519
+ count += 1
520
+ dcerpc_res.pdu_header.pfc_flags.last_frag = 1 if count == nb_of_fragments
521
+ dcerpc_res
522
+ end
523
+ end
524
+
525
+ it 'receives the correct number of fragments' do
526
+ client.dcerpc_request(request_stub)
527
+ expect(client).to have_received(:recv_struct).exactly(nb_of_fragments).times.with(RubySMB::Dcerpc::Response)
528
+ end
529
+
530
+ it 'returns the correct stub' do
531
+ expect(client.dcerpc_request(request_stub)).to eq(response_stub.to_binary_s * nb_of_fragments)
532
+ end
533
+
534
+ context 'when requiring privacy' do
535
+ let(:auth_level) { RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_PRIVACY }
536
+ let(:auth_type) { RubySMB::Dcerpc::RPC_C_AUTHN_WINNT }
537
+ before :example do
538
+ allow(client).to receive(:set_integrity_privacy)
539
+ allow(client).to receive(:handle_integrity_privacy)
540
+ end
541
+
542
+ it 'processes the integrity and privacy information for each fragment' do
543
+ client.dcerpc_request(request_stub, auth_level: auth_level, auth_type: auth_type)
544
+ expect(client).to have_received(:recv_struct).exactly(nb_of_fragments).times.with(RubySMB::Dcerpc::Response)
545
+ expect(client).to have_received(:handle_integrity_privacy).exactly(nb_of_fragments).times.with(dcerpc_res, auth_level: auth_level, auth_type: auth_type)
546
+ end
547
+ end
548
+ end
549
+ end
550
+
551
+ describe '#send_packet' do
552
+ let(:packet) { double('Packet') }
553
+ let(:data) { 'My data to be sent' }
554
+ let(:sock) { double('Socket') }
555
+ before :example do
556
+ client.tcp_socket = sock
557
+ allow(packet).to receive(:to_binary_s).and_return(data)
558
+ allow(sock).to receive(:write).and_return(data.size)
559
+ end
560
+
561
+ it 'sends the correct data' do
562
+ client.send_packet(packet)
563
+ expect(sock).to have_received(:write).with(data)
564
+ end
565
+
566
+ context 'when sending multiple chunks of data' do
567
+ it 'sends the correct chunks of data' do
568
+ chunk_size = 6
569
+ allow(sock).to receive(:write).and_return(chunk_size)
570
+ data_sent = data.dup
571
+ client.send_packet(packet)
572
+ loop do
573
+ break if data_sent.empty?
574
+ expect(sock).to have_received(:write).with(data_sent)
575
+ data_sent = data_sent[chunk_size..-1]
576
+ end
577
+ end
578
+ end
579
+
580
+ context 'when an error occurs with the socket' do
581
+ it 'raises a CommunicationError' do
582
+ allow(sock).to receive(:write).and_raise (Errno::EPIPE)
583
+ expect { client.send_packet(packet) }.to raise_error(RubySMB::Dcerpc::Error::CommunicationError)
584
+ end
585
+ end
586
+ end
587
+
588
+ describe '#recv_struct' do
589
+ let(:socket) { double('Socket') }
590
+ let(:struct) { RubySMB::Dcerpc::Request }
591
+ let(:response) { RubySMB::Dcerpc::Response.new(pdu_header: { ptype: struct::PTYPE}) }
592
+ before :example do
593
+ client.tcp_socket = socket
594
+ allow(socket).to receive(:closed?).and_return false
595
+ allow(IO).to receive(:select).and_return [[client.tcp_socket], [], []]
596
+ allow(struct).to receive(:read).and_return(response)
597
+ end
598
+
599
+ it 'reads the socket' do
600
+ client.recv_struct(struct)
601
+ expect(struct).to have_received(:read).with(socket)
602
+ end
603
+
604
+ context 'when the socket is already closed' do
605
+ it 'raises a CommunicationError' do
606
+ allow(socket).to receive(:closed?).and_return true
607
+ expect { client.recv_struct(struct) }.to raise_error(RubySMB::Dcerpc::Error::CommunicationError)
608
+ end
609
+ end
610
+
611
+ context 'when the read timeout expires' do
612
+ it 'raises a CommunicationError' do
613
+ allow(IO).to receive(:select).and_return nil
614
+ expect { client.recv_struct(struct) }.to raise_error(RubySMB::Dcerpc::Error::CommunicationError)
615
+ end
616
+ end
617
+
618
+ context 'when an error occurs when reading the socket' do
619
+ it 'raises an InvalidPacket' do
620
+ allow(struct).to receive(:read).and_raise(IOError)
621
+ expect { client.recv_struct(struct) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
622
+ end
623
+ end
624
+
625
+ context 'when response has a wrong ptype' do
626
+ it 'raises an InvalidPacket' do
627
+ response.pdu_header.ptype = struct::PTYPE + 1
628
+ expect { client.recv_struct(struct) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
629
+ end
630
+ end
631
+
632
+ context 'when the read timeout expires' do
633
+ it 'raises a CommunicationError' do
634
+ allow(struct).to receive(:read).and_raise (Errno::EPIPE)
635
+ expect { client.recv_struct(struct) }.to raise_error(RubySMB::Dcerpc::Error::CommunicationError)
636
+ end
637
+ end
638
+ end
639
+
640
+ describe '#handle_integrity_privacy' do
641
+ let(:stub) { 'Encrypted Stub' }
642
+ let(:dcerpc_res) do
643
+ RubySMB::Dcerpc::Response.new(
644
+ stub: stub,
645
+ auth_value: signature
646
+ )
647
+ end
648
+ let(:auth_level) { RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_PRIVACY }
649
+ let(:auth_type) { RubySMB::Dcerpc::RPC_C_AUTHN_WINNT }
650
+ let(:session) { double('NTLM Session') }
651
+ let(:decrypted_stub) { 'Decrypted Stub'.b }
652
+ let(:auth_pad) { "\x00" * dcerpc_res.sec_trailer.auth_pad_length.to_i }
653
+ let(:signature) { 'Signature'.b }
654
+ before :example do
655
+ allow(auth_client.ntlm_client).to receive(:session).and_return(session)
656
+ # Make sure the encrypted stub includes the correct pad to make sure sec_trailer is 16-bytes aligned
657
+ allow(session).to receive(:unseal_message).and_return(decrypted_stub + auth_pad)
658
+ allow(session).to receive(:verify_signature).and_return true
659
+ end
660
+
661
+ it 'verifies the signature' do
662
+ auth_client.handle_integrity_privacy(dcerpc_res, auth_level: auth_level, auth_type: auth_type)
663
+ data_to_check = dcerpc_res.to_binary_s[0..-(dcerpc_res.pdu_header.auth_length + 1)]
664
+ expect(session).to have_received(:verify_signature).with(signature, data_to_check)
665
+ end
666
+
667
+ context 'without NTLM EXTENDED_SECURITY flag' do
668
+ it 'verifies the signature against the correct data' do
669
+ flags = auth_client.ntlm_client.flags ^ RubySMB::NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY]
670
+ allow(auth_client.ntlm_client).to receive(:flags).and_return(flags)
671
+ auth_client.handle_integrity_privacy(dcerpc_res, auth_level: auth_level, auth_type: auth_type)
672
+ data_to_check = dcerpc_res.stub.to_binary_s
673
+ expect(session).to have_received(:verify_signature).with(signature, data_to_check)
674
+ end
675
+ end
676
+
677
+ context 'when raise_signature_error is set and the signature is wrong' do
678
+ it 'raises an InvalidPacket exception' do
679
+ allow(session).to receive(:verify_signature).and_return false
680
+ expect {
681
+ auth_client.handle_integrity_privacy(
682
+ dcerpc_res,
683
+ auth_level: auth_level,
684
+ auth_type: auth_type,
685
+ raise_signature_error: true
686
+ )
687
+ }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
688
+ end
689
+ end
690
+
691
+ it 'decrypts the stub' do
692
+ encrypted_stub = dcerpc_res.stub.to_binary_s + dcerpc_res.auth_pad.to_binary_s
693
+ auth_client.handle_integrity_privacy(dcerpc_res, auth_level: auth_level, auth_type: auth_type)
694
+ expect(dcerpc_res.stub).to eq(decrypted_stub)
695
+ expect(session).to have_received(:unseal_message).with(encrypted_stub)
696
+ end
697
+
698
+ context 'without RPC_C_AUTHN_LEVEL_PKT_PRIVACY auth_level' do
699
+ it 'does not encrypt the stub' do
700
+ plain_stub = dcerpc_res.stub.to_binary_s
701
+ auth_client.handle_integrity_privacy(dcerpc_res, auth_level: RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, auth_type: auth_type)
702
+ expect(dcerpc_res.stub).to eq(plain_stub)
703
+ expect(session).to_not have_received(:unseal_message)
704
+ end
705
+ end
706
+
707
+ context 'with an unsupported auth_level' do
708
+ it 'raises an Argument exception' do
709
+ expect { auth_client.handle_integrity_privacy(dcerpc_res, auth_level: auth_level, auth_type: 88) }.to raise_error(ArgumentError)
710
+ end
711
+ end
712
+ end
713
+ end
714
+