Dll Cracking and .reloc Section

Data

by teDDy

 

16/12/2002

UIC's Home Page

Published by Quequero


A volte mi chiedo perchè passiamo le nostre ore a crackare programmi che magari non useremo.

Grazie teDDy, se non ricordo male un tutorial sulla rilocazione proprio ci mancava ed era ora che qualcuno lo facesse, grazie ancora e complimenti per il tutorial

E' la voglia di dimostrare al mondo che possiamo farcela.
Non fatela morire mai.

....

teDDy on #crack-it

....

Difficoltà

( )NewBies (x)Intermedio ( )Avanzato ( )Master

 

Modificare il codice delle dll può portare a degli inconvenienti dovuti alla rilocazione del codice. Vediamo come affrontare questa situazione.


Dll Cracking and .reloc Section

Written by teDDy

Introduzione

Affronteremo il tema della rilocazione nelle dll e vedremo come modificare il codice senza doverci preoccupare della rilocazione.

Tools usati


Softice
PEditor
Hex Workshop
Ida o W32Dasm (Solo per vedere il disassemblato originale)

Essay

PREMESSA
In questi giorni mi sono dedicato ad un programma protetto da chiave hardware WIBU-KEY. Niente di nuovo, il solito check sulla presenza della chiave e lettura del contenuto. In realtà questa dongle permette di fare encripting del codice con diversi metodi in modo da rendere difficile una simulazione della chiave hardware (si parla del metodo 1: RID – Required Information Decryption e del metodo 2: RED – Random Encryption Decryption o una combinazione di essi).
Visto che in questo caso i programmatori si sono limitati solo a controllarne la presenza e a leggerne il contenuto è bastato modificare le due funzioni del driver che svolgono questa mansione.

In questo modo senza toccare l'eseguibile siamo riusciti a simulare la presenza della chiave hardware.


Convinto di aver completato
le modifiche lancio il programma e che succede? Crash! Sembra che le modifiche apportate non piacciano e convinto di aver sbagliato qualcosa nel codice ho dato un'occhiata con Sice per vedere cosa succede.
Il codice dell'eseguibile dopo le mie modifiche si presenta così:
...
...
:200051F8 8B5D18            mov ebx, dword ptr [ebp+18]
:200051FB C70300000000      mov dword ptr [ebx], 00000000
:20005201 66C743040400      mov [ebx+04], 0004
:20005207 66C743060500      mov [ebx+06], 0005
:2000520D C7430800000000    mov [ebx+08], 00000000
:20005214 C7430C00000000    mov [ebx+0C], 00000000
:2000521B B810000000        mov eax, 00000010
:20005220 5B                pop ebx
:20005221 8BE5              mov esp, ebp
:20005223 5D                pop ebp
:20005224 C21800            ret 0018
...
...
In esecuzione invece il codice risulta così:
...
...
:015851F8 8B5D18            mov ebx, dword ptr [ebp+18]
:015851FB C70300000000      mov dword ptr [ebx], 00000000
:01585201 BEA8430404        mov esi,040443A8
:01585206 0066C7            add [esi-39],ah
:01585209 43                inc ebx
:0158520A 06                push es
:0158520B 0500C74308        add eax,0843C700
:01585210 0000              add [eax],al
:01585212 0000              add [eax],al
:01585214 C7430C00000000    mov dword ptr [ebx+0C], 00000000
:0158521B B810000000        mov eax, 00000010
:01585220 5B                pop ebx
:01585221 8BE5              mov esp, ebp
:01585223 5D                pop ebp
:01585224 C21800            ret 0018
...
...
Subito ho pensato che il programma in questione contenesse del codice automodificante (SMC) o avesse utilizzato la funzione di encripting; questo perchè a prima vista il codice modificato sembrava parecchio guardando il disassemblato.
Guardando con più attenzione invece, il codice realmente modificato è di 2 byte, all'indirizzo 0158201; sono questi due byte che stravolgono il disasm successivo.
Ho così deciso di guardare il codice originale, per capire da che cosa potesse dipendere tale modifica.
Ecco come si presenta:
...
...
:200051F6 BFD0000000           mov edi, 000000D0
:200051FB EB0A                 jmp 20005207
:200051FD C7054C75012005000000 mov dword ptr [2001754C], 00000005
:20005207 8BCF                 mov ecx, edi
:20005209 8BBC2444010000       mov edi, dword ptr [esp+00000144]
:20005210 8BD1                 mov edx, ecx
:20005212 8D742430             lea esi, dword ptr [esp+30]
:20005216 C1E902               shr ecx, 02
:20005219 F3                   repz
:2000521A A5                   movsd
:2000521B 8BCA                 mov ecx, edx
:2000521D 83E103               and ecx, 00000003
:20005220 F3                   repz
:20005221 A4                   movsb
:20005222 E839610000           call 2000B360
:20005227 E9DE000000           jmp 2000530A
...
...


Uhm uhm, un indirizzo fisso....
Azz, la rilocazione!!!!!!!

La rilocazione può avviene quando una DLL o un EXE devono essere caricati (mappati) in memoria.
In realtà è molto difficile che avvenga la rilocazione per un file exe ma è frequente invece nelle DLL.
Ogni file ha un indirizzo di partenza prefissato dove dovrebbe essere caricato, chiamato BASE ADDRESS. Se però tale spazio in memoria è già occupato, il codice viene caricato in un'altra locazione.
Questo non comporta alcun problema per il codice che utilizza riferimenti relativi, ma genera invece errori per gli indirizzi assoluti. Mi spiego meglio:
prendiamo come esempio il codice sopra all'indirizzo 20005227:

:20005227 E9DE000000  jmp 2000530A

In realtà questo salto non è un salto assoluto all'indirizzo "2000530A" ma è un salto relativo del tipo "jmp + 0DE byte in avanti". Infatti i byte di tale istruzione indicano "E9" il jump e "DE000000" di quanto deve avvenire il salto: 20005227 + DE + "byte occupati dall'istruzione (5)" uguale appunto 200530A.
Se tutto il codice viene spostato non cambia nulla perchè il salto avviene sempre n-byte più avanti a partire dal nuovo indirizzo.
L'istruzione all'indirizzo 200051FD invece non fa riferimento a indirizzi n-byte più avanti o più indietro rispetto alla posizione corrente ma indica in modo assoluto l'indirizzo di riferimento; all'interno del codice troviamo infatti l'indirizzo scritto per esteso.

:200051FD C7054C75012005000000 mov dword ptr [2001754C], 00000005

Ipotizziamo che all'indirizzo 2001754C ci sia la stringa 'pippo'.

Se tutto il codice viene spostato ad un altro indirizzo (ad esempio 0158xxxx invece che 2000xxxx), anche la stringa 'pippo' verrà spostata, più precisamente al nuovo indirizzo 01581754C.
Cosa succede in questo caso? Questa istruzione fa ancora riferimento all'indirizzo originale 2001754C, che contiene ora altri dati.
Per correggere queste istruzioni interviene appunto la rilocazione che sostituisce a tutti questi indirizzi assoluti il nuovo indirizzo assoluto ricalcolato. Nel nostro caso l'indirizzo a 2001754c dovrà diventare 0159754c quindi la rilocazione fa questo tipo di calcolo:

BASE ADDRESS originale = 20000000
BASE ADDRESS corrente  = 0158
0000

DIFFERENZA             = 1EA80000

e sottrarrà a tutti gli indirizzi assoluti questa cifra.

Per questo motivo il codice che avevo scritto all'indirizzo 200051ff è stato modificato:

:200051FB C70300000000      mov dword ptr [ebx], 00000000
:20005201 66C743040400      mov [ebx+04], 0004


C7660000 - 1EA80000 = A8BE0000 che è appunto il nuovo valore che ci troviamo a run-time:

:015851FB C70300000000      mov dword ptr [ebx], 00000000
:01585201 BEA8430404        mov esi,040443A8




Cosa fare allora per essere sicuri che le modifiche al codice nelle DLL non vengano "corrotte" dalla rilocazione?
Ci sono due soluzioni: la prima meno raffinata consiste nello scrivere il codice solo dove la rilocazione non avviene e unire le varie parti di codice con dei jump:

        nostro codice
        nostro codice
        nostro codice
        jump prox

        codice originale soggetto a rilocazione
        codice originale soggetto a rilocazione
        codice originale soggetto a rilocazione

prox: nostro codice
        nostro codice

        ...
Questo comporta un'analisi del codice originario per vedere in quali istruzioni ci sia un riferimento assoluto in modo da evitarle.


La seconda (la migliore a mio avviso) è quella di intervenire direttamente sulla sezione di rilocazione (.reloc) in modo da evitare che la rilocazione avvenga nel codice scritto da noi; vediamo quindi da vicino la sezione reloc.

La sezione reloc è composta da una serie di strutture dati di questo tipo a lunghezza variabile:

struct IMAGE_BASE_RELOCATION
{
  DWORD VirtualAddress;  RVA di partenza per il blocco di dati corrente
  DWORD SizeOfBlock;     Grandezza del blocco corrente
  WORD TypeOffset();     Array di indirizzi che subiscono la rilocazione
}
Ogni struttura descrive la rilocazione per ogni pagina di 4K (quindi 1000h Byte) e ogni Virtual Address sarà 1000h byte maggiore del precedente.
Il SizeOfBlock dipende dal numero di rilocazioni nel blocco corrente; la grandezza è data da Size(TypeOffset) + 8 quindi per calcolare il numero di rilocazioni del blocco basta fare: (SizeOfBlock - 8) / 2
TypeOffset è composto da due parti: i 4 bit più alti corrispondono al tipo di rilocazione da effettuare, gli altri 12 indicano invece l'offset di rilocazione (che dev'essere sommato al VirtualAddress del blocco per ottenere l'offset completo).

Il formato PE prevede solo due tipi di rilocazioni possibili:
* 0 (IMAGE_REL_BASED_ABSOLUTE):
Questo tipo di rilocazione è senza significato ed è usata per arrotondare la struttura dell' IMAGE_BASE_RELOCATION ad una grandezza multipla di DWORD.
* 3 (IMAGE_REL_BASED_HIGHLOW):
In questo caso la rilocazione avviene per tutti gli offset puntati dall'RVA di partenza + i 16 bits inferiori di ogni TypeOffsetRelocation secondo la formula prima descritta.

Tornando alla dll che ho modificato vediamo la sezione di rilocazione:

PEditor mi dice che:
l'image base del mio file è 20000000.
la sezione di rilocazione inizia a 20000.
.reloc
...
20000:00100000 D8000000 2B30 3430 9F30 A530 C130 D530 2531
...
...
La prima dword ci dice che l'RVA di partenza è 1000h; la seconda dword che il blocco è grande D8h byte.
Tutte le altre WORD ci dicono invece il tipo e l'offset di rilocazione; prendiamo come esempio la prima WORD:
2B30 ==> 302B. I primi 4 bit (3) ci indicano il tipo di rilocazione; gli altri 12 (02B) l'offset.
Quindi all'indirizzo "Image Base + RVA + Offset" (20000000 + 1000 + 2B=2000102B) avverrà la prima rilocazione.

Le nostre modifiche vanno dall'indirizzo
20005110 all' indirizzo 20005224. Il blocco che ci interessa è quindi quello con RVA 5000.
...
20328:
00500000 94000000 0B30 5B30 8F30 9430 B330 CE30 8C31 FF31 4F32 8B32 9032 C132 F232 6A33 9633 D533 0934 0F34 2534 3A34 D534 E134 F434 EB35 1C36 2136 3D36 6536 D636 EA36 0D37 4137 4737 6937 8737 B937 2438 2E38 E238 1339 1A39 4A39 4F39 8139 9F39 C439 C839 CC39 D039 B73A 013B 3E3B 7F3B 893B 8F3B B53B BD3B 023C 443C 7F3C D23C 0C3D 663D AE3D 123E 753E F23E BD3F C33F 0000
00600000

Nel blocco che va da 5000 a 6000 vengono fatte (94h-8)/2 rilocazioni ovvero 70.
Le nostre modifiche però vanno da 5110 a 5224 quindi cerchiamo di individuare gli offset che ci interessano; in questo caso due: (3)18C e (3)1FF. Da notare gli 0000 finali che servono a rendere il blocco multiplo di DWORD.
Andiamo a vedere nel codice originale gli indirizzi 2000518C e 200051FF.
...
...
:20005184 0F8480010000        je 2000530A
:2000518A F6054875012004      test byte ptr [20017548], 04
:20005191 0F8495000000        je 2000522C

...
...
:200051FD C7054C75012005000000 mov dword ptr [2001754C], 00000005
:20005207 8BCF                 mov ecx, edi
...
...
Come ci si aspettava, due indirizzi assoluti.

Siamo arrivati alla fine, manca solo correggere la sezione reloc. Come fare? Beh, ci sono due possibilità.

1) Abbiamo scritto il codice fino a 20005224 dove facciamo il ret dalla funzione, perciò il codice successivo non viene mai eseguito. Pertanto anche se avviene la rilocazione dopo questo indirizzo non ci interessa. Facendo diventare la WORD 8C31 ==>2A32 (2000522A) e la WORD FF31 ==>2A32 (2000522A) verrebbe rilocato tale indirizzo 2 volte ma inutilmente perchè il codice a quell'offset non viene eseguito.

2) Facciamo uso del secondo tipo di rilocazione (0), che in realtà non è una rilocazione vera e propria ma è paragonabile ai nop nel codice ;) In questo modo gli faremo saltare gli offset utilizzati da noi. Le due WORD possono essere scritte come 8C31 ==>8C01 e FF31 ==>FF01 o semplicemente 0000 e 0000, il risultato ottenuto è lo stesso.
Fate come più vi piace, l'importante è evitare la rilocazione.
 
 

                                                                             Alla prossima, gente.

                                                                                                     teDDy
 

Note finali

Un saluto a tutto il chan #crack-it che per motivi di tempo ormai frequento pochissimo.
(Si, lo so che non vi manco affatto, ma voi mancate a me :-P)

Disclaimer

Vorrei ricordare che il software va comprato e  non rubato (e le chiavi hardware? quelle possiamo rubarle? :), 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 immane che ogni singolo programmatore ha dovuto portare avanti per fornire ai rispettivi consumatori i migliori prodotti possibili.

Se poi chi sviluppa le dongle si fa un mazzo per implementare il cripting del codice (grandi WIBU-KEY, pure cripting hardware fate) e invece il programmatore che le utilizza non ne fa uso, questo è un altro discorso :)))))

Noi reversiamo al solo scopo informativo e di miglioramento del linguaggio Assembly.