Skip to content
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

Add linux hidden_modules plugin #1283

Merged
merged 19 commits into from
Oct 30, 2024

Conversation

gcmoreira
Copy link
Contributor

@gcmoreira gcmoreira commented Oct 1, 2024

This PR introduces a new kernel module scanning technique that is significantly faster and more efficient than the traditional Volatility2 plugin. It uses less memory and I/O, while also catching advanced threats missed by the earlier method.

From kernels 4.2 (93c2e10) struct module allocation are aligned to the L1 cache line size. For i386, amd64, and arm64 architectures, this is usually 64 bytes (or 128 bytes, which still works). However, in x86 this can be changed in the Linux kernel configuration via CONFIG_X86_L1_CACHE_SHIFT. For ARM64 this valued is fixed to 64 bytes. The alignment can also be obtained from the DWARF info i.e. DW_AT_alignment<64>. Unfortunately dwarf2json doesn't support this feature yet.
In kernels < 4.2, alignment attributes are absent in the struct module, meaning alignment cannot be guaranteed. Therefore, for older kernels, it's better to use the traditional scan technique.

It checks whether Linux kernel module addresses are aligned to 64 bytes. If not, it falls back to 1-byte alignment and notifies the user of the adjustment.

Demos

The following is an example of a machine infected with the Reptile rootkit. The machine has 2048MB of RAM.

$ time python3 ./vol.py \
    -r pretty \
    -f ../ubuntu180464bit_4.15.0-213-generic_reptile_infected.core \
    linux.hidden_modules
Volatility 3 Framework 2.10.0      
  |        Address |           Name
* | 0xffffc094f400 | reptile_module

real    0m16.501s
user    0m15.378s
sys     0m0.851s

Here is a Kovid rootkit sample provided by @Abyss-W4tcher :

$ time python3 ./vol.py \
    -r pretty \
    -f ../Ubuntu-jammy_5.15.0-87-generic_kovid.lime \
    linux.hidden_modules
Volatility 3 Framework 2.10.0
  |        Address |  Name
* | 0xffffc09ed4c0 | kovid

real    0m16.932s
user    0m16.187s
sys     0m0.739s

We also observed that advanced malware can modify certain values that are invalid when the module is loaded but have no impact once it's running. This technique enables sophisticated threats to evade detection from existing memory forensics methods.

The following is a slightly modified version of the Kovid rootkit, detectable only through the fast scan method.

$ time python3 ./vol.py \
    -r pretty \
    -f ../dump_ubuntu20.04_5.15.0-87-generic_kovid_99.core \
    linux.hidden_modules
  |        Address |  Name
* | 0xffffc07f8600 | kovid

real    0m18.233s
user    0m17.281s
sys     0m0.676s

@Abyss-W4tcher
Copy link
Contributor

Hello, awesome submission, this feature allows to uncover deeply hidden rootkits which is extremely valuable.

Having worked on this problematic for the 2023 contest, I ran your plugin against an infected sample, but unfortunately got no results. Would you be interested in acquiring this sample ("kovid" rootkit), to check if this might come from a scanning or restrictive constraint issue ?

Thanks again !

@gcmoreira
Copy link
Contributor Author

@Abyss-W4tcher interesting.. sure, could you share that with me?

@Abyss-W4tcher
Copy link
Contributor

A first restricting point might be the MODULE_STATE_LIVE assertion, whereas some rootkits modify this attribute to act "unloaded" :

@gcmoreira
Copy link
Contributor Author

Thanks @Abyss-W4tcher .. easy fix with no impact on performance.

$ time python3 ./vol.py \
	-f ../Ubuntu-jammy_5.15.0-87-generic_kovid.lime \
	linux.hidden_modules
Volatility 3 Framework 2.10.0
Address Name

0xffffc09ed4c0  kovid

real    1m12.489s
user    1m11.930s
sys     0m0.555s

$ time python3 ./vol.py \
	-f ../Ubuntu-jammy_5.15.0-87-generic_kovid.lime \
	linux.hidden_modules \
	--fast
Volatility 3 Framework 2.10.0
Address Name

0xffffc09ed4c0  kovid

real    0m15.385s
user    0m14.835s
sys     0m0.543s

Additionally, classmethod helpers were added, and docstrings were enhanced for improved usability and clarity.
…fast scan method for even better performance, using the mkobj.mod self referential validation used in module.is_valid() as pre-filter

Removed the --heuristic-mode and the module.states validation, since the self referential check is enough by itself
@gcmoreira
Copy link
Contributor Author

@Abyss-W4tcher added your suggestion. It's running even faster now. Thanks

@gcmoreira
Copy link
Contributor Author

Since the fast scan method is performing exceptionally well and can detect threats that the traditional method misses, I think we should rename it, any ideas @Abyss-W4tcher @ikelos ?

Another option would be to move it to a new plugin to ensure users don't overlook it, as it might go unnoticed if it's just an argument.

@Abyss-W4tcher
Copy link
Contributor

Abyss-W4tcher commented Oct 3, 2024

On my take, adding multiple plugins "doing the same thing" might only confuse users.

Maybe removing the vol2 method and implicitely relying on fast might be better (forensic wise) :

  • l1 alignement OK : do the "fast" method
  • l1 alignement NOK : do the "fast" method but instead of aligning to 64 bytes, scan byte per byte / every 8 bytes (pointer size)

I have some older infected samples (~kovid like) on which I can test this idea, I'll keep you informed if it is reliable.

@gcmoreira
Copy link
Contributor Author

maybe better going backwards 64, 32, 16, 8, 1 ... and keeping the already tested address in a set

@gcmoreira
Copy link
Contributor Author

gcmoreira commented Oct 3, 2024

maybe better going backwards 64, 32, 16, 8, 1 ... and keeping the already tested address in a set

hm actually, if we are going to test each byte, it doesn't make sense to test the others. If _validate_alignment_patterns() fails with 64 bytes, we should fallback to scan with 1 byte alignment and that's it.

If you see the following metrics, it still performs really good.

Forcing 8 bytes alignment

$ time ./vol.py -f ../dump_ubuntu20.04_5.15.0-87-generic_kovid_99.core  linux.hidden_modules --fast 
Volatility 3 Framework 2.10.0               
Address Name

0xffffc07f8600  kovid

real    0m31.045s
user    0m29.886s
sys     0m0.820s

Forcing 1 byte alignment

$ time ./vol.py -f ../dump_ubuntu20.04_5.15.0-87-generic_kovid_99.core  linux.hidden_modules --fast 
Volatility 3 Framework 2.10.0
Address Name

0xffffc07f8600  kovid

real    2m34.798s
user    2m33.008s
sys     0m0.837s

The Vol2 method takes 1m9s but it doesn't find it

$ time ./vol.py -f ../dump_ubuntu20.04_5.15.0-87-generic_kovid_99.core  linux.hidden_modules
Volatility 3 Framework 2.10.0
Address Name


real    1m8.985s
user    1m7.025s
sys     0m0.852s

@Abyss-W4tcher
Copy link
Contributor

Abyss-W4tcher commented Oct 4, 2024

Looks great, so it doesn't try _validate_alignment_patterns() with 64, 32, 16, 8 and 1, but only 64, 8 and 1 ?

@gcmoreira
Copy link
Contributor Author

@Abyss-W4tcher only 1 byte and 8 bytes alignments. It was just to see how this would impact performance.

module_address_alignment = 1 # 8 # cls._get_module_address_alignment(context, vmlinux_module_name)

@Abyss-W4tcher
Copy link
Contributor

Ah yes, this looks like a good compromise for me, as it's always preferable to spend more time but still detect advanced threats in the end. Should the vol2 method be left as a reference ?

@gcmoreira gcmoreira marked this pull request as draft October 8, 2024 21:37
@atcuno
Copy link
Contributor

atcuno commented Oct 14, 2024

@gcmoreira @Abyss-W4tcher what is the status of this one? It seems high priority based on it blocking other PRs

…and fall back to a 1-byte alignment scan if addresses aren't aligned to the L1 cache size
@gcmoreira gcmoreira marked this pull request as ready for review October 16, 2024 04:23
@gcmoreira
Copy link
Contributor Author

Okay, I made the fast scan method the default. Removed the vol2 implementation and updated the plugin to fall back to 1-byte alignment scanning if addresses aren't aligned with the L1 cache size. Now it's much easier to use, with less code and significantly more powerful!

Thanks @Abyss-W4tcher and @atcuno for your help and suggestions.

@ikelos this is now ready for review

Copy link
Member

@ikelos ikelos left a comment

Choose a reason for hiding this comment

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

Generally pretty good, but a couple of points that have to be fixed before it goes live (even if I have to write new core methods to achieve it!). First up is using child_template rather than directly addressing the structure of the vol.members structure. Secondly shifting the hardcoded limits off to the constants file so they're a bit more obvious than being buried away in the middle of code somewhere.

Otherwise just some documentation other little nitpicks and it should be good to go. 5:)

Copy link
Member

@ikelos ikelos left a comment

Choose a reason for hiding this comment

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

Looks good, I'd like the change on the constants import, but it's not a show stopper. Just lemme know when you're happy for it to go in and we can put it in...

Copy link
Member

@ikelos ikelos left a comment

Choose a reason for hiding this comment

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

Sadly one of the other recent merges conflicts, but if you can get that sorted, this looks good to go straight in now. 5:)

@ikelos
Copy link
Member

ikelos commented Oct 30, 2024

Looks like it's just the cached_property change, so should be trivial to rebase/fix up.

@gcmoreira
Copy link
Contributor Author

Cool, it's ready to go. Thanks @ikelos

@ikelos ikelos merged commit 0ee0f44 into volatilityfoundation:develop Oct 30, 2024
12 checks passed
gcmoreira added a commit to gcmoreira/volatility3 that referenced this pull request Oct 30, 2024
ikelos added a commit that referenced this pull request Oct 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants