Discovering a Revoked LG Monitor Firmware Update

David Zech
6 min readApr 23, 2022
Photo by ETA+ on Unsplash

The current offerings of monitors flat out suck, and unfortunately, I chose to curse myself with two LG 27GP950-B (4k 144hz) monitors. As I detailed in my previous post, I have quite the complicated desk setup consisting of a PC and Mac Studio connected through a KVM switch. Typically, KVM’s do not repeat the video display signals passing through it, meaning that a 6 foot DisplayPort cable feeding into the KVM in combination with another 6 foot cable feeding out of the KVM will effectively result in a 12 foot long cable with the additional signal noise introduced by the KVM itself. This is less than ideal because DisplayPort cables in general are trash, and just barely pass the minimum performance necessary to be validated for a particular speed at the manufacturered length. Conjoining multiple of these cables in series is a recipe for disaster, which is why I ended up replacing every DisplayPort cable I could with Fiber Optic DisplayPort Cables. Unfortunately, the Mac Studio only supports DisplayPort via its Thunderbolt 4 ports, which requires a USB-C to DisplayPort cable. As far as I know, there are no fiber optic offerings for such a cable, and the quality of them are generally pretty poor, having gone through several brands and returning them for poor performance. I am keeping my fingers crossed that Moshi Bi-directional USB-C to DisplayPort cables (recommended by Wendell from Level1Techs) work better.

Frustrations of Monitors and macOS

Interposing any kind of extra hardware between a monitor and a computer exacerbates the myriad of issues that can surface with poor DisplayPort signals. The screen can intermittently flicker black, contain weird sparkly effects, and flat out just not wake up from sleep. Often times when I try and wake my Mac, the monitors sometimes just won’t wake up, despite being seen and recognized as attached by macOS. This is a particularly annoying problem that is due to both faulty firmware on the monitor, and a less than stellar DisplayPort implementation in macOS. One of the remedies is to disable DisplayPort Deep Sleep which is often present and default enabled on LG monitors. Unfortunately, the LG 27GP950 firmware didn’t have the option to disable this feature out of the factory, until they released a firmware update in March 2022. To add insult to injury, LG ended up pulling the firmware update from their monitor service application called OnScreenControl. Of course I was sad that a solution to my problem was no longer available, but a pulled firmware indicates it was buggy and wasn’t worth flashing… usually. I noticed there was absolutely zero reports of regressions in monitor performance from people who have applied the update — sure it didn’t fix all of the problems they might have had, but it surely didn’t cause more. So, I set out to try and see if any heroic soul happened to save the firmware zip from their temporary folder so I could perhaps flash my monitor manually.

Finding the Pulled Firmware Update

I had little hope that someone would have saved the firmware update. It simply just isn’t something that one would ever consider randomly doing, and even if they wanted to, they wouldn’t put in the effort to find out where it is even temporarily stored. I quickly gave up on incessantly googling around the internet, and had a fleeting thought that maybe LG would be foolish enough to leave enough breadcrumbs in the code of OnScreenControl that would let me recover it. Common sense would tell you that a pulled update means it simply is not available from their servers anymore, so no amount of reverse engineering would save me anyway.

A few days ago, after being very frustrated by having to reset my monitors over and over again by pulling the power cord, I decided to ultimately take a jab at examining the OnScreenControl code for any clues. I loaded the binary up in IDA Pro, and was pleasantly greeted with the fact that the application is written in .NET. For those unfamiliar with reverse engineering, examining something written in .NET is a god send because the assemblies contain copious amounts of symbol information, and can be easily reverted into accurate C# source code using a decompiler. I’m pretty rusty when it comes to reverse engineering, and I remember from about a decade ago that .NET Reflector was all the rage. Now I noticed that the venerable JetBrains has a solid free offering called dotPeek, which seems just about end-game for my purposes (thanks JetBrains!).

Reversing OnScreenControl

Upon loading the assembly in dotPeek, I was immediately greeted with a couple namespaces that looked like exactly what I needed.

Reversing .NET is always a cake walk

Uncollapsing the FWUpdateCheck tree yielded immediately even more interesting methods:

Everything we need, staring right as us

I don’t think I’ve ever found what I’ve needed so fast in my entire career. I guess the fact that everything else is prefixed with OnScreenControl* made life easy.

If we look at getDownloadUpdateCheckURL, we can see the logic of how the application constructs a URL that ultimately points to a .txt file that contains the full file name of the latest firmware version for a particular monitor model. The final firmware download url is constructed by appending the name from before to:

lmu.lge.com/ExternalService/onscreencontrol/fw/27GP950_DSC/${filename}

Perfect, so for my monitor the application checks https://lmu.lge.com/ExternalService/onscreencontrol/fw/27GP950_DSC/FWLatestVersion.txt, which will return a filename of the latest firmware, so all we have do is curl that and…

curl https://lmu.lge.com/ExternalService/onscreencontrol/fw/27GP950_DSC/FWLatestVersion.txtNotAllowedPage %

Uh oh… that’s weird. I confirmed there was no authentication of any sort to the URL, and oddly enough, checking the same resource for the very similar previous gen model 27GN950 (N instead of P) does indeed have content:

curl https://lmu.lge.com/ExternalService/onscreencontrol/fw/27GN950_DSC/FWLatestVersion.txtMODEL_27GN950_SV3.09_NV5.57_20210604.zip%

It turns out about half of the model URLs end up with NotAllowedPage as well, which sort of makes me feel like LG just fucked up their update server and didn’t bother to fix anything.

Anyhow, from the information I’ve gathered from various reddit posts, the Firmware I am looking for is described as the 3-Tuple (4.04, 31, 6.0.6). It was also released around March 10, 2022, so we can try just brute forcing a couple URLs and see if anything hits:

curl https://lmu.lge.com/ExternalService/onscreencontrol/fw/27GP950_DSC/MODEL_27GP950_SV4.04_NV6.06_20220310.zipNotAllowedPage %

No luck. I tried basically every date from mid February to mid March and got no hits. I figured at this point, I need to figure out how to interpret the version numbers correctly. Turns out, parseNewFWICVersions() contains the dictionary to decode what the version numbers map to:

So SV means Scaler Version, NV means NXP Version, and DV means DSC Version. DSC stands for Display Stream Compression, and since it seems to be attached to these particuarly models filepaths, I took a leap of faith and cosntructed a new url as such:

curl https://lmu.lge.com/ExternalService/onscreencontrol/fw/27GP950_DSC/MODEL_27GP950_SV4.04_DV31_NV6.06_20220307.zipWarning: Binary output can mess up your terminal. Use "--output -" to tellWarning: curl to output it to your terminal anyway, or consider "--outputWarning: <FILE>" to save to a file.

Awesome, we struck gold! I hastily downloaded the file, stored it in safe keeping, and flashed my monitor. (Note: even though it is a zip file, you do not need to unzip it. OnScreenControl accepts the .zip directly). The update took an excruciatingly long 10 minutes, every second of which I hoped my monitor wasn’t bricked from applying a pulled firmware update. Thankfully, it worked, and I now have the newly added option to disable DisplayPort Deep Sleep in the monitor options menu. Unfortunately, I still have to turn off Display Stream Compression to have things work reliably without flickering, which limits me to 8bit color and 120hz as opposed to 10bit color and up to 160hz. But until I find some amazing cables or DisplayPort 2.0 finally arrives, I’m fairly happy with my setup now.

--

--