Zoom Icon

Lezione 3 Patching

From UIC Archive

Lezione 3: Simple patching

Contents


Lezione 3 Patching
Author: anonymous
Email: .
Website: .
Date: 01/10/2008 (dd/mm/yyyy)
Level: Working brain required
Language: Italian Flag Italian.gif
Comments:



Introduction

Introduzione.


Tools

Lezione3 Crackme
OllyDbg
Explorer Suite
HexWorkshop


Essay

Oggi ci cimenteremo in un simple patching (che evidentemente significa “patching semplice”). Iniziamo col dire che questo tutorial è il secondo, nell’ordine di letture consigliate dai corsi della UIC, per questo motivo, gli unici elementi che darà per scontati (ma comunque, in tutta la mia bontà, qualche ripassino ve lo faccio ugualmente) saranno quelli spiegati dal tutorial 1, ovvero quelli sul linguaggio assembler.

Prima di tutto, vi introduco un attimo l’argomento. Patchare un file eseguibile significa modificare il file eseguibile originale, cambiandone determinati byte (tipicamente byte che costituiscono istruzioni di salto condizionato) in modo da alterare il comportamento del programma. L’esempio più lampante è quello della registrazione di un software. Moltissimi software, soprattutto quelli sviluppati da chi non è un esperto di questi meccanismi, usano strategie molto elementari per verificare se un programma è registrato oppure no; in particolare, usano tecniche del tipo

if registered then

  Continue(); 

else

  Error();

Qualsiasi istruzione di condizionamento (if, while, ecc….) in assembler x86 viene implementata con delle istruzioni di salto condizionato. Ad esempio, supponendo che registered sia una variabile booleana (può valere TRUE oppure FALSE), lo pseudocodice di sopra si implementa applicando delle semplici regole di compilazione, in questo modo:

mov eax, dword ptr [registered] cmp eax, 1 jnz @@else call Continue ;ramo then jmp @@next @@else: call Error ; ramo else @@next:

... ...

Più in generale, qualsiasi comando del tipo

if condizione then

  comando1; 

else

  comando2;

molto spesso (le istruzioni precisamente usate dai compilatori potrebbero essere diverse da quelle che vi presento ma il significato logico resta pressoché invariato) viene compilata in questo modo:

valutazione di condizione in eax
o in qualsiasi registro generale

cmp eax, 1 jnz @@else:

compilazione del ramo THEN

jmp @@cont @@else:

compilazione del ramo ELSE

@@cont:

Pensandoci un attimo, appare chiaro che se, nel caso della registrazione del software, noi potessimo invertire la guardia (oppure invertire il salto), avremmo lo stesso software che si registra con tutti e soli i dati di registrazione non corretti, mentre darebbe un errore nel caso venissero immessi dei dati corretti. Pensando a software del genere, il compito del patching è proprio questo. Più in generale, si vuole patchare un file eseguibile per alterarne il comportamento in modo da farlo diventare più conveniente per l’utente, rispetto alla versione originale.

Dovreste aver letto la lezione sull’assembler, dunque dovreste capire che, per patchare il file eseguibile come vogliamo, abbiamo bisogno di patchare un’istruzione assembler; nel nostro caso, dobbiamo invertire un salto, dunque dobbiamo patchare l’istruzione di salto utilizzata dal programma, sostituendola con un’istruzione di salto contrario. Riprendiamo un attimo queste istruzioni di salto, prima di generale e poi andiamo a studiarle nello specifico. In assembler x86, le istruzioni di salto (da ora jumper o jump) possono essere condizionate (saltano in base al valore di una condizione booleana) o incondizionate (saltano in ogni caso). Quelle che ci interessano, in questo tutorial sono le prime. In particolare, studieremo i jump in funzione di condizioni di uguaglianza o diversità. Iniziamo a vedere qualche esempio. Vogliamo compilare, in assembler x86, il seguente frammento di pseudocodice:

x = < un certo valore ottenuto chissa’ come >; if x diverso da 10 then {

  x = x + 20; 
  x = x*2;

} else {

  x = 10;

}

per la compilazione, assumeremo che, dopo la prima riga, il valore ottenuto di x sia memorizzato nel registro generale eax. La compilazione (secondo le regole di compilazione che vi ho fatto vedere prima) si fa tenendo conto che quella in grassetto è la condizione di diversità da verificare, la parte in rosso è il ramo THEN del comando condizionale if, mentre la parte in blu è il ramo ELSE. In assembler, si fa così:

abbiamo x in eax

cmp eax, 10 jnz @@then mov eax, 10 jmp @@cont @@then: add eax, 20 add eax, eax @@cont:

...

Al solito, condizione+jump in grassetto, ramo THEN in rosso, ramo ELSE in blu. Supponiamo allora di voler invertire il salto appena compilato. Per adesso, supponiamo di conoscere già la posizione dell’istruzione da patchare all’interno del file eseguibile e concentriamoci solo su cosa dobbiamo modificare. Prima di tutto, per modificare un’istruzione, bisogna essere a conoscenza del modello a codice operativo adottato dai linguaggi assembler. Non voglio addentrarmi in cose che non riguardano questa lezione quindi sarò rapido. Ogni istruzione assembler è una sequenza di byte che vanno opportunamente suddivisi in campi di byte. Ognuno di questi campi, avrà, per il processore, un certo significato. Ad esempio, l’istruzione add eax, 20, avrà un primo campo che identifica l’istruzione add, un secondo campo che identifica il primo argomento come il registro eax e un secondo campo che conterrà la costante 20. Entrando ancora nel dettaglio, il primo campo, prende il nome di codice operativo dell’istruzione (da ora opcode) e, nel caso di add eax, 20, identifica l’istruzione di addizione tra le tante istruzioni di addizione esistenti. In particolare l’opcode di quell’istruzione sarà quello che identifica la particolare istruzione di addizione che, nel primo campo specifica un registro generale (eax) e nel secondo campo contiene un numero costante (20). Infatti, il set di istruzioni x86, non ha una sola add.. ma ne ha un sacchettino. Per farvi un esempio pratico, l’istruzione add eax, ebx avrà un opcode diverso dall’istruzione add eax, 20 perché nel primo caso, il processore deve avere la possibilità di capire che nel secondo parametro viene specificata una costante, mentre nel secondo caso, viene specificato un registro. Per non creare conflitti (si pensi ad esempio se ebx venisse proprio identificato dal valore 20, nella codifica di un’istruzione), se non ci fossero due codici operativi per le due tipologie di istruzioni add, sarebbe impossibile determinare quando sommare, ad eax, 20 oppure ebx.La stessa cosa, dunque, succede per il jump che vogliamo patchare. Avrà l’opcode che indica che si tratta dell’istruzione jnz e poi un secondo campo che indica “dove” saltare (l’etichetta @@else sostanzialmente). Per mostrarvi cosa viene realmente scritto nel file eseguibile, prendo quelle poche istruzioni che vi ho scritto, le inserisco in un file assembler e le compilo con il masm. Qua sotto vi mostro uno schema del contenuto del file ottenuto dalla compilazione, evidenziandovi i byte che costituiscono le varie istruzioni. Questa prima immagine, mostra quello che ho scritto io nell’editor di testo, nel codice sorgente del programma assembler:

Sshot-1.jpg

Poi ho compilato il tutto e, una volta ottenuto il file exe, l’ho disassemblato con IDA pro 5, ottenendo questo:

Sshot-2.jpg

La prima istruzione di mov, non serve nulla.. diciamo che è il pezzo corrispondente alla valutazione della variabile x nel registro eax; l’ho inventato; dalla seconda, iniziano invece le istruzioni interessanti; le ultime due fanno semplicemente terminare il programma. Con una precisa opzione di IDA, ho fatto comparire sullo schermo anche i byte che compongono le istruzioni. Per riprendere gli esempi di cui vi ho parlato prima, prendiamo la codifica dell’istruzione add eax, 14h (14h significa 20: è scritto in base 16):

add
eax
14h
83
C0
14

(i valori nella seconda riga sono sempre scritti in base 16). Come vi accennavo prima, 83 è il byte che forma il codice operativo della particolare istruzione add che prevede, al primo parametro, un registro generale e, al secondo parametro, una costante. Il primo parametro è C0 che, in assembler x86 e nell’istruzione add, identifica proprio il registro eax, il secondo parametro è 14 che è una costante. Ripeto: C0 è interpretato come un identificatore di registro generale e non come una costante perché l’istruzione add con opcode = 83 “sa” che il primo parametro è un registro (anche perché non potrebbe essere diversamente.. ma vabè, questi sono dettagli ^_^) (per capire meglio la cosa, notate le codifiche delle istruzioni di add consecutive che ci sono nel ramo THEN: pur essendo due add, hanno il codice operativo differente). Prendiamo la codifica dell’istruzione che vogliamo patchare:

jnz
@@then
jnz
short_loc_401011
75
07

La prima riga mostra quello che ho scritto io nel codice sorgente, la seconda riga mostra quello il disassembly proposto da ida, la terza riga è la codifica dell’istruzione in byte. Nel secondo caso, ida ha fatto quello che ho fatto io sostanzialmente: ha dato un nome mnemonico alla posizione a cui saltare; io l’ho chiamata @@then, ida ha scelto short_loc_401011.. è questione di gusti! ? La terza riga mostra chiaramente che 75 è l’opcode dell’istruzione jnz che, come argomento, ha una costante. Passiamo al sodo: dobbiamo invertire il salto jnz, piazzando, al posto di questo, un salto jz. Non curandoci della posizione a cui si trova, è facile intuire che dobbiamo cambiare il 75, inserendo, al suo posto, l’opcode dell’istruzione jz. Intuitivamente, assumiamo che le due istruzioni abbiano la stessa dimensione (2 byte) perché appartengono alla stessa classe di istruzioni e comunque hanno semantica molto simile. Bene.. tutto sta nel conoscere quale sia l’opcode dell’istruzione jz. Per ottenerlo, ci sono vari modi. Il più intuitivo è quello di scrivere un sorgente assembler che contiene l’istruzione, compilarlo e vedere qual è il byte generato dal compilatore per codificare l’istruzione. Noi però non siamo tanto nerd, e sappiamo che esiste il manuale delle istruzioni assembler (incluso nel masm, se volete, il file si chiama opcodes.hlp); questi sono un pochino dei salti disponibili nel set di istruzioni x86 (sono circa un terzo di quelli esistenti):

Sshot-3.jpg

Vi ho inquadrettato in blu il jump che è stato compilato nel nostro eseguibile e quello che invece dobiamo usare per effettuare la sostituzione. Osservate che, tra i vari jump, dobbiamo usare quello che si sposta di un offset di 8 bit, per rientrare nelle dimensioni di quello che vogliamo sostituire (perché anche quest’ultimo è un salto di un offset espresso in 8 bit). Se volessimo superare questa limitazione, dovremmo usare un salto che ha una codifica in più byte rispetto a quella esistente, e questo complicherebbe non poco la fase di patching, perché andremmo a sovrascrivere le istruzioni successive al jump. Beh.. come vedete, dobbiamo sostituire, nel file eseguibile il byte 75 con il byte 74 e il gioco è fatto! ?

Fase di patching

A questo punto del tutorial, dobbiamo occuparci della modifica vera e propria del file che vogliamo patchare ma per farlo, dobbiamo introdurrei dei concetti relativi alla memoria virtuale di un processo e al formato di un file eseguibile windows (detto anche “formato PE”). Come vedete dal disassembly proposto da ida, l’istruzione da patchare, è la prima all’indirizzo 00401008 (che è espresso in base 16), che corrisponde (in base 10) all’indirizzo 4198408. Se dividiamo questo indirizzo per 1 MB (2^20 = 1024*1024), notiamo che, per trovare l’istruzione, dobbiamo spostarci di circa 4 MB all’interno del file. Questo può sembrare pazzesco perché, come immaginate, la compilazione di quel pezzettino di codice assembler, produce un file eseguibile dalle dimensioni ridottissime (mi è uscito esattamente di 1,50 KB) ben lontane dal MB (siamo proprio a un ordine di grandezza in meno). Come è possibile? La spiegazione è molto semplice: ida vi mostra l’istruzione mappata nella memoria virtuale del processo, e non mappata nella memoria fisica (ovvero il disco) in cui essa è contenuta. La fase di mapping del codice è detta rilocante proprio perché tutto il codice viene rilocato a destra in funzione di un certo valore numerico chiamato base (o imagebase). In più ci sono un sacco di concetti legati alla posizione dei dati che potrebbero presentarci.. ma per questi sarebbe opportuno che leggiate qualche tutorial che spieghi il formato PE e il suo funzionamento. In questo tutorial, affrontiamo solo ciò che serve per patchare questo file. In pratica, ogni file eseguibile windows deve avere, da qualche parte, questo valore base; esso si trova in una parte del file, nota col nome di PCB (Process Control Block, anche chiamato header o intestazione o chi più ne ha più ne metta). Per ottenerlo, possiamo usare uno dei tanti programmi analizzatori che circolano in rete. Un software che fa al caso nostro può essere, per esempio, il CFF Explorer. Lo uso per aprire il file, navigo nel PCB di quest’ultimo e trovo il valore imagebase:

Cff1.jpg

che, come potete vedere, vale 00400000. Quindi, per prima cosa sottraiamo la base dall’indirizzo, in MV (chiamato anche Virtual Address, o VA, - relative per indicare che è relativo a imagebase):

00401008 - 00400000 =


00001008 (hex)

In questo modo abbiamo ottenuto il Relative Virtual Address. Ancora però questo non è sufficiente per andare a trovare il byte su disco. Il Relative Virtual Address (indirizzo virtuale) deve essere convertito in Raw Address (indirizzo fisico). Dopo questa conversione, potremo usare quest’ultimo per spostarci nel file reale e patchare finalmente il nostro byte. Come facciamo questa conversione? Molto facile. Stiamo patchando il codice, dunque dobbiamo analizzare un attimo la struttura della Section Table (sempre contenuta nel PCB) dell’eseguibile e, precisamente, dobbiamo guardare la riga dedicata alla sezione di codice del file:

Cff2.jpg

La riga .text identifica la sezione di codice. Il virtual address vale 1000 (hex) e il raw address vale 200. Allora, per convertire il nostro virtual offset, torniamo indietro di virtual_address byte e poi “andiamo avanti” di raw_address byte, in questo modo:

VA – virtual_address(.text) + raw_address(.text) =

1008 – 1000 + 200

= 208 (hex)

Abbiamo ottenuto 208 (hex). Notate che tutto questo calcolo può anche essere fatto in modo automatico, sempre col buon CFF Explorer, cliccando su “Address converter”. A questo punto, conosciamo l’indirizzo del byte da patchare, sappiamo che dobbiamo patchare 75 con 74… facciamolo! Per editare il file, potremmo usare lo stesso CFF Explorer, ma questa guida ha carattere di generalità.. per cui, userò l’Hex Workshop, un ottimo editor esadecimale. Apro il file, vado al byte 208 (con CTRL+G)

Hex1.jpg

Premo INVIO e, magia delle magie:

Sshot-4.jpg

Sono proprio sul byte da modificare. Scrivete 74, salvate e il gioco è fatto.

Quando le cose non sono banali

Mah.. per adesso, abbiamo imparato la tecnica per individuare un byte su disco, partendo dallo stesso in memoria virtuale, e patcharlo. Adesso però vogliamo sfruttare questo meccanismo almeno per registrare un crackme (che, come sapete, sono programmini fatti apposta per esercitarsi nel cracking/reversing); quindi questa volta dobbiamo individuare il jump da modificare, analizzare il programma e, se avanza un po’ di spazio-tempo, scrivere anche il programmino che lo patcha automaticamente. Questo è un tutorial per newbie e, se lo state leggendo, è bene che non pretendiate di fare cose troppo impegnative o complicate. Crackeremo un semplicissimo crackme di quelli che si trovano in giro. Per la precisione, sul noto sito crackmes.de, ho trovato un keygenme (che trovate come allegato) molto interessante. Le regole ufficiali del crackme sarebbero quelle di NON patcharlo ma di scrivere un generatore di chiavi in grado di registrarlo, lasciandolo inalterato. A noi però non ce ne frega niente e lo utilizziamo bellamente per i nostri scopi di patching! ? Questa volta operiamo in un modo del tutto diverso dal precedente, ovvero debuggando il programma. Un debugger è un programma che serve per controllare il flusso di esecuzione di un qualsiasi altro processo (target) steppando e controllando direttamente la computazione delle istruzioni assembler (ovviamente, non avendo, in generale, a disposizione il sorgente ad alto livello del programma che vogliamo debuggare). Come debugger useremo Ollydbg che, dunque, per prima cosa dovete scaricare.Iniziamo allora a studiare il programmino. Appena lo avviamo, vediamo comparire una console con qualche informazione che non ci riguarda e, subito, notiamo che chiede il nostro nome:

Crackme1.jpg

Beh.. senza nemmeno continuare l’inserimento dei valori, sappiamo che dobbiamo individuare, col debugger, il punto esatto in cui il programma inizia a chiederci queste informazioni, perché, con ogni probabilità, subito dopo, ci sarà la parte interessante (ovvero il check di validità delle informazioni inserite, che noi andremo a patchare). Chiudiamo subito il programma e seguiamo una semplice strategia con Olly. Apriamo il debugger, clicchiamo sul menu File -> Open, sfogliamo il filesystem fino a trovare e selezionare il keygenme. Apriamolo e subito il debugger ci porta (a processo sospeso) sulla prima istruzione da eseguire (ovvero sull’entry point dell’applicazione):

Sshot-5.jpg

Vogliamo arrivare al punto esatto in cui il programma inizia a stampare tutte quelle varie righe. Allora per adesso, eseguiamo un’istruzione per volta con il tasto F8 fino alla comparsa di qualche scritta nella console. Il tasto F8 è uno step over instruction, ovvero un tasto che esegue una riga per volta, di quelle che però vi compaiono sullo schermo. In altre parole, se passate sopra un’istruzione di CALL, non vi entrerete all’interno ma lo step procederà eseguendo tutta la procedura senza mostrarvi nulla e ridandovi il controllo dell’applicazione all’istruzione successiva a quella di CALL. Se invece su di una CALL premete il tasto F7 (step into an instruction), l’effettò sarà quello di continuare la fase di debugging a partire dalla prima istruzione della procedura invocata. Procedendo dunque con F8, notiamo che la prima call (0040128D) non ha effetto sulla console, mentre la seconda sì:

Sshot-6.jpg

Solo che, come vedete, quella call ha scritto molte più cose di quante ce ne aspettassimo.. peraltro il processo resta in attesa dell’input dall’utente. Questo significa che tutta (o probabilmente quasi tutta) la parte “interessante” del programma si consuma all’interno di questa call. Riavviamo il debugging (e quindi il processo) e steppiamo con F7 sulla seconda call. Ci troveremo al suo interno e quindi riprendiamo a steppare con F8 in attesa di altre modifiche sulla console. Dopo un bel po’ di istruzioni steppate arriviamo a questo punto:

Sshot-7.jpg

E’ successa la stessa cosa di prima. Dobbiamo entrare anche nella procedura invocata all’indirizzo 00401246. Anche questa volta dobbiamo riavviare il programma, con la differenza che non siamo più disposti a steppare fino al raggiungimento di questa nuova procedura. Quello che faremo è mettere un breakpoint (letteralmente punto di pausa) sul rigo prima (per sicurezza) di quello a cui dobbiamo fermarci, ovvero al rigo 00401243 (si vede l’indirizzo anche dalla figura, sopra la linea evidenziata in grigio). Riavviamo olly, apriamo il programma exe e premiamo Alt+F1 per far comparire la linea dei comandi di olly:

Sshot-8.jpg

Nella linea di comando, scriviamo bp 00401243 e premiamo invio per indicare al debugger di mettere un breakpoint all’istruzione che si trova all’indirizzo 00401243. Se non ci sono controlli anti-debugger e anti-breakpoint (come è facilmente immaginabile, vista la natura del programma target), se lasciamo correre il programma premendo PLAY, questo si sospenderà proprio alla riga 00401243:

Sshot-9.jpg

che, come vedete, è evidenziata in rosso per via della presenza del breakpoint. Andiamo dunque sulla call e steppiamoci dentro con F7. Riprendiamo dal suo interno a steppare con F8 e a un certo punto, arriviamo a questa situazione:

Sshot-10.jpg

Come vedete, il crackme sta iniziando a stampare le varie righe sulla console. Fermiamoci a riflettere, andiamo su olly e guardiamo il codice che viene dopo:

CALL KeygenME.00402A90 SUB ESP,0C PUSH KeygenME.004040DC ; /s = "manatails007(MTZ007)'s 1st KeygenMe" CALL <JMP.&msvcrt.puts>; \puts ADD ESP,10 SUB ESP,0C PUSH KeygenME.00404100 ; /s = "NO PATCHING!" CALL <JMP.&msvcrt.puts>; \puts</nowiki> ADD ESP,10 SUB ESP,0C PUSH KeygenME.0040410D  ; /format = "Name:" CALL <JMP.&msvcrt.printf>; \printf</nowiki> ADD ESP,10 SUB ESP,8 LEA EAX,DWORD PTR SS:[EBP-12] PUSH EAX PUSH KeygenME.00404113  ; /format = "%s" CALL <JMP.&msvcrt.scanf> ; \scanf</nowiki> ADD ESP,10 SUB ESP,0C LEA EAX,DWORD PTR SS:[EBP-12] PUSH EAX CALL KeygenME.004025B7 ADD ESP,10 SUB ESP,0C PUSH KeygenME.00404116  ; /format = "Serial:" CALL <JMP.&msvcrt.printf> ; \printf ADD ESP,10 SUB ESP,8 LEA EAX,DWORD PTR SS:[EBP-33] PUSH EAX PUSH KeygenME.00404113  ; /format = "%s" CALL <JMP.&msvcrt.scanf> ; \scanf</nowiki> ADD ESP,10 LEA EAX,DWORD PTR SS:[EBP-33] PUSH EAX PUSH KeygenME.004050E0 CALL KeygenME.004027C7 ADD ESP,8 MOV DWORD PTR SS:[EBP-8],EAX CMP DWORD PTR SS:[EBP-8],0 JNZ KeygenME.00402981 SUB ESP,0CPUSH 43  ; /c = 43 ('C') CALL <JMP.&msvcrt.putchar> ; \putchar</nowiki> ADD ESP,10 SUB ESP,0C PUSH 6F ; /c = 6F('o') CALL <JMP.&msvcrt.putchar> ; \putchar ADD ESP,10 SUB ESP,0C PUSH 72 ; /c = 72 ('r') CALL <JMP.&msvcrt.putchar> >; \putchar ADD ESP,10 SUB ESP,0C PUSH 72 ; /c = 72 ('r') CALL <JMP.&msvcrt.putchar> ; \putchar ADD ESP,10 SUB ESP,0C PUSH 65 ; /c = 65 ('e') CALL <JMP.&msvcrt.putchar> ; \putchar ADD ESP,10 SUB ESP,0C PUSH 63 ; /c = 63 ('c') CALL <JMP.&msvcrt.putchar> ; \putchar</nowiki> ADD ESP,10 SUB ESP,0C PUSH 74 ; /c = 74 ('t') CALL <JMP.&msvcrt.putchar> ; \putchar ADD ESP,10 SUB ESP,0C PUSH 20 ; /c = 20 (' ') CALL <JMP.&msvcrt.putchar> ; \putchar</nowiki>

Notate che, con la prima riga in rosso (la scanf), il programma attende che noi inseriamo il seriale (poco prima aveva stampato la richiesta di seriale sulla console); qualche istruzione dopo, c’è un bel jump JNZ, e sotto di questo, ci sono un sacco di righe bizzarre che, carattere per carattere (è una tecnica usata per non far scovare al cracker la stringa “Correct”), viene stampato sulla console il messaggio “Correct serial”. Beh… si vede chiaramente che se quel jump non ci fosse, il programma sarebbe sempre registrato. Ma comunque proviamo a vedere che cosa succede, steppiamo con F8 fino a quando non arriviamo su quel jump. Ci troviamo davanti a questa situazione:

Sshot-11.jpg

Prima di tutto notate che il JNZ di questa volta, è diverso dal JNZ del programma di prima, perché il salto è più lungo, quindi servono più byte per rappresentarlo, e comunque il codice operativo dell’istruzione è diverso. Poi notate, in basso, che il debugger ci informa che, se continuiamo a steppare, il salto viene effettuato (chiaramente salta a errore perché i dati di registrazione che avete immesso non possono essere validi… se sono validi, sparatevi…). Possiamo provare che la nostra teoria è giusta, provando a invertire il salto solo per quanto riguarda questa esecuzione, modificando le cose in memoria (nel senso.. il programma non è ancora crackato.. funzionerà solo questa volta.. poi una volta chiuso, sarà tutto da rifare). Possiamo farlo cambiando il flag Z (sulla destra) da 0 a 1, facendoci doppio clicl con il mouse. Non abbiamo invertito il salto ma abbiamo invertito (complementato su 1 bit) la condizione di salto (ovviamente il flag Z questa volta funziona perché è quello che viene utilizzato dal salto JNZ; in presenza di altri tipi di salto, bisogna complementare il flag opportuno). Se lo fate e premete PLAY per proseguire indiscriminatamente l’esecuzione di tutto il resto del programma, notate che il crackme vi fa i complimenti perché siete riusciti a registrarlo. La figura indica ancora l’indirizzo, in memoria virtuale, a cui si trovava quel salto (ovvero 004028B6). Dobbiamo fare allora una cosa analoga a quella fatta nella prima parte del corso:

  1. Togliere l’imagebase dall’VA per ricavare il RVA;
  2. Convertire il RVA in RA per individuare il salto su disco;
  3. Aprire il file con l’hex workshop e patcharlo.

Notate che questa volta, sarebbe meglio che il jump non ci fosse proprio.. cioè.. sarebbe meglio eliminarlo invece che invertirlo. Fortunatamente questo è ancora più semplice: come vedete l’istruzione è composta da 6 byte. Se li sostituiamo con 6 istruzioni NOP (No OPeration, occupano 1 byte ciascuna, codice operativo 90), il salto si dice che viene “noppato” (termine bizzarro ma efficace). Bene procediamo. Dobbiamo trasformare, in 90, 6 byte a partire dall’indirizzo 004028B6. Procuriamoci i dati con il CFF Explorer, come abbiamo fatto nella parte precedente del tutorial:

  • Imagebase = 00400000 (hex)
  • Virtual address della sezione di codice = 1000 (hex)
  • Raw address della sezione di codice = 400 (hex)

Facciamo i nostri calcoli:

RA = VA – imagebase – virtual_address(.text) + raw_address(.text) =

004028B6 – 00400000 – 1000 + 400

= 1CB6

Amen, chiudiamo olly, prendiamo l’hex workstop, apriamo l’eseguibile, facciamo ctrl+g, scriviamo 1CB6 nel campo di scostamento, indichiamo la base 16 e indichiamo che vogliamo spostarci partendo dall’inizio del file. Allora sulla nostra istruzione:

Sshot-12.jpg

E facciamola diventare così:

Sshot-13.jpg

Salvate, chiudete l’hex workshop, avviate il crackme e provate a registrarlo. Clap clap clap clap! Un programmino, che faccia il patching, scritto in C++ è la seguente:

  1. include <windows.h>
  2. include <tchar.h>

int _tmain(int argc, _TCHAR* argv[]) { // apro il file HANDLE hFile = CreateFileA("KeygenME.exe", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

// mi sposto sul punto da patchare SetFilePointer(hFile, 0x1CB6, NULL, FILE_BEGIN);

// patcho DWORD junk; BYTE buffer[] = { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 }; WriteFile(hFile, buffer, 6, &junk, NULL);

// chiudo ed esco CloseHandle(hFile); return EXIT_SUCCESS; }

Questo non è un corso sul C++ quindi, se non sapete cosa significhi questo programma, avete due alternative: o imparate il C++ oppure ignorate quest’ultimo pezzo di tutorial.


Note Finali

Note finali.


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.