Note written December 27th, 1997:
Copy protection is now a historical curiosity, but this essay still makes for fun reading. Perhaps the concepts can be adapted to network security problems.
Copy protection has been on the wane for some years. The need for it has lessened; as games have grown bigger, with more diskettes and larger manuals, it has become more difficult for customers to casually make copies. Nevertheless, we decided to provide copy protection with Patton Strikes Back (we had an unconventional reason for doing so; I won’t explain it here). But we also had a special goal for our copy protection system: we wanted to create a system that would be, for all practical purposes, uncrackable. I had heard too many idle boasts that crackers could beat anything, and I wanted to prove that boast wrong.
Dave Menconi and I concocted a very good scheme, so good that I decided to treat it as proprietary, something that I wanted to guard closely, and in fact on several occasions I took strong measures to protect my proprietary rights. So why am I giving it away now? Because copy protection continues to decline in importance, and the commercial value of my technology falls with each passing day. I figure I might as well give away what I can’t sell. Besides, I won’t give away all of my tricks.
One might also wonder what the value might be in a published protection system. If the bad guys read this article and learn the techniques from it, won’t they be able to beat them? Wouldn’t that make this article useless? I think not read on, and you’ll see why knowledge of the techniques does not convey a ready solution.
The basic approach of any copy protection system is simple and takes three steps: 1) present the user with a challenge, a question that can only be answered using information from an uncopyable source such as a code wheel or a word from the manual; 2) compare the user’s response with the correct response; and 3) if the response is incorrect, disable the game.
There are many ways that this system can be defeated. The one way that I will not address is for the pirate to copy the uncopyable external source of information; that problem is beyond the scope of this article. Some of the most common strategies available to the cracker are:
•skip over the challenge so that it never happens.
•find the internal file of challenge/password combinations and publish it.
•patch the challenge so that it always thinks that the correct password has been entered.
•patch the challenge so that its attempt to disable the game fails.
The designer’s biggest problem in creating copy protection is that everything he does is out in the open where the cracker can see it. The designer simply cannot guarantee the security of anything he does, because the cracker can observe any computation while it is being done. In short, our problem is to process information in full view of the cracker, yet not allow him to see it.
The trick here is misdirection. We cannot lock the information up; it’s out in the open where he can see it. Our task is to camouflage the information, to make it look like something else. That way, the cracker can look right at it and never notice it. We want to give him so much stuff to look at, so many key look-alikes that, due to fatigue and frustration, he won’t notice it when the real key comes into his field of view.
Remember, this is not an absolute solution. We cannot lock up the key behind a wall of steel. It will be out in the open. But we can create so many false keys, so many deceptive images of keys, that the cracker will probably not find the real key. That is our fundamental strategy.
Hiding the Password File
Our first task is to retrieve the challenge and the password, presumably from a file on the hard disk. This is the most easily cracked process, and we must take special precautions. First, we must disguise the contents of the file. The cracker could play the game once, find a single challenge/ password, and then perform a simple file search for that text. This would reveal the critical file for his publication. The first step, then, is to encode the text of the file. Do not use a standard encoding scheme; the cracker will probably try the standard methods. Make up a simple but unique encoding system. I suggest a moving-key system in which the coded result of each character in the text stream is based on the previous value. For example:
PreviousChar := SeedCode;
FOR i := 1 TO FileLength DO
PlainChar := ASCII(PlainText[i]);
NewChar := (PlainChar + PreviousChar) mod 256;
CodedText[i] := Char(NewChar);
PreviousChar := PlainChar;
Just make sure that you use a different formula for each copy protection system you build. This will slow them down.
Now that your text is encoded, you must hide it. You can’t just have a file called "Password File". And if you have a file just laying around with no obvious purpose, it’s sure to attract attention. You want to hide that file somewhere inside another file, preferably in a way that allows the cracker to examine it without realizing that it’s not quite right. I hid my password file inside the sound file of an explosion. The cracker can look at the file, play it with a sound editor, and never realize that funny scratching sound in the middle of the explosion is really my password file. Of course, I’ll never use that trick again -- there are lots of places to hide a few K of data where nobody will ever notice them.
Reading the Password File
Now you have to get the password file into the computer so that you can select a password. Do not do this immediately before the challenge -- it’s too easy for the cracker to trace backwards. Instead, do it long before the challenge. Ideally, you should read the password file as part of some other routine file-reading operation, so as not to attract attention to the hard disk activity. You can bet that, if the cracker sees the hard disk light flicker with no obvious result, he’s going to do a little investigating. So wait until some necessary disk activity and piggyback your password retrieval operation onto it.
Now select your challenge/password from the full set of challenge/passwords. But don’t decode it just yet! (This was one of Dave’s ideas.) If you decode it now, it’ll be sitting around in RAM just waiting to be found by a simple text string search. Leave it encoded; later, when you collect the user’s input, you can encode the input and compare the encoded strings that’s slightly more secure than decoding the password and comparing it directly with the user input.
How do you pass this data to your challenge routine? If your first thought was to store it in a global variable, go sit in a corner -- you’re just not devious enough for this job. After all, a global variable sits out there with a big sign on it saying, "Look at me!" We need a better way.
I came up with an appropriately fiendish solution to this problem. Please wait a moment while I pat myself on the back for the umpteenth time... thump, thump, thump. OK, here it is: store the data on the local variable stack frame. Remember what happens when a high-level language such as C or Pascal calls a routine? First it sets up a stack frame on the stack to reserve RAM space for the local variables. But these variables are not initialized; they initially contain whatever garbage was already there. The trick is to make sure that the "garbage" just happens to contain the data we want to pass.
The stack accounting for this process can be very tricky. The easiest way to accomplish it is to have one outer-level routine that calls both routines (the one that puts the data onto the stack and the other that uses it). Then you need merely insure that the local variable declarations of the two routines are byte-harmonized. My approach was even simpler than that: I combined the local variable declarations for the two routines into one master local variable declaration which I then used for both routines. This has the bonus of making the cracker’s stack-snooping that much more tedious.
Covering Your Tracks
So here we are at the beginning of the challenge routine. We have our real challenge/ password combination sitting in the garbage of our uninitialized variables, all ready to be used. But now we have to cover our tracks, lest the alert cracker ask where that data came from. We must now go through the motions of loading in a fake set of challenge/passwords. So we ostentatiously load a file called "Password File". This file must, of course, contain plausible challenge/password combinations. The trick is to make the first dozen password combinations legitimate copies of the real password file. That way, the careful cracker who double-checks the file will of course verify that its first entries are indeed genuine ones.
Now at this point we take advantage of one of the most common programming errors in the business. How many times have you set up some messy loop and screwed up the terminating conditions by just one step? In other words, does your loop go all the way to the very last value, or does it stop short by one, or does it do one pass too many? We all make that mistake -- so let’s use it as a means of messing with that punk cracker’s mind. Let’s deliberately screw up the terminating condition such that the very last entry in the list of challenge/passwords is not read in. That’s where we arrange the real password to sit. Thus, we start with an uninitialized array of challenge/passwords, except that the very last entry is the honest-to-God challenge password left on the stack previously, then we load in all the false challenge/passwords, but due to a "bug" in our code, we fail to overwrite the honest-to-God one.
Now we have to select the genuine challenge/password. We have to be careful here; since we are inside the challenge routine, we know that the cracker is watching. Our problem is very similar to that of a card shark who wants to pick a particular card out of the deck without the audience understanding how he did it. And in fact, the algorithm that I used to accomplish this is called "The Hand is Quicker Than the Eye". We want to make sure that we always pick the last entry from the challenge/password table. -- without the watching cracker catching on.
At this point I take advantage of another bit of cracker psychology. Crackers are not well-rounded people. They’re twisted personalities, obsessed with the computer and technical trivia. This means that while they know lots of petty crap about computers, they know very little else. In particular, crackers tend to regard arithmetic the same way that vampires view crucifixes. So a nice, complicated arithmetic calculation is sure to send them skipping to safer areas of code. So let’s just wrap our critical work in some protective layers of arithmetic.
The algorithm I devised to solve my problem looks very much like a card-shuffling routine. It uses a complicated procedure to repeatedly exchange pairs of table entries, using an arithmetic formula to determine which pairs are shuffled. The formula is recursive, so that the cracker who wishes to penetrate it must follow it through all 300+ steps -- and if he makes one mistake along the way, he’s lost it. Suffice it to say that the formula is rigged to insure that after it has gone through a certain number of steps (more than 300 steps), our honest-to-God password is sitting at the top of the table. We now pick the card from the top of the pile. Lo and behold, it’s the honest-to-God entry! The hand is quicker than the eye -- especially at 16 MHz.
So now we go through the normal motions with the player. We perform an ostentatious comparison of the player’s entry with our table entry, and then set a global flag that will later be used to disable the game if it is FALSE. Of course, you didn’t believe for one moment that this flag really does anything, did you? You didn’t think that I would be that obvious, did you? Silly boy!
No, we do the same trick going out that we did coming in. We put the player’s response to the challenge into a local variable where -- guess what -- it remains on the stack. A few million cycles later, we come back to the stack location from a safe routine buried deep inside boring housekeeping stuff. That routine retrieves the player’s response as well as the honest-to-God password, encodes the player’s response, and compares the encoded results. If they match, all is well. If not...
Our next task is to disable the game. Now, we must be circumspect here. Initiating an immediate crash or lockup, or putting up a dialog box that terminates the game, are all incorrect solutions, as they are dead giveaways to the cracker. We should have taken care of the confused or ignorant but otherwise innocent player in the original password dialog. We need a scheme to deal with the cracker who has somehow cracked the outer layer of protection.
There are two requirements for our disabling system: default failure and delayed response. Default failure means that the program begins life with a fatal but hard-to-find bug in it. The copy protection system repairs the bug only if the player meets the password challenge. That way, the simple-minded strategy of patching out the password challenge will fail.
Delayed response means that the disabling does not take place immediately. Instead, it takes place much later, so that when the crash happens, the cracker will have a whole lot of backtracking to do to get to its source.
In Patton Strikes Back, I took one of the military units and fudged its initializing data to a false value that, while initially harmless enough, is certain to cause a program crash just before the end of the game. The copy protection code sets the value back to the correct value. This has the advantage that the code looks for all the world like a typical unit-manipulation routine.
But this is only the second layer of protection. The next layer uses guard routines. These are routines that checksum other routines, thereby insuring that patches are detected at runtime. Guard routines should be executed rarely -- every time you call one, there’s a chance that the cracker will be looking in on you at that moment. Besides, you only need to catch him once. Since guard routines are short and fast, it’s a good idea to sprinkle them liberally throughout your code. Have them guard different things: different portions of the challenge routine, maybe the two routines that precede and follow the challenge routine.
Guard routines have a flaw: they can be found with a carefully contrived automated code search, and when found will point to the routine they are guarding. Thus, guard routines can be "turned" and made to spill their guts about the other routines in the program. The best defense against this is to use many slight variations in your guard routine code, most especially in the line that directly accesses the client code. If just one guard routine uses a code sequence that slips past the automated code search, you’ve created a gigantic headache for the cracker, the more so because he will assume that his automated code search revealed all guard routines.
Harken to the wisdom of the lowly virus here. It does not seek to conquer the body’s immune system. It just keeps making minor changes in its protein sheath, hoping to stay one jump ahead of the immune system’s recognition subsystem. You should do the same.
Above all, have guard routines that guard other guard routines. The idea here is to create such an interlocking web of guard routines that the cracker will go nuts just trying to seek them out and neutralize them.
Do not have the guard routines generate the same bomb. Instead, have them do different things. My most fiendish trick was a guard routine that restores its client code to its original state. I can just see the cracker mumbling to himself, "I thought I disabled that thing half an hour ago..."
I had a lot of fun putting all this together. The basic strategy is simple: mess with the cracker’s mind. It’s not hard: these people are not broadly educated and therefore easily duped. Think of yourself as a master of prestidigitation, not the guardian of a fortress.
And how did Patton Strikes Back fare against the crackers? I think it worked pretty well, but I can’t say for sure. I have yet to receive a single report of a cracked version of the game. This surprises me, because the IBM version was shipped without the full suite of protections (we encountered technical problems with the system late in the project and simply ripped out much of the system). The Mac version, though, contains the full system.
If you really want to understand how all this works, get yourself a copy of the Macintosh version of Patton Strikes Back and try to crack it. Keep track of how much time you invest in the effort and then contact me if (ha!) you succeed. No fair using the IBM version -- it’s truly toothless. And success is defined as creating a copy that can be played without recourse to the manual. My guess is that, even with this article in hand, you’ll still need at least 50 hours to make the crack.