How to Protect Your .NET Source Code

Blog 2.13.2017 No Comments

Warning: I am not a software security expert. Please do not use this blog post as the basis for protecting highly sensitive code.

This past week I presented twice at SolidWorks World 2017 on SolidWorks API topics. In my first presentation, I demonstrated and discussed the pros and cons between macros, addins, and stand-alone programs as well as the common languages used to write each of them (VBA, VB.NET, and C#). One of the angles I considered was security. How can one protect their source code from being pirated or cracked? While this question isn’t about SolidWorks API per se, it is pondered often enough by API developers that I want to discuss it more detail.

Overview

With VBA macros, you only have one option: use the built-in password feature that will allow users to run the macro but not edit it. The problem is, this only stops people from viewing the source code. It does not stop anyone from using the macro. Moreover, with a little effort, one can replace the unknown password with a known password using a hex editor.

With .NET assemblies (e.g., the DLL or EXE that is built from your code), you have the option of implementing some form of licensing or password mechanism. This has two problems, however. First, as the licensing / password mechanism becomes more secure, it also tends to become more tedious for the user. (For example, if the licensing mechanism ties the user’s license to a particular machine.) Second, with some effort, the .NET assembly can be decompiled and then recompiled without the licensing mechanism.

While it isn’t possible to prevent decompilation (which is easily performed using a program like DotPeek and .NET Reflector), it is possible to obfuscate the source code in your assembly so that, even if it does get decompiled, understanding what your code does becomes difficult. Consequently, whatever security mechanism you have in place might be very tedious to remove.

Keep in mind: any client-side software can be pirated / cracked with enough time and effort. The only way to totally secure your source code is to never give your assemblies (i.e., your DLLs or EXEs) to anyone and instead use a Software-As-A-Service (SaaS) business model, in which case your assemblies will reside only on your server and your development machine. But SaaS isn’t even an option for many businesses.

A Change of Attitude

Trying to constantly stay one step ahead of pirates is usually futile. As one person on StackOverflow brilliantly put it, “It only takes one person cracking your code for it to be available to everyone. You have to be lucky every time. The pirates only have to be lucky once.”

More than just being futile, however, it can actually be bad for your business. The time and effort you put into securing your code does nothing to actually improve the user’s experience with your software. Indeed, if your security measures are cumbersome for the user to jump through, it can actually hurt their experience.

Consequently, the best deterrent to piracy may be to regularly improve your software and provide awesome customer service. As a result, the potential customer sees that the value you provide is worth the price they pay. They must believe that they will actually lose out by simply going with a pirated version of your software.

Another point to keep in mind is that if you’re selling business-to-business, the people who are going to use pirated copies of your software probably would not buy your software even if it were un-crackable. Therefore, I’m not convinced that in the B2B sphere, piracy actually results in that much loss of sales. If anything, piracy helps spread the word regarding your product and encourages you to focus harder on running a great business.

Anti-Piracy Measures Considered

Now, just because I think attitude is the most important measure you can take against piracy doesn’t mean its the only measure I think you should take. I think any program worth protecting ought to take these two measures:

  1. Licensing or password protection.
  2. Obfuscation.

Licensing / password protection simply means that your program requires some license key or password in order to operate. That way, pirates can’t simply copy your program and run it without any restrictions. But here’s the interesting part: the complexity or robustness of this mechanism is meaningless if pirates can simply decompile your program, remove the security mechanism, and then recompile it. This is always the case for programs given to the client. For example, consider this VB.NET program:

Imports System.Windows.Forms
    
Module Main
    
    Sub Main()
        Dim password As String = InputBox("Please enter the password.")
    
        If VerifyPassword(password) Then
            DoTheWork()
        Else
            MessageBox.Show("Sorry, wrong password.")
        End If
    End Sub
    
    Private Function VerifyPassword(password As String) As Boolean
        If password = "super secret password" Then
            Return True
        Else
            Return False
        End If
    End Function
    
    Private Sub DoTheWork()
        MessageBox.Show("Running program...")
    End Sub
    
End Module

The VerifyPassword() function is very simple. It tests to see if the user’s input string is identical to a hard-coded string. But what if this function was vastly more complex? It doesn’t matter because a pirate could simply decompile your code and modify Main() to include DoTheWork() and nothing else. The security mechanism would be completely bypassed.

Enter obfuscation. Obfuscation is “the action of making something obscure, unclear, or unintelligible”. We can use a code obfuscator to make our code very hard to read and understand by scrambling our code’s metadata so that the normal namespace, variable, and method names are replaced by extremely short and unhelpful names. You can see an example here. The result is simply that your code is much harder to reverse engineer.

Implementing the Anti-Piracy Measures

If you think that piracy could adversely affect your business, it is worth implementing some simple security measures to at least make piracy very tedious. I’m going to show you, therefore, the simple password mechanism and obfuscation tool I use with some of my programs. Look at these measures as the “biggest bang for your buck” because it will cost you nothing but 30-60 minutes of your time.

Password Mechanism

Earlier I showed a password mechanism in which the user simply enters a string and it is compared to a hardcoded string in the program. That’s a bit too simple and I think someone who decompiled our program could figure out pretty quickly what the password is. I would rather implement a password mechanism that is robust enough that the pirate is forced to modify and recompile our code if they want to bypass the security mechanism. (Again, my aim is to reduce piracy by making it tedious to do.)

We could get a little fancier with our string password using a cipher, which is an algorithm that will convert the user’s input password into something more cryptic. For example, let’s say that the password to our program is “hello”. Rather than hardcoding “hello”, we’ll hardcode “rcoeoqppvxlcvdkeyacd”, which was obtained by the following algorithm:

1. Convert each letter in the string to the 10th letter ahead of that letter. (“hello” –> “rovvy”)
2. Insert three random letters at the end of each of the letters obtained in the previous step.

The disadvantage of this approach is that the pirate could still take the time to study the algorithm after they decompiled the program and thereby discover that the password is “hello”. The pirate probably wouldn’t; they would probably just recompile the code without the security mechanism, but if we implement an even more robust password mechanism, we can at least force them to go the recompilation route.

The more robust mechanism I am thinking of involves the difficulty of factoring extremely large semiprimes. A semiprime is the product of two prime numbers. Currently, the technology for factoring a 1024 bit semiprime in a reasonable amount of time does not exist. Therefore, we will hardcode a very large semiprime into our program. The password the user enters will be divided into this semiprime. If the remainder is zero and the password entered isn’t the semiprime itself, one, or zero, then the program is unlocked.

What I described above is known as asymmetric or public key cryptography. It is the basis for most of the common encryption algorithms, such as RSA. When used, the pirate has no realistic way to determine the public key (the password), which means that they are forced to decompile, remove the security mechanism entirely, and then recompile if they want to pirate the software.

To accomplish this, we need to first generate some really large prime numbers. In our case, we’re interested in two 512 bit primes that we can multiply to get a 1024 bit semiprime. The fastest way to do this, I have found, is through WolframAlpha using the RandomPrime[2*512] function. Using this twice, I get these values:

1030036602598976432683876090384311619779817426568199635721909308334853846166282705348098491480965944058167494832551044953489873701037371663414513077893831

3470286731786319967697161150108216266467687635597343214439309083506091064461655902744916175294419695183449301769152696093634596665123123708757988896620687

We need to get the product of these two numbers. If you try to do this in .NET using double or ulong data types, you’ll run into problems because these types aren’t designed to handle numbers of this size. Instead we have to use System.Numerics.BigInteger class, which has methods for performing calculations with very large integers, such as BigInteger.Multiply(). Using that method for the above primes will produce this semiprime:

3574522355253486376607090679412058587605209637050288224651312594499415157606938016760090741648625564943457074837124758850059987557386762219300792006878464303045773124661517310657808537475286266496854670334149759621269093438046706289243962191687054489020476430544359972981836807607429766985418843679264281897

Now, below is what a simple program that uses one of the prime factors as a password would look like. Don’t forget to add a reference to System.Numerics.

Imports System.Numerics
    
Class Program
    Private Shared Sub Main(args As String())
        Dim enteredPassword As String = Console.ReadLine()
    
        If VerifyPassword(enteredPassword) Then
            Console.WriteLine("Correct password.")
        Else
            Console.WriteLine("Incorrect password.")
        End If
    
        Console.ReadKey()
    End Sub
    
    Private Shared Function VerifyPassword(enteredPassword As String) As Boolean
        Dim semiprime As BigInteger = BigInteger.Parse("YOUR SEMIPRIME HERE")
    
        If BigInteger.TryParse(enteredPassword, Nothing) = False Then Return False
    
        Dim divisor As BigInteger = BigInteger.Parse(enteredPassword)
    
        If divisor = semiprime Or divisor = 1 Or divisor = 0 Then Return False
    
        Dim remainder As BigInteger
        BigInteger.DivRem(semiprime, divisor, remainder)
    
        If remainder = 0 Then Return True Else Return False
    End Function
End Class

A few final considerations:

  1. A password mechanism can be used with DLLs as well. You can have an internal / Friend boolean variable called “unlocked”, for example, that must be true before the public functions in your DLL can be used.
  2. One advantage of the cipher approach over the factorization approach is that with a cipher you can allow for multiple possibly keys, and generate a separate key for each of your users. That way, if a key / password finds itself into the wild, you can at least trace it back to a particular user, for whatever that is worth.

Obfuscation

I’m going to show you how to use a free, open-source obfuscator called Obfuscar. It is not the most convenient obfuscator to use, nor is it especially powerful, but it is free. Check out Eazfuscator.NET and Dotfuscator if you want a more robust, paid obfuscator.

Obfuscar is obtained as as NuGet package. NuGet is a tool created by Microsoft that lets you easily add external libraries to your project. (It’s not used for necessity, but for convenience.)

  1. Open your project in Visual Studio
  2. Right click on the project that you want to obfuscate and choose Manage NuGet Packages
  3. Click Browse and search for Obfuscar
  4. Select it and click the Install button
  5. Create an XML file to your project folder called “obfuscar.xml” (technically, the name is arbitrary) with this text:

    <?xml version='1.0'?>
    <Obfuscator>
        <Var name="InPath" value="PATH TO YOUR BUILD FOLDER" />
        <Var name="OutPath" value="PATH TO YOUR BUILD FOLDER\Obfuscated" />
        <Module file="$(InPath)\MyAddin.dll" />
    </Obfuscator>

  6. Edit the XML file to include your actual build folder (wherever the DLL / EXE is built to). If you have multiple DLLs / EXEs to build then you can have multiple Module nodes.
  7. Locate Obfuscar.Console.exe, which will be in a solution folder called “packages”.
  8. Run this executable from the command prompt with the path to obfuscar.xml as the argument.
  9. The obfuscated assembly will be located in your build folder in a subfolder called Obfuscated.

Note: Signed assemblies will not work after obfuscation with Obfuscar and must be resigned. Instructions on how to have Obfuscar sign your assembly post-obfuscation are here.

Additional Reading On Software Security

<> Obfuscating Code. Discusses the transparency of .NET assemblies and why obfuscation is essential for protecting software written in .NET.
<> Other ideas for protecting DLLs from being referenced: Here and here and here. The last one lists obfuscation and asymmetric cryptography as the first defenses and also explains the importance of focusing on building better software that customers want to pay for, versus obsessing over thwarting people who would never buy your software in the first place.
<> Security through obscurity – Is SolidWorks API-related code “safer” just because the SolidWorks API is not widely known of or used? It depends on what you’re trying to prevent. If you’re trying to prevent someone from using your assembly, no. If you’re trying to prevent someone from repurposing your code, probably.

Conclusion

Every developer has to consider the threat of piracy and then make a business decision on what amount of security is worth implementing. No client-side assembly can be completely safe, therefore it is important to know when “enough is enough”. Sometimes, the best anti-piracy measure is to add enough value through subscription or customer service that users aren’t tempted to use pirated versions of your software.

Most of the software I write is used internally by my customers, therefore piracy is not even on the radar. If you have experience writing software that is sold more publicly then I would be interested in your preferences regarding software security.

Keith

Want to stay up-to-date with new CADSharp content, including new videos, blog posts, and training opportunities? Join our newsletter.


Leave Comment

Techniques for Obtaining Bounding Boxes

Blog 11.29.2016 No Comments

Bounding boxes are used constantly in engineering for a variety of reasons. Amongst SolidWorks users, I frequently see them used to optimize plant floor layouts, packaging sizes, and stock lengths. In this blog post, I want to cover the pros and cons of the various techniques available in the SolidWorks API for determining them.

GetBox / GetBodyBox / GetPartBox

The SolidWorks API comes with several API calls that return the coordinates of two of the bounding box corners. From these 6 double values, all of the bounding box coordinates can be obtained. These methods are:

  • IBody2::GetBodyBox
  • IComponent2::GetBox
  • IFace2::GetBox
  • IAssemblyDoc::GetBox
  • IPartDoc::GetPartBox

The obvious advantage to these methods are their ease of use. They have two notable downsides, however. First, these methods do not always return the true minimum boundary box, sometimes even being 5-10% off in some directions (in my personal experience). Second, they only return the bounding box in the primary coordinate system. If the body you wish to analyze is rotated, this could render these methods useless to you.

Our Macro Library contains an example of IBody2::GetBodyBox, available to any CADSharp.com member.

IFace2::GetTessTriangles

According to the API Help, this method “gets the triangles that make up the shaded picture tessellation for [a] face”. Practically, this means that if we want a more accurate bounding box than the methods described earlier, we can traverse every face in a body and discover the largest and smallest X, Y, and Z values in that body. For each face, we can also transform the tessellation triangle coordinates into another coordinate system, if desired. That way we are not limited to finding the bounding box in just the primary coordinate system.

Even this approach, however, might be unsuitable for machining applications. The API Help remarks for this method state, “These triangles are intended for graphics display purposes and do not represent a tessellation that can be used, for example, by a machining application. If you need the kind of accuracy associated with a machining product, traverse the body faces and extract the topology and geometry data to create your own faceting.”

Our Macro Library contains an example of using IFace2::GetTessTriangles to obtain the bounding box of a body, available to any CADSharp.com member.

IModelDocExtension::Create3DBoundingBox

This method, available as of SolidWorks 2013, creates a 3D sketch containing lines representing what SolidWorks considers to be the “ideal” bounding box for a cut list item. The sketch can be accessed via the API and the actual bounding box points retrieved (in the standard coordinate system). The bounding box created by this method is the same one used to calculate the built-in LENGTH custom property for a Structural Member feature in a weldment part.

Personally, I would hesitate to use this method for bodies not created from Structural Members, for two reasons. The first and less significant reason is that it requires generating a cut list for your model, which might be undesirable. Second, and more significantly: the “ideal” bounding box it returns might not actually be the one you need. For example, consider the following body created by a regular Extrude feature and then rotated at an odd angle with the Move/Copy Body feature:

This bounding box correctly identifies the diameter of the body and therefore returns a more accurate width than IBody2::GetBodyBox would, nevertheless the direction of the body is not recognized and therefore the height and length are still incorrect.

If you want to experiment with method on your own to see if it will meet your requirements, check out the example in our Macro Library, available to any CADSharp.com member.

IBody2::GetExtremePoint

Technically, this method does not return bounding box coordinates, but it can be used to return the length of a bounding box in a specified direction. This is really useful for obtaining the length of a body that was not created with a Structural Member feature but might also be a strange angle. (Again, keep in mind that if it was created with a Structural Member feature then we could simply use the built-in length custom property, and if the desired length were aligned with one of the primary axes then we could obtain the length with one of the other bounding box techniques.)

Unfortunately, getting to that length value using this API call is not easy. First, this method requires a direction. Obtaining that direction programmatically could be a challenge. Second, once the extreme points in that direction and its reverse and obtained, you still will not have the extreme length because the line connecting the extreme points is not necessarily parallel to the direction you want. For example, consider the following body.

Using the normal of the large bottom face as the positive direction (represented by the red line) and its reverse for the negative direction will result in the two points connected by the blue line. Since this blue line is not parallel to the red line, the blue line does not represent the “extreme length” of the model for the chosen direction. Rather, the red line actually represents the desired “extreme length”.

We can get the length of the red line using some vector algebra. Since we know the red and blue lines form the hypotenuse and adjacent of a right triangle (the yellow line is the opposite), we know that if we get the angle between the red and blue line, we can calculate the length of the red line by multiplying the length of the blue by the cosine of that angle. That angle is equal to the arccosine of the dot product of the unit vectors of the red and blue lines.

Available to CADSharp.com Power User members only is a very powerful macro in our Macro Library that uses IBody2::GetExtremePoint to calculate the “extreme length” of a input. To specify the direction, you can pass in a plane, planar face, cylindrical face, straight edge, or circular edge.

In conclusion, the SolidWorks API has many options for obtaining bounding boxes, depending on your needs. If you have another technique you have used in the past, please share it in the comments below.

Keith

Want to learn about new macros, videos, and upcoming training events? Sign up for our newsletter.


Leave Comment

What’s New In The SolidWorks 2017 API

Blog 10.1.2016 No Comments

It’s that time of year again, folks. Let’s look over some of the most notable enhancements to the SolidWorks API, as well as some tips for using the API Help most effectively.

  • Create and access selection sets. (ISelectionSet)
  • Traverse the Manager Pane and activate the FeatureManager design tree, PropertyManager, ConfigurationManager, DimXpertManager, DisplayManager, or custom tab. (IModelViewManager::ActiveFeatureManagerTabIndex)
  • Fire a pre-notification and notification when activating a tab in the Manager Pane. (See swPartCommandManagerTabActivatedPreNotify and swPartActiveDisplayStateChangePostNotify in swPartNotify_e, swAssemblyNotify_e, swwDrawingNotify_e)
  • Get whether an axis is a temporary axis and get its reference face. (IRefAxis::IsTempAxis, IRefAxis::GetTempAxisReferenceFace)
  • Get or set the state of a check box on a PropertyManager page. (IPropertyManagerPageCheckBox::State)
  • Offset selected edges to create a 3D sketch on a face or surface. (IModelDocExtension::SketchOffsetOnSurface)
  • Make a selected component independent. (IAssemblyDoc::MakeIndependent)
  • Control the isolation state of components. (IAssemblyDoc::Isolate, ExitIsolate, SetIsolateVisibility, SaveIsolate)
  • Get the names of the exploded views for a specific configuration, get the number of exploded views in a specific configuration, and get the name of the configuration for a specific exploded view in an assembly.
  • Get the name of the exploded view in a specified configuration. (IAssemblyDoc::GetExplodedViewNames2)
  • Get the name of the configuration for the specified exploded view. (IAssemblyDoc::GetExplodedViewConfigurationName)
  • Get the collapsed or exploded transform of a component when the assembly is exploded. (IComponent2::GetSpecificTransform)
  • Delete either the selected components of a subassembly or the subassembly of the selected component. (IAssemblyDoc::DeleteSelections)
  • Get or set whether to disable an equation and get the number of disabled equations in a model. (IEquationMgr::Disabled, GetDisabledEquationCount)
  • Specify a sketch plane, surface/face/plane, vertex, or offset as a start condition when creating an extruded surface. (IFeatureManager::FeatureExtruRefSurface3)
  • Specify multiple drawing sheets whose setups to modify. (IDrawingDoc::SetSheetsSelected)
  • Create SpeedPak configurations for subassemblies in an assembly. (IAssemblyDoc::SetSpeedPakConfigurations)
  • Rebuild all features or rebuild only those features that need to be rebuilt in all configurations without activating each configuration in a model. (IModelDocExtension::ForceRebuildAll)

You can see all new API calls and interfaces in the Release Notes article in the API Help. You can find it in the local API Help by searching for “Release Notes” in the category tab. Note that the local API Help Release Notes will only be as up-to-date as the service pack you have downloaded.

Tips and tricks for using the SolidWorks API most effectively:

  • Use the local SolidWorks API Help. Besides being easier to navigate than the online help and not requiring SolidWorks to be open, it is also updated each service pack, whereas the online help is only updated for SP0, SP1, and SP5.
  • If you reference the local SolidWorks API Help as often as I do, it’s more convenient to create a start menu, task bar, or desktop shortcut directly to the latest SolidWorks API Help rather than accessing it only through the SolidWorks Help menu. What I do, therefore, is create a shortcut for <SolidWorks installation folder>\api\apihelp.chm and drag this shortcut onto my start menu icon, task bar, or desktop. I’d recommend that you rename the shortcut something like “SolidWorks 2017 API Help” so you know what version it refers to.
  • If you discover an area where the API is lacking, don’t be afraid to submit it to the SolidWorks API support team. On every local API help page is a “Send comments” link at the top and on every online API help page is a “Feedback on this topic” link.

Keith

Keep up with future CADSharp.com content, training events, and special offers via our newsletter:


Leave Comment