How to prevent spoofing of DLLs in .NET
Asked Answered
W

2

26

I have a .NET application that references a managed DLL.

This DLL contains a class, say ScoreKeeper that implements a method called GetHighScore(). The application calls this periodically.

Is there a way to prevent the .NET application from using a "non-authorized" DLL here in place of the one I am supplying?

Woodsman answered 12/11, 2012 at 15:13 Comment(11)
Use Strong Named Assemblies. They directly address your immediate concern.Tertian
How would you stop a hacker from changing the call in the application itself from ScoreKeeper.GetHighScore() to return 9999999 ?Apologia
@ohadsc - no security is perfect, but you can at least make it unappealing.Tertian
@Tertian I'm just saying that if the hacker has access to this DLL then he might have access to the application itself, in which case worrying about the DLL is misdirectedApologia
@ohadsc I'll ask as a separate question... it's a great oneWoodsman
Can they clear the hash and make it look like the executable was never signed to begin with? I ask because I notice how trivial it is to clear the SuppressIldasmAttribute that's inserted by certain obfuscatorsWoodsman
possible duplicate of C#: How to Make it Harder for Hacker/Cracker to Get Around or Bypass the Licensing Check?Tradition
Yes. There is no such thing as an un-defeatable security systemTertian
@casperOne: The way the question is written, it sounds to me like the consumer is trying to use an altered DLL (to cheat).Tradition
@casper: More likely, they aren't aware that code signing can be trivially bypassed. That had to be explained on the earlier question also.Tradition
Both are about preventing the user from substituting modified code (in one case without license check, in one case with a fake score computation -- these details are irrelevant to the security task).Tradition
O
53

You mention:

This DLL contains a class, say ScoreKeeper that implements a method called GetHighScore(). The application calls this periodically.

And then:

Is there a way to prevent the .NET application from using a "non-authorized" DLL here in place of the one I am supplying?

Assuming that you want to prevent someone from swapping out the assembly you have provided with an assembly of their own which has the same name and type (which is in the same namespace), you could apply a strong name to the assembly that contains the ScoreKeeper class and have your consumers reference that.

However, we'll see that there are issues that make this not 100% reliable. Strong Names help you protect unaware users from replacement of your DLL with a malicious spoofed copy. But if the user is complicit in the spoofing (which would be the case if he is trying to cheat), then code signing will be no more than a speed bump and provides no real protection. Certainly, Strong Names don't provide protection comparable to e.g. PunkBuster.

Using a Strong Name to verify an assembly publisher's identity

When you add a strong name to an assembly, you are using a private key (part of an asymmetric public/private key pair, more on this later) to generate a cryptographic hash and the public key is included in the assembly name (along with the hash).

Using the public hash and the public key, the CLR is able to verify that the signature of the assembly did in fact come from the private key.

Of course, this means, you should protect the key (internally and externally); if someone else has your key, then they can effectively impersonate you and publish assemblies that people would trust to be from you.

Then, when you add a reference to your signed assembly, if someone tries to put a different assembly in with the same assembly name (not the fully qualified one, just the name without version, hash and public key) and same type name, the CLR fill fail when trying to load the type, indicating that it couldn't find it; the type is resolved using the fully-qualified assembly name, along with the namespace and type name.

Why Strong Names are not 100% secure (is anything?)

1) Hash Collisions

It is still a hash that is being verified. While the hash is quite large (160 bits for the default hash algorithm, SHA-1), any hash that has a finite number of values is subject to a collision. While extremely unlikely, it is possible (impossible vs. improbable). Furthermore, only the last 8 bytes is used by default. Combined with research indicating that SHA-1 is relatively weak, this is a good reason to use SHA-256 Enhanced Strong Naming as described in MSDN.

2) Removal of the Strong Name

The strong name can be removed. However, in this case, because your assembly is referencing the strong named version of the referenced assembly, when your assembly tries to use the compromised version, it will fail at runtime, assuming you've correctly re-enabled verification (see below).

3) Physical access to the assemblies means all the assemblies

If someone has access to the physical machine and can modify the assembly that you are referencing, then your assembly is just as vulnerable. If the attacker has the ability to modify the strong name of an assembly that you referenced, then they can just as easily modify your assembly and all others involved in the execution. To this end, the only way to be 100% sure that the physical assembly isn't hacked is to deny physical access through it. Of course, that brings up a slew of different security concerns.

4) Administrator disabling the Strong Name check

The computer administrator can simply bypass the strong name check, using sn -Vr. According to MSDN:

Registers assembly for verification skipping... A malicious assembly could use the fully specified assembly name (assembly name, version, culture, and public key token) of the assembly added to the skip verification list to fake its identity. This would allow the malicious assembly to also skip verification.

5) Strong Name checking has to be explicitly enabled post .NET 3.5 SP 1

From .NET 3.5 SP 1 on, simply having a strong name doesn't provide any protection:

Starting with the .NET Framework version 3.5 Service Pack 1 (SP1), strong-name signatures are not validated when an assembly is loaded into a full-trust AppDomain object, such as the default AppDomain for the MyComputer zone.

In order to have .NET check the strong name of each assembly loaded into your application, you'll want to insert the following snippet (provided by MSDN) into your application configuration file:

<configuration>
  <runtime>
     <bypassTrustedAppStrongNames enabled="false" />
  </runtime>
</configuration>

Beware, however, that this only protected against removal of the strong name.

When you override the bypass feature, the strong name is validated only for correctness; it is not checked for a StrongNameIdentityPermission. If you want to confirm a specific strong name, you have to perform that check separately.


If in light of the concerns above, you'd still like to pursue Strong Naming your assembly, here's how.

Generating a Strong Name and signing your assembly

You have two options for which key to use when generating a strong name. In Visual Studio, go to the Signing tab on the project properties and click "Sign the assembly":

"Sign the assembly" option on the "Signing" tab of the project properties in VS.NET

From there, you have two options to generate the public/private key, to have VS.NET generate the keys for you, or point to an existing one:

"New" or "Browse" options for choosing a strong name key file

When selecting "New", Visual Studio will prompt you for the name of the file to generate, as well as whether or not you want to optionally use a password to access it:

Create strong name key dialog

At which point, the key will be added to your project:

Key added to project

Now, you can move this to a solution item (if you have multiple projects in your solution).

Visual Studio in this case is really just calling the Strong Name command line tool to generate a public and private key pair. If you'd rather do that yourself, you'd want to call sn.exe with the -k command line option to generate the key, like so:

sn -k keyPair.snk

And then add it via the "Browse" dialog above.

Note that when you do this, it will pull the key into your project. If you don't want to do this (as it copies the key into every project), then delete the key from the project and then add an existing file to the project, but link it. This will clear the "Choose a strong name key file" option, but if you drop it down, you'll see the full path to your linked key file.

Oteliaotero answered 12/11, 2012 at 15:14 Comment(1)
Chat coordinating CW answerTradition
T
2

Seems like the most straightforward answer would be to calculate a secure hash of the DLL you are loading, and then compare it against a golden value that you have precalculated. Of course,this will still be vulnerable to being broken by a sufficiently determined attacker, but it will significantly raise the bar for people who want to cheat.

Try answered 12/11, 2012 at 15:17 Comment(4)
I was thinking this as a hand-rolled solution so +1 for the idea anywayWoodsman
Why would you do it when strong named assemblies do it for you? It's reinventing the wheel, and chances are, less secure.Oteliaotero
@Oteliaotero - no argument, and I realized this about 2 seconds after I posted the answer. See my first comment on this answer (at the time, yours wasn't posted). FYI, one of your upvotes is mine.Try
@casperOne: Maybe because disabling a hand-coded hash check is more work than bypassing a strong name?Tradition

© 2022 - 2024 — McMap. All rights reserved.