Registration Code (Part 1): w%kQ6
Registration Code (Part 2): b<#$1[*(cw~
In order to register on this forum, you must use the codes above. Combine them into one code (copy paste).

A (Small) Peek Into SteamDRMP.dll

Personal posts regarding my releases and research on various topics.
Post Reply
User avatar
atom0s
Site Admin
Posts: 410
Joined: Sun Jan 04, 2015 11:23 pm
Location: 127.0.0.1
Contact:

A (Small) Peek Into SteamDRMP.dll

Post by atom0s » Thu Mar 26, 2015 10:20 pm

During my past progress with building my project Steamless, I wanted to better understand the DRM itself rather then just removing it and not looking into it more. I took some time to peer into the protection setup that the DRM offers once it is unpacked in memory and the protected target is running.

One of the things that the DRM does is unpacks and loads a module from memory called SteamDRMP.dll. This module is packed away inside of the .bind section of the protected file, which is encrypted.

During the unpacking of the file while it is starting up, this file is decrypted and loaded in memory without extraction to disk.
Then an export is referenced and called to prevent debugging on the game target.

The export looks like this:
  1. char __stdcall steam(int a1, void *a2, int a3)
  2. {
  3.   char result; // al@5
  4.   HMODULE v4; // eax@6
  5.   FARPROC v5; // esi@6
  6.   HANDLE v6; // eax@6
  7.   char v7; // al@10
  8.   char v8; // bl@10
  9.   CHAR ProcName[4]; // [sp+4h] [bp-24h]@6
  10.   CHAR ModuleName[4]; // [sp+1Ch] [bp-Ch]@6
  11.   int v11; // [sp+38h] [bp+10h]@7
  12.  
  13.   if ( (unsigned int)a3 < 0xC8 || *((_DWORD *)a2 + 1) != 0xC0DEC0DE )
  14.   {
  15.     result = 81;
  16.   }
  17.   else
  18.   {
  19.     if ( !(*((_BYTE *)a2 + 52) & 0x20) )
  20.     {
  21.       if ( IsDebuggerPresent() )
  22.         return 84;
  23.       strcpy(ModuleName, "ntdll.dll");
  24.       strcpy(ProcName, "NtSetInformationThread");
  25.       v4 = GetModuleHandleA(ModuleName);
  26.       v5 = GetProcAddress(v4, ProcName);
  27.       v6 = GetCurrentThread();
  28.       ((void (__stdcall *)(_DWORD, _DWORD, _DWORD, _DWORD))v5)(v6, 17, 0, 0);
  29.     }
  30.     v11 = GetTickCount();
  31.     if ( *((_BYTE *)a2 + 52) & 2 || (unsigned __int8)sub_10006160((HMODULE)(a1 - *((_DWORD *)a2 + 4))) )
  32.     {
  33.       v7 = sub_10006270(a2);
  34.       v8 = v7;
  35.       if ( v7 == 48 )
  36.       {
  37.         if ( *((_BYTE *)a2 + 52) & 4 || (result = sub_10005AE0(a1, a2), result == 48) )
  38.         {
  39.           if ( *((_BYTE *)a2 + 52) & 0x20 || GetTickCount() - v11 <= 10000 )
  40.             result = 48;
  41.           else
  42.             result = 83;
  43.         }
  44.       }
  45.       else
  46.       {
  47.         if ( v7 == 53 )
  48.           sub_10006070(*((_DWORD *)a2 + 12));
  49.         result = v8;
  50.       }
  51.     }
  52.     else
  53.     {
  54.       result = 51;
  55.     }
  56.   }
  57.   return result;
  58. }
To start we can dissect this function that is exported and better understand the protection it offers.

The first checks we have are to ensure the file has been unpacked properly:
  1. if ( (unsigned int)a3 < 0xC8 || *((_DWORD *)a2 + 1) != 0xC0DEC0DE )
  2.   {
  3.     result = 81;
  4.   }
The first check is currently unknown.
The second is to ensure the stub header block is decrypted properly. When the stub header is xor decrypted there is a 4 byte signature at the start showing 0xC0DEC0DE to validate that the xor was correctly undone.

Next, the DRM module checks if a thread protection flag is set:
  1. if ( !(*((_BYTE *)a2 + 52) & 0x20) )
  2.     {
  3.       if ( IsDebuggerPresent() )
  4.         return 84;
  5.       strcpy(ModuleName, "ntdll.dll");
  6.       strcpy(ProcName, "NtSetInformationThread");
  7.       v4 = GetModuleHandleA(ModuleName);
  8.       v5 = GetProcAddress(v4, ProcName);
  9.       v6 = GetCurrentThread();
  10.       ((void (__stdcall *)(_DWORD, _DWORD, _DWORD, _DWORD))v5)(v6, 17, 0, 0);
  11.     }
This checks for the 0x20 flag in the header. If it is present, it first checks if a debugger is attached. If not then it continues to call NtSetInformationThread. 17 states that it wants to hide the current thread from the debugger.

Next we have a GetTickCount compare if another flag is set.
  1. v11 = GetTickCount();
  2.     if ( *((_BYTE *)a2 + 52) & 2 || (unsigned __int8)sub_10006160((HMODULE)(a1 - *((_DWORD *)a2 + 4))) )
  3.     {
  4.       v7 = sub_10006270(a2);
  5.       v8 = v7;
  6.       if ( v7 == 48 )
  7.       {
  8.         if ( *((_BYTE *)a2 + 52) & 4 || (result = sub_10005AE0(a1, a2), result == 48) )
  9.         {
  10.           if ( *((_BYTE *)a2 + 52) & 0x20 || GetTickCount() - v11 <= 10000 )
  11.             result = 48;
  12.           else
  13.             result = 83;
  14.         }
  15.       }
  16.       else
  17.       {
  18.         if ( v7 == 53 )
  19.           sub_10006070(*((_DWORD *)a2 + 12));
  20.         result = v8;
  21.       }
  22.     }
So this time a flag 0x02 is checked for (unsure of the second check at this time). If the flag is set it continues with the GetTickCount check.
Just after, there is a steam ticket validation. I am assuming this ensures that Steam is running or that you own the game. This also has a possibility to be a check if the game was launched from Steam properly or trying to be directly ran. The else case here calls on Steam to launch the application otherwise which is what points me to that.

The last chunk is some memory dump protection (from the look of it at a glance) then the GetTickCount compare to ensure the game reached the given point within 10 seconds. Otherwise the function will return with an invalid return.

So from this function a guess of the flags so far would be:
- 0x02 = Launch validation of some sort.
- 0x04 = Memory Dump Protection (Guessing currently.)
- 0x20 = Debugger Checks (IsDebuggerPresent, GetTickCount, and NtSetInformationThread)

Return code wise:
- 48 = GetTickCount timeout.
- 51 = Possible valid return.
- 53 = Valid return, relaunch target through Steam.
- 81 = Invalid header.
- 83 = Possible valid return.
- 84 = Debugger present.

So some things to keep in mind when attaching to or debugging a SteamStub protected file:
- Fake the return of IsDebuggerPresent
- Fake the return of GetTickCount
- Block the NtSetInformationThread API call.

Another thing that can be done is decrypting the stub header and replace the flags to remove the added checks.

This information is based on a simple static analysis of the functions present in the DLL. No debugging was done to step into the functions and determine full purpose.
Take this information lightly as it can be wrong. It is again, a simple analysis.

This information is for educational purposes.
Derp~
Need a great web host? Check out: AnHonestHost.com


Donations can be made via Paypal:
https://www.paypal.me/atom0s
Golem_x86
Posts: 10
Joined: Fri Jan 23, 2015 1:54 pm

Re: A (Small) Peek Into SteamDRMP.dll

Post by Golem_x86 » Thu Apr 02, 2015 2:58 pm

Here's the steam() function rewritten to make clearer what it's doing:
  1. typedef (NTSTATUS *PNSIT)(HANDLE, THREADINFOCLASS, PVOID, ULONG);
  2.  
  3. char __stdcall steam(DWORD baseAddress, SteamStub32Var3Header* config, int configLength)
  4. {
  5.     char ntdllName[10];
  6.     char ntSetInfoThreadName[23];
  7.     PNSIT pNSIT;
  8.     DWORD startTickCount;
  9.     char resultCode;
  10.    
  11.     // Verify header: make sure signature matches and is not smaller than current header size
  12.     // (The assumption is the header will always grow, and if it's smaller the DRM was applied with
  13.     // an earlier version of the DRM tool.)
  14.     // Code Q is a general failure. Probably corrupted data or something.
  15.     if (configLength < sizeof(SteamStub32Var3Header) || config->Signature != 0xc0dec0de) return 'Q';
  16.    
  17.     // Anti-debug, if enabled
  18.     if (!(config->Flags & DrmFlag_NoDebuggerCheck))
  19.     {
  20.         if (IsDebuggerPresent()) return 'T';
  21.        
  22.         // Classic anti-debug technique: bumps off any attached debuggers
  23.         // Use strcpy() for obfuscation (compiles to writing numbers to memory)
  24.         strcpy(ntdllName, "ntdll.dll");
  25.         strcpy(ntSetInfoThreadName, "NtSetInformationThread");
  26.         pNSIT = (PNSIT)GetProcAddress(GetModuleHandle(ntdllName), ntSetInfoThreadName);
  27.         pNSIT(GetCurrentThread(), ThreadHideFromDebugger, 0, 0);
  28.     }
  29.    
  30.     // Also anti-debug
  31.     startTickCount = GetTickCount();
  32.    
  33.     // Validates the signature that is present right after the DOS header. Verifies both the EXE and steamclient.dll.
  34.     // Note how the module handle is obtained by subtracting entry address off of base address. The base address is
  35.     // the in-memory VA of the entry point of the current module.
  36.     if (!(config->Flags & DrmFlag_NoModuleVerification) && !VerifyValvePeSignature((HMODULE)(baseAddress - config->AddressOfEntryPoint))) return '3';
  37.    
  38.     resultCode = appIsSubscribed(config);
  39.     if (resultCode != '0')
  40.     {
  41.         // Code 5 means "couldn't communicate with Steam", which usually means game's not launched from Steam.
  42.         // (It is not mandatory to launch games from Steam, as long as you give an App ID via env-vars or steam_appid.txt.)
  43.         if (resultCode == '5') restartApp(config->SteamAppID);
  44.         return resultCode;
  45.     }
  46.    
  47.     if (!(config->Flags & DrmFlag_NoEncryption))
  48.     {
  49.         resultCode = unscramble_text_section(baseAddress, config);
  50.         if (resultCode != '0') return resultCode;
  51.     }
  52.    
  53.     if (!(config->Flags & DrmFlag_NoDebuggerCheck) && (GetTickCount() - startTickCount > 10000)) return 'S';
  54.     return '0';
  55. }
Note flags are inverted - that is, a feature is disabled if the flag is present. Also, there is a lack of dump protection, which is what makes Steam DRM so worthless. Finally, for debugger checks, there's one in the unpacker code, but it looks directly at the value in the PEB (which is what IsDebuggerPresent() does), so breaking in IsDebuggerPresent() won't do you any good there. If you want to change the flags to disable anti-debug, do mind there's a CRC check in the unpacker code.
User avatar
atom0s
Site Admin
Posts: 410
Joined: Sun Jan 04, 2015 11:23 pm
Location: 127.0.0.1
Contact:

Re: A (Small) Peek Into SteamDRMP.dll

Post by atom0s » Sun Apr 12, 2015 10:36 am

Yeah overall nothing too amazing being done. I'm surprised with how big Steam is they haven't implemented something a bit more in-depth. There are a lot of other debugger tricks they could be using as well as some basic anti-dump methods too. Have you seen any newer variants floating around yet outside of the 3 known ones? I've gotten a few newer games on Steam lately but all of them are using the variant 3 still and not something new.

All in all its still a fun thing to look into and understand. :) Cheers for your additional info.
Derp~
Need a great web host? Check out: AnHonestHost.com


Donations can be made via Paypal:
https://www.paypal.me/atom0s
Golem_x86
Posts: 10
Joined: Fri Jan 23, 2015 1:54 pm

Re: A (Small) Peek Into SteamDRMP.dll

Post by Golem_x86 » Mon Apr 13, 2015 6:50 am

It seems variant 3 was a major rewrite of the previous versions to be cross-platform. There are references to Mach-O and ELF formats in the DRM tool, and in fact you can find the payloads used for Linux executables. However, nothing's been officially announced yet. Windows games still upload to the partner site to be wrapped, and Mac games are still processed by ContentPrep. If Valve still acts like what they were, this variant will probably be used for another five years before they feel the need to change it. I don't even know if their goal with this system is to protect EXEs from unauthorized use.
User avatar
atom0s
Site Admin
Posts: 410
Joined: Sun Jan 04, 2015 11:23 pm
Location: 127.0.0.1
Contact:

Re: A (Small) Peek Into SteamDRMP.dll

Post by atom0s » Mon Apr 13, 2015 8:50 am

Golem_x86 wrote:It seems variant 3 was a major rewrite of the previous versions to be cross-platform. There are references to Mach-O and ELF formats in the DRM tool, and in fact you can find the payloads used for Linux executables. However, nothing's been officially announced yet. Windows games still upload to the partner site to be wrapped, and Mac games are still processed by ContentPrep. If Valve still acts like what they were, this variant will probably be used for another five years before they feel the need to change it. I don't even know if their goal with this system is to protect EXEs from unauthorized use.
That would be my guess. Since the DRM incorporates the SteamDRMP.dll to help enforce Steam is running and such. My guess is its just mainly to try and reduce piracy issues. But given that its not really a protection and more of a packer, it doesn't really do much. (Basically like a glorified UPX.)
Derp~
Need a great web host? Check out: AnHonestHost.com


Donations can be made via Paypal:
https://www.paypal.me/atom0s
Golem_x86
Posts: 10
Joined: Fri Jan 23, 2015 1:54 pm

Re: A (Small) Peek Into SteamDRMP.dll

Post by Golem_x86 » Mon Apr 13, 2015 5:10 pm

Yeah, a packer that increases executable size and launch time. I think their target is to prevent casual sharing, as it's pretty much useless against anyone who has even a bit of reverse engineering sense. (Still, I find it odd that various release groups do memory dumps instead of just pasting in the decrypted code and OEP. I suppose not many people have researched how exactly the DRM works?)
User avatar
atom0s
Site Admin
Posts: 410
Joined: Sun Jan 04, 2015 11:23 pm
Location: 127.0.0.1
Contact:

Re: A (Small) Peek Into SteamDRMP.dll

Post by atom0s » Tue Apr 14, 2015 12:12 am

Golem_x86 wrote:Yeah, a packer that increases executable size and launch time. I think their target is to prevent casual sharing, as it's pretty much useless against anyone who has even a bit of reverse engineering sense. (Still, I find it odd that various release groups do memory dumps instead of just pasting in the decrypted code and OEP. I suppose not many people have researched how exactly the DRM works?)
Guess so since most are interested in having a 0-day release rather then proper'd. But then again other groups usually dig for those chances to proper another groups release for 'e-cred' and gloating lol. Not sure though. I don't pirate games so haven't really looked any releases lately from groups.
Derp~
Need a great web host? Check out: AnHonestHost.com


Donations can be made via Paypal:
https://www.paypal.me/atom0s
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest