User:Cyanic/Steam DRM

From PCGamingWiki, the wiki about fixing PC games

I dunno if more in depth information about DRM schemes are encouraged or discouraged, so instead of the main namespace, this page is a subpage of my user page. The purpose of this page is to expose some technical information about Valve's DRM that many developers have elected to use. It's only going to be a general overview of the scheme; you won't be able to write an unpacker based on this information alone.

Steamworks Digital Rights Management wraps your game's compiled executable and checks to make sure that it is running under an authenticated instance of Steam. This DRM solution is the same as the one used to protect games like Half-Life 2 and Counter-Strike: Source. Steamworks DRM has been heavily road-tested and is customer-friendly.
—Valve, Steamworks API overview[1]

Note Half-Life 2 and Counter-Strike: Source aren't actually Steam DRM protected.

Basic facts

  • Steam DRM is a wrapper around game executables that ensure Steam is running and that the Steamworks API will be available when the game executes. Another key function is to ensure the Steam user that launched the game actually owns the game. The other use for Steam DRM is to encrypt the game code to deter tampering or reverse engineering of the game code.
  • Another common name for the scheme is Steam Stub.
  • There are currently four major variations of the scheme.
  • The protection does not require information from Steam to decrypt and load the executable. Steam is only used for verification. As such, it is possible to unpack a Steam DRM protected executable using a standalone program.
  • Steam DRM is not the same as Custom Executable Generation (CEG). It is also not the Steam Client as DRM (i.e. requiring the Steam Client to be running to play a game is seen as DRM).

Typical reasons for using Steam DRM

As mentioned above...

  • To help curb piracy. For the typical user, they won't be able to run the game if they don't own it. But this doesn't really stop scene groups, especially considering how weak the protection is. It is interesting to note that many otherwise DRM free games on Steam that have no Steamworks integration whatsoever become tied down with Steam because their developers chose to use this DRM. There are numerous examples, including Prey, Project Freedom, Mirror's Edge, LUDWIG, Alpha Prime, Chaser, Chrome, Chrome: SpecForce, Greed: Black Border, and Shadowgrounds: Survivor. The list goes on and on.
  • To ensure Steamworks API is available. Many developers don't bother writing code that handles errors initializing Steamworks, instead choosing to bail. Many are also too lazy to use the proper API to relaunch the game in case the game wasn't launched from Steam. Some don't even care about protecting their code[2]. Steam DRM in this case is seen as a good choice because the developer can make the assumption Steamworks is available and write less error handling code.
  • To prevent code tampering. Some developers don't want to see people cheat (especially on multiplayer) or steal their assets.
  • To make DRM more user-friendly. Some games before they came to Steam had rather intrusive DRM (such as SecuROM). The developers still want their game to be DRM protected, but don't want to deal with e.g. managing serial numbers, so Steam DRM is used instead of the previous scheme. This shifts authentication from machine-based to account-based, and also makes the DRM more transparent to the player.

General structure (Windows)

A Steam DRM protected executable is modified such that when it starts, the first code that gets executed is the DRM code. This code is responsible for checking ownership. Once complete, the game code is decrypted (if the code was encrypted) and control is passed to the original game code. This "stub" code resides in a PE section named ".bind" tacked on to the end of the executable. The executable is modified to start in this section instead of its original start address. Some games may contain overlay data. Those are changed to a new PE section named ".extra". No changes to any data sections are made. Note code encryption is optional in all versions and there are numerous Steam DRM protected executables that do not have the original code encrypted. Executable checksum is recalculated after wrapping if it existed originally. Sometimes a Valve custom signature is applied in the executable header to ensure authenticity.

It is possible to use other forms of DRM alongside Steam DRM. The additional DRM could be applied before wrapping with Steam DRM (such as SolidShield Wrapper in Crysis 2: Maximum Edition) or after (such as SecuROM in Dark Void).

Versions (Windows)

There are four major revisions of Steam DRM.

Note all versions can perform a checksum of the config and stub code.
An interesting identifying mark of Steam DRM in all versions except 1.0 is the presence of the value 0xCODECODE in little-endian order near the stub code or configs.

Version 1.0 (2007)

ProtectionID cannot identify this version.
  • Stub code looks to be unoptimized machine generated assembly with no embedded runtime components.
  • Has embedded configuration, which consists of import pointers, code information, encryption information, app ID, and a bunch of strings.
  • Basic ownership check, no game code encryption.
  • Can wrap relocatable executable, but disables ASLR on the executable (via PE header).
  • No 64-bit support.
  • Cannot wrap if any data directories reside within the PE header (e.g. configuration directory).

Version 1.5 (2007-2008)

ProtectionID identifies this as "Steam Stub", but can't identify earlier versions that have the CRC check in a different order from the rest of the code.
  • Does something interesting with threading. Has option to ensure that there is only one thread in the process.
  • Has options to verify that Steam.dll (this is pre-Steamworks) has a valid digital signature (anti-tampering to prevent launching unowned games).
  • Code encryption implemented. If encryption is used, the code is encrypted with an odd block cipher where it uses the CBC mode of operation, but doesn't actually try to encrypt the blocks themselves (i.e. it relies entirely on the CBC mode to do the encryption).
  • Can display error messages in case there is a problem. The error message was changed at some point (probably some time in 2008) to the current message with the return code from GetLastError().
  • Can wrap executables using TLS, but those using TLS callbacks require the code to be left unencrypted. (Attempting to execute the callbacks with encryption will cause exceptions, which the PE loader will handle, but TLS will not be fully initialized and may result in glitches.)

Version 2 (2008-2013)

ProtectionID identifies this as "Steam Stub (new #1)", and will indicate if the code is encrypted. For some reason it might also identify this as "Steam Stub" version 1.
  • Stub code looks even less optimized, storing the same variable multiple times and typically only using each location once.
  • The game loading process has been beefed up significantly. Perhaps the previous version was too easy to unpack (the encryption used was very cryptographically deficient).
  • Contains debugger detection. Doesn't use the usual Windows API, but the PEB.
  • Contains an embedded, encrypted DLL that does the brunt of the work. DLL loading is done by the stub itself instead of calling a Windows API, so the DLL never gets written to disk.
  • Uses three stages of encryption. First stage decrypts the config (using odd block cipher mentioned above), which contains import pointers, code information, app ID, and information about the remaining stages. Second stage contains information needed by stage three and is decrypted with same block cipher. Third stage is the DLL, which is XTEA encrypted. Once decrypted and loaded, control is passed to it along with a pointer to stage two.
  • Uses public-key cryptography signature verification. Verification is performed on steamclient.dll and the game executable itself if it is signed. This is a more rigorous tamper prevention mechanism than in the previous version.
  • Compares app ID from Steam API with built in value. Prevents using steam_appid.txt to substitute an owned app ID to make it seem the game is owned.
  • Offsets to important information in stage two is embedded directly inside the DLL. Stage two looks like a few KBs of random data, but actually has real data at random offsets. It is hard to distinguish the fake data from real data, and to obtain the offsets the DLL must be decrypted and interpreted, setting moderately difficult obstacles for automatic unpacker writers. This also means each DLL is unique as random offsets are used on different executables.
  • The game code itself is encrypted with AES-256. This prevents decryption of the code based on pattern recognition.
  • New error reporting mechanisms. Error message box also changed. There are lots of error codes that can be displayed.
  • There was a major DLL change around 2011, where they switched to a more rigorous authentication API.
  • Has different flags from version 1.
  • Embedded DLL differences: 2.0 uses ISteamUtils::GetAppID() to check app ID. 2.1 adds a check with ISteamApps::BIsSubscribedApp(). 2.2 uses ISteamAppTicket instead of ISteamApps.
  • If a certain imported function required by the bootstrap is not found in the original EXE, the EXE's import directory is copied to the .bind section and the required import appended.

Version 3 (2013-)

ProtectionID identifies the 32-bit version as "Steam Stub (new #2)", and will indicate if the code is encrypted. The 64-bit version does not appear to be detected right now.
  • Current version of the DRM.
  • Code is a bit nicer, looks like it could have been written by a human or just a better code generator.
  • Slightly less secure than version 2, in that it no longer has two separate stages for config and important values. So no need to decrypt the DLL to decrypt the game code.
  • New addressing system uses relative addresses instead of absolute ones.
  • Imports from MSVCRT for some reason.
  • Much better support for different kinds of executables. Can finally fully handle relocatable modules, and also those that use TLS callbacks.
    • This also means that you have to watch out for TLS callbacks and deal with them when unpacking. Remember those callbacks are executed before the main entry code.
  • Can protect 64-bit executables. Basic process is still the same, only modification is to use 64-bit addresses instead of 32-bit ones.
  • DLL actually has the DRM code separated into functions as opposed to basically laying it flat in version 2.
  • Somewhat more granular error messages. Took out some error handling code.
  • Some code that was originally in the stub (such as the game relaunching code) was moved into the embedded DLL.
  • Interestingly, this version's bootstrap is two-part. The first part runs the second part, and the second part is actually the code section of a DLL copied verbatim. The "nicer looking code" mentioned earlier is probably compiler-generated.
  • Can wrap modules that have directories residing in the PE header. However, such directories won't be copied over after wrapping.

PECompact (2006 and earlier)

Not strictly Steam DRM, but it does serve the same purpose.

  • Standard PECompact compression with a Steam ownership check tacked on the end of the primary loader.
  • Incredibly hard to restore original executable due to the stripping of a lot of metadata from the PE header and rearrangement of sections.

Application load error

Examples

A Steam DRM version 1.5 error looks like this:
Steamdrm v1 error.png

A Steam DRM late version 1.5 or version 2/3 error looks like this:
Steamdrm v2 error.png
Note the code after the colon. Subtract 65432 from this value (it's in decimal), and you will get the return value from GetLastError(). See MSDN for its meaning.

Version 1.0 returned error codes as integers. Version 1.5 and above return the error code converted from characters into ASCII codes.

Common error codes

Below are some common error codes (per Google) and their descriptions:

  • 2: Can't load Steam.dll.
  • 3: Steam.dll, steamclient.dll, or game executable tampered with and failed signature check.
This may be caused by one of the mentioned files being corrupted, or if your antivirus software is modifying executables in-memory. If your files check out, try temporarily disabling your security software. Known offenders include F-Secure DeepGuard[3].
  • 5, P: Ownership checking error, probably can't initialize Steamworks API.
A common cause of being unable to initialize Steamworks API is not having a valid app ID.

Assuming you're launching the game from Steam...

Restart Steam[4]

Sometimes it fixes the problem, sometimes not. If not, reboot your computer too.

If for some reason you can't launch the game from Steam (e.g. using a mod manager), you can manually set the app ID to help the API initialize.

Create steam_appid.txt file
  1. Navigate to the game's installation folder.
  2. Create a new text file named steam_appid.txt.
  3. Open the file, and type in the game's app ID. You can find the app ID using SteamDB or by going to the game's Community Hub, and using the group of digits at the end of the Hub's URL.
  4. Save the file.
If Steam DRM version 2 failed to communicate with Steam, it will attempt to run Steam.exe. Instead of looking up the Steam installation directory from the Registry, it naively tries to locate the file in parent directories. This can be a problem if you have the game installed outside the default game installation location, and results in error 5 being shown. This problem has been addressed in version 2.2 and 3. Version 1 has a similar problem, but won't give any visible indications (the game will just quit). Interestingly enough, version 1 does not cause the game to be relaunched from Steam if the game's EXE was executed on its own and Steam was running.
Create a symbolic link to Steam.exe[5]
  1. Open a Command Prompt as Administrator.
  2. Type cd "<path-to-game>", where <path-to-game> is the fully qualified path to the folder that contains the EXE of the game you are having problems with.
  3. Type mklink Steam.exe "<Steam-folder>\Steam.exe".
Delete ClientRegistry.blob[6]

See instructions on the Steam page.

  • 6: App not owned (you're a pirate).

The problem with Steam DRM

In the real world, Steam DRM is pretty weak. While it's not so trivial that the typical user can defeat it, to anyone who has some experience with reverse engineering DRMs it is quite easy. Its first big weakness is that it has no external dependencies for decrypting game code. That means one doesn't need to own the game or even need to have Steam installed to unpack the executable. If unpacking live, it may be a bit trickier because you have to go around any Steamworks API calls, but it's not that hard. Because the scheme doesn't have this external dependency, it is a good candidate for static unpacking. The second great weakness is that it doesn't encrypt any data sections or really attempt to make it hard to dump. All values in the protected executable are really easy to recover (except for the DOS stub that gets overwritten if a signature is applied). The fact that it doesn't encrypt imports or data sections means only the encrypted code needs to be dumped and written back to the protected executable, along with the original entry point, since code is typically in a read-only segment of memory so it doesn't change. The only thing that it has got going is that it is fairly unobtrusive and works properly 90% of the time (the other 10% can usually be attributed to the Steamworks API not working properly as it tends to do). Further, once control has been passed to the original game code, the DRM has no further part in the game, so no active protection is offered. This scheme is an interesting little thing to study, but is probably not worth the time Valve put into developing it for its rather poor protection value.

References

  1. API overview (Steamworks documentation)
  2. I'm an indie game developer considering using Steam DRM. Tell me reasons not to use it, or reasons to use it. - /r/gamedev on reddit
  3. Crash and Workarounds (for startup problems) - Steam Community Discussions
  4. Steam - PC Gaming Wiki
  5. Application load error 5:0000065434 - Steam Community Discussions
  6. I am having problems installing Skyrim Mods via the workshop - /r/Steam on reddit