Skip to content

Commit

Permalink
Land #19115, read/write registry key SD
Browse files Browse the repository at this point in the history
Module to read/write registry key security descriptor remotely
  • Loading branch information
smcintyre-r7 committed May 13, 2024
2 parents 613ec3c + f1ee10f commit 733c014
Show file tree
Hide file tree
Showing 3 changed files with 255 additions and 6 deletions.
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
```
168 changes: 168 additions & 0 deletions modules/auxiliary/admin/registry_security_descriptor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::SMB::Client::Authenticated
include Msf::OptionalSession::SMB
include Msf::Util::WindowsRegistry

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Windows Registry Security Descriptor Utility',
'Description' => %q{
Read or write 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.
},
'Author' => [
'Christophe De La Fuente'
],
'License' => MSF_LICENSE,
'Actions' => [
[ 'READ', { 'Description' => 'Read a Windows registry security descriptor' } ],
[ 'WRITE', { 'Description' => 'Write a Windows registry security descriptor' } ]
],
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [],
'SideEffects' => [CONFIG_CHANGES]
},
'DefaultAction' => 'READ'
)
)

register_options(
[
OptString.new('KEY', [ false, 'Registry key to read or write' ]),
OptString.new('SD', [ false, 'Security Descriptor to write as a hex string' ], conditions: %w[ACTION == WRITE], regex: /^([a-fA-F0-9]{2})+$/),
OptInt.new('SECURITY_INFORMATION', [
true,
'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)',
RubySMB::Field::SecurityDescriptor::OWNER_SECURITY_INFORMATION |
RubySMB::Field::SecurityDescriptor::GROUP_SECURITY_INFORMATION |
RubySMB::Field::SecurityDescriptor::DACL_SECURITY_INFORMATION
]),
OptString.new('FILE', [
false,
'File path to store the security descriptor when reading or source file path used to write the security descriptor when writing'
])
]
)
end

def do_connect
if session
print_status("Using existing session #{session.sid}")
client = session.client
self.simple = ::Rex::Proto::SMB::SimpleClient.new(client.dispatcher.tcp_socket, client: client)
simple.connect("\\\\#{simple.address}\\IPC$")
else
connect
begin
smb_login
rescue Rex::Proto::SMB::Exceptions::Error, RubySMB::Error::RubySMBError => e
fail_with(Module::Failure::NoAccess, "Unable to authenticate ([#{e.class}] #{e}).")
end
end

report_service(
host: simple.address,
port: simple.port,
host_name: simple.client.default_name,
proto: 'tcp',
name: 'smb',
info: "Module: #{fullname}, last negotiated version: SMBv#{simple.client.negotiated_smb_version} (dialect = #{simple.client.dialect})"
)

begin
@tree = simple.client.tree_connect("\\\\#{simple.address}\\IPC$")
rescue RubySMB::Error::RubySMBError => e
fail_with(Module::Failure::Unreachable, "Unable to connect to the remote IPC$ share ([#{e.class}] #{e}).")
end

begin
@winreg = @tree.open_file(filename: 'winreg', write: true, read: true)
@winreg.bind(endpoint: RubySMB::Dcerpc::Winreg)
rescue RubySMB::Error::RubySMBError => e
fail_with(Module::Failure::Unreachable, "Error when connecting to 'winreg' interface ([#{e.class}] #{e}).")
end
end

def run
do_connect

case action.name
when 'READ'
action_read
when 'WRITE'
action_write
else
print_error("Unknown action #{action.name}")
end
ensure
@winreg.close if @winreg
@tree.disconnect! if @tree
# Don't disconnect the client if it's coming from the session so it can be reused
unless session
simple.client.disconnect! if simple&.client.is_a?(RubySMB::Client)
disconnect
end
end

def action_read
fail_with(Failure::BadConfig, 'Unknown registry key, please set the `KEY` option') if datastore['KEY'].blank?

sd = @winreg.get_key_security_descriptor(datastore['KEY'], datastore['SECURITY_INFORMATION'], bind: false)
print_good("Raw security descriptor for #{datastore['KEY']}: #{sd.bytes.map { |c| '%02x' % c.ord }.join}")

unless datastore['FILE'].blank?
remote_reg = Msf::Util::WindowsRegistry::RemoteRegistry.new(@winreg, name: :sam)
remote_reg.save_to_file(datastore['KEY'], sd, datastore['SECURITY_INFORMATION'], datastore['FILE'])
print_good("Saved to file #{datastore['FILE']}")
end
end

def action_write
if datastore['FILE'].blank?
fail_with(Failure::BadConfig, 'Unknown security descriptor, please set the `SD` option') if datastore['SD'].blank?
fail_with(Failure::BadConfig, 'Unknown registry key, please set the `KEY` option') if datastore['KEY'].blank?
sd = datastore['SD']
key = datastore['KEY']
security_info = datastore['SECURITY_INFORMATION']
else
print_status("Getting security descriptor info from file #{datastore['FILE']}")
remote_reg = Msf::Util::WindowsRegistry::RemoteRegistry.new(@winreg, name: :sam)
sd_info = remote_reg.read_from_file(datastore['FILE'])
sd = sd_info['sd']
key = sd_info['key']
security_info = sd_info['security_info']
vprint_line(" key: #{key}")
vprint_line(" security information: #{security_info}")
vprint_line(" security descriptor: #{sd}")
end

sd = sd.chars.each_slice(2).map { |c| c.join.to_i(16).chr }.join
@winreg.set_key_security_descriptor(key, sd, security_info, bind: false)
print_good("Security descriptor set for #{key}")
rescue RubySMB::Dcerpc::Error::WinregError => e
fail_with(Failure::Unknown, "Unable to set the security descriptor for #{key}: #{e}")
end
end
9 changes: 3 additions & 6 deletions modules/auxiliary/example.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,9 @@ def run
print_status("Running the simple auxiliary module with action #{action.name}")
end

# auxiliary modules can register new commands, they all call cmd_* to
# dispatch them
def auxiliary_commands
{ 'aux_extra_command' => 'Run this auxiliary test commmand' }
end

# Framework automatically registers `cmd_*` methods to be dispatched when the
# corresponding command is used. For example, here this method will be called
# when entering the `aux_extra_command` command in the console.
def cmd_aux_extra_command(*args)
print_status("Running inside aux_extra_command(#{args.join(' ')})")
end
Expand Down

0 comments on commit 733c014

Please sign in to comment.