Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more password changing structures and calls #279

Merged
merged 5 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/ruby_smb/dcerpc/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ class Request < BinData::Record
samr_set_information_user2_request Samr::SAMR_SET_INFORMATION_USER2
samr_delete_user_request Samr::SAMR_DELETE_USER
samr_query_information_domain_request Samr::SAMR_QUERY_INFORMATION_DOMAIN
samr_unicode_change_password_user2_request Samr::SAMR_UNICODE_CHANGE_PASSWORD_USER2
samr_change_password_user_request Samr::SAMR_CHANGE_PASSWORD_USER
string :default
end
choice 'Wkssvc', selection: -> { opnum } do
Expand Down
201 changes: 201 additions & 0 deletions lib/ruby_smb/dcerpc/samr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ module Samr
SAMR_GET_MEMBERS_IN_GROUP = 0x0019
SAMR_OPEN_USER = 0x0022
SAMR_DELETE_USER = 0x0023
SAMR_CHANGE_PASSWORD_USER = 0x0026
SAMR_GET_GROUPS_FOR_USER = 0x0027
SAMR_CREATE_USER2_IN_DOMAIN = 0x0032
SAMR_UNICODE_CHANGE_PASSWORD_USER2 = 0x0037
SAMR_SET_INFORMATION_USER2 = 0x003a
SAMR_CONNECT5 = 0x0040
SAMR_RID_TO_SID = 0x0041
Expand Down Expand Up @@ -318,6 +320,58 @@ class PulongArray < Ndr::NdrConfArray
extend Ndr::PointerClassPlugin
end

# [2.2.7.3 ENCRYPTED_NT_OWF_PASSWORD](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/ce061fef-6d4f-4802-bd5d-26b11f14f4a6)
class EncryptedNtOwfPassword < Ndr::NdrStruct
default_parameter byte_align: 4
ndr_fixed_byte_array :buffer, initial_length: 16

# [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)
def self.to_output_key(input_key)
output_key = []
input_key = input_key.unpack('C'*7)
output_key.append(input_key[0] >> 0x01)
output_key.append(((input_key[0]&0x01)<<6) | (input_key[1]>>2))
output_key.append(((input_key[1]&0x03)<<5) | (input_key[2]>>3))
output_key.append(((input_key[2]&0x07)<<4) | (input_key[3]>>4))
output_key.append(((input_key[3]&0x0F)<<3) | (input_key[4]>>5))
output_key.append(((input_key[4]&0x1F)<<2) | (input_key[5]>>6))
output_key.append(((input_key[5]&0x3F)<<1) | (input_key[6]>>7))
output_key.append(input_key[6] & 0x7F)

output_key = output_key.map {|x| (x << 1) & 0xFE}

output_key.pack('C'*8)
end

# [2.2.11.1 DES-ECB-LM](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/3f5ec79d-b449-4ab2-9423-c4dccbe0b184)
def self.encrypt_hash(hash:, key:)
block1 = hash[0..7]
block2 = hash[8..]
key1 = to_output_key(key[0..6])
key2 = to_output_key(key[7..13]) # The last two bytes are ignored

cipher1 = OpenSSL::Cipher.new('des-ecb').tap do |cipher|
cipher.encrypt
cipher.key = key1
end

cipher2 = OpenSSL::Cipher.new('des-ecb').tap do |cipher|
cipher.encrypt
cipher.key = key2
end

cipher1.update(block1) + cipher2.update(block2)
smcintyre-r7 marked this conversation as resolved.
Show resolved Hide resolved
end
end

EncryptedLmOwfPassword = EncryptedNtOwfPassword

class PencryptedNtOwfPassword < EncryptedNtOwfPassword
extend Ndr::PointerClassPlugin
end

PencryptedLmOwfPassword = PencryptedNtOwfPassword

# [2.2.7.4 SAMPR_ULONG_ARRAY](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/2feb3806-4db2-45b7-90d2-86c8336a31ba)
class SamprUlongArray < Ndr::NdrStruct
default_parameter byte_align: 4
Expand All @@ -333,6 +387,28 @@ class RpcShortBlob < BinData::Record
ndr_uint16_array_ptr :buffer
end

# [2.2.6.21 SAMPR_ENCRYPTED_USER_PASSWORD](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/23f9ef4c-cf3e-4330-9287-ea4799b03201)
class SamprEncryptedUserPassword < Ndr::NdrStruct
default_parameter byte_align: 4
ndr_fixed_byte_array :buffer, initial_length: 516

def self.encrypt_password(password, old_password_nt)
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/5fe3c4c4-e71b-440d-b2fd-8448bfaf6e04
password = password.encode('UTF-16LE').force_encoding('ASCII-8BIT')
buffer = password.rjust(512, "\x00") + [ password.length ].pack('V')
cipher = OpenSSL::Cipher.new('RC4').tap do |cipher|
cipher.encrypt
cipher.key = old_password_nt
end
cipher.update(buffer)
smcintyre-r7 marked this conversation as resolved.
Show resolved Hide resolved
end
end

class PsamprEncryptedUserPassword < SamprEncryptedUserPassword
extend Ndr::PointerClassPlugin
end


# [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)
class SamprEncryptedUserPasswordNew < BinData::Record
ndr_fixed_byte_array :buffer, initial_length: 532
Expand Down Expand Up @@ -413,12 +489,22 @@ class UserControlInformation < BinData::Record
ndr_uint32 :user_account_control
end

# [2.2.6.25 SAMPR_USER_INTERNAL1_INFORMATION](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/50d17755-c6b8-40bd-8cac-bd6cfa31adf2)
class SamprUserInternal1Information < BinData::Record
encrypted_nt_owf_password :encrypted_nt_owf_password
encrypted_nt_owf_password :encrypted_lm_owf_password
ndr_uint8 :nt_password_present
ndr_uint8 :lm_password_present
ndr_uint8 :password_expired
end

# [2.2.6.29 SAMPR_USER_INFO_BUFFER](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/9496c26e-490b-4e76-827f-2695fc216f35)
class SamprUserInfoBuffer < BinData::Record
ndr_uint16 :tag
choice :member, selection: :tag do
user_control_information USER_CONTROL_INFORMATION
sampr_user_internal4_information_new USER_INTERNAL4_INFORMATION_NEW
sampr_user_internal1_information USER_INTERNAL1_INFORMATION
end
end

Expand Down Expand Up @@ -523,6 +609,10 @@ def get_key_values
require 'ruby_smb/dcerpc/samr/samr_get_groups_for_user_response'
require 'ruby_smb/dcerpc/samr/samr_set_information_user2_request'
require 'ruby_smb/dcerpc/samr/samr_set_information_user2_response'
require 'ruby_smb/dcerpc/samr/samr_change_password_user_request'
require 'ruby_smb/dcerpc/samr/samr_change_password_user_response'
require 'ruby_smb/dcerpc/samr/samr_unicode_change_password_user2_request'
require 'ruby_smb/dcerpc/samr/samr_unicode_change_password_user2_response'
require 'ruby_smb/dcerpc/samr/samr_delete_user_request'
require 'ruby_smb/dcerpc/samr/samr_delete_user_response'
require 'ruby_smb/dcerpc/samr/samr_query_information_domain_request'
Expand Down Expand Up @@ -882,6 +972,117 @@ def samr_set_information_user2(user_handle:, user_info:)
nil
end

# Change the password on a user object.
#
# @param user_handle [SamprHandle] Handle representing the user to change the password for
# @param old_password [String] The previous password (either this or old_nt_hash must be specified)
# @param new_password [String] The password to set on the account
# @param new_nt_hash [String] The new password's NT hash
# @param new_lm_hash [String] The new password's LM hash
# @param old_nt_hash [String] The previous password's NT hash (either this or old_password must be specified)
# @param old_lm_hash [String] The previous password's LM hash (currently ignored)
# @return nothing is returned on success
# @raise [RubySMB::Dcerpc::Error::SamrError] if the response error status
# is not STATUS_SUCCESS
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)
if new_password.nil? == new_nt_hash.nil?
raise ArgumentError.new('Provide either new password or new password hashes, but not both')
end

if old_password.nil? == old_nt_hash.nil?
raise ArgumentError.new('Provide either old password or old password hashes, but not both')
end

if old_password
old_lm_hash = Net::NTLM.lm_hash(old_password[0..13])
old_nt_hash = Net::NTLM.ntlm_hash(old_password)
end

if new_nt_hash.nil?
new_nt_hash = Net::NTLM::ntlm_hash(new_password)
new_lm_hash = Net::NTLM.lm_hash(new_password[0..13])
elsif new_lm_hash.nil?
new_lm_hash = Net::NTLM.lm_hash('')
end

samr_change_password_user_request = SamrChangePasswordUserRequest.new(
user_handle: user_handle,
lm_present: 0,
old_lm_encrypted_with_new_lm: nil,
new_lm_encrypted_with_old_lm: nil,
nt_present: 1,
old_nt_encrypted_with_new_nt: PencryptedNtOwfPassword.new(buffer: EncryptedNtOwfPassword.encrypt_hash(hash: old_nt_hash, key: new_nt_hash)),
new_nt_encrypted_with_old_nt: PencryptedNtOwfPassword.new(buffer: EncryptedNtOwfPassword.encrypt_hash(hash: new_nt_hash, key: old_nt_hash)),
nt_cross_encryption_present: 0,
new_nt_encrypted_with_new_lm: nil,
lm_cross_encryption_present: 1,
new_lm_encrypted_with_new_nt: PencryptedNtOwfPassword.new(buffer: EncryptedNtOwfPassword.encrypt_hash(hash: new_lm_hash, key: new_nt_hash)),
)
response = dcerpc_request(samr_change_password_user_request)
begin
samr_unicode_change_password_user2_response = SamrChangePasswordUserResponse.read(response)
rescue IOError
raise RubySMB::Dcerpc::Error::InvalidPacket, 'Error reading SamrUnicodeChangePasswordUser2Response'
end
unless samr_unicode_change_password_user2_response.error_status == WindowsError::NTStatus::STATUS_SUCCESS
raise RubySMB::Dcerpc::Error::SamrError,
"Error returned while changing user password: "\
"#{WindowsError::NTStatus.find_by_retval(samr_unicode_change_password_user2_response.error_status.value).join(',')}"
end

nil
end


# Change the password on a user.
#
# @param server_name [String] The server name; can be ignored by the server
# @param target_username [String] The user for which we're changing the password
# @param old_password [String] The previous password (either this or old_nt_hash must be specified)
# @param new_password [String] The password to set on the account
# @param old_nt_hash [String] The previous password's NT hash (either this or old_password must be specified)
# @param old_lm_hash [String] The previous password's LM hash (currently ignored)
# @return nothing is returned on success
# @raise [RubySMB::Dcerpc::Error::SamrError] if the response error status
# is not STATUS_SUCCESS
def samr_unicode_change_password_user2(server_name: "", target_username:, old_password:nil, new_password:, old_nt_hash:nil, old_lm_hash:nil)
#if old_lm_hash.nil? != old_nt_hash.nil?
# raise ArgumentError.new('If providing the previous NT/LM hash, must provide both')
#end
if old_password.nil? == old_nt_hash.nil?
raise ArgumentError.new('Provide either old password or old password hashes, but not both')
end

if old_password
old_lm_hash = Net::NTLM.lm_hash(old_password[0..13])
old_nt_hash = Net::NTLM.ntlm_hash(old_password)
end

new_nt_hash = Net::NTLM::ntlm_hash(new_password)

samr_unicode_change_password_user2_request = SamrUnicodeChangePasswordUser2Request.new(
server_name: server_name,
user_name: target_username,
new_password_encrypted_with_old_nt: SamprEncryptedUserPassword.new(buffer: SamprEncryptedUserPassword.encrypt_password(new_password, old_nt_hash)),
old_nt_owf_password_encrypted_with_new_nt: PencryptedNtOwfPassword.new(buffer: EncryptedNtOwfPassword.encrypt_hash(hash: old_nt_hash, key: new_nt_hash)),
lm_present: 0
)
samr_unicode_change_password_user2_request
response = dcerpc_request(samr_unicode_change_password_user2_request)
begin
samr_unicode_change_password_user2_response = SamrUnicodeChangePasswordUser2Response.read(response)
rescue IOError
raise RubySMB::Dcerpc::Error::InvalidPacket, 'Error reading SamrUnicodeChangePasswordUser2Response'
end
unless samr_unicode_change_password_user2_response.error_status == WindowsError::NTStatus::STATUS_SUCCESS
raise RubySMB::Dcerpc::Error::SamrError,
"Error returned while changing user password: "\
"#{WindowsError::NTStatus.find_by_retval(samr_unicode_change_password_user2_response.error_status.value).join(',')}"
end

nil
end

# Closes (that is, releases server-side resources used by) any context
# handle obtained from this RPC interface
#
Expand Down
31 changes: 31 additions & 0 deletions lib/ruby_smb/dcerpc/samr/samr_change_password_user_request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module RubySMB
module Dcerpc
module Samr

# [3.1.5.10.1 SamrChangePasswordUser (Opnum 38)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/9699d8ca-e1a4-433c-a8c3-d7bebeb01476)
class SamrChangePasswordUserRequest < BinData::Record
attr_reader :opnum

endian :little

sampr_handle :user_handle
ndr_uint8 :lm_present
pencrypted_nt_owf_password :old_lm_encrypted_with_new_lm
pencrypted_nt_owf_password :new_lm_encrypted_with_old_lm
ndr_uint8 :nt_present
pencrypted_nt_owf_password :old_nt_encrypted_with_new_nt
pencrypted_nt_owf_password :new_nt_encrypted_with_old_nt
ndr_uint8 :nt_cross_encryption_present
pencrypted_nt_owf_password :new_nt_encrypted_with_new_nt
ndr_uint8 :lm_cross_encryption_present
pencrypted_nt_owf_password :new_lm_encrypted_with_new_nt

def initialize_instance
super
@opnum = SAMR_CHANGE_PASSWORD_USER
end
end

end
end
end
21 changes: 21 additions & 0 deletions lib/ruby_smb/dcerpc/samr/samr_change_password_user_response.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module RubySMB
module Dcerpc
module Samr

# [3.1.5.10.1 SamrChangePasswordUser (Opnum 38)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/9699d8ca-e1a4-433c-a8c3-d7bebeb01476)
class SamrChangePasswordUserResponse < BinData::Record
attr_reader :opnum

endian :little

ndr_uint32 :error_status

def initialize_instance
super
@opnum = SAMR_CHANGE_PASSWORD_USER
end
end

end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class PsamprGetMembersBuffer < SamprGetMembersBuffer
extend Ndr::PointerClassPlugin
end

# [2.1.5.8.3 SamrGetMembersInGroup (Opnum 25)](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/a4adbf20-040f-4416-a960-e5b7917fdae7)
# [3.1.5.8.3 SamrGetMembersInGroup (Opnum 25)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/3ed5030d-88a3-42ca-a6e0-8c12aa2fdfbd)
class SamrGetMembersInGroupResponse < BinData::Record
attr_reader :opnum

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module RubySMB
module Dcerpc
module Samr

# [3.1.5.10.3 SamrUnicodeChangePasswordUser2 (Opnum 55)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/acb3204a-da8b-478e-9139-1ea589edb880)
class SamrUnicodeChangePasswordUser2Request < BinData::Record
attr_reader :opnum

endian :little

prpc_unicode_string :server_name
rpc_unicode_string :user_name
psampr_encrypted_user_password :new_password_encrypted_with_old_nt
pencrypted_nt_owf_password :old_nt_owf_password_encrypted_with_new_nt
ndr_uint8 :lm_present
psampr_encrypted_user_password :new_password_encrypted_with_old_lm
pencrypted_nt_owf_password :old_lm_owf_password_encrypted_with_new_nt

def initialize_instance
super
@opnum = SAMR_UNICODE_CHANGE_PASSWORD_USER2
end
end

end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module RubySMB
module Dcerpc
module Samr

# [3.1.5.10.3 SamrUnicodeChangePasswordUser2 (Opnum 55)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/acb3204a-da8b-478e-9139-1ea589edb880)
class SamrUnicodeChangePasswordUser2Response < BinData::Record
attr_reader :opnum

endian :little

ndr_uint32 :error_status

def initialize_instance
super
@opnum = SAMR_UNICODE_CHANGE_PASSWORD_USER2
end
end

end
end
end
10 changes: 10 additions & 0 deletions spec/lib/ruby_smb/dcerpc/samr/encrypted_nt_owf_password_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
RSpec.describe RubySMB::Dcerpc::Samr::EncryptedNtOwfPassword do
it 'Creates output key' do
expect(described_class.to_output_key('ABCDEFG')).to eq ["40a09068442A188E"].pack('H*')
expect(described_class.to_output_key('AAAAAAA')).to eq ["40A05028140A0482"].pack('H*')
end

it 'Encrypts a hash' do
expect(described_class.encrypt_hash(hash: 'AAAAAAAAAAAAAAAA', key: 'BBBBBBBBBBBBBB')).to eq ["8cd90c3de08ecda28cd90c3de08ecda2"].pack('H*')
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
RSpec.describe RubySMB::Dcerpc::Samr::SamprEncryptedUserPassword do

it 'Encrypts correctly' do
password = 'newPassword1!'
old_password_nt = 'AAAAAAAAAAAAAAAA'
expected = ['c2cbe63dc0a3cda1baab695ce4f0352b774592b214a905796a6ba9fd30e0ffc56d60812bfd7f45420f5332922b50cfdcde3133e3e45c5eb6c16023006cb4ff7d41f54fa009a64fa66b00fa41094d7db6c4cc9a430a7ad43122047b696d934645974fee7551dcafac28c0869106417fd3fbfc34a87a56d6141aac2b6d134723f2224791b39749bc93f4405f78ba09dcd9b4d29e72ae0c873a8d468323793fffccc2a9d8dc4d975c42064208d898df71ef0889b2e5a9cfa6c8c2758eb15b6fdd332d6d7d2829f3a3bc2a7ecd7f87d1fd4aed25d93de6a7baf8e0247e2f5b831ca7646eef7a11e2f9410574707ef85c9db8cc125fb6254fe1e111a662006343299fcb8f8fb641cb1d188ff6e5c50334ca199603a93907093e6d35d80b2dade79360bc672870d29cdf5f80120ac53e9ce3c3718d0f8097cdae2318b8e27108fc1066491e5b034f55c1e8ff00a53d2eb7b0e0ffc10236a5a530795b84f33d66eb51388d6da112886c8ea482af0ec7e9c0a549a561244ee9185b5387b05d3c5e74d88e355aef22dab1d5039d0a0caa22437f6e520121ef5d21da729bb0cdee5cbb3b450bf1d0fcafad5ac518b0535628e2a96a2d0d2f8acd3e42147e700c2889cbc92c5533f1889b49d41c0ae9a05fc0a754060c0680296de5c712a3299cdd5c646582348fc2e32a68ae4fc2a3b91fb423adc617a20b28c1d6297f14a870329e6aa802d22ba97c'].pack('H*')
expect(described_class.encrypt_password(password, old_password_nt)).to eq expected
end
end
Loading
Loading