@@ -24,8 +24,10 @@ module Samr
2424 SAMR_GET_MEMBERS_IN_GROUP = 0x0019
2525 SAMR_OPEN_USER = 0x0022
2626 SAMR_DELETE_USER = 0x0023
27+ SAMR_CHANGE_PASSWORD_USER = 0x0026
2728 SAMR_GET_GROUPS_FOR_USER = 0x0027
2829 SAMR_CREATE_USER2_IN_DOMAIN = 0x0032
30+ SAMR_UNICODE_CHANGE_PASSWORD_USER2 = 0x0037
2931 SAMR_SET_INFORMATION_USER2 = 0x003a
3032 SAMR_CONNECT5 = 0x0040
3133 SAMR_RID_TO_SID = 0x0041
@@ -318,6 +320,58 @@ class PulongArray < Ndr::NdrConfArray
318320 extend Ndr ::PointerClassPlugin
319321 end
320322
323+ # [2.2.7.3 ENCRYPTED_NT_OWF_PASSWORD](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/ce061fef-6d4f-4802-bd5d-26b11f14f4a6)
324+ class EncryptedNtOwfPassword < Ndr ::NdrStruct
325+ default_parameter byte_align : 4
326+ ndr_fixed_byte_array :buffer , initial_length : 16
327+
328+ # [2.2.11.1.2 Encrypting a 64-bit block with a 7-byte key](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/ebdb15df-8d0d-4347-9d62-082e6eccac40)
329+ def self . to_output_key ( input_key )
330+ output_key = [ ]
331+ input_key = input_key . unpack ( 'C' *7 )
332+ output_key . append ( input_key [ 0 ] >> 0x01 )
333+ output_key . append ( ( ( input_key [ 0 ] &0x01 ) <<6 ) | ( input_key [ 1 ] >>2 ) )
334+ output_key . append ( ( ( input_key [ 1 ] &0x03 ) <<5 ) | ( input_key [ 2 ] >>3 ) )
335+ output_key . append ( ( ( input_key [ 2 ] &0x07 ) <<4 ) | ( input_key [ 3 ] >>4 ) )
336+ output_key . append ( ( ( input_key [ 3 ] &0x0F ) <<3 ) | ( input_key [ 4 ] >>5 ) )
337+ output_key . append ( ( ( input_key [ 4 ] &0x1F ) <<2 ) | ( input_key [ 5 ] >>6 ) )
338+ output_key . append ( ( ( input_key [ 5 ] &0x3F ) <<1 ) | ( input_key [ 6 ] >>7 ) )
339+ output_key . append ( input_key [ 6 ] & 0x7F )
340+
341+ output_key = output_key . map { |x | ( x << 1 ) & 0xFE }
342+
343+ output_key . pack ( 'C' *8 )
344+ end
345+
346+ # [2.2.11.1 DES-ECB-LM](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/3f5ec79d-b449-4ab2-9423-c4dccbe0b184)
347+ def self . encrypt_hash ( hash :, key :)
348+ block1 = hash [ 0 ..7 ]
349+ block2 = hash [ 8 ..]
350+ key1 = to_output_key ( key [ 0 ..6 ] )
351+ key2 = to_output_key ( key [ 7 ..13 ] ) # The last two bytes are ignored
352+
353+ cipher1 = OpenSSL ::Cipher . new ( 'des-ecb' ) . tap do |cipher |
354+ cipher . encrypt
355+ cipher . key = key1
356+ end
357+
358+ cipher2 = OpenSSL ::Cipher . new ( 'des-ecb' ) . tap do |cipher |
359+ cipher . encrypt
360+ cipher . key = key2
361+ end
362+
363+ cipher1 . update ( block1 ) + cipher2 . update ( block2 )
364+ end
365+ end
366+
367+ EncryptedLmOwfPassword = EncryptedNtOwfPassword
368+
369+ class PencryptedNtOwfPassword < EncryptedNtOwfPassword
370+ extend Ndr ::PointerClassPlugin
371+ end
372+
373+ PencryptedLmOwfPassword = PencryptedNtOwfPassword
374+
321375 # [2.2.7.4 SAMPR_ULONG_ARRAY](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/2feb3806-4db2-45b7-90d2-86c8336a31ba)
322376 class SamprUlongArray < Ndr ::NdrStruct
323377 default_parameter byte_align : 4
@@ -333,6 +387,28 @@ class RpcShortBlob < BinData::Record
333387 ndr_uint16_array_ptr :buffer
334388 end
335389
390+ # [2.2.6.21 SAMPR_ENCRYPTED_USER_PASSWORD](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/23f9ef4c-cf3e-4330-9287-ea4799b03201)
391+ class SamprEncryptedUserPassword < Ndr ::NdrStruct
392+ default_parameter byte_align : 4
393+ ndr_fixed_byte_array :buffer , initial_length : 516
394+
395+ def self . encrypt_password ( password , old_password_nt )
396+ # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/5fe3c4c4-e71b-440d-b2fd-8448bfaf6e04
397+ password = password . encode ( 'UTF-16LE' ) . force_encoding ( 'ASCII-8BIT' )
398+ buffer = password . rjust ( 512 , "\x00 " ) + [ password . length ] . pack ( 'V' )
399+ cipher = OpenSSL ::Cipher . new ( 'RC4' ) . tap do |cipher |
400+ cipher . encrypt
401+ cipher . key = old_password_nt
402+ end
403+ cipher . update ( buffer )
404+ end
405+ end
406+
407+ class PsamprEncryptedUserPassword < SamprEncryptedUserPassword
408+ extend Ndr ::PointerClassPlugin
409+ end
410+
411+
336412 # [2.2.6.22 SAMPR_ENCRYPTED_USER_PASSWORD_NEW](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/112ecc94-1cbe-41cd-b669-377402c20786)
337413 class SamprEncryptedUserPasswordNew < BinData ::Record
338414 ndr_fixed_byte_array :buffer , initial_length : 532
@@ -413,12 +489,22 @@ class UserControlInformation < BinData::Record
413489 ndr_uint32 :user_account_control
414490 end
415491
492+ # [2.2.6.25 SAMPR_USER_INTERNAL1_INFORMATION](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/50d17755-c6b8-40bd-8cac-bd6cfa31adf2)
493+ class SamprUserInternal1Information < BinData ::Record
494+ encrypted_nt_owf_password :encrypted_nt_owf_password
495+ encrypted_nt_owf_password :encrypted_lm_owf_password
496+ ndr_uint8 :nt_password_present
497+ ndr_uint8 :lm_password_present
498+ ndr_uint8 :password_expired
499+ end
500+
416501 # [2.2.6.29 SAMPR_USER_INFO_BUFFER](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/9496c26e-490b-4e76-827f-2695fc216f35)
417502 class SamprUserInfoBuffer < BinData ::Record
418503 ndr_uint16 :tag
419504 choice :member , selection : :tag do
420505 user_control_information USER_CONTROL_INFORMATION
421506 sampr_user_internal4_information_new USER_INTERNAL4_INFORMATION_NEW
507+ sampr_user_internal1_information USER_INTERNAL1_INFORMATION
422508 end
423509 end
424510
@@ -523,6 +609,10 @@ def get_key_values
523609 require 'ruby_smb/dcerpc/samr/samr_get_groups_for_user_response'
524610 require 'ruby_smb/dcerpc/samr/samr_set_information_user2_request'
525611 require 'ruby_smb/dcerpc/samr/samr_set_information_user2_response'
612+ require 'ruby_smb/dcerpc/samr/samr_change_password_user_request'
613+ require 'ruby_smb/dcerpc/samr/samr_change_password_user_response'
614+ require 'ruby_smb/dcerpc/samr/samr_unicode_change_password_user2_request'
615+ require 'ruby_smb/dcerpc/samr/samr_unicode_change_password_user2_response'
526616 require 'ruby_smb/dcerpc/samr/samr_delete_user_request'
527617 require 'ruby_smb/dcerpc/samr/samr_delete_user_response'
528618 require 'ruby_smb/dcerpc/samr/samr_query_information_domain_request'
@@ -882,6 +972,117 @@ def samr_set_information_user2(user_handle:, user_info:)
882972 nil
883973 end
884974
975+ # Change the password on a user object.
976+ #
977+ # @param user_handle [SamprHandle] Handle representing the user to change the password for
978+ # @param old_password [String] The previous password (either this or old_nt_hash must be specified)
979+ # @param new_password [String] The password to set on the account
980+ # @param new_nt_hash [String] The new password's NT hash
981+ # @param new_lm_hash [String] The new password's LM hash
982+ # @param old_nt_hash [String] The previous password's NT hash (either this or old_password must be specified)
983+ # @param old_lm_hash [String] The previous password's LM hash (currently ignored)
984+ # @return nothing is returned on success
985+ # @raise [RubySMB::Dcerpc::Error::SamrError] if the response error status
986+ # is not STATUS_SUCCESS
987+ def samr_change_password_user ( user_handle :, old_password :nil , new_password :nil , new_nt_hash :nil , new_lm_hash :nil , old_nt_hash :nil , old_lm_hash :nil )
988+ if new_password . nil? == new_nt_hash . nil?
989+ raise ArgumentError . new ( 'Provide either new password or new password hashes, but not both' )
990+ end
991+
992+ if old_password . nil? == old_nt_hash . nil?
993+ raise ArgumentError . new ( 'Provide either old password or old password hashes, but not both' )
994+ end
995+
996+ if old_password
997+ old_lm_hash = Net ::NTLM . lm_hash ( old_password [ 0 ..13 ] )
998+ old_nt_hash = Net ::NTLM . ntlm_hash ( old_password )
999+ end
1000+
1001+ if new_nt_hash . nil?
1002+ new_nt_hash = Net ::NTLM ::ntlm_hash ( new_password )
1003+ new_lm_hash = Net ::NTLM . lm_hash ( new_password [ 0 ..13 ] )
1004+ elsif new_lm_hash . nil?
1005+ new_lm_hash = Net ::NTLM . lm_hash ( '' )
1006+ end
1007+
1008+ samr_change_password_user_request = SamrChangePasswordUserRequest . new (
1009+ user_handle : user_handle ,
1010+ lm_present : 0 ,
1011+ old_lm_encrypted_with_new_lm : nil ,
1012+ new_lm_encrypted_with_old_lm : nil ,
1013+ nt_present : 1 ,
1014+ old_nt_encrypted_with_new_nt : PencryptedNtOwfPassword . new ( buffer : EncryptedNtOwfPassword . encrypt_hash ( hash : old_nt_hash , key : new_nt_hash ) ) ,
1015+ new_nt_encrypted_with_old_nt : PencryptedNtOwfPassword . new ( buffer : EncryptedNtOwfPassword . encrypt_hash ( hash : new_nt_hash , key : old_nt_hash ) ) ,
1016+ nt_cross_encryption_present : 0 ,
1017+ new_nt_encrypted_with_new_lm : nil ,
1018+ lm_cross_encryption_present : 1 ,
1019+ new_lm_encrypted_with_new_nt : PencryptedNtOwfPassword . new ( buffer : EncryptedNtOwfPassword . encrypt_hash ( hash : new_lm_hash , key : new_nt_hash ) ) ,
1020+ )
1021+ response = dcerpc_request ( samr_change_password_user_request )
1022+ begin
1023+ samr_unicode_change_password_user2_response = SamrChangePasswordUserResponse . read ( response )
1024+ rescue IOError
1025+ raise RubySMB ::Dcerpc ::Error ::InvalidPacket , 'Error reading SamrUnicodeChangePasswordUser2Response'
1026+ end
1027+ unless samr_unicode_change_password_user2_response . error_status == WindowsError ::NTStatus ::STATUS_SUCCESS
1028+ raise RubySMB ::Dcerpc ::Error ::SamrError ,
1029+ "Error returned while changing user password: " \
1030+ "#{ WindowsError ::NTStatus . find_by_retval ( samr_unicode_change_password_user2_response . error_status . value ) . join ( ',' ) } "
1031+ end
1032+
1033+ nil
1034+ end
1035+
1036+
1037+ # Change the password on a user.
1038+ #
1039+ # @param server_name [String] The server name; can be ignored by the server
1040+ # @param target_username [String] The user for which we're changing the password
1041+ # @param old_password [String] The previous password (either this or old_nt_hash must be specified)
1042+ # @param new_password [String] The password to set on the account
1043+ # @param old_nt_hash [String] The previous password's NT hash (either this or old_password must be specified)
1044+ # @param old_lm_hash [String] The previous password's LM hash (currently ignored)
1045+ # @return nothing is returned on success
1046+ # @raise [RubySMB::Dcerpc::Error::SamrError] if the response error status
1047+ # is not STATUS_SUCCESS
1048+ def samr_unicode_change_password_user2 ( server_name : "" , target_username :, old_password :nil , new_password :, old_nt_hash :nil , old_lm_hash :nil )
1049+ #if old_lm_hash.nil? != old_nt_hash.nil?
1050+ # raise ArgumentError.new('If providing the previous NT/LM hash, must provide both')
1051+ #end
1052+ if old_password . nil? == old_nt_hash . nil?
1053+ raise ArgumentError . new ( 'Provide either old password or old password hashes, but not both' )
1054+ end
1055+
1056+ if old_password
1057+ old_lm_hash = Net ::NTLM . lm_hash ( old_password [ 0 ..13 ] )
1058+ old_nt_hash = Net ::NTLM . ntlm_hash ( old_password )
1059+ end
1060+
1061+ new_nt_hash = Net ::NTLM ::ntlm_hash ( new_password )
1062+
1063+ samr_unicode_change_password_user2_request = SamrUnicodeChangePasswordUser2Request . new (
1064+ server_name : server_name ,
1065+ user_name : target_username ,
1066+ new_password_encrypted_with_old_nt : SamprEncryptedUserPassword . new ( buffer : SamprEncryptedUserPassword . encrypt_password ( new_password , old_nt_hash ) ) ,
1067+ old_nt_owf_password_encrypted_with_new_nt : PencryptedNtOwfPassword . new ( buffer : EncryptedNtOwfPassword . encrypt_hash ( hash : old_nt_hash , key : new_nt_hash ) ) ,
1068+ lm_present : 0
1069+ )
1070+ samr_unicode_change_password_user2_request
1071+ response = dcerpc_request ( samr_unicode_change_password_user2_request )
1072+ begin
1073+ samr_unicode_change_password_user2_response = SamrUnicodeChangePasswordUser2Response . read ( response )
1074+ rescue IOError
1075+ raise RubySMB ::Dcerpc ::Error ::InvalidPacket , 'Error reading SamrUnicodeChangePasswordUser2Response'
1076+ end
1077+ unless samr_unicode_change_password_user2_response . error_status == WindowsError ::NTStatus ::STATUS_SUCCESS
1078+ raise RubySMB ::Dcerpc ::Error ::SamrError ,
1079+ "Error returned while changing user password: " \
1080+ "#{ WindowsError ::NTStatus . find_by_retval ( samr_unicode_change_password_user2_response . error_status . value ) . join ( ',' ) } "
1081+ end
1082+
1083+ nil
1084+ end
1085+
8851086 # Closes (that is, releases server-side resources used by) any context
8861087 # handle obtained from this RPC interface
8871088 #
0 commit comments