That time I cracked my first game
It was early 2004, several months before graduating, when I joined a small startup named Trymedia. At that time, I had very little knowledge about startups. To be honest, I didn’t even know what the term ‘startup’ meant. Trymedia was a small startup that aimed to be the #1 online video game distributor for PC.
I joined as a developer. However, before writing a single line of code, it was customary to spend several weeks rotating through various departments to gain an understanding of the entire process of publishing a game. This was an opportunity for me to learn not only about the process the company followed, but about how the technology was being used to protect the games.
It was during this training period that the CEO approached me one day with a special request. He told me there was this game that needed to be released by the end of that week. The thing was that we could not protect the game because it was already protected 🤯
Let me pause for a moment and provide some context.
In 2004 the majority of people were still purchasing games on CDs from the nearest store. This was the conventional method of game distribution at the time. Typically, once our company acquired the rights to distribute games online, the developers would upload the game to our servers or send us CDs with the games unprotected. However, in this particular case, all we had was the same CD that consumers could buy from their local stores. These CDs often came with copy-protection mechanisms to prevent piracy, usually to prevent the game from working without the CD or prevent the CD to be cloned.
Let’s continue.
On this particular game, before we could apply our protection to the game we had to remove the protection the game already had. Kind of ironic 😂
I guess it was a combination of the other devs being swamped with tight deadlines and the CEO seeing this as an opportunity to test the capabilities of the new hire.
Crack the game? Challenge accepted!
“Sure… how do I do that?” I asked. “You know assembly, right? Figure it out”, the CEO replied. A brief silence followed where we looked at each other. He most likely noticed my puzzled face, because then he said “Use SoftIce for debugging” and moved back to his office.
Uhm… Yes, I did have some knowledge of x86 assembly. I had rewrriten MD5 and SHA1 algorithms in pure x86 assembly for fun the year before1 (who does that, right?). But, cracking a game? I had never debugged another program without access to the source code, let alone tried to crack a program in my life. It felt like venturing into dark magic at the time.
“SoftIce… what the hell is SoftIce?” I thought2.
The first step was clear to me. Install SoftIce and learn about it.
Right after installing it, I started playing with it in order to learn how to launch the debugger, how to attach to a running application, how to set breakpoints…
Next step? “Let’s copy the game to the local disk drive and let’s play the game without the CD-ROM to see what happens” I thought.
A few minutes later the game was copied. When I first launched the game, with the CD-ROM in, it worked without any issues. However, when I removed the CD-ROM and launched the game again, the issue I was told to fix surfaced.
Right after the game started loading, a message appeared, stating something along these lines:
This game requires the original CD to continue playing.
Please insert the CD-ROM and press Enter to continue.
Lucky me, a native error window was showing the error. It was a standard Windows MessageBox.
My reasoning went as follows: I would set a breakpoint on MessageBoxA, stop the program right when it showed the message and then look at the stack-trace. With the stack trace in hand, I could set breakpoints in order to identify all the sequence of functions that led to that point. My goal was to inspect what was going on to get there.
In principle the idea seemed promising, but the debugging all instructions was a crazy/dumb/impractical approach. With hundreds of thousands of instructions being executed stepping throught each one individually was clearly unfeasible. I could try to build a mental map of the program, but SoftIce was not the right tool for that (I was unaware of IDA at the time). It took me a few hours to realize it was a dead end. “There should be another way” I thought.
Then a nice idea occurred to me. I could launch the program with the CD-ROM in, leaving the breakpoints I identified, and then try to see where the game execution diverged.
With the CD-ROM empty, I could see the application executed A, then B, then C, then D, …3 but when the game CD was in, the breakpoints stopped on A, then on B, then on C, but never D. It was clear something was going on after C.
I probably spent another hour launching and debugging the program multiple times in order to try to understand what was going on. It took me quite some time, but I finally discovered there was this function calling other functions who were calling the Windows API and checking stuff related to the CD-ROM unit.
Now the picture became clear. The game was checking whether the original CD-ROM was in, by ensuring some of the game files were there.
Let’s call the function doing the checks IsOriginalCDROM
4.
IsOriginalCDROM
was returning 1 when the original CD was in and 0 otherwise.
Function “C” contained some code that looked like this:
0x0040B820: E8 XX XX XX XX call IsOriginalCDROM
0x0040B825: 85 C0 test eax, eax
0x0040B826: 74 XX jz MissingOrPiratedCDROM
I had to test my hypothesis: “If I revert the logic and replace jz with jnz, the game should work without the CD-ROM”. I set a breakpoint at the jz instruction and with the process paused in that instruction I replaced 0x74 opcode for jz by 0x75 opcode for jnz and then continued the execution.
The game works!! I made it! I made it!! I cracked the game!! Wohoooo!!!
I was ecstatic. The game was working! 😁😁😁
Finally, it was time to patch the game on disk and hand over the patched executable to the right department so they could apply our technology and distribute it.
Mission accomplished! Or… was it?
Well, that’s how the story ends. However, the keen programmer might have noticed a minor, subtle, tiny little detail.
I had patched the executable with 0x75 instead of 0x90 (nop). What’s the difference? Well, since I reverted the logic, the funny thing was that if you had the original CD-ROM in, you wouldn’t be able to play the game 😅
I realized this a few weeks later, but nevertheless, we were distributing the game online, and nobody was going to have the original CD. In fact, nobody ever complained about it.
Back in the day I implemented MD5 and SHA1 cryptographic hashes in pure x86 assembly to see if I could outperform the standard C implementations for both algorithms. As far as I remember I succeeded, even with C speed optimizations turned on. Be aware the processors and compilers available at the time were not the same we have today. I think I used GCC as a benchmark back then. ↩︎
NuMega SoftIce was a kernel-mode debugger that could be attached to any user application or windows driver. It had visibility on the system as a whole, so you could debug even system calls or the kernel if you wanted. Nothing escaped to it. It had some amazing features. If you had the source code, you could see it along with the assembly. You could see not only the code, but the memory. You could even patch memory of the program in runtime. It was very handy because you could debug games even when they were in full screen and SoftIce would appear on top of every window. Here you have an image from http://softice1.free.fr/ ↩︎
I’m using labels, like A, B, C, D, … to make it easier to read and digest, but of course these were memory locations like 0x0040B821 and such. ↩︎
IsOriginalCDROM
, again, was a memory location like 0x00415C40 ↩︎