Software Protection
Question submitted by (04 April 2001)

Return to The Archives
  Do you know any good algorithms/techniques for generating unique CD serial numbers to help identify legitimate customers at game install time, game play time, and to allow access to online games and forums?  

  This is a little off-topic, but as a shareware author, I happen to have an interest in protection against piracy & cracking (i.e. 'reversing').

Let me start off by saying that I don't know anything about the cost or overhead involved in burning a unique serial number onto each CD. I can say, however, that this will do you very little (if any) good at all. Crackers have some very sophisticated tools (SoftIce is the favorite) and they are more on top of this stuff than you or I. I guarantee you that they are also more on top of it than those companies you can purchase your protection from (i.e. hardware dongles or software-based encryption/protection schemes.)

So, let's talk about protection... This is based on stuff I figured out on my own, and while talking to people on efnet's #cracking4newbies IRC channel. If you go there, they'll help you by giving you information on how to best protect your program (they're actually a very friendly group of people.) They'll also crack your program and distribute it. So use caution if you don't want this to happen. That is, after all, what they do.

When a cracker gets his hands on a program with a key, he has two options. (1) He can crack the program so that it no longer requires a key, or (2) he can reverse-engineer the code to figure out how the key works and use that information to create a 'keygen'.

There is a third option called carding. Carding is using a stolen (real) credit card to buy a key and then distributing that key publicly. I don't condone the act of cracking, but I can at least appreciate the talent that is required by cracking some of the more difficult programs out there. However, I find carding rather distasteful in every respect (and, as I understand it, the cracking scene agrees with me on this.) It's also credit card fraud, and a federal offense. Reversing, however, is not necessarily illegal (at least, not until you distribute the crack.) You can help prevent carding (or at least, slow it down) by having a server-side serial number (key) check. If you find any carded keys being publicly distributed, simply shut them off on the server. This could open up two other cans of worms... this might make your server prone to hackers (to get keygen information) or you might find that each time you shut down a key on the server, they card a new key.

Can a program be made to be completely impossible to crack? No, not if you consider what a cracker is willing to do (even hacking into server for server-checks.) Even though I've been told (by crackers) that a program can be made uncrackable, I personally don't think so. Instead, my goal has always been to make the protection more effort to crack than a cracker is willing to expend. And they're willing to expend a lot of energy for a $5 program. It has nothing to do with money; it is all about the challenge. It's important that you understand that when you protect your program, you are challenging a cracker to crack it.

In order to protect your program (and you will need to do this, because the toughest key is useless if the program itself is easy to crack), you'll need to have a small idea of how a program is cracked, and what tools they have at their disposal.

There are some extremely sophisticated disassembles on the market. These tools are often used by driver developers and even by large companies. For example, IDA (Interactive DisAssembler) is one of the better tools. They boast that it's used by government agencies to validate commercial software, by open-source activists to support undocumented architectures, by car tuners to make modified chips for cars, etc. I downloaded the demo of this, and was amazed at what it did with my program. To the trained eye, this is almost as good as having the source code.

So, how do you protect yourself against a cracker, when they've got the source to your program? I don't know. I don't think you can. But I think you can make it more effort than it's worth. At least, that's what I'm hoping for from my protection.

When a cracker wants to crack a program they know that more often than not, the protection will simply result in a conditional jump or two. You might have an RSA encrypted key, but if your result is to simply check to see if that key is valid, you're going to end up in a conditional jump someplace after all the decryption code has run. If they comment out that jump instruction, or switch the condition, then your protection has been foiled.

My approach was a bit different. My approach was to create a key that is required for the program to run. Not logically, with conditions, but by actually using the key in the code itself. For example, there might be a math formula stored in the key. All keys have different values, but after the formula is run, the result is always the same across all keys (i.e. 10-1=9 and 7+2=9). I might use this value as a stepping value through a loop. If I packed my key with a lot of formulas, and used them in various places in the code, then the key is an integral part of the program. But will that stop a cracker? Nope. They just find all the places where I use the key, and crack them individually. This time they're not swapping out conditional jumps they're replacing the key calculation code with a few instructions that simply place the result from a formula into a register. And they can easily find all the places where I use the key, by using the disassembling tools and searching the ASM source.

But I think this is a good start, so I decided to enhance it a little. My goal was to place so many key checks in my program that they couldn't possibly crack them all. At the same time, I didn't want to pollute my program's source with thousands of key checks. As a solution, I decided to use a few simple #defines.

After a few well designed #define statements (so I was sure everything was inlined) I was able to proliferate my key checks so much, that my executable grew by 300K. My key checks are 33% of my program's size. So, as an example:

    #define sizeof(a)  (keycheck_calculation + sizeof(a)) 

If that particular key check calculation were supposed to result in zero, the program would run fine. However, if an invalid key was used, the result drastically affects the way the program operates, and probably makes it crash.

There's still more work to do. You have to secure all aspects of the program as tightly as possible, because a cracker will find the weakest link. For example, my program requires a registration code to run. The trial version of the software simply disables the save feature. If I were able to allow people to register the trial version by simply entering a key, then the cracker could simply crack the trial version to enable the save.

Instead I would need to have two separate versions of the program (trial/registered.) The trial version would have the save code conditionally compiled out, so there's nothing to crack. Or is there? Couldn't the cracker compare the differences between the trial version and the registered version and simply add the code to the trial version? Yes, they could. Fortunately, with my key checks proliferated throughout my registered version so thoroughly, finding this extra code would be a nightmare. Not impossible, but very difficult, especially if the needed code from the registered version was littered with key checks.

This was about as much effort as I was willing to put into it (at this point, it was about an evening's worth of work.) Since it was just an evening's worth of work, if I found that my program was cracked, I would simply change the protection in the next version. After all, I was writing a shareware program, not a protection library.

One thing I haven't covered is the actual key generation. Ideally, you want your keys to be non-reversible, even if the cracker has the source code to see how they work, they don't have enough information to actually create a valid key (much like how many popular encryption algorithms work today.) I won't go into how mine works. Besides, I'm not an encryption expert by any stretch of the imagination. My goal was to make it (what I thought of as) un-reversible, and hide the rest in obscurity. :)

There are a lot of other aspects that I haven't covered (like checksums in your program to make sure it doesn't get patched, debugger-detection, etc.) I didn't cover them because most of them are useless. At best, they'll slow the hacker down and waste a few minutes their time. More importantly, I didn't cover so many aspects because I'm simply not even aware of them. I'm not an expert. If you really want serious help on this topic, go find an expert cracker that's willing to help you.

Response provided by Paul Nettle

This article was originally an entry in flipCode's Ask Midnight, a Question and Answer column with Paul Nettle that's no longer active.


Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
Please read our Terms, Conditions, and Privacy information.