Minima Anonyma Tabularia Ex Reti

nihil vacuum neque sine signo apud MATER

Liber Quartus

 

 

Salve, vera Turingus proles.

Questa è la zine ufficiale di Familia, un gruppo che ha come oggetto di studio la programmazione virale.

MATER ha come scopo l’esame delle tecniche di programmazione virale e delle problematiche ad esse associate. MATER è una raccolta di contributi volontari (e non) di alcuni code-writers del pianeta, in lingua italiana ed è rivolta soprattutto a chi si avvicina per la prima volta al mondo dei virus e necessita dei concetti di base che spesso sono dati per scontati nelle zines reperibili in Rete. Non è, tuttavia, una esemplificazione discorsiva priva di contenuto tecnico: per comprendere gli esempi ivi contenuti è necessaria una minima conoscenza dell’architettura di un elaboratore e del linguaggio assembler.

E’ nostra convinzione che una migliore conoscenza delle metodologie infettive digitali sia la principale strada per accrescere la sicurezza degli utenti e fornire un contributo a chi opera nel settore della programmazione. Ritenendo il vero nemico della sicurezza la disinformazione, auspichiamo che una più profonda comprensione in questo campo dissipi i dubbi, aiuti la ricerca e contribuisca al progresso dell’educazione informatica. Sursum Corda ! 

 

 

ATTENZIONE

Questa pubblicazione contiene informazioni e codice sorgente relativi alla sicurezza informatica. Lo scopo di queste informazioni è di aiutare gli utenti ad accrescere la propria capacità di programmazione. Questo materiale è a puro scopo didattico e non include codice distruttivo né documenti attinenti ad alcuna attività illegale. Si declina ogni responsabilità nel caso chiunque utilizzi le suddette informazioni per creare, compilare e diffondere intenzionalmente programmi diretti a danneggiare o interrompere un sistema informatico o telematico, ovvero a provocarne l'interruzione, totale o parziale, o l'alterazione del suo funzionamento Art. 615-quinquies (Legge n. 547/93 pubblicata in Gazzetta Ufficiale n. 305 del 30.12.1993)

 

 

 

 § MATER Liber Quartus §

 

indice

1.      API

2.      Hello World !

3.      Alcuni concetti

4.      5. il KERNEL32

5.      Gli indirizzi delle API

 

1. API

 

Uno dei cambiamenti più bruschi è tuttavia il passaggio dalle chiamate alle funzioni del buon vecchio INT21 alle API (Application Program Interface). Cosa sono le API ? Sono delle funzioni che, come dice il loro nome, provvedono a fare un po’ di tutto. Parlare di API è un po’ come stuzzicare un balena: se cominciassi a farlo dovrei scrivere un’enciclopedia. Le API sono centinaia. Tanto per darvene un’idea, ecco le API che trattano le operazioni sui files:

 

_hread

_hwrite

_lclose

_lcreat

_llseek

_lopen

_lread

_lwrite

AreFileApisANSI

CopyFile

CreateDirectory

CreateDirectoryEx

CreateFile

CreateIoCompletionPort

DefineDosDevice

DeleteFile

FileIOCompletionRoutine

FindClose

FindCloseChangeNotification

FindFirstChangeNotification

FindFirstFile

FindNextChangeNotification

FindNextFile

FlushFileBuffers

GetBinaryType

GetCurrentDirectory

GetDiskFreeSpace

GetDriveType

GetFileAttributes

GetFileInformationByHandle

GetFileSize

GetFileType

GetFullPathName

GetLogicalDrives

GetLogicalDriveStrings

GetQueuedCompletionStatus

GetShortPathName

GetTempDrive

GetTempFileName

GetTempPath

GetVolumeInformation

LockFile

LockFileEx

MoveFile

MoveFileEx

OpenFile

QueryDosDevice

ReadFile

ReadFileEx

RemoveDirectory

SearchPath

SetCurrentDirectory

SetEndOfFile

SetFileApisToANSI

SetFileApisToOEM

SetFileAttributes

SetFilePointer

SetHandleCount

SetVolumeLabel

UnlockFile

UnlockFileEx

WriteFile

WriteFileEx

 

Come potete vedere siamo ad anni luce di distanza dalle operazioni che eravamo abituati a vedere con il vecchio INT21. Con questo non voglio dire che il sistema degli interrupt è sparito. Tutt’altro. Ne avremo modo di riparlarne. Vorrei solo farvi capire che, se vogliamo studiare del codice che lavora in ambiente win32, dobbiamo fare i conti con queste benedette API (consiglio vivamente di reperire l’help ufficiale API, circa 8 Mbytes, ma ne vale la pena).

 

2. Hello world !

 

Ora un tipico esempio, che rimane sempre un classico. Proviamo a scrivere il programma "Hello world!" .

 

E’ molto semplice. Useremo la funzione API "MessageBoxA" , definita con il comando "extrn" command, inserire i parametri e chiamare la funzione stessa Le stringhe del messaggio devono essere ASCIIZ (ASCII,0) e che i parametri vanno inseriti in ordine inverso:

 

.386                                 ; Processore (386+)

.model flat                          ; Usiamo registri a 32 bit

 

 extrn          ExitProcess:proc     ; le API che usiamo          

 extrn          MessageBoxA:proc

 

 Con la direttiva "extrn" indichiamo l’API che useremo nel programma. ExitProcess è una API usata per ritornare il controllo al sistema operativo. MessageBoxA è usata per emettere un messaggio sullo schermo.

 

 .data

 szMessage       db      "Hello World!",0            ; Messaggio per MsgBox

 szTitle         db      "in ambiente Win32",0       ; Titolo del MsgBox

 

(Ricordiamo che TASM non assembla se non inseriamo almeno un dato)

 

.code                    ; da qui il codice

 

 HelloWorld:

                push    00000000h               ; Stile della MessageBox

                push    offset szTitle          ; Titolo del MessageBox

                push    offset szMessage        ; Il messaggio

                push    00000000h               ; Handle proprietario

 

                call    MessageBoxA             ; API

 

I campi in entrata delle funzioni sono:

 

 int MessageBox(                                                         

   HWND hWnd,              // handle of owner window                         

   LPCTSTR lpText,         // address of text in message box                

   LPCTSTR lpCaption,    // address of title of message box               

   UINT uType                 // style of message box                            

  );                                                                     

 

                   push    00000000h

                call    ExitProcess

e:

 VOID ExitProcess(                                                       

     UINT uExitCode      // exit code for all threads          

  );                                                                     

 

 end HelloWorld

 

 

3. Rings

 

Il processore ha 4 livelli di privilegi: Ring-0, Ring-1, Ring-2 and Ring-3, l’ultimo dei quali, il livello utente, è il più restrittivo. Ring-0 è il paradiso del virus coder, essendo quello che permette la massima libertà di azione. Il DOS, con i suoi interrupt, era Ring-0.

Dunque Ring-3 è lo"user level”. La maggior parte dei virus per Win32, per ora, utilizzano Ring-3. Ring-0 è il livello che il kernel usa per il suo codice, il "kernel level”. Qui il programmatore può accedere alle porte ed al altre cosine. a Ring-0 si può accedere usando uno dei trucchi attualmente conosciuti, come la IDT  modification, la tecnica "Call Gate" che SoPinKy/29A mostra  29A#3, o il VMM inserting, tecnica usata nel virus Padania o in Fuck Harry. Qui i programmatori non hanno bisogno delle API, dato che utilizzano i servizi VxD. Studieremo, più oltre, queste tecniche.

 

4. Alcuni concetti

 

Selettori

Che cos’è un selettore ? E’ un segmento molto grande, che forma la memoria di Win32, chiamata anche  flat memory. Qui possiamo indirizzare 4 Gigabytes di memoria (4.294.967.295 bytes), solo usando offsets di 32 bit. Vediamo come è organizzata questa memoria:

 

------------------------------------------- OFFSET = 00000000h <-> 3FFFFFFFh

    Application Code And Data                              

------------------------------------------- OFFSET = 40000000h <-> 7FFFFFFFh

          Shared Memory                    

------------------------------------------- OFFSET = 80000000h <-> BFFFFFFFh

              Kernel                        

 ------------------------------------------ OFFSET = C0000000h <-> FFFFFFFFh

          Device Driverz                    

 ------------------------------------------ Risultato: abbiamo 4 Giga di memoria utilizzabile.

 

NOTA: WinNT tiene le ultime due sezioni separate dalle prime due.

 

VA:

sta per indirizzo virtuale (Virtual  Address). E’ l’indirizzo di un oggetto in memoria (ricordiamo che in Windows gli oggetti non sono esattamente uguali in memoria e su disco).

 

RVA:

 sta per indirizzo virtuale relativo (Relative Virtual Address). E’ l’ offset  di qualcosa relativo a dove il file è mappato in memoria (is memory- mapped) (dal sistema o dall’utente).

 

RAW Data:

è il nome che useremo per i dati fisicamente presenti su disco e come esattamente si presentano su disco (dati disco != dati ram).

 

Virtual Data:

è il nome che diamo ai dati quando vengono caricati in memoria dal loader del sistema.

 

File Mapping:

 Tecnica, implementata in tutti gli ambienti Win32, che consiste nella manipolazione dei files più veloce ed economica. Tutto quello che modifichiamo in memoria viene automaticamente anche modificato su disco. Il File Mapping è anche il solo modo er scambiare informazioni tra processi che lavorano in un ambiente Win32.

 

 

5. il KERNEL32

 

Quando eseguiamo un’applicazione, il codice è chiamato da una parte del codice del KERNEL32 (per la verità è come se il KERNEL facesse una CALL al nostro codice). Quando viene effettuata una chiamata, l’indirizzo di ritorno è deposto nello stack (che in memoria è ESP). Vediamo un esempio pratico:

 

        .586p                         

        .model  flat                   

 

        .data                           ; necessario per TASM32/TLINK32

       

        db      ?

 

        .code

 

 start:

        mov     eax,[esp]               ; ora EAX dovrebbe essere BFF8XXXXh (se w9X)

                                        ; da qualche parte l’API CreateProcess

        ret                             ; Return

end    start

 

Noi abbiamo in EAX un valore del tipo BFF8XXXX (XXXX non è importante. La piattaforma Win32 usualmente arrotonda superiormente tutto ad una pagina, così noi possiamo cercare all’inizio di ogni pagina, così l’header KERNEL32 è all’inizio di una pagina, noi possiamo facilmente verificarlo. Quando troviamo l’header PE, noi conosciamo l’indirizzo di base del KERNEL32. Come limite dovremmo stabilire 50h pagine

 

        .586p

        .model  flat

 

 extrn  ExitProcess:PROC

 

        .data

 limit  equ     5

        db      0

 

;------------------------------------

; dati non utilizzati

;------------------------------------

 

        .code

 

 test:      

        call    delta

 delta:

        pop     ebp

        sub     ebp,offset delta

 

        mov     esi,[esp]

        and     esi,0FFFF0000h

        call    GetK32

 

        push    00000000h

        call    ExitProcess

 

Il primo blocco di istruzioni è per ricavare il delta offset (già spiegato in    T_4). Nel secondo blocco noi mettiamo in ESI l’indirizzo dal quale la nostra applicazione è chiamata, che è l’indirizzo mostrato da ESP (se non tocchiamo lo stack dopo aver caricato il programma). La seconda istruzione AND è per ricavare l’inizio della pagina da cui il nostro codice è stato chiamato. Noi chiamiamo la nostra routine, quindi terminiamo il processo.

 

 GetK32:

 

 __1:

        cmp     byte ptr [ebp+K32_Limit],00h   ; oltrepassiamo i limiti?

        jz      WeFailed

 

        cmp     word ptr [esi],"ZM"             ; ZM file ?

        jz      CheckPE                         ; Ok passa oltre

 

 __2:

        sub     esi,10000h                      ; togliamo 10 pagine

        dec     byte ptr [ebp+K32_Limit]

        jmp     __1

 

Per prima cosa verifichiamo se oltrepassiamo il nostro limite (50  pagine). Poi controlliamo se all’inizio della pagina (come dovrebbe essere) ci sono i caratteri MZ. Se ci sono andiamo a controllare se siamo in un header PE. Altrimenti sottraiamo 10 pagine (10000h bytes), decrementiamo la variabile del limite e cerchiamo di nuovo.

 

 CheckPE:

        mov     edi,[esi+3Ch]

        add     edi,esi

        cmp     dword ptr [edi],"EP"

        jz      WeGotK32

        jmp     __2

 WeFailed:

        mov     esi,0BFF70000h

 WeGotK32:

        xchg    eax,esi

        ret

 

 K32_Limit      dw      limit

 

 Leggiamo il valore all’offset 3Ch dall’header MZ  (prendendo l’indirizzo RVA da dove comincia l’header  PE), normalizziamo questo valore con l’indirizzo della pagina e se a questo indirizzo troviamo i caratteri PE, abbiamo trovato quello che cerchiamo.

 

end     test

 

 

6. Indirizzi delle API

 

Quando indichiamo una funzione API come extern il file import32.lib ci fornisce l’indirizzo della funzione ed essa è assemblata nel codice. Tuttavia c’è un problema quando scriviamo un programma che debba utilizzare tali API. Se noi “hard-codifichiamo” (hardcode) – nome che diamo quando usiamo un indirizzo assoluto per chiamare una API – probabilmente accadrà che tale indirizzo non lavorerà nella prossima versione di Win32.

Cosa potremmo fare ? Esiste una funzione, GetProcAddress, che ritorna l’offset dell’API desiderata. Purtroppo GetProcAddress è un’API a sua volta, e quindi non possiamo ricavare il suo indirizzo corretto.

Ci sono varie possibilità. ne elenchiamo due:

 

 1. Cercare GetProcAddress nella tabella di Exports;

 2. Quando infettiamo un file, cercare nelle funzioni importate GetProcAddress.

 

Il metodo più facile è la seconda, e sarà quello che illustreremo.

 

Se diamo un’occhiata al formato dell’header PE, troviamo all’offset 78h (dell’header) l’RVA della tabella di exports. Noi abbiamo bisogno di prendere l’indirizzo della tabella exports del kernel. Per Windows 95/98, il kernel è all’offset 0BFF70000h, e in Windows NT il kernel sembra essere a 077F00000h.

 

 In Win2k lo troviamo all’offset 077E00000h. Così, prima di tutto dobbiamo caricare il suo indirizzo nel registro che utilizzeremo come puntatore. Raccomandiamo di usare ESI, perché possiamo ottimizzare del codice con l’istruzione LODSD. Quindi controlliamo se  nell’indirizzo puntato abbiamo l’usuale “MZ” (ricordiamo che Intel  mantiene i dati rovesciati), dato che kernel è una libreria DLL, e le librerie hanno l’header PE. Dopodiché cerchiamo all’offset image_base+[3Ch] (= offset dove il  the kernel  è locato + indirizzo contenuto dall’header PE del KERNEL' a 3Ch), ed effettuiamo il controllo dei caratteri "PE\0\0", (PE signature).

 

Se tutto è a posto, procediamo. Abbiamo bisogno dell’RVA della tabella degli export. Lo troviamo all’offset 78h del PE header. Lo prendiamo ma, come sappiamo, l’RVA (Relative Virtual Address), come indica il nome, è un indirizzo relativo ad un offset, in questo caso l’image  base del kernel. Per “normalizzare” l’indirizzo RVA sommiamo l’offset del kernel al valore trovato del RVA. In questa maniera siamo nella tabella degli export.

 

Diamogli un’occhiata:

 

------------------------------------------- +00000000h

           Export Flags                  Size : 1 DWORD

------------------------------------------- +00000004h

         Time/Date stamp                         Size : 1 WORD

------------------------------------------- +00000006h

          Major version                             Size : 1 WORD

------------------------------------------- +00000008h 

          Minor version                             Size : 1 DWORD

------------------------------------------- +0000000Ch 

          Name RVA                                Size : 1 DWORD

------------------------------------------- +00000010h 

   Number Of Exported Functions         Size : 1 DWORD

------------------------------------------- +00000014h

   Number Of Exported Names             Size : 1 DWORD

-------------------------------------------+00000018h

   Export Address Table RVA               Size : 1 DWORD

------------------------------------------- +0000001Ch

  Export Name Pointers Table RVA       Size : 1 DWORD

-------------------------------------------- +00000020h

      Export Ordinals RVA              Size : 1 DWORD

--------------------------------------------

                                                 Totale 24h BYTES

 

Per noi sono importanti gli ultimi sei campi. I valori di Address Table RVA, Name Pointers RVA e Ordinals RVA sono relativi all’indirizzo della base del KERNEL32. Così, il primo passo per ottenere un indirizzo API consiste nel conoscere la posizione che quell’API occupa. La via più semplice per saperlo è di cercare all’offset  indicato da Name Pointers, confrontare la stringa con il nome dell’API che cerchiamo, e se uguale ne calcoliamo l’offset.

A questo punto avremo un valore nel contatore, che dobbiamo incrementare ogni volta che controlliamo il nome di una API. Questo contatore avrà il numero dell’API richiesta. Il contatore dovrebbe essere una  word o una dword,  ma non un byte, perché le API sono più di 255 )

 

 NOTA Si assume che si sia salvato nelle variabili corrispondenti il VA (RVA+kernel image base) dell’indirizzo, del nome e  dell’Ordinal tables.

 

Immaginiamo di avere il nome dell’API desiderata, così abbiamo nel contatore la posizione che essa occupa nella Name Pointers table. Noi prendiamo il valore del contatore e con esso andiamo a cercare nella Ordinal Table (un array di  dwords) l’ordinale della API che vogliamo. Una volta ottenuto il numero ‘dordine che la API occupa nell’array, dobbiamo moltiplicare tale valore per 2 (si rammenta che l’array di ordinali è costituito da words, così noi dobbiamo effettuare il calcolo per lavorare con words...), e  naturalmente aggiungere ad esso l’offset dell’inizio della tabella degli ordinali (beginning offset).

 

Per riassumere quanto è stato esposto, si consideri la seguente formula:

 

 locazione API Ordinale: ( contatore * 2 ) + Ordinal Table VA

 

Il passo successivo consiste  nel ricavare l’indirizzo definitivo della API dalla Address Table. Noi abbiamo già l’ordinale dell’API. Noi dobbiamo solo moltiplicare l’ordinale per 4 (date che l’array di indirizzi è formato da dwords invece che words, e la misura di una dword è 4), e sommare ad esso l’offset dell’inizio della Address  Table, come abbiamo fatto prima con la tabella degi ordinali. Ed infine abbiamo l’RVA dell’API  cercata. L’ultima operazione è quella di normalizzare l’indirizzo ottenuto aggiungendogli offset del kernel, ed è fatta.

 

indirizzo API: ( API's Ordinale * 4 ) + indirizzo Table VA + KERNEL32 imagebase

 

--------------------------------------- dato che ricaviamo la posizione che la stringa occupa nella Name Table,

  EntryPoint     Ordinal    Name             noi possiamo sapere il suo ordinale (ogni nome di API ha un

                                                            ordinale chel è nella stessa posizione del nome della API), e

 00005090     0001   AddAtomA         conoscendo l’ordinale possiamo saere il suo indirizzo e il suo RVA

                                                            di entrypoint. Normalizzaiamo questo valore ed otteniamo quello

  00005100    0002   AddAtomW        di cui abbiamo bisogno, l’indirizzo API cercato.

                                                          

  00025540    0003  AddConsoleAliasA 

                                                         

  00025500    0004  AddConsoleAliasW 

   ……..          ….       …………….

 

Ed ecco la routine che esegue tutto questo:

 

 

; GetAPI & GetAPIs

;

; Queste procedure sono divise in due blocchi: GetAPI ricava il nome di una API, mentre GetAPIs 

; ricava gli indirizzi di tutte le API che vogliamo.

 

 GetAPI         proc

 

I parametri sono I seguenti:

                                 

 INPUT     - ESI : puntatore al nome dell’API

 OUTPUT - EAX : indirizzo API

 

        mov     edx,esi                         ; Salvo il ptr al nome API

 @_1:   cmp     byte ptr [esi],0                ; carattere Null-terminated ?

        jz      @_2                             ; Si, prendiamo

        inc     esi                             ; No, continuiamo a cercare

        jmp     @_1                             ;

 @_2:   inc     esi                             ;

        sub     esi,edx                         ; ESI = misura nome API

        mov     ecx,esi                         ; ECX = ESI

 

 

Abbiamo in ESI il puntatore al nome dell’API. Immaginiamo di cercare "FindFirstFileA":                                           

FFFA         db   "FindFirstFileA",0                                    

                           ^ il puntatore è qui                                  

 

 Abbiamo bisogno di mantenere questo puntatore a sapere la misura del nome dell’API, così preserviamo  l’iniziale puntatore al nome dell’ API in un registro come EDX (che non useremo). Quindi incrementiamo il puntatore in ESI fino a che [ESI] = 0. 

 

FFFA         db   "FindFirstFileA",0                                    

                                                   ^ il puntatore ora è qui               

 

Dopodichè sottraiamo il vecchio puntatore al nuovo puntatore ottenendo la misura in caratteri del nome della API (la misura ci servirà successivamente) e lo salviamo in ECX, (altro registro che non useremo).

 

        xor     eax,eax                         ; EAX = 0

        mov     word ptr [ebp+Counter],ax       ; contatore azzerato

 

        mov     esi,[ebp+kernel]                ; ricavo kernel's PE head. offset

        add     esi,3Ch                         ; ‘PE’ offset

        lodsw                                   ; da ESI in AX

        add     eax,[ebp+kernel]                ; Normalizziamo ‘PE’ offset

 

        mov     esi,[eax+78h]                   ; ricavo RVA della Export Table

        add     esi,[ebp+kernel]                ; Ptr a indirizzo RVA Address Table                            

        add     esi,1Ch                         ; saltiamo i dati della struttura

 

Ricordiamo che l’istruzione lodsw trasferisce in AX la parola indirizzata da ESI, mentre lodsd (più sotto) carica una doppia parola in EAX. Prima di tutto puliamo EAX, e azzeriamo la variabile contatore per non incorrere in errori. Ricordate l’offset 3Ch di un PE file (contando dall’image base, MZ mark) ?. Noi stiamo lavorando sulla base dell’header del KERNEL32 PE; dato che esso è  un RVA, lo normalizziamo ed così facendo otteniamo l’offset dell’header PE. Ciò che dobbiamo fare a questo punto è ricavare l’indirizzo deòòa Export Table address (in PE Header + 78h),  e l’indirizzo RVA della Address Table.                                       

 

        lodsd                                   ; EAX = RVA Address Table

        add     eax,[ebp+kernel]                ; Normalizziamo

        mov     dword ptr [ebp+AddressTableVA],eax ; Store it in VA form

 

        lodsd                                   ; EAX = RVA Name Ptrz Table

        add     eax,[ebp+kernel]                ; Normalizziamo

        push    eax                             ; mov [ebp+NameTableVA],eax

 

        lodsd                                   ; EAX = RVA Ordinal Table

        add     eax,[ebp+kernel]                ; Normalizziamo

        mov     dword ptr [ebp+OrdinalTableVA],eax ; salvo in forma di VA

        pop     esi                             ; ESI = VA Name Ptrz Table

 

 

Se ricordate, noi abbiamo in ESI il puntatore  al RVA dell’ Address Table RVA, così per ricavare tale indirizzo eseguiamo un LODSD,  che inserisce la DWORD locata da ESI  nell’accumulatore, che è EAX. dato che questo è un RVA, dobbiamo normalizzarlo per ottenere il corrispettivo VA

 

Matt Pietrek dice riguardo a questo campo: "This field is an RVA and points to an array of function addresses. The function addresses are the entry points (RVA) for each exported function in this module".                                                        

 

Naturalmente noi salviamo tale valore in una variabile.

Dopodiché ricaviamo  la Name Pointers Table, della quale Matt Pietrek dice: "This field is an RVA and points to an array of string pointers. The  strings are the names of the exported functions in this module".        

 

Ma non lo salvo in una variabile, bensì nello stack, dato che lo useremo presto.

Finalmente

Matt Pietrek descrive così:: "This field is an RVA and points to an array of WORDs. The WORDs are the   export ordinals of all the exported functions in this module".          

 

 Ed è quello che abbiamo fatto.

 

 

 @_3:   push    esi                             ; Salvo ESI per l8r restore

        lodsd                                   ; ricavo valore ptr ESI in EAX

        add     eax,[ebp+kernel]                ; Normalizziamo

        mov     esi,eax                         ; ESI = VA del nome API

        mov     edi,edx                         ; EDI = ptr all’API

        push    ecx                             ; ECX = misura API

        cld                                     ; Clear direction flag

        rep     cmpsb                           ; Confronto i nomi API

        pop     ecx                             ; Restore ECX

        jz      @_4                             ; Jump se I nomi sono uguali

        pop     esi                             ; Restore ESI

        add     esi,4                           ; prendo il pross. valore dell’array

        inc     word ptr [ebp+Counter]          ; Incremento contatore

        jmp     @_3                             ; vai ancora

 

 

Prima di tutto mettiamo ESI  nello stack (dato che sarà cambiato dall’istruzione CMPSB) per ripristinarlo più tardi. Poi prendiamo la DWORD puntata da ESI (Name Pointerz Table) e la mettiamo nell’accumulatre (EAX), attraverso l’istruzione LODSD. Normalizziamo aggiungendo il kernel base address.

 

Ora noi abbiamo in EAX un puntatore al nome di una API, ma no sappiamo ancora di quale API si tratta. Per esempio EAX potrebbe puntare a qualcosa come "CreateProcessA" ma tale PAI non ci interessa. Allora confrontiamo quella stringa con quella che noi cerchiamo (puntata ora da EDX) con CMPSB. Così prepariamo i suoi parametri: in ESI mettiamo il puntatore all’inizio dell’API nella Name Pointers Table, e in EDI mettiamo il puntatore all’API cercata. In ECX mettiamo la misura della stringa, quindi confrontiamo le due stringhe byte per byte. Se esse sono uguali viene settato il flag zero, ed effettuiamo un jump alla routine che ci fornirà l’indirizzo di quella API. Se falliamo ripristiniamo ESI, e aggiungiamo ad esso la misura di una DWORD per andare a puntare al prossimo valore nella Name Pointers Table array. Incrementiamo il contatore e continuiamo a cercare.

 

 @_4:   pop     esi                             ; evitando mondezza nello stack

        movzx   eax,word ptr [ebp+Counter]      ; mettiamo il contatore in AX

        shl     eax,1                           ; EAX = AX * 2

        add     eax,dword ptr [ebp+OrdinalTableVA] ; Normalizziamo

        xor     esi,esi                         ; pulisce ESI

        xchg    eax,esi                         ; EAX = 0, ESI = ptr to Ord

        lodsw                                   ; metto Ordinale in AX

        shl     eax,2                           ; EAX = AX * 4

        add     eax,dword ptr [ebp+AddressTableVA] ; Normalizziamo

        mov     esi,eax                         ; ESI = ptr all’indirizzo RVA

        lodsd                                   ; EAX = indirizzo RVA

        add     eax,[ebp+kernel]                ; Normaliziamo

        ret

 

Il pop è semplicemente per pulire lo stack, così non sporchiamo il nome dell’API. MOVZX (move with aero extend: sposta un operando a 8 o a 16 bit in un registro a 16 o 32 bit, inserendo una serie di 0 nella metà superiore della destinazione) Ha l’effetto di muovere nella porzione bassa di EAX il valore del contatore (che è una WORD) e azzerare la parte alta del registro. Noi lo moltiplichiamo per 2, ottenendo il numero che occupa in tabella (l’array dove cerchiamo è un array di WORD).

 

Ora aggiungiamo ad esso l’inizio dell’inizio dell’array da cui vogliamo iniziare la ricerca, e in EAX  abbiamo il puntatore all’ordinale dell’API che vogliamo. Così mettiamo EAX in ESI per usare quel puntatore paer ricavare il valore puntato che è l’Ordinale in EAX, con un semplice LODSW. In questo modo abbiamo  l’Ordinal, ma noi vogliamo l’ EntryPoint del codice dell’API, cos’ moltiplichiamo l’ordinale (che contiene la posizione dell’EntryPoint che l’API cercata occupa nella Address Table) per 4, dato che è una DWORD, e così facendo otteniamo un valore RVA, relativo cioè al RVA della AddressTable. Normalizziamo, così abbiamo in EAX il puntatore al valore della EntryPoint della API nella Address Table.Mettiamo EAX in ESI, e ricaviamo il valore puntato in EAX. Così avremo in EAX l’EntryPoint (RVA) dell’API cercata. L’ultima cosa da fare ora è normalizzare tale indirizzo con l’image base di KERNEL32.

Ed ecco l’indirizzo dell’API cercata in  EAX

 

 

 GetAPI         endp

 

------------------------------------------------------------------------------------------------------------------

 

 GetAPIs        proc

 

Questo è il codice per ricavare TUTTE le APIs utilizzando la  procedura descritta più sopra. i parametri sono:

                                                                       

 INPUT  - ESI : Puntatore alla prima API cercata (ASCIIz) – è l’indirizzo dell’array di stringhe

                          contenente i nomi delle API cercate;

                 EDI : Puntatore alla variabile che conterrà la prima API – indirizzo dell’array in cui salveremo

                          gli indirizzi delle API

OUTPUT – nulla.

 

La struttura per ricavare i valori è del tipo:

 

ESI punta a   db         "FindFirstFileA",0                         

              db         "FindNextFileA",0                          

              db         "CloseHandle",0                             

             [...]          [...]                                                 

                                       

              db    0BBh ; Marcatore della fine di questo array                                                                         

 

EDI punta a   dd         00000000h ; Futuro indirizzo di FindFirstFileA

              dd         00000000h ;                   FindNextFileA

              dd         00000000h ;                   CloseHandle

             [...]          [...]                                                 

 

 

-------------------------------------------------------------------------------------                                                                                  

 

 @@1:   push    esi

        push    edi

        call    GetAPI

        pop     edi

        pop     esi

        stosd       ; trasferisce la word da EAX (riornante da GetAPI all’elemento

                    ; della stringa indirizzato da EDI

 

 

Salviamo nello stack i valori prima di chiamare la procedura, dopodiché effettuiamo una call alla GetAPI. Assumiamo che ESI sia un puntatore al nome della API cercata e EDI il puntatore alla variabile che conterrà  il nome della API

 

 @@2:   cmp     byte ptr [esi],0

        jz      @@3

        inc     esi

        jmp     @@2

 @@3:   cmp     byte ptr [esi+1],0BBh

        jz      @@4

        inc     esi

        jmp     @@1

 @@4:   ret

 GetAPIs        endp

 

In questa maniera otteniamo gli indirizzi di tutte le API che vogliamo, a prescindere dalla versione di Windows.