You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: content/post/reversing-nitro/index.md
+67-26Lines changed: 67 additions & 26 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,42 +1,41 @@
1
1
---
2
-
title: Reverse Engineering A Windows Driver For Linux
3
-
description: Turning the unsupported into supported
2
+
title: 'The Key That Did Nothing: My Journey Into the Linux Kernel'
3
+
description: A rabbit hole of reverse engineering, WMI calls, and kernel patches
4
4
slug: reversing-turbo
5
5
date: 2025-04-30
6
6
categories: dev
7
-
draft: true
7
+
draft: false
8
+
image: cover.png
8
9
---
9
-
I was recently watching PewDiePie's [video](https://youtu.be/pVI_smLgTY0?si=Yj5UFrUWDxJg7mFV) on his experience switching to Linux and something he said reminded me of one of the reasons why I enjoy using this operating system so much—the absolute freedom you have. If something doesn't work the way you want it to or a certain feature is unsupported you can dig into the code and fix it yourself!
10
-
11
-
As promised on my [blog-post]({{<ref "lfx-mentorship/#enabling-turbo-support-on-my-laptop">}}) about the Linux Kernel mentorship program, this article will be about my endeavours in trying to enable [turbo]({{<ref "lfx-mentorship/#enabling-turbo-support-on-my-laptop">}}) support for my laptop to Linux.
10
+
As promised on my [blog post]({{<ref "lfx-mentorship/#enabling-turbo-support-on-my-laptop">}}) about the Linux Kernel mentorship program, this article will be about my endeavours in trying to enable turbo support for my laptop on Linux.
12
11
13
12
## How it all began
14
13
It all started one day when I was sitting in front of my laptop completely zoned out. My eyes glanced upon the keyboard and I caught myself staring at a key which usually opens an app to change performance profiles of the laptop.
15
14
16
-
Now, much to the surprise of nobody, this key did not do anything on Linux since the NitroSense app (as is the case with most proprietary software) only works on Windows. I still wondered if it was possible to change thermal profiles on Linux. To my surprise, someone had already made a [kernel module](https://github.com/JafarAkhondali/acer-predator-turbo-and-rgb-keyboard-linux-module) for this purpose! Unfortunately (or fortunately, since we wouldn't have this article otherwise :)) it only supported the Acer Predator Series of laptops, whereas my laptop was from the Nitro Series.
15
+
Now, much to the surprise of nobody, this key did not do anything on Linux since the NitroSense app (as is the case with most proprietary software) only works on Windows, regardless, I still wondered if it was possible to change thermal profiles on Linux. To my surprise, someone had already made a [kernel module](https://github.com/JafarAkhondali/acer-predator-turbo-and-rgb-keyboard-linux-module) for this purpose! Unfortunately (or fortunately, since we wouldn't have this article otherwise :)) it only supported the Acer Predator Series of laptops, whereas my laptop was from the Nitro Series.
17
16
18
-
Regardless, it proved as a good starting point and while going through the source code, I realized that this module was nothing more than a modified fork of a module from the platform profile subsystem on the kernel tree.
17
+
Anyways, it proved as a good starting point and while going through the source code of the project, I realized that this module was nothing more than a modified fork of a module from the platform profile subsystem on the kernel tree.
19
18
20
19
## Let the tinkering begin! 🛠️
21
-
### Discovering WMI
22
-
After a brief skim through the project, I understood that things like RGB LEDs, fan profiles and certain other hardware related functionalities are often controlled through something known as [WMI](https://en.wikipedia.org/wiki/Windows_Management_Instrumentation), during this phase I also chanced upon a youtube [miniseries](https://www.youtube.com/watch?v=97-WNhUmoig&list=PLv2kA4LxAI4Dq2ic_hU9bdvxIzoz5SzBr) created by the author of the project which gave me some great insight into how this project was built.
20
+
### Finding The Hidden Interface
21
+
After a brief skim through the project, I understood that things like RGB LEDs, fan profiles and certain other hardware related functionalities are often controlled through something known as [WMI](https://en.wikipedia.org/wiki/Windows_Management_Instrumentation), during this phase I also chanced upon a youtube [miniseries](https://www.youtube.com/watch?v=97-WNhUmoig&list=PLv2kA4LxAI4Dq2ic_hU9bdvxIzoz5SzBr) created by the author of the project which gave me some great insight into how this project works under the hood.
23
22
24
23
In short, the WMI [interface](https://docs.kernel.org/wmi/acpi-interface.html) allows software to communicate with the hardware by sending certain commands. These commands are handled by special WMI entries defined in the ACPI tables stored in the system firmware. That's a whole lot of words just to say — system send command, hardware do thing.
25
24
26
25
### Playing around with WMI
27
-
First off I started by randomly tweaking a few values in the source code (*sidenote: you probably shouldn't be doing this, especially at the kernel level!*) and briefly got my fans to spin to their maximum speeds but the thermal profiles did not seem to budge, my CPU was still throttled at a respectable 3.2 GHz.<br>Regardless, this confirmed my assumptions regarding WMI and thus I booted into Windows to monitor WMI activity and sure enough, whenever I changed thermal profiles using the app, a WMI event was registered on the [Windows Event Viewer](https://en.wikipedia.org/wiki/Event_Viewer).
26
+
First off I started by randomly tweaking a few values in the source code (*sidenote: you probably shouldn't be doing thisat the kernel level!*) and briefly got my fans to spin to their maximum speeds but the thermal profiles did not seem to budge, my CPU was still throttled at a respectable 3.2 GHz and I was none the wiser<br>Regardless, this confirmed my assumptions regarding WMI and thus I booted into Windows to monitor WMI activity and sure enough, whenever I changed thermal profiles using the app, a WMI event was registered on the [Windows Event Viewer](https://en.wikipedia.org/wiki/Event_Viewer).
28
27
29
-
The monitor told me that two WMI functions - `SetGamingFanBehavior` and `SetGamingMiscSetting` were called for changing fan speeds and applying overclocks respectively. Just knowing this alone wasn't enough though, I also needed to know what inputs are fed into these methods so that they actually do something. Sadly, the monitor provided no means to track inputs.<br>
28
+
The monitor told me that two WMI functions - `SetGamingFanBehavior` and `SetGamingMiscSetting` were called for changing fan speeds and applying overclocks respectively. Just knowing this alone wasn't enough though, I also needed to know what inputs are fed into these methods so that they actually do something. The event viewer, sadly, provided no means to track inputs.<br>
30
29
31
30
### WMI Explorer
32
-
Initially, I tried to do some trial and error using a tool I discovered called [WMI Explorer](https://github.com/vinaypamnani/wmie2) to manually invoke these functions but it didn't seem to do anything. I later realized that the only way to figure out what the inputs are is to reverse engineer the program which calls them.
31
+
Initially, I tried to do some trial and error using a tool I discovered called [WMI Explorer](https://github.com/vinaypamnani/wmie2) to manually invoke these functions but it didn't seem to do anything. I later realized that the only way to figure out the required inputs was to reverse engineer the program which calls the function.
33
32
34
-
I also made a small documentation [patch](https://web.git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/diff/Documentation/wmi/driver-development-guide.rst?id=98e45f0d7b99ceac029913ce3a161154a8c4c4a7) during this time to mention this tool in the WMI driver development [guide](https://docs.kernel.org/wmi/driver-development-guide.html).
33
+
I also made a small documentation [patch](https://web.git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/diff/Documentation/wmi/driver-development-guide.rst?id=98e45f0d7b99ceac029913ce3a161154a8c4c4a7) during this time to mention this neat little tool in the WMI driver development [guide](https://docs.kernel.org/wmi/driver-development-guide.html).
35
34
36
35
## Reversing the NitroSense app
37
-
Thus it began, my first foray into reverse engineering a real app. The NitroSense app was written in C# and thus I used dotPeek to decompile it.
38
-
### Fan Speeds Cracked
39
-
While searching for the input values for the overclock WMI call, I chanced upon a function that was responsible for setting the fan speeds -
36
+
Thus it began, my first foray into reverse engineering a real app. The NitroSense app was written in C# and thus I used dotPeek to decompile it.
37
+
### Fan Modes Cracked
38
+
While searching for the input values for the overclock WMI call, I coincidentally chanced upon the function that was responsible for setting the fan modes —
40
39
```C
41
40
public staticboolset_all_fan_mode(CommonFunction.Fan_Mode_Type mode_index)
42
41
{
@@ -57,7 +56,7 @@ public static bool set_all_fan_mode(CommonFunction.Fan_Mode_Type mode_index)
57
56
}
58
57
```
59
58
60
-
As we can see, input values of the WMI function are readily available here! There are three magic values which correspond to the three different fan modes. Was a bit surprised to the see the typo in the 'intput' variable though, kind of refreshing to know that even billion dollar companies have such mistakes in their code lol.
59
+
As we can see, input values of the WMI function are readily available here! There are three magic values which correspond to the three different fan modes. Was a bit surprised to the see the typo in the 'intput' variable, kind of refreshing to know that even billion dollar companies have such mistakes in their code lol.
61
60
62
61
### In Search Of Overclocks..
63
62
The overclock function was sadly not as simple of an egg to crack. I traced through the GUI code for the app and narrowed it down to this particular function -
@@ -86,27 +85,69 @@ public static async Task<int> set_operation_mode(int Operation_Mode)
86
85
}
87
86
}
88
87
```
89
-
Don't worry if it all looks like gibberish to you, it did to me as well when I first came across it. Since i was wholly unfamiliar with the Windows API, I took to ChatGPT to explain the code to me like a 5 year old. GPT explained that this function was creating something called a ["Named Pipe"](https://en.wikipedia.org/wiki/Named_pipe) which is used for inter-process communication.
88
+
Don't worry if it all looks like gibberish to you, it did to me as well when I first came across it. Since i was wholly unfamiliar with the Windows API, I asked ChatGPT to explain the code to me like a 5 year old. GPT promptly explained that this function was creating something called a ["Named Pipe"](https://en.wikipedia.org/wiki/Named_pipe) which is used for inter-process communication.
90
89
91
90
Uh oh, this meant that the actual WMI call was was being made by a process on the receiving end of this pipe. Interestingly, I noted that the name of the pipe was 'PredatorSense_**service**', suggesting that the recipient process was a service.
92
91
93
92
While we are here, let me also comment that the `Operation_Mode` argument of `set_operation_mode()` took one of three values: 0, 1 or 4. Which makes sense considering that my lapop has three performance modes.
94
93
95
-
### The Final Gatekeeper
96
-
Sure enough, there was indeed a service in services.msc named `Predator Service`, the service started a process called `Pssvc.exe`. This particular program was written in C++, I initially used IDA to dissassemble it before remembering that I had pretty much 0 knowledge of assembly ;-;
94
+
### The Final Piece Of The Puzzle
95
+
Sure enough, there was indeed a service in services.msc named `Predator Service`, the service started a program called `Pssvc.exe`. This particular program was written in C++, I initially used IDA to disassemble it before realizing that I had pretty much 0 knowledge of assembly ;-;
97
96
98
-
That's when I remembered Ghidra, a reversing tool developed by the NSA, that I had used during my first CTF contest - inCTFj. Ghidra tries its best to produce a readable C-like program from the disassembled code.
97
+
That's when I remembered Ghidra, a reversing tool developed by the NSA, that I had used during a CTF contest that I had played a long time ago. Ghidra tries its best to produce a readable C-like program from the disassembled code.
99
98
100
99
After some heavy decompiling on the service file using Ghidra I finally found out that the value is read from the named pipe and then used to call a function from a function pointer table as follows:
Here, `&PTR_LAB_140052c90` refers to the following function pointer table:
103
+
Here, `&PTR_LAB_140052c90` refers to the following function pointer table -
105
104
106
105
<imgsrc="function_table.png"width="480">
107
106
108
-
If you remember from [earlier](#in-search-of-overclocks), a command index of 30 was passed into the named pipe along with the Operation Mode and this corresponds to the function pointer that I've highlighted in the image. Here is the relevant section of the code which calls this particular function:
107
+
If you remember from [earlier](#in-search-of-overclocks), a command index of 30 was passed into the named pipe along with the Operation Mode. This index corresponds to the function pointer that I've highlighted in the image.
108
+
109
+
I still needed to know what the arguments are for this function, here is the relevant section of the code which receives the input from the named pipe and calls this function —
110
+
111
+
<imgsrc="ghidra_decomp.png"width="580">
112
+
113
+
Yes, it's one hell of a monstrosity—but after staring at it for a long time, I figured out that all it does is pass an array of bytes from the named pipe with their corresponding byte offsets to the function. You can observe this in the last line of code which actually performs the function call, `puVar5` is the array of bytes and `puVar8` are the byte offsets (I think anyways)
114
+
115
+
### Jackpot
116
+
Finally, we are ready to analyze the function that makes the WMI call —
117
+
118
+
<imgsrc="final_function.png"width="580">
119
+
120
+
This function dereferences the values from the byte array and performs a bitwise operation (`value << 8 | 0xb;`) on them before passing them to the WMI call. Doing this manually on the `OperationMode` values from earlier, we get the following values for WMI calls:
121
+
```rust
122
+
//SetGamingMiscSetting
123
+
Performance:1035
124
+
Default:267
125
+
Quiet:11
126
+
127
+
//SetGamingFanBehavior
128
+
Custom:12779520
129
+
Auto:4259840
130
+
Max:8519689
131
+
```
132
+
Using WMI Explorer to call this function with this values does indeed change performance profiles and fan speeds! All of this reversing had finally paid off! Or so I thought..
133
+
134
+
## Writing The Patch
135
+
After all of that work—reverse engineering the NitroSense app and painstakingly obtaining the correct values, I was ready to write my patch. Which is when I discovered that the kernel module, [acer-wmi](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/platform/x86/acer-wmi.c), which adds WMI functionality to Linux for my laptop already had all of the above values defined! 🙃
136
+
137
+
Now this truly threw me for a loop, if someone had already taken the time to reverse these values why weren't they working on my laptop? Initially I thought this was because my turbo button was not producing a WMI event when pressed.
138
+
139
+
I [reached out](https://lore.kernel.org/platform-driver-x86/CALiyAom1xDH6A0Q2WNHCMUcpMJfM3pXO2DaW=bgHGUi8ZOpBbQ@mail.gmail.com/) the maintainers on the platform profile subsystem asking for help and they were more than ready to help. In fact, one of them even offered to write the patch for me! However since my main aim was to learn, I politely refused and he was kind enough to guide me in the right direction.
140
+
141
+
From there, I learnt about ACPI tables where these WMI functions are defined and the corresponding [tools](https://unix.stackexchange.com/questions/534429/how-to-print-the-acpi-table) used to read them. After scanning through the ACPI code I realized that the Predator and Nitro series of laptops share the same values for the profiles and that the core issue was that the Predator series supports two additional profiles.
142
+
143
+
The `acer_wmi` module had the `supported_profiles` value hardcoded for the Predator. However, it should have been set dynamically, since the hardware had another WMI call (`GetGamingMiscSetting`) that returns a bitmap indicating the supported performance profiles. This function was simply not being utilised by the current driver.
144
+
145
+
Hence, my [patchset](https://lore.kernel.org/all/20250113-platform_profile-v4-0-23be0dff19f1@gmail.com/) essentially involved adding dynamic support for setting the supported platform profiles and some other miscellaneous improvements to the platform profile handling for acer laptops. After these patches got merged, I could finally set my performance profiles from the sysfs [interface](https://docs.kernel.org/userspace-api/sysfs-platform_profile.html) at `/sys/firmware/acpi/platform_profile`!
146
+
147
+
We weren't done yet though. One last issue I was facing is that on Windows, my CPU was able to reach its max thermal limit of 100°C when operating in performance mode but on Linux, it was throttling at 92°C. I initially tried tweaking CPU frequency scaling drivers and other settings without success. After a lot of trial and error, a maintainer suggested installing [thermald](https://man.archlinux.org/man/extra/thermald/thermald.8.en)—and that ended up solving the problem!
148
+
109
149
110
-
<imgsrc="ghidra_decomp.png"width="480">
150
+
## Conclusion
151
+
That just about does it, in this rather wordy blog post I've written down (almost) my complete approach to adding some unsupported functionality for my hardware to the Linux Kernel. Have to say, while it was certainly frustrating at times, it was a great deal of fun. Rarely do you actually get to go down to the basics like ACPI tables or manually juggle bitmasks to solve problems with your laptop. It's hard to explain but there is a sort of raw simplicity operating at such a low level, everything merely does what its meant to do and it does it well. There's no hidden abstractions or sneaky gotcha's that surprise you.
111
152
112
-
Yes it is one hell of a monstrosity, but after staring at it for a long time, I figured out that all it does is pass an array of bytes taken from the named pipe and their corresponding byte offsets to the function. You can observe this in the last highlighted line of code, `puVar5` is the array of bytes and `puVar8` are the byte offsets (I think anyways)
153
+
Going through this whole process also made me realize that you can literally solve any problem you have with software, as long as you have the grit to stick with it and see it through.
0 commit comments