Monday, 14 February 2011

Driver and application intercommunication

One of the most fundamental principles of the ring protection model in modern CPUs is that intercommunication between processes in different rings has to be done in a very specific way as to provide a guarantee that code has a chance to check and sanitize incoming data. For example, the ring model would be near useless if a user-mode process could push items to the ring0 stack directly, or create kernel threads at arbitrary addresses. Mechanisms such as call gates and I/O Request Packets (IRPs) provide a way to interoperate between user- and kernel-mode.

It is possible, through a Windows API called DeviceIoControl, to send IRPs from user-mode to kernel-mode. Each driver has a list of handler functions that it can bind a function to, or not if it does not wish to handle that particular functionality. These functions are called 'major functions' and are assigned through the DRIVER_OBJECT->MajorFunction array. One of these major functions is designated IRP_MJ_DEVICE_CONTROL, which is called whenever an IRP is sent to the driver. When pointed at an appropriate handler function, the driver intercepts this IRP and can process it however it wants. The most important parts of the IRP structure (at least when using buffered IO) are the IoControlCode and the SystemBuffer parameters. When the user-mode application sends an IRP to the kernel-mode driver, it specifies a control code to define what it is asking the driver to do. It may also provide a buffer for input or output data. The driver performs the task specified by the control code given, utilising SystemBuffer to take parameters or send back data to the user-mode application.

In AetherAV there is a global event object defined under BaseNamedObjects that both user- and kernel-mode code has access to. When a new event occurs, the kernel-mode driver signals this event and the user-mode application sends a DeviceIoControl request back to the driver in order to find out certain information about the event, and finally invokes a managed event to tell the rest of the application about the event.

One issue we have to handle is preemption - e.g. new thread event occurs, timer signalled, application sends IRP to ask for new thread ID, driver sends value back, a second new thread event occurs and overwrites thread ID and process ID values, application sends IRP to ask for new thread's associated process ID, wrong process ID sent back. To stop this, we set up a kernel-only event object that is waited upon until the application has asked for all the required information about the event. This prevents new updates from occuring before the application has asked about the event. If new thread/process starts have occured since then, the system creates a backlog queue of IRPs to the notification handler and the driver will repeatedly signal the user-mode application about these new events until the backlog has been cleared.

Saturday, 12 February 2011

IPC in Aether

Anyone who's ever dealt with unmanaged memory across multiple managed processes will know that it becomes very cumbersome to handle, especially when some of your code is single-threaded and some is multi-threaded. The hardest part is to synchronise the access of data. A mutex on a memory section can often be the best choice, since it works across the whole system. Unfortunately this solves but a single problem in a sea of glitches and issues. As such, the IPC in Aether has been transformed to use UDP packets instead.

Since UDP is a network protocol, one might assume that the packets would be sent across the network. If this were the case, it could open us up to significant security issues. As it turns out though, it doesn't end up being a problem. If any IPv4 packet is sent to localhost (127.0.0.1) the packet is intercepted by the socket provider and immediately copied back into the input buffer and dropped from the output buffer before it is sent to the hardware.


Since the .NET framework allows for simple access to the UDP protocol through the UdpClient class, it is relatively simple to combine asynchronous UDP sockets with a binary serialization class (BinaryFormatter) and wrapper classes to produce very reliable event-driven IPC.

Monday, 31 January 2011

Structural decisions - Kernel notification, usermode action

In the "Pulling the digital rug out from under the malware's feet" article I posted a few months back, I mentioned the PsSetCreateProcessNotifyRoutine and PsSetCreateThreadNotifyRoutine APIs that allow a kernel mode driver to create a notification callback routine that is invoked whenever a process or thread is created or destroyed. There is in fact a third variety of this, PsSetLoadImageNotifyRoutine, which allows a notification callback to be created for executable image mapping events. Combining all three of these APIs provides a wonderful tool for profiling what the system is doing (w.r.t. process/thread objects) at any point in time.

Whilst a fully fledged kernel-mode module that allows for excellent protection against usermode malware by providing a direct method to interfere with their exection (i.e. avoid user mode security mechanisms such as ACLs), it would be infeasible to create such a KMD in this project. However, a KMD will be used to notify the service process of new processes, threads and the loading of executable images.

This kernel module has almost been completed, and is in late beta testing. Here's a screenshot of the DbgView output:



The next step will be to add kernel message notification code to the usermode service.

Sunday, 9 January 2011

Heuristic definitions

The problem with malware definitions is that they are specifically for one type of malware. If you write a definition for a single malware sample, the writer will simply modify their malware or obfuscate it with an executable packer. In order to detect generic malware in a way that cannot be easily obfuscated, we can use heuristics.

Heuristics are rules that are designed to identify malware through analysis of the logical behaviour of an application instead of calculating definitive signatures like hashes. This may seem difficult to do in the case of malware that is not currently executing, since behaviour is derived from activity. However, it is possible through basic interpretation of code and analysis of API calls what a program will do.

Here's an example of code I took from a keylogger:

push 0
push eax
push 0x041102A0
push 0x0D
call kernel32.SetWindowsHookEx

The code here sets a keyboard hook on the system. The prototype of SetWindowsHookEx is as follows:

HHOOK WINAPI SetWindowsHookEx(
  __in  int idHook,
  __in  HOOKPROC lpfn,
  __in  HINSTANCE hMod,
  __in  DWORD dwThreadId
); 

In the stdcall calling convention (the standard convention for Windows) parameters are pushed from right to left. This means that in our sample code the idHook parameter is 0x0D (decimal 13), which specifies a low level keyboard hook. Since the other parameters are arbitrary (i.e. not specific to a keyboard hook) we can simply say "look for any call to SetWindowsHookEx and if the last push done before it has a value of 13, treat it as a possible keylogger". Of course there are other reasons to set up a low level key hook, such as processing global hotkeys, so we have to look at a few other things. We can follow the third push address (HOOKPROC lpfn) to the hook function and see what it does there (e.g. does it call file write operations?) to test decide if it really is a keylogger.

Thursday, 9 December 2010

Pulling the digital rug out from under the malware's feet

Usermode processes are limited in terms of what they can do by the API platform provided by the OS and the non-privileged instructions that they can execute. This means they have to abide by certain rules and regulations put in place to protect system integrity and security. Usermode processes cannot directly communicate with the BIOS or peripheral hardware, nor kernel objects - they must talk to them via drivers and other kernel-mode code. The model of communication roughly looks like this:

Process <-> Service <-> Driver <-> Kernel

The service is not a traditional Windows service that runs as a process - it's a service representing the kernel mode driver (KMD) that sits in the kernel layer. The process communicates with the service using DeviceIoControl and other associated APIs. The service acts as a bridge between user- and kernel-space. The driver then interacts with system APIs, structures and mechanisms in order to perform its tasks. In the case of a hardware driver, the driver uses objects that are provided by the Hardware Abstraction Layer (HAL). The HAL acts as the bridge between software by providing software access to physical device memory. For example, the HAL might inform a KMD the address at which the memory for a graphics card's display buffer is mapped in virtual memory.

The model of isolated kernel- and user-mode is convenient because it alleviates the need for software developers to implement their own low level mechanisms such as spinlocks and memory isolation. It also allows the system much greater control over how everything is running and therefore massively improves stability and security. In a system that doesn't distinguish between user and kernel memory, a process could write over important kernel code or data. This could either be because the process is malicious, or it has a bug. In systems like that, a crashing process often crashes the whole system.

If you put all of this in the context of malware, it's like having a virtual machine running with the malware process in it. To put it bluntly: you can trash the crap out of user memory, but kernel memory remains intact. This means in the case of disaster (e.g. a critical system service such as lsass.exe dies) the kernel can finish important tasks before throwing a bugcheck and halting the system to prevent further damage.

What makes a great weapon in the fight against malware is a KMD-based anti-malware solution. The kernel can in fact hide critical anti-malware processes and hook important calls (e.g. VirtualProtectEx, WriteProcessMemory, etc) to make sure that the anti-malware processes stay intact. It can also access any user object (process, thread, memory, file, etc) regardless of its user-level protection. It's like having the ability to walk through walls. As the title of this post said, you can pull the digital rug out from under the malware's feet without it being able to stop you.

One important thing anti-malware solutions need to do is monitor the execution of processes. In a KMD it is possible to register a callback function that is called whenever a new process is created. This can be achieved in Windows with the PsSetCreateProcessNotifyRoutine system API. The same thing can be done for threads with PsSetCreateThreadNotifyRoutine. You simply pass it a pointer to a callback function and that function gets called whenever a new process is created. Of course it might be more prudent to hook an API responsible for loading executable binaries using the System Service Dispatch Table (SSDT) so that you can check the executable file for known malware before allowing the creation of the process to continue.

What we must remember is that whilst a malware KMD could technically be loaded and attack our kernel-mode code. Whilst this is somewhat unlikely, it is still a scenario that could occur. There's not much we can do about this. At the point when malware infects the kernel, the system can (pretty much) be considered irreparably comprimised.

For now this post is simply a conglomeration of my personal knowledge and a great deal of research. I am currently considering whether it would be feasible to introduce a KMD into my project.

Wednesday, 8 December 2010

Integrity checking

In order to make sure anti-malware software itself isn't comprimised, we can take a few precautions.

The first mechanism is assembly signing. Using a certificate we can make sure that the CLI will refuse to load the binary if it is modified by a 3rd party. It not only provides this security, but also proves the identity of the individual or group that produced the assembly. This means that we get security and assurance of authenticity, which is good for real security and user confidence. Visual Studio provides the ability to sign executables when they are produced. Certain tools are available that strip signatures from an assembly, so it is a good idea to programatically check that the assembly is signed at startup. We can also maintain a read/write lock on critical files to help make sure they are not modified.

An issue that is much more difficult to tackle is runtime manipulation. Process memory has been safe from direct manipulation since Windows NT4 due to the introduction of isolated process memory, but the WriteProcessMemory API still allows us to modify the memory of a remote process. It is also possible to create a thread in another process, allowing execution of arbitrary code in that process's context.

There are a few tricks we can use to check if our binary has been patched in memory. One thing we can do is perform a read operation on our own memory (the memory allocated for .code) and compare it to a hash to check that no code was modified. This is an annoyance for malware writers, but it could easily be patched by writing over the check code or modifying the hash. Whilst the system could be considered irreparebly comprimised if malware could write memory to a process running as SYSTEM, it's not to say that the situation could not be quite easily recovered from manually.

Another trick is memory protection scanning. If we remove the write flag from the protection value on all memory in our process that doesn't need to be read (e.g. the code) the malware must modify that memory's protection in order to write to it. This can be done in usermode by calling VirtualProtectEx. One could write a kernel-mode driver (KMD) to hook such calls and block them, but this is complex and likely outside the scope of this project. In our case, we can simply iterate all pages in our process' memory and check the protection constants match up.

Sunday, 5 December 2010

Type scanning

Files are usually identifiable by their extension. Whilst we'd expect .jpg to be a JPEG image file, it might actually contain text or executable data. This is a problem in malware scanning because it's trivial to modify the registry to make any file extension to be treated as an executable.

HKEY_CLASSES_ROOT\.abc
    (Default) = "exefile"
    Content Type = "application/x-msdownload"
HKEY_CLASSES_ROOT\.abc\PersistentHandler
    (Default) = "{098f2470-bae0-11cd-b579-08002b30bfeb}" 

I could then rename file.exe to file.abc and run it, and it'd run like a normal program. In the context of anti-malware software, we have to scan every file's data to check that files are in fact what they say they are. To shorten this process, we can use file type signatures. For example, an extremely simple signature for Win32 executable files might be that the file starts with the ASCII characters 'MZ' and also contains the ASCII string 'This program cannot be run in DOS mode'.

There is a project called TrID that aims to do exactly this. It comes with a large list of file type definitions that were generated by comparing hundreds of samples of the same file type. This data can be easily re-generated and compiled into a single database file.