Reversing A Tiny Built-In Windows Kernel Module [Journey from Kernel32 to HAL]

Hello readers. Hope you are doing great. In this post I am going to explore our very own windows kernel little bit by reverse engineering a built in kernel module. If you have ever developed any kernel driver/module for windows it will be very easy for you to understand. If you are not very familiar with how device drivers work then has some really good resources to start up. So let's get started.

But Before we can start reversing the core component, take a look at the diagram mentioned below.

In above picture, the green elements are user mode components (Ring3). The diagram actually shows how apps.exe (a user mode application ) calls the kernel mode driver. We will try to cover each an every section mentioned in above diagram and try to reverse them, to understand how thing works.

Choosing a Target Driver to Reverse:

Its always better to start with simple thing. In this article we will reverse the Beep driver. The Beep Driver component provides the beep driver in the beep.sys file. This component also provides some supporting registry information. This is probably the smallest built in Kernel module in windows OS. It has only 6 routines.

First Step : Building apps.exe

We will start with section 1. So first we will start with a very basic C code. [beep.c]

int main(){
     Beep( 50, 750 );
     return 0;

The code is very simple and straight forward. You can see its calling Beep function which produces Beep sound. The function beep resides in Kernel32.dll. After compiling the code you should get beep.exe file. Running beep.exe should generate beep sound.

  _In_  DWORD dwFreq,
  _In_  DWORD dwDuration

Locating the Driver\Device:

From below screen shot we can see, Beep driver has actually created a Device called "Beep".And you can also see many other information like major functions supported by this driver and many more.

Sniffing all I/O Request Packets (IRP) to Beep Device using IRP Tracker utility:

IRP Tracker is very cool and powerful utility. It can actually sniff the Ring3 and Ring0 gateway and show details of messages passed from user mode process and kernel driver. Using this tool we are going to sniff all request which beep.exe actually actually sending from ring3 to ring0 to produce the beep sound.

Ok, So to start sniffing we need to provide the tool the driver name we want to sniff.  Go to File and select driver. Now you need to provide the driver name which we want to sniff. In this case we are only interested in sniffing the "Beep" driver. So now we started sniffing all messages between user and kernel. Now its time to execute the beep.exe we just compiled . When you execute the beep.exe file you will see few new entries in IRP Tracker window.

Now if you look at the IRP Address Sequence Number column you will see first entry is ntCreateFile() and the last entry is ntClose(). In between them you can see ntDeviceIoControlFile is getting called. Now if you look at the major function column you will find "DEVICE_CONTROL". If you look in msdn you will find that this API is actually used to send IOCTL codes from user land to kernel driver.

NTSTATUS WINAPI NtDeviceIoControlFile(
  _In_   HANDLE FileHandle,
  _In_   HANDLE Event,
  _In_   PIO_APC_ROUTINE ApcRoutine,
  _In_   PVOID ApcContext,
  _Out_  PIO_STATUS_BLOCK IoStatusBlock,
  _In_   ULONG IoControlCode,
  _In_   PVOID InputBuffer,
  _In_   ULONG InputBufferLength,
  _Out_  PVOID OutputBuffer,
  _In_   ULONG OutputBufferLength

IRP tracker utility can also provide us the IOCTL code the user mode application sending to kernel driver.

We can see its sending IOCTL code 0x10000 (BEEP_SET) to that device. Keep this in mind. We are going to come back to this in a minute.

Reversing Beep() [Kernel32.dll]

To reverse the Beep routine, lets load Kernel32.dll in IDA Pro. After its loaded lets jump into Beep routine and you should see something like this.

And we can see, its trying to communicate with the device Beep \\Device\\Beep by calling NtCreateFile. When communicating with a Kernel mode driver , any user land application uses NtCreateFile. If its successful this function returns one handle the target device. Using that handle we can read / write to that device. We will come to this later.

If we go little further inside Beep!Kernel32 we should see its trying to verify few parameters passed to it and after that it calls NtDeviceIoControlFile().

I hope now you are able to connect the dots now. If you can remember its the same sequence of function call you have seen in IRP tracker utility. Its already known to us that this NtDeviceIoControlFile is used for sending IOCTL codes to kernel driver.

Reversing ntdll.dll [NtDeviceIoControlFile()]

Now we will have a closer look at the call of NtDeviceIoControlFile. We have seen in msdn that the 6th parameter of NtDeviceIoControlFile is the IOCTL code.

NTSTATUS WINAPI NtDeviceIoControlFile(
  _In_   HANDLE FileHandle,
  _In_   HANDLE Event,
  _In_   PIO_APC_ROUTINE ApcRoutine,
  _In_   PVOID ApcContext,
  _Out_  PIO_STATUS_BLOCK IoStatusBlock,
  _In_   ULONG IoControlCode,
  _In_   PVOID InputBuffer,
  _In_   ULONG InputBufferLength,
  _Out_  PVOID OutputBuffer,
  _In_   ULONG OutputBufferLength

Lets verify that in NtDeviceIoControlFile function routine.

Hope you are able the connect the dots now. In above image you can see, first its loading 10000h into ebx register and then passing it to NtDeviceIoControlFile(). This is the same IOCTL code we have seen in IRP tracker utility.

Now lets attach the beep.exe file with immunity debugger and set a break point at NtDeviceIoControlFile(). After setting up the break point if we continue the execution, we should break at this point as shown in screen shot below.

If you look at the entry point of NtDeviceIoControlFile() routine, you will see below instruction,


Now from this sequence of instructions we can understand that its probably going for a system call. Now if we go further and follow the CALL DWORD PTR DS:[EDX] instruction we will get something like this.


From this INT 2E it's now absolutely clear that its going to invoke a software interrupt. Now EAX is actually pointing to 0x42. So we can say this is the system call number. We can verify this from any SDDT dumping utility. In this case I've used ICESword tool to dump the System Service Descriptor Table.

You can see systemcall 0x42 is actually pointing to Kernel version of NtDeviceIoControlFile() and its resides in the main windows kernel component which is ntoskrnl.exe. After invoking the interrupt OS will switch to kernel-mode to execute a system service. KiSytemServices is going to take the call.
The ‘int’ instructor causes the CPU to execute a software interrupt, i.e. it will go into the Interrupt Descriptor Table at index 2e and read the Interrupt Gate Descriptor at that location. The CPU switches automatically to the kernel-mode stack. The CPU automatically saves the user-mode program’s SS, ESP, EFLAGS, CS and EIP registers on the kernel-mode stack.

More Details Here

Breaking the Beep Driver(Beep.sys):

So till this point we have seen that how user land application is sending request to the kernel driver. Now we will see, how the kernel driver actually process the user land application request and act accordingly. For this we will reverse the Beep.sys file which is the main driver PE file.

After loading the driver into IDA you should first see the Driver Entry subroutine. We know DriverEntry is the first routine called after a driver is loaded. Since its responsible for initializing the driver we should find all the IOCTL handler functions in this driver entry. First thing you will see IoCreateDevice() is getting called.

NTSTATUS IoCreateDevice(
  _In_      PDRIVER_OBJECT DriverObject,
  _In_      ULONG DeviceExtensionSize,
  _In_opt_  PUNICODE_STRING DeviceName,
  _In_      DEVICE_TYPE DeviceType,
  _In_      ULONG DeviceCharacteristics,
  _In_      BOOLEAN Exclusive,
  _Out_     PDEVICE_OBJECT *DeviceObject

The IoCreateDevice() routine creates a device object for use by a driver. You should see call to this function on every driver's driver entry routine.

Now that we have successfully created our \Device\Beep device driver. So going further into the DriverEntry we will get a structure like this.

Equivalent C code will be something like this:

DriverObject->DriverStartIo = sub_1051A;
DriverObject->DriverUnload = DriverUnload;
DriverObject->MajorFunction[0] = sub_1046A;
DriverObject->MajorFunction[2] = sub_104B8;
DriverObject->MajorFunction[14] = sub_10400;
DriverObject->MajorFunction[18] = sub_10354;

More practical idea about IRP Major Functions can be found here

Digging further into all above mentioned IRP handlers (sub_xxxx), it was found that sub_10354 actually responsible for handling all IoControls. So we can conclude,

DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = Beephandler;

Now lets jump into sub_10354 in IDA pro. and have a look what its trying to do. Inside sub_10354 you should see somthing like this.

On a bigger picture you should notice calls to below functions.

KeRemoveDeviceQueue,KfRaiseIrql,IoAcquireCancelSpinLock,IoReleaseCancelSpinLock etc etc.Right now we are not very interested in any of above functions. If you are interested you can explore msdn. The call we are interested in is HalMakeBeep.

Call To HalMakeBeep

Reversing the HAL.dll:

Now we will jump into HalMakeBeep routine. HalMakeBeep routine actually resides into hal.dll.

The Windows Hardware Abstraction Layer (HAL) is implemented in Hal.dll. Hardware abstractions are sets of routines in software that emulate some platform-specific details, giving programs direct access to the hardware resources.They often allow programmers to write device-independent applications by providing standard Operating System (OS) calls to hardware. Each type of CPU has a specific instruction set architecture or ISA. One of the main functions of a compiler is to allow a programmer to write an algorithm in a high-level language without having to care about CPU-specific instructions. Then it is the job of the compiler to generate a CPU-specific executable. The same type of abstraction is made in operating systems, but OS APIs now represent the primitive operations of the machine, rather than an ISA.  This allows a programmer to use OS-level operations (i.e. task creation/deletion) in their programs while still remaining portable over a variety of different platforms.[Source : Wiki]

So to look at the assembly of HalMakeBeep we have to load up hal.dll in IDA. After loading hal.dll we will jump into HalMakeBeep routine. You should see lot of inline assembly inside this function.

Every PC has an internal speaker. It can generating beeps of different frequencies. We can actually control the speaker by providing a frequency number which defines the pitch of the beep, then turning the speaker on for the duration of the beep.

Here the frequency number we provide is nothing but a a counter value. Our computer uses it to determine how long is to wait between sending pulses to the internal speaker. More clearly a smaller frequency number will cause the pulses to be sent quicker, and it will result a higher pitch.Here the frequency number actually tells the PC how many of these cycles to wait before sending another pulse.

Mainly we can communicate with the speaker controller using IN and OUT instructions. Below I've mentioned few steps in generating a beep:

  1. First we need to send the value 182 to port 43h. This will actually set the speaker up.
  2. Next thing is, sending the frequency number to port 42h. Since this is an 8-bit port, we must use two OUT instructions to do this. Send the least significant byte first, then the most significant byte.
  3. After that, to start the beep sound, bits 1 and 0 of port 61h must be set to 1. Since the other bits of port 61h have other uses, they must not be modified. Therefore, you must use an IN instruction first to get the value from the port, then do an OR to set the two bits, then use an OUT instruction to send the new value to the port.
  4. Pause for the duration of the beep.
  5. We can turn off the beep by resetting bits 1 and 0 of port 61h to 0. Remember that since the other bits of this port must not be modified, you must read the value, set just bits 1 and 0 to 0, then output the new value.

So now if we look at the HalMakeBeep routine it should make sense. You can see that its doing the same thing just describe above.

Thanks for reading. Hope you have enjoyed this post. If you believe i did something wrong anywhere and you want me to correct or i've missed something to cover, please drop an email or comment below.



Wiki :


  1. More often than not, producing the PCB prototype requires more time and investment than the actual production. PCB reverse engineering When it comes to PCB prototyping, most clients don't want to know what goes into the manufacturing process, because all they want is a functional PCB prototype that can be used for mass production.

  2. There are many PCB services out there and it is important for any company to choose one that will be reliable, will be able to provide them with the service that they need at a reasonable cost as well as make sure that the product that they are producing contains well working quality PCBs. Aluminum PCB PCB testing is necessary for even small devices so that it can be assured that they will work properly.


Post a Comment