rapid7 / mettle

This is an implementation of a native-code Meterpreter, designed for portability, embeddability, and low resource utilization.
415 stars 122 forks source link

Oxidize mettle? #209

Open sempervictus opened 3 years ago

sempervictus commented 3 years ago

There aren't a lot of us out there who can write in C, and fewer still who can screw with the complex ecosystem in here effectively. This results in reduced community contribution because even skilled contributors have to block off significant slices of time to "get into" the work, unless its something they do often (and the commit log indicates no such person exists).

The reason we moved away from the old merged codebase is because the original POSIX meterpreter was an unsupported (mostly) codebase, relying on RTLD tricks from a version of Bionic which couldn't get pthreads right by the end and devolved into a tire fire. Mettle brought us a stable stack to build and run - MUSL is carried with the payload to give us our own ABI-compatible libc on-target. Nowadays, the modality implemented by @acammack-r7 and @busterb for the libc component is common in the Rust toolchain.

What if we rewrote Mettle in Rust? It would greatly simplify the dependency tracking which is likely going to be the achilles heel of the current codebase with @busterb having moved on from R7, it produces static code on the MUSL toolchain just like Mettle does now, and it has all of the low-level interfaces we get with raw C, with with semantics and idioms much more acceptable by the current generation of hackers (face it OGs, we really are old). Rust should bring new interest from the community, get more developers on-board from their ecosystem, and generally make rapid post-exp development a lot faster. Moreover, the feature-based compilation targets would allow us to have massive entropy in our payloads - blue team can scan memory for a byte sequence of op codes known to be present in extension X all they want - if the user didn't want that function (such as file write for forensic integrity or anything that can even alter atime), it's not there, and defenders can pound sand looking for it. Lastly, there's the rich and rapidly evolving Rust ecosystem to which we would immediately have access for all sorts of on-target data and functional tasks.

My Rust-fu is not great yet, but picking up as i go. I'm never the first one to the party though around these parts so i'm guessing a few of the folks reading this might already be way ahead of me. The trickiest parts i can forsee right now is handling the in-memory libload from a session stream and picking up execution from stage0. The former might require some cajoling of the compiler, hopefully not, the latter i figure that it can be done because Redox boots off an unsafe raw context... Having a TLV-parsing crate would also be very useful for the defensive world - Suricata and such could directly include it. Lastly, if we get it right, we could potentially reunify meterpreter altogether and reduce the amount of parity work required in two codebases to adopt common features.

Who owns this repo nowadays? Any thoughts from maintainers and community folks? Ping @acammack-r7 @OJ @busterb @timwr @zeroSteiner (@smcintyre-r7)

smcintyre-r7 commented 3 years ago

Undertaking a massive project like this to encourage new contributors is a real tough sell for me. I'm interested in the possibility of unifying the codebases though, that would be extremely helpful once completed.

I don't know about owning it per se but I've been fixing bugs as I've been finding them and keeping an eye on the repo in general.

timwr commented 3 years ago

I'd love to see a rust version of mettle, I suspect you can use the elf2bin code in this repository to load any static elf file, it might be worth testing that with a rust hello world. However I'm not sure we should replace mettle with rust, as arguably there are more developers that know C than rust, but I don't see why we can't have both.

sempervictus commented 3 years ago

Good god you guys are quick. I agree that we shouldn't just drop Mettle or Meterp and start something new, and i think Tim's right in the # of coders department overall, though i'd argue that C devs are in so much demand and dwindling in number that getting good one's in here is hard. Then there's the fact that we're all used to how things are and it is no small feat to change our direction and thought patterns. The more rational approach, IMO, would be to build rusty-mettle to aim at feature parity with mettle, and once we've achieved that in both the functionality and diversity of compile-targets, we could talk about replacing one with the other. An initial cross-reference between our current triples and full-fledged (supported) Rust toolchain targets should give us a sense of what the gap in capabilities would be there.

How about this: since i'm learning Rust anyway, and apparently have some form of developer mental disorder, i'm going to start fooling around with payload components and try to put up a repo of dr moreau pig babies once i have something worth sharing. My very first effort is just a TTY-shell over NoiseSocket (Rust Streams are not Ruby Sockets - at least that's clear to me so far), but may be exactly what we need to test the elf2bin bit. I'll downgrade it from noise to plaintext so make it MSF-simple-session compatible, and we can fool around with that.

Far as unifying the codebase - my recollection is that the difficulty in moving ye olde Meterp to Mettle was that the MSFT-specific extensions required a fair deal of winapi at build time. Since @OJ was able to get meterp to compile in Linux, we should be able to get all the requisite includes/headers the same way that he did, so in theory we could port the windows extensions to mettle (and overhaul the process injection/migration code which nobody's touched much since written - a big flag for detection)

acammack-r7 commented 3 years ago

Rust is ok for some stuff, but I don't know if I'd do an implant with it. How well does it handle older kernel versions and missing minor syscalls? That's the big issue we had with trying use libuv. Since it was server-oriented it didn't do well on anything other than recent x64 server type boxes. elf2bin should work on any static PIE ELF, though I don't know useful it would be in practice: the smallest Rust Hello, World!static PIE I can get is 330K and has 483 relocations. That's already about a third of the size of the complete mettle payload.

The biggest hurdle we saw in moving to mettle as our Windows meterpreter is just how different Windows behaves than Linux/BSD. Even in a language/runtime like Rust that tries to abstract a lot of that away, most of the things that we are interested in would have to be written separately for *nix and Windows. Even the existing mingw builds just sorta work.

sempervictus commented 3 years ago

@acammack-r7: Hard to argue with those numbers, killjoy :). Zero-cost abstractions are not so zero cost i guess. I'm also seeing as i go that "static" might be a misnomer in cases where Rust uses C code like SSL libs - even with the vendoring feature tricks and a musl target, it still wants the glibc that openssl was using during build time. Using purely native components seems to do away with this problem, which enforces better coding practices but also does make an already non-trivial task that much more interesting.

sempervictus commented 3 years ago

@acammack-r7: been digging into this a bit with other projects, and i think i see the three problems:

  1. musl build chain needs to be slimmed down like the mettle one
  2. OpenSSL is the enemy, either slim it down too or just screw that legacy nightmare and go RusTLS
  3. The rust runtime - formatting and such seems to eat space

I think that if we had our own slimmer build chain for musl, dropped or slimmed openssl, and if we want to get crazy to to avoid all the fmt! macros, we should get down to the <2MB metsrv level. The bulk of the space is the 1st two elements there, the formatting stuff is likely much more work due to dependencies in the Rust tier.

adfoster-r7 commented 3 years ago

formatting and such seems to eat space

If this is referring to the overhead of rust's standard library, I believe you can opt out of linking it by using #![no_std] https://doc.rust-lang.org/1.7.0/book/no-stdlib.html

First spotted this when I was looking at Rust OS dev here: https://os.phil-opp.com/freestanding-rust-binary/

sempervictus commented 3 years ago

Greetings @adfoster-r7, and thank you. My understanding regarding no_std is that it would significantly complicate dependency inclusion - we'd be rewriting a lot of traits to drop conveniences taken from stdlib. For the time being, i've found this to be a pretty reasonable approach on the nightly toolchain, but with xargo archived we might need to find some "more innovative" constructs to build multi-target libcs and mettle pieces. I ran across one yesterday, and like an ass did not bookmark or document it - will find it again i'm sure. Personally, i think it might be kind of fun to do a ground-up implementation for bare metal as the article suggests - use crates when we can, when they're safe for no_std and such, but write our own implementations as needed optimized for our uses. Should result in a payload-optimized binary/codebase and teach us all a thing or two about Rust in the process. Thoughts?

adfoster-r7 commented 3 years ago

I think it sounds like a cool project; I feel like rust could work well with the bring your own payloads RFC https://github.com/rapid7/metasploit-framework/discussions/14490