WEBVTT

00:00:00.140 --> 00:00:05.330
Using tools like MSFvenom to build your stager is very convenient, but as you

00:00:05.330 --> 00:00:09.350
saw previously, there are many things that you need to fine tune to improve

00:00:09.350 --> 00:00:13.590
your tools and to make them more evasive. Writing your own stager is actually

00:00:13.590 --> 00:00:17.690
pretty simple and avoids having to use encoders, as well as the issues

00:00:17.690 --> 00:00:22.410
revolving around high entropy in read/write execute memory sections inside

00:00:22.410 --> 00:00:23.660
the executable file.

00:00:23.670 --> 00:00:26.850
It also gives you more control and lets you customize it.

00:00:26.930 --> 00:00:27.780
For example,

00:00:27.790 --> 00:00:30.230
you could craft a stager, which attempts to connect

00:00:30.240 --> 00:00:33.140
back over HTTP. And in case it fails,

00:00:33.150 --> 00:00:37.010
you can make it fall back to a different communication channel such as DNS

00:00:37.010 --> 00:00:42.900
tunneling, for example. In this demo, we will write our own custom stager, and

00:00:42.900 --> 00:00:47.170
we will upload different flavors of it to VirusTotal to better understand how

00:00:47.170 --> 00:00:49.620
and why the files are detected on disk.

00:00:49.760 --> 00:00:54.640
Finally, we will bypass the antivirus by writing a Meterpreter stager to disk,

00:00:54.650 --> 00:00:58.150
which doesn't get quarantined when scanned by Windows Defender.

00:00:59.440 --> 00:01:02.590
I have already created a new project in Visual Studio.

00:01:02.600 --> 00:01:06.720
The shell code runner we wrote previously was written in C.

00:01:06.780 --> 00:01:10.890
This time, I chose to write the custom stager in C++.

00:01:10.910 --> 00:01:13.130
Before we dive into the actual code,

00:01:13.140 --> 00:01:16.130
let's take time to discuss what we're trying to achieve.

00:01:16.140 --> 00:01:20.510
You may recall that all our shell code runner does is allocate some memory

00:01:20.520 --> 00:01:24.610
in which it copies the stager, which was generated by MSFvenom and

00:01:24.610 --> 00:01:29.340
executes it. All the stager does is load the WinINet library, as well as

00:01:29.340 --> 00:01:33.890
locate the functions, which it needs to communicate over HTTP, write the

00:01:33.890 --> 00:01:37.900
response containing the payload into a memory buffer, and execute it.

00:01:37.910 --> 00:01:42.060
Therefore, all we need to do to write a stager is to allocate a memory

00:01:42.060 --> 00:01:46.520
buffer large enough to hold the payload and perform an HTTP request to the

00:01:46.520 --> 00:01:48.700
handler, which will send back the payload,

00:01:48.710 --> 00:01:49.940
which we can execute.

00:01:50.070 --> 00:01:51.710
If we now take a look at the code,

00:01:51.720 --> 00:01:54.970
you will notice that inside the main function, I allocate a

00:01:54.970 --> 00:01:58.390
memory buffer large enough to hold the payload with read/write

00:01:58.390 --> 00:02:01.770
execute memory permissions so that we can write the payload into

00:02:01.770 --> 00:02:03.460
the memory and execute it.

00:02:03.740 --> 00:02:08.370
I then declare some variables, which will hold the TCP port, IP address,

00:02:08.370 --> 00:02:13.890
and URI, as well as the user agent and HTTP method I want to use. All

00:02:13.890 --> 00:02:17.600
these parameters are then passed into a wrapper function, which uses

00:02:17.600 --> 00:02:23.730
them to call InternetOpen, InternetConnect, and HttpOpenRequest APIs, as

00:02:23.730 --> 00:02:29.200
well as the HttpSendRequest and InternetReadFile APIs exposed by the

00:02:29.200 --> 00:02:30.370
WinINet module.

00:02:30.620 --> 00:02:33.630
Once the payload has been downloaded and written into the memory

00:02:33.630 --> 00:02:36.930
buffer we provided to the wrapper function, we cast the memory

00:02:36.930 --> 00:02:39.380
buffer to a function pointer and execute it.

00:02:39.590 --> 00:02:42.420
If we execute this code, we are able to obtain a

00:02:42.420 --> 00:02:44.410
fully functional Meterpreter shell.

00:02:44.550 --> 00:02:48.160
Great, now let's upload our custom stager to VirusTotal.

00:02:48.400 --> 00:02:51.660
This time, we only get flagged by three antivirus scanners,

00:02:51.670 --> 00:02:55.150
which is an improvement over our encoded stager embedded inside

00:02:55.150 --> 00:02:59.550
the 7‑Zip executable file using the exe‑only format, which was

00:02:59.550 --> 00:03:01.120
flagged by five scanners.

00:03:01.240 --> 00:03:04.750
Let's now try to improve our stager to make it even more evasive.

00:03:04.940 --> 00:03:09.300
Our stager is currently making use of the VirtualAlloc API. Having

00:03:09.300 --> 00:03:13.910
this frequently abused API referenced in our stages import address

00:03:13.910 --> 00:03:18.050
table will make our file look more dangerous to some classification

00:03:18.050 --> 00:03:20.450
models, which are used by antivirus.

00:03:20.620 --> 00:03:25.380
One way we can avoid this is to leverage the GetProcAddress API, which can

00:03:25.380 --> 00:03:29.960
be used to locate functions inside a loaded module at runtime. This will

00:03:29.960 --> 00:03:34.240
ensure that the linker will not add a reference to the API in our

00:03:34.240 --> 00:03:36.850
executable file's import address table.

00:03:37.170 --> 00:03:40.220
We can go a step further and use a completely different

00:03:40.230 --> 00:03:45.020
undocumented low‑level API inside NTDLL to perform the memory

00:03:45.020 --> 00:03:49.410
allocation in combination with the use of GetProcAddress. Although

00:03:49.410 --> 00:03:51.400
this does make the code more complex,

00:03:51.410 --> 00:03:54.360
we are able to get a pointer to a read/write execute

00:03:54.360 --> 00:03:56.490
memory section as we did previously,

00:03:56.570 --> 00:04:01.510
but this time we are using lesser‑known APIs and resolving them at runtime.

00:04:01.780 --> 00:04:04.040
If we upload this sample to VirusTotal,

00:04:04.050 --> 00:04:07.880
you will notice that we are now being flagged by a single antivirus.

00:04:08.240 --> 00:04:12.460
Let's now discuss how we can get down to 0 detections on VirusTotal.

00:04:12.540 --> 00:04:13.230
Currently,

00:04:13.240 --> 00:04:17.380
our stager will have references to the WinINet APIs, which we use to

00:04:17.380 --> 00:04:21.589
communicate over HTTP. We can apply the same technique we did

00:04:21.589 --> 00:04:25.660
previously using the GetProcAddress API to resolve these functions

00:04:25.660 --> 00:04:29.540
at runtime to avoid having them show up in our import address table.

00:04:29.800 --> 00:04:30.840
This time, however,

00:04:30.850 --> 00:04:35.760
we will need to load the WinINet library at runtime using LoadLibraryA.

00:04:35.770 --> 00:04:39.000
Since unlike the NTDLL module we used previously,

00:04:39.010 --> 00:04:42.140
it is not loaded automatically inside all processes.

00:04:42.240 --> 00:04:46.340
If we recompile our modified stager and upload it to VirusTotal,

00:04:46.350 --> 00:04:48.810
you will notice that we are now successfully evading

00:04:48.810 --> 00:04:51.700
detection from all the scanners on VirusTotal.

00:04:51.780 --> 00:04:54.540
If we take a look at our file using pestudio,

00:04:54.550 --> 00:04:56.860
you will notice that there are no mentions of the

00:04:56.860 --> 00:04:59.760
WinINet APIs in the import address table.

00:04:59.940 --> 00:05:04.130
We can now scan our stager using Windows Defender. And sure enough,

00:05:04.140 --> 00:05:06.390
it does not get flagged by the antivirus.

00:05:06.650 --> 00:05:09.450
Let's now attempt to run the stager to check if we are also

00:05:09.450 --> 00:05:12.340
able to evade runtime heuristic analysis.

00:05:12.470 --> 00:05:16.860
As you can see, this time our custom stager was blocked by Defender.
