Blog ElCodiguero
11 may 2023 Varios

Legal Crime License Check Bypass

TL;DR: Go to “TL;DR” below for a quick way to generate a patched EXE.

What is Legal Crime?

Legal Crime was developed by Byte Enchanters, and released in 1997. It was moderately successful, being featured in many review sites, magazines, and shareware collections, but at some point the game and the company basically disappeared from the web. Today, with link rot corroding the web, and search engines “forgetting” the past, even its Wikipedia page is quite short and unremarkable.

The game is a mob simulator, in which the player controls thugs which go into businesses to sell “protection”, extracting money from them and even converting them into façades for illegal activities. The goal is to reach a certain level of income, while defending against rival gangs. There is also a sort of “technology tree”, which is made available by assigning money to bribe politicians, local police, and military. Overall it is a fun idea, and the game is still playable even with its outdated graphics.

At the time of release it even supported online multiplayer mode, and according to the reviews back then that was its best feature. Not surprisingly, the servers have not existed for quite a long time and therefore that part of the experience will likely never be available anymore.

To finish the description, I must add that back in the day I remember reading that the demise of the company was a bit strange, there was even some kind of threat message circulating online from someone who promised the company would go down and the game would never be published again, something teenage me read as “maybe they anger the wrong person”, but that current me believes was simply that they didn’t pay the sysadmin, or something like that. In any case I’ve been unable to find a page telling that part of the story, so it might be just my headcannon.

Current state

Today, the game can be found online in two versions:

v1.0 can be downloaded from the Internet Archive, while v1.2.3 can be found in the Wayback Machine or directly downloaded from Chip Magazine

There is seemingly no license code available for v1.2.3 anywhere on the Web. This mean this version will only run in Demo mode: fully functional but only the first 8 scenarios of the “campaign” can be played, and no custom scenarios can be added. The latter is not a huge loss considering it is not possible to find any custom maps in the current web, but the former means only about a quarter of the full game is available.

It is worth mentioning that apart from the game itself, the Internet Archive also has a download for a scenario editor, at https://archive.org/details/LegalCrimeMapEditor_1020.

Anyhow, the reason why I’m writing this is because I remember playing Legal Crime a lot, and it is part of a very happy time in my life which later came to an end due to circumstances that don’t matter right now.

Because of that, it is a real shame for me that the game cannot be played in full. I had to try something to fix that.

A bit of theory

I’m not going to go into details, mainly because my knowledge is not enough to do it properly. But, as you might know, deep below all programs are just instructions for the processor to run, and this means all programs, regardless of which language they are written in, eventually must become a series of instructions.

Instructions for a CPU are binary code. Buried below the layers and layers of abstractions which compose the modern Operating Systems and software, the processor simply has some wires with voltage and others without, and the combination tells it whether it has to do a sum, a comparison, or to copy some value somewhere else. I recommend Ben Eater’s excellent Building an 8-bit breadboard computer series in YouTube to learn more about how computers work.

Just a layer above binary is Assembly. In Assembly, binary instructions are written as codes which manipulate CPU registers (very fast memory inside the processor) and RAM directly. This is the lowest level which is practical for a human to learn and work with, and as mentioned before, ALL programs eventually become a series of operations which can be represented in Assembly, regardless of whether they are written in Java, C++, Rust, Python, or any other language.

So, if all programs eventually become a series of instructions, then anything a program does, including for example checking if a valid license is available, can be seen as instructions being executed, and potentially changed.

Of course, to do this, we need proper tools. These tools are called decompilers, and what they do is convert an executable (a program file) to a series of instructions in Assembly. As the C language is very close to Assembly, most of these tools can also attempt to generate C code matching the Assembly code, thus helping analyze the program flow by organizing function calls visually and allowing us to assign useful names to the variables found in the program.

I am not very involved in the community, so I do not know which tools are “popular”, or best regarded. The ones I found easily were IDA Pro and Ghidra. I ended up being successful with Ghidra, so that’s the one I’m using from now on.

Initial analysis

Before starting an analysis of the executable to try to find a way around its license check, I wrote down what I knew, from observing a correct install of v1.0:

  1. The game’s EXE file is LegalCrime.EXE, found in the installation directory.
  2. When the game is licensed, a file called LCData.bin is created in the same directory.
  3. If LCData.bin is removed the game reverts to Demo mode
  4. The file contains 7 numbers separated by a space, in a single line

It is reasonable to assume that at some point in the program flow the file is read and some calculations are made with the contents, and that these calculations result in the game validating whether it is licensed or not.

The goal, then, is to find the place in the program where the file is read, and either figure out the algorithm which checks the numbers so valid keys can be generated (creating a keygen), or just bypass the check altogether.

Preparation

First I installed Ghidra, created a new Project, opened the Code Browser tool, and finally used File -> Import File to load LegalCrime.EXE from the game directory.

This resulted in a window with a tree view at the left, a Listing panel in the middle, and a Decompiler panel at the right, with generated C code (if I select the Analyze option)

Main window of Ghidra with LegalCrime.EXE loaded

Analysis

Once the code is available, I searched for LCData.bin. If the file name is known by the program, then for sure it must be written somewhere in its code. Indeed, selecting to Search, Program Text, for “LCData.bin” in the Listing Display, yields 4 results:

Search results for "LCData.bin", showing 4 matches

Selecting each one shows the generated C code for the “function” where it is found within the Assembly code. And within the Assembly code, selecting a line shows the corresponding line in the C code.

What comes below is my analysis of what I was seeing, in more or less the level of detail I understand, and only describing the “happy path”, as if I had just opened the file once and found the solution right away. That cannot be farther from the truth: I’m not an expert on this so it took me a lot of time to understand what I was doing and get to a satisfactory result. In summary: this is not a guide, instead it is just what I hope is a moderately entertaining story.

First result: FUN_0040c3dd

The first result is listed as FUN_0040c3dd, where FUN is a short for function, and 0040c3dd is the memory location within the program where the function code begins.

Window showing the code corresponding to the first match of 'LCData.bin'

The first thing the code does is check if DAT_0047f994 is equal to zero, which seems to always be the case considering the memory location in the file (which appears if we click the variable name) just says 00000000h. But I guess it could be modified at execution time by some other routine within the program.

Next, a function is called and its value is assigned to uVar2. Notice that this function is the same as the second search result!

If uVar2 is not zero, FUN_00476884 is called, with what seems to be the file name as first parameter, and a second parameter DAT_0047f9f7. This function returns something on piVar3.

Then, if the result of calling that function is not zero, FUN_004759cc is called with piVar3 as first parameter, and a format string (%ld) as second, and the result is assigned to sVar1.

Following the reference DAT_0047f9f7 shows its value is “r“. So, this call looks a lot like a call to fopen(), and so I assume at this point is where the file is opened.

I found it useful at this point to rename those references to match what I thought were their purposes, so I right-clicked FUN_00476884 and renamed it to fopen, and then did the same for DAT_0047f9f7, renaming it to r.

If the assumption is correct regarding that first call being fopen, then since the second call takes a file pointer, a format string, and returns a number, it should be fscanf() trying to read a string from the file.

The next function call takes a file pointer and sole parameter and returns nothing, so I assume it is a call to fclose().

In summary, what this code does is try to open the file and read a number from it, and if it is successful it returns local_c, which is a location from the stack. This is most likely a value set by the call to fscanf, as the function may take an additional number of arguments to store the values it reads from the file. So, local_c is the value read, but since I don’t know which code calls this routine, this doesn’t give me any useful information at this point.

Second result: FUN_0040c442

The second result is FUN_0040c442, and as mentioned above, this is a function called from the first match. Thanks to the small work spent renaming globals, the initial analysis is quite easy.

Window showing the code corresponding to the second match of 'LCData.bin'

This snippet also opens the file and runs fscanf, this time with a different format string. It now expects to read 7 numbers from the file.

Then, FUN_0040c312 is called, with what should be the values read by fscanf. If the result of that function is not zero, this function returns 1, otherwise it returns 0.

Code of FUN_0040c312, what seems to be validating the license

FUN_0040c312 doesn’t match any of the other results for “LCData.bin”, but the code snippet seems to be doing some checks on the values. In any case, apparently the only thing that matters from that function is whether it returns 0 or something else.

My general assumption regarding these checks is that I want the function flow to run as many steps as possible, in the understanding that the license checks have many fail conditions (file doesn’t exist, file can’t be opened, file is empty, file has the wrong format, file has good format but wrong values, etc), but only one success condition, it being everything is OK with the file and the calculations match.

Given that, it seemed to me that a first attempt at cracking the license check could be to switch the return values of this second function, so if I could make it return 1, its caller (the first match, described above) would continue its “good” flow.

Third and fourth results

Checking the third and fourth results shows quite long routines, which I assume are the actual checks on the numbers read and therefore what validates them as a valid license code.

I didn’t spend time analyzing these routines, simply because I first tried to bypass the checks with what I knew at this point.

Also, in the third result (FUN_0042a395) the code is writing to the file (there is a call to fopen with w as second parameter), so most likely it is related to the code which stores a valid license code after it is entered in the game.

Bypass license check

From the analysis of the second snippet where “LCData.bin” is used, I thought I had found the place where the game tries to determine if it has a valid license.

If that assumption was correct, then the license check could be bypassed by reversing the function’s return values, or in other words, making it return 1 when it originally returned 0, and 0 when it originally returned 1.

To test if that would work, I right-clicked the JZ (Jump if Zero) at 0040c4ae and used the “Patch Instruction” option to replace it with a JNZ (Jump if Not Zero).

After making the change, I saved the new .EXE using File -> Export Program.

The new executable should returns success when the license check fails, but it still requires the file LCData.bin to exist and contain 7 numbers. So I created the file and wrote seven zeroes to it:

0 0 0 0 0 0 0

Then launched the game… and it worked! The “DEMO” sign did not appear on the title screen, it didn’t ask me for a license code, and it let me start any of the scenarios. Mission accomplished :)

There is only a minor inconvenience. Because the program was modified, its digital signature breaks, and that causes Windows to detect it as being from an “unknown developer” and ask for confirmation every time the game starts. Nothing that would affect me, really, since I intend on playing it using Lutris.

Generate valid license

A more interesting and satisfactory solution, which wouldn’t require patching the binary, would be to figure out the algorithm used by the program to validate the license numbers from the file, and using that knowledge to generate a valid key.

Finding the code for the algorithm is not hard, it seems to be the function from the fourth result listed above, or maybe the function whose return value the program is now ignoring.

Unfortunately (for me, of course), I lack the experience required to reverse-engineer the algorithm. Especially since every time I launch the game in a debugger it shows an error and exits, and this prevents me from validating my assumptions of how it uses the values from the file.

So, I decided to not go down this particular rabbit hole, and instead use the time actually playing the game :)

Closing remarks

I have been reading about assembly, debuggers, decompilers, and code modification for a long time, but I had never attempted it. This was a learning experience to me.

In particular, I found it quite interesting to discover patterns in the Assembly code and how they translate to the generated C code. For example, the function calling convention is always to PUSH each parameter in reverse order, and then CALL the location in memory of the desired function.

Other examples are things like TEST EBX,EBX, which in C translates to if (variable == 0) or if (variable == 1) depending on which kind of jump instruction follows the TEST. The value of variable is usually set by a MOV immediately before the TEST.

Not being an expert on Assembly, I noticed that it is easier to follow once I started identifying some of the “idioms” and patterns. In the same spirit, I noticed how the generated C code changed when I modified the JZ to JNZ, it reversed the condition of the if block. I found that amusing and spent some time doing other changes elsewhere in the code, to see how the C generator would react.

Anyhow, I’m happy to have learned a bit more about how things work, happy that I can now play this game I played more than 20 years ago, and even happier that this experiment sent my mind back to very good memories of my teen years.

If you have made it here, I thank you for reading. I hope what I wrote here is useful and that by playing this old forgotten game you can remember some good old times and, why not, maybe even create new ones.

Cheers!

TL;DR

All of the above is nice and good (I hope!) but if you just want to create a working game there is no need to install Ghidra or deal with Assembly. At the end of the day the original and patched files differ only by a single instruction, so getting a working binary is relatively easy:

  1. Ensure the MD5 sum of your copy of LegalCrime.EXE is 8045f00ce394a7315cc0e16b72e08dc6. Do not continue if it is not the same. The file should match if the game is version 1.2.3 downloaded from Chip Magazine

  2. Create LCData.bin in the game directory, with contents “0 0 0 0 0 0 0” (seven zeroes separated by a space)

  3. Backup LegalCrime.EXE just to be safe if the patch fails.

  4. Modify the .EXE

    1. In Linux or any system with dd available:

      printf '\x75' | dd of=LegalCrime.EXE bs=1 seek=43694 count=1 conv=notrunc
    2. In Windows, create the following script in PowerShell:

      $bytes = [System.IO.File]::ReadAllBytes("LegalCrime.EXE");
      $bytes[43694] = 0x75;
      [System.IO.File]::WriteAllBytes("LegalCrime.EXE", $bytes);

The Linux solution is a modification of a response from StackOverflow. It uses dd to write a single byte in the right location within the file, that byte corresponds to the instruction modified in the assembly code.

The Windows snippet is also from StackOverflow, and is the same idea, only more explicit: we instruct the script to modify the exact byte we want changed and write the result.

The modification can be validated as correct by checking the md5 sum of each file:

8045f00ce394a7315cc0e16b72e08dc6  LegalCrime.EXE (original)
0626ef6ed085be866ce820738ce9f35e  LegalCrime.EXE (patched)

Activa Javascript para para cargar los comentarios, basados en DISQUS

El Blog de ElCodiguero funciona sobre Pelican

Inicio | Blog | Acerca de