Caveat Emptor "let the buyer beware": For this entire session please understand one thing at the very outset; unless you plan on dedicating a significant portion of your life to writing the ultimate protection scheme and not working on your product - understand that it is simply a matter of time before someone finds its weakness, you can then look forward to many (frustrating) years of trying to shut open doors.....I'm definitely not saying that it is pointless to protect your work, but don't overestimate its importance, many of the most widespread and profitable programs in use today have lousy protections (think mIRC/WinZip) but still generate more than enough revenue for their authors. The last thing in the universe I'd like to see is my HD further cluttered with authors protection monstrosities.
From my site you can clearly see that virtually every serious cracker/reverse engineer will use a debugger or disassembler of sorts to break your application, of those tools out there I'll wager a substantial amount that SoftICE is the ring 0 "debugger of choice" although due to lack of quality control and subsequent abandonment of SoftICE by Compuware, many have switched to using the capable ring 3 alternative OllyDbg. Modern day programmers have still not really caught up with "crackers", maybe they just consider it a wasted cause?, or perhaps their desire to roll out their latest software overrides any desire to protect with any degree of competence.
Please understand that any claim of an 'unbreakable protection' just isn't credible, similarly any claim of 'new or unique patented technology' should be avoided like the proverbial plague, this is usually marketing speak for 'untested', (I could probably write a whole page dedicated to terms used by copy protection vendors with their real meanings but I'll refrain for now), a few years back there were a glut of sites on the web offering the "ultimate in unbreakable protections", to my knowledge not one remains that hasn't been made to eat its words. Using an off-the-shelf protection e.g. Armadillo, ASProtect, ExeCryptor (these are some of the best) may help to raise the individual knowledge bar required to break your application, yet frequently this is a false economy, at any given time there might be thousands of people attacking generic instances of these protection schemes and the odds are very good that one eventually finds a way through. The worlds hard-core of crackers are determined, motivated and very resourceful, they might also be situated in places where the the 'money to time' ratio is not necessarily a factor, to think that you can beat them all is absolute folly.
Most of the following tricks require implementation in Assembly language, evidently that isn't a problem if you are familiar with in-line ASM coding in your compiler environment (sadly many HLL programmers are not). Most of these examples are designed to thwart SoftICE users (virtually all of the existing documents on the web describe tricks which would have been useful in the days of DOS Debug/Turbo Debug but are happily traced by SoftICE).
Bear in mind that the use of these techniques will only add time to a crackers progress if he/she doesn't actually realise the debugger has been detected (use at least 2 techniques to make sure you aren't penalising legitimate users and don't show friendly message boxes like VBox's "Debugger Detected, please remove me screen"). Hidden detection can work very well if used in shareware key generation schemes, set a discrete flag and then send the would-be cracker to a routine that requires hours of analysis time for no result, eventually time is money for the best reverse engineers.
1. - Using the API function CreateFileA to check for the presence of the SoftICE vxd/sys device or its display driver (\\.\SICE, \\.\SIWDEBUG, \\.\SIWVID, \\.\NTICE in Windows NT). This method is now very well known, although you could try using _lread() instead (ASProtect). Some protectors also check for the presence of well known debugging and monitoring utilities for example (\\.\SuperBPMDev0, \\.\TRW, \\.\FILEVXD, \\.\FILEMON, \\.\REGVXD, \\.\REGMON).
Bypass by bpx CreateFileA (change API result) or HEX edit the strings from the detection or patch your nmtrans.dll / Winice.exe against them. (Seen in Advanced Disk Catalog v1.16, MeltICE, Hardlock/HASP envelope).
Another variation of this trick is to check if the NTice service is running using OpenServiceA.
In SoftICE v4.3.2 this detection method no longer works as internally the SymbolicLink name of NTICE has a 4 digit number appended (this is based upon the serial number used to install SoftICE), a discussion of this is available here courtesy of SoftICE guru Kayaker.
Yet another variation of this trick is to check for specific files installed by SoftICE (DriverStudio), for operating system independence one paper recommends checking the windows system directory for dstudio.cpl since it is installed under 9x & XP systems.
2. - INT 41h Debugger Notification.
CALL Kernel32_1 <-- ORD_1 or VxdCall.
This detection pushes 002A002A as a parameter, the high 2A
means we are calling VWIN_32 vxd, the low 2A the INT 41 dispatch
service. Bypass with
bpint 30 if ax==0xF386 and clear
AX, described in Matt Pietrek's legendary book.
3. - Issue commands to SoftICE. This was one detection I uncovered whilst running an installation program protected by a Hardlock dongle (HASP wrappers also use it). I had inadvertently enabled INT 3's with SoftICE's I3HERE toggle.
00004647 0000017991 "FG" <-- Magic Val. 1.
00004A4D 0000019021 "JM" <-- Magic Val. 2.
28F1:0092 MOV SI,4647
28F1:0095 MOV DI,4A4D
28F1:0098 PUSH CS
28F1:0099 POP DS
28F1:009A MOV AX,0911h <-- Function 0911.
28F1:009D MOV DX,000Eh <-- Points at null-terminated command (in this case HBOOT).
28F1:00A0 INT 3 <-- Call Interrupt.
Evidently there are several easy ways to beat this, changing SoftICE's magic values is one option (as described by The_Owl), another would be editing DI/SI or modifying the string at DX, you could also just NOP the INT 3 altogether, this detection routine works with varying levels of success. Other subfunctions exist (0910h, 0912h, 0913h & 0914h) which can be used to manipulate SoftICE breakpoints. In Windows 2000 calling this function with EDX pointing to a garbage string will reboot the computer in the presence of NTICE.
0910h - Display string in SoftICE window.
0911h - Execute commands (DS:DX).
0912h - Get breakpoint information.
0913h - Set SoftICE breakpoints.
0914h - Remove SoftICE breakpoints.
As a trivial aside, the FGJM interface likely represents the initials of the 2 founding members of NuMega (Frank Grossman & Jim Moskun).
Another set of magic values are also known ('BCHK'), this is the (un)documented BoundsChecker interface, if you place BCHK into EBP and set EAX=4 calling INT 3 will return AL=0 in the presence of Winice (works in Windows).
MOV EBP, 04243484Bh <-- 'BCHK'.
MOV AX, 4h
INT 3 <-- Trap debugger.
CMP AL, 4
4. - ICECream detection (Windows 95).
Get the Interrupt Descriptor Table (IDT) with the SIDT command.
Get the address of Interrupt gate 1.
Move 16 bytes back.
Check if byte is 1Eh - if so SoftICE is running.
SIDT FWORD PTR opIDT <-- Store IDT.
MOV EAX, DWORD PTR [opIDT+2] <-- EAX=IDT.
ADD EAX, 8h <-- EAX has INT 1 vector.
MOV EBX, [EAX] <-- EBX=INT 1 vector.
ADD EAX, 10h <-- EAX points at INT 3 vector.
MOV EAX, [EAX] <-- Get EAX=INT 3 vector.
AND EAX, 0FFFFh
AND EBX, 0FFFFh <-- Remove selectors.
SUB EAX, EBX <-- Find displacement.
CMP EAX, 01Eh
Some programs fetch the offset between INT 1 & INT 3 during small unpacking routines, thus, if SoftICE is running, or in your version of Windows the displacement is different to 10h, it crashes.
5. - Detect SoftICE VxD or SoftICE GFX VxD (obviously ineffective under NT).
XOR DI,DI <-- Clear DI.
MOV AX, 1684h
MOV BX, 0202h <-- VxD ID for SoftICE.
MOV AX, ES <-- VxD Entry Point.
ADD DI, AX
CMP DI, 0
The GFX id code is identical to that shown above except AX=1684h & BX=7A5Fh.
6. - Detect WinICE handler using INT 68h (V86) since SoftICE hooks it for its own use.
MOV ECX, 0 or XOR ECX, ECX
MOV CX, CS
XOR CL, CL
MOV AH, 43h
CMP AX, 0F386h <-- Will be set by all system debuggers.
If a debugger is not present AX will be 4300h.
Also one can check the interrupt descriptor table and see if there is a handler installed for INT 68h.
XOR AX, AX
MOV ES, AX
MOV BX, WORD PTR ES:[68h*4]
MOV ES, WORD PTR ES:[68h*4+2]
MOV EAX, 0F43FC80h
CMP EAX, DWORD PTR ES:[EBX]
7. - Detect and crash SoftICE with an illegal form of the instruction CMPXCHG8B (LOCK prefix) - opcode: F0 0F C7 C8 (LOCK CMPXCHG8B EAX).
8. - Searching for names installed by SoftICE in low-memory, "WINICE.BR", "SOFTICE1" + others (described in Stone's code below).
9. - Using the control or debug registers - I remember reading somewhere (perhaps in a fairly old Anti-debugging FAQ) that SoftICE doesn't decode instructions pertaining to the control register CR4, you could also play with the debug registers too (clearing any breakpoints is a favoured trick of protection authors). Note : In order to implement any trick using these registers you are going to have to either write a driver (ring 0) or install and trigger your own thread SEH handler, this is not a trick to be used lightly, however it is very effective.
10. - Dongle protection (used by dongle SSI Win32 Aegis).
EB01: JMP $+1
E8: DB E8h
EB01: JMP $+1
E8xxxxxxxx: CALL bad
TRW (not affected):
11. Calling the Windows function IsDebuggerPresent() (exported from kernel32.dll), returns non-zero in the presence of a debugger, implemented only under NT so not a great trick on its own. A few reversers have already patched their kernel32's against this one and it can be easily patched during runtime using ring 3 APIs.
12. Querying the registry for keys installed by SoftICE, [HKEY_LOCAL_MACHINE/SOFTWARE/NuMega] and subkeys. Far too easy to beat, mine are removed already and the key was NU-MEGA in older versions. The plug-in IceExt can also be checked for by searching for "HKEY_LOCAL_MACHINE\ SYSTEM\ CurrentControlSet\ Services\ IceExt". Other key possibilities (all from HKEY_LOCAL_MACHINE) include:
Common for 9x and XP:
13. Using a timer to check the execution speed of a routine can be an effective way of detecting debuggers, under single step analysis the routine will run much slower, how you choose to react of course is down to your own ingenuity.
14. Check if a potential cracker has set breakpoints on key API functions :-
LEA ESI, GetDlgItemTextA
CMP EAX, "xxxx" <-- Substitute for your own identifier.
JE SoftICEBPIsSet <-- Send bad cracker to some really horrid routine.
MOV ESI, [ESI+2] <-- Get dll function jmp address.
MOV ESI, [ESI] <-- Get dll function real address.
MOV EAX, ESI <-- Get first dword of dll function.
AND EAX, 0FFh <-- Use only first byte.
CMP AL, 0CCh <-- INT 3 ?.
MOV EAX, 'xxxx' <-- Your identifier.
XOR EAX, EAX <-- No BPX.
This sadly won't detect the cracker who routinely sets breakpoints such as 'bpmb GetDlgItemTextA x'. If you do implement a routine that reads your program from memory be very sure there aren't any alignment 0xCC bytes lurking around.
15. Exploit a W32Dasm bug (this probably won't keep IDA users out but will at least stop the real StringRef crackers which make up the majority).
00401000 JMP 00401005
00401005 JMP 00401000
It seems that if this code is placed in a zone that doesn't mess with code flow, it will put W32Dasm in a memory-consuming -> crashing loop. I have to confess I'm not convinced this is actually effective.
16. PROCESSINFOCLASS == ProcessDebugPort, query to see if there is an active process debug port.
NTSTATUS NTAPI Nt/ZwQuerySystemInformation (SYSTEMINFOCLASS sic, PVOID pData, DWORD dSize, PDWORD pdSize);
23h -> SYSTEMINFOCLASS == SystemDebuggerInformation and here to see if there is any kernel debugger running.
17. StopIce (anti-SoftICE protection for Delphi applications apparently). I don't want to get at the person who coded the StopIce v1.0 deprotector, but to put it mildly this tool is for people who can't use a HEX editor, included in the archive is my 32 byte example.exe (view it in a HEX editor and have StopIce deprotect and reprotect it), you get the idea ;-).
StopIce detection works via very well known methods, CreateFileA for SICE & NTICE devices. The v1.0 deprotector merely replaces these device names with profane alternatives, all that has changed in v2.0 is non-profane alternatives and a reprotect option. The interesting feature of the protector is its apparent payload (if it actually worked), the program calls GetCurrentProcess() and then ControlService() twice, the first time with 2 missing parameters (thus messing the stack) and the 2nd time with parameters which I assume are just nonsense because of the stack being +8 after the first failed call, the intended payload seems to be a call to ExitWindowsEx. Needless to say if you use FrogsICE/Icedump or even your hex editor this detection is unlikely to be 'stopping any SoftICE' users for longer than about 15 seconds.
18. Detection from SafeDisc (thanks yAtEs for this one).
secdrv.sys, ANDs dr7 with 500, saves the value and checks it for 500.
.text:00010950 mov eax, dr7 .text:0001095C and eax, 500h .text:00010961 mov [ecx], eax
Normally without SoftICE, DR7 would have a value of 4xx, for example :
450 AND 500 = 400
412 AND 500 = 400
But SoftICE sets all DR7 values in a 7xx range so :
750 AND 500 = 500
712 AND 500 = 500
So what is SoftICE doing? its setting LE & GE (Global/Local Exact) these are bits 9 & 8 i.e. 1100000000 (0x300), a quick scan of NTICE and we find :
.text:0003BEAC mov ebx, dr7 .text:0003BEC4 or ebx, 30Ch .text:0003BECD mov dr7, ebx ... .text:0003BEDD or ebx, 303h .text:0003BEE6 mov dr7, ebx .. .text:0003BEF6 or ebx, 330h .text:0003BEFF mov dr7, ebx .. .text:0003BF0F or ebx, 3C0h .text:0003BF18 mov dr7, ebx
Patch the .sys to remove 300h.
.text:0003BEAC mov ebx, dr7 .text:0003BEC4 or ebx, 0Ch .text:0003BECD mov dr7, ebx ... .text:0003BEDD or ebx, 03h .text:0003BEE6 mov dr7, ebx .. .text:0003BEF6 or ebx, 30h .text:0003BEFF mov dr7, ebx .. .text:0003BF0F or ebx, C0h .text:0003BF18 mov dr7, ebx
I haven't seen any side effects of doing this. Some older versions have an extra OR at the beginning.
19. Detect single-stepping and turn off the monitor.
MOV DX,3C4 ; Set Port to 3C4 (Sequencer Index Register)
MOV AL,1 ; Set register to access to 1 (Clocking Mode Register)
OUT DX,AL ; Select Clocking Mode Register to be accessed at 3C5
INC DX ; Set DX to 3C5
IN AL,DX ; Get Clocking Mode Register to AL
OR AL,20 ; Set Bit 5
OUT DX,AL ; And rewrite that into the Clocking Mode Register
; Bit 5 in the Clocking Mode Register represents:
; Screen Off
; When set to 1, this bit turns off the display
; and assigns maximum memory bandwidth to the
; system. Although the display is blanked, the
; synchronization pulses are maintained. This
; bit can be used for rapid full-screen updates.
a few instructions later ...
MOV DX,3C4 ; Set Port to 3C4 (Sequencer Index Register)
MOV AL,1 ; Set register to access to 1 (Clocking Mode Register)
OUT DX,AL ; Select Clocking Mode Register to be accessed at 3C5
INC DX ; Set DX to 3C5
IN AL,DX ; Get Clocking Mode Register to AL
AND AL,DL ; Reset Bit 5 (AND xxxxxxxx, 11000101)
OUT DX,AL ; And rewrite into the Clocking Mode Register
; Since Bit 5 gets reset, the screen will be turned on
At runtime everything happens fast enough, whilst during debugging the first sequence simply needs to be skipped otherwise the screen will be turned off. This trick works only on Win9x due to priviledge levels.
20. Exit the program if its being debugged by checking execution timing.
33C9 XOR ECX,ECX
03C8 ADD ECX,EAX
2BC1 SUB EAX,ECX
3DFF0F0000 CMP EAX,0FFF
EB08 JB SHORT _FAST_ENOUGH
6A00 PUSH 0 ; ExitCode = 0
E80E060000 CALL <JMP.&KERNEL32.ExitProcess> ; ExitProcess
_FAST_ENOUGH: -> Code continues here
RDTSC is used to retrieve the time stamp counter (number of clocks since boot-up) so this is a time-related trick. When you debug, the distance between those values that are returned in EAX will be higher than those when the program runs without being debugged. So, if there is really a difference you're debugging. The only way around for a cracker is a patch.
21. Anti-tracing trick. More intelligent tracer applications would most likely implement an instruction decoder in order to follow the execution path, of course special handlers will be needed for conditional/unconditional jump instructions as well as others. Another possibility which could fool a tracer is a JMP instruction with a REPE or REPNE prefix. Here is how it works :
REPE (0xF2) <-- prefix only valid with CMPS/SCAS
JMP SHORT LOC_1 (0xEBxx) <-- JMP length is 2
When the instruction decoder reaches the REPE it correctly decodes the instruction as REPE JMP with an instruction length of 3 bytes. If a special handler routine isn't called the tracer will assume the next instruction to be executed is in the junk bytes and not the JMP to LOC_1. This trick is used by portions of the Armadillo loading code but will only be effective against single step tracers, not those using the trap flag. Armadillo uses a SEH handler to detect if there is a tracer using the trap flag.
22. Anti-IDA trick. I'm not entirely sure if this is a deliberate anti-IDA trick or not, however I recently encounted a file which had the Virtual Size's of its .data section set to an extremely large value, when IDA encountered a 100GB Virtual Size it refused to load the file due to lack of available disk space. Of course this isn't going to stop anyone simply setting the sections Virtual Size to the Raw Size.
23. Under Windows 2000 & Windows XP SoftICE places an INT 3 instruction on the first byte of the function 'UnhandledExceptionFilter' apparently for its own internal use, a protector could well check for its presence.
MOV EDI, UnhandledExceptionFilter
MOV ECX, 6 ; Check the first 6 bytes
MOV AL, 0CCh ; INT 3 opcode
TEST ECX, ECX
24. Buffer overflows against OllyDbg.
Format String Bug in OllyDbg 1.10 : http://cert.uni-stuttgart.de/archive/bugtraq/2004/07/msg00211.html
OllyDbg long process Module debug Vulnerability : http://cert.uni-stuttgart.de/archive/bugtraq/2005/03/msg00337.html
25. Detect if running under VMWare (not an anti-debugging measure per se but useful nevertheless since a lot of attackers like VMWare as an environment), if VMWare is running EBX will be non-zero.
MOV EAX, 564D5868h ; 'VMXh' MOV EBX, 0 MOV ECX, 0Ah MOV EDX, 5658h ; 'VX' IN EAX, DX
26. Detect if running under VMWare or any other Virtual Environment by querying the location of the IDT (since SIDT?can be executed at ring 3 and does not generate any exception this is a reliable detection method). Unlike SoftICE?which reloads the original IDT VMWare simply relocates the IDT to its own internal location. This method is better known as the "Red Pill?Method" and is best described here.
SIDT FWORD PTR opIDT <-- Store IDT.
CMP DWORD PTR [opIDT+2], 0F0000000h <-- Check IDT location (possibly Virtual PC check).
CMP DWORD PTR [opIDT+2], 0FF000000h <-- Check IDT VMWare.
CMP DWORD PTR [opIDT+2], 0D0000000h <-- Check IDT Virtual PC.
CrackZ's Collection of Anti-Debugging Resources - A lot of historical references in here which might not be useful as most authors are not going to want to implement DOS tricks to defeat real mode debuggers. Windows authors should read Stone, duelist, the_owl and r!sc's works as these are easily the best tricks you can use (174k - 178,467 bytes).
Daemons Page - Lots of anti-debugging methods, sample code (web archive) (2.09Mb).
FrogsICE - by +FrogsPrint (assisted by +spath), an anti-SoftICE detection tool for reversers. Please note his page is MSIE hostile and will crash your browser.
SoftICE Cover v1.0 - Tool for hiding SoftICE against some of the common tricks above (9.61Mb's).
Software Protection Analysts inc.
I'm sure many authors who stumble across reverse engineering sites regard them as little more than 'how to crack' repositories, or terrorist training camps if you want to work for the BSA or DHS. Yes, software piracy does cost most authors a proportion of their potential revenue and I can sympathise with this point of view but try not to let piracy concerns be the be-all and end-all of your life. In the real world of computer software there is probably very little you as an author can do to prevent the really determined pirate from breaking your protection, or not without spending significant sums of time and money, even the ultimate protection can and will be potentially compromised by a legitimate user doing some research.
Preventing the wide scale distribution of cracks for your program can actually be a worthwhile exercise, use a search string such as "warez + crack + your application name" in AltaVista and several of the other engines and you'll probably be able to find out if and where a crack exists for your software. Remember that most 'crack' sites are operated by bored teenagers and hosted on anonymous free space providers, most of these have Piracy@ e-mail addresses that will remove the offending site within a day or 2, just devise your own 'piracy e-mail letter' and send them off at the click of a button.
Below you'll find a collection of protection tips, these won't make your program bullet proof, in fact at best they'll guarantee you not making the worst mistakes and maybe waste a few hours of a crackers time if nothing else, if you want more details regarding implementation drop me an e-mail.
I've always been convinced that knowledge is power, have a read of this piece from Kudzu's World, entitled Piracy and Unconvential wisdom if you are a developer, it might give you some hope.
1. Virtually all serious crackers will use SoftICE to break your protection, use the undocumented INT 3 interface between SoftICE and BoundsChecker to detect its presence (or other routines), you can then decide upon a suitable form of action should you so wish. However, remember that most anti-debugging tricks are well documented and may be circumvented already at the OS level by existing tools, ultimately with any anti-debugging trick a way around will be found.
2. When writing your protections functions try to avoid using very obvious names like these that I've seen - (Auth_Check(), FindDK47(), IsValidSerialNumber() ). Instead frustrate crackers by using less intuitive or even misleading names, never place your entire protection inside a single dll or function.
3. Avoid issuing helpful message boxes period, and avoid "Thanks for registering" ones as well, most legitimate users will type their serial numbers correctly first time. Encrypt trivially any message strings and others that your program uses, since suspicious looking data will attract attention.
4. If you must track registered status using the registry use several keys and don't use names like "MySoftwareKey', instead use some sort of encryption to decode them. If you decide to use a file based protection, avoid using *.ini and *.reg, use something like *.sys and *.vxd and make sure they are of credible length and name. Please don't fall for the old tip of "give it a dll extension and shove it in the /system / windows directory", it will be very easily isolated.
5. Avoid commercial *wrapper* protection schemes e.g. PreviewSoftware's TimeLock, VBox, ZipLock, Release Software Corporation's SalesAgent, Ken Nesbitt's ShareLock, CrypKey, FLEXlm like the proverbial plague. All of these protections have been generically cracked, you might as well give your software away.
6. Forget about protecting using runtime-limits or 30 day trials,
these are too easy to crack,
cmp eax, 1E - jg times_up.
If you must use this means have the program disable itself completely
perhaps by deleting critical files. Don't waste your time checking
file time stamps, you might at best add a few minutes to a crackers
progress and at worse generate a lot of false alarms.
7. Many crackers struggle with applications written in Visual Basic and Delphi, primarily because the run-time dll's are a mass of spaghetti code which makes isolating your protection more difficult, in some cases it can make patching virtually impossible. If you must use these languages, using hyphenated serial numbers like xxx-xxx-xxx-xxx-xxx make for painful reversing, especially via the various Trim$ functions VB provides. If you use VB always compile your final product to p-code.
8. Bore potential crackers to death with routines using lots of maths and fake checks. Force them to work back through your mass of maths (all to no avail). Mix your flags, in function 1 use return 1 for good and then in function 2 use return 0 as good, this will prevent crackers from heuristically reversing jumps.
9. Save disabling your target?, don't just grey the menu item, take out the code completely and don't provide helpful documents detailing the precise specification of the file format or it could be added back in (only advanced crackers will invest the time doing this).
10. Use encryption of any description, a simple XOR will add about 5 minutes to a crackers progress, commercial and secure schemes like RSA/SHA/ECC etc. can add hours to a crackers progress, it is worth studying the theory of some of these algorithms if you use them and modifying them, since many of the algorithms use constants which can easily be searched for in a disassembly listing.
11. Use CRC or integrity checks to ensure that key sections of your program have not been modified, its not a good idea to CRC the entire file for performance and vulnerability reasons, when you find an error be aware this could be due to virus tampering. It might also be worth reading a copy of your program from memory and checking for modifications there, again be subtle.
12. Pack your executable using a commercial or freeware packer (UPX, use older versions which don't support the -d switch). Yes, there are a zillion unpackers for most packers, but many wannabes struggle with unpacking without automated tools and it may waste some of their time identifying which one to use.
13. ...And finally, invest the money and buy a copy of SoftICE, then attempt to reverse engineer your own software or ask web crackers to try their hands. Make changes at ASM level if necessary and strengthen your protection. The longer it takes you to protect, the longer it will take a cracker to de-protect.
Whilst no protection will ever be completely cracker proof, treat software protecting like you treat your car (or any other prized possession), if you can implement enough deterrents most thieves will find an easier target or they will wait until a more skilled thief decides to turn his hand too it.
I'd really like to add more advice, so if you want to post me a small text file I'll certainly add it here.
The following tricks (in addition to those I devised above) are taken from the book "Crack proof your software", by Pavol Cerven, whilst Pavol might be a fairly competent assembly language programmer, the additional tips provided here are of somewhat debatable use (to say the least), my comments are in italics.
1. Don't start the registration number test for several seconds or even minutes after the registration. This will make it much more difficult for the cracker to trace the application in a debugger. Better still, require the program to restart before testing begins.
Don't waste your time doing this, it will be very easy indeed for a cracker to find your time-wasting routine and remove it, restarting after registration is similarly pointless, he'll be running a file and registry monitor and in a short time where you've hidden the required data will be revealed.
2. Use checksums in your application. If you test your EXE and DLL files, and even other files, for changes, crackers will be unable to modify them with patches. However, they will still be able to modify the code directly in memory, so it is a good idea to test for changes to the application code in memory, as well.
He'll simply update the CRC with the correct value, pointless, although he has to find it.
3. Put your registration information in a non-obvious place. If you keep the registration information in the Windows registry, it can be easily discovered. Instead, keep it in a file that nobody would suspect would contain the registration, like a DLL. By placing such a DLL into the Windows system directory, you will make the registration situation very confusing for the cracker. Even if a cracker uses a monitor to follow access to files, it may take a long time before he understands that the registration is in such a file. Better still, locate the registration in more than one place to make it likely that the protection will be removed incorrectly.
Discussed above, it'll be found in a few minutes via a trawl through the file accessing logs. Firstly we'll delete all reads not performed by your application and then we'll analyse the rest, crackers are very familiar indeed with the windows set of dll's and will probably spot yours a mile off.
4. Encrypt the registration. Encrypt your registration using a technique that is dependent on the particular computer's hardware to make it impossible to use the file to register the program on another computer. (It's a good idea to use the serial number of the hard drive.)
Its a very silly idea to use the HD serial number since (if really need be) it can be changed anyway. Key generators will soon appear regardless.
5. Don't be afraid of long registrations. The longer the registration file or number, the longer it takes to understand it. If the registration file or number is sufficiently long, it may contain important information that the program needs, and if such a program is patched, it will not run correctly.
This one belongs in the "if you can bore them sufficiently" category, don't bet your life on it, someone invariably has more time than you and will find the way through. Bore crackers by all means but don't overdo it, if it looks like crap to you it'll look that way to a cracker as well.
6. Test several current bits of data when using time-limit protection, such as file date, system.dat, and bootlog.txt. If the current date or time is the same or smaller than when the program was run previously, it will be clear that the time was adjusted. Also, you can save the date and time after each launch of the program, and then test the current date and time during a new launch.
Once again, often heard, easily isolated.