Reverse Engineering Keyboard Driver: Part 2 (Decompiling .NET applications)
Rishit Bansal
Posted on October 5, 2021
Introduction
This post is a part of my series to reverse engineer the keyboard driver/Omen Light Studio application on my HP Omen Laptop and re-implement its functionality on Linux. In this post, I will be covering how to decompile .NET services/DLLs
Locating the Service
I have been using the Light Studio Application for a while, and one of its best features is the ability to set up dynamic "wave" lighting on your keyboard. In this setting, the lighting on the keyboard keeps changing dynamically to show a sort of wave animation. An intriguing behavior I have observed with this feature is that the animation works just once you have booted into Windows. During the boot process in Ubuntu, the lighting is set to the last set color from windows and remains static. This means that the animation is most likely set by a background service on windows which starts on boot and is not a feature of the keyboard on the hardware level.
So, I began by opening a task manager and looking for any relevant background services that are running. I found two of particular interest:
Omen Gaming Hub
Light Studio Helper
The second service looks more interesting, so I right-clicked on the service and selected "Open File Location", and found that it was located at C:\Program Files\HP\LightStudioHelper
We can see several files, but one which caught my eye was Newtonsoft.Json.dll. I instantly recognized it as a C# library (https://www.newtonsoft.com/json), as I have worked with it in the past. This is important, as this means that the application was likely to be written in .Net in C#.
Decompiling the Executable and DLL Files
Next, I looked for tools to decompile .NET applications. The top result was a free tool by Jetbrains called DotPeek. I began by opening the folder on DotPeek and it was able to decompile all the DLLs. The two results of most importance to us are the ones for LightStudioHelper and OmenFourZoneLighting:
The LightStudioHelper binary is what runs the background service. We start by looking at the Main() method in this class:
privatestaticintMain(string[]args){stringproductVersion=Process.GetCurrentProcess().MainModule.FileVersionInfo.ProductVersion;Logger.logger.Info("");Logger.logger.Info("----- Log Start, version: "+productVersion);if(Program.ProcessCommands(args)){Logger.logger.Info(string.Format("----- program exits, returnCode = {0}",(object)Program._returnCode));returnProgram._returnCode;}if(Program.IsAnotherInstanceRunning("OLS_HELPER")){Logger.logger.Info("----- program exits");returnProgram._returnCode;}Program.Cleanup();Program.CreateTimer();Program.CreateAndRunThread();Logger.logger.Info("----- program exits");returnProgram._returnCode;}
This method first gets the version of the service, processes command line arguments, and then checks if another process of the OLS_HELPER service is running (not sure why yet) and then runs the Cleanup() method:
The cleanup() method creates a TaskScheduler class, which is another user-defined class in the source code of the service.
The implementation for Stop() and Delete() can be seen in the source, but it's irrelevant as it just deals with killing any already running process of the LightStudioHelper. We concentrate on the CreateAndRunThread() method which is run next:
This is the main loop of the thread(), and on looking at it, we already got a lot of clues on how the background service works:
It maintains a 4 element array of Color, which is encouraging, as I know my keyboard has 4 configurable lighting zones, this might refer to the color of each zone!
The program has a while loop that seems to read colors from some sort of storage (LightStudioStorage<FourZoneLightingData>.ReadData()), and then stores the color data in the 4 element color array. It maintains a flag variable to check if any of the regions has a different color. Finally, if the flag variable is set, and FourZoneLighting.IsTurnOn() is true (presumably checking if the keyboard lights are turned on), it calls FourZoneLighting.SetZoneColors to set the colors.
I went in a little side adventure in checking out the LightStudioStorage and where it stores data, and found that it is a MemoryMappedFile:
This likely refers to some shared memory logic, and another process might be writing to this memory-mapped file and calculating colors based on an algorithm. For now, I stopped here, but this area might be interesting to look at in the future as well.
Moving on, I searched for the implementation of FourZoneLighting.SetZoneColors, and found it was implemented in OmenFourZoneLighting.dll:
This file basically seems too transfer the colors to another data structure, inputData, and then passes them to Execute():
privatestaticintExecute(intcommand,intcommandType,intinputDataSize,byte[]inputData,outbyte[]returnData){returnData=newbyte[0];try{ManagementObjectmanagementObject1=newManagementObject("root\\wmi","hpqBIntM.InstanceName='ACPI\\PNP0C14\\0_0'",(ObjectGetOptions)null);ManagementObjectmanagementObject2=(ManagementObject)newManagementClass("root\\wmi:hpqBDataIn");ManagementBaseObjectmethodParameters=managementObject1.GetMethodParameters("hpqBIOSInt128");ManagementBaseObjectmanagementBaseObject1=(ManagementBaseObject)newManagementClass("root\\wmi:hpqBDataOut128");managementObject2["Sign"]=(object)FourZoneLighting.Sign;managementObject2["Command"]=(object)command;managementObject2["CommandType"]=(object)commandType;managementObject2["Size"]=(object)inputDataSize;managementObject2["hpqBData"]=(object)inputData;methodParameters["InData"]=(object)managementObject2;InvokeMethodOptionsinvokeMethodOptions=newInvokeMethodOptions();invokeMethodOptions.Timeout=TimeSpan.MaxValue;InvokeMethodOptionsoptions=invokeMethodOptions;ManagementBaseObjectmanagementBaseObject2=managementObject1.InvokeMethod("hpqBIOSInt128",methodParameters,options)["OutData"]asManagementBaseObject;returnData=managementBaseObject2["Data"]asbyte[];returnConvert.ToInt32(managementBaseObject2["rwReturnCode"]);}catch(Exceptionex){Console.WriteLine("OMEN Four zone lighting - WmiCommand.Execute occurs exception: "+ex?.ToString());return-1;}}
This method seems to be doing the actual interaction with the hardware of the keyboard. I did a bit of research about ManagementObject and found that it's a class used to interact with WMI (Windows Management Instrumentation). WMI, specifically WMIACPI allows you to interact with the Bios and hardware devices, but more on this on the next blog post, for now, let us just treat this function as a black box which does some magic to set the colors of the keyboard.
Since now we have enough information on how the service works, I tried to implement everything in the OmenFourZoneLighting.dll file in my command line C# program for windows.
Rewriting the WMI Code in a C# program
I started by setting up a .NET console application on Rider and added the System.Drawing, and System.Management DLLs as assembly references from my system.
I copied most of the Code from the dotPeek decompiled result, and fixed some variable references, and wrote a CMD application which is available on this Github repository:
In this post, we saw how to decompile a C# application, and then implemented is using .NET Framework. In the next post, I will research more into ACPI and WMI drivers for Linux, to get a better idea of how to implement this functionality on Linux.