So you've
worked 10- to 12-hour days for the past two years, trying to make your
latest game the best ever. You even added copy protection to try to stop
the pirates, but within a few days of release there are already crack
patches flying around the Internet. Now anyone can help themselves to
your hard work, without so much as a "please" or "thank
you."
This is what happened to Insomniac's 1999 Playstation release, Spyro 2: Ripto's Rage. Even though it had good copy protection, it was cracked in a little over a week. So when we moved on to Spyro: Year of the Dragon (YOTD), we decided that something more had to be done to try to reduce piracy. The effort was largely successful. Though a cracked version of YOTD has become available, it took over two months for the working patch to appear, after numerous false starts on the part of the pirates (the patch for the European version took another month on top of that). The release of patches that didn't work caused a great deal of confusion among casual pirates and plenty of wasted time and disks among the commercial ones.
Two months
may not seem like a long time, but between 30 and 50 percent of most games'
total sales occur in that time. Approximately 50 percent of the total
sales of Spyro 2, up to December 2000, were in the first two months.
Even games released in the middle of the year rather than the holiday
season, such as Eidetic's Syphon Filter, make 30 percent of their total
sales in the first two months. If YOTD follows the same trend,
as it almost certainly will, those two to three months when pirated versions
were unavailable must have reduced the overall level and impact of piracy.
On top of this, since YOTD was released in Europe one month after
the U.S., those two months protected early European sales from pirated
copies of the U.S. version.
|
|
![]() |
|
||
|
|
||||
|
|
Though a cracked version of YOTD has become available, it took over two months for the working patch to appear, after numerous false starts on the part of the pirates. |
|||
So why did
it take so long to crack YOTD when a patch was available for Spyro
2 so quickly? The difference was that Spyro 2 only had copy
protection, while YOTD added crack protection. The crack protection
complemented the copy protection by checking for alterations to the game,
rather than just making sure the game was run from an original disk. This
extra layer of protection slowed down the crackers significantly, because
removing the copy protection had to be done without triggering the crack
protection. Basically, YOTD is booby-trapped — one wrong bit
and it will blow up in your face. This article will explain the techniques
that we used in YOTD, what we learned from using them, and some
ideas about how to take our techniques even farther. However, I will not
go into explicit detail, as most of the coding involved is relatively
simple. Crack protection is more about out-thinking the crackers than
out-coding them. A great advantage of any method of protection is novelty.
Even a new implementation will give an advantage over simply reusing code,
regardless of whether it was successful in previously delaying a crack.
Defining the Problem
From the
very beginning we recognized that nothing is uncrackable. Many different
software and hardware techniques have been used in an attempt to stop
piracy; as far as I know every one of them has been bypassed or cracked.
Our goal was to try to slow the pirates down for as long as possible.
First we
looked at the copy protection: was there any way to reduce its vulnerability
to cracking? We could call the copy protection multiple times throughout
the game, making it harder to bypass. Unfortunately, the copy protection
requires exclusive access to the CD for about 10 seconds, which is an
eternity when you are waiting for a level to load. So that was out of
the question.
Then we
looked at how a typical crack is made. Most cracks for Playstation games
replace the boot executable with an "intro" that proclaims the
prowess of the crackers and allows cheats to be activated. This intro
is concatenated with a copy of the patched executable and compressed so
that the total file size is no larger than the original file. The new
file bears little resemblance to the original boot program. This difference
gives us the opportunity to reload the boot executable sometime after
startup, causing severe corruption if it has been altered in this way.
As with the previous option, this solution suffers from the problem of
adding to the load time. It is also vulnerable to the pirates finding
some other space on the disk to hide their crack, leaving the original
boot file untouched. While this solution might have slowed the pirates
down for a few more days, it didn't seem like it was the answer we were
looking for.
We decided
that we needed a thorough way of detecting at run time that the game had
been cracked. When we could reliably determine whether the game had been
modified, we could stop the game anytime we found a discrepancy. We also
needed a method that didn't require access to the disk. It should just
check the code in memory, unlike the standard copy protection or our option
of reloading the boot program. This would allow us to place the check
anywhere in the game, making it much harder to remove.
So now we
had a definite goal, an approach that should significantly improve the
protection for YOTD.
Checksumming
Finding
out if a block of data has changed in any way is actually pretty easy.
Techniques have been used for error detection for years and are well documented.
Just search for "checksum" on the Internet. For YOTD,
we decided to use a CRC checksum: it's robust, simple, and fast.
The checksum
was calculated bitwise rather than using tables, as tables would be an
easy point for a cracker to attack. We took care to hide and protect the
checksum values as well. If these could be found and altered easily, a
cracker would simply replace the checksum with a new value that matched
the cracked data, which is far easier than removing the code. For the
same reason we didn't use functions to calculate checksums, we inlined
the code as much as possible. If the code was in a function, it would
only have to be removed once. The inline code would have to be removed
as often as it was used.
We used
a few slightly different implementations to stop simple pattern searches
from being used to find the checksum code. To what degree this survived
compiler optimization we don't know. To make our lives easier we made
macros. These could be sprinkled around the code and mixed in with other
tasks, which would make it much more difficult to spot where the checksum
was being calculated.
Unfortunately,
because checksums are designed to detect errors and not modification,
they cannot offer full protection against modification. The checksum value
for any block of data can be made to add up to any value by modifying
the same number of bits that are used for the checksum. In other words,
if the checksum is 16 bits, altering 16 bits in the data can make the
checksum match any value.
To deal
with this, multiple checksums were applied to the same data. Each checksum
used a different start offset into the data, and stepped through the data
by different amounts. This meant that overlapping and interleaved sections
of data were checksummed at different points, making it almost impossible
to alter anything and still have all the checksums add up. I could have
used different checksum algorithms for the same effect, but in this case
I didn't have time to implement more than one method.
We used
the fact that altering a small number of bits can give you any checksum
value to our advantage. By inserting the correct value into the middle
of the data, the checksum could be made to equal any predetermined value.
This meant the checksum value could be hard-coded and therefore become
part of the data being checksummed. This is bewildering to even think
about, let alone try to crack.
Since the game used multiple code overlays (or DLLs), they cross-checked each other as much as possible. This further reduced the chance that any section could be altered without being spotted. If any overlay noticed a discrepancy, it altered data in the core such that no subsequent checksum would be valid. This meant that if an alteration was detected in one overlay, then other overlays loaded later would know about it. This made it difficult for the cracker to spot what actually triggered the protection, as I'll explain later.