How to make cracker's life harder. Anti piracy protections for programmers

Vault

Methods of protecting software against cracking, including anti-piracy and anti-debug tricks that allow for detection of popular cracking tools.

Have you ever wondered why you can find cracks for almost any program that hits the market? Or maybe you are programmers yourselves and you have already experienced that first hand? If so, or if you are simply interested in anti-piracy protection and you want to learn how to protect yourselves against such attacks, I urge you to keep reading.

Where do those cracks come from?

Cracks available online are usually small programs that modify application, for example, causing the „disappearance” of time limit for using program or allowing registering of application on any data, by applying crack.

Where do those cracks, or e.g. serial key generators come from? They are the work of crackers, people whose purpose is to remove program protection, as opposed to hackers, who breach protection in online systems.

Debugger

For a cracker to be able to break protection of a program, he most often needs to locate a certain point within program's code, e.g. where procedures that verify correctness of registration data are called.

Tool most often used for that purpose is debugger, a program that allows for tracking of execution of program code on the level of assembler instructions.

While tracking program code, cracker is able to intercept, using so called breakpoints) calling system functions that are responsible e.g. for reading serial number from program's editor window. After that, by further tracking of program execution, cracker is able to locate a place where verification of entered serial number is verified against the original, and thus he can acquire correct serial number.

OllyDbg debugger window
OllyDbg debugger window

How to defend against protection breach?

It is said that every protection can be breached and it is just a matter of time. If so, than it is a good idea to make cracker's life harder, so that he had to spend much more time to crack it, which in terms could result in his inability to breach our program and resigning to try different less protected one.

The most frequent method to impede program analysis are so called antidebug tricks.It means that before we run our program, we check for presence of tools used by crackers and if those are detected, we can suspend running of this program. We may also use more sophisticated methods, e.g. when we discover debugger, we allow the program to run normally, but set timer, that will cause this program to fail or switch off after five minutes of running.

Methods of this type are very effective, as they require profound analysis of program in order to detect anomaly. The worst solution here is displaying information that e.g. debugger has been detected that is one of the most common mistakes, cracker, who knows this message, will be able to find a part of code where it is called and remove the part responsible for checking for debugger.

It should also be noted that antidebug methods can be divided into two categories, those that use documented functions of Windows system and those that take advantage of gaps in operating system. In this article, I will describe documented methods, as they guarantee proper running of program and do not limit their application on different versions of Windows system.

Debugger detection

Currently the most popular debugger is SoftIce created by Numega, apart from that there are many other tools like TRW, OllyDbg and so on. Debugger program is usually running on the level of privileges higher than ordinary programs (system debuggers), in this case debugger program is supplied in the form of system driver. Windows system allows for communication between ordinary programs with drivers, with use of DeviceIoControl() function, but before it will communicate with driver, it needs to be accessed. This is done by calling WinApi function CreateFile() with the name of driver given in special form \\.\DRIVER. Thanks to this function we are able to detect debugger, example code:

///////////////////////////////////////////////////////////////////////////////
//
// HANDLE CheckDriver(char *lpszDriver)
//
// function tries to gain access to given system driver
//
// on exit:
// driver handle or: INVALID_HANDLE_VALUE in case of lack of driver
//
///////////////////////////////////////////////////////////////////////////////

HANDLE CheckDriver(char *lpszDriver)
{
    return ( CreateFile(lpszDriver, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL) );
}

///////////////////////////////////////////////////////////////////////////////
//
// BOOL IsSice()
//
// function checks for the presence of SoftIce debugger versions for 
// Windows 9x systems
//
// on exit:
// if SoftIce debugger is active, function returns TRUE,
// if otherwise FALSE
//
///////////////////////////////////////////////////////////////////////////////

BOOL IsSice()
{
    return ( CheckDriver("\\\\.\\SICE") != INVALID_HANDLE_VALUE ? TRUE : FALSE);
}

///////////////////////////////////////////////////////////////////////////////
//
// BOOL IsNtice()
//
// this function checks for presence of SoftIce debugger, version for
// Windows NT/2K/XP systems
//
// on exit:
// if SoftIce debugger is active, function returns TRUE,
// if therwise FALSE
//
///////////////////////////////////////////////////////////////////////////////

BOOL IsNtice()
{
    return ( CheckDriver("\\\\.\\NTICE") != INVALID_HANDLE_VALUE ? TRUE : FALSE);
}

///////////////////////////////////////////////////////////////////////////////
//
// BOOL IsSiwdebug()
//
// this function checks for the presence of additional driver installed
// with the SoftIce debugger for Windows 9x systems, its presence
// in the system, indicated presence of a debuggera
//
// on exit:
// if SoftIce debugger is acive, function returns TRUE,
// otherwise FALSE
//
///////////////////////////////////////////////////////////////////////////////

BOOL IsSiwdebug()
{
    // open helper driver
    CheckDriver("\\\\.\\SIWDEBUG");

    return ( GetLastError() != ERROR_FILE_NOT_FOUND ? TRUE : FALSE);
}

Detecting SoftIce debugger by checking its driver is currently not a very efficient method as crackers modify their work tools so that they could go undetected.

There is a slightly different method for checking if there is a debugger in the system, that is checking Windows registry keys for registry entries left e.g. by debugger installation program or for keys containing debugger's configuration setting.

Debugger entries in Windows Registry
Debugger entries in Windows Registry

This method has one drawback, it doesn't allow for checking with 100% certainty that debugger is active, as registry entries could be left from previous instalation.

///////////////////////////////////////////////////////////////////////////////
//
// BOOL IsReg()
//
// function checks for presence of Windows registry hive
//
// on exit:
// if given hive is in Windows registry, function will return
// TRUE otherwise FALSE
//
///////////////////////////////////////////////////////////////////////////////

BOOL IsReg(char *lpszReg)
{
    HKEY phkResult;
    DWORD iResult;

    // open Windows registry hive in HKEY_LOCAL_MACHINE
    iResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, lpszReg, 0, KEY_ALL_ACCESS, &phkResult);

    // close handle
    RegCloseKey(phkResult);

    return (iResult == ERROR_SUCCESS ? TRUE : FALSE);
}

///////////////////////////////////////////////////////////////////////////////
//
// BOOL IsSiceReg1()
//
// function checks for presence of registry hives used by
// SoftIce debugger to handle information needed for 
// debugger deinstalation
//
// on exit:
// if Windows registry contains debugger hive, function will return
// TRUE otherwise FALSE
//
///////////////////////////////////////////////////////////////////////////////

BOOL IsSiceReg1()
{
    return IsReg("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\SoftICE");
}

///////////////////////////////////////////////////////////////////////////////
//
// BOOL IsSiceReg2()
//
// function check for presence of registry hives use by
// SoftIce debugger to handle configuration information
//
// on exit:
// if there is a debugger hive in Windows registry, function will return
// TRUE otherwise FALSE
//
///////////////////////////////////////////////////////////////////////////////

BOOL IsSiceReg2()
{
    return IsReg("SOFTWARE\\NuMega\\SoftICE");
}

Debugger detection can also be done by analyzing records in „autoexec.bat” file, where there is a path to debugger program that is initiated at system booting.

SET SOUND=C:\PROGRA~1\CREATIVE\CTSND
SET MIDI=SYNTH:1 MAP:E MODE:0
SET BLASTER=A220 I5 D1 H5 P330 E620 T6
SET Path=C:\WINDOWS;C:\WINDOWS\COMMAND;E:\DEV\MASM\;E:\DEV\MASM\BIN;E:\DEV\TASM;
mode con codepage prepare=((852) C:\WINDOWS\COMMAND\ega.cpi)
mode con codepage select=852
keyb pl,,C:\WINDOWS\COMMAND\keybrd4.sys
C:\PROGRA~1\Numega\Softice\winice.exe

The last line in for debugger loading. By checking content of „autoexec.bat” file, we can determine the presence of debugger.

///////////////////////////////////////////////////////////////////////////////
//
// BOOL IsAutoexec(char *lpString)
//
// function checks for entries in autoexec.bat file
//
// on exit:
// if the entry is found, function will return TRUE
//
///////////////////////////////////////////////////////////////////////////////

BOOL IsAutoexec(char *lpString)
{
    BOOL bResult = FALSE;
    int iSize, i;
    char *lpMap,*lpAutoexec, lpFile[256];
    HANDLE hFile, hMap;

    // read Windows directory path
    GetWindowsDirectory(lpFile, 256);

    // build path to autoexec.bat file
    strcpy(&lpFile[3], "autoexec.bat");

    // open file
    hFile = CreateFile(lpFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    if (hFile != INVALID_HANDLE_VALUE)
    {
        // get file size
        iSize = GetFileSize(hFile, NULL);

        // if size is equal to 0 return
        if (iSize)
        {
            // create file map
            hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);

            if (hMap)
            {
                // map file
                lpMap = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);

                if (lpMap != 0)
                {
                    lpAutoexec = lpMap;

                    // scan autoexec.bat contents
                    while (iSize--)
                    {
                        if (strnicmp(lpAutoexec++, lpString, strlen(lpString)) == 0)
                        {
                            bResult = TRUE;
                            break;
                        }
                    }
                }

                UnmapViewOfFile(lpMap);
            }

            CloseHandle(hMap);
        }

        CloseHandle(hFile);
    }

    return bResult;
}

Next you should check in the program, if given entries are present in the file:

// detecting debugger entries in autoexec.bat
if (IsAutoexec("nume") || IsAutoexec("softic"))
{
	ExitProcess(-1);
}

Detection of debugger breakpoints

At the beginning of this article I mentioned debugger breakpoints, crackers use them e.g. to capture trigger function that reads serial number from edition window. Debugger breakpoints operate on the basis that the first bite of procedure, at which the breakpoint is aimed, is overwritten with a special instruction of assembler. int 3. When debugger finds instruction int 3, it stops running of that program and gives cracker the command. Hexadecimal representation of that instruction is 0xCCvalue , to detect active breakpoint it is just enough to examine if the first bite of procedure equals 0xCC.

For example if during registration procedure we use WinApi function GetDlgItemText() before calling it we can check if cracker has left a breakpoint on its address and in case of finding it, terminate running of that program.

///////////////////////////////////////////////////////////////////////////////
//
// BOOL IsBpx(char *lpszModule,char *lpszFunction)
//
// function checks if there is a breakpoint set by a debugger on the address of given function
//
// on exit:
// if breakpoint is set, function returns TRUE
//
///////////////////////////////////////////////////////////////////////////////

BOOL IsBpx(char *lpszModule,char *lpszFunction)
{
    HINSTANCE hLibrary;
    DWORD lpProc;

    // check if the library is already loaded
    hLibrary = GetModuleHandle(lpszModule);

    // load library
    if (hLibrary == NULL)
    {
        hLibrary = LoadLibrary(lpszModule);
    }

    if (hLibrary != NULL)
    {
        // get function pointer
        lpProc = (DWORD)GetProcAddress(hLibrary, lpszFunction);

        if (lpProc != 0)
        {
            // check first byte of the function code
            // for the presence of the breakpoint
            if (*(BYTE *)lpProc == 0xCC) return TRUE;
        }

    }

    return FALSE;
}

...

// we check for the breakpoint in the registration function
if (IsBpx("USER32.dll", "GetDlgItemTextA") == TRUE)
{
    ExitProcess(-1);
}

If e.g. our protection relies on key file, instead of checking function GetDlgItemTextA, we should check the function that is used to open key file and in case on finding a breakpoint, cause the program to loop indefinitely, e.g.:

// repeat code execution until the breakpoint is present
while(IsBpx("KERNEL32.dll", "CreateFileA") == TRUE);

Using the function IsBpx can also detect active SoftIce debugger within Windows NT, 2K i XP. In these systems SoftIce debugger by default sets a breakpoint (for its own purposes) on WinApi function SetUnhandledExceptionFilter(), detection of this breakpoint indicated presence of debugger in the system.

// check SoftIce NT debugger presence
if (IsBpx("KERNEL32.dll", "SetUnhandledExceptionFilter") == TRUE)
{
    ExitProcess(-1);
}

System monitors

Apart from debuggers, crackers very often use many auxiliary tools, among those most popular are system monitors. Those are programs that allow to see which program has tried to read keys from Windows registry or allows to analize which files it has tried to open.

By analizing data from monitors, we can easily notice that e.g. application has tried to open a key in Windows registry, where registration data should be stored. Currently the most popular tools of this type are Filemon and Regmon monitors, made by Sysinternals.

Regmon registry monitor in action
Regmon registry monitor in action.

Operation of monitors is also partially based on using and detecting drivers, we can also use methods that resulted in detection of debugger drivers.

///////////////////////////////////////////////////////////////////////////////
//
// BOOL IsFilemon()
//
// function checks for presence of driver used in program
// that traces file references (filemon)
//
// on exit:
// if file monitor is active, function returns TRUE,
// otherwise FALSE
//
///////////////////////////////////////////////////////////////////////////////

BOOL IsFilemon()
{
    return ( CheckDriver("\\\\.\\FILEVXD") != INVALID_HANDLE_VALUE ? TRUE : FALSE);
}

///////////////////////////////////////////////////////////////////////////////
//
// BOOL IsRegmon()
//
// function checks for presence of driver used by the program to trace references to Windows registry (regmon)
//
// on exit:
// if registry monitor is active, function returns TRUE,
// otherwise FALSE
//
///////////////////////////////////////////////////////////////////////////////

BOOL IsRegmon()
{
    return ( CheckDriver("\\\\.\\REGVXD") != INVALID_HANDLE_VALUE ? TRUE : FALSE);
}

Filemon and Regmon programs have window-type user interface, thanks to the name of main window it is possible to detect if monitor program is active or not. For this purpose we use WinApi function FindWindowEx().

///////////////////////////////////////////////////////////////////////////////
//
// BOOL IsFilemonWnd()
//
// function checks, if Filemon window is currently open
//
// on exit:
// if file monitor is active function returns TRUE,
// otherwise FALSE
//
///////////////////////////////////////////////////////////////////////////////

BOOL IsFilemonWnd()
{
    return ( FindWindowEx(NULL, NULL, NULL, "File Monitor - Sysinternals: www.sysinternals.com") != NULL ? TRUE : FALSE);
}

///////////////////////////////////////////////////////////////////////////////
//
// BOOL IsRegmonWnd()
//
// function checks if Regmon window is currently open
//
// on exit:
// if registry monitor is active, function returns TRUE,
// otherwise FALSE
//
///////////////////////////////////////////////////////////////////////////////

BOOL IsRegmonWnd()
{
    return ( FindWindowEx(NULL, NULL, NULL, "Registry Monitor - Sysinternals: www.sysinternals.com") != NULL ? TRUE : FALSE);
}

Thanks to FindWindowEx() we can easily check if monitors are switched on, and take suitable steps, e.g. switch the application off, but we can also switch off the monitor itself. Please take note that the function FindWindowEx() in case of finding the window, returns the handle. Having the window handle, we can send WM_CLOSE, that is sent when user closes the application. Sending this message causes monitor to switch off.

///////////////////////////////////////////////////////////////////////////////
//
// BOOL KillRegmonWnd()
//
// if this function finds Regmon window, it will close it
//
// on the exit:
// if registry monitor had been active and was shut down, function will
// respond TRUE, in other case FALSE
//
///////////////////////////////////////////////////////////////////////////////

BOOL KillRegmonWnd()
{
    HANDLE hWindow;

    // find Regmon window
    hWindow = FindWindowEx(NULL, NULL, NULL, "Registry Monitor - Sysinternals: www.sysinternals.com");

    // in case it's found
    if (hWindow != NULL)
    {
        SendMessage(hWindow, WM_CLOSE, 0, 0);
    }

    return ( hWindow != 0? TRUE : FALSE);
}

It may happen that the application will not react to WM_CLOSE message, we may also try sending message WM_ENDSESSION, that stimulates Windows shut down. If that doesn't help as well, it may be worth a try to use some harsh methods. Having a handle to the window, we open a process to which the window belongs and shut it down using a function TerminateProcess().

///////////////////////////////////////////////////////////////////////////////
//
// BOOL KillFilemonWnd()
//
// if this function finds Filemon window, it will close it
//
// on the exit:
// if registry monitor had been active and was shut down, function will
// respond TRUE, in other case FALSE
//
///////////////////////////////////////////////////////////////////////////////

BOOL KillFilemonWnd()
{
    HANDLE hWindow, hProcess;
    DWORD dwPid;

    // find Filemon window
    hWindow = FindWindowEx(NULL, NULL, NULL, "Registry Monitor - Sysinternals: www.sysinternals.com");

    // in case window is found
    if (hWindow != NULL)
    {
        // get process identifier who owns the window
        if (!GetWindowThreadProcessId(hWindow, &dwPid))
        {
            // open process with an extra flag that can be used to kill it
            hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, dwPid);

            if (hProcess != NULL)
            {
                // terminate the process
                if ( TerminateProcess(hProcess, -1) != 0) return TRUE;
            }
        }
    }

    return FALSE;
}

Protecting application file

Our program, armed with anti-debug methods, may cause some trouble, but still there is a possibility that a cracker will be able to break protection and publish it online.

The best way to avoid it is to have the file extra protected. This precaution is based on a rule that an executable file of our application is additionally encrypted and compressed before its release.

With a file protected that way, any attempted modification, e.g. with use of a crack, will cause the file not to run. For this kind of file precautions, special programs are used, they are so called exe-protectors.

After executable file has been protected, a special code, that is responsible among others for decryption and compression of data and then for running the application, is added to its structure.

Apart from that, code which is added to files which are being protected includes several anti-debug methods, with them it is impossible to run application when debuggers or any other tools used by crackers are present.

Ending

Methods shown here are just a part of all capabilities that allow for protection of your program from being cracked. I hope my article was able to let you see that released software is not as protected as it would seem to be, but I also wanted to show you that in some way you can protect yourself.

https://www.pelock.comPELock protector and license keys system
http://www.sysinternals.comFilemon and Regmon monitors
http://www.borland.com/Products/Software-Testing/Automated-Testing/Devpartner-StudioSoftIce debugger producer's website
http://www.knlsoft.comdebugger TRW
http://www.ollydbg.de/free debugger OllyDbg
http://upx.sourceforge.netUPX executable files compressor
https://bitsum.com/kompresor plików wykonywalnych PECompact