Anti-Debugging & Software Protection Advice

Detecting SoftICE Protecting your Software

As an author 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 "debugger of choice". 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. At any rate, with the progression of commercial wrapper schemes e.g. VBox, I think a list of the most common tricks encountered by reversers would be useful for both communities.

Most of these 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 legitamate 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 (//./SICE, //./SIWDEBUG, //./NTICE in Windows NT). This method is now very well known, although you could try using _lread() instead (ASProtect).

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).

2. - INT 41h Debugger Notification.

PUSH EDI
PUSH 0000004F
PUSH 002A002A
CALL Kernel32_1 <-- ORD_1 or VxdCall.
SUB AX,F386
POP EDI
JZ SoftICE_is_running

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.

:? 4647
00004647 0000017991 "FG" <-- Magic Val. 1.
:? 4A4D
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 (0912h, 0913h & 0914h) which can be used to manipulate SoftICE breakpoints.

Another set of magic values are also known ('BCHK'), this is the 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
JNZ SoftICE_is_here

4. - Using NuMega's own functions. This requires the use of nmtrans.dll but may well appeal to HLL programmers who aren't keen on using in-line ASM. The idea is to call DevIO_ConnectToSoftICE and verify the functions return value before taking appropriate action. Sadly this method is based heavily on CreateFileA (although a small amount of work could change that), hence in its current form it could easily be found. Looking through some of the other exports from nmtrans.dll I reckon a sneaky programmer could use some of the other exports.

5. - 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
JZ SoftICE_3.0_is_running

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.

6. - Detect SoftICE VxD or SoftICE GFX VxD (obviously ineffective under NT).

XOR DI,DI <-- Clear DI.
MOV ES,DI
MOV AX, 1684h
MOV BX, 0202h <-- VxD ID for SoftICE.
INT 2Fh
MOV AX, ES <-- VxD Entry Point.
ADD AX, DI
TEST AX,AX
JNZ SoftICE_is_here

The GFX id code is identical to that shown above except AX=1684h & BX=7A5Fh.

7. - Detect WinICE handler using INT 68h (V86).

MOV AH, 43h
INT 68h
CMP AX, 0F386h <-- Will be set by all system debuggers.
JZ SoftICE_is_here

If a debugger is not present AX will be 4300h.

8. - Detect and crash SoftICE with an illegal form of the instruction CMPXCHG8B (LOCK prefix) - opcode: F0 0F C7 C8.

9. - Searching for names installed by SoftICE in low-memory, "WINICE.BR", "SOFTICE1" + others (described in Stone's code below).

10. - Using the control or debug registers - I remember reading somewhere (perhaps in a fairly old Anti-debugging FAQ) that SoftICE doesn't handle the control or debug registers very well or not at all in the case of CR4, try DR4 and above too - a little something for you to play with me thinks :-).

11. - Dongle protection (used by dongle SSI Win32 Aegis).

xxxx: xxxx
EB01: JMP $+1
E8: DB E8h
xxxx: xxxx

SoftICE View:

xxxx: xxxx
EB01: JMP $+1
E8xxxxxxxx: CALL bad

TRW (not affected):

xxxx: xxxx
EB01E8: NOP
xxxx: xxxx

12. 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.

13. 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.

14. 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.

15. Check if a potential cracker has set breakpoints on key API functions :-

LEA ESI, GetDlgItemTextA
CALL CheckForSoftICEBP
CMP EAX, "xxxx" <-- Substitute for your own identifier.
JE SoftICEBPIsSet <-- Send bad cracker to some really horrid routine.
CALL ESI

CheckForSoftICEBP:

PUSH ESI
PUSH DS
PUSH CS
POP DS
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.
JE BPXSet
XOR EAX, EAX <-- No BPX.

BPXSet:

POP DS
POP ESI
RET

This sadly won't detect the clever cracker who routinely sets breakpoints such as 'bpmb GetDlgItemTextA x'.

16. 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.

17. PROCESSINFOCLASS == ProcessDebugPort, query to see if there is an active process debug port.

PUSH EAX
PUSH 23h
CALL ZwQuerySystemInformation


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.

18. StopIce (anti-SoftICE protection for Delphi applications apparently). I don't want to get at the person who coded the StopIce v1.0 & v2.0 deprotector (216k), 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.

Other Resources

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).

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.

Top Protection Tips for Software Authors

I'm sure many authors who stumble across reverse engineering sites regard them as little more than 'how to crack' repositories, and with software piracy costing most authors a significant proportion of their potential revenue, I do sympathise with this point of view. 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, even the ultimate protection can potentially be compromised by a legitamate 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 but should waste a crackers time if nothing else, if you want more details regarding implementation drop me an e-mail.

Top Protection Tips

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) thus halting the system, better still detect and issue a HBOOT to SoftICE. Use a parity checking routine to ensure that this detection is not tampered with and maybe set a discrete flag each time the parity routine is required, don't tell the cracker what you are doing with helpful message boxes.

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 even misleading names, further minor irritation can be achieved by using very long names with @ symbols, and never place your entire protection inside a single dll.

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, as an example, I give you the latest version of 3D Studio Max, very expensive software, and reverse engineered by me in under 5 minutes because of a message box.

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.

5. Avoid commercial *wrapper* protection schemes e.g. PreviewSoftware's TimeLock, VBox, ZipLock, Release Software Corporation's SalesAgent, Ken Nesbitt's ShareLock 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.

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, make several checks and don't use __VbaStrCmp, hyphenated serial numbers like xxx-xxx-xxx-xxx-xxx make for painful reversing. FPU maths (FCOS, FSIN) are also effective. If you use VB always compile your final product to p-code.

8. Bore potential crackers to death with fake 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.

10. Use encryption of any description, even just simple sliding XOR can add hours to a crackers progress. Consider using arrays of random characters for code calculation routines.

11. Use CRC 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 a CRC error be aware this could be due to virus tampering.

12. ...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.

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.


Return to Miscellaneous Papers (Anti-Debugging)


© 1998, 1999, 2000, 2001 CrackZ. 21st August 2001.