I’ve had some folks on YouTube ask me to cover what I kind of view as a simple concept: Rust modules. I feel like the Rust documentation covers this fairly well, but maybe there are some conceptual understanding about building codebases that go over people’s heads sometimes.

Conceptually, modules are similar to classes in object-oriented languages, in that you can use them to define functionality and data types that are visible or invisible to outside callers.

Note: Classes in OOP languages are more akin to structs in Rust, but I’m trying to walk on familiar ground here so I’ll leave that alone.

The way I use modules, and the way I think they should be used, is to separate functionality and purpose in Rust code. I typically take a file-based approach to modules: one file, one module. The reason I do this is because I use modules to group functionality together. So in the Rust malware I’m writing, I have all of my C2 code in one module, all of my core functionality in another module, all of the registry operations in another file, etc.

src % ls -R
c2	func	main.rs	reg

./c2:
mod.rs

./func:
mod.rs

./reg:
mod.rs

I could absolutely group everything into a single file. At the end of the day, the compiler tosses it all into a single executable binary anyways, so what’s the point?

Grouping functionality makes it easier to add functionality to your code. I know that if I want to add a function that manipulates the registry, I can go to ./src/reg/mod.rs and add the functionality there, versus running a search over a beefy main.rs file with all of my functionality. It also makes it easier to debug for the same reasons. The compiler will tell me exactly what file the compile-time error is in, and if there is a run-time bug, I know exactly where to find it. Keeping your files small, to an extent, makes it much easier to find problems, and modules are a great way to subdivide your code into smaller chunks.

Modules also control visibility. In my C2 functionality, I recently added XOR encoding so that communications between the C2 and the victim are encoded over the wire. That xor encryption function doesn’t need to be visible to the registry functionality, for example. If I decided it did (if I wanted to xor-encode a registry value or something) I could make a new module called encryption and make the xor encryption function a public export of that function that can be called in other modules.

This visibility principle can be extended even further. Let’s say I wanted to add to the func module (standing for functionality or essentially higher level functions that are responsible for actually doing things on the victim) some functions that are focused on information stealing. It may have a function steal_crypto and steal_registry. Those two functions do not need to be visible to the registry module (because the registry functionality aren’t going to need the data returned by steal_crypto and steal_registry ) but the registry module has functions in it that will need to be visible to the infostealer module. I can control that visibility by making the infostealer module a sub-module of the func module and importing functionality from the reg module where I need it. The file system would then look like this:

src % ls -R
c2	func	main.rs	reg

./c2:
mod.rs

./func:
mod.rs
infostealer.rs

./reg:
mod.rs

and the func module (defined in ./func/mod.rs would look like this:

mod infostealer;

...

fn steal_data() -> Result<String, String> {
	...
	let crypto = infostealer::steal_crypto();
	let registry_stuff = infostealer::steal_registry();
	...
}

This visibility just makes the code a lot easier to deal with, honestly.

If you’re still confused about modules, I’d challenge you to just go work with them a bit. Think about how you’d want to divide a codebase and build something just big enough to start dividing functionality. Once you use it a bit, it really just becomes intuitive.