Section Image

Windows (Anti)cracking Techniques

Michal 'GiM' Spadlinski

Original article in polish: 2005, June

Translation: 11:o3:48 CEST 2oo6-19-o5

"Software cracking is the modification of software to remove encoded copy prevention. Distribution of cracked software (known by 'the initiated' as "warez"), or patches to circumvent software protection on software (commonly known as a "crack" or a "[k]") is generally an illegal (or more recently, criminal) act of copyright infringement."

Computer software companies often spend money to protect their software against crackers. Why are these protections so weak? Because to a large degree to walk them around doesn't cause much problems. This article is directed to newbie crackers, who would like to know, how to bypass, traps set for them, as well as to programmers, who would like to know, how to protect their programs from being cracked.

You have to know:

  • WinApi [win32.hlp, msdn]
  • C [I'm using mingw for compilation]
  • winasm [intel syntax, masm]

[by 9x systems I mean Windows 95, 98, Me, an by XP I mean Windows XP an 2K]

Intro

Solving logical or mathematical riddle gives undoubtly satisfaction. if we solve one, we expect another one harder. Just notice growing popularity of websites like: aniolkiajgona.webpark.pl, deathball.net/notpron/, zestriddle.fanzine.pl, loginmatrix.com or the newest simpler game: gwigle. It is the same with cracking. It happened not only once, that crack for a given program was available online before program itself was published. Basic cracker's tool is Softice [it costs about 2500e] of course and we're going to fight mainly with it.
Examples of bypassing are added rather to show the authors of software how simple is bypassing even complicated protection scheme, than to teach cracking techniques.
This article is expanded, more exploring version of article 'Ochrona programów dla Windows przed crackerami' [Protecting Windows programs against crackers'] by Jakub Nowak published in number 2/2oo5 of hakin9 magazine. And it is recommended to read it first, although not required.
I'll try to show more deeply, how presented by Jakub techniques works, some new techniques, how different techniques are useful, how to improve them, and how to crack them. Code examples should be available somewhere on my site [gim.org.pl, probably under gim.org.pl/Windows-Anti-Cracking-Techniques/].

Windows, dynamicly loaded drivers [VxDs], MeltIce

Method testing Softice's VxDs [so called MeltIce] relies on fact, that in Windows 9x systems trying to open a file named "\\.\something" using Win32Api function CreateFileA, causes, that system consider this as an attempt to load VxD driver into memory [go to google for details an try phrase: msdn VxD opening]. Aside from mentioned in Jakub's article driver's names: NTICE and SICE, other names like: SIWVID or SIVDEBUG can be tested. Hovever this method is rather historical, some of people just patches proper files, changing the strings SICE or NTICE to whatever they want.

Method can be detected for example like that:
BPX CreateFileA if *(*(esp+4)) == '\\\\.\\'

In Windows XP/2K there isn't something like dynamicly loaded VxDs and all attempts to load any driver using CreateFileA will fail. Instead of that there are so calles services, which will help us later.

FAMILY: Win 9x FILES: code1.cpp

Opening library files

SoftIce installs in system some files. Successful opening any of these files, therefore it's existance in filesystem, suggest us that SoftIce is probably installed. Unfortunately in Windows 9x an XP these files have different location, and have different names, what makes it harder, to write 'universal' detecting routine. But if you look closer, at file list placed in table below, you will notice, that one of files appears in both systems. It is dstudio.cpl. Because GetSystemDirectory(), returns directory x:\windows\system32 under XP systems and x:\windows under 9x systems, we are able to write a code, taht will work on both systems, what is presented in code1b.cpp.

XP/2k, \windows\system32\9x, \windows\
drivers\dbgmsg.sys system32\drivers\dbgmsg9x.sys
drivers\nmfilter.sys
drivers\ntice.sys winice.vxd
drivers\osinfo.dat
drivers\siwsym.sys
\drivers\siwvid.sys siwvid.386
drivers\winice.dat winice.dat
dstudio.cpl system\dstudio.cpl
nmfiltercoinstaller.dll

FAMILY: Win 9x, XP FILES: code1b.cpp

#include <windows.h>

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInstance, LPSTR lpszArgs, int nWinMode)
{
    HANDLE d;
    char path[1024];

    GetSystemDirectory(path, 1024);
    lstrcat(path, "\\dstudio.cpl");
    if ( (d=CreateFile(path, 0, 0, 0, OPEN_EXISTING, 0, 0)) != (HANDLE)(-1))
    {
        MessageBox(0, "SOFTICE ON DISK", "SI TEST", 0);
        CloseHandle(d);
    } else
        MessageBox(0, "SOFTICE _NOT_ ON DISK", "SI TEST", 0);

    return 0;
}

Opening section in system.ini

In 9x windows systems we can check the existance of SoftIce in system by attempt to open key siwvid [settings of a SoftIce's VxD file responsible for displaying SoftIce] in a system.ini file. We'll use for that functions: GetWindowsDirectory(), not to write things like "c:\windows" in staticly in code, and for checking if this section exists in system.ini file, we'll use GetPrivateProfileSection().

FAMILY: Win 9x FILES: code1c.cpp

Other keys in Windows registry

Of course SoftIce like most of applications stores some informations in Windows' registry, about Jakub have already written. So we can check for example using RegCreateKeyEx, if specific keys exists in registry, what would suggest, that SI is installed. The list below doesn't include names given by Jakub, all keys are in HKEY_LOCAL_MACHINE.

Common for 9x and XP:

  • Software\CLASSES\NmsFile
  • Software\CLASSES\NmsFile
  • Software\CLASSES\NmWizEng.WizardEngine
  • System\CurrentControlSet\Services\DbgMsg
  • System\CurrentControlSet\Services\NTice

XP only:

  • System\CurrentControlSet\Services\Siwsym
  • System\CurrentControlSet\Enum\Root\LEGACY_NTICE
  • System\CurrentControlSet\Enum\Root\LEGACY_SIWSYM
  • System\CurrentControlSet\Enum\Root\LEGACY_SIWVID

FAMILY: 9x, XP FILES: code2.cpp

Methods using int3

  • checking SoftIce's BoundChecker signature [BCHK]
    FAMILY: 9x FILES: code5.cpp
  • 'magic' values FG JM
    FAMILY: 9x, XP FILES: code4.cpp

It is worth to mention, that method using 'magic' values in (E)SI and (E)DI processor's registers, described in Jakub's article is specific backdoor to SI, that we can use to send commands to SoftIce, for example HBOOT :). Although it doesn't work as a backdoor on all versions of SI. Under 9x systems this method, can give false positives [returning true even if there is no debugger in memory]
Both methods are like presented by Jakub, with one difference, they doesn't use SetUnhandledExceptionFilter(). What is this function for? While program is running there can occured so called exceptions. The reasons for them can be different:

  • software: wrong opcodes - instruction in machine language, using interrupts, that aren't handled by system
  • hardware: attempt to access memory, to which we have no rights, division by zero, etc.

By default windows will handle these exceptions, what will cause so nice, and for most people totaly meaningless messagebox with text: 'superprogram caused exception ..... in module ..... ', registers state in moment of exception, and stack content. But here comes SEH - Structured Exception Handling to the rescue. In an application we can write our own exception handling routine. Main idea is to use SetUnhandledExceptionFilter() to install exception handling routine, then call int3 with registers set to proper values, and next working depending on information if we're under debugger or not. [We are forced to do so, because in Windows systems, when an interrupt is called, system checks if the privilege level of a thread, that caused interrupt, is at least as high as interrupt privilege level. If it's not we'll get General Protection Fault]. As I was writin before, we won't use SetUnhandledExceptionFilter(). Each thread has a different value of FS register. There is 16bit value placed there pointing to the TIB structure [Thread Information Block]. First word of this stucture [that is FS:[0]] contains a pointer to EXCEPTION_REGISTRATION structure, which forms one-way linked list of addresses of exception handling routines [specific chain of exceptions is build, nearly as it was in DOS]. Structure has only two pointers:

_EXCEPTION_REGISTRATION struc       prev dd ?       handler dd ? _EXCEPTION_REGISTRATION ends

first pointing to 'previous' structure on list, and second pointing to exception handler procedure. [take a look at: mojart_rysunek_01.sxd]
So we're putting address of our exception handling routine on stack, next we're putting address of previous structure (so the stack looks like this: [address of prev struct][addres of handler]) and we're setting FS:[0] to esp (stack pointer), so we've just added new node to list.

Some other methods

On CD there are also other codes, that were described in Jakub Nowak's article, so here thy will be described just briefly.

Files code6.asm and code6b.asm uses method of interrupt 41h - Debug_Serv_Interrupt, that is the interface for communication between libraries and VxD drivers. According to what can be found in debugsys.inc in Drivers Developer Kit for Windows 95, using value 4Fh: 'check to see if the debugger is installed and knows how to deal with protected mode programs return AX = F386h, if true'. In other words, if SI is installed in memory, after calling this interrupt we will get value D386_Id, that is that F386h. The difference between code6 and code6b is that code6b is a COM file and because using SEH wouldn't be very simple [I'm not sure if even possible], it exchanges interrupt 41h vector, [so it wouldn't fail if SI's not loaded].

In code7.asm mathod using interrupt 68h - Debug386_RealMode_Interrupt. Placing value D386_Identify, that is 43h in AH register, causes to return D386_Id if debugger is installed

Under SoftIce you just need to set following breakpoints, to check if program uses one from tests above:
bpint 68
bpint 41
bpx exec_int if ax == 41
bpx exec_int if ax == 68
If we get positive result You only have to check, where there is a call to SetUnhandledExceptionFilter() or, as it was stated before if Exception List.

In code8.asm there's a method using sidt instruction and counting the difference between interrupts 3 and 1, but in form it is there, there won't be useful, but we'll get back to this later.

IsDebuggerPresent() - Win32Api function, which should return true if program is run under debbuger. It doesn't work of course regarding to SoftIce, but It returns true if program is run under one of liked by crackers debbugers/disassemblers: IDA or W32Dasm [code9.cpp].

Different techniques to walk this around can be invented. Exchange call to this function into XOR eax,eax and some NOPs or change the content of EAX register right after the program has made a call.

On Listing2 [codea.cpp] there is a code of program, that in simple manner tries to check the presence of W32Dasm. Most of crackers are used to making use of FindWindow() or FindWindowEx() by programmers. This piece of code uses function EnumWindows() in order to find W32Dasm's window [identified by class name of a window: 'OWL_Window'] and if W32Dasm is launched, it is gently killed :-). By the way I present some interesting technique. Often it is not worth encipher data, because final executable file stands out. Attached code constructs from string 'Old Window Lookalike' string 'OWL_Window'. Similiar technique could be used to IDA. As a name of class you should use 'TIdaWindow' [codeab.cpp].

The easiest way to avoid this techniques is to patch executables of debbugers mentioned earlier in any hexeditor and changing class's names to any preferable e.g. 'Ala_HasACat'.
#include <windows.h>

char costam[64] = "Old Window Lookalike";
BOOL MyEnumProc(HWND hwnd, LPARAM lpar)
{
    char buf[256];

    GetClassName(hwnd, buf, 256);

    if (!lstrcmp(buf, costam))
    {
        MessageBox(0, "Under W32Dasm, Let's kill babe", "W32Dasm TEST", 0);
        PostMessage(hwnd, WM_DESTROY, 0, 0);
        PostMessage(hwnd, WM_CLOSE, 0, 0);
        ExitProcess(0);
    }

    return 1;
}

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInstance, LPSTR lpszArgs, int nWinMode)
{
    int i, j=0;

    for (i=0; i < lstrlen(costam); i++)
        if (costam[i] == ' ' && (costam[++j] = costam[++i]));
    while (costam[--i] != ' ');
    costam[i] = '\0';
    while (costam[--i] != ' ');
    costam[i] = '_';

    EnumWindows(MyEnumProc, 0);

    MessageBox(0, "Not under W32Dasm", "W32Dasm TEST", 0);
    return 0;
}

Win 9x and Get_DDB

Under Windows 9x systems there is very interesting technique. Using the function (or more properly service), which is made available by virtual machine manager (VMM), with the number 0146h namely Get_DDB(). Unfortunately, this function isn't directly available from the level of normal application, workinkg in the least privilaged level called ring-3. (Truly saying it is possible to call services running in ting-0 from the level of application running in ring-3, but we won't deal with this here). In this connection, we [well, I :)] will write simple VxD driver, which after sending to it control code, using DeviceIoControl() [cause this is the easiest way from application working in ring-3 to communicate with VxD driver], the only thing it will do it will call mentioned Get_DDB() with parameter 202 this is the identification number of SoftIce's driver [you can check that number under SoftIce running under Windows 9x by typing: vxd winice]. Above that we need a small loader, which will load our driver into memory and afterward it will call DeviceIoControl(). Code of a loader is visible on listing 3. The most important fragment of driver's code is showed on listing 4. Sources are in subidrectory drv_call. Sources of VxD driver are in self-compilable bat file.

And here is how we should walk around this:
BPX Get_DDB if eax == 202 do "p ret; r ecx 0"
and that will solve our problem.
VxD loader for Windows 9x

#include <windows.h>

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInstance, LPSTR lpszArgs, int nWinMode)
{
    HANDLE fp;
    DWORD bytes;

    if ( (fp = CreateFile ("\\\\.\\vmmtest.vxd", GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL)) != INVALID_HANDLE_VALUE)
    {
        if (DeviceIoControl(fp, 666, NULL,0, NULL, 0, &bytes, NULL))
            MessageBox (0, "IN MEMORY", "SI vmmcall TEST", 0);
        else
            MessageBox (0, "_NOT_ IN MEMORY", "SI vmmcall TEST", 0);
        CloseHandle (fp);
    } else {
        MessageBox (0, "Nie udalo sie zaladowac sterownika", "SI vmmcall TEST", 0);
    }
    return 0;
}
VxD Driver's code responsible for communication with application working in ring-3

BeginProc VMMTEST_ioctl
      assume esi:ptr DIOCParams
      local idt:DWORD

      mov ecx, [esi].dwIoControlCode ; get ioctl code
      cmp ecx, 666
      jne ret_succ

      mov eax, 202h
      VMMCall Get_DDB

      xor eax, eax
      dec eax
      test ecx, ecx

      jz nie_ma_sodticea
      inc eax
nie_ma_sodticea:
      clc
      ret

ret_succ:
      xor eax, eax
      clc
      ret
EndProc VMMTEST_ioctl

Drivers and SIDT [drv_sidt/]

SIDT is a good method. In IDT - Interrupt Descriptor Table, there are adresses [in form selector:offser] of Interrupt Service Routines [ISR]. This method works by calculating the difference between low 16-bit long parts of offsets of ISRs 1 and 3. Because SoftIce uses them when it's active, the difference will be 1Fh. [I'm not doing ANDing register with 16bit mask as my predecessor, to get only offset, because in IDT there will be 'segment'+'low word of offset', and if SoftIce has changed entries in IDT, the segment will be the same for interrupts 1 and 3]. However in form it is in code8.cpp it probably won't work, since SoftIce itself will hide IDT from ring-3 applications [at least its newer versions]. That's why I thought, we can write drivers working in ring-0 and check IDT state using them. In Windows XP and 2K VxD files has been replaced with sys files. But we can't load sys file as easy as VxD files. There is something called services, that depending on settings can be started while booting the system, during startup, on demand or not at all [for details take a look at documentation for ChangeServiceConfig()]. So here is our scenario, to load our driver in Windows XP/2K systems first we will use OpenSCManager() function, to connect to Service Control Manager, next we will create Service's Object and will add it to SCM's base using CreateService() [which incidentally takes more params, than CreateFile()]. If in SCM's base exists already given driver, CreateService() will return error and the next call to GetLastError() wiell return ERROR_SERVICE_EXISTS, what we will exploit later. If call to CreateService() succeeded we're calling StartService(), what among other things will cause a call to our DriverEntry() procedure from our driver. StartService() function will return only after DriverEntry() proc has finished. DriverEntry() creates 'symbolic link', which we need to get the handle of our driver using CreateFile(). Having the handle we can easily as in Windows 9x drivers use funcion DeviceIoControl to communicate with our driver. Calls to OpenSCManager(), CreateService() and StartService() requries root's [administrator's] privilages, because it is inadmissible, that ordinary user could add any code to system's kernel [logical, isn't it?]. So I've written two drivers one for Windows 9x systems and one for Windows XP/2K systems, and loader, which tries to load proper driver depending on detected Operating system. Both react on sending to them magic value 666h or 8666h using DeviceIoControl(). They return the difference between interrupt's 1 and 3 vectors. Code of loader and important fragment of Windows XP/2K driver is shown on listings 5 and 6.

Code of more universal loader

#include <windows.h>

int isNTfamily(void) { return (GetVersion()&0xff) == 5; }

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInstance, LPSTR lpszArgs, int nWinMode)
{
    HANDLE fp;
    SC_HANDLE scMgr, nSrv;
    SERVICE_STATUS st;
    char vxPath[MAX_PATH];
    DWORD bytes;


    if (isNTfamily()) {
        if ( (scMgr = OpenSCManager (0, 0, SC_MANAGER_ALL_ACCESS)) )
        {
            GetFullPathName ("sidwinxp.sys", MAX_PATH, vxPath, NULL);
            if ( (nSrv = CreateService (scMgr, "vmmmojateraz", "vmmmojateraz", SERVICE_START + SERVICE_STOP + DELETE, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, vxPath, NULL, NULL, NULL, NULL, NULL)) )
            {
                if (StartService(nSrv, 0, NULL))
                {
                    if ( (fp = CreateFile ("\\\\.\\moist", GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL)) != INVALID_HANDLE_VALUE)
                    {
                        if ( DeviceIoControl (fp,0x8666, NULL, 0, NULL, 0, &bytes, NULL) && bytes == 0x1e)
                        {
                            wsprintf (vxPath, "SI DZIAŁA DIFF: [%08X]", bytes);
                            MessageBox (0, vxPath, "SI sidt fru sys TEST", 0);
                        } else {
                            MessageBox (0, "SI chyba NIE DZIAŁA", "SI sidt fru sys TEST", 0);
                        }
                        CloseHandle (fp);
                    } else {
                        MessageBox (0, "CreateFile error", "SI sidt fru sys TEST", 0);
                    }
                    ControlService(nSrv, SERVICE_CONTROL_STOP, &st);
                } else {
                    MessageBox (0, "Start ERROR", "SI sidt fru sys TEST", 0);
                }

                DeleteService (nSrv);
                CloseServiceHandle (nSrv);
            } else {
                MessageBox (0, "Create ERROR", "SI sidt fru sys TEST", 0);
            }

            CloseServiceHandle(scMgr);
        }
    } else {
        if ( (fp = CreateFile ("\\\\.\\sidwin98.vxd", GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL)) != INVALID_HANDLE_VALUE)
        {             if (DeviceIoControl(fp, 666, NULL,0, NULL, 0, &bytes, NULL) && bytes == 0x1e) {
                wsprintf (vxPath, "SI DZIAŁA DIFF: [%08X]", bytes);
                MessageBox (0, vxPath, "SI sidt fru vxd TEST", 0);
            } else
                MessageBox (0, "_NOT_ IN MEMORY", "SI sidt fru vxd TEST", 0);
            CloseHandle (fp);
        } else {
            MessageBox (0, "Nie udalo sie zaladowac sterownika", "SI sidt fru vxd TEST", 0);
        }
    }
    return 0;
}
Most importatnt part of Windows XP/2K sys Driver

DispatchControl proc uses esi edi ebx pDeviceObject:PDEVICE_OBJECT, pIrp:PIRP

      local status:NTSTATUS
      local diff:DWORD
      local idt:FWORD

      mov diff, 0
      mov esi, pIrp
      assume esi:ptr _IRP

      IoGetCurrentIrpStackLocation esi
      mov edi, eax
      assume edi:ptr IO_STACK_LOCATION

      .if [edi].Parameters.DeviceIoControl.IoControlCode == 8666h
            sidt idt
            ; pierwsze pole struktury idt to 'limit' typu word
            ; drugie to 'base' typu dword
            mov eax, dword ptr [idt+2]
            add eax, 8
            mov ebx, [eax]
            add eax, 16
            mov eax, [eax]
            sub eax, ebx
            mov diff, eax
            mov status, STATUS_SUCCESS
      .else
            mov status, STATUS_INVALID_DEVICE_REQUEST
      .endif

      assume edi:nothing

      push status
      pop [esi].IoStatus.Status

      push diff
      pop [esi].IoStatus.Information

      assume esi:nothing

      fastcall IofCompleteRequest, pIrp, IO_NO_INCREMENT

      mov eax, status
      ret

DispatchControl endp

Anti-Frogsice [9x] [codeb.cpp] Anti-NTALL [XP/2K] [codebb.cpp]

Under Windows 9x (un?)fortunately exists application called FrogsIce [as a matter of fact driver for Windows 95, 98, Me], which makes SoftIce immune to ALL techniques presented to this moment [with exception to points 2, 3, 4 but SoftIce itself can handle it] and few others [like looking for a driver by name]. But we can check the presence of FrogsIce itself. I myself use the technique presented on listing 7. We check all running processes [combination of Process32First() + Process32Next()] and comparing certain memory area of each process with our 'pattern' [using OpenProcess() and ReadProcessMemory() [lovely Windows 9x!]]. If our pattern matches, it is nearly sure FrogsIce is running.

#include &lt;windows.h&gt;
#include &lt;tlhelp32.h&gt;

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInstance, LPSTR lpszArgs, int nWinMode)
{
    HANDLE snap, proces;
    PROCESSENTRY32 pe;
    DWORD readed, i;
    LPCVOID adresik = (LPCVOID)0x0401000;
    char buf[32];
    /* magic pattern */
    char fimem[32] = "\x68\x2a\x42\x40\0\xe8\xa3\x2c\0\0\x83\x3d\x3a\x42\x40\0\1\x74\x18\x6a\x10";

    snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
   
    pe.dwSize = sizeof(PROCESSENTRY32);
    Process32First(snap, &pe);
    while (Process32Next(snap, &pe))
    {
        if ( (proces = OpenProcess(PROCESS_TERMINATE|PROCESS_VM_READ, FALSE, pe.th32ProcessID)) )
        {
            if (ReadProcessMemory(proces, adresik, buf, 21, &readed))
            {
                for (i=0; i&lt;21; i++)
                    if (buf[i] != fimem[i])
                        break;
                if (i == 21) {
                    MessageBox(0, "JEST FrogsIce", "FrogsIce TEST", 0);
                    TerminateProcess(proces, 0);
                    CloseHandle(proces);
                    ExitProcess(1);
                }
            }
            CloseHandle(proces);
        }
    }

    MessageBox(0, "Oka chyba nie ma FrogsIce'a", "FrogsIce TEST", 0);
    return 0;
}

There is similiar program for Windows XP/2K called NTALL. It makes SoftIce immune to int3 tests regardless of I3HERE being on or off. The same technique can be applied to NTALL program [codebb.cpp] using as a pattern following string:

char pattern[32] = "\x55\x8B\xEC\x53\x55\x8B\x45\x0C\x35\x80\x60\x48\0\x8B\x6D\x8\xFF\x75\x28\xFF\x75";
Detecting this method is very easy. Just analyse the codes in codeb.cpp and codebb.cpp and set breakpoints on proper functions.

On image 1 [mojart_rysunek_02.tif] you can see logfile from ForgsIce running on Windows 98 SE, generated after running programs 1, 4, 5, 6, 6b [frogsice doesn't detect this method], 7, 8, drv_call, drv_sidt(1) choosen options (2) and the results of programs (3) code8 and drv_sidt run without [on left] and with FrogsIce running [it shows code8 is not effective]. By the way, observe that FrogsIce doesn't log call to sidt instruction, but it prevent detection of SoftIce using this method. The reasons of this are obvious.

Windows XP and services [XP/2K] [codec.cpp]

As I was writing before in Windows XP/2K there are services and SoftIce itself works as a service. In a similiar manner, as we have loaded our driver before, we can check if our favorite service with ntice name is running or not. This is what program from listing 8 does. It is new method for detecting SoftIce, and I don't kno if anyone has presented similar technique before. Of course cracker can easily put traps on any function used and walk our protection around, but because this is new method, some people might not know about this. The same tehnique can be used to detect NTALL, giving as a name of a driver NTALL instead of NTICE in code on listing 8. Then we will detect presence of NTALL, but only if somone after launching NTALL, pressed 'Load' [loaded ntall driver to memory]. Despite the fact that FrogsIce quite effectively prevents detection of SoftIce, we shouldn't get depressed. there are fewer and fewer people using windows 9x [besides crackers ;)], and NTALL in the moment of writing [polish version of] this text, isn't so advanced as FrogsIce. Which methods are effective in Windows XP environment must be empirically tested with and without NTALL running.

New method of detecting SoftIce

#include <windows.h>

/* you can also try combination of
    openservice(SERVICE_START);
    !startservice && getlasterror == already_running
*/
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInstance, LPSTR lpszArgs, int nWinMode)
{
    SC_HANDLE     h, h2;
    SERVICE_STATUS     ss_d;

    if ( (h = OpenSCManager(0, 0, 0x80000000)) )
    {
        if ( (h2 = OpenService(h, "NTICE", SERVICE_QUERY_STATUS)) )
            if (QueryServiceStatus(h2, &ss_d) && ss_d.dwCurrentState != SERVICE_STOPPED)
            {
                MessageBox(0, "SI IS working", "SI QueryService TEST", 0);
                return 0;
            }
    }

    MessageBox(0, "SI is probably NOT working", "SI QueryService TEST", 0);

    return 0;
}

Summary in points

  • "Shareware is bad" [tm], it's better to invest money in making a demo, than giving shareware to public.
  • If you have detected debugger, DO NOT inform (ab?)user about this and don't change actions of a program. Write few functions with the same prototype, carring out registration or checking if the program is registered [but only one mus be correct]. Create pointer to prototype of your function and set it to different values depending on whatever you have detected debugger or not. If you do it 'right way' [tm] cracker wiell think he's cracking the registration procedure, while he will be spending his time cracking some dummy code. Similary he can thought that change of conditional jump in [false] registration check procedure will realy register him...
  • Try to forget about 'if' HLL instruction, at least in places where you check if debugger is present in memory or not. Use of it or any other conditional statment makes cracker's task easier.
  • DO NOT use obvious names like CheckRegStatus or IsValidUser etc.
  • Similary when you do registration do not use obvious file names or entry names in registry like 'RegKey' or 'regfile.dat'. Data about the registration status should be possibly stick to the end of a file that is required for working with program regardles of registration status.
  • Use YOUR OWN file formats, known only to you [it won't protect you in any way, but there is a chance it will make understanding of code difficult]. All 'time trials' and '30-day trial period' only makes cracker's life EASIER.
  • If you decide on ciphering, use whole data, use different cryptographic functions, decipher some strings just before use of them and some on start of application, avoid algorithms which has well-known constant values.
  • Some people make use of checksums [cc, md5, sha-1] of fragment of file. Usualy this is sensless since cracker will only change the place where you compare generated sum with that hard-coded.
  • Inserting dummy opcodes described in Jakub Nowak's article is quite sensless and it won't make ANY difference for advanced cracker.
  • Do not rely on commercial 'protection software'. Most of it has been 'universally' cracked, where I mean every program protected by it can be easily cracked.
  • Regarding to tools mentioned in Jakub Nowak's article, using software like Armadillo, PEspin or ASProtect, all of them can be walked around, and won't be a problem for a cracker [I can add, that AntiVirus Laboratories have ready tools [which aren't made public of course!], that handle deciphering such freely-available tools.
  • ALL ANTI-DEBUG and ANTI-DISASM TECHNIQUES WILL SOONER OR LATER BE BROKEN, SO DO NOT RELY ON THEM!
  • Sometimes it is worth putting somewhere in memory text like: 'I see you're good, pleas do not publish the crack', maybe it helps.
  • If you REALY won't to make your program secure, hire GOOD [in term of abilities] cracker, who will make test for you and maybe secure your application.
  • If cracker will want, he will crack your protections. There is always someone who has excess of free time...
  • It's better NOT TO USE "The best clever and sophisticated' anti-debugger technique if it is to be included in code IN A BAD WAY.

Summary

Cracker will probably break your protections, but you shouldn't worry about this. If the program that company sells is 'good', for sure there will always be a group of honest users, that will pay for it. Companies are forced to use legal software, and the costs of 'slip-up' are so high, that big companies, usually use legal software. There exists some more interesting and advanced techniques of detecting debuggers, but there's no point in publishing them. Strength of debuggers' detecting depends to a large degree on secracy of a method. In moment where protection is well-known, way to walk it around shows up immediatelly. It is important always to look for new techniques. If a Company in its every product uses one and the same protection scheme, it seems it evidently wants to make crackers job easier.

Now let's take a look on the other side of a coin. Phisically it's not possible to create software, that could be registered by typing data in specified fields, and that would be simultaneously resistant to cracking. Author of software were inventing different methods, like getting the needed data from the internet or hardware dongle, but as a practice shows us, all these methods were useless.

Network Resources:

  • http://win32asm.cjb.net/ - Iczelion's Win32 Assembly and VxDs Tutorials,
  • http://msdn.microsoft.com/ Microsoft.Developer Network,
  • http://www.compuware.com/ SoftIce,
  • http://www.woodmann.com/fravia/ Fravia's mirror,
  • http://biw.rult.at/index.php?page=tuts unpacking part,
  • http://www.datarescue.com/ IDA,
  • http://aluigi.altervista.org/adv/w32dasmbof-adv.txt - buffer overflow in W32D.

Bibliography:

  • Win32 Programmer's Reference,
  • Ralph Brown's Interrupt List,
  • A Crash Course on the Depths of Win32 Structured Exception Handling - Matt Pietrek,
  • Windows 95 Programming Secrets - Matt Pietrek,
  • Programming Windows 5th edition - Charles Petzold.
Translation: 11:o3:48 CEST 2oo6-19-o5 Michal 'GiM' Spadlinski