Creare un debugger-patcher
(debugger API, hardware breakpoints)

Data

by faina

 

24/09/2004

UIC's Home Page

Published by Quequero

They who dream by day are cognizant of many things ...

Tanto di cappello faina, veramente complimentissimi!!!!

... which escape those who dream only by night. (E.A. Poe)

....

Se mi vuoi contattare manda un P.M. a faina_mdc sul forum UIC

....

Difficoltà

( )NewBies (X)Intermedio (X)Avanzato ( )Master

 
 

Introduzione

Benvenuto. In questo mio primo tutorial, troverai un esempio di come si può creare un programma che simuli il comportamento di un debugger per lanciare un processo e modificarne il comportamento.
L'oggetto sarà il programma AVI/MPEG/ASF/WMV Splitter (sì, si chiama proprio così) della Boilsoft, nella versione 3.22.
Ti consiglio caldamente di leggere, prima, il tutorial di bender0: "Clone DVD 2.5.0.0" che puoi trovare sempre qui sulla UIC. E' veramente ben fatto e tutte le conoscenze necessarie per quello, lo sono anche per questo che hai davanti. Inoltre, ho dato per scontate le cose spiegate da lui in quella sede.
Devi conoscere: un po' di Assembler e architettura Intel, un (bel) po' di architettura di Windows specialmente le API di debugging, un po' di programmazione e sapere come funziona un debugger.

Tools usati

OllyDbg 1.10
Microsoft Visual C++ 6.0 (o qualunque altro compilatore per Windows)
Mozilla Composer (per scrivere questa pagina)

URL o FTP del programma

Il programma lo puoi prelevare dal sito della "Boilsoft": vvv.boilsoft.com. Probabilmente troverai una versione successiva, diversa. Se vuoi proprio questa fammi sapere: finché non si scassa l'hard disk, la dovrei conservare, e poi è grande solo 1.2 MB

Notizie sul programma

Questo programma permette di tagliare un fimato in parti più piccole. L'ho provato solo su un file AVI/Divx 5.1 ed ha funzionato bene. Questa versione funziona per 10 giorni, dopodiché si avvia solo in modalità "inserimento codice".

Essay

Prima di cominciare, mi raccomando: leggete e capite il tutorial di bender0 intitolato "CloneDVD 2.5.0.0". A proposito, anche se con lui non ci conosciamo (ancora?), lo vorrei ringraziare caldamente: mi ha chiarito molte cose e mi ha permesso di scrivere questo testo; spero di riuscire ad essere altrettanto chiaro.

1 L'inizio.
Il tutto è partito da un messaggio sul forum della UIC che segnalava questo target. Dopo averlo caricato in Olly e aver saltellato qua e là attraverso il codice ho fatto le seguenti considerazioni e deduzioni:
 
- è protetto con Armadillo: infatti in memoria, durante il funzionamento del programma, si possono trovare varie stringhe del tipo ArmAccess.dll, ArmVersion, ecc. Non conosco questo tipo di protezione, ma, in corrispondenza della stringa "ArmVersion" c'è scritto 3.75, quindi presumo che questa sia la versione;

- quando si prova ad inserire una chiave per la registrazione, il programma comincia a "trafficare" con delle lunghe stringhe alfanumeriche, segno di un qualche algoritmo di codifica di cui, però, non mi sono interessato;

- da quanto sento in giro, desumo che non tutte le possibili protezioni anti-crack sono state sfruttate in questo caso; per es. il programma non sembra controllare mai se è stato lanciato da un debugger; meglio per noi;

- all'inizio il programma è quasi tutto criptato in memoria e c'è un segmento iniziale che si preoccupa di fare vari controlli e di decriptarlo per poi farlo girare;

- ha un periodo di scadenza di 10 giorni;

- in alcuni punti, fa un controllo di integrità di parti della memoria per verificare se ci sono cambiamenti;
- ha un controllo sull'orologio del computer: se lo mettete indietro se ne accorge; io ho fatto di peggio: ho avanzato di un mese l'orologio e ho avviato il programma; ovviamente mi ha detto che il periodo di prova era scaduto e mi permetteva solo di inserire la chiave; poi ho rimesso a posto l'orologio, e da quel momento non è più partito perché l'orologio era stato spostato indietro! ora torna a funzionare solo se risposto in avanti l'orologio; vabbè, una difficoltà in più e un check in più da saltare;

- in tutta la parte iniziale, controlli di integrità, controlli sull'orologio, decrittazione delle restanti parti del progrmma, generano moltissime eccezioni; in pratica, è come se questo programma, una volta completata una funzione, invece di un RETURN, o di un GOTO, generi un'eccezione, per la quale ha preventivamente preparato la gestione; al presentarsi di questo evento, il sistema operativo fa proseguire l'esecuzione dalla routine di gestione dell'eccezione che non è altro che un'altra funzione del programma, e così via. Quando il programma viene avviato da un debugger come Olly, tutte le eccezioni vengono passate al debugger; ma Olly permette di ignorarle tutte e di farle gestire al programma stesso che, così, prosegue tranquillamente come è stato progettato;

- le varie parti del programma, non vengono scompattate/decriptate tutte in un colpo solo, ma a blocchi successivi. Per semplificare: il blocco 1 decripta il 2; il blocco 2 controlla l'orologio e, se è tutto ok, decripta il 3; il 3 controlla la scadenza dei 10 giorni e poi continua; in questa situazione, non si può creare un semplice "patcher" che lanci il programma e lo modifichi al volo, perché la parte da modificare (per es. il controllo dei giorni rimasti) diventa disponibile solo dopo un pò; ma comunque la si deve modificare prima che venga eseguita; come fare? Con il debugger, piano piano, a forza di breakpoint in esecuzione o sulle scritture/letture in memoria si riesce ad arrivare al punto fatidico dopo che è stato decriptato ma prima che venga eseguito; ma senza il debugger?

2. Che mi sono messo in testa?
Dunque, quello che voglio fare è un programmino che lanci Avisplitter e riesca a modificarlo nonostante le eccezioni, le sezioni criptate e i controlli.
Insomma, un programma che sia in grado di "simulare" tutti i passi che io seguo col debugger, ma che lo faccia per conto proprio.
Per capire, vi elenco brevemente i passi che eseguo col debugger per saltare o imbrogliare i controlli del programma; un premessa, però: se provate a farlo anche voi, quando un breakpoint non serve più, rimuovetelo; infatti, in alcuni punti, questo programma controlla che non ci siano modifiche al suo codice.

a) lancio il programma e gli passo tutte le eccezioni fino a quando non si verifica quella all'indirizzo di memoria 0x00A68006; ho scelto questa perché, a quel punto, sono sicuro che la sezione successiva è stata decriptata, ma ancora non è stata eseguita;
 
b) imposto un breakpoint all'indirizzo 0x00A7D8BC; anche qui, ho scelto il punto perché la sezione successiva era stata appena decriptata, ma ancora non eseguita;

c) imposto un break all'indirizzo 0x00A7DAA5; è lì, infatti, che è stato appena verificato l'orologio del sistema; se il flag Z è 0, il controllo è fallito e il programma si ferma lamentandosi che l'orologio è stato manomesso; quindi devo settare Z a 1; questo controllo viene fatto 2 volte di seguito;

d) dopo un po', viene decriptata un'altra sezione della memoria, (dove sarà il vero programma), ed è lì che viene eseguito il controllo della scadenza del periodo di prova; mi sono scocciato di impostare breakpoints a ripetizione: stavolta uso un hardware breakpoint in lettura/scrittura direttamente  dove dovrà essere la nuova sezione: ho scelto l'indirizzo 0x00412049;

e) quando Olly ferma il programma, imposto 2 breakpoints normali sull'esecuzione di 0x00410D2A e di 0x00412049. Quando, poi, il programma si ferma al primo punto, cambio (se necessario) il valore di EAX a 1; nel secondo punto, lo cambio a 0x0A. Così facendo, gli faccio credere che non è scaduto e che mancano 10 giorni alla scadenza.

A questo punto il gioco è fatto: il programma continua a funzionare.

Ora viene il bello: voglio fare un programma che ripercorra in automatico questi passi tutto da solo.
Se ricordate il tutorial di bender0, sapete già come si crea e gestisce un piccolo debugger. Questa è semplicemente la mia versione, spero con qualche dettaglio in più.

3. I mattoni.
Ho cominciato con la scrittura di una serie di funzioni da richiamare al momento opportuno. Nota che tutte queste funzioni ritornano "false" se c'è stato qualche errore; altrimenti, ritornano "true".
Questa funzione si preoccupa di impostare un breakpoint di esecuzione in un dato indirizzo; in pratica sostituisce il byte presente con 0xCC.
Ho inserito anche un controllo preventivo sul byte che si va a sostituire: dopo aver analizzato il programma con Olly, ovviamente so quale istruzione c'è lì e conosco il contenuto di quella locazione di memoria; se il byte letto nel punto in cui si sta per scrivere 0xCC non corrisponde con quello che ci si aspetta, la funzione ritorna un errore. Questo controllo può essere utile per capire se stiamo settando, per errore, un break su una parte di memoria ancora non decrittata. L'ho messo perché mi è capitato più volte.

bool SetBreakpoint(PROCESS_INFORMATION &pi,
                              int address, unsigned char expectedByte)
{
    unsigned char charBuffer;
    if (!ReadProcessMemory(pi.hProcess, (void*)address, &charBuffer, 1, NULL))
    {
       
return false;
    }
    if (charBuffer!=expectedByte)
    {
        return false;
    }
    charBuffer = 0xCC;
    if (!WriteProcessMemory(pi.hProcess, (void*)address, &charBuffer, 1, NULL))
    {
        return false;
    }
    return true;
}

Questa funzione modifica il registro EIP di un processo; in pratica modifica il punto da cui continuerà il processo, una volta che riprenderà la sua esecuzione. Io l'ho trovata utile per la prossima funzione: "ClearBreakpoint". Il parametro "delta" indica di quanti bytes si deve spostare l'IP.
bool AdjustInstructionPointer(HANDLE hThread, int delta)
{
    CONTEXT context;
    context.ContextFlags = 0x1003F; // CONTEXT_ALL
    if (!GetThreadContext(hThread,&context))
    {
        return false;
    }
    context.Eip+=delta;
    if (!SetThreadContext(hThread,&context))
    {
        return false;
    }
    return true;
Questa funzione ripristina il byte originale lì dove si era impostato un breakpoint. Inoltre controlla che effettivamente in quel punto si trovi un 0xCC e quindi che non sia stato comesso un errore o che sia stata fatta una modifica indesiderata. Infine indietreggia di un byte l'EIP per riportarlo all'inizio dell'istruzione ancora da eseguire.
bool ClearBreakpoint(PROCESS_INFORMATION &pi,
                              int address, unsigned char originalByte)
{
    unsigned char charBuffer;
    if (!ReadProcessMemory(pi.hProcess, (void*)address, &charBuffer, 1, NULL))
    {
        return false;
    }
    if (charBuffer!=0xCC)
    {
        return false;
    }
    charBuffer = originalByte;
    if (!WriteProcessMemory(pi.hProcess, (void*)address, &charBuffer, 1, NULL))
    {
        return false;
    }
    return AdjustInstructionPointer(pi.hThread, -1);
}
Questa funzione mi è stata utile per far correre il debuggee fino a quando non arrivava ad un dato breakpoint o ad una eccezione interessante; la funzione blocca il debuggee se l'indirizzo al quale si è verificata l'eccezione è uguale a quello passato nel secondo parametro "addressExpected".
bool WaitForEventAtAddress(DEBUG_EVENT &de,int addressExpected, DWORD &dwContinueStatus)
{
    int address;
    bool boolDebugging = true;

    while (boolDebugging)
    {
        dwContinueStatus    = DBG_CONTINUE;
        if (!WaitForDebugEvent(&de,INFINITE))
        {
            return false;
        }
        if (de.dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
        {
            dwContinueStatus =    DBG_EXCEPTION_NOT_HANDLED;
            address = (DWORD)de.u.Exception.ExceptionRecord.ExceptionAddress;
            if ((de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT) ||
                (de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP))
            {
                dwContinueStatus =    DBG_CONTINUE;
            }
        }
        if (address == addressExpected)
        {
            boolDebugging = false;
        }
        else
            ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
    }
    return true;
}
Questa funzione mi è servita per far correre il debuggee fino al verificarsi di un eccezione del tipo SINGLE STEP. Questa è anche l'eccezione che si verifica quando viene attivato un hardware breakpoint.
bool WaitForSingleStep(DEBUG_EVENT &de, DWORD &dwContinueStatus)
{
    int address;
    bool boolDebugging = true;

    while (boolDebugging)
    {
        dwContinueStatus    = DBG_CONTINUE;
        if (!WaitForDebugEvent(&de,INFINITE))
        {
            return false;
        }
        if (de.dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
        {
            dwContinueStatus =    DBG_EXCEPTION_NOT_HANDLED;
            address = (DWORD)de.u.Exception.ExceptionRecord.ExceptionAddress;
            if ((de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT) ||
                (de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP))
            {
                dwContinueStatus =    DBG_CONTINUE;
            }
            if (de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP)
            {
                return true;
            }
        }
        ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
    }
    return true;
}
Su questo argomento forse è il caso di fare una breve introduzione.
Il nostro caro processore Intel, ha 4 registri in cui scrivere l'indirizzo di un hardware breakpoint: DR0, DR1, DR2 e DR3.
Poi, per la loro attivazione, si deve opportunamente modificare anche il registro DR7. Infatti, i bit 0,2,4 e 6, se uguali a 1, abilitano il breakpoint all'indirizzo contenuto, rispettivamente, in DR0,1,2 e 3.
Inoltre, le coppie di bit 16-17, 20-21, 24-25, 28-29, indicano il tipo di breakpoint (sempre in DR0,1,2 e 3) secondo questa tabella:
00 - break sull'esecuzione
01 - break sulla scrittura
10 - non definito
11 - break in lettura o scrittura
Nel mio esempio ne ho utilizzato uno del 4° tipo; al verificarsi dell'evento di lettura/scrittura, viene sollevata un'eccezione di tipo SINGLE STEP.
Se vuoi approfondire, ti rimando al capitolo 15 di "IA-32 Intel ® Architecture Software Developer’s Manual - Volume 3 : System Programming Guide".
Quindi, quando mi è servito un hardware breakpoint ho fatto così: (nota che "context" è una variabile di tipo CONTEXT)
    ....
    // impostare un hdw brk sulla lettura/scrittura di 0x00412049
    context.ContextFlags =  0x1003F; //CONTEXT_ALL;
    if (!GetThreadContext(pi.hThread,&context))
    {
        return;
    }
    context.Dr0 = 0x00412049;
    context.Dr7    = context.Dr7 | 0x30001;    // bits 0,16 e 17 = 1
    if (!SetThreadContext(pi.hThread,&context))
    {
        return;
    }
    ....
    // rimuovere l'hdw brk
    context.ContextFlags =  0x1003F; //CONTEXT_ALL;
    if (!GetThreadContext(pi.hThread,&context))
    {
        return;
    }
    context.Dr0 = 0;
    context.Dr7    = context.Dr7 & ( ~(0x30000));  // azzero i bit 16 e 17
    if (!SetThreadContext(pi.hThread,&context))
    {
        return;
    }
    ....

4. Il gran finale.
Ormai abbiamo tutto il necessario e possiamo muovere il debuggee come una marionetta grazie al nostro debugger (puppeteer?)
Infatti, i passaggi da a) ad e) che hai visto al paragrafo 2, possono essere facilmente tradotti in una serie di chiamate a queste funzioni.
Il passaggio d), in realtà, sembra non funzionare come in Olly: cioè, se con il mio puppeteer imposto un hardware breakpoint all'indirizzo 0x00412049, questo non dà nessun effetto. Probabilmente, la nostra marionetta va a mettere le mani nei registri relativi agli hardware breakpoint; mi chiedo perché con Olly invece funziona; forse, Olly monitora queste modifiche e, a sua volta, prova a ripristinare gli hardware breakpoint.
Comunque ho trovato una soluzione: fra le tante eccezioni lanciate da Avisplitter/Armadillo, ne ho identificata una, precedente alla decrittazione del codice in 0x00412049, ma evidentemente successiva ai controlli sugli hardware breakpoint (è l'unica eccezione di tipo SINGLE STEP); quindi ho semplicemente aspettato il verificarsi di questa eccezione e da lì, ho proseguito come nel punto d).
Qui di seguito puoi trovare il listato completo del mio pseudo-debugger; i passaggi  da a) ad e), sono stati tradotti nei seguenti 19 mini-step. Nel codice ci sono anche dei brevi commenti.
Ricorda che, quando l'esecuzione raggiunge un breakpoint, per continuare, bisogna ripristinare l'istruzione vera che era presente in precedenza.

    1. Esegui il programma fino al verificarsi dell'eccezione all'indirizzo 0x00A68006
    2. Imposta un breakpoint all'indirizzo 0x00A7D8BC dove ci dovrebbe essere il valore 0x8B
    3. Fai continuare il programma.
    4. Attendi fino al breakpoint appena impostato.
    5. Rimuovi il breakpoint (include anche lo spostamento all'indietro di un byte dell'Instruction Pointer del processo)
    6. Imposta un breakpoint all'indirizzo 0x00A7DAA5
    7. Fai continuare il programma.
    8.
Attendi fino al breakpoint appena impostato.
    9. Forza ad uno il bit Z nel registro dei flag. Rimuovi il breakpoint. Imposta un nuovo break all'indirizzo 0x00A7DB0F e continua.
    10.
Attendi fino al breakpoint appena impostato.
    11. Rimuovi il break e reimpostane uno a
0x00A7DAA5.
    12.
Attendi fino al breakpoint appena impostato.
    13.
Forza ad uno il bit Z nel registro dei flag. Rimuovi il breakpoint. Imposta un nuovo break all'indirizzo 0x00A7DB0F e continua.
    14.
Attendi fino al breakpoint appena impostato.
    15. Rimuovi il breakpoint e continua fino al verificarsi di un'eccezione SINGLE STEP.
    16. Imposta un Hardware Breakpoint sulla lettura/scrittura dell'indirizzo 0x00412049
    17. Attendi l'eccezione SINGLE STEP (generata dall'Hardware Breakpoint). Rimuovi il break. Imposta due nuovi breakpoint "normali" agli indirizzi 0x00410D2A e 0x00412049.
    18. Attendi fino al primo break. Poi, rimuovilo, forza il registro EAX del processo ad 1 e continua.
    19.
Attendi fino al successivo break. Poi, rimuovilo, forza il registro EAX del processo a 0x0A (giorni dalla scadenza) e continua.
#include "stdafx.h"
#include <windows.h>


bool SetBreakpoint(PROCESS_INFORMATION &pi,
                              int address, unsigned char expectedByte)
{
    unsigned char charBuffer;
    if (!ReadProcessMemory(pi.hProcess, (void*)address, &charBuffer, 1, NULL))
    {
        return false;
    }
    if (charBuffer!=expectedByte)
    {
        return false;
    }
    charBuffer = 0xCC;
    if (!WriteProcessMemory(pi.hProcess, (void*)address, &charBuffer, 1, NULL))
    {
        return false;
    }
    return true;
}

bool AdjustInstructionPointer(HANDLE hThread, int delta)
{
    CONTEXT context;
    context.ContextFlags = 0x1003F; // CONTEXT_ALL
    if (!GetThreadContext(hThread,&context))
    {
        return false;
    }
    context.Eip+=delta;
    if (!SetThreadContext(hThread,&context))
    {
        return false;
    }
    return true;
}

bool ClearBreakpoint(PROCESS_INFORMATION &pi,
                              int address, unsigned char originalByte)
{
    unsigned char charBuffer;
    if (!ReadProcessMemory(pi.hProcess, (void*)address, &charBuffer, 1, NULL))
    {
        return false;
    }
    if (charBuffer!=0xCC)
    {
        return false;
    }
    charBuffer = originalByte;
    if (!WriteProcessMemory(pi.hProcess, (void*)address, &charBuffer, 1, NULL))
    {
        return false;
    }
    return AdjustInstructionPointer(pi.hThread, -1);
}

bool WaitForEventAtAddress(DEBUG_EVENT &de,int addressExpected, DWORD &dwContinueStatus)
{
    int address;
    bool boolDebugging = true;

    while (boolDebugging)
    {
        dwContinueStatus    = DBG_CONTINUE;
        if (!WaitForDebugEvent(&de,INFINITE))
        {
            return false;
        }
        if (de.dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
        {
            dwContinueStatus =    DBG_EXCEPTION_NOT_HANDLED;
            address = (DWORD)de.u.Exception.ExceptionRecord.ExceptionAddress;
            if ((de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT) ||
                (de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP))
            {
                dwContinueStatus =    DBG_CONTINUE;
            }
        }
        if (address == addressExpected)
        {
            boolDebugging = false;
        }
        else
            ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
    }
    return true;
}

bool WaitForSingleStep(DEBUG_EVENT &de, DWORD &dwContinueStatus)
{
    int address;
    bool boolDebugging = true;

    while (boolDebugging)
    {
        dwContinueStatus    = DBG_CONTINUE;
        if (!WaitForDebugEvent(&de,INFINITE))
        {
            return false;
        }
        if (de.dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
        {
            dwContinueStatus =    DBG_EXCEPTION_NOT_HANDLED;
            address = (DWORD)de.u.Exception.ExceptionRecord.ExceptionAddress;
            if ((de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT) ||
                (de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP))
            {
                dwContinueStatus =    DBG_CONTINUE;
            }
            if (de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP)
            {
                return true;
            }
        }
        ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
    }
    return true;
}

bool WaitForExitProcess(DEBUG_EVENT &de, DWORD &dwContinueStatus)
{
    int address;
    bool boolDebugging = true;

    while (boolDebugging)
    {
        dwContinueStatus    = DBG_CONTINUE;
        if (!WaitForDebugEvent(&de,INFINITE))
        {
            return false;
        }
        if (de.dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
        {
            dwContinueStatus =    DBG_EXCEPTION_NOT_HANDLED;
            address = (DWORD)de.u.Exception.ExceptionRecord.ExceptionAddress;
            if ((de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT) ||
                (de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP))
            {
                dwContinueStatus =    DBG_CONTINUE;
            }
        }
        else if (de.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)
            return true;
        ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
    }
    return true;
}

void DecodeException(EXCEPTION_RECORD &er,DWORD &dwContinueStatus)
{
    dwContinueStatus =    DBG_EXCEPTION_NOT_HANDLED;
    switch(er.ExceptionCode)
    {
    case    EXCEPTION_BREAKPOINT:
        dwContinueStatus =    DBG_CONTINUE;
        break;
    case    EXCEPTION_SINGLE_STEP:
        dwContinueStatus =    DBG_CONTINUE;
        break;
    }
}

void ContinueExecution()
{
    bool boolDebugging = true;
    DEBUG_EVENT    de;
    DWORD    dwContinueStatus;

    while (boolDebugging)
    {
        dwContinueStatus    = DBG_CONTINUE;
        if (!WaitForDebugEvent(&de,INFINITE))
        {
            boolDebugging = false;
            return;
        }
        switch (de.dwDebugEventCode)
        {
        case EXCEPTION_DEBUG_EVENT:
            DecodeException(de.u.Exception.ExceptionRecord,dwContinueStatus);
            break;
        default:
            dwContinueStatus    = DBG_CONTINUE;
        }
    ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
    }
}

int main(int argc, char* argv[])
{
    //launch target
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    CONTEXT    context;
    
    ZeroMemory( &si, sizeof(si) );
    si.cb = sizeof(si);
    ZeroMemory( &pi, sizeof(pi) );

    if( !CreateProcess( "VideoSplitter.exe",
        NULL,
        NULL,
        NULL,
        FALSE,
        NORMAL_PRIORITY_CLASS | DEBUG_PROCESS
        NULL,
        NULL,
        &si,
        &pi )
    )
    {
        return -1;
    }

    DEBUG_EVENT    de;
    DWORD    dwContinueStatus;
    DWORD    address = 0;
    int        baseAddress;
    
    /*
    1 run until
    Debug event: EXCEPTION_DEBUG_EVENT
    at address:A68006
    */
    if (!WaitForEventAtAddress(de,0xA68006,dwContinueStatus))
        return -1;
    
    /*
    2 bp at
    00A7D8BC   8B0D 88CFA900    MOV ECX,DWORD PTR DS:[A9CF88]
    (replace 8B with CC at 00A7D8BC)
    */
    baseAddress    = 0x00A7D8BC;
    if (!SetBreakpoint(pi, baseAddress, 0x8B))
        return -1;
    
    // 3 continue
    ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);


    /*
    4 wait for bp
    Debug event: EXCEPTION_DEBUG_EVENT
    at address:    00A7D8BC
    */
    if (!WaitForEventAtAddress(de,0x00A7D8BC,dwContinueStatus))
        return -1;

    /*
    5 clear breakpoint and do EIP = EIP - 1
    (restore 8B at 00A7D8BC)
    */
    baseAddress    = 0x00A7D8BC;
    if (!ClearBreakpoint(pi,baseAddress,0x8B))
        return -1;

    /*
    6 bp at
    00A7DAA5   74 68            JE SHORT 00A7DB0F
    (replace 74 with CC at 00A7DAA5)
    */
    baseAddress    = 0x00A7DAA5;
    if (!SetBreakpoint(pi, baseAddress, 0x74))
        return -1;


    // 7 continue
    ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);

    /*
    8 wait for bp
    Debug event: EXCEPTION_DEBUG_EVENT
    at address:    00A7DAA5
    */
    if (!WaitForEventAtAddress(de,0x00A7DAA5,dwContinueStatus))
        return -1;

    /* 9
      adjust Z flag
     
clear breakpoint;
     
set breakpoint at the next instruction :
      00A7DAA7   385D 08          CMP BYTE PTR SS:[EBP+8],BL
      continue
      */
    context.ContextFlags =  0x1003F; //CONTEXT_ALL;
    if (!GetThreadContext(pi.hThread,&context))
    {
        return -1;
    }
    context.EFlags = context.EFlags | 0x40; // 1000000
    if (!SetThreadContext(pi.hThread,&context))
    {
        return -1;
    }
    baseAddress    = 0x00A7DAA5;
    if (!ClearBreakpoint(pi,baseAddress,0x74))
        return -1;
    baseAddress    = 0x00A7DB0F;
    if (!SetBreakpoint(pi, baseAddress, 0x51))
        return -1;
    ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);

    /*
    10 wait for bp
    Debug event: EXCEPTION_DEBUG_EVENT
    at address:    00A7DB0F
    */
    if (!WaitForEventAtAddress(de,0x00A7DB0F,dwContinueStatus))
        return -1;

    /*
    11  clear bp
        set bp at
        00A7DAA5   74 68            JE SHORT 00A7DB0F
        (replace 74 with CC at 00A7DAA5)
        continue
    */
    baseAddress    = 0x00A7DB0F;
    if (!ClearBreakpoint(pi,baseAddress,0x51))
        return -1;
    baseAddress    = 0x00A7DAA5;
    if (!SetBreakpoint(pi, baseAddress, 0x74))
        return -1;
    ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);

    /*
    12 wait for bp
    Debug event: EXCEPTION_DEBUG_EVENT
    at address:    00A7DAA5
    */
    if (!WaitForEventAtAddress(de,0x00A7DAA5,dwContinueStatus))
        return -1;

    /* 13
      adjust Z flag
     
clear breakpoint;
     
set breakpoint at the next instruction
      continue
      */
    context.ContextFlags =  0x1003F; //CONTEXT_ALL;
    if (!GetThreadContext(pi.hThread,&context))
    {
        return -1;
    }
    context.EFlags = context.EFlags | 0x40; // 1000000
    if (!SetThreadContext(pi.hThread,&context))
    {
        return -1;
    }
    baseAddress    = 0x00A7DAA5;
    if (!ClearBreakpoint(pi,baseAddress,0x74))
        return -1;
    baseAddress    = 0x00A7DB0F;
    if (!SetBreakpoint(pi, baseAddress, 0x51))
        return -1;
    ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);

    /*
    14 wait for bp
    Debug event: EXCEPTION_DEBUG_EVENT
    at address:    00A7DB0F
    */
    if (!WaitForEventAtAddress(de,0x00A7DB0F,dwContinueStatus))
        return -1;

    // 15 clear bp, continue, wait for a single step
    baseAddress    = 0x00A7DB0F;
    if (!ClearBreakpoint(pi,baseAddress,0x51))
        return -1;
    ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
    WaitForSingleStep(de, dwContinueStatus);


    //16 SET HDW BRKP & continue
    context.ContextFlags =  0x1003F; //CONTEXT_ALL;
    if (!GetThreadContext(pi.hThread,&context))
    {
        return -1;
    }
    context.Dr0 = 0x00412049;
    context.Dr7    = context.Dr7 | 0x30001;
    if (!SetThreadContext(pi.hThread,&context))
    {
        return -1;
    }
    ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);

    // 17 wait for a single step, clear hdw brkp,
    // set bp at 00410D2A and 00412049 & continue
    WaitForSingleStep(de, dwContinueStatus);
    context.ContextFlags =  0x1003F; //CONTEXT_ALL;
    if (!GetThreadContext(pi.hThread,&context))
    {
        return -1;
    }
    context.Dr0 = 0;
    context.Dr7    = context.Dr7 & ( ~(0x30000));
    if (!SetThreadContext(pi.hThread,&context))
    {
        return -1;
    }
    baseAddress    = 0x00410D2A;
    if (!SetBreakpoint(pi, baseAddress, 0x85))
        return -1;
    baseAddress    = 0x00412049;
    if (!SetBreakpoint(pi, baseAddress, 0x50))
        return -1;
    ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);

    // 18 wait for break at 0x00410D2A, clear bp, eax = 1
    if (!WaitForEventAtAddress(de,0x00410D2A,dwContinueStatus))
        return -1;
    baseAddress    = 0x00410D2A;
    if (!ClearBreakpoint(pi,baseAddress,0x85))
        return -1;
    context.ContextFlags =  0x1003F; //CONTEXT_ALL;
    if (!GetThreadContext(pi.hThread,&context))
    {
        return -1;
    }
    context.Eax = 1;
    if (!SetThreadContext(pi.hThread,&context))
    {
        return -1;
    }
    ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);

    // 19 wait for break at 0x00412049, clear bp, eax = 0x0A
    if (!WaitForEventAtAddress(de,0x00412049,dwContinueStatus))
        return -1;
    baseAddress    = 0x00412049;
    if (!ClearBreakpoint(pi,baseAddress,0x50))
        return -1;
    context.ContextFlags =  0x1003F; //CONTEXT_ALL;
    if (!GetThreadContext(pi.hThread,&context))
    {
        return -1;
    }
    context.Eax = 0x0a;
    if (!SetThreadContext(pi.hThread,&context))
    {
        return -1;
    }
    ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
    

    WaitForExitProcess(de, dwContinueStatus);

    return 0;
}

.

                                                                                                                 faina

Note finali

Se non è chiaro, posso essere eventualmente contattato via PrivateMessage sulla UIC dove il mio nick è faina_mdc. Saranno accettati suggerimenti, domande, ecc. ecc.
Un ringraziamento ancora a bender0 e uno anche al padrone di casa Quequero.
Alla prossima.... marionetta. 8-))

Disclaimer

Vorrei ricordare che il software va comprato e  non rubato, dovete registrare il vostro prodotto dopo il periodo di valutazione. Non mi ritengo responsabile per eventuali danni causati al vostro computer determinati dall'uso improprio di questo tutorial. Questo documento è stato scritto per invogliare il consumatore a registrare legalmente i propri programmi, e non a fargli fare uso dei tantissimi file crack presenti in rete, infatti tale documento aiuta a comprendere lo sforzo che ogni sviluppatore ha dovuto portare avanti per fornire ai rispettivi consumatori i migliori prodotti possibili.

Reversiamo al solo scopo informativo e per migliorare la nostra conoscenza del linguaggio Assembly.