■■ Adding periodical checksum calculations from the main thread that
verify the verification thread. If there’s a checksum mismatch, someone
has patched the verification thread—terminate immediately.
■■ Checksums must be stored within the code, rather than in some central-
ized location. The same goes for the actual checksum verifications—
they must be inlined and not implemented in one single function. This
would make it very difficult to eliminate the checks or modify the
checksum.
■■ Store a global handle to the verification thread. With each checksum
verification ensure the thread is still running. If it’s not, terminate the
program immediately.
One thing that should be noted is that in its current implementation the ver-
ification thread is slightly dangerous. It is reliable enough for a cracking exer-
cise, but not for anything beyond that. The relatively short period and the fact
that it’s running in normal priority means that it’s possible that it will termi-
nate the process unjustly, without a debugger.
In a commercial product environment the counter constant should probably
be significantly higher and should probably be calculated in runtime based on
the counter’s update speed. In addition, the thread should be set to a higher
priority in order to make sure higher priority threads don’t prevent it from
receiving CPU time and generate false positives.
Runtime Generation of Decryption Keys
Generating decryption keys in runtime is important because it means that the
program could never be automatically unpacked. There are many ways to
obtain keys in runtime, and Defender employs two methods.
Interdependent Keys
Some of the individual functions in Defender are encrypted using interdepen-
dent keys, which are keys that are calculated in runtime from some other pro-
gram data. In Defender’s case I’ve calculated a checksum during the
reencryption process and used that checksum as the decryption key for the
next function. This means that any change (such as a patch or a breakpoint) to
the encrypted function would prevent the next function (in the runtime execu-
tion order) from properly decrypting. It would probably be worthwhile to use
a cryptographic hash algorithm for this purpose, in order to prevent attackers
from modifying the code, and simply adding a couple of bytes that would
keep the original checksum value. Such modification would not be possible
with cryptographic hash algorithms—any change in the code would result in
a new hash value.
418 Chapter 11