Introduction to IOCTL (Input/Output CONTROL)
Introduction
In this blog, We will be discussing the Basics of `IOCTL (Input/Output CONTROL)`, What is it ?, The modes types, What is it used for ? and I will show an example of sending an IOCTL Request. But, before we moving further with all of this we need to understand the User Mode and the Kernel Mode.
The Modes
Basically, The modes word itself referring to the different states of operation & different levels of privilege that the system or a software could run. In general, each mode different from the other and operate differently with different privileges and access levels & the main 2 modes we have and will talk about is User Mode
&Kernel Mode
.
What is User Mode ?
The User Mode
is the mode which the levels of operations & privileges are restricted and limited in access & permissions. So, the software or any program running in the User Mode
or inside it, Won’t be able to perform certain operations that could be malicious or harmful to other softwares and the system. In short, User Mode
is a non-privileged mode where it can’t access the hardware resources or any low level operations directly. For example, A malware running in the User Mode
can’t access or modify the CPU
instructions cause if this happened the malware easly will have an elevated privileges and as it’s on the hardware. Therefore, It’s the highest privileges you can get and u won’t have any restriction.
What is Kernel Mode ?
The Kernel Mode
is a non-restricted mode where you can access the hardware resources directly where you will be able to manage any related resources such as CPU
& Memory
and perform any low level operations easily. For example, any one get access to the Kernel Mode
will be able to manipulate the resources easily and can also escalate privileges which could happen through a vulnerability or any misconfigurations. In short, The Kernel Mode
opposite to the User Mode
and each of one has different levels of operations & levels of privileges.
Input/Output Control (IOCTL)
IOCTL
is short for “Input/Output Control” which is a system call used for input/output operations and communications from the User Mode
to the Kernel Mode
drivers. Basically, It allows the components in the User Mode
for example a software to communicate and control the behaviour of a device driver in Kernel Mode
.
IOCTLs
The IOCTLs
are a set of codes and each one of these codes has a specific job to be used in and perform. For example:
IOCTL_DISK_GET_LENGTH_INFO
: ThisIOCTL
code is used to retrieve the length of a disk partition.IOCTL_VIDEO_QUERY_SUPPORTED_BRIGHTNESS
: ThisIOCTL
code is used to query the supported brightness levels of a display.IOCTL_SERIAL_SET_BAUD_RATE
: ThisIOCTL
code is used to set the baud rate of a serial port.IOCTL_CDROM_GET_LAST_SESSION
: ThisIOCTL
code is used to retrieve information about the last session on a CD-ROM.IOCTL_STORAGE_QUERY_PROPERTY
: ThisIOCTL
code is used to retrieve information about a storage device.IOCTL_MOUSE_GET_ATTRIBUTES
: ThisIOCTL
code is used to retrieve the attributes of a mouse device.IOCTL_KEYBOARD_SET_TYPEMATIC
: ThisIOCTL
code is used to set the typematic rate of a keyboard.IOCTL_SMARTCARD_TRANSMIT
: ThisIOCTL
code is used to send a command to a smart card reader.IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION
: ThisIOCTL
code is used to retrieve a USB descriptor from a USB device.IOCTL_NDIS_QUERY_GLOBAL_STATS
: ThisIOCTL
code is used to query global statistics for a network adapter.
Those where example for the IOCTLs
you can find all the IOCTL
codes you need from Here.
How IOCTL works ?
When the apps or the components in the User Mode
wants to query information of a device driver in the Kernel Mode
. It sends a request known as IOCTL
request, The IOCTL
requests are made & sent using the DeviceIoControl()
which is a windows API function.
When the the DeviceIoControl()
function gets called it is actually implemented by calling another function which is NtDeviceIoControlFile()
resides in the ntdll.dll, Then the IOCTL
request sent to the device driver. There are 4 buffering methods that can be used with the IOCTL
request which are:
METHOD_BUFFERED
: Copies the input and output buffers to and from the driver through an intermediate system buffer.METHOD_IN_DIRECT
: Its used when the input buffer is large and will not fit in the intermediate system buffer. In this case, the input buffer is mapped directly into the driver’s address space.METHOD_OUT_DIRECT
: In case the output buffer is large and will not fit in the intermediate system buffer. In this case, the output buffer is mapped directly into the driver’s address space.METHOD_NEITHER
: When the input and output buffers are not contiguous and cannot be mapped into a single buffer. The driver and application exchange pointers to the separate input and output buffers.
Note: If you wondering at which part the user get’s a Kernel Mode
access. Basically, there are 2 functions/syscalls used which are kifastsystemcall
and kifastsystemcallret
and both are kernel functions that provide a mechanism for making syscalls from user mode to kernel mode. The kifastsystemcall
function takes the parameters for a syscall and makes the transition from user mode to kernel mode using a secret number called syscall gate
(which grants the Kernel Mode
access) and the parameters onto the stack. Then it makes a syscall instruction to trigger the kernel to execute the syscall. And the kifastsystemcallret
function is used to return from the syscall back to the User Mode
by passing the return address of the User Mode
code which is stored on the stack during the syscall.
Sending IOCTL Request
To understand the IOCTL
request we will do in a clearly way, The DeviceIoControl()
function structure is as the following:
BOOL DeviceIoControl(
HANDLE hDevice,
DWORD dwIoControlCode,
LPVOID lpInBuffer,
DWORD nInBufferSize,
LPVOID lpOutBuffer,
DWORD nOutBufferSize,
LPDWORD lpBytesReturned,
LPOVERLAPPED lpOverlapped
);
each parameter in the function represents as the following:
hDevice
: It’s a handle to the device thatwill be used to perform the operation.dwIoControlCode
: TheIOCTL
code that identifies the operation to be performed.lpInBuffer
: A pointer to the input buffer that contains the data required to perform the operation.nInBufferSize
: The size of the input buffer.lpOutBuffer
: A pointer to the output buffer that receives the data returned by the operation.nOutBufferSize
: The size of the output buffer.lpBytesReturned
: A pointer to the number of bytes returned by the operation.lpOverlapped
: A pointer to anOVERLAPPED
structure that is used for asynchronous operations.
Note: the data size is in bytes & the Asynchronous operations
is an operation that allows the software to continue executing while waiting for a potentially long-running operation to be completed and the OVERLAPPED
structure is used to manage these operations.
Now, It’s the time to send our IOCTL
request and i will be using the IOCTL_DISK_GET_DRIVE_GEOMETRY_EX
code which retrieves extended information about the physical disk’s geometry like the type, number of cylinders, etc.
IOCTL
request Code:
#include <stdio.h>
#include <windows.h>
#include <winioctl.h>
int main() {
HANDLE hDevice;
DWORD dwIoControlCode;
BOOL bResults;
DWORD dwBytesReturned;
DISK_GEOMETRY_EX diskGeometry;
hDevice = CreateFileW(L "\\\\.\\PhysicalDrive0", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
dwIoControlCode = 0x000700A0;
bResults = DeviceIoControl(hDevice, dwIoControlCode, NULL, 0, & diskGeometry, sizeof(diskGeometry), & dwBytesReturned, NULL);
if (hDevice == INVALID_HANDLE_VALUE) {
printf("Handling the device drive faild");
exit(0);
} else if (bResults == FALSE) {
printf("IOCTL request faild");
CloseHandle(hDevice);
exit(0);
} else {
printf("Disk size: %llu bytes\n", diskGeometry.DiskSize.QuadPart);
CloseHandle(hDevice);
}
return 0;
}
Now, The above code is the one we will use to send the IOCTL
Request, Let’s explain it. First, We included the needed header files that contains the needed functions & methods we will use which are:
stdio.h
: Which is used for the standard input/output and contains functions such asprintf()
windows.h
: Which provides us with theWindows APIs
as theCreateFileW()
we used in the code.winioctl.h
: Which contains theIOCTL
codes and let us perform theIOCTL
Request.
After that we defined some variables in the main()
function and each one of them has a role as the following:
HANDLE hDevice
: We declaredhDevice
as aHANDLE
object which used to access and manipulate system resources in our case it’s the device drive.DWORD dwIoControlCode
: Here we declareddwIoControlCode
as aDWORD
data type where we gonna store theIOCTL
code.BOOL bResults
: Declared thebResults
variable as aBoolean
data type to store the result ofDeviceIoControl()
function call.DWORD dwBytesReturned
: Here we declareddwBytesReturned
as aDWORD
data type which we wll use to store the bytes returned byDeviceIoControl()
function.DISK_GEOMETRY_EX diskGeometry
: declaringdiskGeometry
variable as aDISK_GEOMETRY_EX
data type variable that will store the disk geometry information retrieved by theDeviceIoControl()
.
It’s the time now to assign and use these variables and initial our IOCTL
request in the following lines of code:
hDevice = CreateFileW(L"\\\\.\\PhysicalDrive0", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
Here we used the CreatFileW()
Windows API which generally it’s used to create and open files on disk. But, it’s also can be used to open the device drive. The function takes some values as parameters:
L"\\\\.\\PhysicalDrive0"
: Here is device drive andPhysicalDrive0
indicates to the first hard disk drive & the\\\\.\\
is used to communicating with localhost.GENERIC_READ | GENERIC_WRITE
: Specifies the access mode for the handle.FILE_SHARE_READ | FILE_SHARE_WRITE
: Specifies how the handle can be shared with other processes.NULL
: This is the security attributes. We set it to NULL for default security.OPEN_EXISTING
: Specifies that the function should open an existing fileFILE_ATTRIBUTE_NORMAL
: Specifies that the file should have no other attributes set.NULL
: A template file that we don’t need.
Next we assign the IOCTL
code which is 0x000700A0
to the dwIoControlCode
variable. The 0x000700A0
hex code refers to IOCTL_DISK_GET_DRIVE_GEOMETRY_EX
. After that we called the DeviceIoControl()
and assign it to bResults
variable.
bResults = DeviceIoControl(hDevice, dwIoControlCode, NULL, 0, &diskGeometry, sizeof(diskGeometry), &dwBytesReturned, NULL);
And as we can see the DeviceIoControl()
takes some parameters and we have explained the structure of it above.
if (hDevice == INVALID_HANDLE_VALUE) {
printf("Handling the device drive faild");
exit(0);
} else if (bResults == FALSE) {
printf("IOCTL request faild");
CloseHandle(hDevice);
exit(0);
} else {
printf("Disk size: %llu bytes\n", diskGeometry.DiskSize.QuadPart);
CloseHandle(hDevice);
}
Finally, Here we check for errors to handle such as INVALID_HANDLE_VALUE
which indicates that the handling value is invalid and in the second conditionit checks if the returned boolean of DeviceIoControl()
that stored in bResults
False or no, If False that means the operation didn’t done successfully. If the both of the 2 condations aren’t true that means everything goes well and will get us the disk size using diskGeometry.DiskSize.QuadPart
and print it. At the end will close the Handle
we created. Now, I am using CodeBlocks
to write and execute the code.
Running The code
First, I will put a random name to handle. Therefore, it will result in an error:
You can see that the error has been handled. This time i will run the code normally with no problems:
And here is the disk size with no issues. If we opened Procmon
or Process Monitor
and run our code after compiling it, We will be able to see the process very clearly.
As we can see in the screen shot clearly we can see all the operations done by the compiled code process from the start going through the calls and so on, Until the process exit.
Conclusion
Now, we know what is IOCTL (Input/Output CONTROL) and how it works & This was just a basic introduction to the **IOCTL (Input/Output CONTROL)** and in the coming parts we will be diving deep by showing more examples & Reverse Engineer a driver and Identifying the IOCTL and extracting IOCTL codes and many more.