Metasploit Deep Dive Part 2: Framework Internals, Modules, Mixins, and the Datastore

Every module in Metasploit, whether it’s a 2017 EternalBlue exploit or a 2026 Joomla RCE, inherits from the same base class: Msf::Module, defined in lib/msf/core/module.rb. Reading that file directly is the fastest way to understand what “a module” actually means in this framework, and it’s a genuinely different answer than what Sn1per or Impacket gave when this site read their source.

One class, nineteen mixins

Msf::Module doesn’t implement its own behavior directly. It autoloads and includes nineteen separate mixins:

include Msf::Module::Alert
include Msf::Module::Arch
include Msf::Module::Auth
include Msf::Module::Author
include Msf::Module::Compatibility
include Msf::Module::DataStore
include Msf::Module::Failure
include Msf::Module::FullName
include Msf::Module::ModuleInfo
include Msf::Module::ModuleStore
include Msf::Module::Network
include Msf::Module::Options
include Msf::Module::Privileged
include Msf::Module::Ranking
include Msf::Module::Type
include Msf::Module::UI
include Msf::Module::UUID
include Msf::Module::SideEffects
include Msf::Module::Stability
include Msf::Module::Reliability

Each one owns a single concern. Author parses and stores the 'Author' => [...] array every module declares. Platform and Arch track what OS and CPU architecture a module targets. Ranking handles how reliable a module claims to be. Type distinguishes exploit from auxiliary from post. None of these mixins know about each other, and that’s the point: a payload module needs Arch and DataStore but has no use for Auth, while an auxiliary login scanner needs Auth but not Privileged. Composing behavior from narrow mixins instead of inheriting from a fat base class means each module type only carries the weight it actually needs, and a change to how authors are parsed doesn’t risk breaking how architectures are matched.

This is the same lesson the Ansible Deep Dive series drew from roles: small, single-purpose units compose more safely than one large one. Metasploit arrived at it independently, in Ruby, for a completely different problem.

The datastore

Msf::Module::DataStore is the mixin behind every set RHOSTS 10.10.10.20 a user has ever typed. It’s not a hash lookup bolted onto the module; it’s a defined, validated, typed key-value store with import/export semantics, and it’s shared identically across every module type. An exploit’s RHOSTS, a payload’s LHOST, and a post module’s SESSION all go through the same mixin. That uniformity is what lets msfconsole’s set/unset/show options commands work the same way regardless of what kind of module is currently active, and it’s what lets the RPC and MCP layers (covered in Parts 11 and 12) pass options as a flat JSON object without needing per-module-type serialization logic.

Module ranking

Msf::Module::Ranking attaches a reliability rating to every exploit, exposed via rank and rendered as a string via rank_to_s. The scale runs, from lowest to highest: Manual, Low, Average, Normal, Good, Great, Excellent. A module ranked Excellent won’t crash the target service under normal conditions and reliably gets a result; a module ranked Manual might need hand-tuning per target or carries a real chance of taking the service down. This ranking isn’t cosmetic. msfconsole’s exploit -j and automation built on top of the framework (including, as Part 12 covers, the new MCP tools) can filter or warn based on it, which matters a great deal once a workflow lets something other than a human decide which exploit to fire.

Seven module types, one taxonomy

Msf::Module::Type is what separates the seven categories the real clone showed real counts for in Part 1: exploits, auxiliary, post, payloads, encoders, evasion, and NOPs. Each type lives under its own top-level directory (modules/exploits/, modules/auxiliary/, and so on), and each subclasses Msf::Module through an intermediate class that adds type-specific behavior: Msf::Exploit adds targeting and payload compatibility checking, Msf::Auxiliary adds the AuxiliaryAction mixin for modules that support multiple modes (a scanner vs. a DoS action in the same module file), Msf::Post adds the session-interaction mixin covered next.

Post modules and the session mixin

Post-exploitation modules are a distinct case worth calling out on their own, because they don’t target a network service, they target an already-established session. lib/msf/core/post_mixin.rb is the piece that makes a post module aware of SESSION as a first-class datastore option and gives it the API to read from and write to that session (file operations, registry access, process listing) regardless of whether the session underneath is a full meterpreter or a plain command shell. Part 7 of this series, once it’s unblocked, will run real post modules against the lab and lean directly on this mixin.

Why this matters more than it looks

None of this is unique to Metasploit in the abstract, mixins-over-inheritance is a well-worn Ruby pattern, and a typed datastore is just a config object with manners. What’s worth taking away is how consistently the pattern is applied. Every one of the roughly 5,000 modules in this framework, across 23 years of contributions from people who never talked to each other, presents the same info, options, datastore, and run surface, because that surface is enforced at the base class rather than by convention. That consistency is exactly what makes the RPC layer in Part 11 and the MCP tools in Part 12 possible without a mountain of per-module special-casing: msf_module_execute can accept a flat options hash for literally any of the 5,000 modules because every one of them, without exception, inherited the same DataStore mixin this post just walked through.

Part 3 moves one layer down, into what actually gets generated once those options are set: payload architecture, msfvenom, and what “encoding” a payload does and doesn’t buy you in 2026.