Καλώς ορίσατε στο dotNETZone.gr - Σύνδεση | Εγγραφή | Βοήθεια
σε

 

Αρχική σελίδα Ιστολόγια Συζητήσεις Εκθέσεις Φωτογραφιών Αρχειοθήκες

Τεχνικές Anti-Cracking (Part I)

Îåêßíçóå áðü ôï ìÝëïò Thiseas. Τελευταία δημοσίευση από το μέλος Thiseas στις 06-03-2010, 14:07. Υπάρχουν 0 απαντήσεις.
Ταξινόμηση Δημοσιεύσεων: Προηγούμενο Επόμενο
  •  06-03-2010, 14:07 57477

    Τεχνικές Anti-Cracking (Part I)

    Η διαδικασία προστασίας των προγραμμάτων για PC είναι πολύ παλιά. Χάνεται κάπου στις απαρχές του... 1980! Από τότε δηλαδή που εμφανίστηκαν τα πρώτα Personal Computers. Βέβαια από τότε πολλά άλλαξαν. Από τον αρχικό στόχο που ήταν βασικά η προστασία από αντιγραφή, φτάσαμε στους νέους και «μοντέρνους» στόχους που είναι η προστασία από την αλλαγή και την ανάγνωση του κώδικα από μη εξουσιοδοτημένους «χρήστες». Σημεία των καιρών...

    Σε κάθε περίπτωση η έννοια του anti-cracking περιλαμβάνει αρκετά θέματα αλλά ένα βασικό σκοπό: Να μην μπορέσει κάποιος να προσβάλει με οποιονδήποτε τρόπο την ακεραιότητα ενός προγράμματος. Η λέξη «προσβάλει» περιλάμβάνει έννοιες όπως, το να διαβάσει, να κατανοήσει και φυσικά να αλλάξει το πρόγραμμα με στόχο να επιτελέσει διαδικασίες διαφορετικές από αυτές για τις οποίες φτιάχτηκε.

    Η όλη ιστορία ξεκίνησε από τότε που οι κατασκευαστές προγραμμάτων αποφάσισαν οτι έπρεπε να βγάζουν δοκιμαστικές εκδόσεις (όπως demos ή evaluation versions) για τα προγράμματα τους. Τα πρόγραμματα αυτά είναι τα ίδια ακριβώς που θα αποκτούσε κάποιος αν τα αγόραζε κανονικά, με τη μόνη διαφορά κάποιες αλλαγές που στην πραγματικότητα έκρυβαν όλες τις δυνατότητες τους. Ε, αυτή η κίνηση αποτέλεσε την «πτώση του... γαντιού» προς τους crackers, οι οποίοι ξεκίνησαν με διάφορες τεχνικές να προσπαθούν να παρακάμψουν τις μικρές αλλαγές που έκαναν οι κατασκευαστές έτσι ώστε να κάνουν τα προγράμματα να προσφέρουν το 100% των δυνατοτήτων τους, σαν να ήταν αγορασμένα.

    Μετά από λίγο καιρό, το cracking δημιούργησε σοβαρά προβλήματα τόσο στις μεγάλες εταιρίες όσο και στην κοινότητα των "ελεύθερων προγραμματιστών". Οι ελεύθεροι προγραμματιστές είναι επαγγελματίες που δεν βρίσκονται πίσω από μια μεγάλη εταιρεία και προσφέρουν τα προγράμματά τους (τα οποία, ομολογουμένως, πολλές φορές δεν έχουν να ζηλέψουν τίποτα από τα αντιστοιχα των μεγάλων εταιριών) σε download sites, σαν shareware. Ο όρος shareware δηλώνει οτι τα προγράμματα μπορούν να χρησιμοποιηθούν από οποιονδήποτε, για ένα μικρό χρονικό διάστημα. Από εκεί και πέρα χρειάζετε συνήθως να δοθεί κάποιο ποσό για να μπορέσει ο χρήστης να συνεχίσει να χρησιμοποιεί το πρόγραμμα ή για να «ξεκλειδωθούν» όλες οι δυνατότητες του.

    Έτσι, ξεκίνησαν να χρησιμοποιούνται τεχνικές προστασίας από τις επιθέσεις των crackers ώστε αυτοί να μην μπορούν εύκολα να επιτελέσουν το έργο τους. Λέμε, εύκολα, διότι όπως είναι γνωστό «ότι κλειδώνει, ξεκλειδώνει» και τις περισσότερες φορές δεν είναι πολύ εύκολο να εμποδίζουμε ένα γνώστη και αποφασισμένο cracker να «σπάσει» το πρόγραμμά μας. Μπορούμε όμως να του κάνουμε την ζωή από δύκολη έως... εφιαλτική ;-) χρησιμοποιώντας κάποιες τεχνικές και κάποια μικρά μυστικά μερικά από τα οποία ευελπιστούμε να αναλύσουμε σε αυτό το άρθρο.

    Σε πολύ πολύ γενικές γραμμές, για να μπορέσουμε να προστατέψουμε ένα πρόγραμμα πρέπει να έχουμε στο μυαλό μας 2 βασικά κριτήρια προστασίας:
    1.    Να μην μπορεί το πρόγραμμα μας να χρησιμοποιηθεί από ένα debugger.
    2.    Να μην μπορεί το πρόγραμμα μας να διαβαστεί (τουλάχιστον εύκολα) με ένα απλό disassembler ή με ένα memory dumper (μεταφορά του προγράμματος από την μνήμη σε αρχείο στον δίσκο).

    Έστω οτι εμείς θέλουμε να φτιάξουμε ένα shareware πρόγραμμα για περιβάλλον Windows Vista το οποίο θα δέχεται ένα serial number και θα εξετάζει αν αυτό το serial number είναι γνήσιο. Θα δούμε πως μπορούμε να φτιάξουμε έναν ελεγκτή serial number με τους δικούς μας κανόνες γνησιότητας. Επίσης, θα δώσουμε στο πρόγραμμα μας κάποιες δυνατότητες που θα ικανοποιεί τα παραπάνω 2 βασικά κριτήρια προστασίας που αναφέραμε.

    Τα βήματα που θα παρουσιάσουμε υλοποιώντας το πρόγραμμά μας θα είναι τα παρακάτω:
    1.    Τεχνικές Anti-Debugging (θα περιγραφεί σε αυτό το part I).
    2.    Μπέρδεμα του κώδικα ώστε να μην μπορεί να κατανοηθεί εύκολα (code obfuscation - θα περιγραφεί στο part II).
    3.    Packing του εκτελέσιμου και self-encryption (θα περιγραφεί στο part II).
    4.    Δημιουργία αλγόριθμου για την απόδειξη της γνησιότητας ενός serial number (serial number authentication - θα περιγραφεί στο part II).

    Οι προγραμματιστικές τεχνικές που θα χρησιμοποιήσουμε θα υλοποιηθούν κάνοντας χρήση της γλώσσας C++ και της Assembly. Θα κάνουμε επίσης κλήσεις στο API (Application Programming Interface) των Windows Vista. Όσα παρουσιαστούν, αναπτύχθηκαν και δοκιμάστηκαν σε περιβάλλον Windows Vista με το Visual Studio 2008.

    Για να λειτουργήσουν σωστά όλες οι συναρτήσεις που δίνουμε σε αυτό το άρθρο, θα πρέπει να έχετε χρησιμοπoιήσει στο προγραμμά σας, τα παρακάτω headers:
    #include <stdio.h>
    #include <windows.h>
    #include <tchar.h>
    #include <SetupAPI.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <Psapi.h>
    #include <tlhelp32.h>
    #include <vdmdbg.h>
    #include <conio.h>
    #include <tlhelp32.h> 


    Ας ξεκινήσουμε:

    Βήμα 1: Τεχνικές AntiDebugging
    Ένας σημαντικός συντελεστής προστασίας του προγράμματος μας από αδιάκριτα βλέματα είναι να μην μπορεί κάποιος να διαβάσει τον κώδικα μας, μέσω του εκτελέσιμου αρχείο που θα του δώσουμε, με τη χρήση κάποιου ειδικού debugger ή dis-assembler, όπως οι Olly (http://www.ollydbg.de/), W32Dasm (http://is.gd/6GOE), SoftIce (http://is.gd/6GOT) κλπ.

    Υπάρχουν πολλές τεχνικές για να το καταφέρουμε αυτό. Άλλες μπορούν να παραβιαστούν πολύ εύκολα και άλλες πιο δύσκολα. Το αρνητικό που συχνά συμβαίνει στις προστασίες προγραμμάτων είναι οτι χρησιμοποιείται μόνο μία μέθοδος προστασίας. Έτσι, όταν ο cracker την παρακάμψει, το έργο του είναι μετά είναι piece of cake. Εμείς, θα χρησιμοποιήσουμε πολλές μεθόδους μαζί, έτσι ώστε να δυσκολέψουμε το έργο του επίδοξου cracker. Φυσικά, τίποτε δεν είναι απόρθητο, και αν ο cracker είναι πολύ καλός και έχει και... «τεράστια» υπομονή, χρόνο και μερικές φορές τύχη, ίσως (λέμε ίσως!) καταφέρει τελικά να «παραβιάσει» το πρόγραμμά μας ;-)

    Τεχνική 1: Κλήση της API function IsDebuggerPresent().
    H τεχνική αυτή αναφέρεται απλά και μόνο για λόγους επάρκειας και γνώσης μιας και είναι πολύ εύκολο να παραβιαστεί.
    Η function αυτή βρίσκεται μέσα στην kernel32.dll. Πριν μπούμε σε λεπτομέρειες ας αναφέρουμε πρώτα για το περίφημο (PEB Process Enviroment Block - http://is.gd/6GWt).
    Το PEB είναι μια περιοχή στην μνήμη που έχει αποδοθεί (από το λειτουργικό σύστημα) στον χρήστη και περιέχει πληροφορίες για κάθε επεξεργασία (Process) που τρέχει στο σύστημα του. Το χαρακτηριστικό της είναι οτι μπορεί να «πειραχτεί» από τον χρήστη εφόσων τρέχει στο λεγόμενο Process Address Space του. Στο PEB βρίσκονται πολλές χρήσιμες (και πολλές φορές... undocumented) πληροφορίες (http://is.gd/6GU4), όπως στοιχεία για το Image Base Address (η διεύθυνση που «φορτώνεται» το 1ο byte του εκτελέσιμου), για το Ηeap (την μνήμη που αποδίδεται δυναμικά κατά τη διάρκεια εκτέλεσης του προγράμματος) για τις «βιβλιοθήκες» που έχουν κληθεί για να «συνδράμουν» ;) στην τρέχουσα διεργασία, για τις μεταβλητές μνήμης κλπ κλπ. Μέσα στα «κλπ κλπ.» βρίσκεται και μια ένδειξη (flag) που αναφέρει αν η τρέχουσα διεργασία έχει κληθεί από κάποιον debugger. Η ένδειξη αυτή βρίσκεται στην θέση 30h (δεκαεξαδικό) ή 48 (για τους κολλημένους με το δεκαδικό σύστημα) μέσα στο PEB.
    Για λόγους κατανόησης και... «διαφάνειας» ;-) δίνουμε μια δική μας εκδοχή της function IsDebuggerPresent() σε C++ και Inline Assembly, που κάνει το ίδιο ακριβώς με αυτήν του API των Windows.
    int DirectIsDebuggerPresent()
    {
        char IsDbgPresent = 0;
        __asm {
             mov eax, fs:[30h]
             mov al, [eax + 2h]
             mov IsDbgPresent, al
        }
        return(IsDbgPresent);
    }
    Όμως, όπως είπαμε, το BEP μπορεί να «πειραχτεί» από τον χρήστη και όταν λέμε «από τον χρήστη» εννοούμε από οποιονδήποτε χρήστη! Ακόμα κι από τον cracker. Έτσι πολύ εύκολα κάποιος «κακόβουλος» θα μπορούσε να αλλάξει την ένδειξη αυτή από αληθή σε ψευδή και η function DirectIsDebuggerPresent() να επιστρέφει πάντα false.

    Τεχνική 2: Η συνάρτηση CheckRemoteDebuggerPresent().
    Πρόκειται για μια τεχνική παρόμοια με την προηγούμενη με τη διαφορά οτι χρησιμοποιεί την ntdll.dll. Εμείς θα την χρησιμοποιήσουμε κάνοντας κλήση κατ’ ευθείαν στην NtQueryInfoProcess. Μια κλήση που δεν συνίσταται από την MicroSoft μιας και η συμπεριφορά των συναρτήσεων NtXXX μπορεί να αλλάξει χωρίς προειδοποίηση με μια νέα έκδοση ή ακόμα και ένα Patch του Λειτουργικού Συστήματος. Η συνάρτηση είναι η παρακάτω:
    int CheckRemoteDebugger_Direct()
    {
    // Function Pointer Typedef for NtQueryInformationProcess
    typedef unsigned long (__stdcall *pfnNtQueryInformationProcess)(IN  HANDLE,
            IN  unsigned int, OUT PVOID, IN ULONG, OUT PULONG);
     
    const int ProcessDbgPort = 7;
     
    pfnNtQueryInformationProcess NtQueryInfoProcess = NULL;
     
    unsigned long Ret;
    unsigned long IsRemotePresent = 0;
     
    HMODULE hNtDll = LoadLibrary(TEXT("ntdll.dll"));
    if(hNtDll == NULL)
        return(0);
    
    NtQueryInfoProcess = (pfnNtQueryInformationProcess)
       GetProcAddress(hNtDll, "NtQueryInformationProcess");
    if(NtQueryInfoProcess == NULL)
        return(0);
    
    Ret = NtQueryInfoProcess(GetCurrentProcess(), ProcessDbgPort,
          &IsRemotePresent, sizeof(unsigned long), NULL);
    return(Ret == 0x00000000 && IsRemotePresent != 0);
    
    }

    Τεχνική 3: Τσεκάροντας τις ενδείξεις NtGlobalFlags.
    Μια άλλη συχνά χρησιμοποιούμενη τεχνική είναι αυτή που ελέγχει τις ενδείξεις που επιστρέφει η NtGlobalFlags. Το NtGlobalFlags είναι μια λέξη (DWORD) μέσα στο PEB. Αυτή η μεταβλητή περιέχει κάποιες συγκεκριμενες τιμές όταν το πρόγραμμα μας εκτελείτε μέσα από debugger, όπως:
    FLG_HEAP_ENABLE_TAIL_CHECK (0x10),
    FLG_HEAP_ENABLE_FREE_CHECK(0x20)      
    και
    FLG_HEAP_VALIDATE_PARAMETERS(0x40).

    Εάν στο τέλος της κλήσης η τιμή του καταχωρητή EAX  είναι η 70h τότε κάποιος debugger έχει αναλάβει.. δράση!
    Η συνάρτηση που πρέπει να φτιάξουμε έχει ως εξής:
    int TestNtGlobalFlags()
    {
        unsigned long NtGlobalFlags = 0;
        __asm {
            mov eax, fs:[30h]
            mov eax, [eax + 68h]
            mov NtGlobalFlags, eax
            }
        return (NtGlobalFlags & 0x70);
    }

    Τεχνική 4: Ελέγχοντας το parent processes του προγράμματος μας.
    Mια αρκετά αγαπημένη μου τεχνική. Το κολπάκι είναι το εξής: Όταν τρέχει ένα πρόγραμμα στην μνήμη, έχει πάντα ένα γονέα (parent): Πρόκειται για αυτόν που το κάλεσε ή αλλιώς αυτόν που το «γέννησε», το έκανε δηλαδή να κληθεί, να αποκτήσει ζωή! Αν το πρόγραμμα κληθεί κανονικά από τον περιβάλλον των windows ο γονέας του είναι το πρόγραμμα explorer.exe. Αν κληθεί το πρόγραμμα από κάποιον debugger τότε ο γονέας του είναι το όνομα του debugger. Φυσικά υπάρχουν κι άλλοι... «γονείς». Π.χ. αν κληθεί από τη γραμμή εντολών (Command Line) του λειτουργικού (του πάλαι πότε αθάνατου και διαχρονικού... DOS Command!) τότε ο γονέας του είναι ο cmd.exe. Αν πάλι κληθεί μέσα από το περιβάλλον του Visual Studio 2008 o γονέας του είναι ο devenv.exe. Απ’ οτι θα καταλάβατε οι γονείς μπορεί να είναι πολλοί! Μπορούμε όμως να γίνουμε λίγο πιο αυστηροί και να απαιτήσουμε να εκτελείτε το πρόγραμμα μας μόνο από το λειτουργικό ή την γραμμή εντολών και όταν ο γονέας δεν είναι ο explorer.exe ή ο cmd.exe τότε να θεωρούμε οτι το πρόγραμμά μας δεν κλήθηκε με τον σωστό τρόπο ή οτι κλήθηκε από κάποιον debugger.
    Ο κώδικας παρακάτω βρίσκει το Process ID του Parent Process του προγράμματος μας και μετά βρίσκει το όνομα αυτού του parent process. Αν δεν είναι κάποιο από τα "explorer.exe",  "cmd.exe" τότε θεωρεί οτι υπάρχει ενεργοποιημένος debugger. Οπότε bye bye...
    int TestByProcessCheck()
    {
    char filename[256];
    int i, currProcessID, ParentProcessID, IsDebugON = 0;
    
    // Get the process list snapshot.
    HANDLE hProcessSnapShot = CreateToolhelp32Snapshot(
                                            TH32CS_SNAPALL,
                                            0 );
    
    // Initialize the process entry structure.
    PROCESSENTRY32 ProcessEntry = { 0 };
    ProcessEntry.dwSize = sizeof( ProcessEntry );
    
    // Get Current process.
    currProcessID = GetCurrentProcessId();
    
    if (!Process32First( hProcessSnapShot, &ProcessEntry ))
        return IsDebugON;
    do
    {
        for (i=0; ProcessEntry.szExeFile[ i ] != '\0' ; i++ )
            filename[ i ] = ProcessEntry.szExeFile[ i ];
        filename[ i ] =     '\0';
    
        printf("%d %30s PPID-->%d",ProcessEntry.th32ProcessID, filename,
                                 ProcessEntry.th32ParentProcessID);        
    
        if (currProcessID == ProcessEntry.th32ProcessID){
            ParentProcessID = ProcessEntry.th32ParentProcessID;
        }
    }
    while( Process32Next( hProcessSnapShot, &ProcessEntry ));
    
    //Getinfo for parent process ID
    if (!Process32First( hProcessSnapShot, &ProcessEntry ))
        return IsDebugON;
    do
    {
        if (ParentProcessID == ProcessEntry.th32ProcessID){
            for (i=0; ProcessEntry.szExeFile[ i ] != '\0' ; i++ )
                filename[ i ] = ProcessEntry.szExeFile[ i ];
            filename[ i ] =     '\0';
    
            if  (  (stricmp(filename , "cmd.exe")) &&
                 (stricmp(filename , "explorer.exe")))
            {
                IsDebugON = 1;
            }
    
            break;
        }
    }
    while( Process32Next( hProcessSnapShot, &ProcessEntry ));
    
    // Close the handle
    CloseHandle( hProcessSnapShot );
        return (IsDebugON);
    }

    Βέβαια αυτή η τεχνική έχει και αρκετά μειονεκτήματα. Θα μπορούσε να επιστρέφει αρκετά "false alarms" αν κάποιος καλούσε το πρόγραμμα μέσα από κάποιο file explorer, όπως π.χ. τον αγαπημένο μου Total Commander [τον πάλαι πότε Norton Commander - αν τον θυμάται κανένας ακόμα!!! Sad ].

    Τεχνική 5: Μετρώντας τον χρόνο που μεσολαβεί μεταξύ της εκτέλεσης δύο εντολών ή αλλιώς «τεχνική anti-break-point».
    Ένας αρκετά πονηρός και όχι έυκολα παραβιάσιμος τρόπος προστασίας είναι να μετρήσουμε τον χρόνο που έχει περάσει ανάμεσα σε 2 εντολές του προγράμματός μας. Αν ο χρόνος αυτός είναι μεγαλύτερος από (ας πούμε) 1 δευτερόλεπτο σημαίνει οτι μάλλον κάποιος βρίσκεται μέσα σε ένα debugger και μάλιστα έχει βάλει κάποιο break point και διαβάζει το πρόγραμμά μας.
    Θα χρησιμοποιήσουμε 2 μεθόδους που υλοποιούνε αυτήν την τεχνική για να κάνουμε ακόμα πιο δύσκολο τον εντοπισμό της: Την GetTickCount και την QueryPerformanceCounter.

    >>GetTickCount:
    Η βιβλιοθήκη Win32 μας παρέχει μια ειδική συνάρτηση η οποία μετράει τον χρόνο (σε χιλιοστά του δευτερολέπτου) που έχει περάσει από τη στιγμή που ανοίξαμε τον υπολογιστή μας. Πέρα από τα (ομολογουμένως) ενδιαφέροντα κόλπα που μπορούμε να κάνουμε με αυτήν την συνάρτηση, μπορούμε και να μετρήσουμε τον χρόνο που έχει περάσει ανάμεσα σε μια ή περισσότερες εντολές. Το ενδιαφέρον της υπόθεσης είναι οτι η συνάρτηση αυτή δεν χρησιμοποιεί καμιά υπηρεσία (service) του kernel για να τρέξει. Η υλοποίηση της είναι σχετικά απλή:
    ...
    unsigned int BeginTime, EndTime, Difference;
    ...
    BeginTime = GetTickCount();
    function1();
    function2();
    EndTime = GetTickCount();
    Difference = (EndTime - BeginTime) / 1000;  // seconds  
    if (Difference > 1 )
        printf("Debug ON from GetTickCount with number = %d! \n",Difference);
    ...


    >>QueryPerformanceCounter:
    Είναι μια API function και βρίσκεται μέσα στην kernel32.dll. Η υλοποίηση της είναι επίσης εύκολη:
    ...
    __int64 ctr1 = 0, ctr2 = 0, diff, freq=0;
    QueryPerformanceCounter((LARGE_INTEGER *)&ctr1);
    function1();
    function();
    QueryPerformanceFrequency((LARGE_INTEGER *)&freq);
    QueryPerformanceCounter((LARGE_INTEGER *)&ctr2);
    diff = ((ctr2 - ctr1)  / freq);        // seconds
    if (diff > SecondsToWaitBeforeAbort)
        printf("Debug ON from QueryPerformanceCounter with number = %d! \n",diff);
    ...
    Υπάρχουν και κάποιες άλλες τεχνικές που βασίζονται σε κάποια side-effects του τρόπου που λειτουργούν οι API functions των windows XP οι οποίες όμως έχουν αλλάξει στα Vista γι’ αυτό και δεν τις υλοποίησαμε. Για την επάρκεια όμως της γνώσης είναι καλό να τις αναφέρουμε.

    Η περίπτωση του kernel32!CloseHandle και NtClose.
    Κάποιες συναρτήσεις API χρησιμοποιούν την συστημική κλήση ZwClose (http://is.gd/6HhG) (όπως η CloseHandle και η NtClose) η οποία μπορεί να χρησιμοποιηθεί για την εύρεση παρουσίας debugger. H ZwClose δέχεται σαν παράμετρο ένα pointer σε ένα οποιοδήποτε object για να το αποδεσμεύσει. Αν μέσα από ένα debugger καλέσουμε την ZwClose με παράμετρο η οποία δεν αντιστοιχεί σε κάποιο υπαρκτό object τότε αυτή (αν και μόνο αν βρίσκεται κάτω από debugger) επιστρέφει ένα συγκεκριμένο λάθος (exception) το οποίο είναι το STATUS_INVALID_HANDLE (0xC0000008).
    Η περίπτωση του kernel32!OutputDebugStringA (http://is.gd/6Hp1).
    Η τεχνική αυτή αναφέρεται (στην «βιβλιογραφία») οτι έχει χρησιμοποιηθεί στο ReCrypt v0.80. Το κολπάκι βρίκεται στην κλήση της OutputDebugStringΑ η οποία δέχετε σαν παράμετρο μια σειρά χαρακτήρων. Σε κανονικές συνθήκες η function αυτή επιστρέφει 1, αλλά αν κληθεί μέσα από έναν debugger και περάσουμε σαν παράμετρο μια μη αποδεκτή σειρά χαρακτήρων τότε η συνάρτηση αυτή επιστρέφει την διεύθυνση της σειράς χαρακτήρων που περάσαμε σαν παράμετρο και όχι το 1 ή 0, όπως θα περίμενε κανείς.

    Καλώντας τα όλα μαζί:
    Η βασική υποθετική function που θα μπορούσε να καλεί όλες τις παραπάνω συναρτήσεις είναι η ακόλουθη:
    int DirectIsDebuggerPresent();
    int TestNtGlobalFlags();
    int CheckRemoteDebugger_Direct();
    int TestByProcessCheck();
    int CrashAttack();
    
    
    #define SecondsToWaitBeforeAbort 1
    
    int main(int argc, char *argv[])
    {
        unsigned int BeginTime, EndTime, Difference;
        __int64 ctr1 = 0, ctr2 = 0, diff, freq=0;
    
        printf("Hello world!\n");
    
        BeginTime = GetTickCount();
        QueryPerformanceCounter((LARGE_INTEGER *)&ctr1);
        BOOL bDebugged = FALSE;
        CheckRemoteDebuggerPresent( GetCurrentProcess(), &bDebugged );
        if (bDebugged) printf("Debug ON from API CheckRemoteDebuggerPresent() \n");
        else printf("Debug OFF from CheckRemoteDebuggerPresent \n");
    
        if (CheckRemoteDebugger_Direct())
                 printf("Debug ON from CheckRemoteDebugger_Direct \n");
        else
                 printf("Debug OFF from CheckRemoteDebugger_Direct \n");
    
        if (DirectIsDebuggerPresent())
               printf("Debug ON from IsDebuggerPresent() direct call \n");
        else
               printf("Debug OFF from IsDebuggerPresent() direct call \n");
    
        if (TestNtGlobalFlags()) printf("Debug ON from NtGlobalFlags \n");
        else printf("Debug OFF from NtGlobalFlags \n");
    
        if (TestByProcessCheck()) {
            printf("Debug ON from TestByProcessCheck & crash ATTACK\n");
            CrashAttack();  //!!!!!!!!!!!! ΕΠΙΘΕΣΗΗΗΗΗΗΗΗΗΗΗ !!!!!!!!!
        }
        else printf("Debug OFF from TestByProcessCheck & crash ATTACK\n");
    
        QueryPerformanceFrequency((LARGE_INTEGER *)&freq);
    
        EndTime = GetTickCount();
        QueryPerformanceCounter((LARGE_INTEGER *)&ctr2);
    
        Difference = (EndTime - BeginTime) / 1000;  // seconds  
        diff = ((ctr2 - ctr1)  / freq);        // seconds
    
        if (Difference > SecondsToWaitBeforeAbort )
            printf("Debug ON from GetTickCount with number = %d! \n",Difference);
    
        if (diff > SecondsToWaitBeforeAbort)
            printf("Debug ON from QueryPerformanceCounter with number = %d! \n",diff);
    
        printf("END OF PROGRAM....");
        return 0;
    }
    Δώστε βάση στην επιτυχία της TestByProcessCheck(). Σε αυτήν την περίπτωση κάνουμε κάτι... κακό. Καλούμε την function CrashAttack() η οποία κάνει το πρόγραμμα μας να «κολλήσει». Αυτό βέβαια μπορούμε να το κάνουμε κάθε φορά που διαπιστώνουμε οτι βρισκόμαστε μέσα σε έναν debugger. H κακή function απλά ορίζει μια σειρά χαρακτήρων με λάθος ορίσματα κάνοντας τον debugger να τα... «παίξει». Ο κώδικάς της είναι ο ακόλουθος:
    int CrashAttack()
    {
    const char szHello[10] = "%s%s";
    printf("\n---*In Attack*---\n");
    __asm {
    push szHello
    call [OutputDebugStringA]
    }
    return(1);
    }
    Μια βασική παρατήρηση. Ποτέ μα ποτέ να μην γράφετε τον κώδικα σας τόσο εύκολα κατανοητό όσο εμείς σε αυτό το παράδειγμα που έγινε για λόγους παρουσίασης και κατανόησης. Μην επαφίεστε ποτέ σε έναν μόνο έλεγχο μιας μεταβλητής για το αν υπάρχει ή όχι debugger ενεργοποιημένος ή πόσα δευτερόλεπτα μπορεί να περιμένει το πρόγραμμα πριν αποφασίσει οτι έχει μπει ένα break point. Οι έλεγχοι που βασίζονται σε μεμονομένες μεταβλητές δεν είναι καλή πρακτική μιας και εύκολα μπορούν να αλλάξουν από τον cracker. Να προτιμάτε να καλέσετε την ίδια την συνάρτηση μέσα στον έλεγχο (if/then) παρά να βάλετε το αποτελέσεμα της σε μια μεταβλητή και μετά να εξετάσετε την μεταβλητή. Πολλά τέτοια κι άλλα ακόμα κόλπα θα δούμε στο part 2 αυτού του άρθρου όταν θα μιλήσουμε για code obfuscation, δηλαδή για μπέρδεμα του κώδικα για να μην μπορεί εύκολα να γίνει κατανοητός.

    Πιστεύω οτι έδωσα μια μικρή αλλά αντιπροσωπευτική εικόνα για τον τρόπο που μπορούμε να προστατεύσoυμε τα προγράμματα μας από τον... debugger!
    Φυσικά οι τρόποι και οι μέθοδοι είναι πάρα πολλοί και συνεχώς εξελίσονται κατ’ αναλογία πάντα με την τεχνικές cracking! Εγώ, πάντα στα πλαίσια του χώρου από τη μία και της επάρκειας της γνώσης από την άλλη, προπάθησα να δώσω μια εικόνα εικόνα του 1ου βήματος προστασίας. Στο επόμενο part θα ακολουθήσουν και τα επόμενα βήματα τα οποία είναι εξίσου σημαντικά αλλά και ενδιαφέροντα.

    Όλο το project το έχουμε ονομάσει Rock. Τα sources μαζί με το executable των συναρτήσεων που παρουσιάζονται σε αυτό το άρθρο, μπορείτε να το κατεβάσετε από εδώ:  http://rapidshare.com/files/189286984/RockProjectThiseas.rar.
    Ως συνήθως, δεν υπάρχει κανένα πρόβλημα copyright και μπορείτε να χρησιμοποιήσετε τον κώδικα για να προστατεύσετε τα προγράμματα σας, όπως εσείς νομίζετε χωρίς εγώ να έχω ΚΑΜΙΑ απαίτηση. Ούτε απαιτώ καμιά αναφορά σε κανένα προγραμματιστή που τα έφτιαξε, κανένα χρηματικό «έπαθλο» ή άλλα τέτοια χαρωπά! Μπορείτε να τα αλλάξετε, να τα χαλάσετε, να τα εμπλουτίσετε, να τα πουλήσετε, και οτι περνάει από το μυαλό σας που δεν περνάει από το δικό μου... Smile


    Happy Anti-Cracking!!

    [Πρώτη Δημοσίευση: Total XakeЯ
    Magazine #19 (Dec.2008)]


    [ PART II ]


    Nothing to declare...
Προβολή Τροφοδοσίας RSS με μορφή XML
Με χρήση του Community Server (Commercial Edition), από την Telligent Systems