Public key cryptography is frequently used by copy protection schemes in commercial software. As a user, it is common to be asked for a license file after installing new (paid) software. This file often contains information about your purchase, such as the product(s) you are authorized to run.
To prevent users from tampering with these files, a signature of the relevant data is often included so the software can verify the license file:
- has not been tampered with; and
- was truly created by the software vendor.
While this is certainly better than using pattern-based serial numbers, it is far from perfect.1 By itself, this strategy is vulnerable to a trivial attack known as public key replacement.
Public key replacement
Public key replacement (PKR) occurs when an attacker replaces the public key embedded in the software with their own. Simply put, this means that the attacker can now pretend to be the vendor and sign their own license files.
An attacker will first reverse engineer the program to determine valid license parameters. Next, they will generate a key pair matching the type found in the software. They will then construct or generate an ideal license file, signed with their private key. Finally, they will replace the public key found in the binary with their own. Now, when the software runs, it will gladly accept the illegitimate license.
What makes this such an attractive attack for pirates is that replacing the key requires little effort. It is a simple find and replace operation. If there are no other protections in place, once the public key has been swapped, additional patching is usually not required.
In other words, disassembling and patching the program after each release isn’t necessary. A one-time reverse engineering effort paired with a find-and-replace Python script could turn into a reliable crack for years to come.
Key reuse and recycling
In many copy protection implementations, the key used to verify license signatures is used for that purpose only. This means replacing it has no side effects! Instead, try using the same signing key to also decrypt critical application data, server responses, etc. where possible.
By doing this, replacing the key will cripple the rest of the app.2 If the attacker wants to bypass this, they will need to decrypt all the relevant data with the original key, then re-encrypt it to with their private key. If the data comes from the network rather than the disk, this additional hurdle will be even harder to bypass without a special proxy.
Key integrity checks
Another strategy to make PKR more difficult is to check the integrity of the key at runtime. An easy way to do this is to calculate the key’s checksum.
Hold on a moment, though. Don’t even think about including the checksum of the key in the binary. That is just as easy to replace as the key itself!3 Instead, compute the checksum at runtime and manually check individual bytes. This will require patching to bypass, or a key whose checksum collides with the real key at the points in question. Checking a sufficient amount of bytes can reduce the odds of the latter to almost zero.
It’s important to remember that it is impossible to prevent your app from being cracked. However, I think increasing the effort needed (especially aiming to prevent automated patching) is always worthwhile. I like these strategies because they aren’t very difficult to implement as a software vendor, but make cracking your app a bit more annoying. :)
Don’t bother with a revealing message about some error that will make tracking down this logic easier. Just let the software crash. After all, it’s not your customers who will encounter these issues. ↩
You don’t want your copy protection to be defeated by a find and replace operation. However you choose to verify the key integrity, try to make sure the relevant code would need to be removed by hand. ↩