ruby_smb 2.0.11 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (255) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/verify.yml +5 -16
  4. data/examples/auth_capture.rb +28 -0
  5. data/examples/dump_secrets_from_sid.rb +207 -0
  6. data/examples/enum_domain_users.rb +75 -0
  7. data/examples/file_server.rb +76 -0
  8. data/examples/get_computer_info.rb +42 -0
  9. data/examples/query_service_status.rb +42 -4
  10. data/lib/ruby_smb/client.rb +3 -14
  11. data/lib/ruby_smb/create_actions.rb +21 -0
  12. data/lib/ruby_smb/dcerpc/bind.rb +28 -20
  13. data/lib/ruby_smb/dcerpc/bind_ack.rb +29 -28
  14. data/lib/ruby_smb/dcerpc/client.rb +542 -0
  15. data/lib/ruby_smb/dcerpc/drsr/drs_bind_request.rb +24 -0
  16. data/lib/ruby_smb/dcerpc/drsr/drs_bind_response.rb +26 -0
  17. data/lib/ruby_smb/dcerpc/drsr/drs_crack_names_request.rb +57 -0
  18. data/lib/ruby_smb/dcerpc/drsr/drs_crack_names_response.rb +76 -0
  19. data/lib/ruby_smb/dcerpc/drsr/drs_domain_controller_info_request.rb +46 -0
  20. data/lib/ruby_smb/dcerpc/drsr/drs_domain_controller_info_response.rb +168 -0
  21. data/lib/ruby_smb/dcerpc/drsr/drs_extensions.rb +56 -0
  22. data/lib/ruby_smb/dcerpc/drsr/drs_get_nc_changes_request.rb +121 -0
  23. data/lib/ruby_smb/dcerpc/drsr/drs_get_nc_changes_response.rb +118 -0
  24. data/lib/ruby_smb/dcerpc/drsr/drs_unbind_request.rb +24 -0
  25. data/lib/ruby_smb/dcerpc/drsr/drs_unbind_response.rb +26 -0
  26. data/lib/ruby_smb/dcerpc/drsr.rb +909 -0
  27. data/lib/ruby_smb/dcerpc/epm/epm_ept_map_request.rb +26 -0
  28. data/lib/ruby_smb/dcerpc/epm/epm_ept_map_response.rb +25 -0
  29. data/lib/ruby_smb/dcerpc/epm/epm_twrt.rb +211 -0
  30. data/lib/ruby_smb/dcerpc/epm.rb +75 -0
  31. data/lib/ruby_smb/dcerpc/error.rb +17 -0
  32. data/lib/ruby_smb/dcerpc/ndr.rb +1159 -297
  33. data/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_request.rb +3 -13
  34. data/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_response.rb +3 -3
  35. data/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_request.rb +3 -13
  36. data/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_response.rb +1 -1
  37. data/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_request.rb +3 -11
  38. data/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_response.rb +1 -1
  39. data/lib/ruby_smb/dcerpc/netlogon.rb +5 -4
  40. data/lib/ruby_smb/dcerpc/p_syntax_id_t.rb +4 -3
  41. data/lib/ruby_smb/dcerpc/pdu_header.rb +7 -7
  42. data/lib/ruby_smb/dcerpc/ptypes.rb +1 -0
  43. data/lib/ruby_smb/dcerpc/request.rb +79 -32
  44. data/lib/ruby_smb/dcerpc/response.rb +45 -10
  45. data/lib/ruby_smb/dcerpc/rpc_auth3.rb +28 -0
  46. data/lib/ruby_smb/dcerpc/rpc_security_attributes.rb +11 -11
  47. data/lib/ruby_smb/dcerpc/rrp_rpc_unicode_string.rb +118 -0
  48. data/lib/ruby_smb/dcerpc/samr/rpc_sid.rb +150 -0
  49. data/lib/ruby_smb/dcerpc/samr/samr_close_handle_request.rb +23 -0
  50. data/lib/ruby_smb/dcerpc/samr/samr_close_handle_response.rb +24 -0
  51. data/lib/ruby_smb/dcerpc/samr/samr_connect_request.rb +32 -0
  52. data/lib/ruby_smb/dcerpc/samr/samr_connect_response.rb +23 -0
  53. data/lib/ruby_smb/dcerpc/samr/samr_enumerate_users_in_domain_request.rb +26 -0
  54. data/lib/ruby_smb/dcerpc/samr/samr_enumerate_users_in_domain_response.rb +55 -0
  55. data/lib/ruby_smb/dcerpc/samr/samr_get_alias_membership_request.rb +48 -0
  56. data/lib/ruby_smb/dcerpc/samr/samr_get_alias_membership_response.rb +38 -0
  57. data/lib/ruby_smb/dcerpc/samr/samr_get_groups_for_user_request.rb +23 -0
  58. data/lib/ruby_smb/dcerpc/samr/samr_get_groups_for_user_response.rb +48 -0
  59. data/lib/ruby_smb/dcerpc/samr/samr_lookup_domain_in_sam_server_request.rb +24 -0
  60. data/lib/ruby_smb/dcerpc/samr/samr_lookup_domain_in_sam_server_response.rb +25 -0
  61. data/lib/ruby_smb/dcerpc/samr/samr_open_domain_request.rb +27 -0
  62. data/lib/ruby_smb/dcerpc/samr/samr_open_domain_response.rb +24 -0
  63. data/lib/ruby_smb/dcerpc/samr/samr_open_user_request.rb +26 -0
  64. data/lib/ruby_smb/dcerpc/samr/samr_open_user_response.rb +24 -0
  65. data/lib/ruby_smb/dcerpc/samr/samr_rid_to_sid_request.rb +23 -0
  66. data/lib/ruby_smb/dcerpc/samr/samr_rid_to_sid_response.rb +23 -0
  67. data/lib/ruby_smb/dcerpc/samr.rb +613 -0
  68. data/lib/ruby_smb/dcerpc/sec_trailer.rb +26 -0
  69. data/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all.rb +56 -79
  70. data/lib/ruby_smb/dcerpc/srvsvc.rb +27 -4
  71. data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request.rb +13 -25
  72. data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response.rb +2 -2
  73. data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response.rb +1 -1
  74. data/lib/ruby_smb/dcerpc/svcctl/control_service_request.rb +1 -1
  75. data/lib/ruby_smb/dcerpc/svcctl/control_service_response.rb +1 -1
  76. data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request.rb +4 -14
  77. data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response.rb +1 -1
  78. data/lib/ruby_smb/dcerpc/svcctl/open_service_w_request.rb +3 -11
  79. data/lib/ruby_smb/dcerpc/svcctl/open_service_w_response.rb +1 -1
  80. data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request.rb +1 -1
  81. data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response.rb +12 -11
  82. data/lib/ruby_smb/dcerpc/svcctl/query_service_status_response.rb +1 -1
  83. data/lib/ruby_smb/dcerpc/svcctl/service_status.rb +9 -8
  84. data/lib/ruby_smb/dcerpc/svcctl/start_service_w_request.rb +3 -3
  85. data/lib/ruby_smb/dcerpc/svcctl/start_service_w_response.rb +1 -1
  86. data/lib/ruby_smb/dcerpc/svcctl.rb +1 -3
  87. data/lib/ruby_smb/dcerpc/uuid.rb +3 -0
  88. data/lib/ruby_smb/dcerpc/winreg/close_key_response.rb +2 -2
  89. data/lib/ruby_smb/dcerpc/winreg/create_key_request.rb +2 -13
  90. data/lib/ruby_smb/dcerpc/winreg/create_key_response.rb +3 -3
  91. data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +3 -20
  92. data/lib/ruby_smb/dcerpc/winreg/enum_key_response.rb +3 -20
  93. data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +5 -14
  94. data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +5 -14
  95. data/lib/ruby_smb/dcerpc/winreg/open_key_request.rb +1 -9
  96. data/lib/ruby_smb/dcerpc/winreg/open_key_response.rb +4 -3
  97. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +5 -6
  98. data/lib/ruby_smb/dcerpc/winreg/open_root_key_response.rb +2 -2
  99. data/lib/ruby_smb/dcerpc/winreg/query_info_key_response.rb +9 -18
  100. data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +4 -14
  101. data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +7 -15
  102. data/lib/ruby_smb/dcerpc/winreg/regsam.rb +3 -1
  103. data/lib/ruby_smb/dcerpc/winreg/save_key_request.rb +0 -9
  104. data/lib/ruby_smb/dcerpc/winreg/save_key_response.rb +1 -1
  105. data/lib/ruby_smb/dcerpc/winreg.rb +10 -14
  106. data/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_get_info_request.rb +26 -0
  107. data/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_get_info_response.rb +88 -0
  108. data/lib/ruby_smb/dcerpc/wkssvc.rb +65 -0
  109. data/lib/ruby_smb/dcerpc.rb +41 -11
  110. data/lib/ruby_smb/field/file_time.rb +1 -1
  111. data/lib/ruby_smb/field/nt_status.rb +20 -1
  112. data/lib/ruby_smb/field/string16.rb +5 -1
  113. data/lib/ruby_smb/fscc/file_information/file_ea_information.rb +14 -0
  114. data/lib/ruby_smb/fscc/file_information/file_network_open_information.rb +22 -0
  115. data/lib/ruby_smb/fscc/file_information/file_stream_information.rb +16 -0
  116. data/lib/ruby_smb/fscc/file_information.rb +29 -0
  117. data/lib/ruby_smb/fscc/file_system_information/file_fs_attribute_information.rb +46 -0
  118. data/lib/ruby_smb/fscc/file_system_information/file_fs_volume_information.rb +19 -0
  119. data/lib/ruby_smb/fscc/file_system_information.rb +22 -0
  120. data/lib/ruby_smb/fscc.rb +1 -0
  121. data/lib/ruby_smb/generic_packet.rb +6 -0
  122. data/lib/ruby_smb/gss/provider/authenticator.rb +4 -0
  123. data/lib/ruby_smb/gss/provider/ntlm.rb +13 -3
  124. data/lib/ruby_smb/ntlm.rb +18 -2
  125. data/lib/ruby_smb/server/server_client/negotiation.rb +1 -2
  126. data/lib/ruby_smb/server/server_client/session_setup.rb +44 -33
  127. data/lib/ruby_smb/server/server_client/share_io.rb +28 -0
  128. data/lib/ruby_smb/server/server_client/tree_connect.rb +60 -0
  129. data/lib/ruby_smb/server/server_client.rb +214 -25
  130. data/lib/ruby_smb/server/session.rb +71 -0
  131. data/lib/ruby_smb/server/share/provider/disk.rb +437 -0
  132. data/lib/ruby_smb/server/share/provider/pipe.rb +27 -0
  133. data/lib/ruby_smb/server/share/provider/processor.rb +76 -0
  134. data/lib/ruby_smb/server/share/provider.rb +38 -0
  135. data/lib/ruby_smb/server/share.rb +11 -0
  136. data/lib/ruby_smb/server.rb +35 -3
  137. data/lib/ruby_smb/signing.rb +37 -11
  138. data/lib/ruby_smb/smb1/commands.rb +4 -0
  139. data/lib/ruby_smb/smb1/pipe.rb +4 -0
  140. data/lib/ruby_smb/smb1.rb +0 -1
  141. data/lib/ruby_smb/smb2/bit_field/smb2_header_flags.rb +2 -1
  142. data/lib/ruby_smb/smb2/commands.rb +4 -0
  143. data/lib/ruby_smb/smb2/create_context/request.rb +64 -0
  144. data/lib/ruby_smb/smb2/create_context/response.rb +62 -0
  145. data/lib/ruby_smb/smb2/create_context.rb +74 -22
  146. data/lib/ruby_smb/smb2/packet/create_request.rb +44 -11
  147. data/lib/ruby_smb/smb2/packet/create_response.rb +17 -3
  148. data/lib/ruby_smb/smb2/packet/query_directory_request.rb +1 -1
  149. data/lib/ruby_smb/smb2/packet/query_directory_response.rb +2 -2
  150. data/lib/ruby_smb/smb2/packet/query_info_request.rb +43 -0
  151. data/lib/ruby_smb/smb2/packet/query_info_response.rb +23 -0
  152. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +1 -1
  153. data/lib/ruby_smb/smb2/packet/tree_disconnect_response.rb +1 -0
  154. data/lib/ruby_smb/smb2/packet.rb +2 -0
  155. data/lib/ruby_smb/smb2/pipe.rb +4 -0
  156. data/lib/ruby_smb/smb2.rb +11 -0
  157. data/lib/ruby_smb/smb_error.rb +110 -0
  158. data/lib/ruby_smb/version.rb +1 -1
  159. data/lib/ruby_smb.rb +2 -0
  160. data/ruby_smb.gemspec +1 -1
  161. data/spec/lib/ruby_smb/client_spec.rb +1 -2
  162. data/spec/lib/ruby_smb/dcerpc/bind_ack_spec.rb +69 -41
  163. data/spec/lib/ruby_smb/dcerpc/bind_spec.rb +75 -21
  164. data/spec/lib/ruby_smb/dcerpc/client_spec.rb +714 -0
  165. data/spec/lib/ruby_smb/dcerpc/drsr_spec.rb +2169 -0
  166. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +3792 -1373
  167. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_request_spec.rb +4 -4
  168. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_request_spec.rb +4 -4
  169. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_request_spec.rb +2 -2
  170. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_response_spec.rb +2 -2
  171. data/spec/lib/ruby_smb/dcerpc/p_syntax_id_t_spec.rb +18 -4
  172. data/spec/lib/ruby_smb/dcerpc/pdu_header_spec.rb +27 -1
  173. data/spec/lib/ruby_smb/dcerpc/request_spec.rb +76 -11
  174. data/spec/lib/ruby_smb/dcerpc/response_spec.rb +99 -9
  175. data/spec/lib/ruby_smb/dcerpc/rpc_auth3_spec.rb +75 -0
  176. data/spec/lib/ruby_smb/dcerpc/rpc_security_attributes_spec.rb +29 -28
  177. data/spec/lib/ruby_smb/dcerpc/rrp_rpc_unicode_string_spec.rb +340 -0
  178. data/spec/lib/ruby_smb/dcerpc/samr/rpc_sid_spec.rb +116 -0
  179. data/spec/lib/ruby_smb/dcerpc/samr/samr_close_handle_request_spec.rb +40 -0
  180. data/spec/lib/ruby_smb/dcerpc/samr/samr_close_handle_response_spec.rb +48 -0
  181. data/spec/lib/ruby_smb/dcerpc/samr/samr_connect_request_spec.rb +56 -0
  182. data/spec/lib/ruby_smb/dcerpc/samr/samr_connect_response_spec.rb +47 -0
  183. data/spec/lib/ruby_smb/dcerpc/samr/samr_enumerate_users_in_domain_request_spec.rb +63 -0
  184. data/spec/lib/ruby_smb/dcerpc/samr/samr_enumerate_users_in_domain_response_spec.rb +265 -0
  185. data/spec/lib/ruby_smb/dcerpc/samr/samr_lookup_domain_in_sam_server_request_spec.rb +52 -0
  186. data/spec/lib/ruby_smb/dcerpc/samr/samr_lookup_domain_in_sam_server_response_spec.rb +36 -0
  187. data/spec/lib/ruby_smb/dcerpc/samr/samr_open_domain_request_spec.rb +56 -0
  188. data/spec/lib/ruby_smb/dcerpc/samr/samr_open_domain_response_spec.rb +48 -0
  189. data/spec/lib/ruby_smb/dcerpc/samr/samr_rid_to_sid_request_spec.rb +48 -0
  190. data/spec/lib/ruby_smb/dcerpc/samr/samr_rid_to_sid_response_spec.rb +42 -0
  191. data/spec/lib/ruby_smb/dcerpc/samr_spec.rb +420 -0
  192. data/spec/lib/ruby_smb/dcerpc/sec_trailer_spec.rb +92 -0
  193. data/spec/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all_spec.rb +149 -110
  194. data/spec/lib/ruby_smb/dcerpc/srvsvc_spec.rb +21 -17
  195. data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request_spec.rb +56 -79
  196. data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response_spec.rb +4 -4
  197. data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response_spec.rb +2 -2
  198. data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_request_spec.rb +2 -2
  199. data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_response_spec.rb +2 -2
  200. data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request_spec.rb +19 -29
  201. data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response_spec.rb +2 -2
  202. data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_request_spec.rb +9 -15
  203. data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_response_spec.rb +2 -2
  204. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request_spec.rb +2 -2
  205. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response_spec.rb +22 -22
  206. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_response_spec.rb +2 -2
  207. data/spec/lib/ruby_smb/dcerpc/svcctl/service_status_spec.rb +18 -14
  208. data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_request_spec.rb +5 -4
  209. data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_response_spec.rb +2 -2
  210. data/spec/lib/ruby_smb/dcerpc/svcctl_spec.rb +1 -5
  211. data/spec/lib/ruby_smb/dcerpc/uuid_spec.rb +15 -23
  212. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_response_spec.rb +2 -2
  213. data/spec/lib/ruby_smb/dcerpc/winreg/create_key_request_spec.rb +4 -41
  214. data/spec/lib/ruby_smb/dcerpc/winreg/create_key_response_spec.rb +4 -4
  215. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +4 -52
  216. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_response_spec.rb +4 -56
  217. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +10 -34
  218. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +10 -34
  219. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_request_spec.rb +2 -26
  220. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_response_spec.rb +2 -2
  221. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +17 -25
  222. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_response_spec.rb +2 -2
  223. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_response_spec.rb +20 -44
  224. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +8 -32
  225. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +10 -22
  226. data/spec/lib/ruby_smb/dcerpc/winreg/regsam_spec.rb +4 -0
  227. data/spec/lib/ruby_smb/dcerpc/winreg/save_key_request_spec.rb +0 -12
  228. data/spec/lib/ruby_smb/dcerpc/winreg/save_key_response_spec.rb +2 -2
  229. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +18 -47
  230. data/spec/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_get_info_request_spec.rb +43 -0
  231. data/spec/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_get_info_response_spec.rb +410 -0
  232. data/spec/lib/ruby_smb/dcerpc/wkssvc_spec.rb +70 -0
  233. data/spec/lib/ruby_smb/field/nt_status_spec.rb +6 -2
  234. data/spec/lib/ruby_smb/field/string16_spec.rb +22 -0
  235. data/spec/lib/ruby_smb/gss/provider/ntlm/authenticator_spec.rb +4 -0
  236. data/spec/lib/ruby_smb/gss/provider/ntlm/os_version_spec.rb +1 -1
  237. data/spec/lib/ruby_smb/server/server_client_spec.rb +36 -53
  238. data/spec/lib/ruby_smb/server/session_spec.rb +38 -0
  239. data/spec/lib/ruby_smb/server/share/provider/disk_spec.rb +61 -0
  240. data/spec/lib/ruby_smb/server/share/provider/pipe_spec.rb +31 -0
  241. data/spec/lib/ruby_smb/server/share/provider_spec.rb +13 -0
  242. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +18 -37
  243. data/spec/lib/ruby_smb/smb2/bit_field/header_flags_spec.rb +8 -2
  244. data/spec/lib/ruby_smb/smb2/{create_context_spec.rb → create_context/create_context_request_spec.rb} +1 -1
  245. data/spec/lib/ruby_smb/smb2/packet/create_request_spec.rb +5 -5
  246. data/spec/lib/ruby_smb/smb2/packet/create_response_spec.rb +9 -5
  247. data/spec/lib/ruby_smb/smb2/packet/query_directory_response_spec.rb +3 -2
  248. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +18 -16
  249. data/spec/support/bin_helper.rb +9 -0
  250. data.tar.gz.sig +2 -3
  251. metadata +129 -10
  252. metadata.gz.sig +0 -0
  253. data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +0 -38
  254. data/lib/ruby_smb/smb1/create_actions.rb +0 -20
  255. data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +0 -135
@@ -1,383 +1,1245 @@
1
- module RubySMB
2
- module Dcerpc
3
- module Ndr
4
-
5
- # NDR Syntax
6
- UUID = '8a885d04-1ceb-11c9-9fe8-08002b104860'
7
- VER_MAJOR = 2
8
- VER_MINOR = 0
9
-
10
- # An NDR Enum type as defined in
11
- # [Transfer Syntax NDR - Enumerated Types](https://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_02_05_01)
12
- class NdrEnum < BinData::Int16le; end
13
-
14
- # An NDR Conformant and Varying String representation as defined in
15
- # [Transfer Syntax NDR - Conformant and Varying Strings](http://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_04_02)
16
- # The string elements are Stringz16 (unicode)
17
- class NdrString < BinData::Primitive
18
- endian :little
19
-
20
- uint32 :max_count
21
- uint32 :offset, initial_value: 0
22
- uint32 :actual_count
23
- stringz16 :str, max_length: -> { actual_count * 2 }, onlyif: -> { actual_count > 0 }
24
-
25
- def get
26
- self.actual_count == 0 ? 0 : self.str
27
- end
1
+ module RubySMB::Dcerpc::Ndr
28
2
 
29
- def set(v)
30
- if v == 0
31
- self.str.clear
32
- self.actual_count = 0
33
- else
34
- v = v.str if v.is_a?(self.class)
35
- unless self.str.equal?(v)
36
- if v.empty?
37
- self.actual_count = 0
38
- else
39
- self.actual_count = v.to_s.size + 1
40
- self.max_count = self.actual_count
41
- end
42
- end
43
- self.str = v.to_s
44
- end
45
- end
3
+ require 'ruby_smb/field'
46
4
 
47
- def clear
48
- # Make sure #max_count and #offset are not cleared out
49
- self.str.clear
50
- self.actual_count.clear
51
- end
5
+ # NDR Syntax
6
+ UUID = '8a885d04-1ceb-11c9-9fe8-08002b104860'
7
+ VER_MAJOR = 2
8
+ VER_MINOR = 0
52
9
 
53
- def to_s
54
- self.str.to_s
55
- end
10
+ #####################################
11
+ # NDR Primitive Types #
12
+ #####################################
13
+
14
+ # Signed and unsigned integers use BinData primitives directly
15
+
16
+ # [Booleans](https://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_02_03)
17
+ class NdrBoolean < BinData::Uint32le
18
+ default_parameters byte_align: 4
19
+
20
+ def assign(val)
21
+ super(value_to_int(val))
22
+ end
23
+
24
+ def snapshot
25
+ return _value == 0 ? false : true
26
+ end
27
+
28
+
29
+ private
30
+
31
+ def value_to_binary_string(val)
32
+ super(value_to_int(val))
33
+ end
34
+
35
+ def value_to_int(val)
36
+ val = val.snapshot if val.respond_to?(:snapshot)
37
+ case(val)
38
+ when FalseClass
39
+ return 0
40
+ when TrueClass
41
+ return 1
42
+ when Integer
43
+ # Any non-zero value is TRUE, let's assume the caller knows what he's doing
44
+ return val
45
+ else
46
+ raise ArgumentError.new(
47
+ "Type mismatch (#{val.class}). Expecting FalseClass, TrueClass or Integer"
48
+ )
56
49
  end
50
+ end
51
+ end
57
52
 
58
- # An NDR Uni-dimensional Conformant Array of Bytes representation as defined in
59
- # [Transfer Syntax NDR - Uni-dimensional Conformant Arrays](https://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_03_02)
60
- class NdrLpByte < BinData::Primitive
61
- endian :little
53
+ # [Characters](https://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_02_04)
54
+ class NdrChar < BinData::String
55
+ default_parameter(length: 1, byte_align: 1)
56
+ end
62
57
 
63
- uint32 :max_count, initial_value: -> { self.elements.size }
64
- array :elements, type: :uint8, read_until: -> { index == self.max_count - 1 }, onlyif: -> { self.max_count > 0 }
58
+ class NdrWideChar < RubySMB::Field::String16
59
+ default_parameter(length: 2, byte_align: 2)
60
+ end
65
61
 
66
- def get
67
- self.elements
68
- end
62
+ # An NDR Enum type as defined in
63
+ # [Transfer Syntax NDR - Enumerated Types](https://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_02_05_01)
64
+ class NdrEnum < BinData::Int16le
65
+ default_parameters byte_align: 2
66
+ end
69
67
 
70
- def set(v)
71
- v = v.elements if v.is_a?(self.class)
72
- self.elements = v.to_ary
73
- self.max_count = self.elements.size unless self.elements.equal?(v)
74
- end
68
+ # [Integers](https://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_02_05)
69
+ # This will define the four size Integers accepted by the NDR protocol:
70
+ # - NdrUint8
71
+ # - NdrUint16
72
+ # - NdrUint32
73
+ # - NdrUint64
74
+ {Uint8: 1, Uint16le: 2, Uint32le: 4, Uint64le: 8}.each do |klass, nb_bytes|
75
+ new_klass_name = "Ndr#{klass.to_s.chomp('le')}"
76
+ unless self.const_defined?(new_klass_name)
77
+ new_klass = Class.new(BinData.const_get(klass)) do
78
+ default_parameters byte_align: nb_bytes
75
79
  end
80
+ self.const_set(new_klass_name, new_klass)
81
+ BinData::RegisteredClasses.register(new_klass_name, new_klass)
82
+ end
83
+ end
84
+
85
+ class NdrFileTime < RubySMB::Field::FileTime
86
+ # Note that the original Microsoft FILETIME structure is composed of two
87
+ # DWORDs, whereas RubySMB implementation uses an Uint64 field. For this
88
+ # reason, the alignement is set to the size of a DWORD (4 bytes) to match
89
+ # Microsoft structures.
90
+ default_parameters byte_align: 4
91
+ end
76
92
 
77
- # An NDR Uni-dimensional Conformant-varying Arrays of bytes representation as defined in:
78
- # [Transfer Syntax NDR - NDR Constructed Types](http://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_03_04)
79
- class NdrByteArray < BinData::Primitive
80
- endian :little
81
93
 
82
- uint32 :max_count, initial_value: -> { self.actual_count }
83
- uint32 :offset, initial_value: 0
84
- uint32 :actual_count, initial_value: -> { self.bytes.size }
85
- array :bytes, :type => :uint8, initial_length: -> { self.actual_count }
86
94
 
87
- def get
88
- self.bytes
89
- end
95
+ #####################################
96
+ # NDR Constructed Types #
97
+ #####################################
90
98
 
91
- def set(v)
92
- v = v.bytes if v.is_a?(self.class)
93
- self.bytes = v.to_ary
94
- self.max_count = self.bytes.size unless self.bytes.equal?(v)
95
- end
99
+ module ConstructedTypePlugin
100
+ def initialize_instance
101
+ @deferred_ptrs = []
102
+ super
103
+ end
104
+
105
+ def defer_ptr(ref)
106
+ @deferred_ptrs << ref
107
+ end
108
+
109
+ def has_deferred_ptrs?
110
+ !!@deferred_ptrs&.any?
111
+ end
112
+
113
+ def write_ptr(io)
114
+ @deferred_ptrs.each do |ptr_ref|
115
+ ptr_ref.do_write(io, is_deferred: true)
96
116
  end
117
+ @deferred_ptrs.clear
118
+ end
97
119
 
98
- # An NDR Uni-dimensional Fixed Array of bytes representation as defined in:
99
- # [Transfer Syntax NDR - NDR Constructed Types](https://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_03_01)
100
- class NdrFixedByteArray < BinData::BasePrimitive
101
- optional_parameters :read_length, :length, :pad_byte, :pad_front
102
- default_parameters pad_byte: 0
103
- mutually_exclusive_parameters :length, :value
120
+ def read_ptr(io)
121
+ @deferred_ptrs.each do |ptr_ref|
122
+ ptr_ref.do_read(io, is_deferred: true)
123
+ end
124
+ @deferred_ptrs.clear
125
+ end
104
126
 
105
- def initialize_shared_instance
106
- if (has_parameter?(:value) || has_parameter?(:asserted_value)) && !has_parameter?(:read_length)
107
- extend WarnNoReadLengthPlugin
108
- end
109
- super
110
- end
127
+ def is_deferring(obj)
128
+ @deferred_ptrs.any? { |e| e.equal?(obj) }
129
+ end
111
130
 
112
- def assign(val)
113
- super(fixed_byte_array(val))
114
- end
131
+ def get_top_level_constructed_type
132
+ return self if is_a?(PointerPlugin)
115
133
 
116
- def snapshot
117
- clamp_to_length(super)
118
- end
134
+ res = nil
135
+ if parent&.is_a?(ConstructedTypePlugin)
136
+ res = parent.get_top_level_constructed_type
137
+ end
138
+ return res || self
139
+ end
119
140
 
120
- class << self
121
- def arg_processor
122
- NdrFixedByteArrayArgProcessor.new
123
- end
124
- end
141
+ def do_num_bytes_ptr(struct_offset)
142
+ sum = 0
143
+ @deferred_ptrs.each do |ptr_ref|
144
+ sum += ptr_ref.do_num_bytes(struct_offset + sum, is_deferred: true)
145
+ end
146
+ @deferred_ptrs.clear
147
+ sum
148
+ end
149
+ end
125
150
 
126
- private
127
-
128
- def clamp_to_length(val)
129
- val = fixed_byte_array(val)
130
- len = eval_parameter(:length) || val.length
131
- if val.length > len
132
- val = val.first(len)
133
- elsif val.length < len
134
- pad = eval_parameter(:pad_byte)
135
- if get_parameter(:pad_front)
136
- val = val.insert(0, *Array.new(len - val.length, pad))
137
- else
138
- val = val.fill(pad, val.length...len)
139
- end
140
- end
141
151
 
142
- val
143
- end
152
+ #
153
+ # Arrays
154
+ #
144
155
 
145
- def fixed_byte_array(val)
146
- val = val.bytes if val.is_a? String
147
- val.to_ary
148
- end
156
+ module ArrayClassPlugin
157
+ module ExtendArrayPlugin
158
+ def initialize_shared_instance
159
+ super
160
+ extend ArrayPlugin
161
+ end
162
+ end
163
+ module ExtendConfPlugin
164
+ def initialize_shared_instance
165
+ super
166
+ extend ConfPlugin
167
+ end
168
+ end
169
+ module ExtendVarPlugin
170
+ def initialize_shared_instance
171
+ super
172
+ extend VarPlugin
173
+ end
174
+ end
175
+ def self.extended(target)
176
+ target.default_parameters(
177
+ :read_until => lambda { index == (@obj.read_until_index - 1) },
178
+ # Set :byte_align to 4 bytes by default, which is the size of the `size
179
+ # information` field (:max_count or :offset/:actual_count). If the
180
+ # elements set with :type are greater, this parameter will be
181
+ # updated later in NdrArrayArgProcessor::sanitize_parameters!
182
+ :byte_align => 4
183
+ )
184
+ target.arg_processor :ndr_array
185
+ class_name = target.to_s.split('::').last
186
+ if class_name.include?('NdrVar') || class_name.include?('NdrConfVar')
187
+ target.include ExtendVarPlugin
188
+ end
189
+ if class_name.include?('NdrConf')
190
+ target.include ExtendConfPlugin
191
+ target.extend ConfClassPlugin
192
+ end
193
+ target.include ExtendArrayPlugin
194
+ end
195
+ end
149
196
 
150
- def read_and_return_value(io)
151
- len = eval_parameter(:read_length) || eval_parameter(:length) || 0
152
- io.readbytes(len)
153
- end
197
+ module ArrayPlugin
198
+ include ConstructedTypePlugin
154
199
 
155
- def sensible_default
156
- [ ]
157
- end
200
+ def align_element_size(offset)
201
+ align = eval_parameter(:type).instance_variable_get(:@obj_params)[:byte_align]
202
+ align ? (align - (offset % align)) % align : 0
203
+ end
158
204
 
159
- def value_to_binary_string(val)
160
- clamp_to_length(val).pack('C*')
161
- end
205
+ def should_process_max_count?
206
+ # :max_count has already been processed if the parent structure is an
207
+ # NdrStruct, but this is not the case if we are dealing with a pointer
208
+ !parent.is_a?(NdrStruct) || self.is_a?(PointerPlugin)
209
+ end
162
210
 
163
- class NdrFixedByteArrayArgProcessor < BinData::BaseArgProcessor
164
- def sanitize_parameters!(obj_class, obj_params)
165
- obj_params.must_be_integer(:length, :pad_byte)
166
- obj_params.sanitize(:pad_byte) { |byte| sanitized_pad_byte(byte) }
167
- end
211
+ def do_write(io)
212
+ if is_a?(ConfPlugin) && should_process_max_count?
213
+ io.writebytes([@max_count].pack('L<'))
214
+ end
168
215
 
169
- private
216
+ if is_a?(VarPlugin)
217
+ io.writebytes([@offset].pack('L<'))
218
+ io.writebytes([@actual_count].pack('L<'))
219
+ end
170
220
 
171
- def sanitized_pad_byte(byte)
172
- if byte.is_a?(String)
173
- raise ArgumentError, ':pad_byte must not contain more than 1 byte' if byte.bytesize > 1
221
+ unless empty?
222
+ io.writebytes("\x00" * align_element_size(io.offset))
223
+ super
224
+ end
174
225
 
175
- byte = byte.ord
176
- end
177
- raise ArgumentError, ':pad_byte must be within the range of 0 - 255' unless ((byte >= 0) && (byte <= 255))
226
+ if has_deferred_ptrs?
227
+ write_ptr(io)
228
+ end
229
+ end
178
230
 
179
- byte
180
- end
231
+ def has_elements_to_read?
232
+ # When reading a binary stream, the only elements that indicate the array
233
+ # has elements to read are :actual_count, :max_count or :initial_length
234
+ # parameter, depending on the type of NDR Array:
235
+ # 1. When :actual_count is present (NdrVarArray and NdrConfVarArray), it
236
+ # indicates the actual number of elements passed
237
+ # 2. When only :max_count is present (NdrConfArray), we're assuming the
238
+ # maximum number of elements is the actual number of elements in this
239
+ # array
240
+ # 3. None of them are present, but :initial_length parameter has been
241
+ # set, meaning we are delaing with a fixed array without any embedded
242
+ # size information (NdrFixArray)
243
+ (@actual_count&.> 0) ||
244
+ @actual_count.nil? && (@max_count&.> 0) ||
245
+ @actual_count.nil? && @max_count.nil? && (eval_parameter(:initial_length)&.> 0)
246
+ end
247
+
248
+ def do_read(io)
249
+ if is_a?(ConfPlugin) && should_process_max_count?
250
+ set_max_count(io.readbytes(4).unpack('L<').first)
251
+ end
252
+
253
+ if is_a?(VarPlugin)
254
+ @offset = io.readbytes(4).unpack('L<').first
255
+ @actual_count = @read_until_index = io.readbytes(4).unpack('L<').first
256
+ end
257
+
258
+ if has_elements_to_read?
259
+ io.seekbytes(align_element_size(io.offset))
260
+ super
261
+ end
262
+
263
+ if has_deferred_ptrs?
264
+ read_ptr(io)
265
+ end
266
+ end
267
+
268
+ def do_num_bytes(struct_offset = 0)
269
+ sum = 0
270
+
271
+ if is_a?(ConfPlugin) && should_process_max_count?
272
+ sum += 4
273
+ end
274
+
275
+ if is_a?(VarPlugin)
276
+ sum += 8
277
+ end
278
+
279
+ unless empty?
280
+ sum += align_element_size(struct_offset + sum)
281
+ sum += super()
282
+ end
283
+
284
+ if has_deferred_ptrs?
285
+ sum += do_num_bytes_ptr(struct_offset + sum)
286
+ end
287
+ sum
288
+ end
289
+
290
+ def sum_num_bytes_below_index(index)
291
+ (0...index).inject(0) do |sum, i|
292
+ nbytes = 0
293
+ if elements[i].has_parameter?(:byte_align) && elements[i].respond_to?(:bytes_to_align)
294
+ nbytes = elements[i].bytes_to_align(elements[i], sum.ceil)
181
295
  end
296
+ nbytes += elements[i].do_num_bytes
182
297
 
183
- # Warns when reading if :value && no :read_length
184
- module WarnNoReadLengthPlugin
185
- def read_and_return_value(io)
186
- warn "#{debug_name} does not have a :read_length parameter - returning empty array"
187
- ""
188
- end
298
+ if nbytes.is_a?(Integer)
299
+ sum.ceil + nbytes
300
+ else
301
+ sum + nbytes
189
302
  end
190
303
  end
304
+ end
305
+ end
191
306
 
192
- # An NDR Context Handle representation as defined in
193
- # [IDL Data Type Declarations - Basic Type Declarations](http://pubs.opengroup.org/onlinepubs/9629399/apdxn.htm#tagcjh_34_01)
194
- class NdrContextHandle < BinData::Primitive
195
- endian :little
307
+ module ConfClassPlugin; end
196
308
 
197
- uint32 :context_handle_attributes
198
- uuid :context_handle_uuid
309
+ module ConfPlugin
310
+ attr_accessor :read_until_index, :max_count
199
311
 
200
- def get
201
- {:context_handle_attributes => context_handle_attributes, :context_handle_uuid => context_handle_uuid}
202
- end
312
+ def initialize_instance
313
+ @read_until_index = 0
314
+ @max_count = 0
315
+ super
316
+ end
203
317
 
204
- def set(handle)
205
- if handle.is_a?(Hash)
206
- self.context_handle_attributes = handle[:context_handle_attributes]
207
- self.context_handle_uuid = handle[:context_handle_uuid]
208
- elsif handle.is_a?(NdrContextHandle)
209
- read(handle.to_binary_s)
210
- else
211
- read(handle.to_s)
212
- end
318
+ def insert(index, *objs)
319
+ obj = super
320
+ @max_count = length
321
+ obj
322
+ end
323
+
324
+ def slice_index(index)
325
+ obj = super
326
+ @max_count = length
327
+ obj
328
+ end
329
+
330
+ def []=(index, value)
331
+ obj = super
332
+ @max_count = length
333
+ obj
334
+ end
335
+
336
+ def set_max_count(val)
337
+ @max_count = @read_until_index = val
338
+ end
339
+ end
340
+
341
+ module VarPlugin
342
+ attr_accessor :read_until_index, :actual_count, :offset
343
+
344
+ def initialize_instance
345
+ @read_until_index = 0
346
+ @actual_count = 0
347
+ @offset = 0
348
+ super
349
+ end
350
+
351
+ def insert(index, *objs)
352
+ obj = super
353
+ @actual_count = length
354
+ obj
355
+ end
356
+
357
+ def slice_index(index)
358
+ obj = super
359
+ @actual_count = length
360
+ obj
361
+ end
362
+
363
+ def []=(index, value)
364
+ obj = super
365
+ @actual_count = length
366
+ obj
367
+ end
368
+ end
369
+
370
+ # This ArgProcessor needs to inherit from BinData::ArrayArgProcessor to make
371
+ # sure the ArrayArgProcessor `sanitize_parameters!` is called. This will
372
+ # perform proper Array-related sanity checks on the given parameters.
373
+ class ::BinData::NdrArrayArgProcessor < BinData::ArrayArgProcessor
374
+ def sanitize_parameters!(obj_class, params)
375
+ res = super
376
+
377
+ type_class = params[:type]
378
+ # Let the BinData::Array sanitization routine deal with "no type provided"
379
+ return res unless type_class
380
+
381
+ type_class, type_params = params[:type] if type_class.is_a?(Array)
382
+ if type_class.has_parameter?(:byte_align)
383
+ # According to NDR alignemnt rules for arrays: Array alignment is the
384
+ # largest alignment of the array element type and the size information
385
+ # type, if any.
386
+ # So, here, we pick the greatest value between the size of the `size
387
+ # information` field (:max_count or :offset/:actual_count), which is 4
388
+ # bytes for 32-bit NDR, and the element type size
389
+ byte_align = type_class.instance_variable_get(:@obj_params)[:byte_align]
390
+ if obj_class < NdrFixArray
391
+ # Fixed size arrays doesn't have size information
392
+ params[:byte_align] = byte_align
393
+ else
394
+ params[:byte_align] = [4, byte_align].max
213
395
  end
396
+ return res
397
+ elsif type_params&.key?(:byte_align)
398
+ return res
399
+ end
400
+
401
+ raise ArgumentError.new(
402
+ "NDR Arrays must only include elements with the `:byte_align` "\
403
+ "parameter set. This makes sure the whole structure is correctly "\
404
+ "aligned. Use a predefined NDR element instead, or provide the "\
405
+ "`:byte_align` parameter in `:type` (Faulty element type: #{params[:type]})"
406
+ )
407
+ end
408
+ end
409
+
410
+ # [Uni-dimensional Fixed Arrays](https://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_03_01)
411
+ class NdrFixArray < BinData::Array
412
+ arg_processor :ndr_array
413
+
414
+ def initialize_shared_instance
415
+ super
416
+ extend ArrayPlugin
417
+ end
418
+
419
+ def insert(index, *objs)
420
+ fixed_size = get_parameter(:initial_length)
421
+ if (length + objs.size) != fixed_size
422
+ raise ArgumentError, "Can't add new elements to a NdrFixArray (set to #{fixed_size} elements)"
423
+ else
424
+ super
214
425
  end
426
+ end
215
427
 
216
- # An NDR Top-level Full Pointers representation as defined in
217
- # [Transfer Syntax NDR - Top-level Full Pointers](http://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_11_01)
218
- # This class must be inherited and the subclass must have a #referent property
219
- class NdrPointer < BinData::Primitive
220
- endian :little
428
+ def append_new_element
429
+ fixed_size = get_parameter(:initial_length)
430
+ raise ArgumentError, "Can't add new elements to a NdrFixArray (set to #{fixed_size} elements)"
431
+ end
432
+ end
221
433
 
222
- uint32 :referent_id, initial_value: 0
434
+ # Specific implementation for fixed array of bytes, which can be set from an array of unit8 or a string
435
+ class NdrFixedByteArray < NdrFixArray
436
+ default_parameters(type: :ndr_uint8, byte_align: 1)
223
437
 
224
- def do_read(io)
225
- self.referent_id.do_read(io)
226
- if process_referent?
227
- self.referent.do_read(io) unless self.referent_id == 0
228
- end
438
+ def assign(val)
439
+ val = val.bytes if val.is_a?(String)
440
+ super(val.to_ary)
441
+ end
442
+ end
443
+
444
+ # [Uni-dimensional Conformant Arrays](https://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_03_02)
445
+ class NdrConfArray < BinData::Array
446
+ extend ArrayClassPlugin
447
+ end
448
+
449
+ # [Uni-dimensional Varying Arrays](https://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_03_03)
450
+ class NdrVarArray < BinData::Array
451
+ extend ArrayClassPlugin
452
+ end
453
+
454
+ # Uni-dimensional Conformant-varying Arrays
455
+ class NdrConfVarArray < BinData::Array
456
+ extend ArrayClassPlugin
457
+ end
458
+
459
+ # TODO: Multi-dimensional Arrays
460
+
461
+
462
+
463
+ #
464
+ # Strings
465
+ #
466
+
467
+ module StringClassPlugin
468
+ module ExtendVarStringPlugin
469
+ def initialize_shared_instance
470
+ super
471
+ extend VarStringPlugin
472
+ end
473
+ end
474
+ module ExtendConfStringPlugin
475
+ def initialize_shared_instance
476
+ super
477
+ extend ConfStringPlugin
478
+ end
479
+ end
480
+ def self.extended(target)
481
+ target.default_parameters byte_align: 4
482
+ char_size = 1
483
+ char_size = 2 if target < RubySMB::Field::String16 || target < RubySMB::Field::Stringz16
484
+ if target < BinData::Stringz
485
+ target.default_parameters(:max_length => lambda { @obj.actual_count * char_size })
486
+ else
487
+ target.default_parameters(:length => lambda { @obj.actual_count * char_size })
488
+ end
489
+ target.include ExtendVarStringPlugin
490
+ class_name = target.to_s.split('::').last
491
+ if class_name.include?('NdrConfVar')
492
+ target.include ExtendConfStringPlugin
493
+ target.extend ConfClassPlugin
494
+ end
495
+ end
496
+ end
497
+
498
+ module ConfStringPlugin
499
+ attr_accessor :max_count
500
+
501
+ def initialize_instance
502
+ @max_count = 0
503
+ if has_parameter?(:initial_value)
504
+ set_max_count(get_max_count(eval_parameter(:initial_value)))
505
+ end
506
+ super
507
+ end
508
+
509
+ def should_process_max_count?
510
+ # :max_count has already been processed if the parent structure is an
511
+ # NdrStruct, but this is not the case if we are dealing with a pointer
512
+ !parent.is_a?(NdrStruct) || self.is_a?(PointerPlugin)
513
+ end
514
+
515
+ def do_write(io)
516
+ if should_process_max_count?
517
+ io.writebytes([@max_count].pack('L<'))
518
+ end
519
+ super
520
+ end
521
+
522
+ def do_read(io)
523
+ if should_process_max_count?
524
+ set_max_count(io.readbytes(4).unpack('L<').first)
525
+ end
526
+ super
527
+ end
528
+
529
+ def assign(val)
530
+ if val.is_a?(ConfStringPlugin)
531
+ @max_count = val.max_count
532
+ else
533
+ set_max_count(get_max_count(val))
534
+ end
535
+ super
536
+ end
537
+
538
+ def do_num_bytes
539
+ sum = 0
540
+ if should_process_max_count?
541
+ # add max_count size
542
+ sum += 4
543
+ end
544
+ sum + super
545
+ end
546
+
547
+ def get_max_count(val)
548
+ if is_a?(BinData::Stringz)
549
+ max_count = val.to_s.strip.length
550
+ # Only count the terminating NULL byte if the string is not empty
551
+ max_count += 1 if max_count > 0
552
+ return max_count
553
+ else
554
+ return val.to_s.length
555
+ end
556
+ end
557
+
558
+ def set_max_count(val)
559
+ @max_count = val
560
+ end
561
+ end
562
+
563
+ module VarStringPlugin
564
+ attr_accessor :actual_count, :offset
565
+
566
+ def initialize_instance
567
+ @offset = 0
568
+ @actual_count = 0
569
+ if has_parameter?(:initial_value)
570
+ update_actual_count(eval_parameter(:initial_value))
571
+ end
572
+ super
573
+ end
574
+
575
+ def do_write(io)
576
+ io.writebytes([@offset].pack('L<'))
577
+ io.writebytes([@actual_count].pack('L<'))
578
+ super if @actual_count > 0
579
+ end
580
+
581
+ def do_read(io)
582
+ @offset = io.readbytes(4).unpack('L<').first
583
+ @actual_count = io.readbytes(4).unpack('L<').first
584
+ super if @actual_count > 0
585
+ end
586
+
587
+ def assign(val)
588
+ update_actual_count(val)
589
+ super
590
+ end
591
+
592
+ def do_num_bytes
593
+ @actual_count > 0 ? (8 + super) : 8
594
+ end
595
+
596
+ def update_actual_count(val)
597
+ if is_a?(BinData::Stringz)
598
+ @actual_count = val.to_s.strip.length
599
+ # Only count the terminating NULL byte if the string is not empty
600
+ @actual_count += 1 if @actual_count > 0
601
+ else
602
+ @actual_count = val.to_s.length
603
+ end
604
+ end
605
+ end
606
+
607
+ # [Varying Strings](https://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_04_01)
608
+ class NdrVarString < BinData::String
609
+ extend StringClassPlugin
610
+ end
611
+
612
+ class NdrVarStringz < BinData::Stringz
613
+ extend StringClassPlugin
614
+ end
615
+
616
+ class NdrVarWideString < RubySMB::Field::String16
617
+ extend StringClassPlugin
618
+ end
619
+
620
+ class NdrVarWideStringz < RubySMB::Field::Stringz16
621
+ extend StringClassPlugin
622
+ end
623
+
624
+ # [Conformant and Varying Strings](https://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_04_02)
625
+ class NdrConfVarString < BinData::String
626
+ extend StringClassPlugin
627
+ end
628
+
629
+ class NdrConfVarStringz < BinData::Stringz
630
+ extend StringClassPlugin
631
+ end
632
+
633
+ class NdrConfVarWideString < RubySMB::Field::String16
634
+ extend StringClassPlugin
635
+ end
636
+
637
+ class NdrConfVarWideStringz < RubySMB::Field::Stringz16
638
+ extend StringClassPlugin
639
+ end
640
+
641
+ # TODO:[Arrays of Strings](https://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_05)
642
+ # Microsoft DCERPC uses array of pointers for strings. I couldn't find any reference to array of strings.
643
+
644
+
645
+ #
646
+ # [Structures](https://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_06)
647
+ #
648
+
649
+ module StructPlugin
650
+ include ConstructedTypePlugin
651
+
652
+ def should_process_max_count?
653
+ # According to the NDR defintion for Structures Containing a Conformant
654
+ # Array:
655
+ #
656
+ # "In the NDR representation of a structure that contains a
657
+ # conformant array, the unsigned long integers that give maximum element
658
+ # counts for dimensions of the array are moved to the beginning of the
659
+ # structure, and the array elements appear in place at the end of the
660
+ # structure. If a structure that contains a conformant array itself a
661
+ # member of another structure, the maximum element counts are further
662
+ # moved to the beginning of the containing structure. This construction
663
+ # iterates through all enclosing structures."
664
+ #
665
+ # This only applies if the current object is the top level structure (no
666
+ # parent). Note that if it is a pointer to a structure and the current
667
+ # object is being deferred, :max_count still need to be processed since
668
+ # it had not been moved to the beginning of the parent structure.
669
+ klass = is_a?(PointerPlugin) ? self.class.superclass : self.class
670
+ parent_obj = nil
671
+ # TODO: possible issue: parent can be a BinData::Choice, and won't be
672
+ # detected as a ConstructedTypePlugin, even if the embeding structure is.
673
+ # Check this with a BinData::Choice that points to a structure embedding
674
+ # a conformant structure
675
+ if parent&.is_a?(ConstructedTypePlugin)
676
+ parent_obj = parent.get_top_level_constructed_type
677
+ end
678
+ klass.has_conformant_array && (parent_obj.nil? || parent_obj.is_deferring(self))
679
+ end
680
+
681
+ def do_write(io)
682
+ if should_process_max_count?
683
+ max_count = retrieve_max_count
684
+ io.writebytes([max_count].pack('L<')) if max_count
685
+
686
+ # Align the structure according to the alignment rules for the structure
687
+ if respond_to?(:referent_bytes_align)
688
+ io.writebytes("\x00" * referent_bytes_align(io.offset))
689
+ elsif has_parameter?(:byte_align)
690
+ io.writebytes("\x00" * bytes_to_align(self, io.offset))
229
691
  end
692
+ end
230
693
 
231
- def do_write(io)
232
- self.referent_id.do_write(io)
233
- if process_referent?
234
- self.referent.do_write(io) unless self.referent_id == 0
235
- end
694
+ super
695
+
696
+ if has_deferred_ptrs?
697
+ write_ptr(io)
698
+ end
699
+ end
700
+
701
+ def do_read(io)
702
+ if should_process_max_count?
703
+ set_max_count(io.readbytes(4).unpack('L<').first)
704
+
705
+ # Align the structure according to the alignment rules for the structure
706
+ if respond_to?(:referent_bytes_align)
707
+ io.seekbytes(referent_bytes_align(io.offset))
708
+ elsif has_parameter?(:byte_align)
709
+ io.seekbytes(bytes_to_align(self, io.offset))
236
710
  end
711
+ end
237
712
 
238
- def set(v)
239
- if v == :null
240
- self.referent.clear
241
- self.referent_id = 0
242
- else
243
- if self.referent.respond_to?(:set)
244
- self.referent.set(v)
245
- else
246
- self.referent = v
247
- end
248
- self.referent_id = rand(0xFFFFFFFF) if self.referent_id == 0
249
- end
713
+ super
714
+
715
+ if has_deferred_ptrs?
716
+ read_ptr(io)
717
+ end
718
+ end
719
+
720
+ def retrieve_max_count
721
+ obj = self[field_names.last]
722
+ return obj.length if obj.is_a?(ConfPlugin)
723
+ return obj.get_max_count(obj) if obj.is_a?(ConfStringPlugin)
724
+ if obj.is_a?(NdrStruct)
725
+ return obj.retrieve_max_count
726
+ end
727
+ end
728
+
729
+ def set_max_count(val)
730
+ obj = self[field_names.last]
731
+ obj.set_max_count(val)
732
+ end
733
+
734
+ def do_num_bytes
735
+ sum = 0
736
+
737
+ if should_process_max_count?
738
+ # count max_count (4 bytes)
739
+ max_count = retrieve_max_count
740
+ sum += 4 if max_count
741
+
742
+ if respond_to?(:referent_bytes_align)
743
+ sum += referent_bytes_align(sum)
744
+ elsif has_parameter?(:byte_align)
745
+ sum += bytes_to_align(self, sum)
250
746
  end
747
+ end
251
748
 
252
- def get
253
- if self.referent_id == 0
254
- :null
255
- else
256
- self.referent
257
- end
749
+ sum += super
750
+
751
+ if has_deferred_ptrs?
752
+ sum += do_num_bytes_ptr(sum)
753
+ end
754
+
755
+ sum
756
+ end
757
+
758
+ def bytes_to_align(obj, rel_offset)
759
+ if obj.is_a?(PointerPlugin)
760
+ # Pointers are always 4-bytes aligned
761
+ return (4 - (rel_offset % 4)) % 4
762
+ end
763
+ if obj.is_a?(ConfPlugin)
764
+ # `max_count` should have been handled at the begining of the structure
765
+ # already. We need to fix `rel_offset` since it includes the
766
+ # `max_count` 4 bytes, plus the possible padding bytes needed to align
767
+ # the structure. This is required because BinData Struct is not
768
+ # aware of `max_count` and considere the first field to be the begining
769
+ # of the structure instead. We have to make sure the alignment is
770
+ # calculated from the begining of the structure.
771
+ align = eval_parameter(:byte_align)
772
+ pad_length = (align - (4 % align)) % align
773
+ rel_offset += (4 + pad_length)
774
+
775
+ # We need to handle another corner case, which is a Conformant array
776
+ # (not Varying). The size information (max_count) has been place in
777
+ # from of the structure and no other size information is present before
778
+ # the actual elements of the array. Therefore, the alignment must be
779
+ # done accroding to th rules of the elements. Since a NdrArray has its
780
+ # default :byte_align value set to 4 (:max_count size), we have to make
781
+ # sure the element size is used instead.
782
+ unless obj.is_a?(VarPlugin)
783
+ return obj.align_element_size(rel_offset)
258
784
  end
785
+ end
786
+ is_a?(BinData::ByteAlignPlugin) ? super : 0
259
787
 
260
- def process_referent?
261
- current_parent = parent
262
- loop do
263
- return true unless current_parent
264
- return false if current_parent.is_a?(NdrStruct)
265
- current_parent = current_parent.parent
266
- end
788
+ end
789
+ end
790
+
791
+ class NdrStruct < BinData::Record
792
+ # Caller must specify :byte_align according to the type of the largest element in the structure.
793
+ # See https://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_02
794
+ #
795
+ # "The alignment of a structure in the octet stream is the largest of the
796
+ # alignments of the fields it contains. These fields may also be
797
+ # constructed types. The same alignment rules apply recursively to nested
798
+ # constructed types."
799
+ mandatory_parameters(:byte_align)
800
+
801
+ class << self; attr_reader :has_conformant_array end
802
+
803
+ def self.validate_conformant_array(field)
804
+ if @has_conformant_array
805
+ raise ArgumentError.new(
806
+ "Invalid structure #{self}: Conformant array or embedded structure "\
807
+ "with Conformant array must be the last member of the structure"
808
+ )
809
+ end
810
+ obj_proto = field.last.prototype
811
+ obj_class = obj_proto.instance_variable_get(:@obj_class)
812
+ @has_conformant_array = true if obj_class < NdrStruct && obj_class.has_conformant_array
813
+ if obj_class.is_a?(ConfClassPlugin) && !obj_class.is_a?(PointerClassPlugin)
814
+ @has_conformant_array = true
815
+ end
816
+ end
817
+
818
+ def self.method_missing(symbol, *args, &block)
819
+ field = super
820
+ if field.is_a?(::Array) && field.last.is_a?(BinData::SanitizedField)
821
+ unless field.last.has_parameter?(:byte_align) || field.last.instantiate.bit_aligned?
822
+ raise ArgumentError.new(
823
+ "NDR Structures must only include elements with the `:byte_align` "\
824
+ "parameter set. This makes sure the whole structure is correctly "\
825
+ "aligned. Use a predefined NDR element instead, or provide the "\
826
+ "`:byte_align` parameter when defining the structure "\
827
+ "(Faulty element: #{field.last.name})"
828
+ )
267
829
  end
830
+ validate_conformant_array(field)
268
831
  end
832
+ field
833
+ end
834
+
835
+ def initialize_shared_instance
836
+ super
837
+ extend StructPlugin
838
+ end
839
+ end
840
+
841
+ # TODO: Unions
842
+ # TODO: Pipes
269
843
 
270
- # A pointer to a NdrString structure
271
- class NdrLpStr < NdrPointer
272
- endian :little
844
+ #
845
+ # [Pointers](https://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_10)
846
+ #
273
847
 
274
- ndr_string :referent, onlyif: -> { self.referent_id != 0 }
848
+ module PointerClassPlugin
849
+ module ExtendPointerPlugin
850
+ def initialize_shared_instance
851
+ super
852
+ extend PointerPlugin
275
853
  end
854
+ end
855
+ def self.extended(target)
856
+ target.default_parameters byte_align: 4
857
+ target.arg_processor :ndr_pointer
858
+ target.include ExtendPointerPlugin
859
+ end
860
+ end
276
861
 
277
- class NdrLpDword < NdrPointer
278
- endian :little
279
862
 
280
- uint32 :referent, onlyif: -> { self.referent_id != 0 }
863
+ module TopLevelPlugin
864
+ module TopLevelClassMethods
865
+ def pos
866
+ @@pos
867
+ end
868
+ def increment_pos
869
+ @@pos += 1
281
870
  end
871
+ def reset_pos
872
+ @@pos = 0
873
+ end
874
+ end
282
875
 
283
- # A pointer to an NDR Uni-dimensional Conformant-varying Arrays of bytes
284
- class NdrLpByteArray < NdrPointer
285
- endian :little
876
+ def self.extended(target)
877
+ target.class.extend(TopLevelClassMethods)
878
+ target.class.reset_pos
879
+ end
286
880
 
287
- ndr_byte_array :referent, onlyif: -> { self.referent_id != 0 }
881
+ def initialize_instance
882
+ super
883
+ @standalone_ptr = false
884
+ end
288
885
 
289
- def set(v)
290
- if v != :null && v.is_a?(NdrLpByteArray)
291
- super(v.referent)
292
- else
293
- super(v)
294
- end
886
+ def do_write(io, is_deferred: false)
887
+ # If for whatever reasons, the #pos value has been modified, reset it to
888
+ # make sure the pointer ref_id will start from INITIAL_REF_ID
889
+ self.class.reset_pos if is_top_level_ptr || @standalone_ptr
890
+ if is_deferred
891
+ super(io, is_deferred: is_deferred)
892
+ else
893
+ super(io)
894
+ end
895
+ # Since #pos has been incremented for each embedded pointer, let's reset
896
+ # it to go back to its initial state
897
+ self.class.reset_pos if is_top_level_ptr || @standalone_ptr
898
+ end
899
+
900
+ def to_binary_s
901
+ @standalone_ptr = true
902
+ res = super
903
+ @standalone_ptr = false
904
+ res
905
+ end
906
+
907
+ def set_top_level_ptr
908
+ @top_level_ptr = true
909
+ #update_ref_ids
910
+ end
911
+
912
+ def unset_top_level_ptr
913
+ @top_level_ptr = false
914
+ end
915
+
916
+ def is_top_level_ptr
917
+ !!@top_level_ptr
918
+ end
919
+
920
+ def num_bytes
921
+ @standalone_ptr = true
922
+ res = super
923
+ @standalone_ptr = false
924
+ res
925
+ end
926
+ end
927
+
928
+ # Windows SMB client uses 0x00020000 as an initial reference ID, but it is
929
+ # rejected by the server on the Windows Server 2003. On this version, only
930
+ # 0x00000001 seems to be accepted. So, we need to use this value to maintain
931
+ # compatibility.
932
+ INITIAL_REF_ID = 0x00000001
933
+
934
+ module PointerPlugin
935
+ attr_accessor :ref_id
936
+
937
+ def initialize_instance
938
+ if @ref_id.nil?
939
+ if eval_parameter(:initial_value)
940
+ instantiate_referent
941
+ else
942
+ @ref_id = 0
295
943
  end
296
944
  end
945
+ extend_top_level_class
946
+ super
947
+ end
297
948
 
298
- # A pointer to a Windows FILETIME structure
299
- class NdrLpFileTime < NdrPointer
300
- endian :little
301
-
302
- file_time :referent, onlyif: -> { self.referent_id != 0 }
303
- end
304
-
305
- # A generic NDR structure that implements logic to #read and #write
306
- # (#to_binary_s) in case the structure contains BinData::Array or
307
- # NdrPointer fields. This class must be inherited.
308
- class NdrStruct < BinData::Record
309
-
310
- def do_read(io)
311
- super(io)
312
- each_pair do |_name, field|
313
- case field
314
- when BinData::Array
315
- field.each do |element|
316
- next unless element.is_a?(NdrPointer)
317
- next if element.referent_id == 0
318
- pad = (4 - io.offset % 4) % 4
319
- io.seekbytes(pad) if pad > 0
320
- element.referent.do_read(io)
321
- end
322
- when NdrPointer
323
- next if field.referent_id == 0
324
- pad = (4 - io.offset % 4) % 4
325
- io.seekbytes(pad) if pad > 0
326
- field.referent.do_read(io)
327
- end
328
- end
949
+ def extend_top_level_class
950
+ current = self
951
+ loop do
952
+ current.extend(TopLevelPlugin) unless current.is_a?(TopLevelPlugin)
953
+ if current.parent.nil?
954
+ current.set_top_level_ptr unless current.is_top_level_ptr
955
+ break
956
+ else
957
+ current.unset_top_level_ptr if current.is_top_level_ptr
958
+ current = current.parent
329
959
  end
960
+ end
961
+ end
962
+
963
+ def snapshot
964
+ if is_alias?
965
+ fetch_alias_referent
966
+ elsif is_null_ptr? && !eval_parameter(:initial_value)
967
+ :null
968
+ else
969
+ super
970
+ end
971
+ end
972
+
973
+ def referent_bytes_align(offset)
974
+ align = self.class.superclass.default_parameters[:byte_align]
975
+ align = eval_parameter(:referent_byte_align) unless align
976
+ (align - (offset % align)) % align
977
+ end
330
978
 
331
- def do_write(io)
332
- super(io)
333
- each_pair do |_name, field|
334
- case field
335
- when BinData::Array
336
- field.each do |element|
337
- next unless element.is_a?(NdrPointer)
338
- next if element.referent_id == 0
339
- pad = (4 - io.offset % 4) % 4
340
- io.writebytes("\x00" * pad + element.referent.to_binary_s)
341
- end
342
- when NdrPointer
343
- next if field.referent_id == 0
344
- pad = (4 - io.offset % 4) % 4
345
- io.writebytes("\x00" * pad + field.referent.to_binary_s)
346
- end
979
+ def write_ref_id(io)
980
+ if is_alias?
981
+ ref_field = fetch_alias_referent
982
+ if ref_field
983
+ if ref_field.class != self.class
984
+ raise ArgumentError, "Pointer points to a different referent type: #{ref_field.class} (set to #{obj.class})"
347
985
  end
986
+ @ref_id = ref_field.ref_id
987
+ end
988
+ elsif @ref_id != 0 || (is_null_ptr? && eval_parameter(:initial_value))
989
+ @ref_id = INITIAL_REF_ID + self.class.pos
990
+ self.class.increment_pos unless @standalone_ptr
991
+ end
992
+ io.writebytes([@ref_id].pack('L<'))
993
+ end
994
+
995
+ def do_write(io, is_deferred: false)
996
+ if is_deferred
997
+ if is_a?(NdrStruct) && self.class.superclass.has_conformant_array
998
+ # align :max_count since it will be placed in front of the structure.
999
+ # The structure itself will be properly aligned later.
1000
+ align = (4 - (io.offset % 4)) % 4
1001
+ io.writebytes("\x00" * align)
1002
+ else
1003
+ io.writebytes("\x00" * referent_bytes_align(io.offset))
1004
+ end
1005
+ else
1006
+ write_ref_id(io)
1007
+ parent_obj = nil
1008
+ if parent&.is_a?(ConstructedTypePlugin)
1009
+ parent_obj = parent.get_top_level_constructed_type
1010
+ end
1011
+ if parent_obj && @ref_id != 0 && !@standalone_ptr
1012
+ parent_obj.defer_ptr(self)
1013
+ return
1014
+ end
1015
+ end
1016
+ super(io) unless (is_null_ptr? && !eval_parameter(:initial_value)) || is_alias?
1017
+ end
1018
+
1019
+ def do_read(io, is_deferred: false)
1020
+ if is_deferred
1021
+ if is_a?(NdrStruct) && self.class.superclass.has_conformant_array
1022
+ # align :max_count since it will be placed in front of the structure.
1023
+ # The structure itself will be properly aligned later.
1024
+ align = (4 - (io.offset % 4)) % 4
1025
+ io.seekbytes(align)
1026
+ else
1027
+ io.seekbytes(referent_bytes_align(io.offset))
1028
+ end
1029
+ else
1030
+ @ref_id = io.readbytes(4).unpack('L<').first
1031
+ parent_obj = nil
1032
+ if parent&.is_a?(ConstructedTypePlugin)
1033
+ parent_obj = parent.get_top_level_constructed_type
1034
+ end
1035
+ if parent_obj && @ref_id != 0
1036
+ parent_obj.defer_ptr(self)
1037
+ return
348
1038
  end
349
1039
  end
1040
+ super(io) unless is_null_ptr? || is_alias?
1041
+ end
350
1042
 
351
- class NdrStringPtrsw < NdrStruct
352
- endian :little
1043
+ def assign(val)
1044
+ if val == :null
1045
+ @ref_id = 0
1046
+ elsif is_alias?
1047
+ ref_field = fetch_alias_referent
1048
+ raise ArgumentError, "Referent of alias pointer does not exist: #{get_parameter(:ref_to)}" unless ref_field
1049
+ ref_field.assign(val)
1050
+ else
1051
+ instantiate_referent if is_null_ptr?
1052
+ super
1053
+ end
1054
+ end
353
1055
 
354
- uint32 :max_count, initial_value: -> { self.elements.size }
355
- array :elements, type: :ndr_lp_str, read_until: -> { index == self.max_count - 1 }, onlyif: -> { self.max_count > 0 }
1056
+ def is_alias?
1057
+ has_parameter?(:ref_to)
1058
+ end
356
1059
 
357
- def get
358
- self.elements
1060
+ def fetch_alias_referent(current: parent, ref: get_parameter(:ref_to), name: nil)
1061
+ return if current.nil?
1062
+ if current.get_parameter(:ref_to) == ref
1063
+ raise ArgumentError.new(
1064
+ "Pointer alias refering to #{ref} cannot be found. This referent "\
1065
+ "should appears before the alias in the stream"
1066
+ )
1067
+ end
1068
+ return current if name == ref
1069
+ res = nil
1070
+ case current
1071
+ when ArrayPlugin
1072
+ current.each do |element|
1073
+ res = fetch_alias_referent(current: element, ref: ref, name: name)
1074
+ break if res
1075
+ end
1076
+ when BinData::Record, BinData::Struct
1077
+ current.each_pair do |name, field|
1078
+ res = fetch_alias_referent(current: field, ref: ref, name: name)
1079
+ break if res
359
1080
  end
1081
+ end
1082
+ return res
1083
+ end
360
1084
 
361
- def set(v)
362
- v = v.elements if v.is_a?(self.class)
363
- self.elements = v.to_ary
364
- self.max_count = self.elements.size unless self.elements.equal?(v)
1085
+ def do_num_bytes(struct_offset = 0, is_deferred: false)
1086
+ sum = 0
1087
+ if is_deferred
1088
+ if is_a?(NdrStruct) && self.class.superclass.has_conformant_array
1089
+ # align :max_count since it will be placed in front of the structure.
1090
+ # The structure itself will be properly aligned later.
1091
+ align = (4 - (struct_offset % 4)) % 4
1092
+ sum += align
1093
+ else
1094
+ sum += referent_bytes_align(struct_offset)
365
1095
  end
1096
+ else
1097
+ # add ref_id size
1098
+ sum += 4
366
1099
 
367
- def do_num_bytes
368
- to_binary_s.size
1100
+ parent_obj = nil
1101
+ if parent&.is_a?(ConstructedTypePlugin)
1102
+ parent_obj = parent.get_top_level_constructed_type
1103
+ end
1104
+ if parent_obj && @ref_id != 0 && !@standalone_ptr
1105
+ parent_obj.defer_ptr(self)
1106
+ # only return ref_id size, the actual referent size will be added later
1107
+ return sum
1108
+ end
1109
+ end
1110
+ unless (is_null_ptr? && !eval_parameter(:initial_value)) || is_alias?
1111
+ if is_a?(ArrayPlugin)
1112
+ sum += super(struct_offset + sum)
1113
+ else
1114
+ sum += super()
369
1115
  end
370
1116
  end
371
1117
 
372
- class NdrLpStringPtrsw < NdrPointer
373
- endian :little
1118
+ sum
1119
+ end
374
1120
 
375
- ndr_string_ptrsw :referent, onlyif: -> { self.referent_id != 0 }
1121
+ def instantiate_referent
1122
+ @ref_id = INITIAL_REF_ID
1123
+ end
376
1124
 
377
- def set(v)
378
- super(v.respond_to?(:to_ary) ? v.to_ary : v)
379
- end
1125
+ def is_null_ptr?
1126
+ @ref_id == 0
1127
+ end
1128
+
1129
+ def insert(index, *objs)
1130
+ obj = super
1131
+ # If we just pushed a new element and it was a null pointer (ref_id==0),
1132
+ # we will initialize the ref_id to make sure it is not considered a null
1133
+ # pointer anymore
1134
+ if is_null_ptr? && is_a?(BinData::Array) && !empty?
1135
+ instantiate_referent
1136
+ end
1137
+ obj
1138
+ end
1139
+ end
1140
+
1141
+ class ::BinData::NdrPointerArgProcessor < BinData::BaseArgProcessor
1142
+ def sanitize_parameters!(obj_class, params)
1143
+ obj_klass = obj_class
1144
+ obj_klass = obj_class.superclass if obj_class.superclass.arg_processor == self
1145
+ res = obj_class.superclass.arg_processor.sanitize_parameters!(obj_klass, params)
1146
+
1147
+ return res if obj_class.superclass.default_parameters[:byte_align]
1148
+ return res if params[:referent_byte_align]
1149
+
1150
+ raise ArgumentError.new(
1151
+ "NDR Pointers referent must have `:byte_align` parameter set. This "\
1152
+ "makes sure the whole structure is correctly aligned. Use a predefined "\
1153
+ "NDR element instead, or provide the `:referent_byte_align` parameter "\
1154
+ "when defining the structure (Faulty pointer class: #{obj_class})"
1155
+ )
1156
+ end
1157
+
1158
+ def extract_args(obj_class, obj_args)
1159
+ obj_class = obj_class.superclass if obj_class.superclass.arg_processor == self
1160
+ obj_class.superclass.arg_processor.extract_args(obj_class, obj_args)
1161
+ end
1162
+ end
1163
+
1164
+ # Pointers to NDR integers. This defined four pointers:
1165
+ # - NdrUint8Ptr
1166
+ # - NdrUint16Ptr
1167
+ # - NdrUint32Ptr
1168
+ # - NdrUint64Ptr
1169
+ {NdrUint8: 1, NdrUint16: 2, NdrUint32: 4, NdrUint64: 8}.each do |klass, align|
1170
+ new_klass_name = "#{klass.to_s}Ptr"
1171
+ unless self.const_defined?(new_klass_name)
1172
+ new_klass = Class.new(const_get(klass)) do
1173
+ extend PointerClassPlugin
1174
+ end
1175
+ self.const_set(new_klass_name, new_klass)
1176
+ BinData::RegisteredClasses.register(new_klass_name, new_klass)
1177
+ end
1178
+ end
1179
+
1180
+ # Pointers to other classes
1181
+ class NdrCharPtr < NdrChar
1182
+ extend PointerClassPlugin
1183
+ end
1184
+
1185
+ class NdrBooleanPtr < NdrBoolean
1186
+ extend PointerClassPlugin
1187
+ end
1188
+
1189
+ class NdrStringPtr < NdrConfVarString
1190
+ extend PointerClassPlugin
1191
+ end
1192
+
1193
+ class NdrStringzPtr < NdrConfVarStringz
1194
+ extend PointerClassPlugin
1195
+ end
1196
+
1197
+ class NdrWideStringPtr < NdrConfVarWideString
1198
+ extend PointerClassPlugin
1199
+ end
1200
+
1201
+ class NdrWideStringzPtr < NdrConfVarWideStringz
1202
+ extend PointerClassPlugin
1203
+ end
1204
+
1205
+ class NdrByteArrayPtr < NdrConfVarArray
1206
+ default_parameters type: :ndr_uint8
1207
+ extend PointerClassPlugin
1208
+ end
1209
+
1210
+ class NdrFileTimePtr < NdrFileTime
1211
+ extend PointerClassPlugin
1212
+ end
1213
+
1214
+ class UuidPtr < RubySMB::Dcerpc::Uuid
1215
+ default_parameter referent_byte_align: 4
1216
+ extend PointerClassPlugin
1217
+ end
1218
+
1219
+ # An NDR Context Handle representation as defined in
1220
+ # [IDL Data Type Declarations - Basic Type Declarations](http://pubs.opengroup.org/onlinepubs/9629399/apdxn.htm#tagcjh_34_01)
1221
+ class NdrContextHandle < BinData::Primitive
1222
+ default_parameters byte_align: 4
1223
+ endian :little
1224
+
1225
+ uint32 :context_handle_attributes
1226
+ uuid :context_handle_uuid
1227
+
1228
+ def get
1229
+ {:context_handle_attributes => context_handle_attributes, :context_handle_uuid => context_handle_uuid}
1230
+ end
1231
+
1232
+ def set(handle)
1233
+ if handle.is_a?(Hash)
1234
+ self.context_handle_attributes = handle[:context_handle_attributes]
1235
+ self.context_handle_uuid = handle[:context_handle_uuid]
1236
+ elsif handle.is_a?(NdrContextHandle)
1237
+ read(handle.to_binary_s)
1238
+ else
1239
+ read(handle.to_s)
380
1240
  end
381
1241
  end
382
1242
  end
1243
+
383
1244
  end
1245
+