Minima Anonyma Tabularia Ex Reti
nihil vacuum neque sine signo apud MATER
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 Octavus §
indice
2.formato delle istruzioni INTEL
Prima di dare vedere come è fatto un MP dobbiamo
dare uno sguardo al formato delle istruzioni della famiglia di processori x86.
Un MP è un generatore di codice, ed è quindi necessario sapere come funzionano
i codici operativi (opcode) del processore. Per chi si addentrerà nello studio
deve disporre di una tabella di tutte gli opcode del processore che può essere
trovata sui manuali Intel.
Ogni istruzione che effettua un’operazione, è
composta da una parte fissa più una parte che cambia in base al registro (o ai
registri) usati con quella specifica operazione.
Facciamo un esempio:
mov reg16,imm16
E' un’operazione che muove il valore imm16 (un
immediato da 16 bit) nel registro a 16 bit scelto. Vediamo la codifica di un
paio di esempi di questa operazione:
mov ax,0h B8 0000
mov cx,0h B9 0000
mov bx,0h BB 0000
Come si può notare ad un opcode di partenza (B8 in
questo caso), viene aggiunto un dato valore in base al registro scelto. In
particolare è possibile creare codice opportuno per qualsiasi registro aggiungendo
il valore rispettivo secondo la tabella:
Registro Valore
AX -
000b
CX -
001b
DX -
010b
BX -
011b
SP -
100b
BP -
101b
SI -
110b
DI -
111b
Conoscendo quindi l'opcode di partenza (trovato
nelle specifiche del processore) possiamo facilmente generare codice per
qualsiasi registro disponibili.
Con registri a 8 bit le cose rimangono invariate.
Cambia ovviamente l'opcode di partenza (per esempio mov
reg8,imm8
corrisponde nell’istruzione base mov al,0h al codice B0
00).
La tabella di addizione per i registri
si trasforma in:
Registro
Valore
AL -
000b
CL -
001b
DL -
010b
BL -
011b
AH -
100b
CH -
101b
DH -
110b
BH -
111b
Con registri dword (a 32bit) deve essere aggiunto
prima il byte di prefisso 66h o 67h (ma questo dipende dal comando voluto).
Quando l’istruzione richiede due registri, ad
esempio per un generico mov reg16_1,reg16_2 basta prendere l'opcode
base dell'istruzione (in questo caso 8B C0, dalla documentazione
vedrete che il primo byte rimane fisso quindi per i registri andremo a cambiare
solo il secondo), sommare il valore del registro sorgente (reg16_2), quindi sommare il valore
del registro di destinazione moltiplicato per 8 (che poi si riduce ad un shift
a sinistra di 3). Quindi ad esempio per un:
mov cx,dx avremo:
C0h + 02h + (8 *
01h)
e quindi il codice per l'istruzione voluta sarà 8B
CA.
Dove l'istruzione richiede un valore immediato (come
nell'esempio precedente), un indirizzo di memoria, un offset da un registro o
qualcos'altro, oltre al codice verrà aggiunta di seguito dopo l'opcode di base
(ad esempio prima nel mov ax,0000 dopo il B8 chiaramente ce' una word 0h).
La
codifica delle istruzioni Intel è un sottoinsieme del formato generale mostrato
nella figura in basso. Le istruzioni consistono di un prefisso (opzionale e in
qualsiasi ordine), un opcode primario da 1 o 2 bytes, una forma di
indirizzamento costituita da un ModR/M
da 1 byte (se richiesto) e talvolta da un byte SIB (Scale-Index-Base), da uno
spiazzamento (se richiesto), e da un dato immediato (se richiesto).
Ricordiamo che R/M è come è costruita l’istruzione (
con base, con indici, con due registri) e Mod identifica cosa effettua
l’indicizzazione ( DI, BP... ).
Intel Architecture Instruction Format (dal manuale Intel, vol II)
Prefissi Opcode ModR/M SIB Displacement Immediate
---------------------------------------------------------------------------------------------------------------------------------------------------
Fino a 4 prefissi
opcode di 1 1
byte 1 byte spiazzamento dati immediati
di un byte
o 2 bytes (se richiesto) (se richiesto) 0,1,2 o 4 bytes
0,1,2 o 4 bytes
(opzionale)
---------------------------------------------------------------------------------------------------------------------------------------------------
ModRam: 7 6 5 3 2 0
+-------+-----------+---------+
| Mod | Reg/
| R/M |
| | Opcode | |
+ ------------------------------+
SIB:
7 6 5 3 2 0
+-------+-----------+---------+
| Scale| Index | Base |
| | | |
+ ------------------------------+
Prefissi
I prefissi delle istruzioni sono
divisi in 4 gruppi:
• Lock and repeat prefixes.
— F0H—LOCK prefix.
— F2H—REPNE/REPNZ prefix (used only with
string instructions).
— F3H—REP prefix (used only with string
instructions).
— F3H—REPE/REPZ prefix (used only with
string instructions).
• Segment override.
— 2EH—CS segment override prefix.
— 36H—SS segment override prefix.
— 3EH—DS segment override prefix.
— 26H—ES segment override prefix.
— 64H—FS segment override prefix.
— 65H—GS segment override prefix.
• Operand-size override, 66H
• Address-size override, 67H
Opcode primario
l’opcode primario può essere
da 1 o 2 bytes. Talvolta si incontra un campo codificato a 3 bit nel byte
ModR/M. Questi campi definiscono la direzione dell’operazione, la misura dello
spiazzamento, la codifica dei registri, i codici di condizione, il segno.
Ecco ad esempio la codifica di
tutti i tipi di istruzione MOV (dal manuale Intel, vol. II):
88 / r MOV r/m8,r8 Move r8 to r/m8
89 / r MOV r/m16,r16 Move r16 to r/m16
89 / r MOV r/m32,r32 Move r32 to r/m32
8A / r MOV r8,r/m8 Move r/m8 to r8
8B / r MOV r16,r/m16 Move r/m16 to r16
8B / r MOV r32,r/m32 Move r/m32 to r32
8C / r MOV r/m16,Sreg** Move segment register to r/m16
8E / r MOV Sreg,r/m16** Move r/m16 to segment register
A0 MOV AL, moffs8* Move byte at ( seg:offset) to AL
A1 MOV AX, moffs16* Move word at ( seg:offset) to AX
A1 MOV EAX, moffs32* Move doubleword at ( seg:offset) to
EAX
A2 MOV moffs8*,AL Move AL to ( seg:offset)
A3 MOV moffs16*,AX Move AX to ( seg:offset)
A3 MOV moffs32*,EAX Move EAX to ( seg:offset)
B0+ rb MOV r8,imm8 Move imm8 to r8
B8+ rw MOV r16,imm16 Move imm16 to r16
B8+ rd MOV r32,imm32 Move imm32 to r32
C6 / 0 MOV r/m8,imm8 Move imm8 to r/m8
C7 / 0 MOV r/m16,imm16 Move imm16 to r/m16
C7 / 0 MOV r/m32,imm32 Move imm32 to r/m32
Ad esempio:
89/r MOV r/m32, r32
à Move r32 to r/m32
sarà quindi uguale a:
Simbologia utilizzata
dall’Intel:
• rel8—indirizzo
relativo in un range da 128 bytes prima della fine dell’istruzione a 127 bytes
dopo al fine dell’istruzione.
• rel16 e rel32—indirizzo
relativo allo stesso code segment come
l’istruzione è assemblata.
• ptr16:16 e ptr16:32—puntatore
FAR, che punta in un segmento diverso da quello dell’istruzione.
• r8—uno dei registri
di uso generale da un bytes: AL, CL, DL, BL, AH, CH, DH, o BH.
• r16—uno dei
registri di uso generale da una word: AX, CX, DX, BX, SP, BP, SI, o DI.
• r32—uno dei
registri di uso generale da una doubleword: EAX, ECX, EDX, EBX, ESP, EBP,ESI, o
EDI.
• imm8—un valore
immediato da un byte.
• imm16—un valore
immediato da 16 bits. E’ un numero tra –32,768 e +32,767 inclusi.
• imm32— un valore
immediato da 32 bits. E’ un numero tra +2,147,483,647 e –2,147,483, 648
inclusi.
• r/m8—un operando da
1 byte che può contenere un registro generale da un byte (AL, BL, CL, DL, AH,
BH, CH, and DH), o un byte dalla memoria.
• r/m16— un operando
da 2 bytes che può contenere un registro generale da 2 bytes: AX, BX, CX,
DX, SP, BP, SI, e DI o due bytes di memoria.
• r/m32—un registro
doubleword general-purpose: EAX, EBX, ECX, EDX, ESP, EBP, ESI, e EDI.
• m—un operando da
16- o 32-bit in memoria.
• m8— un operando da
1 byte in memoria, usualmente espresso come una variabile o nome di array ma
puntato dai registri DS:(E)SI o ES:(E)DI.
• m16—come sopra, con
operando da una word. Questa nomenclatura è usata solo con le istruzioni con
stringhe.
• m32—come sopra, con
operando di doubleword. Questa nomenclatura è usata solo con le istruzioni con
stringhe.
• m64—come sopra con
operando di quadword. Solo usata con l’istruzione CMPXCHG8B.
• m16:16, m16:32—un
operando di memoria contenente un puntatore far composto da due numeri. Il
numero alla sinistra della colonna corrisponde al selettore del puntatore al
segmento. Il numero alla destra corrisponde al suo offset.
• m16&32, m16&16,
m32&32— Un operando di memoria che consiste in copie la cui misura è
indicata alla sinistra e alla destra di &
• moffs8, moffs16,
moffs32—una variabile di memoria (memory offset) di tip byte, word, o
doubleword usata con alcune varianti dell’istruzione MOV.
• Sreg—un registro di
segmento: ES=0, CS=1, SS=2, DS=3, FS=4, and GS=5.
• m32real, m64real, m80real—un operando floating point single-,
double-, e extended-real (rispettivamente)
• m16int, m32int, m64int—rispettivamente un operando floating-point
word-, short-, e long-integer
• ST or ST(0)—l’elemento
più in alto (top element) dello stack registri FPU.
• ST(i)—l’ i-elemento
dalla cima dello stack FPU (i da 0 a 7)
• mm—un registro MMX™
. I registri a 64-bit MMX sono da MM0 a
MM7.
• mm/m32— i “low order
32 bits” di un registro MMX register o un operando di 32-bit- I registri MMX
vanno da MM0 a MM7.
• mm/m64—Un registro MMX o un operando di 64-bit.
Vedremo come viene costruita
un’istruzione in un MP. Riprendiamo
lo scheletro di una istruzione, semplificato:
8
bits 2 3 3 8 o
16 bits 8 o 16 bits
Istruzione Mod Reg
R/M Spiazzamento Dati
1
byte -------1 byte------ 1 o 2 bytes 1 o 2 bytes
per semplicità si utilizzerà la seguente notazione:
campo reg - codice che contiene il registro usato
campo sreg - codice che contiene il registro di segmento
campo r/m - come è costruita l0istruzione( con base, indicizzata, etc.)
campo mod - cosa effettua l’indicizzazione (es. DI, BP, etc.)
campo dir - direzione
campo w - marcatore word
si danno le seguenti tabelle:
campo reg:
~~~~~~~~~~
AX or AL - 000 = 0
CX or CL - 001 = 1
DX or DL - 010 = 2
BX or BL - 011 = 3
SP or AH - 100 = 4
BP or CH - 101 = 5
SI or DH - 110 = 6
DI or BH - 111 = 7
Quando è codificata un’istruzione dove vengono utilizzati registri da un byte o da una word, la maniera per sapere se si tratta di uno o dell’altro tipo di registri è quella di controllare il bit 'w'. Se è 1 allora vengono utilizzati registri word, se è 0 allora vengono utilizzati registri byte.
campo sreg
~~~~~~~~~~
ES - 001 = 1
CS - 011 = 3
SS - 101 = 5
DS - 111 = 7
campo r/m
~~~~~~~~~
00 - base o indice
01 - base o indice con spiazzamento a 8 bit
10 – base o indice con spiazzamento a 16 bit
11 – due registri
campo mod (se r/m è base o indice)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
000 - [BX+SI]
001 - [BX+DI]
010 - [BP+SI]
011 - [BP+DI]
100 - [SI]
101 - [DI]
110 - se R/M = 00 un offset diretto o [BP]
111 - [BX]
overrides di segmento
~~~~~~~~~~~~~~~~~
ES - 00100110 - 26h
CS - 00101110 - 2Eh
SS - 00110110 - 36h
DS - 00111110 - 3Eh
Direzione
Se settato, reg è la destinazione e mod è l’origine, altrimenti viceversa.
Vediamo come viene generata un’istruzione mov. Se disassembliamo un programma che contiene la linea:
MOV BX, [SI+0134h] vedremo il seguente opcode: 8B 9C 34 01
o, in binario:
10001011100111000011010000000001
che significa:
100010 | 1 | 1 | 10 | 011 | 100 | 0011010000000001 |
mov d w r/m reg mod dati
in cui
d (direzione) = 1, da 'mod' a 'reg'
w = 1 (il bit word)
r/m = 10 (base con spiazzamento a 16-bit)
reg (registro)= 011 (BX)
mod = 100 ([SI])
dati = 0134h (lo spiazzamento a 16 bit aggiunto a SI)
Per creare un’istruzione Mov reg, [si+imm] “vuota” dovremmo avere:
100010 1 1 10 000 100 0000000000000000,
cioè
10001011 10000100 0000000000000000
^ dati dati dati
registro
La sequenza: 10001001100011011000000000000000
Dà
MOV [DI+1000h], CX
in quanto:
d = 1
r/m = 10
mod = 101
reg = 001
Altri esempi, ricavati con il debugger:
MOV ax, 1234h ; mov
reg16,imm16
B8h+RegWord imm16
B83412 -- 10111000 00110100 00010010
XOR ax,1234h 35h imm16
353412 -- 00110101 00110100 00010010
AND bx,1234h ; and regmem16,imm16
81h \4 imm16
81E33412 -- 10000001 11100011 00110100 00010010
ADD dx, 1234h ;
regmem16,imm16
81h \0 imm16
81C23412 -- 10000001 11000010 00110100 00010010
mov ax,1234h
B83412
casi di indirizzamenti (per la
codifica in binario provate da soli, come esercizio):
mov al,[bx] ; indirizzamento indiretto
8A07
mov al,cs:[bx] ; ind.indiretto con override
prefix
AL,[BX] – 8A07
mov al, 20h[bx] ; indirizzamento indicizzato (spiazzam=20h)
AL,[BX+20] – 8A4720
mov al,
ss:20h[bx] ; idem con override
come sopra
mov al,[bx][si] ; ind.indiciz. con base
AL,[BX+SI] – 8A00
mov al,
20h[bx][si] ; idem con override
AL,[BX+SI+20] –
8A4020
mov al,
[bp+si+20h] ; idem con override
come sopra
MOV
1) mov reg, imm 1011, w, reg, imm
- se w = 0 imm è 8 bit -> 2 byte
- se w = 1 imm è 16 bit -> 3 byte
2) mov reg, reg
mov reg, mem
mov mem, reg
100010, d, w, r/m, reg, mod, dati
- se r/m = 00 -> 2 byte
- se r/m = 01 dati is 8 bit -> 3 byte
- se r/m = 10 dati is 16 bit -> 4 byte
3) mov sreg, reg
mov reg, sreg
100011, d, 0, 1, sreg, reg, 1
- 2 byte
XCHG
xchg reg, reg
xchg reg, mem
xchg mem, reg
100001, w, r/m, reg, mod, dati
Operazioni sullo Stack
PUSH reg - 01010, reg
POP reg - 01011, reg
PUSH sreg - 000, sreg, 10
POP sreg - 000, sreg, 11
PUSH imm - 01101000, dati
PUSH mem - 11111111, r/m, 110, mod, dati
POP mem - 1000111, r/m, 0000, mod, dati
PUSHA - 01100000
POPA - 01100001
PUSHF - 10011100
POPF - 10011101
istruzioni logiche
1) XOR
1.1) XOR reg, reg 001100, d, w, 11, reg1, reg2
d = 1 solo se reg1 = reg2
1.2) XOR reg, imm 100000, w, r, 11110, reg, dati
r = 0 registro è 8 bit altrimenti è 16 bit
1.3) XOR reg, mem
XOR mem, reg
00110, d, w, r/m, reg, mod, dati
2) OR
2.1) OR reg, reg 0000100, d, w, 11, reg1, reg2
2.2) OR reg, imm 100000, w, r, 11001, reg
2.3) OR reg, mem
OR mem, reg
000010, d, w, r/m, reg, mod, dati
3) AND
3.1) AND reg, reg 001000, d, w, 11, reg1, reg2
3.2) AND reg, imm 100000, w, r, 11000, reg
3.3) AND reg, mem
AND mem, reg
001000, d, w, r/m, reg, mod, dati
4) NOT
4.1) NOT reg 1111011111010, reg
4.2) NOT mem 1111011, w, r/m, 010, mod
5) NEG
5.1) NEG reg 1111011111011, reg
5.2) NEG mem 1111011, w, r/m, 011, mod
6) TEST
6.1) TEST reg, reg 1000010, w, 11, reg1, reg2
6.2) TEST reg, imm 1111011, w, 11010, reg, dati
7) CMP
7.1) CMP reg, reg 0011101, d, w, 11, reg1, reg2
7.2) CMP reg, imm 100000, w, r, 11111, reg
7.3) CMP reg, mem
CMP mem, reg
001110, d, w, r/m, reg, mod, dati
istruzioni aritmetiche
1) ADD
1.1) ADD reg, reg 0000001, w, 11, reg, reg
1.2) ADD reg, imm 100000, w, r, 11000, reg
1.3) ADD reg, mem
ADD mem, reg
000000, d, w, r/m, reg, mod
2) ADC
2.1) ADC reg, reg 0001001, w, 11, reg, reg
2.2) ADC reg, imm 100000, w, r, 11010, reg
2.3) ADC reg, mem
ADC mem, reg
000100, d, w, r/m, reg, mod
3) SUB
3.1) SUB reg, reg 0010101, w, 11, reg, reg
3.2) SUB reg, imm 100000, w, r, 11101, reg
3.3) SUB reg, mem
SUB mem, reg 001010, d, w, r/m, reg, mod
4) SBB
4.1) SBB reg, reg 0001101, w, 11, reg, reg
4.2) SBB reg, imm 100000, w, r, 11011, reg
4.3) SUB reg, mem
SUB mem, reg
000110, d, w, r/m, reg, mod
5) INC 01000, reg16 1111111111000, reg8
6) DEC 01001, reg16 1111111011001, reg8
istruzioni di shifting
1) SHR
1.1) SHR reg, 1 1101000, w, 11101, reg
1.2) SHR reg, imm 1100000, w, 11101, reg
1.3) SHR reg, cl 1101001, w, 11101, reg
2) SHL
2.1) SHL reg, 1 1101000, w, 11100, reg
2.2) SHL reg, imm 1100000, w, 11100, reg
2.3) SHL reg, cl 1101001, w, 11100, reg
3) ROR
3.1) ROR reg, 1 1101000, w, 11001, reg
3.2) ROR reg, imm 1100000, w, 11001, reg
3.3) ROR reg, cl 1101001, w, 11001, reg
4) ROL
4.1) ROL reg, 1 1101000, w, 11000, reg
4.2) ROL reg, imm 1100000, w, 11000, reg
4.3) ROL reg, cl 1101001, w, 11000, reg
5) RCL
5.1) RCL reg, 1 1101000, w, 11010, reg
5.2) RCL reg, imm 1100000, w, 11010, reg
5.3) RCL reg, cl 1101001, w, 11010, reg
6) RCR
6.1) RCR reg, 1 1101000, w, 11011, reg
6.2) RCR reg, imm 1100000, w, 11011, reg
6.3) RCR reg, cl 1101001, w, 11011, reg
istruzioni di flag
CLI - 11111010
STI - 11111011
CLD - 11111100
STD - 11111101
CLC - 11111000
STC - 11111001
CMC - 11110101
SAHF - 10011110
LAHF - 10011111
istruzioni di salto
1) JMP SHORT - EBh, dati8
2) JMP NEAR - E9h, dati16
3) JMP FAR - EAh, dati
dati è un segment:offset dati in formato inverso.
Esempio: jmp far 1000:432fh = EAh, 2f43h, 0010h
I salti condizionali (nota che dati8 è un numero signed a 8 bit, va da -127 a -1 il jump è verso l’alto altrimenti verso il basso. Il jump è calcolato dalla fine della’istruzione di JMP.
Ciò significa che un’struzione JE 00 è un jump all’sitruzione successiva (più sotto), mentre un’istruzione JE 02 è un jump che salta ai du ebytes dopo l’opcode del jump.
4) JBE/JNA - 76h, dati8
5) JLE/JNG - 7Eh, dati8
6) JB/JNAE/JC - 72h, dati8
7) JL/JNGE - 7Ch, dati8
8) JZ/JE - 74h, dati8
9) JNE/JNZ - 75h, dati8
10) JAE/JNB/JNC - 73h, dati8
11) JGE/JNL - 7Dh, dati8
12) JA/JNBE - 77h, dati8
13) JG/JNLE - 7Fh, dati8
14) JCXZ - E3h, dati8
15) JNO - 71h, dati8
16) JO - 70h, dati8
17) JP/JPE - 7Ah, dati8
18) JNP/JPO - 7Bh, dati8
19) JNS - 79h, dati8
20) JS - 78h, dati8
21) LOOP - E2h, dati8
22) CALL SHORT - E8h, dati8
23) RETN - C3h
24) RETF - CBh
35) IRET - CFh
36) INT - CD, dati8
altre istruzioni
1) lea reg, mem
10011, d, w, r/m, reg, mod, dati
per es LEA DI, [BP+1000h] dovrebbe essere:
10011, 0, 1, 10, 111, 110, 0010
Opcode d w r/m reg mod dati
Un esempio di creazione dello scheletro di istruzioni
abbiamo i seguenti:
kreg = CX = 00000001
creg = BX = 00000011
e vogliamo creare:
(1) XOR CX, BX
(2) MOV [DI], CX
per la (1) abbiamo lo scheletro:
001100, d, w, 11, reg1, reg2
che codifichiamo come:
00110000 11000000
e quindi completiamo:
00110000 or 11000000 or
00000011 00001011
-------- --------
00110011 11001011 e abbiamo 0011001111001011 = 33CBh
Per la (2) lo scheletro è:
100010, d, w, r/m, reg, mod, dati
che codifichiamo come:
10001000 00000000
e quindi completiamo:
10001000 or 00000000 or
00000001 00001101
-------- --------
10001001 00001101 = 1000100100001101 = 890Dh
Così abbiamo:
33 CB xor cx, bx
89 0D mov [di], cx