While doing research for my Proxy DLL tutorial I stumbled across an issue in Valve’s digital games platform which I thought to be a dramatic enough issue to warrant reporting it to their development team.
I reported this issue to Valve twice and was told that because it required local access to a user’s machine it wasn’t a serious enough issue to warrant investigation. Perhaps this is due to me not persuasively explaining it to them or them paying me off because I’m some unknown internet guy with 8 twitter followers.
Update: Steam Support said they would forward the information directly to their development team.
How it works
The issue isn’t entirely the fault of the developer it’s largely due to the design of Windows and how it loads DLLs. However Valve could mitigate this issue and make more difficult to exploit as it currently stands. This also another issue that could potentially lead this to being integrated into a piece of malware that when coupled with a code injection (say in a browser) could lead to said malware taking root into the operating system.
Valve uses at Dynamic Link Library called steam_api.dll that 3rd party developers can use to easily integrate their application into Steam using the Steamworks SDK and indeed in their own games. You can download the their SDK here. This library usually lives in the same directory as the main executable for the application. With default install settings this location is here:
C:\Program Files (x86)\Steam\steamapps\common\application_name
Reading through their documentation we can see that applications use dynamic linking to link to steam_api.dll to allow their application to access steam and access information like the purchase status of the game or the user’s steam profile information. They can also access Steam Matchmaking and lookup other pieces of information like VAC bans or DLC purchases.
The first issue I discovered was the Access Control List (ACL) for the root steam directory is set to full access for all users. Usually when a program is installed to the Program Files directory any attempt to modify this directory will result in a UAC prompt asking for permission to modify this directory. Windows does this so non-elevated users cannot write to this directory. However Steam for whatever reason modifies it’s ACLs so any user can modify its directory, this is most likely done so steam can update itself without elevating it’s process.
This is a problem because it means ANY account is free to modify this directory without elevation. This opens Steam up to being abused by any local code that is running, say a buffer overflow in flash or any other piece of malicious code that may find itself on the user’s system.
This however is not a complete deal breaker because Steam itself can perform integrity checks on startup and restore any files modified.
The real issue comes from the fact that applications will load steam_api.dll blindly. Originally I thought it couldn’t be that simple. Surely due to Steam’s popularity and how many active users it has it would implement so type of code signing or verification process on the steam_api.dll before just loading it, but it doesn’t. This is largely due to applications using dynamic linking instead of run time linking.
Microsoft has an article on msdn with sample code showing you how to use CryptQueryObject in order to verify the code signing on executable objects. Which is fine if you’re using run-time linking to authenticate 3rd party libraries, however integrating with Steamworks uses Dynamic Linking which means this step is bypassed. Windows will open the PE and parse the IAT looking for DLLs to load into it, finds steam_api.dll looks inside the target directory and loads this file with no authentication.
When using 3rd party libraries you should always use run-time linking to verify the code before loading it. It takes a bit more work to implement but it is far more secure than blindly trusting code.
So right now you’re probably thinking “so what you’re saying is I can take my DLL rename it steam_api.dll and drop it in the directory? Surely it’s not that simple!”.
Narrator: As it turned out it was that simple
If we use a tool like dumpbin or LordPE we can extract the imports for our target. Searching through we locate steam_api.dll and see what functions it is pulling from the DLL.
An extremely basic example is something like the following:
If we create a new DLL that exports these functions then our DLL will load! Checking the SDK we see SteamAPI_Init is defined like so:
extern "C" __declspec(dllexport) BOOL __cdecl SteamAPI_Init()
To exploit this at basic level we only need to define the API that the target uses. If you export everything the target application expects Windows will load the DLL and you’ll be completely injected into the target.
Simply returning TRUE from inside SteamAPI_Init will cause the DLL to be loaded successfully and we’re greeted with following:
But hang on there must be some other type of validation occurring?
There’s not. This is the solitary entry point into Steam. By attacking it at this point we nullify any and all protections in place. Another perplexing thing is that you would think returning bogus values (which my code does) would cause the application to immediately quit or throw some type of access violation but it doesn’t. It seems like there’s no validation occurring.
Huh? What the actual?
And it’s at this point most people loose interest because they fail to understand just how bad this is. Not only do we have rogue code injected into a trusted process we’re also masquerading as steam itself. From this point on ANY information queried by the application is controlled by us. We could for example act like a proxy between the application and steam filtering or modifying data as we see fit, user VAC banned? Not anymore! User hasn’t purchased DLC? They have now!
But by far the worst thing about this an emulation attack. By implementing the entirety of the SteamWorks SDK we can make ANY steam game run effectively nullifying their DRM. This would in effect create a universal patch that would allow any steam game released to date to run without steam or steam servers.
As a PoC I got TF2 to run all the way to the main menu before crashing (as I hadn’t implemented any network code). This was on a clean system without Steam installed which you would think would immediately fail.
Here’s another game called Clicker Heroes running completely without steam:
So what? TF2 is F2P now?
You’re missing the point just like Valve did. The point is I ran their game. Steam is their platform, it’s their DRM. Breaking the protection on a non-valve game could simply mean that a 3rd party developer got lazy and implemented something incorrectly. By getting it to launch without Steam means their protection is broken. Valve can patch this now but everything already released is vulnerable.
So hard is this to pull off?
Not that difficult. Don’t get me wrong this would require a considerable investment in time to create something like this but is it possible? Yes, a thousand times yes and it’s not difficult to do only time consuming. Could a team of hackers do this a few weeks? Absolutely.
So how to fix it?
Well abusing an extremely old trick on the Windows platform is hardly ground breaking. This has been around for decades and really the developers should have known about this and mitigated it with run-time linking and code signing.
So if it’s as bad you say why are you releasing this?
I have the strong feeling this isn’t new to the hacking community. I haven’t done a lot of research into what’s currently available but an educated guess would be this is known attack vector and is being actively exploited in the wild (due to its relative simplicity).
Jul 31st – Issue posted to Valve’s bug bounty (#388762).
Aug 2nd – Issue closed (not applicable to program)
Aug 2nd – Contacted Steam Support on Twitter and Steam Boards
Aug 3rd – No response
Aug 4th – No response
Aug 5th – No response
Aug 6th – No Response
Aug 7th – Steam support acknowledged report, write up released.
Support me on Patreon: