I don't know whether the advice I'll give you is the "leading edge way", but I will describe some steps that you can perform in order to protect your software.
Is it possible to protect it such that no one will be able to crack it?
No, and it's important to know that and keep in mind that what you really want to do is to keep it as hard to crack as possible and dishearten potential violator from doing that. So, you want to make it so complex and obfuscated that it would require a lot of work to change anything both statically and during the runtime.
Anti-debugging techniques
Software breakpoints detection
As you probably know, to trigger a software breakpoint, debuggers use int 3
instruction, i.e. when you put a software breakpoint at some address, debugger will overwrite the byte at that address to 0xCC
(int 3
opcode) and when this instruction triggers the interrupt, a debugger will restore the byte that was previously there. That means that every software breakpoint will change some byte in the code section, so to detect it, you may create a function searching for 0xCC
.
Control sum checking
It's a generalization of the previous technique. Instead of detecting only 0xCC
, you will detect any change made in code, including fragments replaced by NOP
s. You want to apply it at the end of protection process so that you don't need to change required control sum value at each change made in the code. I would advise you to put functions checking checksums at many different places in your code and if possible make each of them slightly different (so that it's not so easy to find them all at once).
Hardware breakpoint detection
Since hardware breakpoints don't require any changes in a process image, they cannot be detected using the above mentioned methods. They are implemented using DR
registers and it's possible to create 4
of them as a maximum at given moment. To defend against them, you can just reset their values from time to time. More info about debug registers.
Time / cycles checking
Since you have the source code, you can import other libraries even during runtime. On Linux you can do it via dlopen
(to load library) and dlsym
to get specific procedure from it.. Of course you should encrypt every string so that it's not straightforward to find out which functions you are using for measuring the time elapsed.
Other tricks
Inlining every function
That will stop debuggers from recognising library functions, thus will be very annoying for everyone who wants to analyse your code. Of course, using C++
templates will also help. ;)
Inserting junk code
You can put (and inline of course) many garbage functions that won't change your program behaviour, but it won't be obvious for person analysing disassembly. You don't have to write them yourself - you may use library functions as well.
Being nasty
Of course, if someone is determined enough, he will eventually find all your control sum / breakpoint checking functions and NOP
them out. But what if you put there some code that will have an impact on the rest of application? Consider:
bool isBeingDebugged
{
if (controlSum(address) & rand() == requiredValue)
return true;
return false;
}
Above function not only checks for code integrity, but also modifies the global state of a program. For instance, if you use later on some string decoding function relying on rand()
results, the application will likely behave differently if that function is simply replaced by NOP
s.
Additional information