Avisplitter 3.01 + Armadillo 3.75a
(uno sguardo ad Armadillo + debugging patching)

Data

by faina

 

6/10/2004

UIC's Home Page

Published by Quequero

First law of Robotics: "A robot may not injure a human being or, ...

Altro tute assolutamente degno di nota, bravo faina, complimenti.

... trough inaction, allow a human being to come to harm" I. Asimov

....

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

....

Difficoltà

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

 
 

Introduzione

Benvenuto. In questo tutorial saranno trattati 2 argomenti:
- descrizione di alcune tecniche usate da Armadillo
- realizzazione di un mini debugger / patcher che aggiri la protezione

Se ti interessa il primo, dovrai conoscere un pò di architettura di Windows e avere un'infarinatura su come funzionano i debugger.
Se ti interessa il secondo, dovresti conoscere anche le API di windows per il debugging. A questo proposito, ti consiglio un tutorial molto chiaro su questo tema, che trovi sempre sulla UIC: "Clone DVD 2.5.0.0" di bender0 che saluto. Puoi trovare anche un altro esempio sull'applicazione di questi concetti, in un altro mio tutorial: "Scrivere un debugger/patcher per crackare AVI/MPEG/ASF/WMV Splitter 3.22", sempre qui sulla UIC; lì potrai trovare un esempio un pò più semplice di quello che hai davanti, in quanto la protezione adottata lì, pur essendo sempre Armadillo 3.75a, è meno ostica.

L'oggetto sarà ancora il programma AVI/MPEG/ASF/WMV Splitter della Boilsoft, che abbrevieremo in Avisplitter, nella versione 3.01.
Attualmente sul loro sito c'è una versione successiva che, però, usa solo una parte delle protezioni offerte da Armadillo e che erano invece usate precedentemente dalla 3.01. Quindi, se non trovi in giro una copia del pacchetto di istallazione della 3.01, mandami un P.M. sulla UIC.

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

Vedi l'introduzione.

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".
Se lo trovate utile, una volta usato per questo esercizio, disinstallatelo e, se non lo avete già, scaricatevi VirtualDub: potente, open source e senza schifezze inutili che vi sporcano il registro di Windows.

Essay


1. Saltellando in Armadillo

Ecco alcune delle tecniche di protezione che ho incontrato in questo programma.

1.1 "Autodebug". Parte prima: chi fa debugging e chi lo subisce.
Quando lanciamo l'eseguibile, questo, a sua volta, esegue una seconda copia di sè stesso in modalità "debugging"; cioè, la prima copia fa la parte del debugger, mentre, la seconda, quella del programma debuggato. Sarà, poi, questa seconda quella "vera", cioè visibile all'utente. L'altra rimane nascosta e visibile solo nell'elenco dei processi. Infatti, se apri TaskManager, puoi vedere due copie di "Videosplitter.exe" girare contemporaneamente.
Tu dirai: "Un bell'esempio di efficienza!". Infatti, la memoria viene occupata da due copie dello stesso programma di cui, però, solo una viene utilizzata dall'utente. Non ti lamentare; in fondo il programma è piccolo e tu, ormai, hai centinaia di MB di RAM da riempire! ;))
Lo scopo, chiaramente, è quello di rendere più difficile la vita ai cracker. Infatti, se un processo è già sotto "debugging", il nostro fido Olly non riesce ad assumerne il controllo. Inoltre, almeno fino a windows 2000, non era possibile "sganciare" un processo dal suo debugger senza chiuderlo; leggo in giro, che con Windows XP è invece possibile (se ti interessa questo, cerca il tutorial di un certo Mephisto).
Insomma, la mia situazione era questa: ho Windows 2000 e la mia arma preferita, Olly, non può attaccare. Che faccio?
Mi sono chiesto: "Come fa uno stesso eseguibile a comportarsi in due modi diversi, a seconda di chi lo lancia?". Infatti, quando lo lancio io, si comporta da debugger; quando, poi, viene lanciato dal "suo" debugger, si comporta da Avisplitter vero e proprio.
Per farla breve, dopo alcune ipotesi, ho scoperto che il programma utilizza i cosiddetti "Mutex". I mutex sono degli oggetti, forniti dal sistema operativo, utilizzati per la sincronizzazione dei processi; tra le altre, hanno la caratteristica che non possono esistere due mutex con lo stesso nome. La API per creare un mutex è CreateMutex, mentre quella per verificarne l'esistenza è OpenMutex.
Quando Avisplitter parte, verifica se già esiste, in Windows, un "suo" mutex. Se non esiste, crea il mutex e si comporta da debugger lanciando una seconda copia di sè. Se invece esiste, si comporta da programma debuggato.
Quindi, se noi lanciamo con Olly il nostro Avisplitter, e gli facciamo credere che il mutex esista, lui si comporterà da Avisplitter/Debuggato e lo potremo finalmente analizzare!

1.2 "Autodebug". Parte seconda: comunicano tra loro!
Una volta partito, Avisplitter\Debuggato continua a girare per conto suo, ma, di tanto in tanto, all'interno del suo codice, incontra degli INT3. Come sai, questi sono i breakpoint che i debugger impostano nel codice dei programmi che stanno debuggando; nel nostro caso, succede che Avisplitter\Debuggato si ferma, e Avisplitter\Debugger prende il controllo in quanto, appunto, il "programma che sta debuggando" ha incontrato un breakpoint.
Che cosa fa, poi? Semplicemente, cambia l'indirizzo contenuto nell'EIP e restituisce il controllo a
Avisplitter\Debuggato.
In pratica, lo fa continuare da un altro punto.
L'idea di Armadillo è cercare di fare in modo che il programma non possa funzionare senza il suo "debugger" attivo in memoria, in quanto non riesce a continuare da solo dopo un INT 3.
Ma anche qui interverremo noi...

1.3 Le altre protezioni.
Ci sono poi altri dettagli che copio e incollo dal mio precedente tutorial. Se lo hai già letto, puoi saltare questa parte:

- gli eventuali hardware breakpoint, in vari punti vengono rimossi
- 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;

- all'inizio il programma è quasi tutto criptato in memoria; 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.

2. L'obiettivo.
Voglio creare un programma che lanci Avisplitter e lo faccia funzionare in modalità "debuggato", convincendolo che il suo debugger sia presente e operativo. Dovranno poi essere saltate le eccezioni e i controlli sulla scadenza del periodo di prova, nonostante le sezioni criptate e i controlli.
Questo programma dovrà ripetere tutto quello che ho fatto con OllyDebug per far girare correttamente Avisplitter. Assomiglierà, quindi, ad un debugger e muoverà il target come una marionetta: lo battezzeremo quindi "Puppeteer".
Elenco qui i passi da seguire con Olly per raggiungere l'obiettivo; premetto che bisogna passare al programma in esecuzione tutte le eccezioni che si presentano (ACCESS VIOLATION, ILLEGAL INSTRUCTION e INVALID LOCK SEQUENCE).
Suddividerò i passi in due gruppi: quelli da seguire all'avvio e quelli necessari durante l'uso.
2.1 L'avvio del programma.
a) impostare un memory breakpoint sull'accesso a 004DC25D; attenzione: non deve essere un hardware breakpoint, altrimenti non funzionerà;
b) attendere finché l'esecuzione non arriva in quel punto; l'ho scelto perché lì, la porzione successiva di programma sarà stata appena decriptata, ma non ancora eseguita;
c) impostare 2 breakpoints su 004c80bd e 004c84ad; in quei punti, il controllo sull'esistenza del "mutex" è appena finito; bisogna, quindi, assegnare al registro EAX il valore 1, che significa "mutex esistente";
d) far girare il programma fino all'eccezione all'indirizzo 00A58006;ho scelto questa perché, a quel punto, sono sicuro che la sezione successiva è stata decriptata, ma ancora non è stata eseguita;
e) impostare un bp all'indirizzo 00A6D8BC e continuare;
f) rimuovere il precedente e impostare un nuovo bp a 00A6DAA5; continuare l'esecuzione;
g) quando l'esecuzione arriva a quel punto, è stato appena controllato l'orologio del sistema; se il flag Z è 0, il controllo è fallito e bisogna settarlo a 1; questo controllo viene fatto 2 volte di seguito;
h) impostare un bp su 0x00a78c8e; quando l'esecuzione arriva lì, è stata appena decriptata la porzione successiva di programma; ora impostare un bp su 0040F210 (uff! resisti, che siamo quasi alla fine);
i) a questo punto, EAX contiene il numero di giorni rimasti dalla scadenza; impostare EAX ad un valore maggiore di 0, come per es. 0x0A.

La prima parte è finita e il programma parte. Nel titolo della finestra, scriverà comunque il numero di giorni rimasti; nel caso i 10 giorni siano scaduti, scriverà "0".
Questo non comporta problemi; infatti, la stringa contenente il numero viene creata prima del punto i); se non ti piace leggere "0 days left", puoi provare, per esercizio, a fare qualche altra piccola "modifica" ;-)
2.2 Check durante l'utilizzo.
La funzione principale del programma Avisplitter è quella di spezzare in due o più parti un file video. Questa operazione viene lanciata tramite il bottone "Split".
E' proprio durante questa operazione che interviene l'ulteriore meccanismo di protezione di cui al paragrafo 1.2. Infatti, OllyDebug riprende il controllo e ci avverte che il target ha raggiunto un breakpoint: ora dobbiamo farlo continuare dal punto giusto. Come vedremo, durante l'operazione di "Split", succederà altre 2 volte.
Per capire come far continuare il nostro programma, bisogna aver già visto come risponde, a questo evento, l'Avisplitter/Debugger: viene modificato il registro EIP così:
al primo INT3, EIP diventa 00414279;
al secondo, EIP diventa 00414279A;
al terzo, infine, EIP diventa 004142ed.

Bene, dovremmo aver finito. Questi sono tutti gli INT3 che ho incontrato. Non so se ve ne sono altri in qualche altro punto, ma sembra che, saltati questi, tutto funzioni correttamente.

3. Puppeteer.
Bene, è ora di scrivere un pò di codice. Creeremo un programmino che lanci Avisplitter in modalità "debugging" e lo manovri così come abbiamo fatto "a mano" con OllyDbg.

3.1 Funzioni fondamentali.
Qui descrivo brevemente tutte le funzioni che ho scritto e che verranno richiamate durante il programma principale.
Tutte restituiscono il valore "true" se vanno a buon fine, "false" se hanno riscontrato qualche errore.
Per non appesantire la lettura, ho riportato qui solo le dichiarazioni delle funzioni; potrai trovare il listato completo più avanti.



 bool SetBreakpoint(PROCESS_INFORMATION &pi, int address, unsigned char expectedByte

Questa funzione imposta un breakpoint all'indirizzo passato nel parametro "address". Lo fa sostituendo il byte presente a quell'indirizzo con il valore 0xCC. Il parametro "expectedByte" deve contentere il valore effettivamente presente a quell'indirizzo: infatti questa funzione controlla, prima di sostituirlo, che effettivamente il valore atteso sia presente. Ho inserito questo controllo perché, con questi programmi automodificanti, tanto di moda oggi, può capitare di commettere errori e tentare di impostare un break su un'istruzione che ancora non è realmente presente in memoria (esperienza personale ;) ).


 bool AdjustInstructionPointer(HANDLE hThread, int delta)

Questa funzione modifica il registro EIP di un processo in esecuzione. L'effetto è che ne modifica il punto da cui continuerà l'esecuzione. Il parametro "delta" indica di quanti bytes si deve spostare l'EIP. L'ho utilizzata nella prossima funzione "ClearBreakpoint".



 bool ClearBreakpoint(PROCESS_INFORMATION &pi,int address, unsigned char originalByte)


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 WaitForEventAtAddress(DEBUG_EVENT &de,int addressExpected, DWORD &dwContinueStatus)


Questa funzione mi è stata utile per far correre il programma debuggato fino a quando non arriva ad un dato breakpoint o ad una eccezione interessante; la funzione blocca il debuggee se l'indirizzo al quale si è verificata il break o l'eccezione è uguale a quello passato nel secondo parametro "addressExpected".



 int WaitForExitOrEventAtAddress(DEBUG_EVENT &de,int addressExpected, DWORD &dwContinueStatus)


Questa funzione è simile alla precedente, ma, in più, distingue il tipo di evento che si è verificato:
- restituisce 1 se si è verificata un'eccezione (o un break) all'indirizzo "addressExpected"
- restituisce 2 se, invece, si è verificata l'eccezione EXIT_PROCESS_DEBUG_EVENT
Mi è servita perché, una volta avviato Avisplitter, si possono verificare due eventi:
- l'utente dà il comando di "Split" nel qual caso si incontrano dei breakpoints, come descritto più sopra
- l'utente chiude il programma, e in questo caso si verifica l'eccezione EXIT_PROCESS_DEBUG_EVENT

A seconda dell'evento, Puppeteer deve rispondere, rispettivamente:
- modificando l'EIP di Avisplitter come descritto sopra
- chiudendosi.




 bool SoftwareBreakpointAt(PROCESS_INFORMATION &pi, int baseAddress,
                                     unsigned char byteExpected, DEBUG_EVENT &de,
                                     DWORD &dwContinueStatus)

Questa funzione richiama in sequenza SetBreakpoint, WaitForEventAtAddress e ClearBreakpoint.
In pratica, imposta un breakpoint, attende che l'esecuzione lo raggiunga e, infine, lo rimuove. L'ho voluta per rendere il programma più snello e chiaro.




 bool WaitForSingleStep(DEBUG_EVENT &de, DWORD &dwContinueStatus)

Questa funzione attende semplicemente che il programma in esecuzione raggiunga un'eccezione di tipo SINGLE STEP. Questa eccezione può essere utile se si imposta un hardware breakpoint, che stavolta non ho usato, oppure se si attiva il flag Trap del registro dei flag. Quando questo flag è attivato, il sistema operativo genera, ad ogni istruzione eseguita dal programma debuggato, un'eccezione di tipo SINGLE STEP che può essere intercettata dal debugger.




 bool WaitForExitProcess(DEBUG_EVENT &de, DWORD &dwContinueStatus)

Questa funzione fa girare il programma debuggato finchè non si verifica l'eccezione che indica che il programma è terminato.





 bool SetEaxValue(PROCESS_INFORMATION &pi, DWORD newValue)

Con questa funzione possiamo cambiare il valore del registro EAX del processo debuggato.





 bool SetEipValue(PROCESS_INFORMATION &pi, DWORD newValue)

Con questa funzione possiamo cambiare il valore del registro EIP del processo debuggato cambiando, quindi, il flusso di esecuzione.





 bool SetZFlag(PROCESS_INFORMATION &pi)

Questa funzione forza ad 1 il valore del flag Z, nel registro dei flag, per il processo in esecuzione.





 bool ClearZFlag(PROCESS_INFORMATION &pi)

Questa funzione forza a 0 il valore del flag Z, nel registro dei flag, per il processo in esecuzione.





 bool SetTrapFlag(PROCESS_INFORMATION &pi)

Questa funzione forza a 1 il valore del flag Trap, nel registro dei flag, per il processo in esecuzione. In questo modo si attiva la modalità di esecuzione "SINGLE STEP" (vedi anche la funzione "WaitForSingleStep").





 bool ClearTrapFlag(PROCESS_INFORMATION &pi)

Questa funzione forza a 0 il valore del flag Trap, nel registro dei flag, per il processo in esecuzione.


3.2 La fase finale.
Rimane, ora, da tradurre tutto quello che abbiamo fatto al paragrafo 2, con Olly, in una successione di queste funzioni.
Per adesso non è disponibile una funzione che imposti un "memory breakpoint" come abbiamo fatto con Olly al passo a); otteniamo, però, lo stesso risultato così:
- attendiamo il verificarsi dell'eccezione all'indirizzo 0x004E7AE7
- usiamo un breakpoint all'indirizzo 0x004E86DB
I passi da seguire diventano:
    1. Attendi il verificarsi dell'eccezione all'indirizzo 0x004E7AE7
.
    2. Imposta un breakpoint all'indirizzo 0x004E86DB dove dovrebbe esserci il valore 0xFF, attendi che l'esecuzione arrivi lì e, poi, rimuovilo.
    3. Imposta 2 breakpoint, uno su 0x004C80BD e l'altro su 0x004c84ad; in entrambe le locazioni c'è il valore 0x85.
    4. Quando l'esecuzione arriva al primo break, rimuovilo e setta EAX a 1.
    5. Fai lo stesso per il secondo.
    6. Attendi che si verifichi l'eccezione all'indirizzo 0x00A58006.
    7. Imposta un breakpoint all'indirizzo 0x00A6D8BC dove dovrebbe esserci il valore 0x8B, attendi che l'esecuzione arrivi lì e, poi, rimuovilo.
    8. Imposta un breakpoint all'indirizzo 0x00A6DAA5 dove dovrebbe esserci il valore 0x74, attendi che l'esecuzione arrivi lì e, poi, rimuovilo.
    9. Setta il flag Z a 1.
    10. Abilita il flag Trap e continua.
    11. Attendi il SINGLE STEP event.
    12 Imposta un breakpoint all'indirizzo 0x00A6DAA5 dove dovrebbe esserci il valore 0x74, attendi che l'esecuzione arrivi lì e, poi, rimuovilo.
    13. Setta il flag Z a 1.
    14. Abilita il flag Trap e continua.
    15. Attendi il SINGLE STEP event.
    16. Imposta un breakpoint all'indirizzo 0x00A78C8E dove dovrebbe esserci il valore 0xFF, attendi che l'esecuzione arrivi lì e, poi, rimuovilo.
    17. Imposta un breakpoint all'indirizzo 0x0040F210 dove dovrebbe esserci il valore 0x83, attendi che l'esecuzione arrivi lì e, poi, rimuovilo.
    18. Imposta il valore di EAX ad un valore maggiore di 0, per es. 0x0A. Poi, continua.

A questo punto inizia un ciclo.
    19. Attendi finchè non si verifica un'eccezione all'indirizzo 0x00414274, oppure un'eccezione di tipo EXIT_PROCESS_DEBUG_EVENT.
    20. Nel primo caso, cambia il valore di EIP a 0x00414279. Nel secondo, esci.
    21. Attendi un'eccezione all'indirizzo 0x00414298.
    22. Cambia il valore di EIP a 0x0041429A
    23. Attendi l'eccezione all'indirizzo 0x004142e8.
    24. Cambia il valore di EIP a 0x004142ED. Poi, continua e ricomincia il ciclo dal punto 19.

Bene, abbiamo finito. Questo è il listato corrispondente che dovresti poter compilare direttamente così come lo vedi, ed eseguire: buon divertimento.


// this patcher is for Avisplitter 3.01

#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;
}

int WaitForExitOrEventAtAddress(DEBUG_EVENT &de,int addressExpected, DWORD &dwContinueStatus)
{
    /*
        returns
            0 on error
            1 if execution reaches addressExpected
            2 if EXIT_PROCESS_DEBUG_EVENT is signaled
    */
    int address;
    bool boolDebugging = true;

    while (boolDebugging)
    {
        dwContinueStatus    = DBG_CONTINUE;
        if (!WaitForDebugEvent(&de,INFINITE))
        {
            return 0;
        }
        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)
        {
            return 1;
        }
    else if (de.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)
        return 2;
        else
            ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
    }
    return 0;
}

bool SoftwareBreakpointAt(PROCESS_INFORMATION &pi, int baseAddress,
                                     unsigned char byteExpected, DEBUG_EVENT &de,
                                     DWORD &dwContinueStatus)
{
    if (!SetBreakpoint(pi, baseAddress, byteExpected))
        return false;
    ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
    if (!WaitForEventAtAddress(de,baseAddress,dwContinueStatus))
        return false;
    if (!ClearBreakpoint(pi,baseAddress,byteExpected))
        return false;
    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;
}

bool SetEaxValue(PROCESS_INFORMATION &pi, DWORD newValue)
{
    CONTEXT context;
    context.ContextFlags =  0x1003F; //CONTEXT_ALL;
    if (!GetThreadContext(pi.hThread,&context))
    {
        return false;
    }
    context.Eax = newValue;
    if (!SetThreadContext(pi.hThread,&context))
    {
        return false;
    }
    return true;
}

bool SetEipValue(PROCESS_INFORMATION &pi, DWORD newValue)
{
    CONTEXT context;
    context.ContextFlags =  0x1003F; //CONTEXT_ALL;
    if (!GetThreadContext(pi.hThread,&context))
    {
        return false;
    }
    context.Eip = newValue;
    if (!SetThreadContext(pi.hThread,&context))
    {
        return false;
    }
    return true;
}

bool SetZFlag(PROCESS_INFORMATION &pi)
{
    CONTEXT context;
    context.ContextFlags =  0x1003F; //CONTEXT_ALL;
    if (!GetThreadContext(pi.hThread,&context))
    {
        return false;
    }
    context.EFlags = context.EFlags | 0x40; // 1000000
    if (!SetThreadContext(pi.hThread,&context))
    {
        return false;
    }
    return true;
}

bool ClearZFlag(PROCESS_INFORMATION &pi)
{
    CONTEXT context;
    context.ContextFlags =  0x1003F; //CONTEXT_ALL;
    if (!GetThreadContext(pi.hThread,&context))
    {
        return false;
    }
    context.EFlags = context.EFlags & (~(0x40)); // 1000000
    if (!SetThreadContext(pi.hThread,&context))
    {
        return false;
    }
    return true;
}

bool SetTrapFlag(PROCESS_INFORMATION &pi)
{
    CONTEXT context;
    context.ContextFlags =  0x1003F; //CONTEXT_ALL;
    if (!GetThreadContext(pi.hThread,&context))
    {
        return false;
    }
    context.EFlags = context.EFlags | 0x100; // 100000000
    if (!SetThreadContext(pi.hThread,&context))
    {
        return false;
    }
    return true;
}

bool ClearTrapFlag(PROCESS_INFORMATION &pi)
{
    CONTEXT context;
    context.ContextFlags =  0x1003F; //CONTEXT_ALL;
    if (!GetThreadContext(pi.hThread,&context))
    {
        return false;
    }
    context.EFlags = context.EFlags & (~(0x100)); // 100000000
    if (!SetThreadContext(pi.hThread,&context))
    {
        return false;
    }
    return true;
}

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

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

    
    bool boolDebugging = true;
    DEBUG_EVENT    de;
    DWORD    dwContinueStatus;
    DWORD    address = 0;
    int        baseAddress;
    
    // 1)
    if (!WaitForEventAtAddress(de,0x004e7ae7,dwContinueStatus))
        return -1;

   
// 2)
    baseAddress    = 0x004E86DB;
    if (!SoftwareBreakpointAt(pi,baseAddress, 0xFF, de, dwContinueStatus))
        return -1;

    // 3)
    baseAddress    = 0x004C80BD;
    if (!SetBreakpoint(pi, baseAddress, 0x85))
        return -1;
    baseAddress    = 0x004c84ad;
    if (!SetBreakpoint(pi, baseAddress, 0x85))
        return -1;
    ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);

    // 4)
    if (!WaitForEventAtAddress(de,0x004C80BD,dwContinueStatus))
        return -1;
    baseAddress    = 0x004C80BD;
    if (!ClearBreakpoint(pi,baseAddress,0x85))
        return -1;
    if (!SetEaxValue(pi, 1))
        return -1;
    ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);

    // 5)
    if (!WaitForEventAtAddress(de,0x004c84ad,dwContinueStatus))
        return -1;
    baseAddress    = 0x004c84ad;
    if (!ClearBreakpoint(pi,baseAddress,0x85))
        return -1;
    if (!SetEaxValue(pi, 1))
        return -1;
    ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);

    // 6)
    if (!WaitForEventAtAddress(de,0x00A58006,dwContinueStatus))
        return -1;

    // 7)
    baseAddress    = 0x00A6D8BC;
    if (!SoftwareBreakpointAt(pi,baseAddress, 0x8B, de, dwContinueStatus))
        return -1;

    // 8)
    baseAddress    = 0x00A6DAA5;
    if (!SoftwareBreakpointAt(pi,baseAddress, 0x74, de, dwContinueStatus))
        return -1;

    // 9)
    if (!SetZFlag(pi))
        return -1;

    // 10)
    if (!SetTrapFlag(pi))
        return -1;
    ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);

    // 11)
    if(!WaitForSingleStep(de,dwContinueStatus))
        return -1;

    // 12)
    if (!SoftwareBreakpointAt(pi,baseAddress,0x74,de,dwContinueStatus))
        return -1;

    // 13)
    if (!SetZFlag(pi))
        return -1;

    // 14)
    if (!SetTrapFlag(pi))
        return -1;
    ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);

    // 15)
    if(!WaitForSingleStep(de,dwContinueStatus))
        return -1;

    // 16)
    baseAddress    = 0x00a78c8e;
    if (!SoftwareBreakpointAt(pi,baseAddress, 0xFF, de, dwContinueStatus))
        return -1;

    // 17)
    baseAddress    = 0x0040F210;
    if (!SoftwareBreakpointAt(pi,baseAddress, 0x83, de, dwContinueStatus))
        return -1;

    // 18)
    if (!SetEaxValue(pi, 0x0A))
        return -1;
    ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);

    bool programRunning = true;
    while (programRunning)
    {
    // 19)
        int retValue = WaitForExitOrEventAtAddress(de,0x00414274,dwContinueStatus);
        switch(retValue)
        {
        case 0:
            return -1;
            break;
        case 1:
            break;
        case 2:
    // 20)
            return 0;
            break;
        }
    // 20)
        if (!SetEipValue(pi, 0x00414279))
            return -1;
        ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);

    // 21)
        if (!WaitForEventAtAddress(de,0x00414298,dwContinueStatus))
            return -1;

    // 22)
        if (!SetEipValue(pi, 0x0041429A))
            return -1;
        ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);

    // 23)
        if (!WaitForEventAtAddress(de,0x004142e8,dwContinueStatus))
            return -1;

    // 24)
        if (!SetEipValue(pi, 0x004142ed))
            return -1;
        ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
    }

    return 0;
}



                                                                                                                 faina

Note finali

Bene; anche questa è finita. Mi raccomando: se volete il pacchetto di installazione del programma, o per qualsiasi chiarimento, segnalazione di errori, richieste, ecc. ecc., mandate un Messaggio Privato a faina_mdc sul sito della UIC.
Un saluto a bender0, Saturn e gianni75 e un ringraziamento, come sempre, al padrone di casa Quequero.
Saluti,
                faina

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.