Click here to Skip to main content
Click here to Skip to main content

Copy Protection for Windows Applications

By , 7 Sep 2009
 

Introduction

This is the first of a four part series of articles on a system that allows you to protect your software against unlicensed use. This series will follow the outline below:

  • Describe the architecture of the system
  • Describe the creation of the license key
  • Describe the process of installing a license key
  • Describe the process of validating an installation

Along the way, we will look at n-bit block feedback encryption (also known as cipher-block chaining; see the Wikipedia article), and an obfuscation method that should suitably prevent any attempts to understand how the system works. To add to the difficulty of reverse engineering the system, the result will be a set of COM objects written in unmanaged C++. Interop can be used to invoke the appropriate methods from the .NET framework.

Background

Protecting the investment of time and energy made to develop the next great software application is something that worries a lot of developers. If you are one of the software powerhouses, hackers / crackers will target you. But for everyone else, having a decent system that prevents casual copying should ward off all but those with the skill and determination to use your software without paying you for your services.

This was always an interesting problem in my mind, but it wasn't until I wrote a fairly large application (based on Leslie Sanford's awesome MIDI C# Toolkit) to manage my MIDI addressable music devices on stage during the shows my band played that I started taking the idea seriously. After all, it took me two years to get the system to a truly usable point, and since I thought the system was definitely marketable, I wanted to protect my investment.

Disclaimer

Before we begin, your expectations need to be properly set. Specifically, no one reading this series should delude themselves into thinking this system, or any copy-protection mechanism for that matter, is ironclad. The question you should be asking yourself is not "can anyone crack this" but instead "does the person have the skills to do it and will they think it is a reasonable investment of their time to figure out how it works?" Remember: where there's a will, there's a way.

Objectives

The overall objective of the system, obviously, is to prevent people from distributing your application in an unauthorized manner. More specific objectives that lead to this main goal are listed below:

  • Ensure that attempts to reverse engineer the code responsible for providing these services is not trivial
  • Provide that any license keys are sufficiently "hidden" when stored, i.e., they are not easily found nor can they be easily changed
  • Separate the code to create license keys from the code to validate them to allow your application to distribute only the validation routines
  • Provide for easy integration with Microsoft's installation utility or a home grown installer
  • Allow your application to determine if an installation is valid
  • Allow your application to specify licensed capabilities, e.g., allow them to use the runtime but not the designer
  • Allow your application to determine the date of installation so that evaluation periods may be supported

Since the prevalent means of distributing frameworks like this is COM, we will be using that for the create key and validate key functionality. By necessity, the install key functionality will simply be a DLL with the appropriate entry points exported. This is because Microsoft's installation utility does not allow for integration with external COM objects, ironically enough.

Requirements Mapping

Let's match up the objectives with specific means to achieve those objectives.

  • Reverse engineering should be non-trivial. The code will be written in a non-managed language. While there is still the possibility of using a disassembler to view the compiled output, the difficulty with which understanding of the code is achieved is considerably higher, especially given the skeletal constructs that are created by the IDL compiler to support the necessary interfaces with the COM subsystem.
  • Hidden license keys. We will be using the n-bit block feedback cipher to encrypt the data that the license keys provide, and will also use it to encrypt the key itself before storing it.
  • Separation of code. We will have separate executable images for each of the main function groups.
  • Easy integration with Microsoft's installation utility. We will discuss this in detail in part 3.
  • Determine if an installation is valid, specify licensed capabilities and evaluation periods. This is all related to the data that we store in the key itself, and will be discussed in parts 2 and 4.

In addition to these, I also added the ability to specify a manufacturer identifier and a product identifier. This was to allow me, as was originally intended, to write a Web Service that would accept a license key printed on a packing slip and generate an installation key as a result. Having these identifiers would have allowed me to advertise this Web Service to software vendors that wanted to use this system.

Logical Architecture

Below is the logical architecture of the system:

The diagram above illustrates the final, logical design of the system. It evolved to this structure as the capabilities of the system matured. The shared functions reside in a statically linked library and contain the cipher functions in addition to the storage / retrieval system. The other components are fairly simplistic in nature; typically, they convert the arguments to internal structures and call one or more functions in the shared library.

Interfaces

The COM interfaces are listed below. Since COM interfaces are easily inspected using any number of publicly available tools, the argument names are intentionally vague. Granted, this isn't as robust as code obfuscation, but since there are a total of three interfaces, I didn't feel that obfuscation was worth the hassle.

BSTR NbbfCLib.Create(LONG lID1, LONG lID2, LONG lID3);
bool NbbfVLib.Validate(LONG lID1, LONG lID2, LONG lID3);
LONG NbbfVLIb.Elapsed(LONG lID1, LONG lID2);

As stated above, the install key functionality is - by necessity - a standard DLL with exported functions. These functions are listed below:

void InstallMSI(MSIHANDLE hInstall);
bool InstallDirect(LPSTR lpstrKey, LONG lID1, LONG lID2);

We'll understand the meaning of each of the arguments as we discuss the specific functionality of each component.

N-bit Block Feedback Encryption

As a tangent, I remember "discovering" this algorithm one morning in the shower as I prepared to go to work. I was obviously disappointed when I saw that I wasn't the first person to think of it, but given the difficulty that is usually associated with Cryptography, I was secretly happy that I didn't look like a total idiot either. According to the Wikipedia entry, this type of cipher (also known in some places as cipher block chaining) was developed by IBM in 1976 and patented under the title "Message verification and transmission error detection by block chaining" (US Patent 4074066).

The cipher essentially works in the following manner. We'll use schar to denote a character from the source (unencrypted), pchar to denote a character from a password-like overlay, and dchar to denote a character after the cipher operation has completed.

XOR schar[0] and pchar[0] to get dchar[0]
For each i > 0, XOR schar[i] and pchar[i] and dchar[i-1]

Decryption is exactly the opposite process.

As a side note: n (in the name of the cipher) is 8 since 8-bits is one character, and that is the block size.

Since I am processing one byte at a time - and because I want to look like a professionally done registration key - I also take the opportunity to convert the data (which is numerical) from base 16 to base 32, using a modified character set to represent values above 9. In hexadecimal numbers, go from 0-9A-F, and base 32 would be 0-9A-V. But to mix it up, I extend that all the way to the letter Z and removed 4 letters from the middle of the alphabet. Then, I moved the digits 0-9 from the front to various places within the set of letters.

The conversion works this way: the code looks at the hexadecimal digit being processed and then finds its position in the array of pseudo base 32 set of "digits". The index within that array is taken as the numerical value for the original digit. The XOR operations are performed on the digits post conversion to ensure that a complete range of values are produced as a result.

It's important to note my choice of the size of the set of digits: it's a power of 2. This means that any unused bits (in this case, the 3 most significant bits) will never be set, meaning that you never run the risk of ending up with a numerical value that cannot be represented using the set of digits. It's also important to note that, since a hexadecimal number is always used as an input to the conversion, every hexadecimal digit must be in the set of characters used in the base 32 conversion. Specifically, the set looks like the following:

#define BASE32_CHARSET (LPSTR)"AB0CD1EF2HI3KL4MN5PQ6RS7TV8WX9YZ"

If you want to change things to prevent your use of the library from being "cracked" by readers of this article, simply rearrange the letters in the preprocessor macro definition.

Summary

The next installment will look at the mechanism used to create the keys themselves.

History

  • September 7, 2009 - Initial version.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

About the Author

Foolomon
Sales
United States United States
Member
Larry Salomon has been writing code since he started in Basic on a TRS-80 Model I computer (4K of RAM!) in 1980. Professionally, he started in the OS/2 arena in the late 80's until he switched to Windows development in 1996.
 
During his multi-decade career, he has coauthored two programming books and published an electronic magazine for just over 3 years. He has written applications in a variety of languages - C# is currently his favorite - and has a few applications available for sale on the Android Application Store.
 
Currently, Larry works in corporate software sales in the NYC area. You may follow him via his blog at http://larrysalomon.blogspot.com

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionProblems with 64-Bit Operation systemmemberSkopi25 Jul '12 - 3:19 
In 32-bit operation system everything is working well, but in 64-bit operation system a can't create key. Can you help why this happening.
AnswerRe: Problems with 64-Bit Operation systemmemberFoolomon25 Jul '12 - 3:21 
Did you compile the project as a 64-bit target? Or are you running it as a 32-bit target in compatibility mode?
GeneralMy vote of 5memberMihai MOGA22 Jun '12 - 4:13 
This is a great inspiring article. I am pretty much pleased with your good work. You put really very helpful information. Keep it up once again.
Questionproblems in installing more then (2^16 - 1) 65535 value as ManuID or ProdIDmemberchandrakar ashish7 Feb '12 - 1:50 
Hi Foolomon,
 
First of all thanks for creating nice article.
 
i am creating my own license manager utility for that i found your article. but when i tried to run this application its work only for BASE_HEX 16 and failed if you enter value more then that. i tried again after changing the BASE_HEX value to 32. as shown below
#define BASE_HEX 32
 
#define LWORD(lValue) (int)(lValue & 0xFFFF)
#define UWORD(lValue) (int)((lValue & 0xFFFFFFFF) >> 32)
 
But still i am getting the install and validation errors
please let me know what changes required so that it can work for base 32.
 
My another problem is similar to above one.
I have to put MAC address information as part of encrypted key but MAC-ID is 12 Hex digit long, it is possible to encrypt if algorithm supports Base 64. Will this algorithm supports Base 64 encryption. and what changes required if it is?
Breaking the MAC address in parts is my last option.
 
Thanks & Regards,
Ashish Chandrakar
AnswerRe: problems in installing more then (2^16 - 1) 65535 value as ManuID or ProdIDmemberFoolomon7 Feb '12 - 10:07 
The encoding of the manufacturer and product IDs is limited to 16-bits, which is defined by the MAX_PARTLEN constant (4, which represents the number of bytes).
 
Unrelated to this, the BASE_HEX constant (#define technically) is used to define the radix for hexadecimal numbers only (and is used in computations within the code). Granted, this value will never change so technically a constant isn't needed, but using one increases code readability.
 
As far as node locking a license (which is presumably why you want to include the MAC address), there are only two use cases that I can envision:
 
1. You print the license key on the CD/DVD envelope, in which case you don't know the MAC address anyway.
 
2. You generate the license key dynamically on a website, via a web service, etc. in which case you have several other ways for storing the MAC address to prevent the software from being used on another computer.
 
To support what you're attempting, however, you would need to extend the length of the license key by at least 12 digits (MAX_KEYLEN value); modify all of the OFF_* macros to return subsets of the license key based on hard-coded starting indices; change the MAX_PARTLEN value to 8; update the configuration data for the MSI installer in Orca; and (probably) make a number of code related changes. (I haven't done this, obviously, so I am only assuming code changes would be required since I would be hard pressed to believe my code would work after making such a huge architectural change by simply recompiling.)
 
This is beyond the scope of the series but not impossible to do. You simply need to ensure you understand how the code works (read the articles carefully!) and then you should be able to make the changes yourself.
GeneralQuick Questionmemberkcrismon19 Jan '10 - 8:26 
I am trying to run this on Windows 7. I download the app, go to Test/bin/debug. I run the app, use 1 and 1 and then click create, I get an exception that boils down to the COM component has NOT been registered. No worries... Use the old stand by regsvr32 command line tool. Alas, it is unable to register the component.
 
Any thoughts?
 
Thanks,
 
Ken
GeneralRe: Quick QuestionmemberFoolomon22 Jan '10 - 10:17 
The COM components are not self-registering if I recall correctly. You do have to either download the NbbfC and NbbfV projects and build them (which will register the executables) or use regsvr32 as you did.
QuestionWhat's the purpose in the second XOR?memberTheFeaz5 Nov '09 - 13:54 
I haven't been all the way through this article (haven't read all four parts yet.) but I'm wondering, when it comes to the encryption of the key, we have the two-step process where we XOR s[0] and p[0] = d[0], then for all s[n] where n > 0, we XOR s[n] and p[n] and d[n-1]. What is the purpose of XOR'ing the result on each number to d[n-1]? If this is a standard formula, then this particulr step seems superfluous, since the reverse engineer would simply reverse that step for all d[n] where n > 0. Granted, this wouldn't reveal the source yet, because they'd still need the key. The step though just seems pointless since it's easily stripped away. Am I missing something? It's been a long time since I took algebra courses (even though I'm a software engineer, I don't work with this kind of stuff every day.) Isn't XOR a distibutive operation? (meaning a ^ b ^ c == a ^ c ^ b == c ^ b ^ a, etc.)
 
-- James
AnswerRe: What's the purpose in the second XOR?memberFoolomon5 Nov '09 - 14:01 
The purpose of the second XOR is to induce the following behavior: the values of the encrypted bytes are affected by the values in the source data prior to that particular position.
 
You are correct in that XOR is distributed and, by definition, is easily undone. But for the casual observer who is looking to use pattern matching techniques or other rudimentary analysis to try and break the cypher algorithm, this "forward reaching" behavior makes things more difficult since any attempt to change a single byte in hopes of observing the change in the encrypted result (to build a mapping, for example) will in actuality change the entire result from that point onward.
GeneralRe: What's the purpose in the second XOR?memberTheFeaz5 Nov '09 - 14:36 
Thanks. I kind of figured it was something like that, but wasn't sure. Thank you for writing this. I'm really enjoying it... It's forcing me to dig WAY back into the vault to the good ol' days of C++. Smile | :) Man I miss pointers... (Really). Today I work in C#. It's great for business, but all the managed code makes it nearly impossible to obfucate your work. Not a big deal in the corporate environmetn I work in; All of our customers are internal, but I'd hate to write the next great app in .net only to have someone run .net reflector and read my source code.
 
I guess I sort of play both sides of the fence; On one hand, I want cheap software, especially when it's things like Adobe photoshop that I just want to play around with, not make a living on, and can't justify paying $700 for a "toy". On the other hand, as a developer, I'd really like to develop cheap, but highly functional applications. To be fiscally successful, such a venture demands pretty good copy protection. Unfortunately, even if an app is only $5, there are those who will copy it rather than just pay the price.
 
I'm intrigued by appliations such as the NCP Client (a really nice app that runs in Windows 7 / Vista 64-bit for connecting to, among other things, Cisco IPSEC VPN). Thier software hasa a pretty aggressive copy-protection scheme that involves server-based authentication. They can restrict the number of installations and basically "marry" a given licence to a PC to prevent you from installing a licenced copy on multiple PC's. Similarly, the Altova XML-SPY application has some sort of "tattle-tale" feature, that lets the world (or someone) know when it's in use, so you can INSTALL multiple copies, but can't RUN them simultaneously (becasue they "tattle" on each other over the network.) I'm persaonlly quite intrigued at such an approach.
GeneralRe: What's the purpose in the second XOR?memberFoolomon5 Nov '09 - 14:48 
Well, this is similar to what I was alluding to when I was responding to the other individual that commented my scheme is 100% useless (see the Part 4 comments). My suggestion about using an online activation scheme would require an installer that submits the CPU ID along with the installation key to a server, which then uses the CPU ID as part of the data that gets encrypted.
 
When the application is run, it would extract the CPU ID from the data and compare it to what Windows is reporting. The bad thing is that it "node locks" the application, but this is a standard licensing scheme for many corporate software packages.
GeneralRe: What's the purpose in the second XOR?memberTheFeaz5 Nov '09 - 15:01 
Well, I haven't gotten through part IV yet, but I find this all FAR from "useless". Again, I'm appreciating the concise info. What do you mean by "node locking"?
GeneralRe: What's the purpose in the second XOR?memberFoolomon5 Nov '09 - 18:05 
Node locking is a means of licensing where the software may only be used on a specific computer (the "node"). Other licensing schemes use name locking, where the license is for a particular userid. Finally, there are concurrent sessions that may come into play or perhaps the software is licensed by the amount of data it processes.
 
And I'm glad you like the series. Thank you for your support.
GeneralMy vote of 1memberBartosz Wojcik13 Oct '09 - 9:12 
because it is
QuestionMIDI app?memberjeffb4227 Sep '09 - 19:44 
"...but it wasn't until I wrote a fairly large application (based on Leslie Sanford's awesome MIDI C# Toolkit) to manage my MIDI addressable music devices on stage..."
 
What is the application which you wrote? I didn't see any mention of it on your blog.
 
-Jeff
AnswerRe: MIDI app?memberFoolomon28 Sep '09 - 3:35 
http://midi-live.shaven-goodness.info
GeneralTimely InfomemberBill Gord15 Sep '09 - 9:24 
Thanks, so far it gets my 5, looking forward to the last two parts.
GeneralRe: Timely InfomemberFoolomon15 Sep '09 - 12:49 
Thank you for your support!
GeneralGood article, still easy bypass!membergalpha12 Sep '09 - 7:11 
First of all, nice article Smile | :) . The only downside I have with this is that you offer no protection for reverse engineering situations. First of all, you use a DLL to do your validation for the serial. This is easy for a reverse as you only need to look at the exported functions and you quickly find the functions responsible for the validation (even with their names!).
 
All I would need to do is hook the Validate method in NbbfV.dll and make it return true and voilà, all the protection is removed.
 
While I agree everything is possible to reverse and there's no way to actually prevent a cracker from doing it, some techniques can be used to make the job harder, and probably discourage the bad ones.
GeneralRe: Good article, still easy bypass!memberFoolomon12 Sep '09 - 7:51 
Good point as others have made. I will say that it isn't necessarily trivial to find these COM objects as the source of the validation. As you will notice, I named the objects rather vaguely and did not update the description string in the IDL file intentionally.
 
The great thing is that you will have the code for the entire solution (after part 4 Wink | ;) ) so you can just as easily integrate it directly into your application.
 
As a side note, if it weren't for the need to integrate with .NET applications I would have left this as a statically linked library (i.e. .LIB file). That would have rendered the COM method interception technique as a means to bypass the validation useless.
GeneralPart 2 has been publishedmemberFoolomon10 Sep '09 - 10:57 
Part 2 in the series has been published.
 
Copy Protection for Windows Applications (Part 2)[^]
GeneralExcellentmemberLeslie Sanford8 Sep '09 - 18:37 
Excellent article, Larry. Copy Protection is something that I've become more and more interested in. I'll be keeping an eye out for upcoming articles. Smile | :)
GeneralRe: ExcellentmemberFoolomon9 Sep '09 - 3:29 
Heh. I'm trying to pay it forward like you did with the MIDI toolkit.
 
In all seriousness, I've been trying to reverse engineer copy protection schemes on and off since the mid-80's when companies were using oddly numbered sectors on 5 1/4" floppies. It wasn't until later in the 80's when a friend showed me his CopyIIPC board that I knew any copy protection scheme could be broken. (That board did an analog copy of the floppy rather than do an A->D conversion and then write out the bit values. This allowed you to copy disks with "weak bits" on them.)
 
Still, writing a decent copy protection scheme isn't terribly hard. You just have to think it through a bit.
GeneralValidationmemberGuido_d7 Sep '09 - 23:28 
Quick question, how do you validate whether the product has been licensed? Isn't that easy to circumvent - by replacing your COM object with another, supporting the same interface, but always returning true when querying if the product is licensed?
GeneralRe: ValidationmemberFoolomon8 Sep '09 - 2:45 
This is certainly possible. But the new COM object would have to have the same CLSID, IIDs, etc. and that may be more trouble than you think.
GeneralRe: Validationmemberbob1697229 Sep '11 - 8:23 
Foolomon wrote:
But the new COM object would have to have the same CLSID, IIDs, etc. and that
may be more trouble than you think.

 
That's COM 101 at it's most basic level. This approach would be a piece of cake for any MFC programmer(past or present). I was thinking the same thing as the OP before I even read the OP's comment.
 
Besides, it all boils down to an if/else statement that can be easily manipulated by using a binary editor to insert a NOP instruction or two in the executable or dll, rendering all the advanced obfuscation meaningless.
 
I spent months designing a solution similar to this one(with all the code in the executable). When testing if someone could break it, I simply traced the assembly which led me to that last if/else and I inserted a few NOP instructions in there (using the binary editor in Visual Studio) at the appropriate spot and my "advanced" licensing and obfuscation code was rendered useless. It ran fine as if I had a valid license key.
 
It's a waste of time at the end of the day IMHO.
GeneralThanks!memberreinaldohf7 Sep '09 - 12:41 
I was looking for something like this.
Foolomon, thanks so much!
 
Reynaldo Henrique
GeneralRe: Thanks! [modified]memberFoolomon7 Sep '09 - 12:51 
Thanks!
 
The code may continue to evolve as I discover bugs or whatever while writing parts 2-4 so be ready to replace the COM objects contained in the sample application with the final versions.
 
-Larry
 
modified on Monday, September 7, 2009 7:07 PM

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 7 Sep 2009
Article Copyright 2009 by Foolomon
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid