• Tidak ada hasil yang ditemukan

A Common Mechanism for User-Kernel Communication

Dalam dokumen Practical Reverse Engineering (Halaman 178-181)

Many mechanisms are used to facilitate user-kernel communication. For example, a driver can communicate with user-mode code through a shared memory region double-mapped in user and kernel space. Another method is for the driver to create an event that a user-mode thread can wait on; the event state can be used as a trigger for further action. Yet another (although hackish) method is through interrupt handling. A driver can manually set up a custom interrupt handler

in the IDT and user-mode code can trigger it with the INT instruction; you will probably never see this technique used in a commercial driver.

While the precise communication mechanism depends on the developer’s ultimate goal, a generic documented interface is typically used for user-kernel data exchange. This mechanism is supported by the IRP_MJ_DEVICE_CONTROL operation and commonly referred to as device I/O control or simply IOCTL. It works as follows:

1. The driver defi nes one or more IOCTL codes for each operation it supports.

2. For each supported operation, the driver specifi es how it should access the user input and return data to the user. There are three access methods:

buffered I/O, direct I/O, and neither. These methods are covered in the next section.

3. Inside the IRP_MJ_DEVICE_CONTROL handler, the driver retrieves the IOCTL code from its IO_STACK_LOCATION and processes the data based on the input method.

User-mode code can request these IOCTL operations through the DeviceIoControl API.

Buff ering Methods

A driver can access a user-mode buffer using one of the following three methods:

Buffered I/O—This is referred to as METHOD_BUFFERED in the kernel. When using this method, the kernel validates the user buffer to be in accessible user-mode memory, allocates a block of memory in non-paged pool, and copies the user buffer to it. The driver accesses this kernel-mode buffer through the AssociatedIrp.SystemBuffer fi eld in the IRP structure.

While processing the request, the driver may modify the system buffer (perhaps it needs to return some data back to the user); after completing the request, the kernel copies the system buffer’s content back to the user- mode buffer and automatically frees the system buffer.

Direct IO—This is referred to as METHOD_IN_DIRECT or METHOD_OUT_DIRECT in the kernel. The former is used for passing data to the driver; the latter is used for getting data from the driver. This method is similar to buffered I/O except that the driver gets an MDL describing the user buffer. The I/O manager creates the MDL and locks it in memory before passing it to the driver. Drivers can access this MDL through the MdlAddress fi eld of the IRP structure.

Neither—This is referred to as METHOD_NEITHER in the kernel. When using this method, the I/O manager does not perform any kind of validation on the user data; it passes the raw data to the driver. Drivers can access

fi eld in its IO_STACK_LOCATION. While this method may seem the fastest of the three (as there is no validation or mapping of additional buffers), it is certainly the most insecure one. It leaves all the validation to the developer. Without proper validation, a driver using this method may expose itself to security vulnerabilities such as kernel memory corruption or leakage/disclosure.

There is no written rule for determining which method to use in drivers because it depends on the driver’s specifi c requirements. However, in practice, most software drivers use buffered I/O because it provides a good balance between simplicity and security. Direct I/O is common in hardware drivers because it can be used to pass large data chunks without buffering overhead.

I/O Control Code

An IOCTL code is a 32-bit integer that encodes the device type, operation-specifi c code, buffering method, and security access. Drivers usually defi ne IOCTL codes through the CTL_CODE macro:

#define CTL_CODE( DeviceType, Function, Method, Access ) ( \ ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \ )

DeviceType is usually one of the FILE_DEVICE_* constants, but for third-party drivers it can use anything above 0x8000. (This is only the recommended value and there is nothing enforcing it.) Access specifi es generic read/write operations allowed by the IOCTL; it can be a combination of FILE_ANY_ACCESS, FILE_READ_

ACCESS, and FILE_WRITE_ACCESS. Function is the driver-specifi c IOCTL code;

it can be anything above 0x800. Method specifi es one of the buffering methods.

A typical way to defi ne an IOCTL code is as follows:

#define FILE_DEVICE_GENIOCTL 0xa000 // our device type

#define GENIOCTL_PROCESS 0x800 // our special IOCTL code

#define IOCTL_PROCESS CTL_CODE(FILE_DEVICE_GENIOCTL, \ GENIOCTL_PROCESS, \

METHOD_BUFFERED, FILE_READ_DATA)

This defi nes an IOCTL called IOCTL_PROCESS for a custom driver using METHOD_BUFFERED.

When analyzing a driver, it is important to decompose the IOCTL down to its device type, code, access, and buffering method. This can be achieved with a couple of simple documented macros:

#define DEVICE_TYPE_FROM_CTL_CODE(ctrlCode) \

(((ULONG)(ctrlCode & 0xffff0000)) >> 16)

#define METHOD_FROM_CTL_CODE(ctrlCode) ((ULONG)(ctrlCode & 3)

Dalam dokumen Practical Reverse Engineering (Halaman 178-181)