↑ Back to top
Have you ever reviewed a network trace, noticed I/O Control Codes in the messages like the example highlighted below and wondered what exactly they represent?
IoControlCode: 2228276 (0x220034)
I/O Control Codes are 32-bit unsigned integers that are used to communicate between various Windows components and Windows drivers. This article details how to read those I/O Control Codes, decode them to identify the associated device types, functions, access codes, and methods, and map that information back to a specific name that describes where they originated. To do all this, you’ll need to understand the concept of bitwise manipulation, in particular bit masking and shifting. A good tutorial on bit manipulation is “Bit Twiddling”.
The Open Specifications/Protocols team occasionally receives questions from protocol implementers regarding I/O control codes (a.k.a., FSCTLs, IOCTLs, etc.) that are unknown. These are typically IOCTLs that flow through a protocol such as [MS-SMB2], [MS-RDPEUSB] or [MS-RDPEFS], but are not necessarily a part of the protocol itself (the protocol is just a conduit between upper-layer peers and there are no special processing rules relative to the protocol itself).
The following is a typical customer question:
“I’m looking for some information about the control requests 0x00220034 and … See the network trace below:”
+ RDPBCGR: RDPEFS
VirtualChannelData: RDPEFS
- RDPEFS: RDPDrDeviceIORequest
+ RdpdrHeader: Component = RDPDR_CTYP_CORE, PacketId = PAKID_CORE_DEVICE_IOREQUEST
- DrCoreDeviceIORequest: IRP_MJ_DEVICE_CONTROL, DeviceId = 1
- DeviceId: 1 (0x1)
- FileId: 1 (0x1)
- CompletionId: 1 (0x1)
- MajorFunction: IRP_MJ_DEVICE_CONTROL
- MinorFunction: Not used
- OutputBufferLength: 1024 (0x400)
- InputBuffreLength: 0 (0x0)
- IoControlCode: 2228276 (0x220034)
The first resource available to find more information on these I/O Control Codes is the collection of Open Specifications (protocol documents) themselves on MSDN. The IOCTLs that require special attention by protocol implementers are described in the protocol technical specification documents. In the above example, you would use the collection of Windows protocols (available at http://msdn.microsoft.com/en-us/library/cc216517.aspx), and search for the IOCTL in hexadecimal form: “0x00220034” or just “220034”.
If the I/O Control Code is not found in the Open Specifications protocols, the second resource to search is the Windows Driver Kit (WDK) for Windows driver developers. Of particular interest for our purpose are the kit’s header files (INC and INCLUDE folders) and, specifically, the header files ntddk.h, ntifs.h, wdm.h and devioctl.h.
It is often difficult to correlate a given 32-bit value to the name assigned to it. That’s because the values are determined at compile time via the macro in the WDK at ntddk.h, ntifs.h, wdm.h and devioctl.h as:
#define CTL_CODE( DeviceType, Function, Method, Access ) ( \
((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) )
It takes four inputs: the Device Type, a Function Code, an Access Check and an I/O Method and each are arranged, as bits (subdivided into four-bit nibbles for clarity), as follows:
DDDD.DDDD.DDDD.DDDD.AAFF.FFFF.FFFF.FFMM
(Where ‘D’ is Device Type, ‘A’ is Access Check, ‘F’ is Function Code and ‘M’ isI/O Method)
Its output is the 32-bit I/O Control Code.
Given an I/O Control Code of, say, 0x0f60401a, let’s break it down into its components by washing it through the CTL_CODE backward using the following steps.
To determine the DeviceType, mask it with 0xFFFF0000 and shift it right 16 bits. Thus 0x0f60401a masked with 0xFFFF0000 becomes 0x0f600000, which becomes 0x00000f60 when shifted. Searching the WDK header files for 0x0F60 we find FILE_DEVICE_IRCLASS:
#define FILE_DEVICE_IRCLASS 0x0F60
While most common Device Types are defined in ntddk.h or devioctl.h, not all are. In this example, a bit of sleuthing was necessary to find it in irclass_ioctl.h. In this example, it was also necessary to search for both 0x00000f60 and 0x0f60.
Access can take the values 0, 1, 2 or 3:
#define FILE_ANY_ACCESS 0
#define FILE_SPECIAL_ACCESS (FILE_ANY_ACCESS)
#define FILE_READ_ACCESS ( 0x0001 ) // file & pipe
#define FILE_WRITE_ACCESS ( 0x0002 ) // file & pipe
Three is represented as ( FILE_READ_ACCESS | FILE_WRITE_ACCESS )
It can be determined by masking off the Device Type and shifting 14 bits right. Thus 0x0f60401a, masking out DeviceType, becomes 0x0000401a. Further bit-shifting right yields 0x1: FILE_READ_ACCESS.
Function is determined by masking off the Device Type and Access Type and right-shifting two bits. Thus, 0x0f60401a is masked with 0xffffc000 to get 0x0000001a. Shifting 0x1a right by two bits yields 6. When searching for CTL_CODEs with a function number, use both decimal and hex values.
Often times, the Function Code is expressed in the WDK as a BASE+Value, so this could be tricky, as in:
#define MYDRIVER_FUNCTION_BASE 0x100
#define IOCTL_MY_COOL_FUNCTION \
CTL_CODE( ..., MYDRIVER_FUNCTION_BASE+2, ..., ... );
Resulting in the value of 0x102 as the Function Code for IOCTL_MY_COOL_FUNCTION
Lastly, the I/O Method is the last two bits, so just mask out Device Type, Function and Access using 0xfffffffc. Method can be 0, 1, 2 or 3:
#define METHOD_BUFFERED 0
#define METHOD_IN_DIRECT 1
#define METHOD_OUT_DIRECT 2
#define METHOD_NEITHER 3
Therefore, 0x0f60401a masked with 0xfffffffc yields 2, equivalent to METHOD_OUT_DIRECT.
Now that we have all the information we need about the I/O Control Code, let’s put it together
------------------------
The I/O Control Code 0x0f60401a maps to:
Device Type: FILE_DEVICE_IRCLASS
Access: FILE_READ_ACCESS
Function: 6
Method: METHOD_OUT_DIRECT
After some effort and searching in the WDK, the following match is found in irclass_ioctl.h:
#define IOCTL_IR_RECEIVE CTL_CODE(FILE_DEVICE_IRCLASS, \
6, \
METHOD_OUT_DIRECT, \
FILE_READ_ACCESS)
Thus, the hexadecimal value 0x0f60401a is mapped to the name IOCTL_IR_RECEIVE. With that knowledge, you search on IOCTL_IR_RECEIVE in MSDN for more information.
��� Back to top
Discovering a name for a given value involves a bit of art. To assist you in your search, keep these points in mind:
A DeviceIoControl function that sends a control code directly to a specified driver might not always be rolled with CTL_CODE directly, so searching the headers for only "CTL_CODE" may not be sufficient. IOCTL_TDI_ACCEPT (0x00210000), for example, is defined as:
#define IOCTL_TDI_ACCEPT _TDI_CONTROL_CODE( 0, METHOD_BUFFERED )
Which indirectly references CTL_CODE via:
#define _TDI_CONTROL_CODE(request,method) \
CTL_CODE(FILE_DEVICE_TRANSPORT, request, method, FILE_ANY_ACCESS)
Furthermore, there may be more than one name for a given Device Type. Take 0x00000022, for example, which is defined as FILE_DEVICE_UNKNOWN:
#define FILE_DEVICE_UNKNOWN 0x00000022
However, some of the DeviceIoControls that specify a DeviceType of 0x00000022 do not directly reference FILE_DEVICE_UNKNOWN. IOCTL_INTERNAL_USB_SUBMIT_URB (0x00220003), which clearly indicates FILE_DEVICE_UNKNOWN (0x00000022 << 16: 0x0022xxxx), gets there indirectly via the intermediary FILE_DEVICE_USB:
#define FILE_DEVICE_USB FILE_DEVICE_UNKNOWN
#define IOCTL_INTERNAL_USB_SUBMIT_URB \
CTL_CODE(FILE_DEVICE_USB, USB_SUBMIT_URB, METHOD_NEITHER, FILE_ANY_ACCESS) And, finally, as already mentioned, function codes might be in hex or decimal and may be added to a base value.
By following the steps above, you can decode any hexadecimal I/O Control Code and map it to a descriptive name. For your convenience and reference, in the Appendix, I provide a table, in IOCTL order, listing the hexadecimal values and names of all I/O Control Codes presently defined in the Windows Driver Kit (WDK). This is a snapshot using the now-current WDK 8.1. New IOCTLs are routinely added and existing IOCTLs sometimes are marked as obsolete or deprecated.
The WDK prior to Windows 8 is available at http://www.microsoft.com/en-us/download/details.aspx?id=11800. It is a self-contained, all-encompassing package that includes all documentation, samples, header files, libraries, headers as well as build tools: compilers, linkers, etc., and its own build environment. After installing the package, you will find the header files under <kit>\Inc.
The WDK for Windows 8 and following (Windows Server 2012 and following) is available at http://msdn.microsoft.com/en-us/library/windows/hardware/ff557573(v=vs.85).aspx. It is designed to be integrated with the Visual Studio build environment. Accordingly, it will provide directions to install Visual Studio first, then the kit. However, if your only purpose of obtaining the kit is to get the static header files, you can install the Windows 8+ WDK by itself (without the Visual Studio build environment).
==========================================