Using tools like MSFvenom to build your stager is very convenient, but as you
saw previously, there are many things that you need to fine tune to improve
your tools and to make them more evasive. Writing your own stager is actually
pretty simple and avoids having to use encoders, as well as the issues
revolving around high entropy in read/write execute memory sections inside
the executable file.
It also gives you more control and lets you customize it.
For example,
you could craft a stager, which attempts to connect
back over HTTP. And in case it fails,
you can make it fall back to a different communication channel such as DNS
tunneling, for example. In this demo, we will write our own custom stager, and
we will upload different flavors of it to VirusTotal to better understand how
and why the files are detected on disk.
Finally, we will bypass the antivirus by writing a Meterpreter stager to disk,
which doesn't get quarantined when scanned by Windows Defender.
I have already created a new project in Visual Studio.
The shell code runner we wrote previously was written in C.
This time, I chose to write the custom stager in C++.
Before we dive into the actual code,
let's take time to discuss what we're trying to achieve.
You may recall that all our shell code runner does is allocate some memory
in which it copies the stager, which was generated by MSFvenom and
executes it. All the stager does is load the WinINet library, as well as
locate the functions, which it needs to communicate over HTTP, write the
response containing the payload into a memory buffer, and execute it.
Therefore, all we need to do to write a stager is to allocate a memory
buffer large enough to hold the payload and perform an HTTP request to the
handler, which will send back the payload,
which we can execute.
If we now take a look at the code,
you will notice that inside the main function, I allocate a
memory buffer large enough to hold the payload with read/write
execute memory permissions so that we can write the payload into
the memory and execute it.
I then declare some variables, which will hold the TCP port, IP address,
and URI, as well as the user agent and HTTP method I want to use. All
these parameters are then passed into a wrapper function, which uses
them to call InternetOpen, InternetConnect, and HttpOpenRequest APIs, as
well as the HttpSendRequest and InternetReadFile APIs exposed by the
WinINet module.
Once the payload has been downloaded and written into the memory
buffer we provided to the wrapper function, we cast the memory
buffer to a function pointer and execute it.
If we execute this code, we are able to obtain a
fully functional Meterpreter shell.
Great, now let's upload our custom stager to VirusTotal.
This time, we only get flagged by three antivirus scanners,
which is an improvement over our encoded stager embedded inside
the 7‑Zip executable file using the exe‑only format, which was
flagged by five scanners.
Let's now try to improve our stager to make it even more evasive.
Our stager is currently making use of the VirtualAlloc API. Having
this frequently abused API referenced in our stages import address
table will make our file look more dangerous to some classification
models, which are used by antivirus.
One way we can avoid this is to leverage the GetProcAddress API, which can
be used to locate functions inside a loaded module at runtime. This will
ensure that the linker will not add a reference to the API in our
executable file's import address table.
We can go a step further and use a completely different
undocumented low‑level API inside NTDLL to perform the memory
allocation in combination with the use of GetProcAddress. Although
this does make the code more complex,
we are able to get a pointer to a read/write execute
memory section as we did previously,
but this time we are using lesser‑known APIs and resolving them at runtime.
If we upload this sample to VirusTotal,
you will notice that we are now being flagged by a single antivirus.
Let's now discuss how we can get down to 0 detections on VirusTotal.
Currently,
our stager will have references to the WinINet APIs, which we use to
communicate over HTTP. We can apply the same technique we did
previously using the GetProcAddress API to resolve these functions
at runtime to avoid having them show up in our import address table.
This time, however,
we will need to load the WinINet library at runtime using LoadLibraryA.
Since unlike the NTDLL module we used previously,
it is not loaded automatically inside all processes.
If we recompile our modified stager and upload it to VirusTotal,
you will notice that we are now successfully evading
detection from all the scanners on VirusTotal.
If we take a look at our file using pestudio,
you will notice that there are no mentions of the
WinINet APIs in the import address table.
We can now scan our stager using Windows Defender. And sure enough,
it does not get flagged by the antivirus.
Let's now attempt to run the stager to check if we are also
able to evade runtime heuristic analysis.
As you can see, this time our custom stager was blocked by Defender.