Come potrei fare per ricostruire l'IAT di un eseguibile compresso?

10

Quando gli eseguibili sono impacchettati con uno strumento come UPX, il codice reale e le sezioni di dati vengono crittografati o oscurati, quindi caricati in memoria utilizzando uno stub decrypter iniettato. Ciò rende impossibile l'analisi statica.

Per ovviare a ciò, normalmente eseguivo il file eseguibile, collego un debugger, eseguo un dump della memoria, quindi uso quel dump per produrre un eseguibile decompresso. Sfortunatamente, questo distrugge la tabella degli indirizzi di importazione (IAT).

Sono a conoscenza di alcuni strumenti che possono essere utilizzati per applicare patch allo IAT, ma non so come funzionano internamente. Come faccio a ricreare manualmente lo IAT?

    
posta Polynomial 12.10.2012 - 22:48
fonte

2 risposte

10

Ok, ho detto che avrei cercato una risposta ed eccolo qui, come promesso.

In primo luogo, volevo costruire un obiettivo reale con cui giocare. Non c'è niente come un esempio funzionante e tangibile, e questo è un processo molto manuale. Quindi, senza ulteriori indugi, ho compilato questo in MSVC con /MT :

#define WINVER 0x501
#define _WIN32_WINNT   0x0501
#define _WIN32_WINDOWS 0x0501
#define _WIN32_IE      0x0501
#define UNICODE

#include <wchar.h>
#include <windows.h>

int APIENTRY wWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    MessageBox(NULL, L"Hello, world.", L"A messagebox", MB_OK | MB_ICONEXCLAMATION);
    return 0;
}

Ci aspetteremmo che questo bestiale importasse user32.dll!MessageBoxW e sicuramente lo faccia effettivamente.

Per trovarlo all'interno dell'eseguibile stesso, usiamo WinDbg. Io uso WinDbg per due motivi: in primo luogo, non posso permettermi IDA, e in secondo luogo, è lo stesso debugger che useresti per un driver del kernel (anche con IDA - che usa il suo motore) quindi è buono, anche se un po 'non intuitivo.

Ho compilato il mio eseguibile a UnpackSimple.exe per l'architettura x86, poiché UPX non supporta Win64. Ora, la prima cosa che noterai quando lo esegui sotto WinDbg è questa linea:

ModLoad: 01360000 01370000   UnpackSimple.exe

Proprio lì, vediamo l'intervallo di indirizzi a cui è stato mappato UnpackSimple.exe . Questo è importante, perché tutti gli indirizzi nella sua intestazione sono relativi a 01360000 .

Quindi ora, per trovare le informazioni dell'intestazione, possiamo semplicemente richiederle:

!dh UnpackSimple.exe
OPTIONAL HEADER VALUES

    10B magic #
   11.00 linker version
    5E00 size of code
    6C00 size of initialized data
       0 size of uninitialized data
    1193 address of entry point
    1000 base of code
         ----- new -----
01360000 image base            <--- this is the image base address.
    1000 section alignment
     200 file alignment
       2 subsystem (Windows GUI)
    6.00 operating system version
    0.00 image version

    ... blah blah blah ....

   0 [       0] address [size] of Export Directory
8D54 [      3C] address [size] of Import Directory
D000 [     1E0] address [size] of Resource Directory
   0 [       0] address [size] of Exception Directory
   0 [       0] address [size] of Security Directory
E000 [     6D0] address [size] of Base Relocation Directory
7150 [      38] address [size] of Debug Directory
   0 [       0] address [size] of Description Directory
   0 [       0] address [size] of Special Directory
   0 [       0] address [size] of Thread Storage Directory
89D8 [      40] address [size] of Load Configuration Directory
   0 [       0] address [size] of Bound Import Directory
7000 [     108] address [size] of Import Address Table Directory
   0 [       0] address [size] of Delay Import Directory
   0 [       0] address [size] of COR20 Header Directory
   0 [       0] address [size] of Reserved Directory

Oh guarda, uno IAT! Vive a 01360000+7000 e possiamo convincere WinDbg a fornirci tali informazioni in modo abbastanza semplice. Il comando dps che stiamo per utilizzare è abbreviato per dump pointers, dereferencing with symbols , richiede un indirizzo argomento e uno spazio per indirizzo di ingresso.

dps 01360000+7000 L108/4
01367000  75b3d7ea kernel32!TerminateProcessStub
01367004  75b23f3c kernel32!CreateFileWImplementation
01367008  75b2520b kernel32!GetCommandLineWStub
... <snip> ...
013670f4  75b47ab2 kernel32!WriteConsoleW
013670f8  75b21410 kernel32!CloseHandleImplementation
013670fc  00000000
01367100  76bafd3f USER32!MessageBoxW
01367104  00000000

Puoi vedere alcune importanti caratteristiche dello IAT qui. Il primo è l'indirizzo sul lato sinistro - Indirizzo di IAT. L'indirizzo a sinistra è l'indirizzo in memoria per la DLL. Infine, nota che ci sono alcune voci zero. Ogni importazione DLL termina con una voce zero, dicendo al caricatore che non ci sono più voci da caricare.

Ora esamineremo lo smontaggio della funzione principale. Per fare ciò, digita questi due comandi

bp UnpackSimple!wWinMain
g

Quindi interromperai la partecipazione al wWinMain sopra. Ora, se guardi da vicino:

UnpackSimple!wWinMain:
    01361000 6a30            push    30h
    01361002 68a0893601      push    offset UnpackSimple!'string' (013689a0)
    01361007 68bc893601      push    offset UnpackSimple!'string' (013689bc)
    0136100c 6a00            push    0
    0136100e ff1500713601    call    dword ptr [UnpackSimple!_imp__MessageBoxW (01367100)]
    01361014 33c0            xor     eax,eax
    01361016 c21000          ret     10h

Non sorprendentemente, c'è la nostra funzione, _imp__MessageBoxW , che fa riferimento a una bella voce IAT:)

Quindi ora la domanda è: come mai andiamo indietro? Bene, per prima cosa costruisci qualcosa che non funziona, dobbiamo procurarci un pacchetto completo.

A tal fine, ho eseguito upx.exe -o UnpackSimplePacked.exe UnpackSimple.exe per produrre un bel file binario. Carica questo file binario in WinDbg e la prima cosa che noterai è:

!dh UnpackSimplePacked.exe

Non fa assolutamente nulla. Tuttavia, possiamo notare che:

ModLoad: 01220000 01233000   image01220000

è stato visto - quindi l'immagine di cui abbiamo bisogno è lì, nella memoria. Semplicemente non esiste come un'immagine su disco. In questa fase, suppongo che upx carichi effettivamente le DLL direttamente da sé, piuttosto che usare il caricatore NT. Possiamo provare questo in due modi: in primo luogo, se digiti sxe ld in WinDbg, si interromperà su tutti i carichi del modulo. Nessuna interruzione per user32.dll , né è già stata caricata.

In secondo luogo, come volevi vedere:

    0 [       0] address [size] of Import Address Table Directory

È completamente vuoto.

Tuttavia, se controlliamo l'elenco dei moduli caricati:

lm
start    end        module name
00210000 00223000   image00210000   (deferred)             
74c20000 74c2c000   CRYPTBASE   (deferred)             
74c30000 74c90000   SspiCli    (deferred)             
74d80000 74e20000   ADVAPI32   (deferred)             
74eb0000 74f4d000   USP10      (deferred)             
74f50000 74f5a000   LPK        (deferred)             
75030000 750dc000   msvcrt     (deferred)             
755a0000 755b9000   sechost    (deferred)             
75720000 757b0000   GDI32      (deferred)             
757e0000 758d0000   RPCRT4     (deferred)             
75b10000 75c20000   kernel32   (deferred)             
76ab0000 76af7000   KERNELBASE   (deferred)             
76b40000 76c40000   USER32     (deferred)             
77550000 776d0000   ntdll      (pdb symbols) 

Come puoi vedere, USER32 ha spazio assegnato pronto per l'uso.

Ora ecco alcuni trucchi. Ho aperto un'altra finestra WinDbg con la mia versione di UnpackSimple.exe e ho trovato l'indirizzo di user32!MessageBoxW . Avendo ottenuto ciò, ho trovato che era 76bafd3f da un offset di base di 76b40000 dandomi un offset di 6FD3F . Nella mia versione imballata, ho impostato un breakpoint su bp 76b40000+6FD3F e sicuramente, c'era una traccia dello stack!

k
003cfd0c 013a1014 USER32!MessageBoxW
WARNING: Stack unwind information not available. Following frames may be wrong.
003cfd6c 75b233aa image013a0000+0x1014
003cfd78 77589ef2 kernel32!BaseThreadInitThunk+0xe
003cfdb8 77589ec5 ntdll!__RtlUserThreadStart+0x70
003cfdd0 00000000 ntdll!_RtlUserThreadStart+0x1b

Il disassemblaggio nella posizione in image0... assomiglia a questo:

013a100e ff1500713a01    call    dword ptr [image013a0000+0x7100 (013a7100)]
013a1014 33c0            xor     eax,eax
013a1016 c21000          ret     10h

Ora stiamo arrivando da qualche parte! A quell'indirizzo troverai alcuni dati interessanti:

db 013a7100
013a7100  3f fd ba 76 00 00 00

Stranamente, c'è il nostro indirizzo! 76bafd3f nel buon vecchio piccolo endian. Facciamo un passo indietro:

db 013a7098
013a7098  a0 22 57 77 60 22 57 77-c9 14 b2 75 ff 10 b2 75  ."Ww'"Ww...u...u
013a70a8  7b 44 b2 75 9c 17 b2 75-91 d1 b4 75 71 51 b2 75  {D.u...u...uqQ.u
013a70b8  45 49 b2 75 c4 d1 b4 75-13 49 b2 75 b3 d1 b4 75  EI.u...u.I.u...u
013a70c8  46 e0 57 77 f1 24 59 77-0d 17 b2 75 46 19 b2 75  F.Ww.$Yw...uF..u
013a70d8  2a 30 58 77 61 48 ba 75-83 46 b2 75 07 7c bc 75  *0XwaH.u.F.u.|.u
013a70e8  28 13 b2 75 bf 45 ba 75-ef c7 b3 75 b2 7a b4 75  (..u.E.u...u.z.u
013a70f8  10 14 b2 75 00 00 00 00-3f fd ba 76 00 00 00 00  ...u....?..v....

Vedo i puntatori!

Questo è lo IAT che abbiamo trovato in precedenza, o almeno un array di IMAGE_IMPORT_DESCRIPTOR thunks.

Quindi, arrivato così lontano, cosa rimane? Bene, dovremmo cercare nella memoria di dumping per cercare di trovare le funzioni che corrispondono alle DLL di Windows conosciute. Da lì, possiamo iniziare a ricostruire uno IAT completo per il nostro nuovo file PE. Sospetto che il processo ImpRec abbia esito positivo, come descritto nella risposta di Viv è cercare di trovare queste voci come ho fatto io manualmente. Ho scelto di utilizzare i punti di interruzione, ma ci sono diversi modi in cui questo potrebbe essere automatizzato in modo diverso, immagino. Non ho mai scritto uno strumento automatico per passare il processo a mano ...! Detto questo, ad un'ipotesi che ho recuperato le istruzioni di call ptr , esaminare le voci al di fuori di una regione mappata tentare di trovare quelle contro gli offset DLL noti farebbe il trucco.

Due note:

  1. Ho scritto questa risposta come risposta del tipo "ecco come attaccherei manualmente", con uno sfondo pertinente, non un "usa questo strumento".
  2. Fai attenzione all'ASLR. Gli indirizzi dei moduli continueranno a spostarsi, quindi tieni d'occhio il cambiamento delle basi dei moduli!
risposta data 16.10.2012 - 18:20
fonte
4

Prima di tutto per una panoramica generale del formato pe, ti suggerisco di leggere pecoff formato file fornito da Microsoft. La tabella di importazione viene distrutta parzialmente o completamente dalla maggior parte dei packer. Imprec è solitamente la scelta preferita per ricostruire lo IAT (Import Address Table) ma se vuoi davvero entrare nei dettagli puoi leggere questo eccellente articolo- PE packer utilizzati nel software dannoso di Paul Craig di valutazione della sicurezza team.Puoi anche provare uno strumento open source scylla

Negli eseguibili di Windows, la tabella di importazione è la tabella (directory dei dati) che contiene tutte le informazioni di importazione (nome di DLL e funzioni riferite nello spazio degli indirizzi) di quella immagine.

IMAGE_IMPORT_DESCRIPTOR ha il seguente formato:

struct IMAGE_IMPORT_DESCRIPTOR {
DWORD   OriginalFirstThunk; //same as FirstThunk
DWORD   TimeDateStamp;
DWORD   ForwarderChain;     //usually set to 0
DWORD   Name;               //name of the dll
DWORD   FirstThunk;         //before loading,contains the pointer to the api name thunk array 
};

Esiste un descrittore per ogni DLL importata e l'ultimo è costituito da NULL. Il caricatore di Windows lo farà carica tutte le DLL indicate (ricorda una DLL per ogni IMAGE_IMPORT_DESCRIPTOR ) nell'eseguibile visualizzando il campo del nome della struttura. Quindi cerca di costruire lo IAT nel modo seguente. Per ogni nome nella matrice puntata da FirstThunk , sostituisce quell'indirizzo dall'effettivo indirizzo dell'api. Se l'indirizzo del nome di api è non trovato in FirstThunk , andrà al OriginalFirstThunk e cercherà di ottenere informazioni da lì (consideralo come un backup di FirstThunk ). Il sovrascritto FirstThunk è il tuo IAT! (tutto di questo accade prima del puntatore dell'istruzione ie. l'eip raggiunge il punto di ingresso dell'exe).

Durante la costruzione della nuova tabella di importazione (o la correzione del file vecchio), Imprec prima di tutto troverà l'IAT cercando automaticamente nel codice o attraverso il valore fornito dall'utente. Dopo quello dal fresco IAT troverà i nomi API dai loro indirizzi e popolerà il IMAGE_IMPORT_DESCRIPTORs . Questo facilita il nostro lavoro in quanto il caricatore ora sarà in grado di trovare tutte le informazioni nella tabella di importazione. Questo è visivamente spiegato molto bene in lena's tutorial no. 21.

    
risposta data 13.10.2012 - 20:31
fonte

Leggi altre domande sui tag