Disabling updates in MacsFanControl (by replacing a byte)

MacsFanControl is this utility that enables manual controls for my Macbook’s fans on Windows. Recently, I had the unpleasant chance to interact with their update mechanism.

Update pls

The update prompt must be acknowledged, and there is user-configurable option to disable or ignore the updates. Considering the app works just fine as it is, along with the fact that future updates will still have this issue, I chose to disable the updater permanently.

Digging into MacsFanControl.exe


Loading the binary in IDA and checking Strings yields promising results:

Updater strings

The nag string was right there, with only one reference to it.

Next up was finding the region where MacsFanControl decides to nag; by travelling “up” the cross references, there should eventually be a point where the decision-making instructions appear.

Skipping the irrelevant instructions was especially smooth due to IDA’s ability to identify and indicate Qt functions.

Travelling up the cross references

About 6 x-refs later, this jump-table appears, with the nag path at 0x436770 as one of the options Case 5 (top center). Interestingly, there are 2 paths where the nag path can take:

  1. Continue executing and therefore arrive at the default case (bottom left)
  2. Jump to 0x43679A, which then arrives at 0x43679F (bottom right)

0x43679F also happens to be Case 0 of the jump table.

The actions of the 2 branches can be understood from QDialog::exec(). Based on the nag dialog’s result, the application decides whether to

  1. Exit. It “does nothing” and ends up at the default case.
  2. Continue to start MacsFanControl, at 0x43679F or Case 0. This is where QApplication::exec() is called, and also where we’d like to go.

Decisions at the jumptable

Jumptable View-A

The jumptable above, which decides whether the application has to nag, works by..

  1. First having the zero-indexed result (which case?) stored in esi
  2. Checking if esi is greater than 5, and if so, jumps to the default case at 0x436769
  3. Determines the address to jump to: The jmp instruction jumps to the address stored in 0x436840 + (esi * 4)

Jumptable in hex

The memory region at 0x436840 contains 6 32 bit addresses (4 byte groups) in little-endian (expressed in reverse).

To reach the nag case, the value of esi has to be 5. The jmp instruction will read the value at 0x436854 calculated from 0x436840 + (5 * 4), and therefore arrive at Case 5 at address 0x436770 (70 67 43 00 in memory).

Patching away the nag

Ideally, the nag case should never be reached. One possibility is ensuring that esi never reaches 5, by disabling (nop) the instructions which assigns it, or by replacing the value of esi before the jump (mov esi, 0).

Another possibility, which I prefer, is to replace the addresses at the jumptable. Since Case 0 should be the way to go, replacing the address at 0x436770 (70 67 43 00) with the values of (9F 67 43 00) will reroute program execution to our desired destination whenever it decides to nag.

This process is incredibly simple - searching for the array of bytes 70 67 43 00 in your favourite hex editor and replacing 70 with 9F is all it takes to patch the application. (More specifically, at 0x35C54, replace 9F with 70)

Yay it works!

Yay! Turning off updates really shouldn’t require IDA..

Why not approach this from a network perspective

MacsFanControl stores the last known latest version that it has obtained from its servers. The nag will permanently remain until the application is updated.