| |
|
| |
|
| Descrizione |
Questo documente spiega brevemente e sommariamente
la basi dell'Assembler x86.
Puo essere un primo approccio alla programmazione di primo livello. |
| |
|
| |
|
|
|
|
Sistemi binario,
decimale,
esadecimale |
Noi siamo abituati a lavorare con il sistema decimale,
ma i computer a livello hardware utilizzano il sistema binario (combinazioni
di 0 e 1), il pc usa ovviamente anche il sistema esadecimale.
Un cracker o un programmatore di Assembler deve conoscere tutti
i sistemi elencati e sapere come convertire cifre.
Il sistema più vicino a noi è quello decimale, quindi useremo quello
per rendere gli altri sistemi più "amichevoli", il sistema
binario utilizza infatti le cifre 0 e 1, quello il base 8 riciclerà
le cifre da 0 a 7.
Per il sistema esadecimale ci sono 16 cifre e quindi siamo costretti
a ricorrere alle letter dell'alfabeto:
| Esadecimale: |
0 1 2 3 4 5 6
7 8 9 10 11 12 13 14 15 . |
| Convertito: |
0 1 2 3 4 5 6
7 8 9 A B C D E F . |
Quindi mentre il sistema decimale è "matematicamente incompatibile"
col sistema esadecimale, il sistema binario è molto vicino ad entrambi,
esso infatti lavora in base due e 2*2*2*2=16 (base esadecimale).
- Conversione Binaria a Decimale
Prendiamo ad esempio 11011b in complemento a due, a che numero
decimale corrisponde?
| numero binario: |
1 |
1 |
0 |
1 |
1 |
| potenze di 2: |
2^4 |
2^3 |
2^2 |
2^1 |
2^0 |
| formato decimale: |
16 |
+8 |
+0 |
+2 |
+1 |
| sommando.. |
|
|
|
|
|
| risultato: |
= 27 |
|
|
|
|
- Conversione Binaria a Esadecimale
Utilizzando lo stesso esempio calcoliamo il corrispondente esadecimale.
(dividiamo in gruppi di quattro da dx verso sx)
| numero binario: |
1 |
|
1 |
0 |
1 |
1 |
| potenze di 2: |
2^4 |
|
2^3 |
2^2 |
2^1 |
2^0 |
| formato decimale: |
16 |
|
+8 |
+0 |
+2 |
+1 |
| |
|
|
|
|
|
|
| in Hex |
1 |
|
8+2+1=11 = B |
|
|
|
| risultato: |
|
|
|
= 1B |
|
|
- Conversione Decimale a Binaria
Come avrete tutti studiato parecchi anni fa per convertire un
valore decimale in uno binario si gioca sulle divisioni e sul
rimanente. DIV è il risultato della divisione mentre MOD è il
resto.
Così se scriverò 15 MOD 2 il risultato sarà DIV=7, MOD=1 (il simbolo
che identifica MOD è %).
Praticamente per trasformare un numero decimale in uno binario
è sufficiente continuare a dividere per due componendo la cifra
binaria da destra verso sinistra:
15/2 =7 +1 BIN -> 1
7 /2 =3 +1 BIN -> 11
3 /2 =1 +1 BIN -> 111
1 /2 =0 +1 BIN -> 1111
| Hex |
Bin |
| 0 |
0000 |
| 1 |
0001 |
| 2 |
0010 |
| 3 |
0011 |
| 4 |
0100 |
| 5 |
0101 |
| 6 |
0110 |
| 7 |
0111 |
| 8 |
1000 |
| 9 |
1001 |
| A |
1010 |
| B |
1011 |
| C |
1100 |
| D |
1101 |
| E |
1110 |
| F |
1111 |
Quindi ad esempio 1101 1011 0111 0101 0011
corrisponde a D B 7 5 3.
- Operazioni & Logica
Per distinguere la base di ogni cifra l'Assembler aggiunge l'iniziale
del sistema al termine del numero
| Suffisso |
Sistema |
| b
|
Binario |
| o |
Ottale |
| d |
Decimale |
| h |
Esadecimale |
Per scrivere il numero 7 in base due in 8 bit di memoria: 00000111.
Se vogliamo rappresentare il numero -7 la cosa è più complessa:
| |
|
| |
00000111 |
| invertiamo i bit: |
11111000 |
| aggiungiamo 1 al numero |
11111000 + 1 = |
| |
11111001 --> -7 |
Infatti:
(111 + 11111001 = 0 <-> 7
- 7 = 0)
Il valore 1 corrisponde a vero mentre 0 corrisponde a falso,
entriamo nell'affascinante mondo della logica (sezione della matematica
che studia i predicati).
Le operazioni fondamentali (porte logiche) sono AND, OR, NOT,
XOR.
Ecco una tabella riassuntiva dei risutati:
| c = a AND b |
|
|
|
|
a |
b |
c |
|
0 |
0 |
0 |
|
0 |
1 |
0 |
|
1 |
0 |
0 |
|
1 |
1 |
1 |
|
| c = a OR b |
|
|
|
|
a |
b |
c |
|
0 |
0 |
0 |
|
0 |
1 |
1 |
|
1 |
0 |
1 |
|
1 |
1 |
1 |
|
|
(NOT funziona con una sola variabile)
|
| c =
a XOR b |
|
|
|
|
a |
b |
c |
|
0 |
0 |
0 |
|
0 |
1 |
1 |
|
1 |
0 |
1 |
|
1 |
1 |
0 |
|
|
|
|
| |
|
| |
|
|
|
|
Unità di misura
della memoria |
E' necessario introdurre ancora altri concetti prima
di passare alla vera e propria programmazione, un concetto che non
sarà senz'altro nuovo è quello de Bit, Byte, Word..
Un Bit è una cifra binaria: 0 oppure 1.
Un Byte è una concatenazione di 8 Bit quindi 10010010, un
byte può rappresentare un qualsiasi numero tra 0 e 2^8-1
(=255) o un qualsiasi esadecimale da 0 a FF.
Una Word è l'unione di 2 byte, quindi sono in totale 16bit
di memoria, i numeri arrivano a 65535, in Esadecimale fino a FFFF.
Una Double Word è 2 Word ossia 4 byte, da 0 a 4294967295
e in esa da 0 a FFFFFFFF.
Poi: un Kilobyte è 1024 Byte, un Mega 1024 Kilobyte, Un
Gigabyte 1024 Megabyte. |
| |
|
| |
|
|
|
|
I Registri |
I registri sono delle locazioni di memoria dove
il nostro programma può salvare dati, puntatori.. I Registri
in questione sono AX, BX, CX, DX.
- AX - Registro a 16bit (divisibile in AH e AL da 8bit ciascuno),
è detto "accumulatore" poichè è
spesso usato per memorizzare calcoli matematici.
- BX - Registro a 16bit (divisibile in BH e BL da 8bit ciascuno),
usato spesso per memorizzare OFFSET.
- CX - Registro a 16bit (divisibile in CH e CL da 8bit ciascuno),
impiegato nella maggior parte dei casi come contatore di cicli.
- DX - Registro a 16bit (divisibile in DH e DL da 8bit ciascuno),
usato per memorizzare grosse cifre matematiche e come puntatore
nell'I/O.
- BP - Registro a 16bit (Base Pointer), Puntatore di base dello
stack.
- CS - (Code Segment), Contiene l'istruzione di programma da eseguire.
- DS - (Data Segment), Contiene dati dell'applicazione.
- ES - (Extra Segment), Registro aggiuntivo.
- SS - (Stack Segment), Contiene lo Stack.
Dalle architetture del 386 sono stati aggiunti i registri 32bit:
EAX, EBX, ECX, EDX, ESI, EDI, EBP.
Ci sono altri due registri (spesso utili nel cracking) creati appositamente
per la manipolazione delle stringhe:
- DI - Registro di Destinazione
- SI - Registro di Partenza
|
| Il Registro FLAG |
Questo registro viene utilizzato come memoria per
l'esecuzione delle istruzioni, ad esempio se si esegue una sottrazzione
che da come risultato zero, il FLAG verrà impostato a zero, quindi
il 6° bit sarà 1.
| Bit |
Descrizione |
| 0 |
CF Carry Flag |
| 1 |
1 |
| 2 |
PF Parity Flag |
| 3 |
0 |
| 4 |
AF Auxiliary Flag |
| 5 |
0 |
| 6 |
ZF Zero Flag |
| 7 |
SF Sign Flag |
| 8 |
TF Trap Flag (Single Step) |
| 9 |
IF Interrupt Flag |
| A |
DF Direction Flag |
| B |
OF Overflow flag |
| C |
0 |
| D |
IOPL I/O Privil. Level(286+
only) |
| E |
NT Nested Task Flag (286+ only) |
| F |
0 |
| 10 |
RF Resume Flag (386+ only) |
| 11 |
VM Virtual Mode Flag (386+ only) |
|
| |
|
| |
|
|
|
|
Segmenti e OffSets |
Le vecchie CPU dovevano gestire solo 1 mega di memoria,
ma per indirizzare 1Mb di memoria sono necessari 20bit (2^20=1Mb)
e noi abbiamo a disposizione solo 16bit; quindi i progettisti Intel
hanno introdotto un nuovo tipo di indirizzo costituito da due parti:
un SEGMENTO e un OFFSET (16bit ciascuno). Un indirizzo valido è
nella forma SEGMENT:OFFSET, gli indirizzi sono in base esadecimale
del tipo: 35B2:C02E.
Il problema è che questo non è un indirizzo a 20 bit,
ma a 32bit; per trovare il vero indirizzo è necessare effettuare
due operazioni: moltiplicare il segmento per 10h (16) e sommarlo
all'OFFSET.
I registri relativi al segmento sono : CS, DS, ES, SS, quindi un
segmento è 64K. |
| |
|
| |
|
|
|
|
Il primo programma "Hello
World! |
Come da tradizione partiremo con il programma "Hello
World!", tanto per famigliarizzare con l'ambiente di sviluppo
e con i primi elementari comandi ASM. Non preoccupatevi se non capite,
non è ancora questo lo scopo.
| ;ASM01.ASM |
[bY_ Alessandro Polo 1999] |
| .MODEL small |
[modello di memoria] |
| .STACK 100h |
[dimensione dello Stack] |
| .DATA |
[inizio del segmento dati] |
| MyString DB "Ciauz
WoRld",13,10,'$' |
[dichiarazione del Stringa] |
| .CODE |
[inizio del segmento di codice] |
| mov ax,SEG MyString |
[ax = indirizzo del Segmento
Dati] |
| mov ds,ax |
[ds = ax] |
| mov dx,OFFSET MyString |
[ds = offset del Segmento Dati] |
| mov ah,09h |
[ ah = 09h ] |
| int 21h |
[call all'interrupt DOS] |
| mov ah,4Ch |
[ ah = 4Ch ] |
| int 21h |
[call all'interrupt DOS] |
| END |
[fine del programma] |
Questo programmino scriverà sullo schermo il messaggio "Ciauz
WoRld", ora analizzaremo più in dettaglio i comandi
usati e la struttura del codice:
.Model
E' il modello di memoria da utilizzare nel programma, può
essere: Large (codice e dati superano i 64Kb), Compact (a differenza
dello Small, i puntatori Far possono superare i 64Kb), Medium (il
codice può superare i 64Kb), Small (non oltre i 64kb), Tiny
(non oltre i 64kb, file .COM).
.Stack
Spazio riservato per lo Stack (predefinito = 200h =1kb).
.Data
Sezione dei dati del programma, qui sono assegnati i valori alle
costanti.
MyString DB "Ciauz WoRld",13,10,'$'
Viene assegnato il testo "Ciauz World" alla variabile
Message, DB assegna il numero di byte necessari, in tutto 14: 11
per la stringa, 1 per l'invio (#13), 1 per #10 e uno per "$"
che deve essere al termine di ogni stringa dichiarata.
Nota: Mentre DB assegna un numero di byte, esistono altre direttive
come:
DW (Define Word), DD (Define Double Word), DQ (Define Quadword),
DF (Define 48bit), DT (Define TenByte).
.Code
Questa sezione è il cuore del programma: il codice sorgente. |
| |
|
| |
|
|
|
|
| Gli Interrupt |
Analizziamo il codice del programma proposto:
| Istruzione MOV ds,ax
|
Quest'istruzione sposta il contenuto
di ax nel data segment. |
| Istruzione MOV dx,OFFSET
MyString |
Con quest'istruzione si copia
l'offset della variabile (MyString) in dx. (N.B. OFFSET e SEG
sono parole riservate.) |
| Istruzione MOV ah,09h
|
Modifichiamo il registro ah
inserendo il valore esadecimale 09. |
| Istruzione INT 21h
|
Chiama l'interrupt 21h per stampare
sullo schermo il messaggio. |
| Istruzione MOV ah,4Ch
|
Copiamo il valore 4C in ah,
spiegherò il motivo di quest'istruzione nella sezione
Interrupt. |
| Istruzione INT 21h
|
Chiama l'interrupt 21h per terminare
il programma. (l'alternativa è resettare il pc) |
Gli interrupt sono delle funzioni paragonabili alle API di Windows
(anche se meno avanzate e comode) del DOS o del BIOS. L'interrupt
usato precedentemente è il 21h (fa parte del DOS), per capire
il funzionamento è necessario sapere il valore di AH quando
l'interrupt viene chiamato (AH è paragonabile alle variabili
date alle API). Consultando il manuale in relazione all'interrupt
21h si cerca il valore 09h e si scopre che la funzione è:
stampa una stringa nell'output del monitor, la stringa stampata
è in DS:DX (infatti c'è il nostro messaggio).
Le funzioni Interrupt sono salvate nel primo Kbyte di RAM, cioè
dall'indirizzo 000h a 3FFh, ogni funzione è di 4 byte, quindi
è possibile calcolare dove si trovi ogni interrupt in memoria:
4(byte) * 21h (interrupt) = 84h (indirizzo in memoria).
|
| |
|
| |
|
|
|
|
| Secondo Esempio |
Questo programma è molto simile a quello
precedente nel senso che ha lo stesso fine: stampare qualcosa sullo
schermo.
| ;ASM02.ASM |
[bY_ Alessandro Polo 1999] |
| SEG_A SEGMENT |
|
| ASSUME CS:SEG_A, DS:SEG_A |
|
| ORG 100H |
|
| Ciauz PROC FAR |
|
| PARTENZA: JMP START |
[salta a START] |
| MyString DB "Ciauz WoRld",13,10,'$'
|
[dichiarazione del Stringa] |
| START: |
|
| mov dx,OFFSET MyString |
[ds = offset del Segmento Dati] |
| mov ah,09h |
[ah = 09h] |
| int 21h |
[chiamata l'interrupt] |
| RETN |
|
| Ciauz ENDP |
|
| SEG_A ENDS |
|
| END PARTENZA |
|
| |
|
Rispetto all'esempio precedente qui manca il Data Segment e lo
Stack Segment. D'altra parte questo non è un EXE ma un COM
ed infatti è necessario specificarlo al momento del"linking". |
| |
|
| |
|
|
|
|
| Le Funzioni |
La call:
message db 'Ciauz WoRld','$'
|
mov dx, offset message
|
call DisplayString
|
La Routine:
DisplayString:
|
mov ax,cs
|
mov ds,ax
|
mov ah,9 ; DOS FUNCTION: display message
|
int 21h ; Call the DOS interruptmov ah,
|
ret
|
|
| |
|
| |
|
|
|
|
Linker, OBJ
e file EXE |
Una volta terminato di scrivere il programma ASM e
salvato il file xxx.ASM, è necessario prima compilare il programma
e poi linkarlo; compilando il programma verrà creato un file
oggetto (.OBJ) che deve essere linkato per creare il vero e proprio
eseguibile. Il file risultante è apparentemente un ammasso
di caratteri ASCII senza senso, ma in realtà esiste una relazione
tra il file EXE e il codice ASM, infatti è anche possibile
tornare dall'eseguibile al sorgente tramite un Disassemblatore oppure
un Debugger (questo permette anche di modificare le istruzioni in
Runtime cioè mentre il programma stà funzionando).
Ad esempio l'istruzione JNE X8(Jump if Not Equal) ha come valore 75cb,
quando si vuole patchare un programma, cioè modificare l'eseguibile
per aggirare uan protezione (ad esempio) è necessario aprire
il file e cambiare alcuni valori di questi caratteri ASCII (potremmo
semplicemente modificare 75 a 74 per esempio). Comunque tratterò
questi argomenti in altra sede. |
| |
|
| |
|
| |
|
|