Skip to content

Commit c29e6d2

Browse files
authored
Merge pull request #279 from smashery/change-password
Add more password changing structures and calls
2 parents ed72079 + c2a2e97 commit c29e6d2

13 files changed

+405
-1
lines changed

lib/ruby_smb/dcerpc/request.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ class Request < BinData::Record
7979
samr_set_information_user2_request Samr::SAMR_SET_INFORMATION_USER2
8080
samr_delete_user_request Samr::SAMR_DELETE_USER
8181
samr_query_information_domain_request Samr::SAMR_QUERY_INFORMATION_DOMAIN
82+
samr_unicode_change_password_user2_request Samr::SAMR_UNICODE_CHANGE_PASSWORD_USER2
83+
samr_change_password_user_request Samr::SAMR_CHANGE_PASSWORD_USER
8284
string :default
8385
end
8486
choice 'Wkssvc', selection: -> { opnum } do

lib/ruby_smb/dcerpc/samr.rb

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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
#
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
module RubySMB
2+
module Dcerpc
3+
module Samr
4+
5+
# [3.1.5.10.1 SamrChangePasswordUser (Opnum 38)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/9699d8ca-e1a4-433c-a8c3-d7bebeb01476)
6+
class SamrChangePasswordUserRequest < BinData::Record
7+
attr_reader :opnum
8+
9+
endian :little
10+
11+
sampr_handle :user_handle
12+
ndr_uint8 :lm_present
13+
pencrypted_nt_owf_password :old_lm_encrypted_with_new_lm
14+
pencrypted_nt_owf_password :new_lm_encrypted_with_old_lm
15+
ndr_uint8 :nt_present
16+
pencrypted_nt_owf_password :old_nt_encrypted_with_new_nt
17+
pencrypted_nt_owf_password :new_nt_encrypted_with_old_nt
18+
ndr_uint8 :nt_cross_encryption_present
19+
pencrypted_nt_owf_password :new_nt_encrypted_with_new_nt
20+
ndr_uint8 :lm_cross_encryption_present
21+
pencrypted_nt_owf_password :new_lm_encrypted_with_new_nt
22+
23+
def initialize_instance
24+
super
25+
@opnum = SAMR_CHANGE_PASSWORD_USER
26+
end
27+
end
28+
29+
end
30+
end
31+
end
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module RubySMB
2+
module Dcerpc
3+
module Samr
4+
5+
# [3.1.5.10.1 SamrChangePasswordUser (Opnum 38)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/9699d8ca-e1a4-433c-a8c3-d7bebeb01476)
6+
class SamrChangePasswordUserResponse < BinData::Record
7+
attr_reader :opnum
8+
9+
endian :little
10+
11+
ndr_uint32 :error_status
12+
13+
def initialize_instance
14+
super
15+
@opnum = SAMR_CHANGE_PASSWORD_USER
16+
end
17+
end
18+
19+
end
20+
end
21+
end

lib/ruby_smb/dcerpc/samr/samr_get_members_in_group_response.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class PsamprGetMembersBuffer < SamprGetMembersBuffer
1414
extend Ndr::PointerClassPlugin
1515
end
1616

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)
17+
# [3.1.5.8.3 SamrGetMembersInGroup (Opnum 25)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/3ed5030d-88a3-42ca-a6e0-8c12aa2fdfbd)
1818
class SamrGetMembersInGroupResponse < BinData::Record
1919
attr_reader :opnum
2020

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
module RubySMB
2+
module Dcerpc
3+
module Samr
4+
5+
# [3.1.5.10.3 SamrUnicodeChangePasswordUser2 (Opnum 55)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/acb3204a-da8b-478e-9139-1ea589edb880)
6+
class SamrUnicodeChangePasswordUser2Request < BinData::Record
7+
attr_reader :opnum
8+
9+
endian :little
10+
11+
prpc_unicode_string :server_name
12+
rpc_unicode_string :user_name
13+
psampr_encrypted_user_password :new_password_encrypted_with_old_nt
14+
pencrypted_nt_owf_password :old_nt_owf_password_encrypted_with_new_nt
15+
ndr_uint8 :lm_present
16+
psampr_encrypted_user_password :new_password_encrypted_with_old_lm
17+
pencrypted_nt_owf_password :old_lm_owf_password_encrypted_with_new_nt
18+
19+
def initialize_instance
20+
super
21+
@opnum = SAMR_UNICODE_CHANGE_PASSWORD_USER2
22+
end
23+
end
24+
25+
end
26+
end
27+
end
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module RubySMB
2+
module Dcerpc
3+
module Samr
4+
5+
# [3.1.5.10.3 SamrUnicodeChangePasswordUser2 (Opnum 55)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/acb3204a-da8b-478e-9139-1ea589edb880)
6+
class SamrUnicodeChangePasswordUser2Response < BinData::Record
7+
attr_reader :opnum
8+
9+
endian :little
10+
11+
ndr_uint32 :error_status
12+
13+
def initialize_instance
14+
super
15+
@opnum = SAMR_UNICODE_CHANGE_PASSWORD_USER2
16+
end
17+
end
18+
19+
end
20+
end
21+
end
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
RSpec.describe RubySMB::Dcerpc::Samr::EncryptedNtOwfPassword do
2+
it 'Creates output key' do
3+
expect(described_class.to_output_key('ABCDEFG')).to eq ["40a09068442A188E"].pack('H*')
4+
expect(described_class.to_output_key('AAAAAAA')).to eq ["40A05028140A0482"].pack('H*')
5+
end
6+
7+
it 'Encrypts a hash' do
8+
expect(described_class.encrypt_hash(hash: 'AAAAAAAAAAAAAAAA', key: 'BBBBBBBBBBBBBB')).to eq ["8cd90c3de08ecda28cd90c3de08ecda2"].pack('H*')
9+
end
10+
end
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
RSpec.describe RubySMB::Dcerpc::Samr::SamprEncryptedUserPassword do
2+
3+
it 'Encrypts correctly' do
4+
password = 'newPassword1!'
5+
old_password_nt = 'AAAAAAAAAAAAAAAA'
6+
expected = ['c2cbe63dc0a3cda1baab695ce4f0352b774592b214a905796a6ba9fd30e0ffc56d60812bfd7f45420f5332922b50cfdcde3133e3e45c5eb6c16023006cb4ff7d41f54fa009a64fa66b00fa41094d7db6c4cc9a430a7ad43122047b696d934645974fee7551dcafac28c0869106417fd3fbfc34a87a56d6141aac2b6d134723f2224791b39749bc93f4405f78ba09dcd9b4d29e72ae0c873a8d468323793fffccc2a9d8dc4d975c42064208d898df71ef0889b2e5a9cfa6c8c2758eb15b6fdd332d6d7d2829f3a3bc2a7ecd7f87d1fd4aed25d93de6a7baf8e0247e2f5b831ca7646eef7a11e2f9410574707ef85c9db8cc125fb6254fe1e111a662006343299fcb8f8fb641cb1d188ff6e5c50334ca199603a93907093e6d35d80b2dade79360bc672870d29cdf5f80120ac53e9ce3c3718d0f8097cdae2318b8e27108fc1066491e5b034f55c1e8ff00a53d2eb7b0e0ffc10236a5a530795b84f33d66eb51388d6da112886c8ea482af0ec7e9c0a549a561244ee9185b5387b05d3c5e74d88e355aef22dab1d5039d0a0caa22437f6e520121ef5d21da729bb0cdee5cbb3b450bf1d0fcafad5ac518b0535628e2a96a2d0d2f8acd3e42147e700c2889cbc92c5533f1889b49d41c0ae9a05fc0a754060c0680296de5c712a3299cdd5c646582348fc2e32a68ae4fc2a3b91fb423adc617a20b28c1d6297f14a870329e6aa802d22ba97c'].pack('H*')
7+
expect(described_class.encrypt_password(password, old_password_nt)).to eq expected
8+
end
9+
end

0 commit comments

Comments
 (0)