ruby_smb 3.3.9 → 3.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/metasploit-framework_acceptance.yml +47 -0
  4. data/lib/ruby_smb/client.rb +5 -5
  5. data/lib/ruby_smb/dcerpc/client.rb +2 -4
  6. data/lib/ruby_smb/dcerpc/icpr.rb +2 -2
  7. data/lib/ruby_smb/dcerpc/request.rb +4 -1
  8. data/lib/ruby_smb/dcerpc/samr/samr_get_members_in_group_request.rb +23 -0
  9. data/lib/ruby_smb/dcerpc/samr/samr_get_members_in_group_response.rb +34 -0
  10. data/lib/ruby_smb/dcerpc/samr/samr_open_group_request.rb +26 -0
  11. data/lib/ruby_smb/dcerpc/samr/samr_open_group_response.rb +24 -0
  12. data/lib/ruby_smb/dcerpc/samr.rb +71 -1
  13. data/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_get_info_request.rb +0 -3
  14. data/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_user_enum_request.rb +25 -0
  15. data/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_user_enum_response.rb +25 -0
  16. data/lib/ruby_smb/dcerpc/wkssvc.rb +118 -3
  17. data/lib/ruby_smb/gss/provider/ntlm.rb +5 -9
  18. data/lib/ruby_smb/ntlm/client.rb +3 -74
  19. data/lib/ruby_smb/ntlm.rb +0 -37
  20. data/lib/ruby_smb/server/server_client/tree_connect.rb +2 -2
  21. data/lib/ruby_smb/version.rb +1 -1
  22. data/lib/ruby_smb.rb +0 -2
  23. data/ruby_smb.gemspec +9 -2
  24. data/spec/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_get_info_request_spec.rb +0 -8
  25. data/spec/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_get_info_response_spec.rb +1 -1
  26. data/spec/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_identity_handle.rb +7 -0
  27. data/spec/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_user_enum_request_spec.rb +71 -0
  28. data/spec/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_user_enum_response_spec.rb +65 -0
  29. data/spec/lib/ruby_smb/dcerpc/wkssvc_spec.rb +58 -1
  30. data/spec/lib/ruby_smb/gss/provider/ntlm/authenticator_spec.rb +119 -4
  31. data/spec/lib/ruby_smb/ntlm/client/session_spec.rb +1 -1
  32. data.tar.gz.sig +0 -0
  33. metadata +19 -7
  34. metadata.gz.sig +0 -0
  35. data/lib/ruby_smb/ntlm/custom/string_encoder.rb +0 -22
  36. data/lib/ruby_smb/utils.rb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 278206369fbc36f22011dbdaa45154a7a937468c2178e05c76b2ccc0b5032345
4
- data.tar.gz: 9fc1a683bc32bd5e35860fba5a74749611d9ae4298c3cd3a93b7b0949adea78d
3
+ metadata.gz: 200e26ef444f49e42d6671ae291c557dbaa6d0b7ab5424df4b7c973fa990dbe5
4
+ data.tar.gz: 27ec24e3cd852a0634c1ef11067776a887ea06ad10e2752fc0acb2e6473d8d9a
5
5
  SHA512:
6
- metadata.gz: d7cd41abca3c816ad18fedad7f544a42a46f8d863447678e408661d11688696bab1ef91cf85557c5757b916f5bcaee7b047231a4f35e8d4683f8a9e56a2a559b
7
- data.tar.gz: 408e4e81542732f5f31e16c8bc970629518f7a8a56c62c697cd98e3066e93280fb2270f346a63769e05d2cf9cd7bdd2b5ac9344e058c5190d9d88621d321bf24
6
+ metadata.gz: 84922acc15245aee475e8d8b3db354be2b389ef68fd9988ee2e14f850e9173b03220b51152a18bdcf097aaabbac08b78aeea7dc6e2be5ee3994e5894c5c7bc60
7
+ data.tar.gz: 1bc3d330a3c241a2e2e2b877d8b932830621fc6f12dd66cf718536a43971ab8c15bb4bcba498da72b891233a68d87d02a28396ee959652382f74d02d274bb2b0
checksums.yaml.gz.sig CHANGED
Binary file
@@ -0,0 +1,47 @@
1
+ name: Metasploit Framework Acceptance
2
+
3
+ # Optional, enabling concurrency limits: https://docs.github.com/en/actions/using-jobs/using-concurrency
4
+ #concurrency:
5
+ # group: ${{ github.ref }}-${{ github.workflow }}
6
+ # cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
7
+
8
+ # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
9
+ permissions:
10
+ actions: none
11
+ checks: none
12
+ contents: none
13
+ deployments: none
14
+ id-token: none
15
+ issues: none
16
+ discussions: none
17
+ packages: none
18
+ pages: none
19
+ pull-requests: none
20
+ repository-projects: none
21
+ security-events: none
22
+ statuses: none
23
+
24
+ on:
25
+ workflow_dispatch:
26
+ inputs:
27
+ metasploitFrameworkCommit:
28
+ description: 'metasploit-framework branch would like to test'
29
+ required: true
30
+ default: 'master'
31
+ push:
32
+ branches-ignore:
33
+ - gh-pages
34
+ - metakitty
35
+ pull_request:
36
+ branches:
37
+ - '*'
38
+ # Example of running as a cron, to weed out flaky tests
39
+ # schedule:
40
+ # - cron: '*/15 * * * *'
41
+
42
+ jobs:
43
+ build:
44
+ uses: rapid7/metasploit-framework/.github/workflows/shared_smb_acceptance.yml@master
45
+ with:
46
+ metasploit_framework_commit: ${{ github.event.inputs.metasploitFrameworkCommit }}
47
+ build_smb: true
@@ -4,7 +4,6 @@ module RubySMB
4
4
  class Client
5
5
  require 'ruby_smb/ntlm'
6
6
  require 'ruby_smb/signing'
7
- require 'ruby_smb/utils'
8
7
  require 'ruby_smb/client/negotiation'
9
8
  require 'ruby_smb/client/authentication'
10
9
  require 'ruby_smb/client/tree_connect'
@@ -320,11 +319,12 @@ module RubySMB
320
319
  if smb1 == false && smb2 == false && smb3 == false
321
320
  raise ArgumentError, 'You must enable at least one Protocol'
322
321
  end
322
+
323
323
  @dispatcher = dispatcher
324
324
  @pid = rand(0xFFFF)
325
325
  @domain = domain
326
326
  @local_workstation = local_workstation
327
- @password = RubySMB::Utils.safe_encode((password||''), 'utf-8')
327
+ @password = (password || '')
328
328
  @sequence_counter = 0
329
329
  @session_id = 0x00
330
330
  @session_key = ''
@@ -334,7 +334,7 @@ module RubySMB
334
334
  @smb1 = smb1
335
335
  @smb2 = smb2
336
336
  @smb3 = smb3
337
- @username = RubySMB::Utils.safe_encode((username||''), 'utf-8')
337
+ @username = (username || '')
338
338
  @max_buffer_size = MAX_BUFFER_SIZE
339
339
  # These sizes will be modified during negotiation
340
340
  @server_max_buffer_size = SERVER_MAX_BUFFER_SIZE
@@ -417,8 +417,8 @@ module RubySMB
417
417
  local_workstation: self.local_workstation, ntlm_flags: NTLM::DEFAULT_CLIENT_FLAGS)
418
418
  @domain = domain
419
419
  @local_workstation = local_workstation
420
- @password = RubySMB::Utils.safe_encode((pass||''), 'utf-8')
421
- @username = RubySMB::Utils.safe_encode((user||''), 'utf-8')
420
+ @password = (pass || '')
421
+ @username = (user || '')
422
422
 
423
423
  @ntlm_client = RubySMB::NTLM::Client.new(
424
424
  @username,
@@ -9,12 +9,10 @@ module RubySMB
9
9
  require 'ruby_smb/dcerpc'
10
10
  require 'ruby_smb/gss'
11
11
  require 'ruby_smb/peer_info'
12
- require 'ruby_smb/utils'
13
12
 
14
13
  include Dcerpc
15
14
  include Epm
16
15
  include PeerInfo
17
- include Utils
18
16
 
19
17
  # The default maximum size of a RPC message that the Client accepts (in bytes)
20
18
  MAX_BUFFER_SIZE = 64512
@@ -120,8 +118,8 @@ module RubySMB
120
118
  @read_timeout = read_timeout
121
119
  @domain = domain
122
120
  @local_workstation = local_workstation
123
- @username = RubySMB::Utils.safe_encode(username, 'utf-8')
124
- @password = RubySMB::Utils.safe_encode(password, 'utf-8')
121
+ @username = username
122
+ @password = password
125
123
  @max_buffer_size = MAX_BUFFER_SIZE
126
124
  @call_id = 1
127
125
  @ctx_id = 0
@@ -35,7 +35,7 @@ module RubySMB
35
35
  def cert_server_request(attributes:, authority:, csr:)
36
36
  cert_server_request_request = CertServerRequestRequest.new(
37
37
  pwsz_authority: authority,
38
- pctb_attribs: { pb: (RubySMB::Utils.safe_encode(attributes.map { |k,v| "#{k}:#{v}" }.join("\n"), 'UTF-16le').force_encoding('ASCII-8bit') + "\x00\x00".b) },
38
+ pctb_attribs: { pb: (attributes.map { |k,v| "#{k}:#{v}" }.join("\n").encode('UTF-16LE').force_encoding('ASCII-8BIT') + "\x00\x00".b) },
39
39
  pctb_request: { pb: csr.to_der }
40
40
  )
41
41
 
@@ -53,7 +53,7 @@ module RubySMB
53
53
  ret = {
54
54
  certificate: nil,
55
55
  disposition: cert_server_request_response.pdw_disposition.value,
56
- disposition_message: cert_server_request_response.pctb_disposition_message.buffer.chomp("\x00\x00").force_encoding('utf-16le').encode,
56
+ disposition_message: cert_server_request_response.pctb_disposition_message.buffer.chomp("\x00\x00").force_encoding('UTF-16LE').encode,
57
57
  status: {
58
58
  CR_DISP_ISSUED => :issued,
59
59
  CR_DISP_UNDER_SUBMISSION => :submitted,
@@ -70,6 +70,8 @@ module RubySMB
70
70
  samr_close_handle_request Samr::SAMR_CLOSE_HANDLE
71
71
  samr_get_alias_membership_request Samr::SAMR_GET_ALIAS_MEMBERSHIP
72
72
  samr_open_user_request Samr::SAMR_OPEN_USER
73
+ samr_open_group_request Samr::SAMR_OPEN_GROUP
74
+ samr_get_members_in_group_request Samr::SAMR_GET_MEMBERS_IN_GROUP
73
75
  samr_get_groups_for_user_request Samr::SAMR_GET_GROUPS_FOR_USER
74
76
  samr_enumerate_domains_in_sam_server_request Samr::SAMR_ENUMERATE_DOMAINS_IN_SAM_SERVER
75
77
  samr_lookup_names_in_domain_request Samr::SAMR_LOOKUP_NAMES_IN_DOMAIN
@@ -80,7 +82,8 @@ module RubySMB
80
82
  string :default
81
83
  end
82
84
  choice 'Wkssvc', selection: -> { opnum } do
83
- netr_wksta_get_info_request Wkssvc::NETR_WKSTA_GET_INFO
85
+ netr_wksta_get_info_request Wkssvc::NETR_WKSTA_GET_INFO
86
+ netr_wksta_user_enum_request Wkssvc::NETR_WKSTA_USER_ENUM
84
87
  string :default
85
88
  end
86
89
  choice 'Epm', selection: -> { opnum } do
@@ -0,0 +1,23 @@
1
+ module RubySMB
2
+ module Dcerpc
3
+ module Samr
4
+
5
+ # [3.1.5.8.3 SamrGetMembersInGroup (Opnum 25)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/3ed5030d-88a3-42ca-a6e0-8c12aa2fdfbd)
6
+ class SamrGetMembersInGroupRequest < BinData::Record
7
+ attr_reader :opnum
8
+
9
+ endian :little
10
+
11
+ sampr_handle :group_handle
12
+
13
+ def initialize_instance
14
+ super
15
+ @opnum = SAMR_GET_MEMBERS_IN_GROUP
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+
23
+
@@ -0,0 +1,34 @@
1
+ module RubySMB
2
+ module Dcerpc
3
+ module Samr
4
+ # [2.2.7.14 SAMPR_GET_MEMBERS_BUFFER](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/225147b1-45b7-4fde-a5bf-bf420e18fa08)
5
+ class SamprGetMembersBuffer < Ndr::NdrStruct
6
+ default_parameter byte_align: 4
7
+
8
+ ndr_uint32 :member_count
9
+ ndr_uint32_conf_array_ptr :members, type: :ndr_uint32
10
+ ndr_uint32_conf_array_ptr :attributes, type: :ndr_uint32
11
+ end
12
+
13
+ class PsamprGetMembersBuffer < SamprGetMembersBuffer
14
+ extend Ndr::PointerClassPlugin
15
+ end
16
+
17
+ # [2.1.5.8.3 SamrGetMembersInGroup (Opnum 25)](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/a4adbf20-040f-4416-a960-e5b7917fdae7)
18
+ class SamrGetMembersInGroupResponse < BinData::Record
19
+ attr_reader :opnum
20
+
21
+ endian :little
22
+
23
+ psampr_get_members_buffer :members
24
+ ndr_uint32 :error_status
25
+
26
+ def initialize_instance
27
+ super
28
+ @opnum = SAMR_GET_MEMBERS_IN_GROUP
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+
@@ -0,0 +1,26 @@
1
+ module RubySMB
2
+ module Dcerpc
3
+ module Samr
4
+
5
+ # [3.1.5.1.7 SamrOpenGroup (Opnum 19)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/d396e6c9-d04a-4729-b0d8-f50f2748f3c8)
6
+ class SamrOpenGroupRequest < BinData::Record
7
+ attr_reader :opnum
8
+
9
+ endian :little
10
+
11
+ sampr_handle :domain_handle
12
+ # Access control on a server object: bitwise OR of common ACCESS_MASK
13
+ # and user ACCESS_MASK values (see lib/ruby_smb/dcerpc/samr.rb)
14
+ ndr_uint32 :desired_access
15
+ ndr_uint32 :group_id
16
+
17
+ def initialize_instance
18
+ super
19
+ @opnum = SAMR_OPEN_GROUP
20
+ end
21
+ end
22
+
23
+ end
24
+ end
25
+ end
26
+
@@ -0,0 +1,24 @@
1
+ module RubySMB
2
+ module Dcerpc
3
+ module Samr
4
+
5
+ # [3.1.5.1.7 SamrOpenGroup (Opnum 19)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/d396e6c9-d04a-4729-b0d8-f50f2748f3c8)
6
+ class SamrOpenGroupResponse < BinData::Record
7
+ attr_reader :opnum
8
+
9
+ endian :little
10
+
11
+ sampr_handle :group_handle
12
+ ndr_uint32 :error_status
13
+
14
+ def initialize_instance
15
+ super
16
+ @opnum = SAMR_OPEN_GROUP
17
+ end
18
+ end
19
+
20
+ end
21
+ end
22
+ end
23
+
24
+
@@ -20,6 +20,8 @@ module RubySMB
20
20
  SAMR_ENUMERATE_USERS_IN_DOMAIN = 0x000D
21
21
  SAMR_GET_ALIAS_MEMBERSHIP = 0x0010
22
22
  SAMR_LOOKUP_NAMES_IN_DOMAIN = 0x0011
23
+ SAMR_OPEN_GROUP = 0x0013
24
+ SAMR_GET_MEMBERS_IN_GROUP = 0x0019
23
25
  SAMR_OPEN_USER = 0x0022
24
26
  SAMR_DELETE_USER = 0x0023
25
27
  SAMR_GET_GROUPS_FOR_USER = 0x0027
@@ -337,7 +339,7 @@ module RubySMB
337
339
 
338
340
  def self.encrypt_password(password, key)
339
341
  # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/5fe3c4c4-e71b-440d-b2fd-8448bfaf6e04
340
- password = RubySMB::Utils.safe_encode(password, 'UTF-16LE').force_encoding('ASCII-8bit')
342
+ password = password.encode('UTF-16LE').force_encoding('ASCII-8BIT')
341
343
  buffer = password.rjust(512, "\x00") + [ password.length ].pack('V')
342
344
  salt = SecureRandom.random_bytes(16)
343
345
  key = OpenSSL::Digest::MD5.new(salt + key).digest
@@ -509,8 +511,12 @@ module RubySMB
509
511
  require 'ruby_smb/dcerpc/samr/samr_rid_to_sid_response'
510
512
  require 'ruby_smb/dcerpc/samr/samr_close_handle_request'
511
513
  require 'ruby_smb/dcerpc/samr/samr_close_handle_response'
514
+ require 'ruby_smb/dcerpc/samr/samr_get_members_in_group_request'
515
+ require 'ruby_smb/dcerpc/samr/samr_get_members_in_group_response'
512
516
  require 'ruby_smb/dcerpc/samr/samr_get_alias_membership_request'
513
517
  require 'ruby_smb/dcerpc/samr/samr_get_alias_membership_response'
518
+ require 'ruby_smb/dcerpc/samr/samr_open_group_request'
519
+ require 'ruby_smb/dcerpc/samr/samr_open_group_response'
514
520
  require 'ruby_smb/dcerpc/samr/samr_open_user_request'
515
521
  require 'ruby_smb/dcerpc/samr/samr_open_user_response'
516
522
  require 'ruby_smb/dcerpc/samr/samr_get_groups_for_user_request'
@@ -935,6 +941,40 @@ module RubySMB
935
941
  samr_get_alias_membership_reponse.membership.elements.to_ary
936
942
  end
937
943
 
944
+ # Returns a handle to a group, given a RID
945
+ #
946
+ # @param domain_handle [RubySMB::Dcerpc::Samr::SamprHandle] An RPC context
947
+ # representing a domain object
948
+ # @param access [Integer] An access control that indicates the requested
949
+ # access for the returned handle. It is a bitwise OR of common
950
+ # ACCESS_MASK and user ACCESS_MASK values (see
951
+ # lib/ruby_smb/dcerpc/samr.rb)
952
+ # @param group_id [Integer] RID of a group
953
+ # @return [RubySMB::Dcerpc::Samr::SamprHandle] The group handle
954
+ # @raise [RubySMB::Dcerpc::Error::InvalidPacket] if the response is not a
955
+ # SamrOpenGroup packet
956
+ # @raise [RubySMB::Dcerpc::Error::SamrError] if the response error status
957
+ # is not STATUS_SUCCESS
958
+ def samr_open_group(domain_handle:, access: MAXIMUM_ALLOWED, group_id:)
959
+ samr_open_group_request = SamrOpenGroupRequest.new(
960
+ domain_handle: domain_handle,
961
+ desired_access: access,
962
+ group_id: group_id
963
+ )
964
+ response = dcerpc_request(samr_open_group_request)
965
+ begin
966
+ samr_open_group_response = SamrOpenGroupResponse.read(response)
967
+ rescue IOError
968
+ raise RubySMB::Dcerpc::Error::InvalidPacket, 'Error reading SamrOpenGroupResponse'
969
+ end
970
+ unless samr_open_group_response.error_status == WindowsError::NTStatus::STATUS_SUCCESS
971
+ raise RubySMB::Dcerpc::Error::SamrError,
972
+ "Error returned when getting a handle to group #{group_id}: "\
973
+ "#{WindowsError::NTStatus.find_by_retval(samr_open_grou_response.error_status.value).join(',')}"
974
+ end
975
+ samr_open_group_response.group_handle
976
+ end
977
+
938
978
  # Returns a handle to a user, given a RID
939
979
  #
940
980
  # @param domain_handle [RubySMB::Dcerpc::Samr::SamprHandle] An RPC context
@@ -969,6 +1009,36 @@ module RubySMB
969
1009
  samr_open_user_response.user_handle
970
1010
  end
971
1011
 
1012
+ # Returns a listing of members of the given group
1013
+ #
1014
+ # @param group_handle [RubySMB::Dcerpc::Samr::SamprHandle] An RPC context
1015
+ # representing a group object.
1016
+ # @return [Array<Array<String,String>>] Array of RID and Attributes
1017
+ # @raise [RubySMB::Dcerpc::Error::InvalidPacket] if the response is not a
1018
+ # SamrGetMembersInGroup packet
1019
+ # @raise [RubySMB::Dcerpc::Error::SamrError] if the response error status
1020
+ # is not STATUS_SUCCESS
1021
+ def samr_get_members_in_group(group_handle:)
1022
+ samr_get_members_in_group_request = SamrGetMembersInGroupRequest.new(
1023
+ group_handle: group_handle
1024
+ )
1025
+ response = dcerpc_request(samr_get_members_in_group_request)
1026
+ begin
1027
+ samr_get_members_in_group_response = SamrGetMembersInGroupResponse.read(response)
1028
+ rescue IOError
1029
+ raise RubySMB::Dcerpc::Error::InvalidPacket, 'Error reading SamrGetMembersInGroupResponse'
1030
+ end
1031
+ unless samr_get_members_in_group_response.error_status == WindowsError::NTStatus::STATUS_SUCCESS
1032
+ raise RubySMB::Dcerpc::Error::SamrError,
1033
+ "Error returned while getting group membership: "\
1034
+ "#{WindowsError::NTStatus.find_by_retval(samr_get_members_in_group_response.error_status.value).join(',')}"
1035
+ end
1036
+ members = samr_get_members_in_group_response.members.members.to_ary
1037
+ attributes = samr_get_members_in_group_response.members.attributes.to_ary
1038
+
1039
+ members.zip(attributes)
1040
+ end
1041
+
972
1042
  # Returns a listing of groups that a user is a member of
973
1043
  #
974
1044
  # @param user_handle [RubySMB::Dcerpc::Samr::SamprHandle] An RPC context
@@ -2,9 +2,6 @@ module RubySMB
2
2
  module Dcerpc
3
3
  module Wkssvc
4
4
 
5
- # [2.2.2.1 WKSSVC_IDENTIFY_HANDLE](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wkst/9ef94a11-0e5c-49d7-9ac7-68d6f03565de)
6
- class WkssvcIdentifyHandle < Ndr::NdrWideStringPtr; end
7
-
8
5
  # [3.2.4.1 NetrWkstaGetInfo (Opnum 0)](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wkst/4af41d6f-b800-4de1-af5b-0b15a85f8e04)
9
6
  class NetrWkstaGetInfoRequest < BinData::Record
10
7
  attr_reader :opnum
@@ -0,0 +1,25 @@
1
+ module RubySMB
2
+ module Dcerpc
3
+ module Wkssvc
4
+
5
+ # [3.2.4.3 NetrWkstaUserEnum (Opnum 2)](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wkst/4af41d6f-b800-4de1-af5b-0b15a85f8e04)
6
+ class NetrWkstaUserEnumRequest < BinData::Record
7
+ attr_reader :opnum
8
+
9
+ endian :little
10
+
11
+ wkssvc_identify_handle :server_name
12
+ wksta_user_enum_structure :user_info
13
+ ndr_uint32 :preferred_max_length, initial_value: 0xFFFFFFFF
14
+ ndr_uint32_ptr :result_handle, initial_value: 0
15
+
16
+ def initialize_instance
17
+ super
18
+ @opnum = NETR_WKSTA_USER_ENUM
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+
@@ -0,0 +1,25 @@
1
+ module RubySMB
2
+ module Dcerpc
3
+ module Wkssvc
4
+
5
+ # [3.2.4.3 NetrWkstaUserEnum (Opnum 2)](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wkst/4af41d6f-b800-4de1-af5b-0b15a85f8e04)
6
+ class NetrWkstaUserEnumResponse < BinData::Record
7
+ attr_reader :opnum
8
+
9
+ endian :little
10
+
11
+ wksta_user_enum_structure :user_info
12
+ ndr_uint32_ptr :total_entries
13
+ ndr_uint32_ptr :result_handle
14
+ ndr_uint32 :error_status
15
+
16
+ def initialize_instance
17
+ super
18
+ @opnum = NETR_WKSTA_USER_ENUM
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+
@@ -7,7 +7,8 @@ module RubySMB
7
7
  VER_MINOR = 0
8
8
 
9
9
  # Operation numbers
10
- NETR_WKSTA_GET_INFO = 0x0000
10
+ NETR_WKSTA_GET_INFO = 0x0000
11
+ NETR_WKSTA_USER_ENUM = 0x0002
11
12
 
12
13
  PLATFORM_ID = {
13
14
  0x0000012C => "DOS",
@@ -23,9 +24,85 @@ module RubySMB
23
24
  WKSTA_INFO_102 = 0x00000066
24
25
  #TODO: WKSTA_INFO_502 = 0x000001F6
25
26
 
27
+ # User Enum Information Level
28
+ WKSTA_USER_INFO_0 = 0x00000000
29
+ WKSTA_USER_INFO_1 = 0x00000001
30
+
31
+ # [2.2.2.1 WKSSVC_IDENTIFY_HANDLE](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wkst/9ef94a11-0e5c-49d7-9ac7-68d6f03565de)
32
+ class WkssvcIdentifyHandle < Ndr::NdrWideStringzPtr; end
33
+
34
+ # [2.2.5.9 WKSTA_USER_INFO_0](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wkst/b7c53c6f-8b92-4e5d-9a2e-6462cb4ef1ac)
35
+ class WkstaUserInfo0 < Ndr::NdrStruct
36
+ default_parameter byte_align: 4
37
+ endian :little
38
+
39
+ ndr_wide_stringz_ptr :wkui0_username
40
+ end
41
+
42
+ class WkstaUserInfo0ArrayPtr < Ndr::NdrConfArray
43
+ default_parameter type: :wksta_user_info0
44
+ extend Ndr::PointerClassPlugin
45
+ end
46
+
47
+ # [2.2.5.12 WKSTA_USER_INFO_0_CONTAINER](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wkst/0b0cff8f-09bc-43a8-b0d3-88f0bf7e3664)
48
+ class WkstaUserInfo0Container < Ndr::NdrStruct
49
+ default_parameter byte_align: 4
50
+ endian :little
51
+
52
+ ndr_uint32 :wkui0_entries_read
53
+ wksta_user_info0_array_ptr :wkui0_buffer
54
+ end
55
+
56
+ class PwkstaUserInfo0Container < WkstaUserInfo0Container
57
+ extend Ndr::PointerClassPlugin
58
+ end
59
+
60
+ # [2.2.5.10 WKSTA_USER_INFO_1](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wkst/c37b9606-866f-40ac-9490-57b8334968e2)
61
+ class WkstaUserInfo1 < Ndr::NdrStruct
62
+ default_parameter byte_align: 4
63
+ endian :little
64
+
65
+ ndr_wide_stringz_ptr :wkui1_username
66
+ ndr_wide_stringz_ptr :wkui1_logon_domain
67
+ ndr_wide_stringz_ptr :wkui1_oth_domains
68
+ ndr_wide_stringz_ptr :wkui1_logon_server
69
+ end
70
+
71
+ class WkstaUserInfo1ArrayPtr < Ndr::NdrConfArray
72
+ default_parameter type: :wksta_user_info1
73
+ extend Ndr::PointerClassPlugin
74
+ end
75
+
76
+ # [2.2.5.13 WKSTA_USER_INFO_1_CONTAINER](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wkst/22a813e4-fc7d-4fe3-a6d6-78debfd2c0c9)
77
+ class WkstaUserInfo1Container < Ndr::NdrStruct
78
+ default_parameter byte_align: 4
79
+ endian :little
80
+
81
+ ndr_uint32 :wkui1_entries_read
82
+ wksta_user_info1_array_ptr :wkui1_buffer
83
+ end
84
+
85
+ class PwkstaUserInfo1Container < WkstaUserInfo1Container
86
+ extend Ndr::PointerClassPlugin
87
+ end
88
+
89
+ # [2.2.5.14 WKSTA_USER_ENUM_STRUCT](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wkst/4041455a-52be-4389-a4fc-82fea3cb3160)
90
+ class WkstaUserEnumStructure < Ndr::NdrStruct
91
+ default_parameter byte_align: 4
92
+ endian :little
93
+
94
+ ndr_uint32 :level
95
+ ndr_uint32 :tag, value: -> { self.level }
96
+ choice :info, selection: :level, byte_align: 4 do
97
+ pwksta_user_info0_container WKSTA_USER_INFO_0
98
+ pwksta_user_info1_container WKSTA_USER_INFO_1
99
+ end
100
+ end
26
101
 
27
102
  require 'ruby_smb/dcerpc/wkssvc/netr_wksta_get_info_request'
28
103
  require 'ruby_smb/dcerpc/wkssvc/netr_wksta_get_info_response'
104
+ require 'ruby_smb/dcerpc/wkssvc/netr_wksta_user_enum_request'
105
+ require 'ruby_smb/dcerpc/wkssvc/netr_wksta_user_enum_response'
29
106
 
30
107
  # Returns details about a computer environment, including
31
108
  # platform-specific information, the names of the domain and local
@@ -33,14 +110,14 @@ module RubySMB
33
110
  #
34
111
  # @param server_name [optional, String] String that identifies the server (optional
35
112
  # since it is ignored by the server)
36
- # @param server_name [optional, Integer] The information level of the data (default: WKSTA_INFO_100)
113
+ # @param level [optional, Integer] The information level of the data (default: WKSTA_INFO_100)
37
114
  # @return [RubySMB::Dcerpc::Wkssvc::WkstaInfo100, RubySMB::Dcerpc::Wkssvc::WkstaInfo101,
38
115
  # RubySMB::Dcerpc::Wkssvc::WkstaInfo102] The structure containing the requested information
39
116
  # @raise [RubySMB::Dcerpc::Error::InvalidPacket] if the response is not a
40
117
  # NetrWkstaGetInfoResponse packet
41
118
  # @raise [RubySMB::Dcerpc::Error::WkssvcError] if the response error status
42
119
  # is not STATUS_SUCCESS
43
- def netr_wksta_get_info(server_name: "\x00", level: WKSTA_INFO_100)
120
+ def netr_wksta_get_info(server_name: '', level: WKSTA_INFO_100)
44
121
  wkst_netr_wksta_get_info_request = NetrWkstaGetInfoRequest.new(
45
122
  server_name: server_name,
46
123
  level: level
@@ -59,6 +136,44 @@ module RubySMB
59
136
  wkst_netr_wksta_get_info_response.wksta_info.info
60
137
  end
61
138
 
139
+ # Returns details about users who are currently active on a remote computer.
140
+ #
141
+ # @param server_name [optional, String] String that identifies the server (optional
142
+ # since it is ignored by the server)
143
+ # @param level [optional, Integer] The information level of the data (default: WKSTA_USER_INFO_0)
144
+ # @return [RubySMB::Dcerpc::Wkssvc::WkstaUserInfo0, RubySMB::Dcerpc::Wkssvc::WkstaUserInfo1]
145
+ # The structure containing the requested information
146
+ # @raise [RubySMB::Dcerpc::Error::InvalidPacket] if the response is not a
147
+ # NetrWkstaGetInfoResponse packet
148
+ # @raise [RubySMB::Dcerpc::Error::WkssvcError] if the response error status
149
+ # is not STATUS_SUCCESS
150
+ def netr_wksta_user_enum(server_name: '', level: WKSTA_USER_INFO_0)
151
+ wkst_netr_wksta_enum_user_request = NetrWkstaUserEnumRequest.new(
152
+ server_name: server_name,
153
+ user_info: {
154
+ level: level,
155
+ tag: level,
156
+ info: {
157
+ wkui0_entries_read: 0,
158
+ },
159
+ },
160
+ preferred_max_length: 0xFFFFFFFF,
161
+ result_handle: 0
162
+ )
163
+ response = dcerpc_request(wkst_netr_wksta_enum_user_request)
164
+ begin
165
+ wkst_netr_wksta_enum_user_response = NetrWkstaUserEnumResponse.read(response)
166
+ rescue IOError
167
+ raise RubySMB::Dcerpc::Error::InvalidPacket, 'Error reading WkstNetrWkstaUserEnumResponse'
168
+ end
169
+ unless wkst_netr_wksta_enum_user_response.error_status == WindowsError::NTStatus::STATUS_SUCCESS
170
+ raise RubySMB::Dcerpc::Error::WkssvcError,
171
+ "Error returned with netr_wksta_enum_user: #{wkst_netr_wksta_enum_user_response.error_status.value} - "\
172
+ "#{WindowsError::NTStatus.find_by_retval(wkst_netr_wksta_enum_user_response.error_status.value).join(',')}"
173
+ end
174
+ wkst_netr_wksta_enum_user_response.user_info.info
175
+ end
176
+
62
177
  end
63
178
  end
64
179
  end
@@ -142,10 +142,7 @@ module RubySMB
142
142
  case type3_msg.ntlm_version
143
143
  when :ntlmv1
144
144
  my_ntlm_response = Net::NTLM::ntlm_response(
145
- ntlm_hash: Net::NTLM::ntlm_hash(
146
- RubySMB::Utils.safe_encode(account.password, 'UTF-16LE'),
147
- unicode: true
148
- ),
145
+ ntlm_hash: Net::NTLM::ntlm_hash(account.password),
149
146
  challenge: @server_challenge
150
147
  )
151
148
  matches = my_ntlm_response == type3_msg.ntlm_response
@@ -155,9 +152,9 @@ module RubySMB
155
152
  their_blob = type3_msg.ntlm_response[digest.digest_length..-1]
156
153
 
157
154
  ntlmv2_hash = Net::NTLM.ntlmv2_hash(
158
- RubySMB::Utils.safe_encode(account.username, 'UTF-16LE'),
159
- RubySMB::Utils.safe_encode(account.password, 'UTF-16LE'),
160
- RubySMB::Utils.safe_encode(type3_msg.domain, 'UTF-16LE'), # don't use the account domain because of the special '.' value
155
+ Net::NTLM::EncodeUtil.encode_utf16le(account.username),
156
+ Net::NTLM::EncodeUtil.encode_utf16le(account.password),
157
+ type3_msg.domain.dup.force_encoding('ASCII-8BIT'), # don't use the account domain because of the special '.' value
161
158
  {client_challenge: their_blob[16...24], unicode: true}
162
159
  )
163
160
 
@@ -310,8 +307,7 @@ module RubySMB
310
307
  domain = @default_domain if domain.nil? || domain == '.'.encode(domain.encoding)
311
308
  domain = domain.downcase
312
309
  @accounts.find do |account|
313
- RubySMB::Utils.safe_encode(account.username, username.encoding).downcase == username &&
314
- RubySMB::Utils.safe_encode(account.domain, domain.encoding).downcase == domain
310
+ account.username.encode(username.encoding).downcase == username && account.domain.encode(domain.encoding).downcase == domain
315
311
  end
316
312
  end
317
313