Anti-Hacker Standard Practices
The task of protecting your software from hackers is not easy. Put simply, complete and total protection - is not possible. If the hacker has access to your complete program, they can eventually break any and all protections you put in their way. This is not opinion - it is a fact. This is simply because they control the hardware at the most "intimate" level on which your program is running. As you know, a program is simply a set of instructions. Since the hacker controls the hardware on which those instructions are executed, eventually they will find the instructions in your program where your protections lie and simply change them.
A simple analogy illustrates this point: You have a lock on the front-door to your home don't you? When you leave the house, do you lock it? Of course you do. Why? You lock it because you don't want people to steal your stuff. If the door was unlocked, they would walk in and rob you. But, ask yourself, if they really wanted to get in, could they? If they had a crowbar and a chain-saw of course they could, sometimes they could simply kick in the door. If you had a great deal of valuables, you might get an alarm-system or a stronger door, but again, if they really wanted to rob you - they could. Software that resists hackers is just like a home with a locked door. You must make a reasonable attempt to keep your software safe since that will "keep the honest people honest", but at the same time, do not delude yourself into a false sense of security.
Of course - all is not lost. We recommend that, in light of this knowledge and accepting the facts as they are, instead of playing "the game" of protection with the intent to "win" (to completely stop the hackers) - we play for an impasse. Since we know that a "win" is impossible, we play the "game" for the next best thing - a tie. What we mean by a "tie" is that you should engineer your protections in such a way that:
It is as difficult, tedious and time consuming as possible for a cracker to crack.
Is difficult to crack successfully or without breaking some key part of the program.
If cracked, can be quickly and easily changed so the crack no longer works with the current version of the software.
What can you, as a developer, do to make your implementation of you software licensing solution as secure as possible?
What can you, as a developer, do on an ongoing basis, in order to make your software as secure as possible?
Anti-hacker standard practices are just that - standard. They should not be limited solely to your implementation of the licensing System and the code around it. So, in fact, the two concerns above are one and the same. Below are several simple "tricks" you should consider, that when used together, will force crackers trying to defeat your system to invest incrementally more time in doing so. When used in conjunction with the licensing System, their job will be unpleasant to say the least. Please be sure you understand that the purpose of playing the "game" is an impasse.
Here are several standard things you should do in any software you wish to protect to slow down crackers:
1 - Never give any registration or protection testing function a self describing name. Never use a function name like "IsActivated". Crackers will quickly locate these names (in most high level languages) and simply remove or rewrite them.
2 - Avoid unnecessary error messages. If your program gives an error message after an unsuccessful activation attempt, the crackers will be able to quickly locate the error message and know that at some point in the call stack immediately before it - a protection or activation-test function call failed.
3 - Wait using a timer in a separate thread to provide error messages. When an error message is unavoidable, you may wish to set a timer in a separate thread that will fire several seconds or minutes later to warn the user and take action. This makes tracing back to the protection call that caused the timer to start up more difficult and will slow the cracker down.
4 - Encrypt any and all protection strings. When an error message in unavoidable, be sure to dynamically generate the message string itself. The best way to do this is to store it in encrypted form in the binary and decrypt only immediately before you need it. This makes searching for the string very difficult. Better yet - encrypt all your programs strings.
5 - Use random tests for activation. Test that the program is correctly activated at random time intervals; hours, days or even weeks after it was first installed and run. Possibly test only after certain key functions (unrelated to activation) are used. This will often be missed by crackers and again will slow them down or make their crack worthless.
6 - Don't start the activation test for several seconds (or even minutes) after the activation. you may wish to store any Activation Codes input by the user temporarily in memory and after a brief pause try to activate them. You may even wish to require the user to restart the program before you input the value. This makes it difficult to trace and will slow the cracker down.
7 - Use CRC checksums in your application and dlls. Have your EXEs check themselves, check other DLLs, or even have them check each other. Consider computing a checksum on just a small section of the file itself instead of the whole thing - this makes the crackers job more difficult.
8 - Use Anti-Debugging tricks. Be sure you have read the Anti-Debugging section for more information on how to slow down crackers by having your software resist debuggers.
9 - Use Anti-Disassembling tricks. Be sure you have read the Anti-Disassembling section for more information on how to slow down crackers by having your software resist disassembling.
10 - Use more activation checking routines than the minimum necessary. You should test in several places, and in places where the crackers might not expect it. Check at random time intervals too (see above). One routine requires a time investment from the cracker to remove, then more routines will require even more time to break.
11 - Use Honey Pots. A "Honey Pot" is a fake or decoy routine (or set of routines) you code into your program that seem to check activation, but in fact - does nothing. Have it write and read registry keys, manipulate files, jump all over in memory computing useless values and doing meaningless comparisons. Use your imagination here. Name honey pots to draw the crackers attention (like "CheckReg"). The point is to waste their time with a ruse.
12 - Wrap Activation testing routines in ridiculously long code. Consider using several long procedures which do a huge amount of unnecessary (but seemingly meaningful) processing. At some point during this useless processing, legitimately check the activation. Consider even checking it two or three times in different ways. This make the crackers job of tracing through this giant jumble of assembly looking for the "real" code a more frustrating experience. This will slow them down. Your testing routines can safely take 5 to 20 seconds to execute on a reasonably fast machine.
13 - If you use a demo version of your software with limited features, make sure you don't have "grayed out" buttons or menu options. A cracker can simply change the enabled property of these controls which will effectively make your features visible again. It is always preferable to dynamically create these controls only when they are available.
If you use a demo version of your software with limited features, consider not distributing the actual binary code to execute those functions. The simplest way to do this is to include the "paid version only code" inside a DLL which is called from your program when it is legitimately purchased. Then when a customer purchases your software, give them an Activation Code to release your software protections as well as the required DLL containing the code for the missing features.
14 - If your program has been cracked, release a new version. This is probably the most important principle which makes all the above tricks worth while. Once you have wasted the crackers time as much as possible, slowing them down with all these tricks (and others you may think up yourself), you must immediately make all their hard work go to waste - release a new version fast. Change a few things around, re-organize the code, update the version number, test - and re-release. The cracker will have to start all over again. Reaction time is important too. Expect to move quickly if a crack is released. Try and release a new version as rapidly as possible to maximize the frustration effect.
cyclic redundancy check
A CRC (cyclic redundancy check) is a numeric value computed from some source (file, string, digest, etc). Unlike encryption, a CRC value is always the same size regardless of the source size. A CRC is also commonly referred to as a hash or a checksum.
There are numerous sources on the internet for information on what a CRC is, so the algorithms wont be discussed in depth here. Regardless - it may be preferable from a security standpoint for you to develop your own CRC algorithm variation based on a well-known cryptographically secure one.
A CRC can be used to help slow down hackers by computing a CRC checksum of some file (on Windows your EXE or a DLL) and comparing the computed value with a stored value from a known and trusted source. If any of the instructions have been changed, the CRC checksum will not match.
This can be used by your software team in a couple ways to slow down the crackers:
Trojan Server Attack
A cracker may try to replace the DLL (in case of COM with a Trojan COM server with the exact same interface but whose functions always returns true values). You can use a CRC value to attempt to defeat this attack:
The basic premise is:
On Windows Operating Systems, read from the registry the location of the DLL based on the known GUID of the COM server you want to check.
If that file exists, calculate a CRC checksum on it. (Any CRC or hashing algorithm can be used - it doesn't matter).
Compare the checksum calculated against your known checksum which you have previously computed on a trusted copy of the COM server. Take action depending on the match.
Perform a CRC check on the dll every time your program starts but before you actually use the system. Check the return value of the CRC check on the dll against a value you store inside your program. If they are not the same - the dll has been swapped, damaged or altered. To make this even more secure have the value inside the program you check against be stored in three different places, and assemble it just before you use it. A C++ example follows where, at development time, you have already run your CRC function on a trusted copy of the dll file and you know it should return a value of 3070908778 :
char buffer1[25] = {'\0'};
char buffer2[25] = {'\0'};
const char * val1 = "778"; // declared in a different module somewhere
const int val2 = 3070; // declared in a different module somewhere
static int val3 = 908; // maybe read from the registry where your installer wrote it.
if (CRCCheck("mydll.dll") != atol(strcat(strcat( itoa(val2, buffer1, 10), itoa(val3, buffer2, 10)), val1)))
{
// DefeatAttempt! - shut down.
}
You can even implement or use you own version of itoa, atol and strcat
CRCChecks can be quite simple to implement. We suggest that you use your own CRC to ensure that it is original.
Binary Instruction Replacement Attack
A cracker may simply replace the protection calls in your program to do nothing - ignoring the COM server altogether. In fact - this is a very common attack used by most crackers. You can use a CRC value to attempt to defeat this attack:
All 32-bit executables have a CRC checksum field in their programs PE header. You can force your compiler to generate this checksum. Then, using the Win32 API MapFileAndCheckSumA() you can get both the computed checksum and the stored one. Then simply compare them. This of course only works if the cracker doesn't change the stored one, but can sometimes be effective. There are many more sophisticated approaches to using CRCs in your program to detect the integrity of both itself and external modules (dlls) but they are beyond the scope of this help file.
You may even wish to consider computing the checksum on just a small section of the file itself instead of the whole thing - this makes the crackers job more difficult. Don't just have EXEs and DLLs check themselves, have them check each other.
Anti-Debugging
Debuggers are tools just like your IDE that allow crackers to trace a programs execution as they are running - instruction by instruction.
There are numerous anti-debugging tricks that are documented in books and on-line, but all approach the problem from two specific angles:
They attempt to detect any debugger that is currently attached to the process or running in the background.
They attempt to detect specific debuggers (like SoftICE and others).
Protected software should use some sophisticated techniques to protect you from debuggers in both these ways, However, this is only effective when the Binary/DLL methods are actually called to perform some action. To be even more resistant you should consider implementing anti-debugging tricks of your own.
There are simply too many well-documented ways to detect specific debuggers (like SoftICE) available to discuss them here. Many of them are very complex to implement or are ineffective. Instead, we suggest you make at least a simple attempt to detect debuggers. The simplest way to do this is with the Win32 API function call "IsDebuggerPresent". Although this is relatively simple to replace by most crackers, and only works on Windows NT, 2000 and XP, placing it in various unexpected locations in your program can sometimes be quite effective at slowing them down.
Anti-Disassembling
Disassemblers are tools crackers use to translate application code back into assembler or even a higher level language. There are several disassemblers on the market of various quality. Some even write comments on the translated code, which makes it easier for the cracker to understand.
Since the purpose of disassembling is to understand the programs code, a good way to counter disassemblers is to make it more incomprehensible internally. The simplest way to defend against disassemblers is to insert numerous small bits of code in between the legitimate program code instructions that do nothing meaningful, but appear to, and are complex to understand.
A simple way to do this is to define a number of anti-disassembling macros and inline functions. Use your imagination to write these macros and functions. Do lots of pointer arithmetic and mathematical transformations. Conditionally and unconditionally jump all around in memory. Make sure your anti-disassembling routines are macros or inline functions so the anti-disassembling code gets pre-processed and duplicated everywhere it is placed (instead of a simple function call which can be found and replaced in a single location by a cracker).
Then, sprinkle these macros and functions liberally around areas of your programs you wish to protect from disassembling. Increase the density of the routines around sensitive areas. Naturally this will increase the footprint of your binary and can potentially impact performance so use this trick very wisely. You may wish to reserve its use for areas only around your protections and licensing code.
The more you can make the crackers job tedious, the more time he will have to invest in cracking your software - which is the ultimate goal - to slow them down as much as possible.
Reading
http://www.amazon.com/gp/search?index=books&linkCode=qs&keywords=1597494224
http://www.amazon.com/gp/search?index=books&linkCode=qs&keywords=0321294319
http://www.amazon.com/gp/search?index=books&linkCode=qs&keywords=0072262877
http://blogs.technet.com/b/markrussinovich/archive/2005/10/31/sony-rootkits-and-digital-rights-management-gone-too-far.aspx
http://www.amazon.com/IDA-Pro-Book-Unofficial-Disassembler/dp/1593271786/ref=pd_sim_b_7
Monday, July 26, 2010
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment