ShellFl0w – Architetture ed Assembly Hack

Introduzione

Se vogliamo davvero essere in grado di trovare vulnerabilità in un sistema computazionale, non possiamo non conoscere le architetture di sistema, come funzionano le allocazioni di memoria e la gestione dell’astrazione (e non) software del PC. In fatti se noi non sapessimo che l’unita’ di base della memoria e’ il byte, non riusciremmo a capire come nascono e si sfruttano molte della vulnerabilità software. Quindi in questo articolo tratterò le basi teoriche della architettura dei sistemi e continueremo con Assembly. Io vi consiglierei di approfondire sempre gli argomenti che tratto e che tratterò in questa rubrica, considerando che più cose si conoscono, più facile è la comprensione e l’ingegno per un exploit di una vulnerabilità.

Architetture

Bene, ora qui verrano messa in lista alcune informazioni che sono da conoscere obbligatoriamente.

  • L’unità di base della memoria è il byte che in memoria è contrassegnato da un numero unico che è il suo indirizzo.
  • Spesso la memoria può essere sfruttata anche in pezzi più grandi di un singolo byte, e a queste sezioni più grandi sono state dati dei nomi, che sono:
    Word: 2 Byte
    Double Word: 4 Byte
    Quad Word: 8 Byte
    Paragraph: 16 Byte
  • Tutti i dati in memoria sono numerici. I Caratteri sono memorizzati utilizzando un codice carattere che associa un numero a ciascun carattere.
  • La CPU può accedere ai dati nei registri in modo molto più veloce rispetto ai dati in memoria.
  • L’hardware di un computer fornisce un meccanismo chiamato Interrupts che serve per interrompere un flusso ordinario di un programma per elaborare eventi che richiedono una risposta rapida.
  • Ogni istruzione assembly rappresenta esattamente una istruzione in linguaggio macchina.
  • Ogni tipo di CPU capisce solo il suo linguaggio macchina.

Assembly Hack

In questo articolo tratteremo Assembly con i registri 32/16/8-bit , perché è importante saper lavorare con entrambe le architetture e soprattutto tornerà utile per una conversione di qualche exploit.

Bisogna tenere in considerazione che ogni istruzione è composta da una istruzione in formato mnemonic (comprensibile all’essere umano) più degli operandi facoltativi in base alla menmonic (argomenti).
Il calcolatore comprende l’opcode rappresentate la mnemonic e l’oprand rappresentate gli argomenti o operandi.
Questa conversione per fortuna è compito dell’assembler.

Direttive

Una direttiva e’ un artefatto dell’assembler, non della CPU. Le direttive generalmente sono usate per istruire l’assembler o di fare qualcosa o di informarlo di qualcosa.

Non sono tradotte in codice macchina.

Direttive Dati

Per definire spazi di memoria vengono usati i segmenti di dati, esistono due modi per compiere questo lavoro e sono:

  1. L’uso della direttiva RESX (Che verrà sostituita con la lettera che indentifica gli oggetti memorizzati)
  2. L’uso alternativo della direttiva DX dove la lettera X ha lo stesso uso della direttiva RESX e puo’ assumere gli stessi valori.

E invece per RESX

Altro punto molto importante è il seguente:

In L5 definisco 4 byte.
In L6 definisco una stringa: “hack”.
In L7 definisco lo stesso risultato della stringa L6.

Ci sono molte cose da tenere in considerazione mentre si programma in Assembly, ad esempio si devono fare degli accorgimenti sopratutto su:

  • La direttiva DD puo’ essere usata sia con interi che con costanti in virgola mobile a singola precisione (virgola mobile a singola precisione e’ equivalente ad una variabile di tipo float in C).
  • La direttiva DQ puo’ invece essere usata solo per definire costanti in virgola mobile a doppia precisione.

Etichette

Se volessimo riferirci a dati nel codice potremmo usare le etichette, ci sono banalmente due modi in cui le etichette possono essere usate:

  • Etichetta Piana: E’ interpretata come offset (o indirizzo) dei dati.
  • Etichetta con Brackets Square: Questa è interpetrata come valore dei dati a quell’indirizzo.

Etichetta piana: mov eax, msg
Etichetta con Brackets Square: mov eax, [msg]

Si può pensare quindi ad una label come un puntatore, mentre le parentesi quadre (Brackets Square) dereferenziano il puntatore tipo l’asterisco in C.

Lavorare con gli interi

Gli interi possono essere contraddistinti in 2 tipi:

Signed: Possono essere sia positivi che negativi
Unsigned: Non possono essere negativi e sono rappresentati direttamente in binario.

Ma in forma binaria, come fa il computer a definirne il tipo? Esistono varie metodologie tra i quali signed magnitude.
Con questa metodologia si scompone l’intero in 2 parti, di quale la prima parte rappresenta il bit del segno, mentre la seconda il magnitude dell’intero.
Link: https://en.wikipedia.org/wiki/Signed_number_representations
Ma questa metodologia non è consigliabile in quanto, in tal caso noi trovassimo il valore +0 (00000000) o -0 (10000000), il “gioco” casca, perché? Perché  sappiamo che matematicamente parlando, 0 non è ne positivo, ne negativo, perciò è  valore neutro e in quanto neutro esso non può assumere nessun segno. Queste informazioni seppur datate non le spiego nel dettaglio in quanto sono facili da reperire e poi vi serve per una vostra responsabilizzazione.

Un altra metodologia è il completamento a uno, che si può ottenere convertendo ogni bit del numero, per esempio:

Estensione dei segni

In assembly tutti i dati hanno una dimensione specificata, e in molti casi può sorgere la necessità di cambiare questa dimensione per dar spazio ad altri dati.
Per esempio: Noi abbiamo 8 contenitori, ed in ognuno di questo può essere inserito massimo un cellulare, qual ora noi avessimo 16 cellulari da inserire, noi abbiamo bisogno chiaramente di altri contenitori, per l’esattezza 16.
Chiaramente la scelta non è proprio arbitraria, nel senso che dobbiamo noi programmatori ad attenerci a quelli che sono i registri le locazioni di memoria, in questa parte faremo uso dei registri 8-bit e 16-bit, giusto per dare un’accenno a quello che è l’utilità dell’estensione del segno.
Tratteremo questa parte in maniera superficiale, ma vi consiglio vivamente di approfondirlo.

Decrementare la dimensione di un dato:

Per decrementare una dimensione di un dato vengono rimossi quelli che sono i bit più significativi. Ecco un’esempio:

Invece per incrementare la dimensione di un dato, la faccenda si fa leggermente più complicata.
Consideriamo il byte F2. Se esso viene esteso ad una word, non sappiamo quale valore assumerà la word… il risultato è dato da come FF è interpretato. Se FF è un byte senza segno (255 in decimale), allora la word dovrebbe essere 00F2h.
Qui entriamo in discorsi sempre più complessi, e a mio modo questo articolo non può andare oltre a quello che è il suo scopo ultimo, quindi cercate su DuckDuckGo.

 

Conclusione

Nel prossimo articolo vedremo le comparazioni e i suoi relativi registri, come formare un loop e altre nozioni fondamentali.

Per questo articolo, ma in generale devo dei ringraziamenti a Neetx, che ha supportato e continua a supportare questa rubrica.



Leave a comment