Join GitHub today
Remove OpenSSL from Windows Meterp, packet header changes, and TLV packet encryption #8625
Associated Metasploit Payloads PR: rapid7/metasploit-payloads#211
Those payloads will need to be built and tested alongside this if anything is going to work.
This PR supercedes the previous MSF side PR: #8570, which has now been closed.
This all began after some of the powers that be (and me too!) decided to remove the OpenSSL library from Windows Meterpreter. As a result of that discussion, @busterb went ahead and did the initial OpenSSL removal work in rapid7/metasploit-payloads#205. As shown in the PR comments, various community members expressed a perfectly valid concern about now having encryption on
This is another large set of changes, and comes with a large PR description. You'll have to fortive me. I'll do my best to break up the boring bits with amusing memes, gifs and useful tidbits of information.
High-level rationale goes like this:
It turned out that we had a few other needs that were in some way related, and given that we don't like to constantly break binary compatibility with old payloads, it was a good idea to be "in for a penny, in for a pound" and break everything at once so we didn't have to do it again any time soon. The primary need was to modify the packet header so that it included the Session GUID that has been added recently. This would mean that we would have the ability to associate a packet with a session handler, regardless of the transport that the packet came from, and for us to handle the routing of this packet to the relevant session handler that contains the per-session encryption key. This opens up a world of fun things that we can do down the track (more to come on that in future PRs, but trust me, it's going to be funzies!).
Given that we were going to make a point of implementing encryption for
For various reasons (including not updating all the Meterpreters, and needing to have "raw" packets to begin with), the packet needed to be modified to indicate whether or not the packet contained encrypted data. Combine that with the session GUID requirement, and we had the need to modify the packet header... resulting in the breaking of binary backwards compatibility.
Given that we were breaking back compat, I felt now was the time to also fix up the obscure XOR byte swizzling thing we were doing as a result of attempting to use UINT values on Windows. At this point, all the XOR stuff is done assuming a straight 4-byte array, and the Meterpreter code has been updated to reflect this as well.
Packet header changes
Prior to this PR, the packet header looked like this:
After this PR, the packet header looks like this:
There is a 16 byte initialisation vector that is used to being the AES256 decryption process. After those 16 bytes is the full encrypted TLV payload.
In short, the packet size has increased from
As mentioned before, packets are now encrypted with AES256 in windows. But not immediately. The session needs to be established, and MSF will negotiate keys with Meterpreter on the fly. This is done for a few reasons:
This then begs the question: how do we exchange keys in a "secure" manner. At the end of this PR I'll post a rationale behind the approach that we're taking, particularly in an effort to address the "OMG THIS CAN BE MITM'd" crowd. The following shows the "handshake" process:
There are a couple of cases that needed to be considered when it came to packet encryption:
These cases have been considered and coded for.
I am sorry. I am so, so sorry for whoever has to do this. But, sometimes work sucks! This is one of those times.
These verification steps should be done for each of the Meterpreters that exist (yeah.. I know, right?), and for each transport:
For non-HTTPS payloads on Windows:
ERMAGHERD!! MAN IN THE MIDDLE!!
The most common question that has come up so far, and probably the most common that will come up down the track, is:
I'm glad you asked. At leaset, I think I am.
The Short Answer
Yes! but this is no different to before ripping out OpenSSL.
If you don't believe me, read the next answer.
The Long Answer
There are two types of MITM attacks that should be considered if we're going to be completely thorough: active MITM and passive MITM.
The risk of active MITM is no different to how it was before. Meterpreter over TCP did nothing to validate that the
What can we do about it? Actually not a great deal. In order to avoid active MITM threats, we need a way of validation that the "thing on the other end" is what we expected, and validating that in the context of Meterp that was just brought to life via an exploit in an unknown network isn't exactly easy. It's fair to assume that the network is hostile, and that any means of validation we have isn't reliable. In the case of HTTPS connections, certificates are validated against a certificate store that references a hierarchy of authorities that can be verified. We just don't have this capability. The closest thing we have is baking a certificate of some kind into Meterpreter and validating the keys that come from MSF against that cert. This is again moot though, because if we have Active MITM going on, then that certificate can be replaced on the wire with a malicious one, and we're back to where we started.
It's fair to say that if you are currently being actively MITM'd, you have literally zero points of trust. You can choose to trust nothing, and shut yourself down, or you can do what you've always done, and accept the session and carry on with the assessment.
This is a different beast, as it doesn't muck with the traffic. The concern here is that the packets captured can be analysed and decrypted later on. Assuming that we're not also being Actively MITM'd we're in a position to prevent the pcap from being decrypted, as we've got the equivalent of HTTPS with PFS enabled. The only differences are:
For someone to decrypt the traffic, they need to get hold of the AES keys. In order to get hold of the AES keys, they need to be able to get access to private key that was generated by MSF (again, we're assuming no active MITM where keys can be switched). For an attacker to get access to the private RSA key, the attacker's machine must be compromised. However, given that the attacker's machine does not store the RSA key pair for any longer than the duration of the request, compromsing the attacker's machine would be useless as the key is no longer there to be found. In the case of normal HTTPS, PSF is supported by generating ephemeral keys that are signed by the RSA private key associated with the HTTPS certificate. If the HTTPS certificate is later accessed, the private key can not be used to decrypt the traffic, as it's public key not the one that was used to encrypt it in the first place. We're doing the same thing, just without signing the key.
I honestly feel that the risk of passive MITM resulting in the compromise of data going across the wire is as small as it can be.
As always, props to the crew behind the scenes that helped with this (even if it was just the thought process), including @busterb and @sempervictus. Thanks in advance to those people who get involved with the discussion and who help get this over the line.
Let the discussion commence!
I know, I lied about the memes and gifs!
Edit 28th June
referenced this pull request
Jun 27, 2017
Great question @justinsteven. Perhaps! But perhaps not. Might be worth having a pow-wow about this in an open forum like IRC or something to see whether this is a concern. If it is, it's also a concern for unencrypted packets, and for any other packet we're sending, ever. We should definitely discuss and consider the negatives.
Is it though? e.g.
@OJ: Working through it, had to convert named pipes to this, found out x64 doesnt like the HANDLE vs SOCKET piece :). Will get back with test results in a bit.
One note on active MITM: we can encrypt symmetric keys in stage1 by using rc4 in stage0 such as to prevent acquisition on the wire. If stage0 is delivered out of band, or uses contextual data (hostname, domain name, a low-precision time value) to decrypt stage1, we should be able to significantly increase our posture against this vector.
Basically stage0(shellcode which opens socket, recv, decrypt_in_place(via context key), load into memory) -> stage1(bring key and establish symmetric context, renegotiate under that context if needed). Migrations could bring the original key with them i suppose, or in the event of using context data for this, simply pull that ctx again (the time based approach could be a problem here, unless our stage handler keeps incrementing its clock too in order to maintain relative deltas which are acceptable).
Manual memory analysis will catch this approach, but i think we can rule out autonomous defenses currently (or in the near future) being able to do so.
Far as the "keep a certificate chain" piece, we may be able to use a simplified version of asymetric relationships to check a pair of small tokens instead of going through the rigmarole of a cert chain...
@OJ: migration from 32->64 is hosed. 32->32 works fine.
Attempting to tab-complete compatible sessions results in:
Not sure if this happens upstream, lemme confirm this one before wasting your time and sanity
Interesting! I'll have to review that. I thought I'd tested that case. Thanks!…
On Jun 28, 2017 08:36, "RageLtMan" ***@***.***> wrote: @OJ <https://github.com/oj>: migration from 32->64 is hosed. 32->32 works fine. — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <#8625 (comment)>, or mute the thread <https://github.com/notifications/unsubscribe-auth/AABw4PXzqsN0wsUAw-w05Ul5zLOscFU5ks5sIYPvgaJpZM4OGRF8> .
I can confirm that this is indeed a problem, but has nothing to do with this PR. From my testing on master:
Investigating further, but as part of another PR, not this one :)
I remove my concerns regarding stability of this code. Turns out my payloads repo didn't keep the commented out version of unhooking call from jefftangs PR. That was causing my migration failures. So far with the comments back in place I'm only seeing a ~10% fail rate. Still too high for master/pro, but rational. I'm also cleaning out old named pipes code as I adopt OJs current work from the packet pivot branch, which seems to be stabilizing things more as I go. I need to do proof passes before official approval, but for all purposes of framework, I confirm this works
Also @bcook-r7 as far as mettle goes, the main area of pain is the packet flushing code which analyses TLVs on the fly. That's going to have to change in some way given that we'll have encryption in place so we'll need to do something similar to what I'm doing, which is a bit of a hack. Can we tag team the work on that please?
Quick progress report so this doesn't lose momentum. I'm about 50% through with the mettle bits (had an uninterrupted 3 hours on it yesterday, so I think the rest should come this weekend). I started updating the payloads branch a little too, because it has gotten a little convoluted with the merges. Once those bits are complete, we should be good to go. This is going to disable -aggregator for a while as we rethink how it will work.
As I was poking, I noticed that the xor patterns are really easy to identify due the large number of zeros in the first packet. I'm contemplating augmenting the xor obfuscation with an rng, using the xor instead as a seed.
@bcook-r7 I like that idea. Yes the first packet does indeed stand out given the NULL guid. The only concern would be making it consistent across platforms. We could easily solve that with a custom implementation though, right? It's not like it has to be cryptographically secure; it's just used for obfuscation.
pushed a commit
this pull request
Aug 21, 2017
Aug 21, 2017
1 check failed
pushed a commit
this pull request
Aug 21, 2017
Transport-level encryption provided by OpenSSL has been replaced in some Meterpreters with application-level encryption provided per-message instead. This reduces the size of Windows Meterpreter in particular, while providing an encryption mechanism that is easier to implement in other Meterpreter implementations as well.