8 febbraio 2008

Analisi forense su Oracle - analisi esadecimale dei Redo Logs

Le due tecniche di analisi forense su Oracle viste nei post precedenti sono semplici e relativamente rapide, ma solo l’analisi esadecimale dei Redo Logs restituisce una descrizione completa di quello che è accaduto nella base dati. Per questa tecnica, in realtà complessa e poco documentata*, è sufficiente usare uno dei tanti editor esadecimali disponibili in rete, da usare come sempre su copie dei log esatte e certificate (per garantirne la validità di prova in sede giudiziaria).

* La Oracle non fornisce ufficialmente le specifiche dei formati interni dei file. Una delle poche fonti di informazioni è il sito www.databasesecurity.com, da cui è stato tratto l’esempio riportato in questo paragrafo (si veda in particolare: D. Litchfield, “Dissecting the Redo Logs”, white paper della serie Oracle Forensics), integrato nelle parti che mi sembravano meno chiare.

La maggior parte dei file binari di dati e di log in Oracle condivide la stessa struttura. In testa è presente un header, la cui prima parte riporta la tipologia di file (valore 0x22 per i Redo Logs), la dimensione del blocco logico (solitamente 512 byte), e il numero di blocchi che compongono il file (a cui va aggiunto quello occupato dall’header, non contato). Moltiplicando il totale dei blocchi per la dimensione del singolo blocco si ottiene la dimensione effettiva del file. Segue quindi un magic number, usato dal DBMS per determinare se effettivamente si tratta di un file Oracle.La seconda parte dell’header contiene una serie di informazioni relative all’istanza del database, tra cui il suo SID (Serial Identificator), la versione del database, la data e l’ora in cui è partita l’operazione di log. Inoltre contiene quattro coppie di numeri a 32 bit: Low, Next, Enabled e Thread Closed System Change Numbers (SNC), ognuno associato a un timestamp (data e ora).

Gli SNC sono una sorta di “segnalibri” per indicare un punto in cui lo stato del database è cambiato, per esempio per effetto di una INSERT seguita da un COMMIT.
I primi due elencati, in particolare, indicano rispettivamente la prima e l’ultima transazione registrata nel log. In caso di necessità si potrà quindi ripristinare il database a una particolare transazione mediante il relativo SCN.

Riguardo ai blocchi, ognuno di essi ha un header di 16 byte che inizia con un identificativo, seguito dal numero di blocco, dal numero di sequenza e da un offset.



È da notare che un record del Redo Log può eccedere le dimensioni di un blocco, continuando nel successivo. L’investigatore dovrà tenerne opportunamente conto, facendo attenzione alla cadenza degli identificatori di inizio blocco. L’offset rappresenta il Relative Block Address (indirizzo relativo di blocco), ovvero il punto all’interno del blocco in cui inizia un nuovo record del log. Chiude l’header il valore di checksum del blocco, che garantisce l’integrità dei dati in esso riportati.

Sui checksum in particolare si concentra l’attenzione degli hacker più esperti, che cercheranno di riscrivere in breve tempo i record dei Redo Logs (mediante appositi programmi) lasciando intatto tutto il resto. In questi casi il lavoro dell’investigatore si complica in maniera esponenziale perché i checksum risulteranno falsamente corretti; ma fortunatamente sono pochissime le persone in grado di compiere operazioni del genere in un tempo limitato. È qui utile sottolineare quanto detto in altri post: una strategia di difesa in profondità complica il lavoro dell’aggressore, ed è più probabile che a qualche livello rimanga traccia del suo passaggio, nonostante i suoi tentativi - per quanto sofisticati - di eliminare le prove.


Continuiamo il quadro sulla struttura di un Redo Log parlando di come vengono gestiti i timestamp. Questi numeri a 32 bit rappresentano il numero di secondi trascorsi dalla mezzanotte del primo gennaio 1988 (rappresentata * come 01/01/1988 00:00:00).

* In realtà l’algoritmo di Oracle che produce il valore non calcola il numero esatto di secondi trascorsi, ma ai fini di questa trattazione potremo accettare questa semplificazione. Il lettore interessato ad approfondire potrà consultare il paper di Litchfield già segnalato.


Infine, descriviamo un Redo Record: per ogni SCN, esiste nel log un record che ne descrive tutte le modifiche apportate al database. Ogni record inizia con un header, seguito da uno o anche più change vector. Ad esempio, nel caso di una INSERT su una tabella con un indice verranno generati diversi change vector: uno per il redo e uno per l’undo della INSERT, un insert leaf row per l’indice e un commit.

Ai change vector è associato un codice operativo, formato da un codice di livello e da un sotto-codice operazionale separati da un punto. Esistono più di 150 codici operativi, di cui si elencano i più comuni:



A questo punto abbiamo gli elementi principali necessari per decodificare un Redo Log. I passi da seguire sono i seguenti:

  • messa in sicurezza del server violato (secondo le metodologie forensi fondamentali per garantire l’autenticità delle prove raccolte);
  • copia dei Redo Logs su cui effettuare tutte le analisi;
  • di ogni oggetto del database inventariare ID, tipo e proprietari.
Esamineremo a titolo d’esempio un unico record di uno dei file di log, la cui codifica esadecimale, riportata nella figura seguente, comincia dall’header del relativo blocco.



L’offset dell’header (0x10) indica che il record comincia all’indirizzo 0x1d2810 (0x1d2800 + 0x10). Qui troviamo la dimensione in byte del record: 0x01A8 (424 byte).

A 68 byte dall’inizio del record troviamo la coppia 0B 02, codice operativo dell’operazione: in decimale “11 02”, corrispondente a un inserimento su riga singola. Nei quattro byte preced
enti è riportato il timestamp dell’inserimento (0x24CC67F9), che è possibile convertire in forma leggibile usando all’inverso l’algoritmo di Oracle (13:15:37 del 16 marzo 2007).

A 22 byte dal codice operativo è riportato il codice identificativo dell’oggetto del database coinvolto dall’operazione. In questo caso 0x0057, ovvero 87 in decimale. Dall’inventario degli oggetti si evince che l’ID 87 corrisponde alla tabella sysauth$ (che tiene traccia dei ruoli e dei membri ad essi associati) di proprietà dell’utente sys.

La coppia di byte successiva serve per dedurre il numero di colonne coinvolte e va interpretato come dimensione in byte di un vettore. Il valore rappresentato è 0x000C, in decimale 12. Ogni elemento del vettore occupa 2 byte, da cui 6 elementi. Il primo è occupato dal valore di dimensione, il secondo e il terzo sono le coppie successive (0x0014 e 0x0031), che sono dei valori sostanzialmente fissi, quindi seguono le ultime tre coppie, che rappresentano le tre colonne su cui ha agito l’operazione che stiamo esaminando. Queste tre coppie riportano il numero di byte inseriti nelle colonne: 2 nella prima e nella seconda, 3 nella terza.

Le prime tre colonne della tabella sysauth$ sono di tipo numerico: la posizione dei valori inseriti è indicata dal byte immediatamente successivo, che può valere 01 (valori a 72 byte di distanza), 02 (valori a 64 byte di distanza) o 17 (valori a 120 byte di distanza). Nel nostro caso il valore è 01, per cui individuiamo il primo dato (0xC102) a 72 byte di distanza.


I valori numerici sono gestiti da Oracle in modo particolare. I numeri decimali vengono separati in coppie (per esempio 12345 in 01-23-45) e l’ordine di grandezza è codificato su un byte: C1 per i numeri compresi tra 1 e 99, C2 per numeri tra 100 e 9.999, C3 per numeri tra 10.000 e 1.000.000 e così via. Ad ogni coppia, se diversa da zero, viene associato un byte e al numero da rappresentare - in esadecimale - è aggiunto 1. A titolo di esempio:



Come si diceva, il primo dato inserito è 0xC102, seguito da 0xC105 e 0xC20931, ovvero i valori 1, 4 e 848. Allora possiamo ricostruire l’operazione compiuta alle 13:15:37 del 16 marzo 2007:

INSERT INTO SYS.SYSAUTH$ (GRANTEE#,PRIVILEGE#, SEQUENCE#) VALUES (1,4,848);

Dall’inventario compiuto a inizio indagine si evince che l’utente “1” corrisponde a PUBLIC e che il privilegio “4” è quello di DBA. In altre parole, questa operazione ha reso l’utente PUBLIC (ovvero chiunque!) un amministratore del database.

Nel log, ogni record riporta subito dopo l’operazione anche la relativa UNDO, nel quale è segnalato anche l’ID dell’utente che ha impartito il comando. Nel caso della INSERT in esame il valore è 0x36 (54 decimale): consultando la mappatura degli ID si potrà capire se l’operazione è stata effettuata dall’utente sys, come sarebbe lecito aspettarsi (l’accesso alla tabella sysauth$ gli è riservato), o se non si tratti piuttosto di un utente a cui è stato illecitamente consentito l’accesso. In tal caso, individuato l’utente con il profilo compromesso, si dovrà procedere a investigare sulle operazioni che sono state da lui compiute e su quelle che hanno portato all’assegnazione di privilegi non previsti. In questo modo si riuscirà a riscostruire una linea temporale delle operazioni illecite compiute sul database, da presentare in sede giudiziaria.

L’esempio trattato, apparentemente complicato, è in realtà uno dei casi più semplici tra quelli affrontati nell’analisi forense delle basi dati, e rappresenta una delle operazioni preliminari dell’indagine. Sul sito segnalato sono presenti anche gli altri white paper di Litchfield sull'analisi forense su Oracle: la lettura è molto interessante, e la consiglio per chi è interessato a questi argomenti.

Nessun commento: