ZeroTotal: Msfvenom Calc
The quest for an undetectable calc payload on VirtusTotal
Last updated
The quest for an undetectable calc payload on VirtusTotal
Last updated
The motivation for this research is to demonstrate the fact that at the end of the day, a skilled attacker can overcome detection. Lets begin this series with our initial payload and results.
We make our shellcode with msfvenom. This is particularly a bad idea because these payloads should be thoroughly signatured.
We test our payload with runshc64.exe and get a working payload. Let see what VirusTotal says about this payload.
It's worth noting that in my experiments, using some of msfvenom's built in encoders was enough to return zero results from VirusTotal. But in the spirit of this research, lets aim to achieve that manually.
If we open up our binary in a hex editor, this is what we'll see:
Lets disassemble that.
Hmm..it looks like we should be able to scramble some of the opcodes around without affecting the functionality of the program. That might be enough to break some of the signatures.
Notice that the xor instruction is now on line f instead of line 11 where it was previously.
We run our shellcode to ensure that it still works.
Lets try VirusTotal again and see if anything changed.
It looks like that single opcode swap reduced our detection from 20 to 14! That’s a 20% reduction with one change.
Before we start getting too complicated, we can try to adding some nops in a couple places (like the very beginning to see if we get positive effects).
And it looks like we've reduced our detectability by two more.
Lets move a little bit past this primitive workflow and open this binary up in BinaryNinja for a more useful interface.
We open up our payload and see the following:
We tell BinaryNinja to interpret this binary dump as a function for clarity, and we get a nice flow graph of our payload.
We change "test rax, rax":
To "cmp eax, 0x0".
Note: This change is not perfectly equivalent, but it doesn't break the payload.
We can change "mov rdx, 0x1" to "mov edx, 0x1". We can now fill the newly empty space with garbage instructions.
And now it looks like this:
Lastly, we can make some changes to how we clear variables off the stack. This should help get rid of the things we don't need, and confuse automated analysis because it will break how a function is identified in some cases.
Let see what VirusTotal says about our payload now.
And it looks like we're down to two contenders. Let see if there's something we can do about that.
If we take another look at our payload in BinaryNinja we notice some hardcoded constants:
After doing some research into how msfvenom generates payload, we know that it does not pass the API call by name, but rather by a hash value. It's super likely that one of those values is what's getting us flagged.
Earlier, we turned this instruction:
Into this:
But we can rewrite that more succinctly to achieve 0x1 in edx and generate some empty space for us to play with.
So we turn this:
Into this:
[Intentionally left blank]
We check that our payload still works.
Lets see what VirusTotal says about our payload now.
In this write up, we saw how we were able to bypass all VirusTotal checks without encrypting, encoding, or wrapping our payload in any way. This demonstrates the value that that low-level binary manipulations bring to Red Teams and validates how important these skills are in any engagement.
However, this is not the end of the battle for the Red Team. More advanced analysis by EDR that leverage machine learning, heuristic analysis, or executable emulation are still apt to detect our payload. This is but one skillset and one data point to draw from when analyzing a target's attack surface.
Astute readers might notice that my payload size changed throughout this write up. I ended up injecting a few NOP bytes to give myself space to work and write the opcodes that got use from 2 to 0 detections in Part 4.