Zoom Icon

Dll Reloc

From UIC

Dll Cracking and .reloc Section

Contents


Dll Reloc
Author: teDDy
Email: teDDyUIC@yahoo.it
Website:
Date: 16/12/2002 (dd/mm/yyyy)
Level: Luck and skills are required
Language: Italian Flag Italian.gif
Comments: Que: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



Introduction

Modificare il codice delle dll può portare a degli inconvenienti dovuti alla rilocazione del codice.
Affronteremo il tema della rilocazione nelle dll e vedremo come modificare il codice senza doverci preoccupare della rilocazione.


Tools


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ì:

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 = 01580000

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

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