Reverse Engineering Keyboard Driver: Part 2 (Decompiling .NET applications)
Published: 2021-10-05 10:19:22

## 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**

![Omen Gaming Hub Process](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lvd2p9ys07tvuw3mjxg6.png)


-  **Light Studio Helper**

![Light Studio Helper Process](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/39y65if43hqtw52bwe47.png)

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:
```C#
 private static int Main(string[] args)
    {
      string productVersion = 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));
        return Program._returnCode;
      }
      if (Program.IsAnotherInstanceRunning("OLS_HELPER"))
      {
        Logger.logger.Info("----- program exits");
        return Program._returnCode;
      }
      Program.Cleanup();
      Program.CreateTimer();
      Program.CreateAndRunThread();
      Logger.logger.Info("----- program exits");
      return Program._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:
```C#
private static void Cleanup()
    {
      Logger.logger.Info("Cleanup()");
      TaskScheduler taskScheduler = new TaskScheduler("LightStudioHelperTemp");
      taskScheduler.Stop();
      taskScheduler.Delete();
      DirectoryInfo directoryInfo = new DirectoryInfo(Program.InstallDirTemp);
      if (!directoryInfo.Exists)
        return;
      directoryInfo.Delete(true);
    }
```
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:

```C#
 private static void CreateAndRunThread()
    {
      Logger.logger.Info("CreateAndRunThread()");
      Thread thread = new Thread(new ParameterizedThreadStart(Program.ThreadLightingUpdate));
      thread.IsBackground = true;
      thread.Start();
      if (thread == null || !thread.IsAlive)
        return;
      thread.Join();
    }
```
This method creates a new [Thead](https://docs.microsoft.com/en-us/dotnet/api/system.threading.thread?view=net-5.0) which executes the `ThreadLightingUpdate` method in the background:
```C#
private static void ThreadLightingUpdate(object state)
    {
      Color[] colorArray = new Color[4];
      Logger.logger.Info("enter ThreadLightingUpdate()");
      while (Program._isRunning)
      {
        FourZoneLightingData zoneLightingData = LightStudioStorage<FourZoneLightingData>.ReadData();
        if (zoneLightingData != null && zoneLightingData.FourZoneColors != null)
        {
          bool flag = false;
          for (int index = 0; index < 4; ++index)
          {
            if (!flag && colorArray[index] != zoneLightingData.FourZoneColors[index])
              flag = true;
            colorArray[index] = zoneLightingData.FourZoneColors[index];
          }
          if (flag && FourZoneLighting.IsTurnOn())
          {
            Thread.Sleep(5);
            FourZoneLighting.SetZoneColors(zoneLightingData.FourZoneColors);
          }
        }
        Thread.Sleep(33);
      }
      Logger.logger.Info("leave ThreadLightingUpdate()");
    }

```

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:

1. It maintains a 4 element array of [Color](https://docs.microsoft.com/en-us/dotnet/api/system.drawing.color?view=net-5.0), which is encouraging, as I know my keyboard has 4 configurable lighting zones, this might refer to the color of each zone!
2. 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](https://docs.microsoft.com/en-us/dotnet/api/system.io.memorymappedfiles.memorymappedfile?view=net-5.0):
```C#
namespace CommonLib.SharedMemory
{
  public sealed class LightStudioStorage<T>
  {
    private static MemoryMappedFileHelper _mmfHelper = new MemoryMappedFileHelper(typeof (T).Name);

    private LightStudioStorage()
    {
    }

    public static void WriteData(T data)
    {
      if (LightStudioStorage<T>._mmfHelper == null)
        return;
      LightStudioStorage<T>._mmfHelper.WriteData<T>(data);
    }

    public static T ReadData()
    {
      T obj = default (T);
      if (LightStudioStorage<T>._mmfHelper != null)
        obj = LightStudioStorage<T>._mmfHelper.ReadData<T>();
      return obj;
    }
  }
}
```

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`:
```C#
public static bool SetZoneColors(Color[] zoneColors)
    {
      if (zoneColors.Length != 4)
        return false;
      byte[] returnData = (byte[]) null;
      int num = FourZoneLighting.Execute(131081, 2, 0, (byte[]) null, out returnData);
      Thread.Sleep(5);
      if (num == 0 && returnData != null)
      {
        byte[] inputData = returnData;
        returnData = (byte[]) null;
        for (int index = 0; index < 4; ++index)
        {
          inputData[25 + index * 3] = zoneColors[index].R;
          inputData[25 + index * 3 + 1] = zoneColors[index].G;
          inputData[25 + index * 3 + 2] = zoneColors[index].B;
        }
        num = FourZoneLighting.Execute(131081, 3, inputData.Length, inputData, out returnData);
      }
      return num == 0;
    }
```
This file basically seems too transfer the colors to another data structure, `inputData`, and then passes them to `Execute()`:
```C#
 private static int Execute(
      int command,
      int commandType,
      int inputDataSize,
      byte[] inputData,
      out byte[] returnData)
    {
      returnData = new byte[0];
      try
      {
        ManagementObject managementObject1 = new ManagementObject("root\\wmi", "hpqBIntM.InstanceName='ACPI\\PNP0C14\\0_0'", (ObjectGetOptions) null);
        ManagementObject managementObject2 = (ManagementObject) new ManagementClass("root\\wmi:hpqBDataIn");
        ManagementBaseObject methodParameters = managementObject1.GetMethodParameters("hpqBIOSInt128");
        ManagementBaseObject managementBaseObject1 = (ManagementBaseObject) new ManagementClass("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;
        InvokeMethodOptions invokeMethodOptions = new InvokeMethodOptions();
        invokeMethodOptions.Timeout = TimeSpan.MaxValue;
        InvokeMethodOptions options = invokeMethodOptions;
        ManagementBaseObject managementBaseObject2 = managementObject1.InvokeMethod("hpqBIOSInt128", methodParameters, options)["OutData"] as ManagementBaseObject;
        returnData = managementBaseObject2["Data"] as byte[];
        return Convert.ToInt32(managementBaseObject2["rwReturnCode"]);
      }
      catch (Exception ex)
      {
        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](https://docs.microsoft.com/en-us/samples/microsoft/windows-driver-samples/wmi-acpi-sample/) 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](https://www.jetbrains.com/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:

{% github thebongy/omen-cli %}

## Conclusion
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.