ruby_smb 2.0.12 → 2.0.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (194) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/verify.yml +1 -1
  4. data/examples/dump_secrets_from_sid.rb +207 -0
  5. data/examples/enum_domain_users.rb +75 -0
  6. data/examples/get_computer_info.rb +42 -0
  7. data/examples/query_service_status.rb +42 -4
  8. data/lib/ruby_smb/client.rb +3 -14
  9. data/lib/ruby_smb/dcerpc/bind.rb +28 -20
  10. data/lib/ruby_smb/dcerpc/bind_ack.rb +29 -28
  11. data/lib/ruby_smb/dcerpc/client.rb +542 -0
  12. data/lib/ruby_smb/dcerpc/drsr/drs_bind_request.rb +24 -0
  13. data/lib/ruby_smb/dcerpc/drsr/drs_bind_response.rb +26 -0
  14. data/lib/ruby_smb/dcerpc/drsr/drs_crack_names_request.rb +57 -0
  15. data/lib/ruby_smb/dcerpc/drsr/drs_crack_names_response.rb +76 -0
  16. data/lib/ruby_smb/dcerpc/drsr/drs_domain_controller_info_request.rb +46 -0
  17. data/lib/ruby_smb/dcerpc/drsr/drs_domain_controller_info_response.rb +168 -0
  18. data/lib/ruby_smb/dcerpc/drsr/drs_extensions.rb +56 -0
  19. data/lib/ruby_smb/dcerpc/drsr/drs_get_nc_changes_request.rb +121 -0
  20. data/lib/ruby_smb/dcerpc/drsr/drs_get_nc_changes_response.rb +118 -0
  21. data/lib/ruby_smb/dcerpc/drsr/drs_unbind_request.rb +24 -0
  22. data/lib/ruby_smb/dcerpc/drsr/drs_unbind_response.rb +26 -0
  23. data/lib/ruby_smb/dcerpc/drsr.rb +909 -0
  24. data/lib/ruby_smb/dcerpc/epm/epm_ept_map_request.rb +26 -0
  25. data/lib/ruby_smb/dcerpc/epm/epm_ept_map_response.rb +25 -0
  26. data/lib/ruby_smb/dcerpc/epm/epm_twrt.rb +211 -0
  27. data/lib/ruby_smb/dcerpc/epm.rb +75 -0
  28. data/lib/ruby_smb/dcerpc/error.rb +17 -0
  29. data/lib/ruby_smb/dcerpc/ndr.rb +1159 -297
  30. data/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_request.rb +3 -13
  31. data/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_response.rb +3 -3
  32. data/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_request.rb +3 -13
  33. data/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_response.rb +1 -1
  34. data/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_request.rb +3 -11
  35. data/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_response.rb +1 -1
  36. data/lib/ruby_smb/dcerpc/netlogon.rb +5 -4
  37. data/lib/ruby_smb/dcerpc/p_syntax_id_t.rb +4 -3
  38. data/lib/ruby_smb/dcerpc/pdu_header.rb +7 -7
  39. data/lib/ruby_smb/dcerpc/ptypes.rb +1 -0
  40. data/lib/ruby_smb/dcerpc/request.rb +79 -32
  41. data/lib/ruby_smb/dcerpc/response.rb +45 -10
  42. data/lib/ruby_smb/dcerpc/rpc_auth3.rb +28 -0
  43. data/lib/ruby_smb/dcerpc/rpc_security_attributes.rb +11 -11
  44. data/lib/ruby_smb/dcerpc/rrp_rpc_unicode_string.rb +118 -0
  45. data/lib/ruby_smb/dcerpc/samr/rpc_sid.rb +150 -0
  46. data/lib/ruby_smb/dcerpc/samr/samr_close_handle_request.rb +23 -0
  47. data/lib/ruby_smb/dcerpc/samr/samr_close_handle_response.rb +24 -0
  48. data/lib/ruby_smb/dcerpc/samr/samr_connect_request.rb +32 -0
  49. data/lib/ruby_smb/dcerpc/samr/samr_connect_response.rb +23 -0
  50. data/lib/ruby_smb/dcerpc/samr/samr_enumerate_users_in_domain_request.rb +26 -0
  51. data/lib/ruby_smb/dcerpc/samr/samr_enumerate_users_in_domain_response.rb +55 -0
  52. data/lib/ruby_smb/dcerpc/samr/samr_get_alias_membership_request.rb +48 -0
  53. data/lib/ruby_smb/dcerpc/samr/samr_get_alias_membership_response.rb +38 -0
  54. data/lib/ruby_smb/dcerpc/samr/samr_get_groups_for_user_request.rb +23 -0
  55. data/lib/ruby_smb/dcerpc/samr/samr_get_groups_for_user_response.rb +48 -0
  56. data/lib/ruby_smb/dcerpc/samr/samr_lookup_domain_in_sam_server_request.rb +24 -0
  57. data/lib/ruby_smb/dcerpc/samr/samr_lookup_domain_in_sam_server_response.rb +25 -0
  58. data/lib/ruby_smb/dcerpc/samr/samr_open_domain_request.rb +27 -0
  59. data/lib/ruby_smb/dcerpc/samr/samr_open_domain_response.rb +24 -0
  60. data/lib/ruby_smb/dcerpc/samr/samr_open_user_request.rb +26 -0
  61. data/lib/ruby_smb/dcerpc/samr/samr_open_user_response.rb +24 -0
  62. data/lib/ruby_smb/dcerpc/samr/samr_rid_to_sid_request.rb +23 -0
  63. data/lib/ruby_smb/dcerpc/samr/samr_rid_to_sid_response.rb +23 -0
  64. data/lib/ruby_smb/dcerpc/samr.rb +613 -0
  65. data/lib/ruby_smb/dcerpc/sec_trailer.rb +26 -0
  66. data/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all.rb +56 -79
  67. data/lib/ruby_smb/dcerpc/srvsvc.rb +27 -4
  68. data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request.rb +13 -25
  69. data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response.rb +2 -2
  70. data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response.rb +1 -1
  71. data/lib/ruby_smb/dcerpc/svcctl/control_service_request.rb +1 -1
  72. data/lib/ruby_smb/dcerpc/svcctl/control_service_response.rb +1 -1
  73. data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request.rb +4 -14
  74. data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response.rb +1 -1
  75. data/lib/ruby_smb/dcerpc/svcctl/open_service_w_request.rb +3 -11
  76. data/lib/ruby_smb/dcerpc/svcctl/open_service_w_response.rb +1 -1
  77. data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request.rb +1 -1
  78. data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response.rb +12 -11
  79. data/lib/ruby_smb/dcerpc/svcctl/query_service_status_response.rb +1 -1
  80. data/lib/ruby_smb/dcerpc/svcctl/service_status.rb +9 -8
  81. data/lib/ruby_smb/dcerpc/svcctl/start_service_w_request.rb +3 -3
  82. data/lib/ruby_smb/dcerpc/svcctl/start_service_w_response.rb +1 -1
  83. data/lib/ruby_smb/dcerpc/svcctl.rb +1 -3
  84. data/lib/ruby_smb/dcerpc/uuid.rb +3 -0
  85. data/lib/ruby_smb/dcerpc/winreg/close_key_response.rb +2 -2
  86. data/lib/ruby_smb/dcerpc/winreg/create_key_request.rb +2 -13
  87. data/lib/ruby_smb/dcerpc/winreg/create_key_response.rb +3 -3
  88. data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +3 -20
  89. data/lib/ruby_smb/dcerpc/winreg/enum_key_response.rb +3 -20
  90. data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +5 -14
  91. data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +5 -14
  92. data/lib/ruby_smb/dcerpc/winreg/open_key_request.rb +1 -9
  93. data/lib/ruby_smb/dcerpc/winreg/open_key_response.rb +4 -3
  94. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +5 -6
  95. data/lib/ruby_smb/dcerpc/winreg/open_root_key_response.rb +2 -2
  96. data/lib/ruby_smb/dcerpc/winreg/query_info_key_response.rb +9 -18
  97. data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +4 -14
  98. data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +7 -15
  99. data/lib/ruby_smb/dcerpc/winreg/regsam.rb +3 -1
  100. data/lib/ruby_smb/dcerpc/winreg/save_key_request.rb +0 -9
  101. data/lib/ruby_smb/dcerpc/winreg/save_key_response.rb +1 -1
  102. data/lib/ruby_smb/dcerpc/winreg.rb +10 -14
  103. data/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_get_info_request.rb +26 -0
  104. data/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_get_info_response.rb +88 -0
  105. data/lib/ruby_smb/dcerpc/wkssvc.rb +65 -0
  106. data/lib/ruby_smb/dcerpc.rb +41 -11
  107. data/lib/ruby_smb/field/file_time.rb +1 -1
  108. data/lib/ruby_smb/field/string16.rb +5 -1
  109. data/lib/ruby_smb/ntlm.rb +18 -2
  110. data/lib/ruby_smb/smb1/pipe.rb +4 -0
  111. data/lib/ruby_smb/smb2/pipe.rb +4 -0
  112. data/lib/ruby_smb/version.rb +1 -1
  113. data/spec/lib/ruby_smb/client_spec.rb +1 -2
  114. data/spec/lib/ruby_smb/dcerpc/bind_ack_spec.rb +69 -41
  115. data/spec/lib/ruby_smb/dcerpc/bind_spec.rb +75 -21
  116. data/spec/lib/ruby_smb/dcerpc/client_spec.rb +714 -0
  117. data/spec/lib/ruby_smb/dcerpc/drsr_spec.rb +2169 -0
  118. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +3792 -1373
  119. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_request_spec.rb +4 -4
  120. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_request_spec.rb +4 -4
  121. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_request_spec.rb +2 -2
  122. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_response_spec.rb +2 -2
  123. data/spec/lib/ruby_smb/dcerpc/p_syntax_id_t_spec.rb +18 -4
  124. data/spec/lib/ruby_smb/dcerpc/pdu_header_spec.rb +27 -1
  125. data/spec/lib/ruby_smb/dcerpc/request_spec.rb +76 -11
  126. data/spec/lib/ruby_smb/dcerpc/response_spec.rb +99 -9
  127. data/spec/lib/ruby_smb/dcerpc/rpc_auth3_spec.rb +75 -0
  128. data/spec/lib/ruby_smb/dcerpc/rpc_security_attributes_spec.rb +29 -28
  129. data/spec/lib/ruby_smb/dcerpc/rrp_rpc_unicode_string_spec.rb +340 -0
  130. data/spec/lib/ruby_smb/dcerpc/samr/rpc_sid_spec.rb +116 -0
  131. data/spec/lib/ruby_smb/dcerpc/samr/samr_close_handle_request_spec.rb +40 -0
  132. data/spec/lib/ruby_smb/dcerpc/samr/samr_close_handle_response_spec.rb +48 -0
  133. data/spec/lib/ruby_smb/dcerpc/samr/samr_connect_request_spec.rb +56 -0
  134. data/spec/lib/ruby_smb/dcerpc/samr/samr_connect_response_spec.rb +47 -0
  135. data/spec/lib/ruby_smb/dcerpc/samr/samr_enumerate_users_in_domain_request_spec.rb +63 -0
  136. data/spec/lib/ruby_smb/dcerpc/samr/samr_enumerate_users_in_domain_response_spec.rb +265 -0
  137. data/spec/lib/ruby_smb/dcerpc/samr/samr_lookup_domain_in_sam_server_request_spec.rb +52 -0
  138. data/spec/lib/ruby_smb/dcerpc/samr/samr_lookup_domain_in_sam_server_response_spec.rb +36 -0
  139. data/spec/lib/ruby_smb/dcerpc/samr/samr_open_domain_request_spec.rb +56 -0
  140. data/spec/lib/ruby_smb/dcerpc/samr/samr_open_domain_response_spec.rb +48 -0
  141. data/spec/lib/ruby_smb/dcerpc/samr/samr_rid_to_sid_request_spec.rb +48 -0
  142. data/spec/lib/ruby_smb/dcerpc/samr/samr_rid_to_sid_response_spec.rb +42 -0
  143. data/spec/lib/ruby_smb/dcerpc/samr_spec.rb +420 -0
  144. data/spec/lib/ruby_smb/dcerpc/sec_trailer_spec.rb +92 -0
  145. data/spec/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all_spec.rb +149 -110
  146. data/spec/lib/ruby_smb/dcerpc/srvsvc_spec.rb +21 -17
  147. data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request_spec.rb +56 -79
  148. data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response_spec.rb +4 -4
  149. data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response_spec.rb +2 -2
  150. data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_request_spec.rb +2 -2
  151. data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_response_spec.rb +2 -2
  152. data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request_spec.rb +19 -29
  153. data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response_spec.rb +2 -2
  154. data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_request_spec.rb +9 -15
  155. data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_response_spec.rb +2 -2
  156. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request_spec.rb +2 -2
  157. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response_spec.rb +22 -22
  158. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_response_spec.rb +2 -2
  159. data/spec/lib/ruby_smb/dcerpc/svcctl/service_status_spec.rb +18 -14
  160. data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_request_spec.rb +5 -4
  161. data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_response_spec.rb +2 -2
  162. data/spec/lib/ruby_smb/dcerpc/svcctl_spec.rb +1 -5
  163. data/spec/lib/ruby_smb/dcerpc/uuid_spec.rb +15 -23
  164. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_response_spec.rb +2 -2
  165. data/spec/lib/ruby_smb/dcerpc/winreg/create_key_request_spec.rb +4 -41
  166. data/spec/lib/ruby_smb/dcerpc/winreg/create_key_response_spec.rb +4 -4
  167. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +4 -52
  168. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_response_spec.rb +4 -56
  169. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +10 -34
  170. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +10 -34
  171. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_request_spec.rb +2 -26
  172. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_response_spec.rb +2 -2
  173. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +17 -25
  174. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_response_spec.rb +2 -2
  175. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_response_spec.rb +20 -44
  176. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +8 -32
  177. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +10 -22
  178. data/spec/lib/ruby_smb/dcerpc/winreg/regsam_spec.rb +4 -0
  179. data/spec/lib/ruby_smb/dcerpc/winreg/save_key_request_spec.rb +0 -12
  180. data/spec/lib/ruby_smb/dcerpc/winreg/save_key_response_spec.rb +2 -2
  181. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +18 -47
  182. data/spec/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_get_info_request_spec.rb +43 -0
  183. data/spec/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_get_info_response_spec.rb +410 -0
  184. data/spec/lib/ruby_smb/dcerpc/wkssvc_spec.rb +70 -0
  185. data/spec/lib/ruby_smb/field/string16_spec.rb +22 -0
  186. data/spec/lib/ruby_smb/gss/provider/ntlm/os_version_spec.rb +1 -1
  187. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +18 -37
  188. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +18 -16
  189. data/spec/support/bin_helper.rb +9 -0
  190. data.tar.gz.sig +0 -0
  191. metadata +96 -5
  192. metadata.gz.sig +0 -0
  193. data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +0 -38
  194. data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +0 -135
@@ -0,0 +1,542 @@
1
+ module RubySMB
2
+ module Dcerpc
3
+
4
+ # Represents DCERPC SMB client capable of talking to an RPC endpoint in stand-alone.
5
+ class Client
6
+ require 'bindata'
7
+ require 'windows_error'
8
+ require 'net/ntlm'
9
+ require 'ruby_smb/dcerpc'
10
+ require 'ruby_smb/gss'
11
+
12
+ include Epm
13
+
14
+ # The default maximum size of a RPC message that the Client accepts (in bytes)
15
+ MAX_BUFFER_SIZE = 64512
16
+ # The read timeout when receiving packets.
17
+ READ_TIMEOUT = 30
18
+ # The default Endpoint Mapper port
19
+ ENDPOINT_MAPPER_PORT = 135
20
+
21
+ # The domain you're trying to authenticate to
22
+ # @!attribute [rw] domain
23
+ # @return [String]
24
+ attr_accessor :domain
25
+
26
+ # The local workstation to pretend to be
27
+ # @!attribute [rw] local_workstation
28
+ # @return [String]
29
+ attr_accessor :local_workstation
30
+
31
+ # The NTLM client used for authentication
32
+ # @!attribute [rw] ntlm_client
33
+ # @return [String]
34
+ attr_accessor :ntlm_client
35
+
36
+ # The username to authenticate with
37
+ # @!attribute [rw] username
38
+ # @return [String]
39
+ attr_accessor :username
40
+
41
+ # The password to authenticate with
42
+ # @!attribute [rw] password
43
+ # @return [String]
44
+ attr_accessor :password
45
+
46
+ # The Netbios Name of the Peer/Server.
47
+ # @!attribute [rw] default_name
48
+ # @return [String]
49
+ attr_accessor :default_name
50
+
51
+ # The Netbios Domain of the Peer/Server.
52
+ # @!attribute [rw] default_domain
53
+ # @return [String]
54
+ attr_accessor :default_domain
55
+
56
+ # The Fully Qualified Domain Name (FQDN) of the computer.
57
+ # @!attribute [rw] dns_host_name
58
+ # @return [String]
59
+ attr_accessor :dns_host_name
60
+
61
+ # The Fully Qualified Domain Name (FQDN) of the domain.
62
+ # @!attribute [rw] dns_domain_name
63
+ # @return [String]
64
+ attr_accessor :dns_domain_name
65
+
66
+ # The Fully Qualified Domain Name (FQDN) of the forest.
67
+ # @!attribute [rw] dns_tree_name
68
+ # @return [String]
69
+ attr_accessor :dns_tree_name
70
+
71
+ # The OS version number (<major>.<minor>.<build>) of the Peer/Server.
72
+ # @!attribute [rw] os_version
73
+ # @return [String]
74
+ attr_accessor :os_version
75
+
76
+ # The maximum size SMB message that the Client accepts (in bytes)
77
+ # The default value is equal to {MAX_BUFFER_SIZE}.
78
+ # @!attribute [rw] max_buffer_size
79
+ # @return [Integer]
80
+ attr_accessor :max_buffer_size
81
+
82
+ # The TCP socket to connect to the remote host
83
+ # @!attribute [rw] tcp_socket
84
+ # @return [TcpSocket]
85
+ attr_accessor :tcp_socket
86
+
87
+
88
+ # @param host [String] The remote host
89
+ # @param endpoint [Module] A module endpoint that defines UUID, VER_MAJOR and
90
+ # VER_MINOR constants (e.g. Drsr)
91
+ # @param tcp_socket [TcpSocket] The socket to use. If not provided, a new
92
+ # socket will be created when calling #connect
93
+ # @param read_timeout [Integer] The read timeout value to use
94
+ # @param username [String] The username to authenticate with, if needed
95
+ # @param password [String] The password to authenticate with, if needed.
96
+ # Note that a NTLM hash can be used instead of a password.
97
+ # @param domain [String] The domain to authenticate to, if needed
98
+ # @param local_workstation [String] The workstation name to authenticate to, if needed
99
+ # @param ntlm_flags [Integer] The flags to pass to the Net:NTLM client
100
+ def initialize(host,
101
+ endpoint,
102
+ tcp_socket: nil,
103
+ read_timeout: READ_TIMEOUT,
104
+ username: '',
105
+ password: '',
106
+ domain: '.',
107
+ local_workstation: 'WORKSTATION',
108
+ ntlm_flags: NTLM::DEFAULT_CLIENT_FLAGS)
109
+
110
+ @endpoint = endpoint
111
+ extend @endpoint
112
+
113
+ @host = host
114
+ @tcp_socket = tcp_socket
115
+ @read_timeout = read_timeout
116
+ @domain = domain
117
+ @local_workstation = local_workstation
118
+ @username = username.encode('utf-8')
119
+ @password = password.encode('utf-8')
120
+ @max_buffer_size = MAX_BUFFER_SIZE
121
+ @call_id = 1
122
+ @ctx_id = 0
123
+ @auth_ctx_id_base = rand(0xFFFFFFFF)
124
+
125
+ unless username.empty? && password.empty?
126
+ @ntlm_client = Net::NTLM::Client.new(
127
+ @username,
128
+ @password,
129
+ workstation: @local_workstation,
130
+ domain: @domain,
131
+ flags: ntlm_flags
132
+ )
133
+ end
134
+ end
135
+
136
+ # Connect to the RPC endpoint. If a TCP socket was not provided, it takes
137
+ # care of asking the Enpoint Mapper Interface the port used by the given
138
+ # endpoint provided in #initialize and connect a TCP socket
139
+ #
140
+ # @param port [Integer] An optional port number to connect to. If
141
+ # provided, it will not ask the Enpoint Mapper Interface for a port
142
+ # number.
143
+ # @return [TcpSocket] The connected TCP socket
144
+ def connect(port: nil)
145
+ return if @tcp_socket
146
+ unless port
147
+ @tcp_socket = TCPSocket.new(@host, ENDPOINT_MAPPER_PORT)
148
+ bind(endpoint: Epm)
149
+ begin
150
+ host_port = get_host_port_from_ept_mapper(
151
+ uuid: @endpoint::UUID,
152
+ maj_ver: @endpoint::VER_MAJOR,
153
+ min_ver: @endpoint::VER_MINOR
154
+ )
155
+ rescue RubySMB::Dcerpc::Error::DcerpcError => e
156
+ e.message.prepend(
157
+ "Cannot resolve the remote port number for endpoint #{@endpoint::UUID}. "\
158
+ "Set @tcp_socket parameter to specify the service port number and bypass "\
159
+ "EPM port resolution. Error: "
160
+ )
161
+ raise e
162
+ end
163
+ port = host_port[:port]
164
+ @tcp_socket.close
165
+ @tcp_socket = nil
166
+ end
167
+ @tcp_socket = TCPSocket.new(@host, port)
168
+ end
169
+
170
+ # Close the TCP Socket
171
+ def close
172
+ @tcp_socket.close if @tcp_socket && !@tcp_socket.closed?
173
+ end
174
+
175
+ # Add the authentication verifier to the packet. This includes a sec
176
+ # trailer and the actual authentication data.
177
+ #
178
+ # @param req [BinData::Record] the request to be updated
179
+ # @param auth [String] the authentication data
180
+ # @param auth_type [Integer] the authentication type
181
+ # @param auth_level [Integer] the authentication level
182
+ def add_auth_verifier(req, auth, auth_type, auth_level)
183
+ req.sec_trailer = {
184
+ auth_type: auth_type,
185
+ auth_level: auth_level,
186
+ auth_context_id: @ctx_id + @auth_ctx_id_base
187
+ }
188
+ req.auth_value = auth
189
+ req.pdu_header.auth_length = auth.length
190
+
191
+ nil
192
+ end
193
+
194
+ def process_ntlm_type2(type2_message)
195
+ ntlmssp_offset = type2_message.index('NTLMSSP')
196
+ type2_blob = type2_message.slice(ntlmssp_offset..-1)
197
+ type2_b64_message = [type2_blob].pack('m')
198
+ type3_message = @ntlm_client.init_context(type2_b64_message)
199
+ auth3 = type3_message.serialize
200
+
201
+ @session_key = @ntlm_client.session_key
202
+ challenge_message = @ntlm_client.session.challenge_message
203
+ store_target_info(challenge_message.target_info) if challenge_message.has_flag?(:TARGET_INFO)
204
+ @os_version = extract_os_version(challenge_message.os_version.to_s) unless challenge_message.os_version.empty?
205
+ auth3
206
+ end
207
+
208
+ # Send a rpc_auth3 PDU that ends the authentication handshake.
209
+ #
210
+ # @param response [BindAck] the BindAck response packet
211
+ # @param auth_type [Integer] the authentication type
212
+ # @param auth_level [Integer] the authentication level
213
+ # @raise [ArgumentError] if `:auth_type` is unknown
214
+ # @raise [NotImplementedError] if `:auth_type` is not implemented (yet)
215
+ def send_auth3(response, auth_type, auth_level)
216
+ case auth_type
217
+ when RPC_C_AUTHN_NONE
218
+ when RPC_C_AUTHN_WINNT, RPC_C_AUTHN_DEFAULT
219
+ auth3 = process_ntlm_type2(response.auth_value)
220
+ when RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_GSS_SCHANNEL, RPC_C_AUTHN_GSS_KERBEROS
221
+ # TODO
222
+ raise NotImplementedError
223
+ else
224
+ raise ArgumentError, "Unsupported Auth Type: #{auth_type}"
225
+ end
226
+
227
+ rpc_auth3 = RpcAuth3.new
228
+ add_auth_verifier(rpc_auth3, auth3, auth_type, auth_level)
229
+ rpc_auth3.pdu_header.call_id = @call_id
230
+
231
+ # The server should not respond
232
+ send_packet(rpc_auth3)
233
+ @call_id += 1
234
+
235
+ nil
236
+ end
237
+
238
+ # Bind to the remote server interface endpoint. It takes care of adding
239
+ # the necessary authentication verifier if `:auth_level` is set to
240
+ # anything different than RPC_C_AUTHN_LEVEL_NONE
241
+ #
242
+ # @param endpoint [Module] the endpoint to bind to. This must be a Dcerpc
243
+ # class with UUID, VER_MAJOR and VER_MINOR constants defined.
244
+ # @param auth_level [Integer] the authentication level
245
+ # @param auth_type [Integer] the authentication type
246
+ # @return [BindAck] the BindAck response packet
247
+ # @raise [Error::InvalidPacket] if an invalid packet is received
248
+ # @raise [Error::BindError] if the response is not a BindAck packet or if the Bind result code is not ACCEPTANCE
249
+ # @raise [ArgumentError] if `:auth_type` is unknown
250
+ # @raise [NotImplementedError] if `:auth_type` is not implemented (yet)
251
+ def bind(endpoint: @endpoint, auth_level: RPC_C_AUTHN_LEVEL_NONE, auth_type: nil)
252
+ bind_req = Bind.new(endpoint: endpoint)
253
+ bind_req.pdu_header.call_id = @call_id
254
+ # TODO: evasion: generate random UUIDs for bogus binds
255
+
256
+ if auth_level && auth_level != RPC_C_AUTHN_LEVEL_NONE
257
+ case auth_type
258
+ when RPC_C_AUTHN_WINNT, RPC_C_AUTHN_DEFAULT
259
+ raise ArgumentError, "NTLM Client not initialized. Username and password must be provided" unless @ntlm_client
260
+ type1_message = @ntlm_client.init_context
261
+ auth = type1_message.serialize
262
+ when RPC_C_AUTHN_GSS_KERBEROS, RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_GSS_NEGOTIATE
263
+ when RPC_C_AUTHN_GSS_KERBEROS, RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_GSS_SCHANNEL
264
+ # TODO
265
+ raise NotImplementedError
266
+ else
267
+ raise ArgumentError, "Unsupported Auth Type: #{auth_type}"
268
+ end
269
+ add_auth_verifier(bind_req, auth, auth_type, auth_level)
270
+ end
271
+
272
+ send_packet(bind_req)
273
+ bindack_response = recv_struct(BindAck)
274
+ # TODO: see if BindNack response should be handled too
275
+
276
+ res_list = bindack_response.p_result_list
277
+ if res_list.n_results == 0 ||
278
+ res_list.p_results[0].result != BindAck::ACCEPTANCE
279
+ raise Error::BindError,
280
+ "Bind Failed (Result: #{res_list.p_results[0].result}, Reason: #{res_list.p_results[0].reason})"
281
+ end
282
+
283
+ @max_buffer_size = bindack_response.max_xmit_frag
284
+ @call_id = bindack_response.pdu_header.call_id
285
+
286
+ if auth_level && auth_level != RPC_C_AUTHN_LEVEL_NONE
287
+ # The number of legs needed to build the security context is defined
288
+ # by the security provider
289
+ # (see [2.2.1.1.7 Security Providers](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/d4097450-c62f-484b-872f-ddf59a7a0d36))
290
+ case auth_type
291
+ when RPC_C_AUTHN_WINNT
292
+ send_auth3(bindack_response, auth_type, auth_level)
293
+ when RPC_C_AUTHN_GSS_KERBEROS, RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_GSS_NEGOTIATE
294
+ # TODO
295
+ raise NotImplementedError
296
+ end
297
+ end
298
+
299
+ nil
300
+ end
301
+
302
+ # Extract and store useful information about the peer/server from the
303
+ # NTLM Type 2 (challenge) TargetInfo fields.
304
+ #
305
+ # @param target_info_str [String] the Target Info string
306
+ def store_target_info(target_info_str)
307
+ target_info = Net::NTLM::TargetInfo.new(target_info_str)
308
+ {
309
+ Net::NTLM::TargetInfo::MSV_AV_NB_COMPUTER_NAME => :@default_name,
310
+ Net::NTLM::TargetInfo::MSV_AV_NB_DOMAIN_NAME => :@default_domain,
311
+ Net::NTLM::TargetInfo::MSV_AV_DNS_COMPUTER_NAME => :@dns_host_name,
312
+ Net::NTLM::TargetInfo::MSV_AV_DNS_DOMAIN_NAME => :@dns_domain_name,
313
+ Net::NTLM::TargetInfo::MSV_AV_DNS_TREE_NAME => :@dns_tree_name
314
+ }.each do |constant, attribute|
315
+ if target_info.av_pairs[constant]
316
+ value = target_info.av_pairs[constant].dup
317
+ value.force_encoding('UTF-16LE')
318
+ instance_variable_set(attribute, value.encode('UTF-8'))
319
+ end
320
+ end
321
+ end
322
+
323
+ # Extract the peer/server version number from the NTLM Type 2 (challenge)
324
+ # Version field.
325
+ #
326
+ # @param version [String] the version number as a binary string
327
+ # @return [String] the formated version number (<major>.<minor>.<build>)
328
+ def extract_os_version(version)
329
+ #version.unpack('CCS').join('.')
330
+ begin
331
+ os_version = NTLM::OSVersion.read(version)
332
+ rescue IOError
333
+ return ''
334
+ end
335
+ return "#{os_version.major}.#{os_version.minor}.#{os_version.build}"
336
+ end
337
+
338
+ # Add the authentication verifier to a Request packet. This includes a
339
+ # sec trailer and the signature of the packet. This also encrypts the
340
+ # Request stub if privacy is required (`:auth_level` option is
341
+ # RPC_C_AUTHN_LEVEL_PKT_PRIVACY).
342
+ #
343
+ # @param dcerpc_req [Request] the Request packet to be updated
344
+ # @param opts [Hash] the authenticaiton options: `:auth_type` and `:auth_level`
345
+ # @raise [NotImplementedError] if `:auth_type` is not implemented (yet)
346
+ # @raise [ArgumentError] if `:auth_type` is unknown
347
+ def set_integrity_privacy(dcerpc_req, auth_level:, auth_type:)
348
+ dcerpc_req.sec_trailer = {
349
+ auth_type: auth_type,
350
+ auth_level: auth_level,
351
+ auth_context_id: @ctx_id + @auth_ctx_id_base
352
+ }
353
+ dcerpc_req.auth_value = ' ' * 16
354
+ dcerpc_req.pdu_header.auth_length = 16
355
+
356
+ data_to_sign = plain_stub = dcerpc_req.stub.to_binary_s + dcerpc_req.auth_pad.to_binary_s
357
+ if @ntlm_client.flags & NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY] != 0
358
+ data_to_sign = dcerpc_req.to_binary_s[0..-(dcerpc_req.pdu_header.auth_length + 1)]
359
+ end
360
+
361
+ encrypted_stub = ''
362
+ if auth_level == RPC_C_AUTHN_LEVEL_PKT_PRIVACY
363
+ case auth_type
364
+ when RPC_C_AUTHN_NONE
365
+ when RPC_C_AUTHN_WINNT, RPC_C_AUTHN_DEFAULT
366
+ encrypted_stub = @ntlm_client.session.seal_message(plain_stub)
367
+ when RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_GSS_SCHANNEL, RPC_C_AUTHN_GSS_KERBEROS
368
+ # TODO
369
+ raise NotImplementedError
370
+ else
371
+ raise ArgumentError, "Unsupported Auth Type: #{auth_type}"
372
+ end
373
+ end
374
+
375
+ signature = @ntlm_client.session.sign_message(data_to_sign)
376
+
377
+ unless encrypted_stub.empty?
378
+ pad_length = dcerpc_req.sec_trailer.auth_pad_length.to_i
379
+ dcerpc_req.enable_encrypted_stub
380
+ dcerpc_req.stub = encrypted_stub[0..-(pad_length + 1)]
381
+ dcerpc_req.auth_pad = encrypted_stub[-(pad_length)..-1]
382
+ end
383
+ dcerpc_req.auth_value = signature
384
+ dcerpc_req.pdu_header.auth_length = signature.size
385
+ end
386
+
387
+ # Send a DCERPC request with the provided stub packet.
388
+ #
389
+ # @param stub_packet [BinData::Record] the stub packet to be sent as
390
+ # part of a Request packet
391
+ # @param opts [Hash] the authenticaiton options: `:auth_type` and `:auth_level`
392
+ # @raise [Error::CommunicationError] if socket-related error occurs
393
+ def dcerpc_request(stub_packet, auth_level: nil, auth_type: nil)
394
+ stub_class = stub_packet.class.name.split('::')
395
+ #opts.merge!(endpoint: stub_class[-2])
396
+ values = {
397
+ opnum: stub_packet.opnum,
398
+ p_cont_id: @ctx_id
399
+ }
400
+ dcerpc_req = Request.new(values, { endpoint: stub_class[-2] })
401
+ dcerpc_req.pdu_header.call_id = @call_id
402
+ dcerpc_req.stub.read(stub_packet.to_binary_s)
403
+ # TODO: handle fragmentation
404
+ # We should fragment PDUs if:
405
+ # 1) Payload exceeds max_xmit_frag (@max_buffer_size) received during BIND response
406
+ # 2) We'e explicitly fragmenting packets with lower values
407
+
408
+ if auth_level &&
409
+ [RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY].include?(auth_level)
410
+ set_integrity_privacy(dcerpc_req, auth_level: auth_level, auth_type: auth_type)
411
+ end
412
+
413
+ send_packet(dcerpc_req)
414
+
415
+ dcerpc_res = recv_struct(Response)
416
+ unless dcerpc_res.pdu_header.pfc_flags.first_frag == 1
417
+ raise Error::InvalidPacket, "Not the first fragment"
418
+ end
419
+
420
+ if auth_level &&
421
+ [RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY].include?(auth_level)
422
+ handle_integrity_privacy(dcerpc_res, auth_level: auth_level, auth_type: auth_type)
423
+ end
424
+
425
+ raw_stub = dcerpc_res.stub.to_binary_s
426
+ loop do
427
+ break if dcerpc_res.pdu_header.pfc_flags.last_frag == 1
428
+ dcerpc_res = recv_struct(Response)
429
+
430
+ if auth_level &&
431
+ [RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY].include?(auth_level)
432
+ handle_integrity_privacy(dcerpc_res, auth_level: auth_level, auth_type: auth_type)
433
+ end
434
+
435
+ raw_stub << dcerpc_res.stub.to_binary_s
436
+ end
437
+
438
+ raw_stub
439
+ end
440
+
441
+ # Send a packet to the remote host
442
+ #
443
+ # @param packet [BinData::Record] the packet to send
444
+ # @raise [Error::CommunicationError] if socket-related error occurs
445
+ def send_packet(packet)
446
+ data = packet.to_binary_s
447
+ bytes_written = 0
448
+ begin
449
+ loop do
450
+ break unless bytes_written < data.size
451
+ retval = @tcp_socket.write(data[bytes_written..-1])
452
+ bytes_written += retval
453
+ end
454
+
455
+ rescue IOError, Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE => e
456
+ raise Error::CommunicationError, "An error occurred writing to the Socket: #{e.message}"
457
+ end
458
+ nil
459
+ end
460
+
461
+ # Receive a packet from the remote host and parse it according to `struct`
462
+ #
463
+ # @param struct [Class] the structure class to parse the response with
464
+ # @raise [Error::CommunicationError] if socket-related error occurs
465
+ def recv_struct(struct)
466
+ raise Error::CommunicationError, 'Connection has already been closed' if @tcp_socket.closed?
467
+ if IO.select([@tcp_socket], nil, nil, @read_timeout).nil?
468
+ raise Error::CommunicationError, "Read timeout expired when reading from the Socket (timeout=#{@read_timeout})"
469
+ end
470
+
471
+ begin
472
+ response = struct.read(@tcp_socket)
473
+ rescue IOError
474
+ raise Error::InvalidPacket, "Error reading the #{struct} response"
475
+ end
476
+ unless response.pdu_header.ptype == struct::PTYPE
477
+ raise Error::InvalidPacket, "Not a #{struct} packet"
478
+ end
479
+
480
+ response
481
+ rescue Errno::EINVAL, Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE => e
482
+ raise Error::CommunicationError, "An error occurred reading from the Socket: #{e.message}"
483
+ end
484
+
485
+ # Process the security context received in a response. It decrypts the
486
+ # encrypted stub if `:auth_level` is set to anything different than
487
+ # RPC_C_AUTHN_LEVEL_PKT_PRIVACY. It also checks the packet signature and
488
+ # raises an InvalidPacket error if it fails. Note that the exception is
489
+ # disabled by default and can be enabled with the
490
+ # `:raise_signature_error` option
491
+ #
492
+ # @param dcerpc_response [Response] the Response packet
493
+ # containing the security context to process
494
+ # @param opts [Hash] the authenticaiton options: `:auth_type` and
495
+ # `:auth_level`. To enable errors when signature check fails, set the
496
+ # `:raise_signature_error` option to true
497
+ # @raise [NotImplementedError] if `:auth_type` is not implemented (yet)
498
+ # @raise [Error::CommunicationError] if socket-related error occurs
499
+ def handle_integrity_privacy(dcerpc_response, auth_level:, auth_type:, raise_signature_error: false)
500
+ decrypted_stub = ''
501
+ if auth_level == RPC_C_AUTHN_LEVEL_PKT_PRIVACY
502
+ encrypted_stub = dcerpc_response.stub.to_binary_s + dcerpc_response.auth_pad.to_binary_s
503
+ case auth_type
504
+ when RPC_C_AUTHN_NONE
505
+ when RPC_C_AUTHN_WINNT, RPC_C_AUTHN_DEFAULT
506
+ decrypted_stub = @ntlm_client.session.unseal_message(encrypted_stub)
507
+ when RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_GSS_SCHANNEL, RPC_C_AUTHN_GSS_KERBEROS
508
+ # TODO
509
+ raise NotImplementedError
510
+ else
511
+ raise ArgumentError, "Unsupported Auth Type: #{auth_type}"
512
+ end
513
+ end
514
+
515
+ unless decrypted_stub.empty?
516
+ pad_length = dcerpc_response.sec_trailer.auth_pad_length.to_i
517
+ dcerpc_response.stub = decrypted_stub[0..-(pad_length + 1)]
518
+ dcerpc_response.auth_pad = decrypted_stub[-(pad_length)..-1]
519
+ end
520
+
521
+ signature = dcerpc_response.auth_value
522
+ data_to_check = dcerpc_response.stub.to_binary_s
523
+ if @ntlm_client.flags & NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY] != 0
524
+ data_to_check = dcerpc_response.to_binary_s[0..-(dcerpc_response.pdu_header.auth_length + 1)]
525
+ end
526
+ unless @ntlm_client.session.verify_signature(signature, data_to_check)
527
+ if raise_signature_error
528
+ raise Error::InvalidPacket.new(
529
+ "Wrong packet signature received (set `raise_signature_error` to false to ignore)"
530
+ )
531
+ end
532
+ end
533
+
534
+ @call_id += 1
535
+
536
+ nil
537
+ end
538
+
539
+ end
540
+ end
541
+ end
542
+
@@ -0,0 +1,24 @@
1
+ module RubySMB
2
+ module Dcerpc
3
+ module Drsr
4
+
5
+ # [4.1.3 IDL_DRSBind (Opnum 0)](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-drsr/605b1ea1-9cdc-428f-ab7a-70120e020a3d)
6
+ class DrsBindRequest < BinData::Record
7
+ attr_reader :opnum
8
+
9
+ endian :little
10
+
11
+ uuid_ptr :puuid_client_dsa, initial_value: NTSAPI_CLIENT_GUID
12
+ drs_extensions_ptr :pext_client
13
+
14
+ def initialize_instance
15
+ super
16
+ @opnum = DRS_BIND
17
+ end
18
+ end
19
+
20
+ end
21
+ end
22
+ end
23
+
24
+
@@ -0,0 +1,26 @@
1
+ module RubySMB
2
+ module Dcerpc
3
+ module Drsr
4
+
5
+ # [4.1.3 IDL_DRSBind (Opnum 0)](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-drsr/605b1ea1-9cdc-428f-ab7a-70120e020a3d)
6
+ class DrsBindResponse < BinData::Record
7
+ attr_reader :opnum
8
+
9
+ endian :little
10
+
11
+ drs_extensions_ptr :ppext_server
12
+ drs_handle :ph_drs
13
+ ndr_uint32 :error_status
14
+
15
+ def initialize_instance
16
+ super
17
+ @opnum = DRS_BIND
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+
25
+
26
+
@@ -0,0 +1,57 @@
1
+ module RubySMB
2
+ module Dcerpc
3
+ module Drsr
4
+
5
+ class DrsNameArrayPtr < Ndr::NdrConfArray
6
+ default_parameters type: :ndr_wide_stringz_ptr
7
+ extend Ndr::PointerClassPlugin
8
+ end
9
+
10
+ #[4.1.4.1.2 DRS_MSG_CRACKREQ_V1](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-drsr/b47debc0-59ee-40e4-ad0f-4bc9f96043b2)
11
+ class DrsMsgCrackreqV1 < Ndr::NdrStruct
12
+ default_parameter byte_align: 4
13
+ endian :little
14
+
15
+ ndr_uint32 :code_page
16
+ ndr_uint32 :locale_id
17
+ ndr_uint32 :dw_flags
18
+ ndr_uint32 :format_offered
19
+ ndr_uint32 :format_desired
20
+ ndr_uint32 :c_names, initial_value: -> { rp_names.size }
21
+ drs_name_array_ptr :rp_names
22
+ end
23
+
24
+ # [4.1.4.1.1 DRS_MSG_CRACKREQ](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-drsr/f2d5166e-09f6-4788-a391-66471b2f7d6d)
25
+ class DrsMsgCrackreq < Ndr::NdrStruct
26
+ default_parameter byte_align: 4
27
+ endian :little
28
+
29
+ ndr_uint32 :switch_type, initial_value: 1
30
+ choice :msg_crack, selection: :switch_type, byte_align: 4 do
31
+ drs_msg_crackreq_v1 1
32
+ end
33
+ end
34
+
35
+ # [4.1.4 IDL_DRSCrackNames (Opnum 12)](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-drsr/9b4bfb44-6656-4404-bcc8-dc88111658b3)
36
+ class DrsCrackNamesRequest < BinData::Record
37
+ attr_reader :opnum
38
+
39
+ endian :little
40
+
41
+ drs_handle :h_drs
42
+ ndr_uint32 :dw_in_version, initial_value: 1
43
+ drs_msg_crackreq :pmsg_in
44
+
45
+ def initialize_instance
46
+ super
47
+ @opnum = DRS_CRACK_NAMES
48
+ end
49
+ end
50
+
51
+ end
52
+ end
53
+ end
54
+
55
+
56
+
57
+