Zoom Icon

Mdc Splitter 3.22 Scrivere un debugger/patcher

From UIC

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

Contents


Mdc Splitter 3.22 Scrivere un debugger/patcher
Author: faina
Email:
Website:
Date: 24/09/2004 (dd/mm/yyyy)
Level: Slightly hard
Language: Italian Flag Italian.gif
Comments:



Introduction

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. È 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

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


Link e Riferimenti

Il programma lo puoi prelevare dal sito della "Boilsoft": http://www.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".
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

Prima di cominciare, mi raccomando: leggete e capite il tutorial di bender0 intitolato Clone DVD 2.5.0.0. A proposito 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 po'; 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".

3.1 SetBreakpoint
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;
}


3.2 AdjustInstructionPointer
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;
}

3.3 ClearBreakpoint
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);
}

3.4 WaitForEventAtAddress
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;
}

3.5 WaitForSingleStep
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;
}

3.6 Impostare e rimuovere un hardware breakpoint.
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"
&#35;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. Saranno accettati suggerimenti, domande, ecc. ecc. Un ringraziamento ancora a bender0 e uno anche al padrone di casa Quequero. Alla prossima.... marionetta. 8-))


Disclaimer

I documenti qui pubblicati sono da considerarsi pubblici e liberamente distribuibili, a patto che se ne citi la fonte di provenienza. Tutti i documenti presenti su queste pagine sono stati scritti esclusivamente a scopo di ricerca, nessuna di queste analisi è stata fatta per fini commerciali, o dietro alcun tipo di compenso. I documenti pubblicati presentano delle analisi puramente teoriche della struttura di un programma, in nessun caso il software è stato realmente disassemblato o modificato; ogni corrispondenza presente tra i documenti pubblicati e le istruzioni del software oggetto dell'analisi, è da ritenersi puramente casuale. Tutti i documenti vengono inviati in forma anonima ed automaticamente pubblicati, i diritti di tali opere appartengono esclusivamente al firmatario del documento (se presente), in nessun caso il gestore di questo sito, o del server su cui risiede, può essere ritenuto responsabile dei contenuti qui presenti, oltretutto il gestore del sito non è in grado di risalire all'identità del mittente dei documenti. Tutti i documenti ed i file di questo sito non presentano alcun tipo di garanzia, pertanto ne è sconsigliata a tutti la lettura o l'esecuzione, lo staff non si assume alcuna responsabilità per quanto riguarda l'uso improprio di tali documenti e/o file, è doveroso aggiungere che ogni riferimento a fatti cose o persone è da considerarsi PURAMENTE casuale. Tutti coloro che potrebbero ritenersi moralmente offesi dai contenuti di queste pagine, sono tenuti ad uscire immediatamente da questo sito.

Vogliamo inoltre ricordare che il Reverse Engineering è uno strumento tecnologico di grande potenza ed importanza, senza di esso non sarebbe possibile creare antivirus, scoprire funzioni malevole e non dichiarate all'interno di un programma di pubblico utilizzo. Non sarebbe possibile scoprire, in assenza di un sistema sicuro per il controllo dell'integrità, se il "tal" programma è realmente quello che l'utente ha scelto di installare ed eseguire, né sarebbe possibile continuare lo sviluppo di quei programmi (o l'utilizzo di quelle periferiche) ritenuti obsoleti e non più supportati dalle fonti ufficiali.