Skip to content

Msf::Module::UUID#generate_uuid: Replace Rex::Text with SecureRandom.uuid #20170

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

bcoles
Copy link
Contributor

@bcoles bcoles commented May 12, 2025

Module UUIDs were added in commit 9277f06 titled "Store a uuid for each module, track this in sessions" in 2011. Module UUIDs differ between Metasploit runs, as they are dynamically generated at runtime using Msf::Module::UUID#generate_uuid.

This method was authored in 2011 when Metasploit contained far fewer modules. Now this method is called approximately 27 thousand (!) times during startup. Due to the ever-increasing number of modules, this behaviour will cause the startup time to grow.

The UUIDs are generated with Rex::Text.rand_text_alphanumeric(8).downcase, which does not generate standard UUIDs.

self.uuid = Rex::Text.rand_text_alphanumeric(8).downcase

The generated UUID is 8 characters in length (64bit). Most of the key space is wasted, as only 36 values (a-z and 0-9) of 256 are used.

Generating collisions is unlikely, and the impact to startup time is minimal, but the computation is needlessly expensive and wasteful:

  • rand_text_alphanumeric returns mixed-case alphanumic characters, but we ultimately discard uppercase A-Z
  • rand_text_alphanumeric constructs an array, then calls Rex::Text#rand_base,[1] which:
    • needlessly deals with bad characters,[2] of which there are none
    • performs a bunch of expensive array operations (join, pack, uniq) and appends to an array in a loop[2]

All we really want is a unique value that is unlikely to cause collisions. If UUID collisions or speed were an issue we could pre-compute values (which has the added benefit of consistency between Metasploit runs).

Instead, this PR replaces Rex::Text.rand_text_alphanumeric with SecureRandom.uuid which is significantly faster (almost 10x 🚀):

#!/usr/bin/env/ruby
require 'benchmark'
require 'securerandom'
require 'rex/text'

n = 100_000

Benchmark.bm(20) do |bm|
  bm.report('Rex::Text.rand_text') do
    n.times { uuid = Rex::Text.rand_text_alphanumeric(8).downcase }
  end

  bm.report('SecureRandom.uuid') do
    n.times { uuid = SecureRandom.uuid }
  end
end

Benchmarked on a system with 2 cores and 4GB RAM:

  • 100,000 iterations:
                           user     system      total        real
Rex::Text.rand_text    2.484292   0.017122   2.501414 (  2.522912)
SecureRandom.uuid      0.300432   0.040074   0.340506 (  0.341532)
  • 27,000 iterations:
                           user     system      total        real
Rex::Text.rand_text    0.681830   0.003688   0.685518 (  0.692985)
SecureRandom.uuid      0.083827   0.011939   0.095766 (  0.095777)

This does not introduce an extra dependency as we already use SecureRandom in Framework:

# grep -rn securerandom lib/
lib/msf/base/sessions/encrypted_shell.rb:2:require 'securerandom'
lib/msf/base/sessions/mettle_config.rb:4:require 'securerandom'
lib/msf/core/modules/external/message.rb:4:require 'securerandom'
lib/msf/core/exploit/remote/tincd_exploit_client.rb:1:require 'securerandom'
lib/msf/core/db_manager/user.rb:2:require 'securerandom'
lib/msf/core/module/uuid.rb:2:require 'securerandom'
lib/msf/core/payload/windows/encrypted_payload_opts.rb:1:require 'securerandom'
lib/msf/core/web_services/json_rpc_app.rb:1:require 'securerandom'
lib/msf/core/web_services/metasploit_api_app.rb:1:require 'securerandom'
lib/metasploit/framework/spec/threads/logger.rb:5:require 'securerandom'
lib/metasploit/framework/obfuscation/crandomizer/utility.rb:2:require 'securerandom'
lib/rex/post/meterpreter/pivot.rb:4:require 'securerandom'
lib/rex/payloads/meterpreter/config.rb:4:require 'securerandom'

Note: I'm not sure what effect this change will have on Metasploit Pro, if any.
Note: Maybe there is a reason we only want UUIDs to be only 8 characters? If so, we could simply truncate the generated UUID. This would still be much faster than using Rex::Text.


[1] https://github.com/rapid7/rex-text/blob/0d30d394c4378dbaddf7b489f0d22c2db4024ec7/lib/rex/text/rand.rb#L110-L118
[2] https://github.com/rapid7/rex-text/blob/0d30d394c4378dbaddf7b489f0d22c2db4024ec7/lib/rex/text/rand.rb#L144-L151

@bcoles bcoles force-pushed the msf-module-uuid branch from a1f0ea1 to 80222c8 Compare May 15, 2025 14:13
@bcoles bcoles requested a review from sjanusz-r7 May 17, 2025 01:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants