Skip to content

Commit

Permalink
Add GetKeySecurity and SetKeySecurity MS-RRP structures, specs and ex…
Browse files Browse the repository at this point in the history
…ample script (DCERPC)
  • Loading branch information
cdelafuente-r7 committed Apr 19, 2024
1 parent d23478f commit 9ba051e
Show file tree
Hide file tree
Showing 14 changed files with 738 additions and 39 deletions.
109 changes: 109 additions & 0 deletions examples/registry_key_security_descriptor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#!/usr/bin/ruby

# This example script is used for testing the Winreg registry key security descriptor functionalities.
# It will attempt to connect to a host and reads (or writes) the security descriptor of a specified registry key.
#
# Example usage:
# - read:
# ruby examples/read_registry_key_security.rb --username msfadmin --password msfadmin -i 7 -o r 192.168.172.138 'HKLM\SECURITY\Policy\PolEKList'
# This will try to connect to \\192.168.172.138 with the msfadmin:msfadmin
# credentialas and read the security descriptor of the
# `HKLM\SECURITY\Policy\PolEKList` registry key with the security information 7
# (OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION |
# DACL_SECURITY_INFORMATION).
#
# - write:
# ruby examples/read_registry_key_security.rb --username msfadmin --password msfadmin -i 4 --sd 01000480000000000000000000000000140000000200340002000000000214003f000f00010100000000000512000000000218000000060001020000000000052000000020020000 -o w 192.168.172.138 'HKLM\SECURITY\Policy\PolEKList'
# This will try to connect to \\192.168.172.138 with the msfadmin:msfadmin
# credentialas and write the given security descriptor to the
# `HKLM\SECURITY\Policy\PolEKList` registry key with the security information 4
# (DACL_SECURITY_INFORMATION).

require 'bundler/setup'
require 'optparse'
require 'ruby_smb'

OPERATIONS = %w{read write}
OPERATION_ALIASES = { "r" => "read", "w" => "write" }

args = ARGV.dup
options = {
domain: '.',
username: '',
password: '',
smbv1: true,
smbv2: true,
smbv3: true,
target: nil,
key: nil,
operation: 'read',
info: RubySMB::Field::SecurityDescriptor::OWNER_SECURITY_INFORMATION | RubySMB::Field::SecurityDescriptor::GROUP_SECURITY_INFORMATION | RubySMB::Field::SecurityDescriptor::DACL_SECURITY_INFORMATION,
sd: nil
}
options[:key] = args.pop
options[:target ] = args.pop
optparser = OptionParser.new do |opts|
opts.banner = "Usage: #{File.basename(__FILE__)} [options] target reg_key"
opts.on('--[no-]smbv1', "Enable or disable SMBv1 (default: #{options[:smbv1] ? 'Enabled' : 'Disabled'})") do |smbv1|
options[:smbv1] = smbv1
end
opts.on('--[no-]smbv2', "Enable or disable SMBv2 (default: #{options[:smbv2] ? 'Enabled' : 'Disabled'})") do |smbv2|
options[:smbv2] = smbv2
end
opts.on('--[no-]smbv3', "Enable or disable SMBv3 (default: #{options[:smbv3] ? 'Enabled' : 'Disabled'})") do |smbv3|
options[:smbv3] = smbv3
end
opts.on('-u', '--username [USERNAME]', "The account's username (default: #{options[:username]})") do |username|
if username.include?('\\')
options[:domain], options[:username] = username.split('\\', 2)
else
options[:username] = username
end
end
opts.on('-p', '--password [PASSWORD]', "The account's password (default: #{options[:password]})") do |password|
options[:password] = password
end
operation_list = (OPERATION_ALIASES.keys + OPERATIONS).join(', ')
opts.on('-o', '--operation OPERATION', OPERATIONS, OPERATION_ALIASES, "The operation to perform on the registry key (default: #{options[:operation]})", "(#{operation_list})") do |operation|
options[:operation] = operation
end
opts.on('-i', '--info [SECURITY INFORMATION]', Integer, "The security information value (default: #{options[:info]})") do |password|
options[:info] = password
end
opts.on('-s', '--sd [SECURITY DESCRIPTOR]', "The security descriptor to write as an hex string") do |sd|
options[:sd] = sd
end
end
optparser.parse!(args)

if options[:target].nil? || options[:key].nil?
abort(optparser.help)
end

sock = TCPSocket.new options[:target], 445
dispatcher = RubySMB::Dispatcher::Socket.new(sock)

client = RubySMB::Client.new(dispatcher, smb1: options[:smbv1], smb2: options[:smbv2], smb3: options[:smbv3], username: options[:username], password: options[:password], domain: options[:domain])
protocol = client.negotiate
status = client.authenticate

puts "#{protocol}: #{status}"

case options[:operation]
when 'read', 'r'
puts "Read registry key #{options[:key]} security descriptor with security information #{options[:info]}"
security_descriptor = client.get_key_security_descriptor(options[:target], options[:key], options[:info])
puts "Security descriptor: #{security_descriptor.b.bytes.map {|c| "%02x" % c.ord}.join}"
when 'write', 'w'
unless options[:sd] && !options[:sd].empty?
puts "Security descriptor missing"
abort(optparser.help)
end
puts "Write security descriptor #{options[:sd]} to registry key #{options[:key]} with security information #{options[:info]}"
sd = options[:sd].chars.each_slice(2).map {|c| c.join.to_i(16).chr}.join
status = client.set_key_security_descriptor(options[:target], options[:key], sd, options[:info])
puts "Success!"
end

client.disconnect!

12 changes: 12 additions & 0 deletions lib/ruby_smb/client/winreg.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,18 @@ def enum_registry_values(host, key)
end
end

def get_key_security_descriptor(host, key, security_information = RubySMB::Field::SecurityDescriptor::OWNER_SECURITY_INFORMATION)
connect_to_winreg(host) do |named_pipe|
named_pipe.get_key_security_descriptor(key, security_information)
end
end

def set_key_security_descriptor(host, key, security_descriptor, security_information = RubySMB::Field::SecurityDescriptor::OWNER_SECURITY_INFORMATION)
connect_to_winreg(host) do |named_pipe|
named_pipe.set_key_security_descriptor(key, security_descriptor, security_information)
end
end

end
end
end
Expand Down
14 changes: 10 additions & 4 deletions lib/ruby_smb/dcerpc/ndr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -563,8 +563,11 @@ def do_num_bytes
def get_max_count(val)
if is_a?(BinData::Stringz)
max_count = val.to_s.strip.length
# Only count the terminating NULL byte if the string is not empty
max_count += 1 if max_count > 0
# Add one to count the terminator. According to
# https://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_04_02,
# the NDR String must contain at least one element, the terminator. So,
# add one even if it is an empty string.
max_count += 1
return max_count
else
return val.to_s.length
Expand Down Expand Up @@ -618,8 +621,11 @@ def do_num_bytes
def update_actual_count(val)
if is_a?(BinData::Stringz)
@actual_count = val.to_s.strip.length
# Only count the terminating NULL byte if the string is not empty
@actual_count += 1 if @actual_count > 0
# Add one to count the terminator. According to
# https://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_04,
# the NDR String must contain at least one element, the terminator. So,
# add one even if it is an empty string.
@actual_count += 1
else
@actual_count = val.to_s.length
end
Expand Down
34 changes: 18 additions & 16 deletions lib/ruby_smb/dcerpc/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,24 @@ class Request < BinData::Record
choice :stub, label: 'Stub', selection: -> { @obj.parent.get_parameter(:endpoint) || '' } do
string 'Encrypted'
choice 'Winreg', selection: -> { opnum } do
open_root_key_request Winreg::OPEN_HKCR, opnum: Winreg::OPEN_HKCR
open_root_key_request Winreg::OPEN_HKCU, opnum: Winreg::OPEN_HKCU
open_root_key_request Winreg::OPEN_HKLM, opnum: Winreg::OPEN_HKLM
open_root_key_request Winreg::OPEN_HKPD, opnum: Winreg::OPEN_HKPD
open_root_key_request Winreg::OPEN_HKU, opnum: Winreg::OPEN_HKU
open_root_key_request Winreg::OPEN_HKCC, opnum: Winreg::OPEN_HKCC
open_root_key_request Winreg::OPEN_HKPT, opnum: Winreg::OPEN_HKPT
open_root_key_request Winreg::OPEN_HKPN, opnum: Winreg::OPEN_HKPN
close_key_request Winreg::REG_CLOSE_KEY
enum_key_request Winreg::REG_ENUM_KEY
enum_value_request Winreg::REG_ENUM_VALUE
open_key_request Winreg::REG_OPEN_KEY
query_info_key_request Winreg::REG_QUERY_INFO_KEY
query_value_request Winreg::REG_QUERY_VALUE
create_key_request Winreg::REG_CREATE_KEY
save_key_request Winreg::REG_SAVE_KEY
open_root_key_request Winreg::OPEN_HKCR, opnum: Winreg::OPEN_HKCR
open_root_key_request Winreg::OPEN_HKCU, opnum: Winreg::OPEN_HKCU
open_root_key_request Winreg::OPEN_HKLM, opnum: Winreg::OPEN_HKLM
open_root_key_request Winreg::OPEN_HKPD, opnum: Winreg::OPEN_HKPD
open_root_key_request Winreg::OPEN_HKU, opnum: Winreg::OPEN_HKU
open_root_key_request Winreg::OPEN_HKCC, opnum: Winreg::OPEN_HKCC
open_root_key_request Winreg::OPEN_HKPT, opnum: Winreg::OPEN_HKPT
open_root_key_request Winreg::OPEN_HKPN, opnum: Winreg::OPEN_HKPN
close_key_request Winreg::REG_CLOSE_KEY
enum_key_request Winreg::REG_ENUM_KEY
enum_value_request Winreg::REG_ENUM_VALUE
open_key_request Winreg::REG_OPEN_KEY
query_info_key_request Winreg::REG_QUERY_INFO_KEY
query_value_request Winreg::REG_QUERY_VALUE
create_key_request Winreg::REG_CREATE_KEY
save_key_request Winreg::REG_SAVE_KEY
get_key_security_request Winreg::REG_GET_KEY_SECURITY
set_key_security_request Winreg::REG_SET_KEY_SECURITY
string :default
end
choice 'Netlogon', selection: -> { opnum } do
Expand Down
2 changes: 1 addition & 1 deletion lib/ruby_smb/dcerpc/rrp_rpc_unicode_string.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def assign(val)
when BinData::Stringz, BinData::String, String
self.buffer = val.to_s
val_length = val.strip.length
val_length += 1 unless val == ''
val_length += 1
self.buffer_length = val_length * 2
self.maximum_length = val_length * 2
else
Expand Down
Loading

0 comments on commit 9ba051e

Please sign in to comment.