GUIDA ALL'ASSEMBLY
by
   _____            __
  / ___/__  ___  __/ /____  ____ ___
  \__ \/ / / / |/_/ __/ _ \/ __ `__ \
 ___/ / /_/ />  </ /_/  __/ / / / / /
/____/\__, /_/|_|\__/\___/_/ /_/ /_/
     /____/
 


--[INTRODUZIONE]--

Non so neanch'io perchè mi son messo a fare sta guida, di solito non ne sono il tipo. Però sono un amante delle sfide, e siccome mi piace testare le mie abilità in tutto ho deciso di provare pure questo campo. Inoltre, trovo questo modo molto utile per saggiare le mie conoscenze.
Si tratta di una bella guida sull'assembly (rivolto ai traduttori di software)... è inutile farla su argomenti di cui si trova materiale a destra e a manca. Quindi questa non vorrà essere un sostituto per le tante altre guide che si possono trovare, ma sarà (o meglio... cercherà di essere) un completamento delle altre guide che si trovano in giro.
Questa, però, non sarà una semplice guida dedicata all'assembly (cosa di per sè inutile per quello che dobbiamo fare noi). Sarà una guida in cui protagonista sarà il reverse-engineering.
Cos'è il reverse-engineering? In pratica si tratta di fare il lavoro inverso di un programmatore. Mentre un programmatore scrive codice sorgente (nel linguaggio che lui preferisce) e poi lo compila, il reverse-engineer prende il compilato e cerca di ricostruirne il sorgente. Vedrete poi a che cosa possa servire la cosa.
Quindi questa non sarà una guida per farvi imparerete a "programmare" in assembly, però tramite questa imparerete l'assembly... se non capite adesso la differenza dovreste riuscirci man mano che seguirete questo tutorial.
L'inizio sarà un po' una palla e spesso vi chiederete cosa può servire tutto quello per la traduzione, ma vi assicuro... senza di quelle conoscenze non vi muovete, oppure lo fate MOLTO più lentamente. Cercate di sopportare.
Un'ultima cosa... questa guida è molto indirizzata al x86 (l'asm dei PC) in quanto è il tipo di assembler in cui io sono più preparato. Comunque una volta che ne si conosce uno non è tanto difficile passare ad un altro. Cambiano le caratteristiche e gli opcodes, ma il resto è sempre quello.

 

 


--[INDICE]--

- [CAPITOLO I - Introduzione all'Assembly] - Breve introduzione all'Assembly per chi non ne sa nulla
- [CAPITOLO II - Sistemi di numerazione] - Come funziona il sistema binario e l'esadecimale
- [CAPITOLO III - Assembly 4 Newbies] - Cerchiamo di capire subito le istruzioni fondamentali dell'assembly (x86)
x86 (PC):
     - [CAPITOLO IV - I registri] - Cosa sono i registri e quali sono quelli del x86
     - [CAPITOLO V - Gli interrupt] - Cosa sono gli interrupt e quali sono quelli del x86
     - [CAPITOLO VI - Le memorie] - Cosa sono le memorie e i vari modelli del x86
     - [CAPITOLO VII - Le istruzioni] - Quali sono le istruzioni del x86
     - [CAPITOLO VIII - Accenni alle modalità] - Accenni alla programmazione in modalità reale e in modalità protetta
     - [CAPITOLO Extra - Accenni al formato PE] - Qualcosina sul formato dei Portable Executable (exe, dll, ocx...)
Z80 (GB/GBC, ColecoVision):
     - [CAPITOLO IX - I Registri] - Quali sono i registri dello Z80
     - [CAPITOLO X - Gli interrupt] - Quali sono gli interrupt dello Z80
     - [CAPITOLO XI - Le istruzioni] - Quali sono le istruzioni dello Z80
     - [CAPITOLO XII - GameBoy] - Che differenze ci sono tra la sua CPU ed un normale Z80
     - [CAPITOLO Extra - L'header delle rom per GameBoy] - Conoscere come è formato l'header di una rom per GB/GBC
68000 (SMS/GG, NeoGeo):
     - [CAPITOLO Y - /] - Da realizzare
6502 (Nes, PC-Engine):
     - [CAPITOLO Y - /] - Da realizzare
65816 (Snes):
     - [CAPITOLO Y - /] - Da realizzare
MIPS (N64, PSX, PS2):
     - [CAPITOLO Y - /] - Da realizzare

- [CAPITOLO XIII - Comprendere meglio l'Assembly] - Alcuni consigli per imparare a reversare il codice Assembly
- [CAPITOLO XIV - Si traduce] - Qualche esempio guidato applicando le nozioni apprese
- [CAPITOLO XV - Compressioni] - Capire come un programma legge del software compresso per poi poterlo decomprimere

- [CAPITOLO XVI - Progressi] - Tutte le revisioni e aggiornamenti alla guida
- [CAPITOLO XVII - Conclusioni] - Saluti vari, disclaimer etc...

 

 


--[PROGRAMMI]--

Questa è la lista dei programmi che verranno usati nel corso della guida. Si possono trovare tutti nella rete.
 

HexWorkshop: E' il mio editor esadecimale preferito. Tramite questo modificheremo il codice assembly del gioco.
IDA: Il miglior disassemblatore in circolazione! Con questo programma ricostriremo il codice asm dal file compilato.
WDasm: Altro famoso dissassemblatore. Solo per avere sottomano del codice asm meno filtrato di quello che restituisce IDA.
P.H.O.G. Pentium Hexadecimal Opcode Generator. Questo programmino restituisce i byte di ogni istruzione asm che gli date.
Altro: Tutti i programmi e i giochi per gli esempi, in modo da seguire meglio la guida.


 


--[CAPITOLO I - Introduzione all'Assembly]--

Ok, finalmente si parte... cominciamo col dire che qualsiasi sistema informatico, qualunque sia la sua potenza o costo, capisce solo una cosa: il linguaggio binario. La combinazioni di 0 e 1 (che corrispondono ad un livello basso e ad un livello alto di corrente) assumono per il processore un significato che lo fanno eseguire determinate operazioni..
Sarebbe però impensabile per l'uomo leggere del codice tipo:

11011000 01010111 00001011 10101100 etc...
diventerebbe scemo subito!
Per facilitare questo è stato inventato questo astruso linguaggio chiamato assembly. L'assembly infatti non è altro che un assegnazione a queste combinazioni di sigle che spesso sono abbreviazioni del significato delle istruzioni.
Va anche detto che questo tipo di linguaggio è strettamente legato al processore (infatti molte volte viene utilizzato per ottimizzare piccole parti di codice) e varia da macchina a macchina (il linguaggio asm della Playstation è diversissimo da quello per Nintendo64).
Segue una lista delle piattaforme più importanti, seguite dalla loro CPU o dal loro linguaggio asm

PC:
DreamCast:
Nintendo64:
PlayStation 2:
PlayStation:
Neo-Geo:
Neo-GeoPocket:
Super Nintendo:
Nintendo:
GameBoy:
GameBoy Advance:
SegaMasterS./GGear:
Sega Genesis:
Sega Saturn:
PC-Engine:
IntelliVision:
CreatiVision:
ColecoVision:
WonderSwan:
Atari Lynx:
Atari 2600:
x86
Hitachi SH4
MIPS R4300i (Big Endian)
MIPS Mark IV: R5900
MIPS Mark II: R3000A
Motorola 68000
Toshiba TLCS-900H (ma è una CPU proprietaria, si basa sul TLCS-900H ma è proprietaria)
65c816
6502
Z80 (ma è una CPU proprietaria, si basa sullo Z80 ma è proprietaria)
ARM7TDMI (32 bit)
Motorola 68000
Motorola 68000
Hitachi SH2
6502
GI
Rockwell 6502A (8 bit)
Z80A (8bit)
NEC V30 MZ
6507


Vediamo un'altra cosa adesso... cosa significa che Windows è a 32 bit ?
Penso sappiate cosa è un bit (il singolo 1 o 0) e che un byte è formato da 8 bit. Quindi se volete fare una conversione bit -> byte basta che dividiate il numero per otto.
Allora, cosa significa che Windows è a 4 byte? Ciò è riferito alla dimensione dei registri del processore. Più avanti vedremo il perchè è importante questa informazione.

 


--[CAPITOLO II - Sistemi di numerazione]--

Uff, questo capitolo non lo volevo neanche fare (dato che viene spiegato in praticamente ogni guida per traduzioni), ma dato che me l'hanno chiesto vedrò di dire qualcosina.
Esistono molti sistemi di numerazione (possono essere infiniti), però in informatica se ne usano principalmente tre: il binario (0 e 1 ... perchè è proprio il modo in cui conta il processore, con impulsi alti e bassi), il decimale (da 0 a 9 ... perchè è il sistema che usiamo "di solito" noi, a meno che voi non andate al mercato a chiedere 4ACh di frutta :D) e l'esadecimale (da 0 a 15 ... uff... qua non vi sto a spiegare il motivo che è un pochino più lungo). (un tempo si usava anche l'ottale (da 0 a 7), però poi questo sistema è stato abbandonato)
Vediamoli uno per uno.
Beh, penso che non debba spiegarvi il sistema decimale (se non lo sapete la cosa è preoccupante).

Parliamo invece del sistema binario... abbiamo solo 0 e 1, ma combinandoli possiamo creare qualsiasi numero.
Per convertire un numero da decimale a binario... dobbiamo fare divisioni successive, del numero, per 2 e se abbiamo il resto uguale a uno, scriviamo 1, altrimenti scriviamo 0... fino alla fine.
Facciamo un esempio con un numero a caso (737) che è meglio...

737 / 2 = 368
368 / 2 = 184
184 / 2 = 92
92 / 2 =  46
46 / 2 = 23
23 / 2 = 11
11 / 2 = 5
5 / 2 = 2
2 / 2 = 1
1 / 2 = 0
resto 1
resto 0
resto 0
resto 0
resto 0
resto 1
resto 1
resto 1
resto 0
resto 1

A questo punto prendiamo tutti i resti ottenuti (1000011101) e invertiamo tutto... otteniamo 1011100001.
Proviamo a vedere che ci dice la calcolatrice di Windows... settiamola a scientifica e scriviamo 737, quindi switchamo in modalità binaria (Bin) e magia! Il numero restituito è proprio 1011100001!
Per convertire da binario a decimale è più facile. Basta moltiplicare ogni cifra per...
la base che si sta usando (2 perchè lavoriamo in binario, base 2. Se lavorassimo in base 3 (da 0 a 2) dovremmo moltiplicare per 3) alla potenza di n.
Insomma 2^n, in cui n è un numero che va da 0 a il numero di cifre - 1, partendo dalla cifra meno significativa, e infine sommare ogni risultato.
Vabbè... ho capito... facciamo il solito esempio.
1011100001.... 1*2^0 + 0*2^1 + 0*2^2 + 0*2^3 + 0*2^4 + 1 * 2^5 + 1*2^6 + 1*2^7 + 0*2^8 + 1*2^9
Ok, semplifichiamo un po' di roba...
2^0 + 2^5 +2^6 + 2^7 + 2^9 = 1 + 32 + 64 + 128 + 512 = 737

Bene... parliamo adesso del sistema esadecimale.
Sapete come va l'esadecimale, no? Da 0 a 9 e prosegue con le lettere dalla A alla F.
E poi in pratica è la stessa cosa del binario... Per convertire da dec->hex si fanno divisioni successive, convertendo alla fine i resti (ad esempio... se il resto è 13 ci mettete una D) e poi invertendoli.
Per la convertire hex->dec invece, convertiamo eventuali lettere in numeri, poi li moltiplichiamo per 16^n (n è sempre un numero che va da 0 al numero di cifre -1... come prima). Infine sommiamo tutto.

Beh... direi che qua abbiamo finito. Con questo dovreste essere in grado di convertire anche qualunque altro sistema di numerazione (tanto, visto uno visti tutti), ma io vi consiglio di usare per questi tre sistemi di numerazione la calcolatrice di windows (versione scientifica) che gestisce pure l'ottale.
Volendo ci sono anche metodi più veloci per convertire un numero bin->hex e viceversa... però non ve lo sto a spiegare in questa guida. Usate la calcolatrice di windows che fate prima. :)

 

 


--[CAPITOLO III - Assembly 4 Newbies]--

Beh, è difficile cercare di spiegare in modo semplice e chiaro per tutti l'assembly. Quello che questo capitolo cerca di fare è velocizzare il vostro apprendimento delle istruzioni più usate in modo di poter, quanto prima, permettervi di immergervi in un listato assembly.
Quindi, verranno spiegate le istruzioni principali in un modo un po' più "facile e dettagliato" rispetto alle spiegazioni del capitolo delle istruzioni asm per il x86. Vi avverto che a volte le spiegazioni delle varie istruzioni saranno volutamente incomplete... questo per semplificarvi la lettura, tralasciando tutte quelle informazioni che non sono indispensabili per ci è a gli inizi. Comunque le informazioni complete le trovate nel capitolo delle istruzioni del x86. Prima di cominciare un'ultima cosa... E' meglio che vi leggiate il capitolo sui registri (successivo a questo), perchè quando dico "AX va in ECX" dovete capirmi al volo, e se non sapete che cosa sono i registri e come funzionano, non imparerete mai l'assembly, non c'è niente da fare ;)
Ok, finita questa rottura... incominciamo.
Intanto vediamo di classificare un po' le istruzioni, in modo da poter seguire meglio tutto il capitolo...
Un modo per organizzare le istruzioni è di tipo funzionale, cioè in base all'azione che esse svolgono. Quindi possiamo suddividere le istruzioni in:
- istruzioni di spostamento dei dati
- istruzioni logiche
- istruzioni aritmetiche

- istruzioni di Input/Output
- istruzioni di modifica del flusso (salti e iterazioni)
- istruzioni varie
Un'altra cosa che bisogna tenere a mente è che tutte le istruzioni riportano prima l'operando destinazione e poi l'operando sorgente dell'operazione. Capirete meglio man mano che spiegherò le varie istruzioni.

Bene, e ora cominciamo con le istruzioni di spostamento dei dati.
Per spostamento dei dati si intende il trasferimento di dati da una zona a un'altra della memoria oppure la diretta assegnazione di un valore a una locazione di memoria o a un registro.
Alcuni esempi:
Metti in BH il contenuto di AH
Metti in CL il contenuto della locazione di memoria 178D
Metti nella locazione 25D9 il contenuto di CH
Metti in CH il valore 0xF5
Le istruzioni che appartengono a questa classe hanno una grande versatilità, unita ad una notevole semplicità d'uso.
Il x86, con un'unica istruzione riesce a garantire il trasferimento dei dati tra le differenti aree del processore.
Si tratta dell'istruzione MOV (che sta per MOVE).
Traduciamo adesso i quattro esempi in codice ASM...
mov BH, AH
mov CL, [178D]
mov [25D9], CH
mov BH, F5h
Come potete vedere usiamo sempre l'istruzione MOV. L'unica annotazione da fare è questa: se si fa riferimento al contenuto di una locazione di memoria (come nel secondo e terzo esempio), l'indirizzo è indicato tra parentesi (quadre nel x86)... mentre se facciamo riferimento immediatamente ad un valore (come nell'ultimo caso) o al contenuto di un singolo registro, non si usano parentesi.
Da notare anche che se passiamo immediatamente un valore (come nell'ultimo caso abbiamo passato 0xF5), è sempre meglio aggiungere una "h" alla fine. In questo caso non ci sarebbero stati problemi, ma mettiamo il caso che l'istruzione fosse stata "mov BH, 10"... a questo punto il 10 come va considerato? Vale 10 perchè è già espresso in decimale o vale 16 perchè 10 era espresso in esadecimale? Solitamente l'assemblatore considera questo caso come un numero decimale, ma la cosa può variare da assemblatore ad assemblatore. Quindi è meglio che seguiate la regola "se è esadecimale ci metto sempre una "h" dopo", se è decimale (e potete assegnare dei numeri decimali, ma io vi consiglio di operare sempre in esadecimale quando lavorate in asm) ovviamente no.
Notare anche che i processori Pentium dispongono della MOV condizionale (CMOV). Vi rimando al capitolo delle istruzioni x86 per maggiori dettagli

Istruzioni logiche:
Le istruzioni logiche effettuano le normali operazioni come ad es. OR, AND, NOT e XOR.
Gli operandi sono intesi nella loro forma binaria e il risultato dell'operazione influenza i bit del registro dei flag.
Vediamo qualche esempio:
AND BL, CH
OR   AH, AL
XOR CL, CH
XOR EAX, EAX
NOT DL
e adesso vediamole in dettaglio.

AND... la sapete la tabellina logica della AND, vero?
 

And 0 1
0 0 0
1 0 1


Come vedete, l'AND fa un confronto tra bit e restituisce 1 solo se entrambi sono 1.
Ad esempio.... prendiamo 2 byte a caso e vediamone il risultato.
CH contiene il byte B9h e BL contiene il byte 73h

1° Byte 1 0 1 1 1 0 0 1
2° Byte 0 1 1 1 0 0 1 1
Risultato 0 0 1 1 0 0 0 1


Quindi... Viene fatto il confronto tra il 1° e il 2° byte. Il risultato è 31h che viene messo in BL

Vediamo adesso la OR
 

Or 0 1
0 0 1
1 1 1


Restituisce 0 se entrambi gli operandi sono 0, altrimenti restituisce 1.
Prendiamo sempre i 2 byte d'esempio sopra.

1° Byte 1 0 1 1 1 0 0 1
2° Byte 0 1 1 1 0 0 1 1
Risultato 1 1 1 1 1 0 1 1


Il risultato è FBh che, seguendo i 5 esempi sopra, va nel registro AH.

Ok, è il turno della XOR, ossia della Or esclusiva.
 

Xor 0 1
0 0 1
1 1 0


Restituisce 1 se gli operandi sono diversi, altrimenti restituisce 1.
Prendiamo sempre i 2 byte d'esempio sopra.

1° Byte 1 0 1 1 1 0 0 1
2° Byte 0 1 1 1 0 0 1 1
Risultato 1 1 0 0 1 0 1 0


Risultato = CAh che va a finire in CL
L'esempio dopo l'ho fatto solo per farvi notare una cosa che vi sarà molto utile... Quando trovate uno Xor che ha i due operandi uguali, potete commentare subito con "operando = 0", in quando non potrà mai essere diverso... fatevi qualche esempio da soli e guardate, se non ci credete!
Troverete quest'istruzione molto frequentemente, dato che è uno dei modi più veloci per settare a 0 un byte (oltre a mov <op>, 0h ;) ). Viene usato spesso anche per inizializzare i registri... insomma, la Xor la dovete sapere come le vostre tasche.

Bene, vediamo l'ultima... la NOT.
La Not è un'operazione unaria, ossia richiede un solo operando. Ecco la tabella
 

Not  
0 1
1 0


In sintesi, non fa altro che invertire i bit... se c'è 1 ci mette 0 e se c'è 0 ci mette 1.
Vediamo la Not sul byte B9h.

1° Byte 1 0 1 1 1 0 0 1
Risultato 0 1 0 0 0 1 1 0


DL assumerà il valore di 46h.

Ok, abbiamo finito le istruzioni logiche... passiamo alle istruzioni aritmetiche.
Si tratta delle istruzioni che consentono di realizzare addizioni, sottrazioni, moltiplicazioni, divisioni... più altre istruzioni come incremento, decremento e confronto.
Ecco la solita carrellata di esempi utilizzata per descrivere le varie istruzioni.
ADD AH, AL
ADC AH, AL
SUB CL, CH
SBB CL, CH
CMP DL, DH
NEG AX
INC   EBX
DEC EDX


La prima (ADD) è l'istruzione dell'addizione. Somma AL a AH e memorizza il risultato in AH. ADC è sempre un'addizione tra i due operandi, solo che in quella viene sommato anche l'eventuale riporto (contenuto nel flag C). Entrambe le istruzioni modificano quindi i flag Z (se il risultato è zero, Z = 1 altrimenti Z = 0) e C (se c'è riporto C = 1 altrimenti C = 0).

Quindi abbiamo una SUB. È come l'ADD, solo che qua si tratta della sottrazione. La corrispondente della ADC invece è la SBB (sottrazione con riporto. Coinvolgono gli stessi flag delle precedenti 2 istruzioni.

CMP è l'istruzione di confronto. E' in pratica come la SUB (una sottrazione che modifica i flag), solo che qua non viene effettuato il salvataggio finale nel registro. Serve per individuare (grazie ai flag) la relazione d'ordine (uguaglianza, maggiore, minore) tra i due operandi. Se sono uguali viene impostato il flag Z a 1 e se il primo è minore del secondo il flag C diventa 1.
Questa operazione è strausata prima di salti e call condizionate, per cui imparatela bene che è importantissima.

La NEG è semplicemente il cambio del segno.

Invece INC e DEC corrispondono rispettivamente all'incremento e al decremento degli operandi.

Notate che ho omesso le operazioni di moltiplicazione e di divisione (quest'ultima in alcuni processori non esiste neanche, ma è ricavata :) ). Il motivo è che le reputo più difficili da capire per un Newbie, rispetto a queste. Comunque, se volete, potete sempre andare a consultarle nel capitolo sulle istruzioni del x86. Sono rispettivamente MUL/IMUL e DIV/IDIV (senza e con segno).

È arrivato il turno delle istruzioni di Input/Output.
Sono quelle che consentono il trasferimento di informazioni tra periferiche e microprocessore sia in ingresso (IN, da una perifierica verso il processore) sia in uscita (OUT, dal processore verso la periferica). Viene trattato alla fine come un normale spostamento di dati visto prima: si tratta, infatti, semplicemente di trasferire dati da un dispositivo a un altro specificando un indirizzo di sorgente e uno di destinazione.
Non c'è tanto altro da dire su queste istruzioni (e poi visto che questo è il capitolo per newbies è già tanto che ne parlo)... 
Per utilizzare queste istruzioni potrete usare esclusivamente (E)AX (decidendo così la quantità di byte da inviare e ricevere) e DX.

IN <accumulatore>, <indirizzo>          
IN <accumulatore>, DX
OUT <indirizzo>, <accumulatore>
OUT DX, <accumulatore>
Input in <accumulatore> dalla periferica di indirizzo <indirizzo>
Input in <accumulatore> dalla periferica con indirizzo nel registro DX
Output nella periferica <indirizzo> del valore di <accumulatore>
Output nella periferica di indirizzo in DX del valore di <accumulatore>

Istruzioni di modifica del flusso
In questa sezione rientrano tutte quelle istruzioni che modificano la normale esecuzione sequenziale delle istruzioni. Una modifica del flusso viene causata dalle istruzioni di salto e dalle istruzioni iterative.
Salti incondizionati:
Fanno in modo che l'esecuzione del programma continui a partire dall'indirizzo segnalato ogni volta che viene incontrata l'istruzione (è simile all'istruzione di alto livello GO TO)
La JMP e l'istruzione più facile da capire dell'assembly e si fa così:
jmp <indirizzo>

oppure con
jmp <registro>
Notare che è possibile saltare anche di un tot di byte (salti incondizionati relativi)... basta fare qualcosa tipo
JMP 4B (in questo modo si salta 75 byte in avanti)
È possibile saltare di un tot di byte anche indietro... se vogliamo saltare di 47 byte indietro, basta vedere il complemento a 2 della sua conversione in esadecimale (insomma, farci sopra un NEG). Otteniamo come risultato JMP D1.
Salti condizionati:
Vengono eseguiti solo al verificarsi di una determinata condizione. Se quella condizione non si verifica l'istruzione di salto viene ignorata e si prosegue col programma secondo la sequenza prevista.
Come forse avrete già capito, tali condizioni riguardano il valore dei flag.
Da notare che per i salti condizionati (nel x86) non possono essere effettuati salti relativi.

JA
JAE
JB
JBE
JCXZ
JE
JGE
JL
JNE
JNO
JNP
JO
JP
JS
salta se il flag C e Z sono entrambi a 0
salta se il flag C vale 0
salta se il flag C vale 1
salta se il flag C e Z sono a 1
salta se il registro CX vale 0
salta se il flag Z vale 0
salta se il flag S è uguale al flag O
salta se il flag S è diverso dal flag O
salta se il flag Z vale 0
salta se il flag O vale 0
salta se il flag P vale 0
salta se il flag O vale 1
salta se il flag P vale 1
salta se il flag S vale 1


Iterazioni:
Nel x86 esiste un'istruzione che permette di effettuare iterazioni.
Tale istruzione è la LOOP e utilizza il registro CX come contatore a decremento. Vengono effettuati dei salti relativi.

 

LOOP <byte da saltare>
LOOPZ/LOOPE <byte da saltare>
LOOPNZ/LOOPNE <byte da saltare>
salta se CX è diverso da 0
salta se CX è diverso da 0 e il flag Z vale 1
salta se CX è diverso da 0 e il flag Z vale 0

 

Chiamata a procedura (+ istruzioni per la manipolazione dello stack):
Si tratta delle fantomatiche CALL <indirizzo/registro>. Con queste è possibile richiamare sottoprogrammi nel codice, in modo incondizionale e non. Vediamo meglio come funziona la storia...
Quando viene eseguita questa istruzione la CPU fa un jump all'indirizzo indicato. Dal punto in cui arriva, continua normalmente finchè non trova l'istruzione RET. A quel punto la cpu esce dal sottoprogramma ritornando all'istruzione dopo la Call. Ma come fa la Cpu a capire dove tornare dalla call, se RET è un'istruzione senza parametri? Beh... è semplice, viene usato lo stack.
   mode SPIEGAZIONE on...
Che cos'è lo stack? Si tratta di una parte della memoria che può essere gestita in modo LIFO o FIFO (dipende dal tipo di processore). Nel x86 è LIFO, ossia Last In First Out... cioè l'ultima l'istruzione inserita è la prima ad essere elaborata. Non capite, eh?! Vabbè... facciamo così... immaginatevi una pila di piatti su un tavolo. Man mano che ne arriva uno lo si mette sopra a gli altri. Se però volete prenderne uno, dovete prendere il primo in cima alla pila. Ecco, uno stack LIFO funziona così (infatti si dice che è una struttura a pila).
   mode SPIEGAZIONE off
Quando viene effettuata una call, viene memorizzato nello stack l'indirizzo di ritorno e quindi viene effettuato il jump all'indirizzo indicato nell'istruzione.
Ok... visto che ormai ho introdotto lo stack, tanto vale che parlo pure un poco delle istruzioni che lo coinvolgono attivamente.
Si tratta di PUSH <valore/segmento/registro/memoria> e POP <segmento/registro/memoria>.
La prima immette nello stack il valore dell'operando. Quindi viene decrementato lo stack pointer a seconda della dimensione dell'operando, che può essere grande 2 o 4 byte (già, non potete passargli un byte soltanto).
POP invece, diciamo che fa tutto il contrario di PUSH. Questa viene usata per prelevare valori dallo stack che vengono assegnati a gli operandi. Anche qui potete leggere solo 2 o 4 byte e SP viene incrementato di conseguenza. 

Istruzioni di scorrimento
:
E abbiamo cominciato a parlare delle istruzioni varie. Le istruzioni di scorrimento permettono di far scorrere il byte indicato come sorgente di un bit, con modalità che cambiano da istruzione a istruzione. Sono suddivise in due gruppi: le istruzioni di rotazione e le istruzioni di shift. Le prime effettuano semplicemente uno spostamento di bit nella direzione indicata, per cui i bit che, durante lo scorrimento, traboccano da un'estremità, ricompaiono circolarmente dall'altra. Le istruzioni di shift invece, effettuano modifiche o forzature legate al bit che trabocca durante lo scorrimento, secondo i casi.
Mi rendo conto che questi discorsi possono apparire un po' complicati... vediamo allora di fare un disegnino per esplicare meglio i concetti.
                        ROL
         +----------------------------------+
  ___    |   __ __ __ __ __ __ __  __       |
 |   |   |  |  |  |  |  |  |  |  ||  |      |
 |___|<--+--|__|__|__|__|__|__|__||__|<-----+
 carry      bit7                  bit0
                        RCL
 +------------------------------------------+
 |   ___       __ __ __ __ __ __ __  __     |
 |  |   |     |  |  |  |  |  |  |  ||  |    |
 +<-|___|<----|__|__|__|__|__|__|__||__|<---+
    carry      bit7                  bit0
                        SHL
  ___       __ __ __ __ __ __ __  __
 |   |     |  |  |  |  |  |  |  ||  |
 |___|<----|__|__|__|__|__|__|__||__|<----- 0
 carry     bit7                  bit0
                        SAL
  ___       __ __ __ __ __ __ __  __
 |   |     |  |  |  |  |  |  |  ||  |
 |___|<----|__|__|__|__|__|__|__||__|<------+
 carry     bit7                  bit0       |
                                   |        |
                                   +--------+

Ora dovrebbe essere più chiaro, vero? Qui di seguito invece trovate le varie istruzioni di rotazione e di shift.
 

ROL <destinazione>, <volte>
RCL <destinazione>, <volte>
ROR <destinazione>, <volte>
RCR <destinazione>, <volte>
SAL <destinazione>, <volte>
SHL <destinazione>, <volte>
SAR <destinazione>, <volte>
SHR <destinazione>, <volte>
rotazione a sinistra
rotazione a sinistra con riporto
rotazione a destra
rotazione a destra con riporto
shift a sinistra aritmetico
shift a sinistra logico
shift a destra aritmetico
shift a destra logico


Ok, io direi di finire qui... queste mi sembrano più che sufficienti per l'inizio e cmq trovate tutte le altre nel capitolo delle istruzioni per il x86. Cercate di capire bene come funzionano, perchè le ritroverete (anche se, forse, con nomi diversi e particolarità proprie) pure negli altri processori. Spero che con questo capitolo i niubbi siano riusciti a capire meglio le principali istruzioni assembly, in modo da essere più preparati a seguire il resto della guida. 

 



--[ x86 ]--
 


--[CAPITOLO IV - I registri]--

So già che vi sarete chiesti "ma cosa sono questi registri?". Ebbene essi non sono altro che locazioni interne al processore, nelle quali memorizza variabili in modo istantaneo (senza dialogare col bus, la ram, la cache etc...).
Vi avverto... sta per iniziare una sfilza di notizie che riguardano più che altro il x86 (che comunque sono utili per conoscere dopo anche quelli di altri processori) e che, escludendo i registri generali, probabilmente non vi servirebbero... ma per completezza ci sono. Fate un bel respiro e procedete.
Si dividono in...

Registri generali
(32 bit):
EAX - Registro accumulatore, usato per la maggior parte delle operazioni. E' composto in due parti, la word alta e la word bassa. Per mantenere
la compatibilità con i vecchi applicativi esistenti in commercio quando il processore 80386 - il primo ad aver introdotto tale registro - è possibile accedere alla word bassa del registro semplicemente usando il registro AX. Il registro AX è successivamente scomposto in AH ed AL, il primo il byte alto e il secondo il byte basso che compongono tale registro.

EBX
- Registro base, utilizzato in modo spesso e volentieri. In modo identico al registro EAX e al registro ECX ed EDX di cui andremo a parlare è composto dal registro BX che successivamente si divide in BH e BL.

ECX - Registro contatore, come dice il nome stesso viene utilizzato come contatore ovvero nei cicli e nelle operazioni di stringa o addirittura nelle operazioni di moltiplicazione con EAX ed EDX, o nelle divisioni. Composto da CX (CH e CL)

EDX - Registro dati, usato principalmente per estendere la capacità di EAX nelle moltiplicazioni o per tenere il resto delle divisioni. Composto da DX (DH e DL)

ESI/EDI - Questi due registri vengono spesso usati in coppia ed hanno la stessa medesima funzione... servono per manipolare le stringhe! Il registro ESI è il registro di origine (Source Index) e il registro EDI è il registro di destinazione (Destination Index). Come avrete occasione di leggere più avanti vengono utilizzati da talune tipi di istruzioni. A differenza dei 4 registri sopra elencati questi due registri sono composti da SI e DI e non hanno ulteriori scomposizioni!

EBP - Base Pointer ... puntatore di base, viene spesso utilizzato dai compilatori per la gestione del passaggio dei parametri nello stack o per la gestione delle variabili... è composto dal registro BP.

ESP - Stack Pointer, questo registro è utilizzato in coppia al registro di segmento SS per gestire lo stack... ovvero la memorizzazione di valori in modo temporaneo. Nell'architettura iAPX86 lo stack viene gestito in modo LIFO ovvero Last In First Out, cioè l'ultimo valore che sarà memorizzato sarà il primo valore ad uscire dallo stack! Oltre a questo tipo di gestione esiste il modo FIFO (First In First Out), il primo ad entrare sarà il primo ad uscire, che è adoperato in architetture Motorola (per quel che ne sappia.. non so quali altri processori lo utilizzano!). Da notare che l'uscita di un elemento dallo stack non ne comporta la cancellazione dallo stesso, ma bensì l'incremento del registro ESP (4 byte se si tratta di una DWORD o 32bit, 2 byte se si tratta di una WORD o 16bit). Avremo modo di esaminare più avanti lo stack.

EIP - Istruction pointer... questo è un registro di cui non potrete mai conoscere il valore!!! Infatti è inviolabile, nessuna istruzione vi permette di usarlo, ma è modificabile in modo indiretto. Il suo valore indica l'offset dell'istruzione da eseguire.

CS  - Segmento codice, viene utilizzato in coppia al registro EIP per puntare all'istruzione successiva da eseguire. Il valore del registro CS non può essere cambiato in modo diretto, ma soltanto tramite alcune istruzioni per eseguire porzioni di codice al di fuori del segmento in cui si sta lavorando.

DS  - Segmento dati, utilizzato per gestire i dati, usato in coppia con ESI DS:ESI costituiscono il punto di origine nelle manipolazioni di stringhe di dati.

ES  - Segmento EXTRA, utilizzato per gestire dati, usato in coppia con EDI ES:EDI costituiscono il punto di destinazione nella manipolazione di stringhe di dati.

SS  - Segmento STACK, usato insieme ad ESP SS:ESP vengono utilizzate per la gestione dello stack.

FS/GS  - Segmenti aggiunti con l'architettura 80386 in poi, vengono utilizzate per gestire i segmenti o per la manipolazione dei task.

EFLAGS - Registro dei FLAG ovvero di bit di controllo. Serve a stabilire alcuni comportamenti del processore, o semplicemente ad influenzare l'esecuzione degli applicativi. Solo il registro FLAG è disponibile con i processori precedenti al 80386

CR0/CR2/CR3 - Registri di controllo. Usati solamente dai programmatori di sistemi operativi. Non vi dovrete scontrare con essi a meno che non siate decisi a scrivere un vostro sistema operativo.                                                                

DR0/DR1/DR2/DR3 - Registri d'indirizzo di debugging. Usati per facilitare il debugging su questa architettura.

DR7 - Registro di controllo di debugging

DR6 - Registro di stato di debugging


Registri di test - Usati per controllo.... quasi impossibile usarli! Non conosco nemmeno i nomi!!!

GDTR - Global Descriptor Table Register


LDTR - Local Descriptor Table Register


IDTR - Interrupt Descriptor Table Register


TR   - Task Register

A meno che non siate programmatori di sistema operativo questi ve li scordate! Sono utilizzati nella modalità protetta... più avanti vi accennerò!


Registri FLOATING POINT - I registri del coprocessore matematico. Non farò nessun accenno a questo set... equivale quasi ad un'altro processore affiancato a quello che avrete... dotato di un proprio set di istruzioni (e non sono poche!!! Poi ve li elenco!!!) per l'esecuzione di operazioni in virgola mobile (ovvero quelli dove ci sta la parte decimale!)


Registri MMX - Registri per le istruzioni MMX, praticamente sono gli stessi registri della FLOATING POINT infatti non è possibile usare istruzioni MMX insieme ad istruzioni F.P. in quanto succede un macello e la translazione da MMX a F.P. richiede uno spreco di cicli. E come se non bastasse le istruzioni F.P. sono a dir poco lunghette come quantità di tempo richiesto per l'esecuzione.


Vabbè, direi che è già abbastanza essere arrivati fino ai registri MMX. Tanto non c'è un compilatore decente che implementi queste istruzioni!
Un'occhiata più da vicino però va data al registro dei flag

IL REGISTRO EFLAGS 

       E             FLAGS
31              |              0
00000000000000000000000000000000
||||||||||||||||||||||||||||||||
\\\\\\\\\\\\\\||\|||||||||\|\|\| Da definire
              \| ||||||||| | | | Modo virtuale 8086
               \ ||||||||| | | | Flag ripristino
                 \|||||||| | | | Flag task annidato
                  \\|||||| | | | Livello privilegio I/O
                    \||||| | | | Overflow
                     \|||| | | | DF
                      \||| | | | Ablitazione interruzione
                       \|| | | | Trap Flag
                        \| | | | SF
                         \ | | | Zero Flag
                           \ | | AF
                             \ | PF
                               \ Carry Flag


 


--[CAPITOLO V - Gli interrupt]--

Cosa sono gli interrupt? Non sono altro che eccezioni, che potete invocare direttamente voi o che sono generate via hardware (ad esempio... ad ogni pressione di un tasto viene generata un'eccezione).
Beh, sentite ragazzi..., io non posso spiegarvi uno per uno gli interrupt del x86, anche perchè l'ha già fatto Ralf Brown (e bene) e non mi va di riportare qui i suoi quasi 6 MB di file *.hlp . Mi limito a copiarne la lista.
INT 00 - CPU-generated - DIVIDE ERROR
INT 01 - CPU-generated - SINGLE STEP; (80386+) - DEBUGGING EXCEPTIONS
INT 02 - external hardware - NON-MASKABLE INTERRUPT
INT 03 - CPU-generated - BREAKPOINT
INT 04 - CPU-generated - INTO DETECTED OVERFLOW
INT 05 - PRINT SCREEN; CPU-generated (80186+) - BOUND RANGE EXCEEDED
INT 06 - CPU-generated (80286+) - INVALID OPCODE
INT 07 - CPU-generated (80286+) - PROCESSOR EXTENSION NOT AVAILABLE
INT 08 - IRQ0 - SYSTEM TIMER; CPU-generated (80286+)
INT 09 - IRQ1 - KEYBOARD DATA READY; CPU-generated (80286,80386)
INT 0A - IRQ2 - LPT2/EGA,VGA/IRQ9; CPU-generated (80286+)
INT 0B - IRQ3 - SERIAL COMMUNICATIONS (COM2); CPU-generated (80286+)
INT 0C - IRQ4 - SERIAL COMMUNICATIONS (COM1); CPU-generated (80286+)
INT 0D - IRQ5 - FIXED DISK/LPT2/reserved; CPU-generated (80286+)
INT 0E - IRQ6 - DISKETTE CONTROLLER; CPU-generated (80386+)
INT 0F - IRQ7 - PARALLEL PRINTER
INT 10 - VIDEO; CPU-generated (80286+)
INT 11 - BIOS - GET EQUIPMENT LIST; CPU-generated (80486+)
INT 12 - BIOS - GET MEMORY SIZE
INT 13 - DISK
INT 14 - SERIAL
INT 15 - CASSETTE
INT 16 - KEYBOARD
INT 17 - PRINTER
INT 18 - DISKLESS BOOT HOOK (START CASSETTE BASIC)
INT 19 - SYSTEM - BOOTSTRAP LOADER
INT 1A - TIME
INT 1B - KEYBOARD - CONTROL-BREAK HANDLER
INT 1C - TIME - SYSTEM TIMER TICK
INT 1D - SYSTEM DATA - VIDEO PARAMETER TABLES
INT 1E - SYSTEM DATA - DISKETTE PARAMETERS
INT 1F - SYSTEM DATA - 8x8 GRAPHICS FONT
INT 20 - DOS 1+ - TERMINATE PROGRAM
INT 21 - DOS 1+ - Function Calls
INT 22 - DOS 1+ - PROGRAM TERMINATION ADDRESS
INT 23 - DOS 1+ - CONTROL-C/CONTROL-BREAK HANDLER
INT 24 - DOS 1+ - CRITICAL ERROR HANDLER
INT 25 - DOS 1+ - ABSOLUTE DISK READ
INT 26 - DOS 1+ - ABSOLUTE DISK WRITE
INT 27 - DOS 1+ - TERMINATE AND STAY RESIDENT
INT 28 - DOS 2+ - DOS IDLE INTERRUPT
INT 29 - DOS 2+ - FAST CONSOLE OUTPUT
INT 2A - NETBIOS
INT 2B - DOS 2+ - RESERVED
INT 2C - DOS 2+ - RESERVED
INT 2D - DOS 2+ - RESERVED
INT 2E - DOS 2+ - PASS COMMAND TO COMMAND INTERPRETER FOR EXECUTION
INT 2F - Multiplex
INT 30 - (NOT A VECTOR!) - DOS 1+ - FAR JMP instruction
INT 31 - overwritten by CP/M jump instruction in INT 30
INT 32 - (no special use)
INT 33 - MS MOUSE
INT 34 - FLOATING POINT EMULATION - OPCODE D8h
INT 35 - FLOATING POINT EMULATION - OPCODE D9h
INT 36 - FLOATING POINT EMULATION - OPCODE DAh
INT 37 - FLOATING POINT EMULATION - OPCODE DBh
INT 38 - FLOATING POINT EMULATION - OPCODE DCh
INT 39 - FLOATING POINT EMULATION - OPCODE DDh
INT 3A - FLOATING POINT EMULATION - OPCODE DEh
INT 3B - FLOATING POINT EMULATION - OPCODE DFh
INT 3C - FLOATING POINT EMULATION - SEGMENT OVERRIDE
INT 3D - FLOATING POINT EMULATION - STANDALONE FWAIT
INT 3E - FLOATING POINT EMULATION - Borland "SHORTCUT" CALL
INT 3F - Overlay manager interrupt (Microsoft/Borland)
INT 40 - DISKETTE - RELOCATED ROM BIOS DISKETTE HANDLER
INT 41 - SYSTEM DATA - HARD DISK 0 PARAMETER TABLE; CPU - MS Windows
INT 42 - VIDEO - RELOCATED DEFAULT INT 10 VIDEO SERVICES (EGA,VGA)
INT 43 - VIDEO DATA - CHARACTER TABLE (EGA,MCGA,VGA)
INT 44 - VIDEO DATA - CHARACTER FONT (PCjr); Novell NetWare
INT 45 - Z100/Acorn
INT 46 - SYSTEM DATA - HARD DISK 1 DRIVE PARAMETER TABLE
INT 47 - Z100/Acorn/Western Digital/SQL Base
INT 48 - KEYBOARD (PCjr) - Z100/Watstar/Acorn/Western Digital/Compaq
INT 49 - SYSTEM DATA (PCjr) - Z100/TI/Watstar/Acorn/MAGic
INT 4A - SYSTEM - USER ALARM HANDLER
INT 4B - IBM SCSI interface; Virtual DMA Specification (VDS)
INT 4C - Z100/Acorn/TI
INT 4D - Z100
INT 4E - TI/Z100
INT 4F - Common Access Method SCSI
INT 50 - IRQ0 relocated by software
INT 51 - IRQ1 relocated by software
INT 52 - IRQ2 relocated by software
INT 53 - IRQ3 relocated by software
INT 54 - IRQ4 relocated by software
INT 55 - IRQ5 relocated by software
INT 56 - IRQ6 relocated by software
INT 57 - IRQ7 relocated by software
INT 58 - IRQ8/0 relocated by software
INT 59 - IRQ9/1 relocated by software; GSS Computer Graphics Interface
INT 5A - IRQ10/2 relocated by software
INT 5B - IRQ11/3 relocated by software; Network
INT 5C - IRQ12/4 relocated by software; Network Interface
INT 5D - IRQ13/5 relocated by software
INT 5E - IRQ14/6 relocated by software
INT 5F - IRQ15/7 relocated by software; HP 95LX GRAPHICS PRIMITIVES
INT 60 - reserved for user interrupt; multiple purposes
INT 61 - reserved for user interrupt; multiple purposes
INT 62 - reserved for user interrupt; multiple purposes
INT 63 - reserved for user interrupt; multiple purposes
INT 64 - reserved for user interrupt; multiple purposes
INT 65 - reserved for user interrupt; multiple purposes
INT 66 - reserved for user interrupt; multiple purposes
INT 67 - reserved for user interrupt; LIM EMS; multiple purposes
INT 68 - multiple purposes
INT 69 - multiple purposes
INT 6A - multiple purposes
INT 6B - multiple purposes
INT 6C - CONVERTIBLE; DOS 3.2; DECnet DOS network scheduler
INT 6D - VGA - internal
INT 6E - DECnet DOS - DECnet NETWORK PROCESS API
INT 6F - Novell NetWare; 10NET; MS Windows 3.0
INT 70 - IRQ8 - CMOS REAL-TIME CLOCK
INT 71 - IRQ9 - REDIRECTED TO INT 0A BY BIOS
INT 72 - IRQ10 - RESERVED
INT 73 - IRQ11 - RESERVED
INT 74 - IRQ12 - POINTING DEVICE (PS)
INT 75 - IRQ13 - MATH COPROCESSOR EXCEPTION (AT and up)
INT 76 - IRQ14 - HARD DISK CONTROLLER (AT and later)
INT 77 - IRQ15 - RESERVED (AT,PS); POWER CONSERVATION (Compaq)
INT 78 - DOS extenders; multiple purposes
INT 79 - multiple purposes
INT 7A - Novell NetWare; IBM 3270; multiple purposes
INT 7B - multiple purposes
INT 7C - multiple purposes
INT 7D - multiple purposes
INT 7E - RESERVED FOR DIP, Ltd. ROM LIBRARY; multiple purposes
INT 7F - multiple purposes
INT 80 - reserved for BASIC; multiple purposes
INT 81 - reserved for BASIC
INT 82 - reserved for BASIC
INT 83 - reserved for BASIC
INT 84 - reserved for BASIC
INT 85 - reserved for BASIC
INT 86 - IBM ROM BASIC - used while in interpreter; multiple purposes
INT 87 - IBM ROM BASIC - used while in interpreter
INT 88 - IBM ROM BASIC - used while in interpreter; multiple purposes
INT 89 - IBM ROM BASIC - used while in interpreter
INT 8A - IBM ROM BASIC - used while in interpreter
INT 8B - IBM ROM BASIC - used while in interpreter
INT 8C - IBM ROM BASIC - used while in interpreter
INT 8D - IBM ROM BASIC - used while in interpreter
INT 8E - IBM ROM BASIC - used while in interpreter
INT 8F - IBM ROM BASIC - used while in interpreter
INT 90 - IBM ROM BASIC - used while in interpreter
INT 91 - IBM ROM BASIC - used while in interpreter
INT 92 - IBM ROM BASIC - used while in interpreter; multiple purposes
INT 93 - IBM ROM BASIC - used while in interpreter
INT 94 - IBM ROM BASIC - used while in interpreter; multiple purposes
INT 95 - IBM ROM BASIC - used while in interpreter
INT 96 - IBM ROM BASIC - used while in interpreter
INT 97 - IBM ROM BASIC - used while in interpreter
INT 98 - IBM ROM BASIC - used while in interpreter
INT 99 - IBM ROM BASIC - used while in interpreter
INT 9A - IBM ROM BASIC - used while in interpreter
INT 9B - IBM ROM BASIC - used while in interpreter
INT 9C - IBM ROM BASIC - used while in interpreter
INT 9D - IBM ROM BASIC - used while in interpreter
INT 9E - IBM ROM BASIC - used while in interpreter
INT 9F - IBM ROM BASIC - used while in interpreter
INT A0 - IBM ROM BASIC - used while in interpreter
INT A1 - IBM ROM BASIC - used while in interpreter
INT A2 - IBM ROM BASIC - used while in interpreter
INT A3 - IBM ROM BASIC - used while in interpreter
INT A4 - IBM ROM BASIC - used while in interpreter
INT A5 - IBM ROM BASIC - used while in interpreter
INT A6 - IBM ROM BASIC - used while in interpreter
INT A7 - IBM ROM BASIC - used while in interpreter
INT A8 - IBM ROM BASIC - used while in interpreter
INT A9 - IBM ROM BASIC - used while in interpreter
INT AA - IBM ROM BASIC - used while in interpreter
INT AB - IBM ROM BASIC - used while in interpreter
INT AC - IBM ROM BASIC - used while in interpreter
INT AD - IBM ROM BASIC - used while in interpreter
INT AE - IBM ROM BASIC - used while in interpreter
INT AF - IBM ROM BASIC - used while in interpreter
INT B0 - IBM ROM BASIC - used while in interpreter
INT B1 - IBM ROM BASIC - used while in interpreter
INT B2 - IBM ROM BASIC - used while in interpreter
INT B3 - IBM ROM BASIC - used while in interpreter
INT B4 - IBM ROM BASIC - used while in interpreter
INT B5 - IBM ROM BASIC - used while in interpreter
INT B6 - IBM ROM BASIC - used while in interpreter
INT B7 - IBM ROM BASIC - used while in interpreter
INT B8 - IBM ROM BASIC - used while in interpreter
INT B9 - IBM ROM BASIC - used while in interpreter
INT BA - IBM ROM BASIC - used while in interpreter
INT BB - IBM ROM BASIC - used while in interpreter
INT BC - IBM ROM BASIC - used while in interpreter
INT BD - IBM ROM BASIC - used while in interpreter
INT BE - IBM ROM BASIC - used while in interpreter
INT BF - IBM ROM BASIC - used while in interpreter
INT C0 - IBM ROM BASIC - used while in interpreter
INT C1 - IBM ROM BASIC - used while in interpreter
INT C2 - IBM ROM BASIC - used while in interpreter
INT C3 - IBM ROM BASIC - used while in interpreter
INT C4 - IBM ROM BASIC - used while in interpreter
INT C5 - IBM ROM BASIC - used while in interpreter
INT C6 - IBM ROM BASIC - used while in interpreter
INT C7 - IBM ROM BASIC - used while in interpreter
INT C8 - IBM ROM BASIC - used while in interpreter
INT C9 - IBM ROM BASIC - used while in interpreter
INT CA - IBM ROM BASIC - used while in interpreter
INT CB - IBM ROM BASIC - used while in interpreter
INT CC - IBM ROM BASIC - used while in interpreter
INT CD - IBM ROM BASIC - used while in interpreter
INT CE - IBM ROM BASIC - used while in interpreter
INT CF - IBM ROM BASIC - used while in interpreter
INT D0 - IBM ROM BASIC - used while in interpreter
INT D1 - IBM ROM BASIC - used while in interpreter
INT D2 - IBM ROM BASIC - used while in interpreter
INT D3 - IBM ROM BASIC - used while in interpreter
INT D4 - IBM ROM BASIC - used while in interpreter
INT D5 - IBM ROM BASIC - used while in interpreter
INT D6 - IBM ROM BASIC - used while in interpreter
INT D7 - IBM ROM BASIC - used while in interpreter
INT D8 - IBM ROM BASIC - used while in interpreter
INT D9 - IBM ROM BASIC - used while in interpreter
INT DA - IBM ROM BASIC - used while in interpreter
INT DB - IBM ROM BASIC - used while in interpreter
INT DC - IBM ROM BASIC - used while in interpreter
INT DD - IBM ROM BASIC - used while in interpreter
INT DE - IBM ROM BASIC - used while in interpreter
INT DF - IBM ROM BASIC - used while in interpreter
INT E0 - IBM ROM BASIC - used while in interpreter; multiple purposes
INT E1 - IBM ROM BASIC - used while in interpreter
INT E2 - IBM ROM BASIC - used while in interpreter
INT E3 - IBM ROM BASIC - used while in interpreter
INT E4 - IBM ROM BASIC - used while in interpreter
INT E5 - IBM ROM BASIC - used while in interpreter
INT E6 - IBM ROM BASIC - used while in interpreter
INT E7 - IBM ROM BASIC - used while in interpreter
INT E8 - IBM ROM BASIC - used while in interpreter
INT E9 - IBM ROM BASIC - used while in interpreter
INT EA - IBM ROM BASIC - used while in interpreter
INT EB - IBM ROM BASIC - used while in interpreter
INT EC - IBM ROM BASIC - used while in interpreter
INT ED - IBM ROM BASIC - used while in interpreter
INT EE - IBM ROM BASIC - used while in interpreter
INT EF - BASIC - ORIGINAL INT 09 VECTOR
INT F0 - BASICA.COM, GWBASIC, compiled BASIC - ORIGINAL INT 08 VECTOR
INT F1 - reserved for user interrupt
INT F2 - reserved for user interrupt
INT F3 - reserved for user interrupt
INT F4 - reserved for user interrupt
INT F5 - reserved for user interrupt
INT F6 - reserved for user interrupt
INT F7 - reserved for user interrupt
INT F8 - reserved for user interrupt
INT F9 - reserved for user interrupt
INT FA - reserved for user interrupt
INT FB - reserved for user interrupt
INT FC - reserved for user interrupt
INT FD - reserved for user interrupt
INT FE - AT/XT286/PS50+ - destroyed by return from protected mode
INT FF - AT/XT286/PS50+ - destroyed by return from protected mode
Questo è solo il principio... in realtà ogni interrupt ha almeno altri due o tre significati.
Procuratevi anche voi la guida degli interrupt di Ralf Brown (dovreste riuscire a trovarla facilmente su internet). L'unica pecca è che è in inglese ma, se voi che state leggendo siete traduttori, non dovreste avere problemi a capirla (come non ne ho io)... vero?

 

 


--[CAPITOLO VI - Le memorie]--

Nel x86 potete usare vari modelli di memoria. Eccoveli elencati uno per uno.
Modello TINY - È il modello più semplice che viene usato per i file più piccoli di 64 Kb. Solitamente ha estensione .com e non dà la possibilità di avere un segmento per lo stack (che si riduce a pochi byte). Tutti i registri di segmento puntano allo stesso valore, in quanto dati, stack e codice si ritrovano nella stessa locazione di memoria.
Modello SMALL - È un po' più complesso del primo. Adesso potrete disporre di ben due segmenti. Un segmento di massimo 64 Kb che contiene dati e codice e un altro di massimo 64 Kb per lo stack.
Modello COMPACT - Un po' più avanzato, vi permette di avere un segmento per il codice, un altro per i dati ed un altro per lo stack. Solitamente viene usato per quei programmi che hanno più codice e meno dati.
Modello LARGE - Questo invece è stato pensato per quei programmi che hanno più dati e meno codice (tipo gli applicativi che restituiscono un output dopo aver elaborato molto dati).
Modello HUGE - Tutto lo spazio per codice e per i dati che volete. Lo stack può essere presente in più segmenti. Per il resto è uguale al modello Compact e Large.
Modello FLAT - è una rivisitazione del modello Tiny. Si hanno a disposizione ben 4 GB, contro i 64 Kb del Tiny. Viene usato per tutte le applicazioni che sfruttano un sistema operativo in modalità protetta o in ambiente protetto (i vari Windows, Linux, Win3.1, BeOS, Dos4GW etc..).. 
Penso che queste informazioni possano servire più al coder che al reverser, però io ce le ho messe lo stesso... Mica male sapere delle cose in più, no?

 

 


--[CAPITOLO VII - Le istruzioni]--
Questo capitolo è veramente importante, e spiega le varie istruzioni asm. Pasto direttamente parte della documentazione di cui dispongo.
Uhm, comunque penso sia meglio per voi consultarlo solo in caso di necessità (se disassemblate e trovate un'istruzione che non conoscete), altrimenti le leggerete tutte e alla fine ne ricorderete la metà (inoltre ci sono istruzioni che molto probabilmente non incontrerete ami in vita vostra), senza sapere effettivamente a cosa servono.
Il mio consiglio quindi è di saltare questo capitolo e consultarlo solo durante il disassemblaggio (si dice così?) di un programma.
Datevi un'occhiata solo alle funzioni logiche (AND, NOT, OR e XOR) se non avete letto il capitolo "Assembly 4 Newbies". In questo modo mi sono risparmiato un capitolo :P
Vi consiglio anche di scaricare la guida a gli opcode "Intel Hex Opcodes And Mnemonics". Questo perchè all'interno di quel file *.hlp sono listati i byte delle varie istruzioni, con le loro forme. È vero che il problema si potrebbe ovviare completamente con il PHOG, però è anche vero che quella è una guida (pur non contenendo tutte le istruzioni che sono listate qui) velocissima da consultare ed utilissima. Credo proprio che scaricandola vi farete un favore.

Nota: Quando parlo di cifre BCD intendo Binary Convert Decimal ovvero che il valore ASCII espresso dal numero binario equivale a quello decimale.
Ipotesi: Prendiamo il carattere '0' il cui valore decimale è 48. La cifra BCD 0 equivale allo 0 decimale.
Il primo operando a sinistra dopo il codice operativo rappresenta sempre la destinazione quando questa non è definita dalla stessa istruzione.

AAA - Correzione ASCII dopo l'addizione
Questa operazione va eseguita dopo l'istruzione ADD nel caso in cui abbiamo come risultato un solo byte in AL. Ipotizzando di avere il carattere 9 espresso sottoforma di cifra BCD (ovvero il carattere 9) eseguendo AAA avremo direttamente il valore binario. Cioè 9 decimale.
Flag modificati AF CF

AAD - Correzione ASCII prima della divisione
Va eseguita prima di effettuata una moltiplicazione nel caso abbiamo dei numeri espressi sottoforma di cifre BCD convertendolo in cifre decimali.

AAM - Correzione ASCII dopo la moltiplicazione
Da eseguire dopo una moltiplicazione tra due cifre BCD. Converte il risultato in valore decimale.

AAS - Correzione ASCII dopo la sottrazione
Da eseguire dopo una sottrazione tra cifre BCD. Converte il risultato in valore decimale.

ADC - Somma con riporto
Conoscendo i registri del processore che hanno un limite di 32bit nel caso di processori 386 o maggiori o di 16bit con processori 80286 in già nel caso sommiamo due numeri che dovrebbero generare un errore di overflow ovvero numero troppo grande e quindi ci sarebbe un riporto l'operazione ADC destinazione,origine non fa altro che sommare a destinazione il valore origine ed il riporto. Ipotizziamo una situazione del genere.
EAX = 80000000h
EBX = 80000000h
EAX + EBX = 100000000h
Questo valore ha una lunghezza di 33 bit ed in un registro a 32bit non può entrare ma solo i 32bit inferiori! Il che significa che EAX che conterrà la somma avrà come valore 0 ed il riporto sarà inserito nel registro eflags al bit CF. Quindi eseguendo 
ADC EDX, 0
Noi sommiamo ad EDX (una qword o un intero a 64 bit va inserito in EDX:EAX) il valore 0 per non cambiare EDX + il riporto nel caso sia presente o non presente. Quindi in questo caso EDX incrementerà di 1 unità. Comunque è un ben usare ADC dopo le addizioni quando non si è sicuri del risultato. Nel caso in cui il riporto sia assente non sarà incrementato il registro EDX.

ADD - Somma
Questa esegue la più semplici delle operazioni, somma due valori, destinazione ed origine.

AND - AND logico
Esegue l'AND tra due operandi. Ovvero se entrambi i bit dei due operandi sono settati ad 1 in destinazione viene lasciato 1 mentre se sono diversi viene inserito 0.
Esempio:
11101100 AND
00100100 =
-------------
00100100

ARPL - Correzione del campo RPL del selettore
Processore 286+
Usato per assicurarsi che una subroutine di un software non richieda un livello di esecuzione maggiore. Tipo siamo a RING3 e chiamiamo una CALL in un altro selettore. Con ARPL il s.o. verifica che tale routine non richieda un RING2 1 o 0.

BOUND - Confronto che l'indice di array non superi i limiti
Processore 386+
Garantisce che un indice di array con segno rientri nei limiti specificati da un blocco di memoria consistente da un limite superiore e da un inferiore.

BSF - Scansione di bit in avanti
Processore 386+
Scandisce il bit nel secondo operando di word o doppia word a partire dal bit 0

BSR - Scansione di bit in indietro
Processore 386+
Scandisce il bit nel secondo operando di word o doppia word a partire dall'ultimo bit dell'operando.

BSWAP - Scambio BIT
Processore 586+
Scambia la word più alta di un registro con la word più piccola del medesimo registro.
Ipotizziamo di avere in EAX i seguenti valori ASCII:
31    23   15   7   0
    C     A    S   A
Dopo tale istruzione avremo 
31    23   15   7   0
    S     A    C   A

BT - Test di bit
Processore 386+
Testa il bit n di un registro o di una locazione di memoria. Il risultato viene settato in CF.
Ipotesi: dobbiamo conoscere il 5 bit di EAX
BT EAX, 5
JNC nonsettato
settato:
bla bla bla
nonsettato:
continua come se niente fosse accaduto

BTC - Test di bit e complemento
Processore 386+
Identica a BT ma il valore del bit richiesto viene invertito

BTR - Test di bit e reset
Processore 386+
Identica a BT ma il valore del bit viene settato a 0

BTS - Test di bit e attivazione
Processore 386+
Identica a BT ma il valore del bit viene settato a 1

CALL - Chiamata di procedura
Esegue una sottoprocedura che può trovarsi nello stesso segmento (NEAR CALL) o in un segmento diverso (FAR CALL).
L'indirizzo della NEAR CALL può anche essere specificato in un registro. Nello stack viene salvato il valore di CS:(E)IP prima che la CALL venga
eseguita. Il ritorno da una CALL avviene tramite l'istruzione RET assicurandosi che l'indirizzo SS:(E)SP punti al valore effettivo di ritorno.

CBW / CWDE - Conversione byte in word e word in dword
Processore 386+ x CWDE
Istruzione con parametro implicito. Viene usato il registro (E)AX. Il valore di AL nel caso di CBW non viene modificato mentre AH viene azzerato, stessa cosa con CWDE ma in tal caso AX non viene toccato mentre dal 16 al 31 bit di EAX vengono azzerati

CDQ - Conversione di dword in qword
Processore 586+
Parametro implicito, ma stavolta EAX non viene modificato ed EDX viene azzerato

CLC - Azzeramento del flag di riporto
Il bit CF del registro EFLAGS viene settato a 0

CLD - Azzeramento del flag di direzione
Il bit DF del registro EFLAGS viene settato a 0

CLI - Azzeramento del flag di interrupt
Il bit di Interrupt del registro EFLAGS viene settato a 0

CLTS - Azzeramento del flag di task commutato in CR0
Processore 386+
Azzera il flag di task commutato (TS) nel registro CR0. Viene attivato dal 386 quando si presenta una commutazione di task. Per sistema operativo.

CMC - Complementazione del flag di riporto
Viene invertito il valore CF del registro EFLAGS

CMP - Confronto di due operandi
Confronta due operandi e cambia i bit OF, SF, ZF, AF, PF e CF del registro EFLAGS. Tutti questi bit sono modificati per via dei salti condizionali.

CMPS/CMPSB/CMPSW/CMPSD - Confronto di operandi di stringa
Confronta due stringhe puntate da DS:ESI e ES:EDI Il valore di ESI ed EDI viene aumentato o decrementato in base al bit DF del registro EFLAGS.
Se DF è 1 allora aumentano altrimenti decrementano.
Possono essere usati con le varienti di REP per n volte in base al valore di (E)CX.

CMPXCHG - Confronto e scambio
Processore 486+
Confronto tra due operandi e scambio degli operandi. Destinazione assume il valore di origine e viceversa.

CMPXCHG8 - Confronto e scambio con 8 bytes
Processore 586+
Identica a CMPXCHG ma i byte stavolta diventano 8.

CPUID - Identificazione CPU
Processore 586+
L'unica istruzione che da risultati diversi sui processori. Il risultato è protetto da Copyright in quanto praticamente inserisce nei registri la classe e il nome del fabbricatore. AuthenticAMD per AMD GenuineINTEL per Intel, per Ciryx e gli altri non lo so! Provate per credere!!!

DAA - Correzione decimale dopo l'addizione
Va eseguitadopo un'istruzione ADD che lascia un risultato di un byte di due cifre BCD in AL. L'istruzione corregge AL per contenere il risultato decimale corretto di due cifre impaccate (ovvero in un solo byte!)

DAS - Correzione decimale dopo la sottrazione
Identica a DAA ma va eseguita dopo la sottrazione

DEC - Decremento di una unit…
Decrementa di una unità un operando. Ovvero se EAX contiene 128 DEC EAX farà si che EAX conterrà 127.

DIV - Divisione tra interi senza segno
Effettua una divisione tra interi senza segno. 
                          Dividendo   Quoziente     Resto
Divisione byte              AX         AL             AH   
Divisione word        DX:AX         AX              DX
Divisione dword    EDX:EAX      EAX            EDX

ENTER - Creazione del frame di stack - per i parametri di procedura
Processore 386+
Alloca una certà quantità di stack per assicurare l'esatto passaggio dei parametri ad una routine. Richiede 2 operandi: 
1ø operando - Quantità di memoria da allocare max 65536
2ø operando - Livello di annidamento lessicale della procedura nel codice sorgente del linguaggio ad alto livello

HLT - Alt
Pone il processore in uno stato di HALT. Si risveglia quando sarà provocata un'eccezione NMI o un RESET. Ovvero quando un evento hardware lo risveglia!!

IDIV - Divisione con segno
Si veda l'istruzione DIV ma questa volte il bit più alto degli operandi specifica il segno stesso (0 + 1 -)

IMUL - Moltiplicazione con segno
Si veda l'istruzione MUL ma il bit più alto degli operando specifica il segno stesso (0 + 1 -)

IN - Input da porta
Dialoga con una porta logica per prelevare l'input. Il valore che può essere prelevato è un byte, word o una dword e viene usato come destinazione
il registro (E)AX. Le porte del PC sono 65536 e fanno di tutto! Dall'hd al fd, al video a tutto ciò che ci sta di ferro vecchio nel PC!!!
Le prime 256 porte possono essere specificate immediatamente altrimenti si deve usare per forza il registro DX!

INC - Incremento di una unit…
Somma 1 all'operando specificato

INS/INSB/INSW/INSD - Input da porta a stringa
Preleva un byte, word o dword da una porta specificata in DX. Il valore sarà immagazzinato in ES:EDI che a secondo del bit DR sarà incrementato
o decrementato. Può essere usato con l'istruzione REPxx inserendo in (E)CX il numero di volte da eseguire l'istruzione, utile per leggere i byte
provenienti da un componente hardware quali il controller e-ide o scsi.

INT/INTO - Chiamata ad un interrupt
Allora vi ho già spiegato gli interrupt, ora qui faremo una ulteriore distinzione. INT numerointerrupttra0e256 chiama l'interrupt desiderato.
Però esistono due interrupt speciali che hanno un'istruzione a parte. INT 3 che è codificabile con CC (mentre tutti gli altri INT si codificano
con CD xx dove xx esprime il numero dell'int.) e viene eseguito da un debugger per bloccare il programma in un dato punto. INTO codificabile
con CC o CE viene eseguito qualora il bit OF sia settato... ovvero nel caso in cui si sia verificato un overflow. Attenzione INTO non viene
chiamato esplicitamente dal processore ma bens deve essere l'istruzione inserita ed un gestore collegato.
Prima dell'esecuzione viene salvato nello stack CS, (E)IP e i flags

INVD - Cache non valida
Processore 486+
Serve per annullare il contenuto della cache e renderlo non utilizzabile. Usato da o.s. e non da software vostro! Rischiate di fare un macello!!

INVLPG - Inizio TLB non valido
Processore 586 + ?
Annulla l'inizio di TLB ... non mi chiedete che sia!!! Eseguibile a ring0

IRET/IRETD
Esce da una interruzione, anche se si può terminare l'interruzione con un RETF ma in tal caso bisogna prima inserire un POPF o POPFD.

IRET o IRETD ripristina i flags, CS e (E)IP

Jcc - Salti condizionali e non
JA       Salto breve se sopra                              CF=0 ZF=0
JAE      Salto breve se sopra o uguale                     CF=0
JB       Salto breve se sotto                              CF=1
JBE      Salto breve se sotto o uguale                     CF=1 ZF=1
JC       Salto breve se riporto                            CF=1
JCXZ     Salto breve se reg. CX Š 0
JECXZ    Salto breve se reg. ECX Š 0
JE       Salto breve se uguale                             ZF=1
JG       Salto breve se maggiore                           ZF=0 SF=OF
JGE      Salto breve se maggiore o uguale                  SF=OF
JL       Salto breve se minore                             SF<>OF
JLE      Salto breve se minore o uguale                    ZF=1 SF=0
JNA      Salto breve se non sopra                          CF=1 o ZF=1
JNAE     Salto breve se non sopra o uguale                 CF=1
JNB      Salto breve se non sotto                          CF=0
JNBE     Salto breve se non sotto o uguale                 CF=0 ZF=0
JNC      Salto breve se non riporto                        CF=0
JNE      Salto breve se non uguale                         ZF=0
JNG      Salto breve se non maggiore                       ZF1 o SF<<OF
JNGE     Salto breve se non maggiore o uguale              SF<>OF
JNL      Salto breve se non minore                         SF=OF
JNLE     Salto breve se non minore o uguale                ZF=0 SF=OF
JNO      Salto breve se non overflow                       OF=0
JNP      Salto breve se non parit…                         PF=0
JNS      Salto breve se non segno                          SF=0
JNZ      Salto breve se non zero                           ZF=0
JO       Salto breve se overlow                            OF=1
JP       Salto breve se parit…                             PF=1
JPE      Salto breve se parit… pari                        PF=1
JPO      Salto breve se parit… dispari                     PF=0
JS       Salto breve se segno                              SF=1
JZ       Salto breve se sopra                              ZF=1
Accanto a questa bella lista di salti brevi esistono le stesse ed identiche funzioni per i salti di tipo near, ma a quanto pare nessuno dei compilatori
assembler oggi le implementa.. almeno quello che uso io! Provate con il PASS32 ... forse li accetta!!!!!! In ogni caso i salti condizionali di tipo near hanno una ulteriore codifica tutta diversa, e vengono introdotti dal codice operativo override 0F

JMP - Salto non condizionale
Effettua un salto alla locazione specificata. A differenza delle precedenti può saltare ad una locazione short (+ o - 128 byte di distanza) a near ( + o - 32kb di distanza) o ad una far ( modalit… reale + o - 1mb e in protetta + o - 2gb!)

LAHF - Caricamento dei flag nel registro AH
Carica in AH il byte basso della word dei flag

LAR - Caricamento del byte dei diritti d'accesso
Processore 386+
Memorizza una forma contrassegnata della seconda dword del descrittore per il selettore di provenienza se il selettore è visibile al CPL ed il tipo di descrittore è valido.

LDS/LES/LFS/LGS/LSS - Carica un puntatore in un registro ed in un selettore o segmento
386+ LFS e LGS
Ipotizziamo di dover caricare l'indirizzo di una stringa in un registro e anche il suo segmento o selettore.... cosa fare ? Semplice
Lxx nomeregistro, indirizzo:offset
dove xx si sostituisce con uno dei registri DS ES FS GS SS e nomeregistro uno dei registri validi, e indirizzo:offset l'indirizzo dei valori da caricare!

LEA - Carica l'indirizzo effettivo in un registro
Carica l'indirizzo effettivo in un registro... è una forma abbastanza veloce e sostituisce l'istruzione più blasonata:
MOV nomeregistro, OFFSET quellochevipare

LEA nomeregistro, quellochevipare
Se in MOV non specificate la scritta OFFSET ed avete la fortuna o sfortuna di possedere un assemblatore inteligente o deficente vi potrebbe caricare al posto dell'indirizzo il contenuto.

LEAVE - Uscita da procedura ad alto livello
Processore 386+
Ricordate l'istruzione ENTER ? Allocava della memoria... ebbene questa non fa altro che l'opposto dell'ultima ENTER eseguita. Infatti ne troverete una ad ogni propabile uscita di una funzione se è presente ENTER!

LGDT/LIDT - Caricamento del registro della tabella di descrittori globale o d'interruzione
Processore 386+
Carica nel registro GDT o IDT il puntatore alla tabella GDT o IDT. Questa istruzione viene usata dai programmatori di o.s. prima di entrare in modalità protetta per preparare la tabella dei descrittori. Non è un'istruzione eseguibile a RING3

LLDT - Caricamento del registro della tabella di descrittori locale
Processore 386+
Carica il registro della tabella di descrittori locali. Utilizzata nel o.s. e non nei programmi... niente ring3 o modo virtuale 86

LMSW - Caricamento della word di stato macchina
Processore 286+
Questa word contiene alcune particolari informazioni che sono contenute nella parte bassa del registro CR0. Questa le carica, può anche essere usata per accedere in modo protetto usando SMSW, settando a 1 il bit 0 del registro e poi usando LMSW.

LOCK - Dichiarazione del prefisso del segnale LOCK#
Questa istruzione è utilizzata in ambienti multiprocessore dove deve essere garantita la condivisione della memoria. Il processore in cui viene attivato il segnale LOCK# eseguendo talune istruzioni si congela. Ovvero non prosegue finchè il segnale LOCK# non viene disattivato. Tale segnale LOCK# equivale ad un piedino del processore.
Le istruzioni che bloccano il processore sono:
BTS BTR BTC
XCHG
ADD OR ADC SBB AND SUB XOR
NOT NEG INC DEC
CMPXCHG XADD

LODS/LODSB/LODSW/LODSD - Caricamento di operando di stringa
Processore 386+ per LODSD
Queste operazioni ( a secondo del loro nome come avrete ben capito manipolano un byte, una word, una dword) caricano nel registro AL, AX, EAX i dati puntati dalla coppia DS:(E)SI. E' utile per fare una scansione dati alla ricerca di un byte, di una word o di una dword, ed è sempre collegata al flag DF (direzione)

LOOP/LOOPcond - Controllo del ciclo con contatore (E)CX
Salta all'indirizzo specificato dopo l'istruzione LOOP (max 128 byte di distanza dal LOOP) finchè (E)CX non è uguale a 0 o non si verifica la
condizione

LSL - Carica il limite del segmento
Processore 386+
Carica un registro con un segmento limite decodificato, e pone ZF ad 1, purchè il selettore di provenienza sia visibile al CPL 'indebolito' da RPL,
e che il descrittore sia di un tipo accettato da LSL. Altrimenti il registro non viene modificato e ZF viene posto a 0

LTR - Carica registro di task
Processore 386+
LTR carica il registro di task dal registro o dalla locazione di memoria di provenienza specificata dall'operando. Non avviene nessuna commutazione di task

MOV - Trasferimento di dati
L'istruzione più usata in assoluto. Copia i dati dalla sorgente alla destinazione. Sia ben chiaro che questa architettura prevede che la destinazione sia l'operando più vicino all'istruzione, ovvero in tal caso avremo MOV dest, src
Possiamo avere le seguenti combinazioni:
MOV reg8, reg8
MOV reg8, mem8
MOV reg8, imm8
MOV reg16, reg16
MOV reg16, mem16
MOV reg16, imm16
MOV reg32, reg32
MOV reg32, mem32
MOV reg32, imm32
MOV mem8, reg8
MOV mem16, reg16
MOV mem32, reg32
Non possiamo avere istruzioni del genere:
MOV mem8, mem8
MOV mem16, mem16
MOV mem32, mem32
E ricordate che la dimensione dell'operando src deve essere sempre uguale a quella di dest altrimenti il compilatore segnalerà un errore!

MOVS/MOVSB/MOVSW/MOVSD - Trasferimento di dati da stringa a stringa
Processore 386+ per MOVSD
Queste istruzioni di solito vengono utilizzate con l'istruzione REP o REPcond che vedremo più avanti, e ci permettono di copiare i dati puntati dalla coppia DS:(E)SI a ES:(E)DI.
La dimensione dei dati viene specificata dal prefisso inserito dopo MOVS!

MOVSX - Trasferimento con estensione di segno
Processore 386+
Copia un byte o una word estendendo il bit più alto nell'altro registro per mantenere il segno.

MOVZX - Trasferimento con estensione di zero
Processore 386+
Copia un byte o una word azzerando i bit dell'intero registro non utilizzati (tutti i bit non utilizzati vengono contrassegnati da 0) e azzerando anche
l'altro registro.

MUL - Moltiplicazione senza segno
Esegue una moltiplicazione senza segno.
Operando    Risultato     Dimensione altro operando
AL                  AX            8bit
AX               DX:AX         16bit
EAX          EDX:EAX       32bit

NEG - Negazione in complemento a due
Restituisce il valore di un registro o di un operando in memoria col suo complemento a due, ovvero sottrae da 0 l'operando e ne memorizza il risultato.

NOP - Nessuna operazione
Non esegue nessuna operazione, l'unico registro che viene modificato è il registro (E)IP. Il resto dei registri rimane invariato. Questa operazione se sembrerebbe inutile è da prendere in considerazione qualora si deve dialogare con componenti hardware che richiedano un piccolo lasso di tempo per elaborare le informazioni. E' inoltre vivamente consigliato prenderla in considerazione quando si deve editare codice assembler che abbiamo disassemblato.

NOT - Negazione in complemento a uno
Inverte l'operando, inserendo al posto di 1 lo 0 e viceversa per tutti i bit dell'operando stesso.

OR - OR inclusivo logico
OR calcola l'OR inclusivo dei due operandi e pone il risultato nel primo operando. Ogni bit del risultato è 0 se entrambi i bit corrispondenti sono 0, altrimenti, ogni bit è 1.
Esempio
10 01 00 01 OR
01 00 10 10 =
11 01 10 11

OUT - Output a porta
Trasferisce un byte o una word di dati dal registro aritmetico ad una porta specificata immediatamente o nel registro DX.

OUTS/OUTSB/OUTSW/OUTSD - Output di stringa a porta
Trasferisce un dato dal byte, word o dword di memoria presente in DS:(E)SI alla porta specificata in DX. Esegue il contrario di INS/INSB/INSW/INSD.
Utile per trasferire enormi quantità di dati ad una determinata porta (tipo controller ide, scsi ecc...)


Istruzioni per la manipolazione dello stack
POP  - POPA/POPAD   - POPF/POPFD
PUSH - PUSHA/PUSHAD - PUSHF/PUSHFD

Queste istruzioni le raggruppo in una sezione unica in quanto hanno come obiettivo principale la manipolazione dei dati dallo stack.
Cosa è uno stack ? Esso è un'area di memoria in cui vengono memorizzati i valori dei registri ed altri dati temporanei affinchè possano essere
riutilizzati i registri stessi effettuando una eventuale copia in caso di ripristino. Lo stack dei processori INTEL come già spiegato è di tipo LIFO, ovvero l'ultimo dato ad entrare è il primo ad uscire!
Le istruzioni nella 1. riga, contrassegnata dall'istruzione POP sono tutte usate per prelevare dei dati dallo stack mentre quelle sulla 2. riga invece servono per inserire i dati nello stack.
Incominciamo a spiegare le varie istruzioni

PUSH - Inserimento di dati nello stack
Immagazzina il valore del registro o dell'operando immediato o in memoria nello stack. Il registro (E)SP viene decrementato a seconda della lunghezza dell'operando, ovvero 2 o 4 byte in quanto non si possono memorizzare singoli byte ma solo word o dword.
(E)SP = 1000h
PUSH valoredi4byte
(E)SP = 1000h - 0004h = 0FFCh

POP - Preleva i dati dallo stack
Ripristina il valore nell'operando specificato prelevando il valore dallo stack. Il registro (E)SP viene incrementato a seconda della lunghezza dell'operando, 2 o 4 byte.
(E)SP = 0FFCh
POP nomeoperandodi4byte
(E)SP = 0FFCh + 0004h = 1000h

PUSHA / PUSHAD - Memorizzazione dei registri nello stack
Abbiamo queste due operazioni nella stessa riga in quanto hanno entrambe lo stesso significato, ma la dimensione degli operandi coinvolti è diversa!
Questa differenza viene fatta in quanto un compilatore Assembler non è in grado di capire se l'istruzione che si desidera eseguire sia quella su word o su dword, pertanto è stato inserito il codice mnemonico PUSHAD proprio per fare tale differenza.
I registri che vengono memorizzati nello stack sono i seguenti
(E)AX
(E)CX
(E)DX
(E)BX
(E)SP
(E)BP
(E)SI
(E)DI
Nel caso usiate il codice PUSHA salverete solo il contenuto della word più bassa del registro (ovvero il registro che è fuori dalla parentesi ;)) mentre se userete PUSHAD salverete tutto il contenuto del registro. L'ordine inserito è quello rispettato dal processore

PUSHF/PUSHFD
Inserisce nello stack il contenuto del registro (E)FLAGS, anche qui notate due istruzioni quasi simili, e quindi tale differenza è stata fatta per i compilatori. PUSHF salva solo i primi 16bit dei FLAGS mentre PUSHFD i 32bit dei FLAGS

POPA/POPAD
Ripristina i dati dallo stack seguendo quest'ordine
(E)DI
(E)SI
(E)BP
(E)SP
(E)BX
(E)DX
(E)CX
(E)AX
La differenza tra POPA e POPAD è data dalla dimensione dei registri... per ulteriori informazioni date un'occhiata a PUSHA/PUSHAD

POPF/POPFD
Ripristina dallo stack il contenuto del registro (E)FLAGS. Ulteriori informazioni li trovate in PUSHF/PUSHFD

Nota finale:
Qualora eseguiate la seguente istruzione:
PUSH EAX e poi POP EBX tanto per fare un esempio il valore immagazzinato con PUSH EAX non sarà mai cancellato dallo stack finchè non verrò sovrascritto. Infatti nel caso facciate MOV EAX, DWORD PTR SS:[ESP]-4 voi avrete sempre in EAX lo stesso valore salvato con PUSH. Questo è dato dal fatto che la cpu si limita soltanto ad incrementare e decrementare il registro (E)SP e non eventualmente a rimuovere i dati presenti in essa. Inoltre lo stack viene oggi gestito dai compilatori per il passaggio tra le varie funzioni.
Avendo una funzione del genere in linguaggio C
int somma(int p(0), int p(1), .... int p(n))
disassemblando il codice potrete notare che viene salvato prima p(n) e man man si scende fino a p(0) prima di effettuare la call all'istruzione.
Infatti i dati nello stack nei moderni compilatori e sistemi operativi vengono passati dall'ultimo parametro al primo. Ricordatevelo sempre!

RCL/RCR/ROL/ROR - Rotazione
Queste operazioni fanno ruotare i bit a sinistra (RCL-ROL) e a destra (RCR-ROR).
Cosa significa ruotare i bit ? Farli scivolare di n posizioni nel senso indicato.
Esempio:
1000 1000 ROL 0001 0001 
1000 1000 ROR 0100 0100
La differenza tra questi due gruppi di istruzioni sta nel fatto che quelle in cui è specificata nell'operando la lettera C (RCL ed RCR) il bit 0 alla fine della rotazione va a costituire il bit del CARRY FLAG mentre con ROL e ROR il bit CF rimane invariato.

RDMSR - Legge dal registro dei modelli specifici
Processore 586+
Questa istruzione carica uno dei registri a 64bit della famiglia Pentium nella coppia dei registri EDX:EAX. Il registro da caricare viene specificato
nel registro ECX

RDTSC - Legge dal Time Stamp Counter
Processore 586+
Il processore Pentium è dotato di un registro a 64bit che conteggia il numero di cicli di clock eseguiti dal RESET in poi. Tale registro viene caricato nella coppia EDX:EAX ed è utile per stabilire quante cicli assegnare ad ogni processo ;) Solo al livello CPL 0 è possibile modificare il contenuto di tale registro.

REP/REPE/REPZ/REPNE/REPNZ - Ripetizione della successiva operazione di stringa
Questa categoria di istruzioni va sempre accompagnata da una delle operazioni che servono per manipolare le stringhe, ovvero SCAS?, LODS?, INS?, OUTS?, MOVS? e tale istruzione viene ripetuta per n volte quanto specificato nel registro contatore (E)CX o finchè non si verifica una condizione.

RET - Ritorna da procedura
Questa istruzione serve per ritornare da una procedura di tipo near in modalità reale o in modalità protetta. Può anche essere utilizzata per effettuare il ritorno da una procedura di tipo FAR utilizzando però l'opcode RETF. Però bisogna fare attenzione in quanto la word o la dword puntata dalla coppia SS:[ESP] sarà il valore che assumerà il registro puntatore (E)IP e pertanto bisogna assicurarsi che il valore puntato nello stack sia il valore esatto. Può essere anche accompagnata da un numero che specifica la quantità di byte da sottrarre dallo stack prima di prelevare l'indirizzo del registro (E)IP.

RSM - Ripristino dalla modalità System Management
Processore 586+
Questa istruzione serve per uscire dalla modalità System Management introdotta con la famiglia Pentium. Tale operazione serve per uscire da tale modalità e ritornare il controllo al sistema operativo o all'applicazione interrota. Per maggiori informazioni su tale modalità è vivamente consigliato leggere uno dei libri della Intel presenti nella bibliografia.

SAHF - Memorizza AH nei flag
Carica nel registro dei flag il contenuto di AH e precisamente al bit 7, 6, 4, 2, 0 ovvero vengono modificati SF, ZF, AF, PF e CF.

SAL/SAR/SHL/SHR - Istruzioni di scorrimento
Ecco altre istruzioni per la manipolazione dei bit. Stavolta però i bit non vengono rotati, ma bensì shiftati, ovvero i bit che andranno oltre la dimensione dell'operando non saranno ripristinati dall'altro lato del registro ma bensì eliminati. 
Esempio:
1000 1000 SAL (Shift aritmetico a sinistra) di un bit
0001 0000 - Risultato: 1 scivolato a sinistra viene eliminato e viene caricato
                       uno 0 per coprire il bit0
L'unica differenza tra la coppia SAL/SAR e SHL/SHR è che in quest'ultima coppia il bit OF viene memorizzato con il contenuto del bit più alto. La quantità di bit da shiftare può essere specificata immediatamente oppure può essere specificata nel registro CL, ma in ogni caso per questioni di ottimizzazione e velocità di esecuzione la quantità di bit non può essere maggiore di 31!

SBB - Sottrazione con riporto negativo
Aggiunge il secondo operando (DEST) al flag di riporto (CF) e sottrae il risultato dal primo operando (PROV). Il risultato della sottrazione viene
assegnato al primo operando (DEST) ed i flag sono impostati di conseguenza.
Per farla breve questa sottrazione tiene conto del segno ;)

SCAS/SCASB/SCASW/SCASD - Confronto tra dati di stringa
Sottrae dal registro AL, AX o EAX il byte, la word, la dword di memoria alla locazione puntata da ES:(E)DI. Il risultato viene scartato e solo i flag sono impostati.

SETcc - Impostazione di byte su condizione
Imposta l'operando se è vera la condizione:
Istruzione         Condizioni di verità
SETA               CF=0 ZF=0
SETAE              CF=0
SETB               CF=1
SETBE              CF=1 ZF=1
SETC               CF=1
SETE               ZF=1
SETG               ZF=0 SF=OF
SETGE              SF=OF
SETL               SF<>OF
SETLE              ZF=1 SF=OF
SETNA              CF=1 ZF=1
SETNAE             CF=1
SETNB              CF=0
SETNBE             CF=0 ZF=0
SETNC              CF=0
SETNE              ZF=0
SETNG              ZF=1 SF<>OF
SETNGE             SF<>OF
SETNL              SF=OF
SETNLE             ZF=0 SF=OF
SETNO              OF=0
SETNP              PF=0
SETNS              SF=0
SETNZ              ZF=0
SETO               OF=1
SETP               PF=1
SETPE              PF=1
SETPO              PF=0
SETS               SF=1
SETZ               ZF=1

SGDT/SIDT - Memorizzazione del registro della tabella di descrittori globali o di interruzione
Processore 386+
Copia il contenuto del registro della tabella dei descrittori nei 6 byte indicati dall'operando. In poche parole copia il seg:offset dove seg è di 16bit (modalità protetta, max 8192 descrittore) e l'offset l'indirizzo (spero che non abbiate dimenticate queste cose!).

SHLD - Scorrimento a sinistra in doppia precisione
Processore 386+
Fa scorrere il primo operando fornito dal campo r/m a sinistra di tanti bit quanti sono specificati dall'operando contatore. Il secondo operando (reg16 o reg32) specifica i bit da far scorrere all'interno da destra. Il risultato viene memorizzato nell'operando r/m ovvero il primo operano.

SHRD - Scorrimento a destra in doppia precisione
Processore 386+
Fa scorrere il primo operando fornito dal campo r/m a destra di tanti bit quanti sono specificati dall'operando contatore. Il secondo operando (reg16 o reg32) specifica i bit da far scorrere all'interno da sinistra. Il risultato viene memorizzato in r/m.

SLDT - Memorizzazione del registro della tabella di descrittori locale
Processore 386+
Memorizza la tabella di descrittori locale nei due byte di un registro o di una locazione di memoria indicata dall'operando d'indirizzo effettivo.
Solo per sistemi operativi!

SMSW - Memorizzazione della word di stato della macchina
Processore 286+
Memorizza la Machine status word ( o i primi 16 bit di CR0 nel 386) nei 2 byte di un registro o di una locazione di memoria specificata.

ST? - Attivazione del flag specificato
STC - Attivazione flag carry
STD - Attivazione flag direzione
STI - Attivazione flag d'interruzione

STOS/STOSB/STOSW/STOSD - Memorizzazione di dati di stringa
Trasferisce il contenuto di AL, AX o EAX nel byte, word o dword puntata dalla coppia ES:(E)DI. Pu• essere utilizzata con REP o REPcond.

STR - Memorizzazione del registro di task
Processore 386+
Il registro di task viene copiato nel registro o nei 2 byte di memoria specificata. Impiegato nei sistemi operativi.

SUB - Sottrazione tra interi
Effettua la sottrazione tra due numeri interi ponendo il risultato in dest che è anche il primo parametro.
SUB dest, orig ;)

TEST - Confronto logico
Esegue l'and logico tra i due operandi senza però memorizzare il risultato in nessuno dei due operandi a differenza di AND. L'AND viene eseguito tra ogni bit, ovvero se 1 e 1 viene impostato 1 altrimenti 0.

VERR/VERW - Verifica di un segmento per la lettura o scrittura
Processore 386+
Verifica che il segmento specificato sia disponibile per una eventuale lettura o scrittura. La disponibilità viene anche influenzata dal CPL corrente. In poche parole se siete al CPL 0 ok altrimenti vi arrangiate! ;)

WAIT - Attesa fino a che il piedino BUSY# non diventi inattivo
Sospende l'esecuzione delle istruzioni della cpu finchè la NPU non disattivi il segnale BUSY# che viene pilotato da quest'ultima. Utile per capire se la NPU ha terminato le operazioni assegnatele!

WBINVD - Write-Back and Invalidate Cache
Processore 486+ ?
Svuota il contenuto della cache scrivendolo nella memoria ram.

WRMSR - Write to Model Specific Register
Processore 486+ ?
Il registro ECX specifica uno dei registri interni da 64bit del Pentium e la coppia EDX:EAX contiene la qword da caricare.

XADD - Scambio e addizione
Processore 586+
L'istruzione XADD richiede come parametro due operandi, di cui il 2nd operando o origine deve essere sempre un registro da 8, 16 o 32bit.
Prima di effettuare la somma XADD cambia i valori, copiando origine in destinazione e viceversa e successivamente esegue la somma.

XCHG - Scambio di registro/memoria con registro
Processore 386+
Scambia i due operandi. Il primo operando o dest viene copiato in orig e orig in dest. orig deve essere un registro da 8, 16 o 32bit.

XLAT/XLATB - Conversione di ricerca in tabella
Processore 386+
Modifica il registro AL dall'indice di tabella all'entrata di tabella. AL deve essere l'indice senza segno in una tabella indirizzata da DS:(E)BX
XLAT è utilizzata quando la tabella risiede nel segmento DS:(E)BX 

XOR - OR esclusivo logico
Calcola l'OR esclusivo dei suoi due operandi e pone il risultato nel primo operando. L'OR esclusivo è vero quando i bit dei due operandi sono diversi.
Esempio:
1000 1000 XOR
0101 1001 =
0010 1110

Bene con questa istruzione ho terminato il capitolo più lungo di questa guida. Tralascio le istruzioni NPU e le istruzioni MMX, altrimenti qua non finiamo più... e comunque penso che già queste siano più che sufficienti (anzi... molte di queste probabilmente non le vedrete mai)

 


--[CAPITOLO VIII - Accenni alle modalità]--

Modalità reale:
è la più vecchia, dove potrete usufruire di un massimo di 1MB di memoria. All'inizio il processore parte in questa modalità.
Ogni segmento ha una dimensione massima di 64Kb e i registri segmento (CS DS ES SS FS GS) possono assumere un valore compreso tra 0 e 65535. Per ricavare l'indirizzo effettivo il processore possiede un registro interno di 20 bit non accessibile al programmatore nel quale viene
effettuato il seguente calcolo:
Valore del segmento * 16 + Offset che va da 0 a 16 anche se i registri potrebbero assumere un valore tra 0 e 65535.
In modalità reale potete eseguire qualsiasi tipo di operazione e modificare tutto (accedendo ovunque nella memoria), ma in una piccola parte di memoria. Inoltre è bene sapere che ad ogni avvio, un'applicazione non si troverà nella stessa parte di memoria (a meno che non riproduciate esattamente le stesse condizioni).
Oggi la modalità reale viene utilizzata da pochi programmatori, che spesso ricorrono a Device Driver per allocare memoria XMS o EMS. Questa memoria non è altro che la memoria effettivamente presente sul vostro PC e superiore all'indirizzamento dei 20 bit.

All'indirizzo assolute 0 è presente una tabella di puntatori di tipo FAR (... 32bit, 16bit di segmento e 16 di offset... comunque tanto per darvi qualche informazione in più... per l'architettura x86 i dati vengono memorizzati in ordine di importanza... ovvero i primi saranno gli ultimi e gli ultimi saranno i primi. Cioè prima sarà memorizzato l'offset e poi il segmento) costituita da ben 256 elementi che sono gli indirizzi degli INTERRUPT.

Successivamente a questa tabella di vettori troviamo un'area chiamata BIOS DATA AREA, in questo piccolo segmento di memoria che ha inizio
in 0040:0000 troviamo memorizzate particolari informazioni, quali la presenza o meno di un coprocessore matematico, il numero di floppy disk
drive, la quantità di memoria convenzionale disponibile, in poche parole valori che vi vengono restituite da talune funzioni del BIOS.

Bene ora avremo una quantità di spazio di memoria vuota! Qui per circa 637Kb troveremo il kernel del sistema operativo, i TSR ecc. Ricordo comunque che stiamo parlando di modalità reale... il che significa come sistemi operativi il DOS in tutte le sue forme ed espressioni. I moderni sistemi operativi usano la modalità reale solo per l'inizializzazione in quanto sono disponibili numerose funzioni che ne permettono il caricamento corretto del sistema operativo.

Arrivati al segmento A000:0000 incominciamo a trovare la cosiddetta memoria video, non quella presente sulla scheda video ma bensì un'area che viene utilizzata dalla scheda video per reperire informazioni sulle immagini da disegnare. Questa area ha una dimensione di 64Kb, e viene utilizzata per le modalità grafiche (320x200x256 , 640x480x16 ecc...). Risoluzioni più alte o con una quantità maggiore di colori vengono garantite dalle funzioni dello standard VESA.

Segmento B000:0000 Utilizzato per le modalità testuali... è suddiviso in 2 parti. In ogni caso la risoluzione 80x25x16 colori comunque ha inizio
all'indirizzo B800:0000

Successivamente troveremo la cosiddettà memoria SHARE ... ovvero condivisa! Condivisa per due semplici motivi... 1. perchè un qualsiasi componente hardware la potrebbe utilizzare per allocare il proprio BIOS che gli permetta di dialogare con il resto del sistema in modo trasparente... leggesi schede video e controller scsi. Nel caso in cui nessun BIOS è caricato in questi paragrafi da 32Kb (massima quantità disponibile per ciascun BIOS) esso viene occupato dai software di gestione della memoria.

Infine troviamo il segmento F000:0000, dove risiede la vera anima del sistema. I fatidici 64Kb del BIOS. Anche se oggi i nuovi BIOS hanno dimensioni esasperate qui risiede solo quella parte di codice contenente le funzioni in grado di garantire il dialogo con il floppy disk driver, il gestore del timer, il disco fisso, la tastiera, le seriali. Comunque questi INTERRUPT possono essere anche gestiti direttamente dal vostro programma... ma dovrete saper bene cosa fate, pena il crash del sistema!

Modalità Protetta:
in modalità protetta ogni applicativo ha a sua disposizione la bellezza di 4Gb di memoria virtuale. Però non potete andare a utilizzare porzioni di memoria che più vi piacciono, altrimenti il sistema operativo incomincierà a dare i numeri... tipo Windows che ogni tanto senza motivo apparente ci avverte che il programma ha generato un errore generale. Perchè questi messaggi ? Per poter capire l'origine di tali messaggi bisogna conoscere almeno in teoria la modalità protetta e ciò che sta alla sua base.

Bisogna incominciare a sapere che per poter utilizzare la modalità protetta bisogna preparare una tabella di descrittori globali... la quale viene chiamata GLOBAL DESCRIPTOR TABLE. Cosa è questa tabella ? E' costituita da un massimo di 8192 descrittori, che contengono informazioni quali i privilegi di esecuzione di un task, in che modo deve essere indirizzata la memoria ed altre informazioni sul task. In modalità protetta i registri di segmento non serviranno più per sapere dove si trovano in memoria i dati ma conterranno il valore del descrittore che si deve usare. In tale modalità inoltre non avrete a disposizione tutto il set di istruzioni disponibili... ecco i famosi RING3 RING2 RING1 RING0 di cui si sente tanto parlare tra i virus writer ed i programmatori. Al RING3 troveremo sempre la nostra bella applicazione... a cui viene consentito l'uso di un set vasto ma ristretto di istruzioni. Per poter modificare il RING di esecuzione bisogna mettere le mani sulla GDT! Solo lì potremmo stabilire cosa possiamo fare e cosa non possiamo fare. Successiva analisi... la memoria disponibile. Prendiamo il caso del PC che sto usando per scrivere questo documento, dotato di 256Mb di RAM e in alcuni casi di un file di swap di qualche centinaio di mega, a seconda dei programmi che uso. O anche il caso di Linux che richiede almeno una partizione swap per l'installazione! A cosa serve ?

Un programma richiede al sistema per poter funzionare in modo corretto per esempio 128Mb di RAM, ma sulla scatola c'è scritto.. richiede 32Mb di RAM minima. Cosa significa ? Se ne vuole 128 come fa a stare anche in 32Mb o addirittura in 16Mb e perchè tutti quei continui accessi al disco fisso ? Quando un applicativo richiede una zona di memoria che non risulta presente nella memoria ram il processore genera un'eccezione... e a tale eccezione ci sta attaccato il sistema operativo, qualsiasi esso sia! Il sistema carica così in memoria i dati richiesti prelevandoli dal disco fisso e immagazzina altri dati dalla memoria sul disco per poter utilizzare sempre la stessa area di memoria... segnalando al processore quale area di memoria o quale descrittore è disponibile nella memoria ram.

Inoltre assieme a questa particolare gestione della memoria con il processore 80386 Intel introdusse un meccanismo di tasking, per garantire l'esecuzione di più programmi contemporaneamente! Teoricamente... in quanto viene suddiviso il tempo macchina tra i vari processi... con un solo processore ve lo potrete dimenticare per sempre il multitasking reale qualsiasi sia il sistema operativo.

A differenza della modalità reale in cui abbiamo a disposizione le routine del BIOS, in modalità protetta ci possiamo dimenticare gli INTERRUPT per chiamare servizi. La totalità dei sistemi operativi scritti per funzionare in tale modalità devono essere dotati di routine per dialogare con tutti
i componenti hardware che possono essere presenti su una macchina. Alcune di queste routine, quali quelle per gestire la scheda video, la tastiera, il floppy disk drive, il controller e-ide, le porte seriali, la parallela, la scheda audio ed i controller scsi sono inserite sotto forma generica
nei sistemi operativi. Per questo motivo i produttori di hardware spesso allegano al loro prodotto un floppy disk contenente il driver, che partendo
da comandi standard per tutti i componenti appartenenti alla stessa categoria dialogano in modo ottimale con l'hardware per offrire il massimo delle prestazioni. Comunque vi sono anche presenti gli INTERRUPT ma non avremo più la solita tabella di vettori come nella modalità reale ma bensì la INTERRUPT DESCRIPTOR TABLE situata in un punto qualsiasi della memoria puntata dal registro IDT dove saranno assegnati agli interrupt il selettore e l'offset in cui si trova la routine.

Per mantenere la compatibilità con i vecchi applicativi che girano in modalità reale è stata introdotta la cosiddetta modalità V86 o Virtual 86.
All'apparenza all'applicativo la macchina compare come un processore x86 piuttosto veloce ma al quale vengono segate alcune istruzioni!


Beh.... dopo tutte queste informazioni (prese dal tutorial di cod/e-ViP) sarebbe interessante spiegarvi come poter scendere da Ring3 a Ring0, però non si tratta della guida giusta, e quindi mi tocca tralasciare questo argomento. Sorry :) . Se proprio lo desiderate, riferitemelo via e-mail che vedo cosa posso fare.

 

 


--[CAPITOLO Extra - Accenni al formato PE]--

Uhm... questo capitolo sono stato indeciso fino alla fine se trattarlo o no... Il fatto è che mi sembra doveroso farci un accenno, ma io non ve ne parlerò approfonditamente. Il motivo è che questo argomento ha talmente tanti prerequisiti che mi ci vorrebbe una guida solo per introdurlo (e anche perchè ammetto di non averci capito tutto neppure io). L'obiettivo di questo capitolo è mettervi in guardia contro particolari trucchi, indicarvi come procedere nella traduzione degli eseguibili e ampliare un pochettino le vostre conoscenze (però vi avverto... è roba un po' complicata. Se non vi sentite pronti di imparare un mucchio di roba e di capirla, vi consiglio di rimandare).
Ok, uff.. come cominciamo? Boh... direi che prima di tutto si debba mostrare come solitamente è formato un file eseguibile (uhm... prevedo che sarà un luuungo capitolo :\ ).
    +===================+  +00 -> dos header.[3C] ---+
    | DOS (MZ) Header   |                            |
    +-------------------+  +40                       |
    | DOS Stub          |                            |
    +===================+  +00 -> inizio PE header <-+
    | NT (PE) Header    |
    |- - - - - - - - - -|  +04
    | file-header       |
    |- - - - - - - - - -|  +1A
    | optional header   |
    |- - - - - - - - - -|  +78
    | data directories  |
    |                   |
    +===================+  <- PE header + FileHeader.SizeOfOptionalHeader + sizeOf(FileHeader)
    | section headers   |
    |       array       |
    ~-------------------~
    |......padding......|
    ~-------------------~
    |                   |
    | dati section2     |
    |                   |
    +-------------------+
    |                   |
    | dati section2     |
    |                   |
    +-------------------+
    | ..............    |
    +-------------------+
    |                   |
    | dati section n    |
    |                   |
    +-------------------+

Bene bene... come si può vedere abbiamo all'inizio l'header del DOS (Dos Header).
IMAGE_DOS_HEADER STRUC
    e_magic                   DW      ?   ;+00     ; Magic number
    ......
    e_lfanew                  DD      ?   ;+3C     ; Address of PE header
IMAGE_DOS_HEADER  ENDS
L'e_magic è composto da due byte che identificano il formato. Tali byte sono 0x4D5A e corrispondono ai caratteri MZ (infatti se aprite un eseguibile con l'editor esadecimale vedrete che all'inizio c'è sempre "MZ".
L'e_lfanew invece è una dword che contiene un RVA (spiego dopo) che punta alla sezione NT Headers. Si tratta quindi della chiave d'accesso al nuovo PE Header.
Cos'è invece quel "DOS Stub"? Con un editor esadecimale potreste arrivarci anche da soli, ma non voglio essere cattivo e ve lo dico lo stesso :).
Si tratta di una specie di programmino che, se provate a far partire un programma Windows su Dos (il vero Dos, non il prompt dei comandi!) fa comparire quella famosa frasetta tipo "This program cannot be run in DOS mode.". È comunque opzionale, anche se c'è in praticamente tutti i file eseguibili.
Ora invece soffermiamoci sulla struttura IMAGE_NT_HEADERS.

    31                         0 31                         0
    +-------------------------------------------------------+ <--+
    |          SIGNATURE        |   MACHINE   | # SECTIONS  |    |
    +---------------------------+-------------+-------------+    | Signature  +
    |       TIME/DATE STAMP     |  POINTER TO SYMBOL TABLE  |    | FileHeader
    +---------------------------+-------------+-------------+    |
    |    NUMBER OF SYMBOL       |  NT HDR SIZE| IMAGE FLAGS | <--+
    +=============+======+======+=============+=============+ <--+
    |    MAGIC    |LMAJOR|LMINOR|       SIZE OF CODE        |    |
    +-------------+------+------+---------------------------+    |
    | SIZE OF INITIALIZED DATA  | SIZE OF UNINITIALIZED DATA|    |
    +---------------------------+---------------------------+    |
    |       ENTRYPOINT RVA      |        BASE OF CODE       |    |
    +---------------------------+---------------------------+    |
    |        BASE OF DATA       |        IMAGE BASE         |    |
    +---------------------------+---------------------------+    |
    |    SECTION ALIGNMENT      |      FILE ALIGNMENT       |    |
    +-------------+-------------+-------------+-------------+    | Optional Header
    |  OS MAJOR   |  OS MINOR   |  USER MAJOR |  USER MINOR |    |
    +-------------+-------------+-------------+-------------+    |
    | SUBSYS MAJOR| SUBSYS MINOR|       WIN32 VERSION       |    |
    +-------------+-------------+---------------------------+    |
    |        IMAGE SIZE         |       HEADER SIZE         |    |
    +---------------------------+-------------+-------------+    |
    |       FILE CHECKSUM       |  SUBSYSTEM  |  DLL FLAGS  |    |
    +---------------------------+-------------+-------------+    |
    |   STACK RESERVE SIZE      |     STACK COMMIT SIZE     |    |
    +---------------------------+---------------------------+    |
    |   HEAP RESERVE SIZE       |     HEAP COMMIT SIZE      |    |
    +---------------------------+---------------------------+    |
    |       LOADER FLAGS        |  # INTERESTING RVA/SIZES  |    |
    +===========================+===========================+    | <-+
    |   EXPORT TABLE RVA        |   TOTAL EXPORT DATA SIZE  |    |   |
    +---------------------------+---------------------------+    |   |
    |   IMPORT TABLE RVA        |   TOTAL IMPORT DATA SIZE  |    |   |
    +---------------------------+---------------------------+    |   |
    |  RESOURCE TABLE RVA       |  TOTAL RESOURCE DATA SIZE |    |   |
    +---------------------------+---------------------------+    |   |
    |  EXCEPTION TABLE RVA      | TOTAL EXCEPTION DATA SIZE |    |   |
    +---------------------------+---------------------------+    |   |
    |  SECURITY TABLE RVA       |  TOTAL SECURITY DATA SIZE |    |   |  Data Directory
    +---------------------------+---------------------------+    |   |
    |    FIXUP TABLE RVA        |   TOTAL FIXUP DATA SIZE   |    |   |
    +---------------------------+---------------------------+    |   |
    |    DEBUG TABLE RVA        |  TOTAL DEBUG DIRECTORIES  |    |   |
    +---------------------------+---------------------------+    |   |
    |  IMAGE DESCRIPTION RVA    |  TOTAL DESCRIPTION SIZE   |    |   |
    +---------------------------+---------------------------+    |   |
    |   MACHINE SPECIFIC RVA    |   MACHINE SPECIFIC SIZE   |    |   |
    +---------------------------+---------------------------+    |   |
    |  THREAD LOCAL STORAGE RVA |      TOTAL TLS SIZE       |    |   |
    +---------------------------+---------------------------+    |   |
    |  LOADER CONFIGURATION RVA |     LOADER DATA SIZE      |    |   |
    +---------------------------+---------------------------+    |   |
    |   BOUNDED IMPORTS TABLE   | BOUNDED IMPORTS DATA SIZE |    |   |
    +---------------------------+---------------------------+    |   |
    |  IMPORT ADDRESSES TABLE   |       TOTAL IAT SIZE      |    |   |
    +---------------------------+---------------------------+ <--+ <-+


Eheheheh... ve l'avevo detto che era un casino... :D 
Dite la verità... di header così complicati ne avete visti pochi, vero?
E ora pensate che bello, che ve li devo spiegare tutti... :D ...ma col caxxo! Vi dico solo quelli più importanti! Sono già un pazzo a fare questa guida, non mi metto certo a spiegare anche questo. Cercate altra documentazione.
Vabbè... continuano che se no non finiamo più.

Come potete vedere, è in pratica l'unione di due strutture: IMAGE_FILE_HEADER e IMAGE_OPTIONAL_HEADER, più una dword (la signature).
Questo ci permette di dedurre che i file PE sono consecutivi in memoria (o su disco) e quindi i campi possono essere letti con semplicità come offsets relativi all'inizio degli NT headers.
Vediamo adesso i campi più importanti.
Signature: identifica il tipo di eseguibile e il sistema operativo a cui è destinato.
            IMAGE_OS2_SIGNATURE       0x4E45     = NE   = new executable = os/2 o win3x
            IMAGE_NT_SIGNATURE        0x50450000 = PE00 = win9x / winNT

IMAGE_FILE_HEADER:
Machine: indica il processore target.
TimeDateStamp: servirebbe per indicare la versione del modulo, ma spesso è inconsistente.
NumberOfSections: indica il numero di sezioni presenti, nonchè il numero di entries nel section headers table. Il loader però pare infischiarsene.
ImgFlags: indica il tipo di immagine (eseguibile, dll, etc...) ed altre caratteristiche.
SizeOfOptionalHeaders:indica la size degli optional headers (normamente 0xE0). La presenza di questo campo e' la conseguenza della natura estensibile del formato PE.

IMAGE_OPTIONAL_HEADER:
Questa struttura contiene informazioni molto importanti per il loader, che gli consentono di creare il processo in memoria, dopo aver recuperato i dati dalle varie sezioni. Anche qui tratto solo le più importanti.
AddressOfEntryPoint: contiene l'RVA dell'entrypoint del modulo (in pratica la prima istruzione di codice che andrà letta). Inevitabilmente punta ad una sezione coi flag readable/executable (di solito .text o CODE).
BaseOfCode: l'RVA della prima sezione di codice (.text o CODE)
BaseOfData: come sopra, solo per la sezione dati.
ImageBase: campo importantissimo. Contiene l'indirizzo lineare nello spazio di indirizzamento privato utilizzato dal linker per risolvere gran parte dei fixup nonchè la base a cui si riferiscono tutti gli RVA.
SectionAlignment: quando il loader di window mappa in memoria il file immagine utilizza i Memory Mapped Files in modo che occupi uno blocco consecutivo di memoria nello spazio di indirizzamento.Tuttavia per questioni di ottimizzazione nella gestione della memoria virtuale (ad exp. nello share di porzioni di codice, nel caricamento di pagine non presenti,ecc.) in w9x ogni sezione deve essere allineata ad un multiplo della unita' minima gestita dal VMM : 1 pagina x86 = 4096 = 1000h (attenzione a non confonderla con la granularita' di allocazione che e' di 64k). Questa limitazione non si applica a NT (il minimo e' 32byte) ma non credo che vogliate degli eseguibili "incompatibili".
FileAlignment: questo campo e' un antico retaggio di quando Windows 95 utilizzava il filesystem FAT, e per ottimizzare i caricamenti si era pensato di allineare i dati delle sezioni su disco ad un multiplo della grandezza di un settore (200h = 512 b). Nel caso sia necessario i linkers paddano a zero lo spazio inutilizzato.
SizeOfImage: è la grandezza dell'immagine una volta in memoria, quindi lo spazio che il loader deve riservare al suo caricamento. È costituito dalla somma dell'header + le Virtual Size delle sezioni presenti, arrotondata al multiplo più vicino della SectionAlignment. Mentre il loader di Win9x ignora un valore incorretto, quello di WinNT se trova un valore inconsistente si arrabbia un po' :) .
SizeOfHeaders: il valore corrisponde a questa somma: DosHeader+Stub+NtHeaders,SectionHeaders. È una specie di puntatore ai rawdata dato che ImageBase+SizeOfHeaders vi porta direttamente all'inizio della prima sezione.
CheckSum: beh... non penso che vi sia nuova la cosa del checksum per gli eseguibili, vero (oltretutto si trovano molti programmi su internet che lo correggono)? Comunque bisogna dire che farlo è totalmente inutile per i file per Win9x, mentre deve essere fatto per i file di sistema di WinNT. L'algoritmo di calcolo è proprietario della Microsoft.
NumberOfRvaAndSizes: questo campo indica la dimensione dell'array di strutture IMAGE_DATA_DIRECTORY che inizia dal campo DataDirectory. Attualmente è fissato a 16 elementi ma non necessariamente per sempre.
DataDirectory: questo pseudo-campo in realtà è un array di strutture che rappresentano per il loader una sorta di shortcut per accedere velocemente alle informazioni più sensibili per la creazione/inizializzazione del processo: ogni entry (indici da 0..15) riporta l'RVA e la VirtualSize di specifiche informazioni/strutture: le più importati sono: 0 : funzioni esportate dal modulo (ET) 1 : funzioni importate ma non bounded (IT) 2 : inizio della resource directory (resROOT) 5 : base relocations 9 : blocco thread local storage (TLS) 11: funzioni importate bound (BIT) 12: import addresse table (IAT) Una cosa importante da dire è che il loader fa sempre riferimento a questa tabella per accedere ai dati del processo e non alla tabella dei section headers. Se volete ad esempio reperire le informazioni su dove reperire le risorse (ad esempio per evitare di criptarle) non utilizzate i nomi delle section tipo .rsrc visto che questi sono puramente convenzionali: nessuno ci garantisce cosa ci sia dentro o che qualcuno li abbia rinominati (molti crypters lo fanno). Detto questo va da se che i dati qui presenti devono essere ASSOLUTAMENTE coerenti o il programma si pianterà inesorabilmente.

IMAGE_SECTION_HEADER:
È un array di strutture noto come Sections Table, di cui ogni elemento descrive i dati essenziali di una sezione presente nel file. Neanche a dirlo, vengono trattati solo i campi più importanti.
SName: stringa di 8 byte con il nome della sezione (attenzione che non e' null termined)
SVirtualSize: convenzionalmente contiene la dimensione fisica (vedi SizeOfRawData) dei dati arrotondata ad un multiplo del section aligment. Questo campo in pratica dovrebbe dire al loader quanto spazio riservare in memoria per questa sezione. Notate che ho usato il condizionale perchè il loader sembra perfettamente ignorare questo campo in presenza di una rawsize "valida" ed effetuare da sè i calcoli per una VSize corretta. Questo probabilmente spiega anche il fatto che la ImageSize venga ignorata da w9x. Comunque è anche perfettamente lecito avere una rawsize = 0 e una VSize=0x1000, tant'è che i packer sfruttano proprio questa caratteristica cambiando la rawsize ma lasciando inalterata la VSize (a dir il vero la VSize può anche sovrapporsi alla sezione successiva dato che è comunque uno spazio solo "riservato" e non necessariamente utilizzato) purchè ovviamente non ci sia vera sovrascrizione. Insomma, il loader di win32 scieglie con oculatezza quali informazioni siano più coerenti o se le calcola da se.
SVirtualAddress: questo RVA permette di calcolare la posizione che avrà la sezione una volta caricata in memoria dal loader. Come ormai avrete capito deve essere maggiore, o un multiplo, del section alignment (che non può essere minore di 0x1000 per compatibilità con 9x)
SizeOfRawData: la dimensione fisicamente occupata dai dati su disco solitamente allineata al file alignment. Questo campo può essere totalmente indipendente dalla VSize, ad esempio spesso incontrerete sezioni con rawsize = 0 ma che occupano spazio in memoria (tipicamente sezioni con dati non inizializzati (BSS, TLS, ecc.)), ma comunque è importante capire che almeno uno dei due valori dovrà contenere l'informazione dello spazio da minimo da riservare in memoria. Tenete conto di questa anomalia quando calcolate la ImageSize.
PointerToRawData: l'offset "fisico" a cui troverete i dati della sezione
SFlags: i flag che identificano le caratterestiche (codice,dati,ecc.) e quindi le i flags e le protezioni di pagina che verranno applicate (writable,readable,ecc.)

Bene abbiamo analizzato gli headers che precedono i dati veri e propri delle sezioni... resta solo da notare che in effetti tra la fine dell'ultimo section header e l'inizio dei dati spesso si trova una "cavità" ovvero un blocco non utilizzato ma presente per questioni di allineamento. Queste cavità presenti anche tra le sezioni possono essere sfruttate per salvare codice e/o dati a patto che siano abbastanza grandi (i virus sono un classico esempio di utilizzatori di queste cavità). Fra tutte queste cavità quella che più ci interessa è proprio quella fra la sections table e l'inizio della prima sezione, in quanto è lì che possiamo introdurre una nuova sezione semplicemente incrementando il campo FileHeader.NumberOfSection e accodando una struttura IMAGE_SECTION_HEADER all'array. Ovviamente questo discorso è valido se c'è abbastanza 
spazio (attenzione che per spazio va inteso quella tra la fine degli NTHeaders e l'RVA della prima sezione e non solo lo spazio "fisico", che normalmente è minore per via che di solito file alignment < section alignment) altrimenti dobbiamo "necessariamente" appendere il nostro 
codice/dati nell'ultima sezione del file (oddio non è proprio necessario che sia l'ultima, potremmo scegliere una sezione qualsiasi, ma sicuramente è molto più semplice che alterare gli RVA di tutte quelle successive).

Ora parliamo un po' di più dell'ImageBase e degli RVA
ImageBase e Relative Virtual Address
L'image base è sostanzialmente l'indirizzo lineare a cui il loader mapperà l'immagine dell'eseguibile quando crea un nuovo processo, o carica un modulo (DLL). Questo indirizzo, riportato nel campo OptionalHeader.ImageBase, è specifico per ogni eseguibile ed è essenzialmente l'indirizzo utilizzato (o specificato da noi) dal linker per risolvere i fixup. Tuttavia non sempre il loader può caricare l'immagine alla ImageBase specificata 
(detta appunto "preferred"): questa eventualità (chiamata "collisione"), è sostanzialmente impossibile per gli eseguibili (ovviamente se consideriamo il fatto che ogni processo win32 ha un suo spazio di indirizzamento "assolutamente" privato.. per gli exe vedrete infatti sempre specificata come imagebase 0x400000) ma e' altamente probabile per una DLL che invece può essere caricata nell'area condivisa ( > 2gb e < 3gb in 9x; Nt non ha spazi r3 shared) o comunque in un'area già impegnata da una precedente allocazione di memoria. Se si verifica una collisione il loader per permettere all'eseguibile di funzionare sarà costretto ad applicare la c.d. base relocation, a patchare cioè tutti quei riferimenti assoluti che il programma utilizza in modo che siano di nuovo coerenti. Considerati questi problemi si è pensato di "virtualizzare" gli indirizzi assoluti almeno delle strutture utilizzate dal loader rendendo così possibile referenziare le informazioni salvate dal linker a prescindere dalla imagebase: ecco quindi nascere l'idea dell'RVA, che e' appunto un scostamento relativo alla imagebase: quindi se volete leggere il valore di una DWORD che sta ad un RVA = 1234 basta che sommiate a questo l'imagebase ed otterrete il suo Virtual Address (VA) cioè l'indirizzo nello spazio di indirizzamento del processo: 
VA       = RVA    + ImageBase
0x401234 = 0x1234 + 0x400000
Ovviamente questo ragionamento è valido se l'eseguibile è stato mappato dal loader, perchè come sappiamo questo terrà conto del section alignment... ma se volessimo ottenere un offset "fisico" (su disco,MMF) dato un VA ? In questo caso dovremmo utilizzare le informazioni relative alla sezione che contiene quell'indirizzo (ovviamente dobbiamo trovarla cercando nella section table verificando che SVirtualAddress <= VA <= SVirtualAddress + SVirtualSize), relativizzare l'indirizzo rispetto all'inizio di quella sezione sottraendo l'imagebase e VA della sezione, ottenendo così un offset che andremo a sommare all'offset fisico della sezione stessa:
RAW OFS = (VA       - ImageBase - SVirtualAddress) + PointerToRawData
0x834   =    x401234  - 0x400000   - 0x1000         )     + 0x600


Ok, abbiamo finito tutti questi concetti di base. Perchè tutta questa sfilza di roba qua? Beh, prima di tutto perchè il sapere è potere (e già questa dovrebbe essere una motivazione sufficiente)... e poi essenzialmente per due motivi: primo, perchè così capite come modificare un puntatore in una file eseguibile, e secondo, perchè ultimamente sono veramente tanti gli eseguibili che vengono compressi e/o criptati. Quindi è inutile sapere l'assembly se dopo ci troviamo di fronte a del codice criptato. Io non vi spiegherò come decriptare questo testo (a meno che voi non me lo richiedate in tanti), però in tanto vi ho fornito delle informazioni base per capire come si muovono i vari packers e crypters.

Allora come si modifica un puntatore? Beh, penso che la cosa migliore sia prendere un esempio concreto per farvi capire bene cosa succede.
Per tale esempio sfrutterò Kotobuki.exe (che viene trattato anche nel capitolo "Si traduce").
Abbiamo questo testo da tradurre (offset 0x4535D):
.data:00447730 4F 57 4D 4F 44 45 00 00-46 55 4C 4C 53 43 52 45 "OWMODE..FULLSCRE"
.data:00447740 45 4E 00 00 48 45 4C 50-00 00 00 00 47 4F 48 4F "EN..HELP....GOHO"
.data:00447750 55 42 49 00 43 4F 4E 54-49 4E 55 45 00 00 00 00 "UBI.CONTINUE...."

Ora... supponiamo di dover tradurre "FULLSCREEN" con "PIENO SCHERMO". Sgarra di tre lettere rispetto all'originale. Ciò vuol dire che dovremo modificare i puntatori di "Help"! Quindi, come procediamo? Guardiamo da dove viene puntato il testo...
.data:00447743 align 4
.data:00447744 dword_447744 dd 504C4548h ; DATA XREF: sub_423860+B5r  <---
.data:00447748 byte_447748 db 0 ; DATA XREF: sub_423860+BAr

Andiamo quindi a vedere...
.text:00423915 loc_423915: ; CODE XREF: sub_423860+22j
.text:00423915 ; DATA XREF: .text:00423A30o
.text:00423915 mov eax, dword_447744 ; case 0x4
.text:0042391A mov cl, byte_447748

Dobbiamo cambiare il puntatore e spostarlo in avanti di 2 (in modo da farci stare "pieno Schermo", il byte 0x00 di fine stringa e quindi Help).
Andiamo a vedere i byte che compongono questa istruzione:
.text:00423915 mov eax, dword_447744 ; case 0x4
00 00 00 A1 44 77-44 00 8A 0D 48 77 44 00 <== in grassetto sono i byte che compongono quell'istruzione. A1 è l'opcode e 44774400 è l'indirizzo.
Ok, come vedete il codice punta non all'offset, ma all'indirizzo (RVA). Quindi anche voi non dovrete sostituirlo con l'offset, ma con il nuovo RVA. Non vi preoccupate, è facilissimo. Dobbiamo aggiungere due al RVA di prima, no? Bene, prendiamo i byte (44774400) e, dato che il x86 è Little Endian, invertiamoli (00447744). Quello è l'RVA del testo. A questo punto ci aggiungiamo due (00447746) e reinvertiamo (46774400). Questi sono i byte dell'indirizzo da usare. Per far questo prenderemo il nostro editor esadecimale, ritorneremo sui byte 00 00 00 A1 44 77-44 00 8A 0D 48 77 44 00 e li cambieremo in 00 00 00 A1 46 77-44 00 8A 0D 48 77 44 00 .
Come vedete si lavora solamente con gli RVA. 
Poi, vabbè... in questo gioco non sarebbe sufficiente cambiare gli RVA, bisognerebbe anche codare un po' in asm... però questo era quello che vi volevo fare vedere.

E invece per quanto riguarda i packers e i crypters? Intanto... cosa sono? Sono dei programmi che, criptano e comprimono il contenuto degli eseguibili, senza però compromettere loro la possibilità... di essere eseguiti! Alcuni programmatori li usano per comprimere il loro software per poterlo scaricare più velocemente dalla rete, ma i più lo fanno per proteggerlo da i primi reverser che se lo trovano tra le mani.
Ma come capire quando abbiamo a che fare con un eseguibile compresso? Beh, possiamo prima di tutto dare una rapida occhiata ai nomi delle sezioni. Anche se non è indicativo, se vediamo esserci sezioni con nomi strani (ossia sezioni con nomi "non standard") possiamo essere quasi certi che si tratti di eseguibili packati. Poi, un altro modo può essere quello di dare in pasto ad IDA il file. Se al momento del caricamento ci dice che il file non è proprio come dovrebbe essere (segmenti distrutti etc...) allora credo che la cosa dovrebbe farci storcere il naso.
Ma se invece vogliamo proprio sapere QUALE packer è stato usato? L'unica lì, se non riusciamo a riconoscerlo da soli, è di affidarsi a tool come GetType, GenericUnpacker o altri programmini simili che riconoscano automaticamente i packers.
Che vantaggio se ne può ricavare dal conoscere il packer utilizzato? Beh, dovete sapere che per le protezioni commerciali esistono dei programmi che depackano (in modo più o meno buono) gli eseguibili automaticamente. Questi tool (solitamente molto intuitivi) sono una vera e propria manna dal cielo per tutti quelli che non sanno fare quello che in gergo si chiama manual unpacking. Il tool fa tutto da solo, sempre che si tratti della stessa versione del crypter e che il file non sia stato ulteriormente modificato in qualche modo.
Se non si riescono a trovare i tool, l'unica quindi è decriptare tutto a mano. In questo capitolo non vi spiegherò come fare (oltre ad essere già troppo lungo, questo è anche un argomento difficilotto... non sto scherzando.), se proprio siete curioso e volete che tratti pure questo richiedetemelo per e-mail (o in qualunque modo, purchè mi arrivi il messaggio).
 



--[ Z80 ]--
 


--[CAPITOLO IX - I registri]--

Nello Z80 sono disponibili i seguenti registri:
Registri a 8 bit:
A
- E' il registro accumulatore. Nello Z80 sarete costretti ad usare questo durante molte operazioni e molto spesso, quando in un'espressione viene omesso un operando, quasi sicuramente vorrà dire che dovrete usare il registro A.

B - C - D - E - H - L - Sono altri registri a 8 bit. Non hanno particolarità da segnalare. Si possono "unire" (vedi sotto).

F - E' il registro dei flag... è così suddiviso:

Bit7             Bit0
S Z   H   P/V N C

S: è il flag di segno. Se il risultato di un'operazione è negativo, questo flag viene settato a 1.
Z: il flag di zero. Se il risultato di un'operazione è 0 il flag viene portato a 1.
H: è il carry dal bit 3 al bit 4 dell'operazione. Usato con l'istruzione DAA.
P/V: questo flag può contenere la parità (la parità dei bit a 1 nel risultato dell'operazione) o l'overflow (eccedenza... viene settato il flag a 1 se il valore del complemento a 2 non ci sta nel registro.
N: viene settato a 1 se l'ultima operazione è stata una sottrazione. Altrimenti vuol dire che era un'addizione. Usato con l'istruzione DAA.
C: il flag del carry. Se il risultato dell'ultima operazione non ci sta nel registro, questo bit viene settato a 1.
A volte i bit del flag hanno significati diversi... dipende dalle istruzioni. Comunque questi sono i significati "standard". 

Come avrete notato, ci sono dei bit vuoti... il motivo è che non hanno nè nomi ufficiali, nè scopi ufficiali. Comunque qualcosa fanno anche loro.
Bit 5: contiene la copia del bit 5 del risultato.
Bit 3: contiene la copia del bit 3 del risultato.


R
- Serve per il refresh della memoria (il "rinfresco" della memoria). Viene incrementato di 1 dopo ogni istruzione, durante il quale i prefissi CB, DD, DDCB, ED, FD e FDCB sono visti come istruzioni separate. Il bit 7 non viene mai modificato, ma può essere cambiato tramite LD A, R.

I - Esiste anche questo registro (sempre a 8 bit), ma non so di preciso cosa faccia. Comunque quasi sicuramente si tratta di un registro per gli interrupt (forse per il vettore degli interrupt).

Nello Z80 è possibile anche "unire" i registri (prenderli a coppia), per poter disporre in questo modo di registri a 16 bit, secondo questa tabella.
Le coppie vengono fatte per riga.

Generali   Alternativi
A F A' F'
B C B' C'
D E D' E'
H L H' L'


Tramite certe istruzioni è anche possibile "salvare" il contenuto dei registri generali nei registri alternativi.

Registri a 16 bit:
IX - IY - Sono i registri di indirizzamento. Vengono utilizzati quasi sempre per puntare ad un certo indirizzo di memoria.

SP - È lo stack pointer. Come da nome, punta allo stack.

PC - Program Counter. È il registro che contiene l'indirizzo con l'istruzione da eseguire. (NB. I jump settano il valore di questo registro per "saltare" da un'istruzione all'altra.

E con questo direi che abbiamo finito anche questo capitolo... Se trovate a cosa serve di preciso il registro I contattatemi :) . La mia e-mail la trovate alla fine della guida.
 

 


--[CAPITOLO X - Gli interrupt]--

Lo Z80 dispone di due tipi di interrupt: mascherabili e non mascherabili. Quando si verifica un interrupt non mascherabile (NMI), viene effettuata una call all'indirizzo 0x66 e vengono disattivati gli interrupt mascherabili (INT). La routine dovrebbe terminare con una RETN o semplicemente con una RET.
Invece, con gli interrupt mascherabili (INT) è possibile richiamare un'interrupt anche quando non è ancora stata terminata una routine INT (per questo sarebbe meglio disabilitare gli interrupt quando si entra in queste routine). Esistono anche varie modalità per gli interrupt mascherabili. Vediamoli in dettaglio una per una.
IM 0: viene eseguita l'istruzione sul bus (di solito un RST p)
IM 1: viene eseguita l'istruzione RST all'indirizzo 0x38
IM 2: viene eseguita una call all'indirizzo letto dalla memoria. Tale indirizzo è calcolato in questo modo: <registro I> * 256 + <valore sul bus> . Vengono letti due byte.
Alla fine delle routine INT gli interrupt dovrebbero essere riabilitati. Per fare questo possiamo scrivere EI prima di una RETI (o RET):
INT: .
       .
       .
       EI
       RETI (or RET)
Il comando per disabilitare gli interrupt (DI) non è necessario all'inizio delle routine INT, visto che lo Z80 disabilita gli interrupt automaticamente quando si entra in queste routine.
Beh... abbiamo già finito! Pensavo di metterci molto di più. Poco male... potete passare subito al prossimo capitolo.
 

 


--[CAPITOLO XI - Le istruzioni]--

Sono stato un po' a pensare a quale tipo di lista di opcode passarvi. Il motivo è che ognuno è utile per vedere meglio un certo aspetto delle istruzioni. Alla fine ho scelto un tipo di lista molto chiaro (ideale per capire le istruzioni), senza tanti particolari tecnici (anche se a volte sarebbe utile saperli).
Comunque, se le volete, richiedetemi per e-mail la lista che fa vedere bene i byte che compongono le varie istruzioni o quella per vedere meglio cose tipo le modifiche ai flag etc...
Comunque adesso passiamo alle istruzioni.

ADC   HL,ss       Somma, con riporto, la coppia di registri ss a HL.
ADC   A,s         Somma, con riporto, l'operando s all'accumulatore.
ADD   A,n         Somma il valore n all'accumulatore.
ADD   A,r         Somma il registro r all'accumulatore.
ADD   A,(HL)      Somma il valore puntato da (HL) all'accumulatore.
ADD   A,(IX+d)    Somma il valore puntato da (IX+d) all'accumulatore.
ADD   A,(IY+d)    Somma il valore puntato da (IY+d) all'accumulatore.
ADD   HL,ss       Somma la coppia di registri ss ad HL.
ADD   IX,pp       Somma la coppia di registri pp a IX.
ADD   IY,rr       Somma la coppia di registri rr a IY.
AND   s           Esegue l'operazione logica AND tra l'operando s e l'accumulatore.
BIT   b,(HL)      Testa il bit b alla locazione (HL).
BIT   b,(IX+d)    Testa il bit b alla locazione (IX+d).
BIT   b,(IY+d)    Testa il bit b alla locazione (IY+d).
BIT   b,r         Testa il bit b del registro r.
CALL  cc,nn       Chiamata a subroutine all'indirizzo nn se la condizione CC è verificata.
CCF               Complemento al flag di carry.
CP    s           Confronta l'operando s con l'accumulatore.
CPD               Confronta la locazione (HL) con l'accumulatore, decrementa HL e BC,
CPDR              Esegui una CPD e ripeti fino a che BC=0.
CPI               Confronta la locazione (HL) con l'accumulatore, incrementa HL, decrementa BC.
CPIR              Esegui un CPI e ripeti fino a che BC=0.
CPL               Complemento dell'accumulatore (complemento a 1).
DAA               Correzione decimale dell'accumulatore.
DEC   m           Decrementa l'operando m.
DEC   IX          Decrementa IX.
DEC   IY          Decrementa IY.
DEC   ss          Decrementa la coppia di registri ss.
DI                Disabilita gli interrupt.
DJNZ  e           Decrementa B e esegui un salto relativo se B=0.
EI                Abilita gli interrupt.
EX    (SP),HL     Scambia la locazione (SP) e HL.
EX    (SP),IX     Scambia la locazione (SP) e IX.
EX    (SP),IY     Scambia la locazione (SP) e IY.
EX    AF,AF'      Scambia i contenuti di AF e AF'.
EX    DE,HL       Scambia i contenuti di DE e HL.
EXX               Scambia i contenuti di BC,DE,HL con BC',DE',HL'.
HALT              Arresta il processore e attendi un interrupt.
IM    0           Setta la modalità interrupt 0.
IM    1           Setta la modalità interrupt 1.
IM    2           Setta la modalità interrupt 2.
IN    A,(n)       Carica nell'accumulatore un dato dall'input all'indirizzo n.
IN    r,(c)       Carica nel registro r un dato dall'input all'indirizzo (C).
INC   (HL)        Incrementa la locazione (HL).
INC   IX          Incrementa IX.
INC   (IX+d)      Incrementa la locazione (IX+d).
INC   IY          Increment IY.
INC   (IY+d)      Incrementa la locazione (IY+d).
INC   r           Incrementa il registro r.
INC   ss          Incrementa la coppia di registri ss.
IND               (HL)=Input dalla porta (C). Decrementa HL e B.
INDR              Esegui una IND e ripeti fino a che B=0.
INI               (HL)=Input dalla porta (C). HL=HL+1. B=B-1.
INIR              Esegui una INI e ripeti fino a che B=0.
JP    (HL)        Salto incondizionato alla locazione (HL).
JP    (IX)        Salto incondizionato alla locazione (IX).
JP    (IY)        Salto incondizionato alla locazione (IY).
JP    cc,nn       Salta alla locazione nn se la condizione cc è verificata.
JR    C,e         Salto relativo a PC+e se il carry=1.
JR    e           Salto incondizionato relativo a PC+e.
JR    NC,e        Salto relativo a PC+e se il carry=0.
JR    NZ,e        Salto relativo a PC+e se non zero (Z=0).
JR    Z,e         Salto relativo a PC+e se zero (Z=1).
LD    A,(BC)      Carica in accumulatore la locazione (BC).
LD    A,(DE)      Carica in accumulatore la locazione (DE).
LD    A,I         Carica in accumulatore I.
LD    A,(nn)      Carica in accumulatore la locazione nn.
LD    A,R         Carica in accumulatore R.
LD    (BC),A      Carica nella locazione (BC) il contenuto dell'accumulatore.
LD    (DE),A      Carica nella locazione (DE) il contenuto dell'accumulatore.
LD    (HL),A      Carica nella locazione (HL) il contenuto dell'accumulatore.
LD    dd,nn       Carica nella coppia di registri dd il valore nn.
LD    dd,(nn)     Carica nella coppia di registri dd la locazione (nn).
LD    HL,(nn)     Carica in HL la locazione (nn).
LD    (HL),r      Carica nella locazione (HL) il registro r.
LD    I,A         Carica in I il valore dell'accumulatore.
LD    IX,nn       Carica in IX il valore nn.
LD    IX,(nn)     Carica in IX la locazione (nn).
LD    (IX+d),n    Carica nella locazione (IX+d) il valore n.
LD    (IX+d),r    Carica nella locazione (IX+d) il registro r.
LD    IY,nn       Carica in IY il valore nn.
LD    IY,(nn)     Carica in IY la locazione (nn).
LD    (IY+d),n    Carica nella locazione (IY+d) il valore n.
LD    (IY+d),r    Carica nella locazione (IY+d) il registro r.
LD    (nn),A      Carica nella locazione (nn) il contenuto dell'accumulatore.
LD    (nn),dd     Carica nella locazione (nn) la coppia di registri dd.
LD    (nn),HL     Carica nella locazione (nn) il valore di HL.
LD    (nn),IX     Carica nella locazione (nn) il valore di IX.
LD    (nn),IY     Carica nella locazione (nn) il valore di IY.
LD    R,A         Carica in R il contenuto dell'accumulatore.
LD    r,(HL)      Carica nel registro r la locazione (HL).
LD    r,(IX+d)    Carica nel registro r la locazione (IX+d).
LD    r,(IY+d)    Carica nel registro r la locazione (IY+d).
LD    r,n         Carica in r il valore n.
LD    r,r'        Carica nel registro r il contenuto di r'.
LD    SP,HL       Carica in SP il contenuto di HL.
LD    SP,IX       Carica in SP il contenuto di IX.
LD    SP,IY       Carica in SP il contenuto di IY.
LDD               Carica nella locazione (DE) la locazione (HL), decrementa DE,HL,BC.
LDDR              Esegui una LDD e ripeti fino a che BC=0.
LDI               Carica nella locazione (DE) la locazione (HL), incrementa DE,HL; decrementa BC.
LDIR              Esegui una LDI e ripeti fino a che BC=0.
NEG               Nega il contenuto dell'accumulatore (Complemento a 2).
NOP               Nessuna operazione.
OR    s           Esegue l'operazione logica OR tra l'operando s e l'accumulatore.
OTDR              Esegui una OUTD e ripeti fino a che B=0.
OTIR              Esegui una OTI e ripeti fino a che B=0.
OUT   (C),r       Carica un output dalla porta (C) nel registro r.
OUT   (n),A       Carica un output dalla porta (n) nell'accumulatore.
OUTD              Carica un output dalla porta (C) in (HL), decrementa HL e B.
OUTI              Carica un output dalla porta (C) in (HL), incrementa HL, decrementa B.
POP   IX          Carica in IX la cima dello stack.
POP   IY          Carica in IY la cima dello stack.
POP   qq          Carica nella coppia di registri qq la cima dello stack.
PUSH  IX          Carica IX nello stack.
PUSH  IY          Carica IY nello stack.
PUSH  qq          Carica la coppia di registri qq nello stack.
RES   b,m         Resetta il bit b dell'operando m.
RET               Ritorna dalla subroutine.
RET   cc          Ritorna dalla subroutine se la condizione cc è verificata.
RETI              Ritorna dall'interrupt.
RETN              Ritorna dall'interrupt non mascherabile.
RL    m           Rotazione a sinistra con riporto all'operando m.
RLA               Esegui una rotazione a sinistra con riporto all'accumulatore.
RLC   (HL)        Rotazione della locazione (HL) a sinistra.
RLC   (IX+d)      Rotazione della locazione (IX+d) a sinistra.
RLC   (IY+d)      Rotazione della locazione (IY+d) a sinistra.
RLC   r           Rotazione del registro r a sinistra.
RLCA              Rotazione dell'accumulatore a sinistra.
RLD               Rotazione a sinistra tra l'accumulatore e  (HL).
RR    m           Rotazione a destra con riporto dell'operando m.
RRA               Rotazione a destra con riporto dell'accumulatore.
RRC   m           Rotazione a destra dell'operando m.
RRCA              Rotazione a destra dell'accumulatore.
RRD               Rotazione a destra tra l'accumulatore e (HL).
RST   p           Riparti dalla locazione p.
SBC   A,s         Sottrai con riporto l'operando s dall'accumulatore.
SBC   HL,ss       Sottrai con riporto la coppia dei registri ss da HL.
SCF               Setta il flag di carry (C=1).
SET   b,(HL)      Setta il bit b della locazione (HL).
SET   b,(IX+d)    Setta il bit b della locazione (IX+d).
SET   b,(IY+d)    Setta il bit b della locazione (IY+d).
SET   b,R         Setta il bit b del registro r.
SLA   m           Shift aritmetico a sinistra dell'operando m.
SRA   m           Shift aritmetico a destra dell'operando m.
SRL   m           Shift logico a destra dell'operando m.
SUB   s           Sottrai l'operando s dall'accumulatore.
XOR   s           Esegui l'operazione logica OR tra l'operando s e l'accumulatore.

 

 


--[CAPITOLO XII - GameBoy]--

Come ho spiegato all'inizio, il GameBoy si basa su un processore che è molto simile allo Z80, ma non identico. Ci sono alcune istruzioni assembly che sono state aggiunte, altre che sono state modificate e altre ancora che sono state rimosse.
Tenendo in considerazione gli opcode dello Z80, che sono stati spiegati sopra...
Sono state aggiunte le seguenti istruzioni:
  ADD  SP,nn             ;nn = signed byte
  LDI  (HL),A            ;Scrivi A in (HL) e incrementa HL
  LDD  (HL),A            ;Scrivi A in (HL) e decrementa HL
  LDI  A,(HL)            ;Scrivi (HL) in A e incrementa HL
  LDD  A,(HL)            ;Scrivi (HL) in A e decrementa HL
  LD  A,($FF00+nn)
  LD  A,($FF00+C)
  LD  ($FF00+nn),A
  LD  ($FF00+C),A
  LD  (nnnn),SP
  LD  HL,(SP+nn)         ;nn = signed byte
  STOP                   ;?
  SWAP r                 ;Ruota il registro r di 4 bit

Queste istruzioni invece sono state rimosse:
  Tutte le istruzioni che usano i registri IX o IY.
  Tutte le istruzioni IN/OUT.
  Tutte le istruzioni di scambio.
  Tutte le istruzioni prefissate da ED (tranne il RETI riprogettato).
  Tutti i jumps/calls/rets condizionati da parity/overflow e dal flag del segno sign flag.

Queste invece hanno degli opcode differenti:
  LD  A,[nnnn]
  LD  [nnnn],A
  RETI


Le modifiche che potrebbero interessarci dovrebbero essere solo queste. 

 

 


--[CAPITOLO Extra - L'Header delle rom per GameBoy]--

Bene bene. Questo è uno dei "capitoli extra" di questa guida. Non c'entra tanto con l'assembly, ma visto che lo so ci tengo a spiegarvelo anche a voi. Cominciamo subito. Vi dico subito che molte di queste informazioni sono ricavabili tramite il programma HebeGB, ma vi siete mai chiesti come ricavi molte di queste? Io sì!
L'header di una rom per gb/gbc (se è sprovvista dello SmartCart header) comincia sempre all'offset 0x100.

0x100-103
L'entry-point della rom. In pratica è la prima istruzione di codice della rom che viene eseguita. Solitamente si tratta di una NOP e di un JUMP

0x104-133
Lo Scrolling del logo Nintendo:
CE ED 66 66 CC 0D 00 0B 03 73 00 83 00 0C 00 0D
00 08 11 1F 88 89 00 0E DC CC 6E E6 DD DD D9 99
BB BB 67 63 6E 0E EC CC DD DC 99 9F BB B9 33 3E
( Se viene cambiato la rom non partirà!!! )

0x134-142
Sono riservati al titolo della rom (in maiuscolo). I byte riservati al titolo, non usati, vanno riempiti con dei 0x00.

0x143
Indica invece se la rom è compatibile con il GameBoy Color (0x80), se gli è dedicata (0xC0) o se è incompatibile (altro byte).

0x144-145
Servono per identificare la "New Licensee". Per identificarla si deve far riferimento alla tabella sotto e ad una regola... avete presente che un byte in esadecimale è formato da 2 cifre? Bene... prendete per ogni byte la cifra a destra. In questo modo se avete 40 e D1, otterrete 01 che corrisponde al codice "Nintendo". Di solito sono 0x00 se il byte all'indirizzo 0x14B è diverso da 0x33. Eccovi la tabella.
00- none                     01- nintendo           08- capcom
13- electronic arts          18- hudsonsoft         19- b-ai
20- kss                      22- pow                24- pcm complete
25- san-x                    28- kemco japan        29- seta
30- viacom                   31- nintendo           32- bandia
33- ocean/acclaim            34- konami             35- hector
37- taito                    38- hudson             39- banpresto
41- ubi soft                 42- atlus              44- malibu
46- angel                    47- pullet-proof       49- irem
50- absolute                 51- acclaim            52- activision
53- american sammy           54- konami             55- hi tech entertainment
56- ljn                      57- matchbox           58- mattel
59- milton bradley           60- titus              61- virgin
64- lucasarts                67- ocean              69- electronic arts
70- infogrames               71- interplay          72- broderbund
73- sculptured               75- sci                78- t*hq
79- accolade                 80- misawa             83- lozc
86- tokuma shoten i*         87- tsukuda ori*       91- chun soft
92- video system             93- ocean/acclaim      95- varie
96- yonezawa/s'pal           97- kaneko             99- pack in soft


0x146
Indica se la rom è compatibile (0x03) o meno (0x00) con il SuperGameBoy.

0x147
Specifica il tipo di cartuccia secondo questa tabella.
00- ROM                      01- MBC1                02- MBC1+RAM
03- MBC1+RAM+BATTERY         05- MBC2                06- MBC2+BATTERY
08- ROM+RAM                  09- ROM+RAM+BATTERY     0B- MMM01
0C- MMM01+RAM                0D- MMM01+RAM+BATTERY   0F- MBC3+TIMER+BATTERY
10- MBC3+TIMER+RAM+BATTERY   11- MBC3                12- MBC3+RAM
13- MBC3+RAM+BATTERY         15- MBC4                16- MBC4+RAM
17- MBC4+RAM+BATTERY         19- MBC5                1A- MBC5+RAM
1B- MBC5+RAM+BATTERY         1C- MBC5+RUMBLE         1D- MBC5+RUMBLE+RAM
1E- MBC5+RUMBLE+RAM+BATTERY  FC- POCKET CAMERA       FD- Bandai TAMA5
FE- HuC3                     FF- HuC1+RAM+BATTERY
*** Il Bandai TAMA5 è stato progettato specificatamente per Tamagotchi. ***

0x148
Quindi viene specificata la dimensione della rom...
00- 32k         01- 64k         02- 128k        03- 256k        04- 512k
05- 1024k       06- 2048k       07- 4096k


0x149
...la dimensione della memoria ram per i salvataggi
00- 0k          01- 2k          02- 8k          03- 32k


0x14A
Identifica la nazionalità della cartuccia. 0x00 equivale a Giappone e 0x01 a "Non Giappone" (USA. Europa e Australia).

0x14B
In seguito abbiamo un altro byte... questo identifica il dato "licensee"
La rom non sarà compatibile col Super GameBoy se è diverso da 0x33.
00- none               01- nintendo           08- capcom
09- hot-b              0A- jaleco             0B- coconuts
0C- elite systems      13- electronic arts    18- hudsonsoft
19- itc entertainment  1A- yanoman            1D- clary
1F- virgin             24- pcm complete       25- san-x
28- kotobuki systems   29- seta               30- infogrames
31- nintendo           32- bandai             33- "vedi sopra"
34- konami             35- hector             38- capcom
39- banpresto          3C- *entertainment i   3E- gremlin
41- ubi soft           42- atlus              44- malibu
46- angel              47- spectrum holoby    49- irem
4A- virgin             4D- malibu             4F- u.s. gold
50- absolute           51- acclaim            52- activision
53- american sammy     54- gametek            55- park place
56- ljn                57- matchbox           59- milton bradley
5A- mindscape          5B- romstar            5C- naxat soft
5D- tradewest          60- titus              61- virgin
67- ocean              69- electronic arts    6E- elite systems
6F- electro brain      70- infogrames         71- interplay
72- broderbund         73- sculptered soft    75- the sales curve
78- t*hq               79- accolade           7A- triffix entertainment
7C- microprose         7F- kemco              80- misawa entertainment
83- lozc               86- *tokuma shoten i   8B- bullet-proof software
8C- vic tokai          8E- ape                8F- i'max
91- chun soft          92- video system       93- tsuburava
95- varie              96- yonezawa/s'pal     97- kaneko
99- arc                9A- nihon bussan       9B- tecmo
9C- imagineer          9D- banpresto          9F- nova
A1- hori electric      A2- bandai             A4- konami
A6- kawada             A7- takara             A9- technos japan
AA- broderbund         AC- toei animation     AD- toho
AF- namco              B0- acclaim            B1- ascii or nexoft
B2- bandai             B4- enix               B6- hal
B7- snk                B9- pony canyon        BA- *culture brain o
BB- sunsoft            BD- sony imagesoft     BF- sammy
C0- taito              C2- kemco              C3- squaresoft
C4- *tokuma shoten i   C5- data east          C6- tonkin house
C8- koei               C9- ufl                CA- ultra
CB- vap                CC- use                CD- meldac
CE- *pony canyon or    CF- angel              D0- taito
D1- sofel              D2- quest              D3- sigma enterprises
D4- ask kodansha       D6- naxat soft         D7- copya systems
D9- banpresto          DA- tomy               DB- ljn
DD- ncs                DE- human              DF- altron
E0- jaleco             E1- towachiki          E2- uutaka
E3- varie              E5- epoch              E7- athena
E8- asmik              E9- natsume            EA- king records
EB- atlus              EC- epic/sony records  EE- igs
F0- a wave         F3- extreme entertainment  FF- ljn


0x14C
E' la versione (mascherata) della rom.

0x14D
E' per il controllo sull'header (il Complement Check). Se non è corretto la rom non andrà sul GameBoy (ma funzionerà lo stesso sul Super GameBoy.

0x14E-0x14F
Ed infine abbiamo due byte... è il checksum della rom. Si ottiene sommando tutti i byte della rom (tranne i due del checksum) e prendendo i due byte più bassi dal risultato ottenuto (il GameBoy ignora questo valore).

Ok, e con questo concludo il capitolo sull'header delle rom per GameBoy/Color. Non penso ve ne farete molto di queste informazioni (al massimo le potrete usare per fare i fighi in chat) ma se siete come me, con la voglia di sapere sempre cose nuove, potrebbe essersi rivelato interessante. 
Ci sarebbero tante altre cose da dire sulle rom per GameBoy (come si identifica il formato delle cartucce, descrivere la sequenza di accensione, il basso consumo, lo schermo, il sonoro, il timer, le porte seriali, gli interrupt, i registri I/O... cacchio, ho abbastanza informazioni per scrivere un emulatore! :D) ma, dato che questo è già un capitolo extra, penso sia meglio chiudere qui. Se siete interessati a sapere altro basta che mi mandiate un'e-mail (che trovate in fondo alla guida).

 





--[CAPITOLO XIII - Comprendere meglio l'Assembly]--

Uno dei requisiti più ostici per chi vuole imparare l'assembly è una capacità di astrazione (ben superiore a quella richiesta per linguaggi di altro livello) non da poco. Il reverser (colui che fa reversing-engineering, ossia quello che dovreste fare voi) deve fare una conversione da linguaggio assembly a linguaggio naturale, e può non essere facile (specie nel caso di algoritmi complessi) visto che a differenza degli altri linguaggi in asm un'istruzione può avere infiniti utilizzi (insomma, può servire per fare le cose più disparate).
Come fare allora per cercare di impadronirsi di questa capacità di astrazione? Un buon trucco può essere quello di allenarsi a guardare dei sorgenti (in linguaggi evoluti) di programmi e confrontarli con il codice asm del file compilato.
Prenderò come esempio OmakeBMP, un piccolo programmino che ho realizzato in c. Non è nè lungo nè complicato, quindi dovrebbe fare al caso nostro.
Il programma prende in input un file ne restituisce un altro uguale al primo, fatta eccezione per i primi 999 byte che sono maggiori di 0x90 rispetto ai byte originali.
Segue il suo sorgente c commentato...

#include <stdio.h>
#include <string.h>        // Per poter usare strcat()
int main()
{
  char path[255];          // Che file trattare?
  FILE *File1, *File2;     // Puntatori a strutture FILE
  printf("Questo programmino \"decripta\" i file di SENTIMENTAL SHOOTING che\ncomparirebbero nella cartella OMAKE sconfiggendo il SuperBoss dopo un livello.\n\n");
  printf("La criptazione e' veramente stupida... le immagini sono normali BMP\ncon l'unica differenza che il valore dei primi 999 byte e' minore di quello\ngiusto di 0x90. ");
  printf("I file in questione sono quelli con estensione *.pcg .\nPer usare questo programma, occorre che sia nella stessa cartella dei file\n(o volete scrivervi tutto il percorso?).\n\n");
  printf("\nInserisci il nome del file senza l'estensione *.pcg\n");
  scanf("%s", path);
  strcat(path,".pcg");     // Aggiunge automaticamente l'estensione PCG
  if ((File1 = fopen(path, "rb")) == NULL)      // Controllo su apertura file
    printf("\nImpossibile aprire il file");
  else
  {
    printf("\nFile aperto correttamente.\nElaborazione in corso...");
    int c;
    strcat(path,".bmp");         // Aggiunge l'estensione BMP
    File2 = fopen(path, "wb");   // Apre il File2 (scrittura-binaria)
/* 
Questo ciclo l'ho voluto fare in una maniera un po' "particolare
per sperimentare:
Un while in questo modo genera cicli infiniti, ma è possibile
interromperli con un "break"
Questo break entra in azione dopo che è stato letto l'ultimo
byte del File Sorgente (File1)
Sarebbe stata la stessa cosa scrivere
while (!feof(File1)){}
*/
    while (1) 
    {
      if (feof(File1)) break;    // Legge tutti i byte
      c = fgetc(File1);          // Mette in c il byte letto
      if (ftell(File2) < 1000)   // Controlla se il byte è criptato
        c+=0x90;                 // Se sì, somma al byte il valore 90h
      fputc(c, File2);           // Quindi lo scrive in File2
    }
    fclose(File1);               // Chiude tutto
    fclose(File2);

  }
  return 0;                      // E il programma termina
}


Beh, mi sembra chiaro, no?
Ora diamo un'occhiata al codice asm del programma compilato (vado subito nella parte interessante, tralasciando le librerie e tutte le altre cose inserite dal compilatore).
Cercate di seguire i miei commenti e capire il perchè delle varie istruzioni assembly.

.text:0040153E ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
.text:0040153E 
.text:0040153E ; Attributes: bp-based frame
.text:0040153E 
.text:0040153E sub_40153E proc near ; CODE XREF: sub_401080+EBp
.text:0040153E 
.text:0040153E var_118 = dword ptr -118h
.text:0040153E var_114 = dword ptr -114h
.text:0040153E var_110 = dword ptr -110h
.text:0040153E var_10C = dword ptr -10Ch
.text:0040153E var_108 = byte ptr -108h
.text:0040153E 
.text:0040153E push ebp
.text:0040153F mov ebp, esp
.text:00401541 sub esp, 118h
.text:00401547 and esp, 0FFFFFFF0h
.text:0040154A mov eax, 0
.text:0040154F mov [ebp+var_118], eax
.text:00401555 mov eax, [ebp+var_118]
.text:0040155B call sub_402D90
.text:00401560 call sub_4017D0
.text:00401565 sub esp, 0Ch
.text:00401568 push offset aQuestoProgramm
; "Questo programmino \"decripta\" i file di"...
.text:0040156D call printf  ; Mette la stringa (parametro) nello stack e poi la passa alla funzione prinf
.text:00401572 add esp, 10h ; \
.text:00401575 sub esp, 0Ch ; \=> e già qui si vede come sarebbe possibile ottimizzare il codice ;)
.text:00401578 push offset aLaCriptazioneE ; "La criptazione e' veramente stupida... "...
.text:0040157D call printf ; Come sopra, va nello stack per poi diventare parametro della funzione printf
.text:00401582 add esp, 10h
.text:00401585 sub esp, 0Ch
.text:00401588 push offset aIFileInQuestio
; "I file in questione sono quelli con est"...
.text:0040158D call printf
.text:00401592 add esp, 10h
.text:00401595 sub esp, 0Ch
.text:00401598 push offset aInserisciIlNom
; "\nInserisci il nome del file senza l'est"...
.text:0040159D call printf
.text:004015A2 add esp, 10h
.text:004015A5 sub esp, 8
.text:004015A8 lea eax, [ebp+var_108]
; [1] ; vabbè, abbiamo finito a stampare i messaggini stupidi
.text:004015AE push eax ; cominciamo a leggere il nome del file da elaborare.
.text:004015AF push offset aS ; "%s"
.text:004015B4 call scanf
; ma chissà cosa farà mai "scanf"... questi nomi strani :)
.text:004015B9 add esp, 10h
.text:004015BC sub esp, 8
.text:004015BF push offset a_pcg
; ".pcg" ; [2]
.text:004015C4 lea eax, [ebp+var_108] ; [1]
.text:004015CA push eax
.text:004015CB call strcat
; Beh, si tratta della strcat che prende i parametri [1] e [2]

Ricordarsi sempre che i listati asm dal c hanno sempre prima gli ultimi parametri... e infine vogliono i primi (come qui... prima c'è il [2] e poi c'è [1]). Invece se disassemblate un prog in Pascal vedrete che è l'esatto contrario. Vabbè... andiamo avanti.

.text:004015D0 add esp, 10h
.text:004015D3 sub esp, 8
.text:004015D6 push offset aRb
; "rb" ; modalità
.text:004015DB lea eax, [ebp+var_108] ; [1] : la stringa letta con scanf
.text:004015E1 push eax 
.text:004015E2 call fopen
; Apriamo il nostro bel file...
.text:004015E7 add esp, 10h
.text:004015EA mov [ebp+var_10C], eax
; Memorizza il risultato (eax) su File1 => File1 = fopen(...)
.text:004015F0 cmp [ebp+var_10C], 0 ; IF = NULL
.text:004015F7 jnz short loc_40160E
; Se no, vai lì... ELSE prosegui
.text:004015F9 sub esp, 0Ch
.text:004015FC push offset aImpossibileApr
; "\nImpossibile aprire il file"
.text:00401601 call printf
.text:00401606 add esp, 10h
.text:00401609 jmp loc_4016DF
; e finisce tutto
.text:0040160E ; ---------------------------------------------------------------------------
.text:0040160E 
.text:0040160E loc_40160E: ; CODE XREF: sub_40153E+B9j
.text:0040160E sub esp, 0Ch
; LOL siamo ancora alla 28° riga... :D
.text:00401611 push offset aFileApertoCorr ; "\nFile aperto correttamente.\nElaborazion"...
.text:00401616 call printf
.text:0040161B add esp, 10h
.text:0040161E sub esp, 8
.text:00401621 push offset a_bmp
; ".bmp"
.text:00401626 lea eax, [ebp+var_108] ; [1]
.text:0040162C push eax
.text:0040162D call strcat
; Solita storia del primo strcat
.text:00401632 add esp, 10h
.text:00401635 sub esp, 8
.text:00401638 push offset aWb
; "wb" ; modalità
.text:0040163D lea eax, [ebp+var_108] ; [1] sempre quella... la nostra variabile Path
.text:00401643 push eax
.text:00401644 call fopen
; e apriamo il file...
.text:00401649 add esp, 10h
.text:0040164C mov [ebp+var_110], eax
; invece qua si tratta di File2 = fopen(...)
.text:00401652 
.text:00401652 loc_401652: ; CODE XREF: sub_40153E+17Dj
.text:00401652 sub esp, 0Ch
; e da qui si nota che parte un ciclo...
.text:00401655 push [ebp+var_10C] ; [ebp+var_10C] è File1
.text:0040165B call feof ; feof non sto a spiegarvelo :P 
.text:00401660 add esp, 10h
.text:00401663 test eax, eax
; condizione del ciclo... se feof ha restituito 0...
.text:00401665 jz short loc_401669
; ...esegui ciclo,
.text:00401667 jmp short loc_4016BD ; altrimenti finisci
.text:00401669 ; ---------------------------------------------------------------------------
.text:00401669 
.text:00401669 loc_401669: ; CODE XREF: sub_40153E+127j
.text:00401669 sub esp, 0Ch
.text:0040166C push [ebp+var_10C]
.text:00401672 call fgetc ;
prende da File1 un carattere
.text:00401677 add esp, 10h
.text:0040167A mov [ebp+var_114], eax
; in pratica si tratta della nostra variabile c
.text:00401680 sub esp, 0Ch
.text:00401683 push [ebp+var_110]
; parametro per ftell: File1
.text:00401689 call ftell ; chiama ftell(File1)
.text:0040168E add esp, 10h
.text:00401691 cmp eax, 3E7h
; com'è eax rispetto al numero 999?
.text:00401696 jg short loc_4016A4 ; Se è più grande vuol dire che i byte non sono criptati
.text:00401698 lea eax, [ebp+var_114]
.text:0040169E add dword ptr [eax], 90h
; Se non è così aggiungi a c, 90h
.text:004016A4 
.text:004016A4 loc_4016A4: ; CODE XREF: sub_40153E+158j
.text:004016A4 sub esp, 8
; Copia il carattere da c a File2
.text:004016A7 push [ebp+var_110] ; File2
.text:004016AD push [ebp+var_114]
; c
.text:004016B3 call fputc ; fputc(c,File2)
.text:004016B8 add esp, 10h
.text:004016BB jmp short loc_401652
; ritorna al ciclo
.text:004016BD ; ---------------------------------------------------------------------------
.text:004016BD 
.text:004016BD loc_4016BD: ; CODE XREF: sub_40153E+129j
.text:004016BD sub esp, 0Ch
.text:004016C0 push [ebp+var_10C]
.text:004016C6 call fclose
; Chiude File1
.text:004016CB add esp, 10h
.text:004016CE sub esp, 0Ch
.text:004016D1 push [ebp+var_110] 
.text:004016D7 call fclose
; Chiude File2
.text:004016DC add esp, 10h
.text:004016DF 
.text:004016DF loc_4016DF: ; CODE XREF: sub_40153E+CBj
.text:004016DF mov eax, 0
.text:004016E4 leave
.text:004016E5 retn
.text:004016E5 sub_40153E endp
; FINE :)

Come vedete il codice è facilmente riconducibile al listato in c... vi sembra ancora così incomprensibile? ;)
Sapere come "ragiona" un compilatore (cioè come trasforma da sorgente a binario un file) è utile per decifrare poi più facilmente le istruzioni asm. Più pratica fate e col passare del tempo la conversione da codice asm al linguaggio naturale vi sarà sempre più immediata.

Però voi potreste dire... "Sì Syxtem, però qua ci abbiamo sottomano il sorgente! E' ovvio che è più facile capirlo!"
E allora vi propongo un altro esempio... questa volta il sorgente non c'è, lo dobbiamo ricostruire noi dalle istruzioni assembly.
Cerchiamo di seguire questa parte di programma. Si tratta di un piccolo programmino gratuito (un giochino) il cui scopo è riuscire a trovare un seriale. Per quanto sia perfettamente legale il programma, non lo sfrutteremo per carpirne il seriale (infatti pasto solo il codice relativo alla manipolazione del seriale, come si arrivi lì non è necessario saperlo per quello che dobbiamo fare noi), ma lo utilizzeremo come esercizio per imparare a capire il codice asm e per esercitarci a districarci tra le varie istruzioni.

.text:004031AD ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦

.text:004031AD

.text:004031AD ; Attributes: bp-based frame

.text:004031AD

.text:004031AD sub_4031AD      proc near               ; CODE XREF: sub_4021F0+Fp

.text:004031AD                                         ; sub_40266E+BCp ...

.text:004031AD

.text:004031AD var_4           = dword ptr -4

.text:004031AD arg_0           = dword ptr  8

.text:004031AD

.text:004031AD                 push    ebp

.text:004031AE                 mov     ebp, esp

.text:004031B0                 sub     esp, 4

.text:004031B3                 push    ebx

.text:004031B4                 push    esi

.text:004031B5                 push    edi

.text:004031B6                 mov     eax, [ebp+arg_0]   ; Eax = questo è il seriale inserito, prendete per buona la cosa e non chiedetemi come faccio a dirlo... vi ricordo che ho omesso del codice precedente.

.text:004031B9                 xor     ecx, ecx           ; Xor di un registro con lo stesso --> Registro = 0 (a volte è fatto per inizializzazione)

.text:004031BB                 mov     cl, [eax+7]        ; Se il seriale è più lungo di 7 caratteri esci.

.text:004031BE                 test    ecx, ecx           ; Se ecx = 0 allora si continua, altrimenti "Seriale non corretto"
.text:004031C0                 jz      loc_4031CD  

Prende la 7+1 lettera (la prima lettera sarebbe [eax] ), e vede se il suo byte è 0x00. Quando c'è un test X,X vuol dire che si confronta con 0 e se è così allora si mette il flag Z a 1, altrimenti lo si mette a 0. Quindi con queste 2 operazioni il programma guarda se il seriale inserito è lungo 7 caratteri, visto che il byte di fine stringa è 0x00

.text:004031C6                 xor     eax, eax        ; Eax = 0 (per il boolean sul seriale. Se eax=0 il seriale non è corretto, altrimenti sì.

.text:004031C8                 jmp     loc_403312

.text:004031CD ; ---------------------------------------------------------------------------

.text:004031CD

.text:004031CD loc_4031CD:                             ; CODE XREF: sub_4031AD+13j

.text:004031CD                 mov     [ebp+var_4], 0     ; I = 0 (variabile settata a 0... la chiamiamo I per convenienza)

.text:004031D4                 jmp     loc_4031DC

.text:004031D9 ; ---------------------------------------------------------------------------

 

.text:004031D9

.text:004031D9 loc_4031D9:                             ; CODE XREF: sub_4031AD+5Fj

.text:004031D9                 inc     [ebp+var_4]         ; I++

.text:004031DC

.text:004031DC loc_4031DC:                             ; CODE XREF: sub_4031AD+27j

.text:004031DC                 cmp     [ebp+var_4], 7      ; I >= 7 ?

.text:004031E0                 jge     loc_403211          ; Se sì salta, altrimenti continua

.text:004031E6                 mov     eax, [ebp+var_4]    ; eax = I 

.text:004031E9                 mov     ecx, [ebp+arg_0]    ; ecx = seriale

.text:004031EC                 mov     al, [eax+ecx]       ; al = seriale[I]  
Da questo capiamo che eax = Lettera del Seriale perchè quando eax = 0 si ha che al = [ecx] (prima lettera), quando eax = 1, al = [ecx+1] (seconda lettera)
Al diventa solo una lettera perchè, come vi ho spiegato precedentemente, Al ha la dimensione di 1 byte.

.text:004031EF                 push    eax                 ; Passiamo allo stack la lettera del seriale

.text:004031F0                 call    sub_403317          ; E la elaboriamo in questa Routine

.text:004031F5                 add     esp, 4

.text:004031F8                 xor     ecx, ecx  

.text:004031FA                 mov     cl, al             ; cl = Lettera modificata del seriale

.text:004031FC                 cmp     ecx, 24h           ; Se è una lettera o un numero o “[“ continua, (il perchè ho commentato così lo vedrete dopo)

.text:004031FF                 jle     loc_40320C         ; altrimenti "seriale non valido"

.text:00403205                 xor     eax, eax

.text:00403207                 jmp     loc_403312

.text:0040320C ; ---------------------------------------------------------------------------

.text:0040320C

.text:0040320C loc_40320C:                             ; CODE XREF: sub_4031AD+52j

.text:0040320C                 jmp     loc_4031D9

.text:0040320C ; ---------------------------------------------------------------------------

.text:00403312 loc_403312:                             ; CODE XREF: sub_4031AD+1Bj

.text:00403312                                         ; sub_4031AD+5Aj ...

.text:00403312                 pop     edi

.text:00403313                 pop     esi

.text:00403314                 pop     ebx

.text:00403315                 leave

.text:00403316                 retn

.text:00403316 sub_4031AD      endp                       ; Esci

 

.text:00403317 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦

.text:00403317

.text:00403317 ; Attributes: bp-based frame

.text:00403317

.text:00403317 sub_403317      proc near               ; CODE XREF: sub_4031AD+43p

.text:00403317                                         ; sub_4031AD+6Bp ...

.text:00403317

.text:00403317 arg_0           = byte ptr  8

.text:00403317

.text:00403317                 push    ebp

.text:00403318                 mov     ebp, esp

.text:0040331A                 push    ebx

.text:0040331B                 push    esi

.text:0040331C                 push    edi

.text:0040331D                 xor     eax, eax
.text:0040331F                 mov     al, [ebp+arg_0]    ; al = Seriale[I]  

.text:00403322                 cmp     eax, 61h           ; A 

.text:00403325                 jl      loc_403336         ;  |

.text:0040332B                 xor     eax, eax           ;  | Se è minuscola converte in maiuscolo,

.text:0040332D                 mov     al, [ebp+arg_0]    ;  | altrimenti continua

.text:00403330                 sub     eax, 20h           ;  |

.text:00403333                 mov     [ebp+arg_0], al    ; V  
Infatti, se la lettera ha un codice ASCII maggiore o uguale a 0x61 (a) vuol dire che probabilmente si tratta di una lettera è minuscola. Quindi la converte in maiuscola (0x61 - 0x20 = 0x41 (A) ). Quindi continua.

.text:00403336

.text:00403336 loc_403336:                             ; CODE XREF: sub_403317+Ej

.text:00403336                 xor     eax, eax           ; A

.text:00403338                 mov     al, [ebp+arg_0]    ;  |

.text:0040333B                 cmp     eax, 41h           ;  | Se è >= “A” (leggasi "se è una lettera"), sottrai 7 al codice

.text:0040333E                 jl      loc_40334F         ;  | della lettera.

.text:00403344                 xor     eax, eax           ;  | altrimenti continua

.text:00403346                 mov     al, [ebp+arg_0]    ;  |

.text:00403349                 sub     eax, 7             ;  |

.text:0040334C                 mov     [ebp+arg_0], al    ; V

.text:0040334F

.text:0040334F loc_40334F:                             ; CODE XREF: sub_403317+27j

.text:0040334F                 xor     eax, eax         

.text:00403351                 mov     al, [ebp+arg_0]

.text:00403354                 sub     eax, 30h           ; Sottrai comunque 30h al codice della lettera.

.text:00403357                 jmp     $+5

.text:0040335C                 pop     edi

.text:0040335D                 pop     esi

.text:0040335E                 pop     ebx

.text:0040335F                 leave

.text:00403360                 retn                       ; eax contiene il codice della lettera

.text:00403360 sub_403317      endp                       ; modificata.

.text:00403211 ; ---------------------------------------------------------------------------

.text:00403211  
A questo punto può essere comodo farsi una tabellina per vedere come il codice delle varie lettere venga modificato.
Scrivo solo quelle che possono essere utili (come vedrete dal loro codice le altre farebbero scattare immediatamente un "seriale non valido")

TABELLA DELLE LETTERE MODIFICATE:

0: 0

1: 1

2: 2

3: 3

4: 4

5: 5

6: 6

7: 7

8: 8

9: 9  

 

:: A  A

;: B  |

<: C  | Escluse

=: D  | xkè

>: E  | doppi.

?: F  |

@: 10 V  
A: A

B: B

C: C

 

D: D

E: E
F: F
G: 10
H: 11
I: 12
J: 13
K: 14
L: 15
M: 16

N: 17
O: 18
P: 19
Q: 1A

R: 1B
S: 1C
T: 1D
U: 1E
V: 1F
W: 20

X: 21
Y: 22
Z: 23
[: 24  

 

 

Dal di qua si capisce che:

: = A

; = B

< = C

= = D

> = E

? = F

@ = G


.text:00403211 loc_403211:                             ; CODE XREF: sub_4031AD+33j

.text:00403211                 mov     eax, [ebp+arg_0]   ; eax = seriale

.text:00403214                 mov     al, [eax+5]        ; al = 6° lettera del seriale

.text:00403217                 push    eax 

.text:00403218                 call    sub_403317

.text:0040321D                 add     esp, 4

.text:00403220                 xor     ebx, ebx

.text:00403222                 mov     bl, al             ; bl = 6° lettera modificata

.text:00403224                 mov     eax, [ebp+arg_0]

.text:00403227                 mov     al, [eax+2]        ; al = 3° lettera del seriale

.text:0040322A                 push    eax

.text:0040322B                 call    sub_403317

.text:00403230                 add     esp, 4

.text:00403233                 xor     ecx, ecx

.text:00403235                 mov     cl, al             ; cl = 3° lettera modificata

.text:00403237                 mov     esi, 24h

.text:0040323C                 lea     eax, [ecx+ebx*2+1Ch]

.text:00403240                 cdq

.text:00403241                 idiv    esi                ; Guardatevi come funzionano queste istruzioni nei capitoli precedenti.

.text:00403243                 mov     ebx, edx           ; ebx = eax mod 24h  [6|3]

.text:00403245                 mov     eax, [ebp+arg_0]

.text:00403248                 mov     al, [eax]          ; al = 1° lettera del seriale

.text:0040324A                 push    eax

.text:0040324B                 call    sub_403317

.text:00403250                 add     esp, 4

.text:00403253                 xor     ecx, ecx

.text:00403255                 mov     cl, al             ; cl = 1° lettera modificata

.text:00403257                 cmp     ebx, ecx           ; se ebx (mod[6|3]) non è = alla prima

.text:00403259                 jnz     loc_40330B         ; lettera modificata, vai a "Seriale non valido"

.text:0040325F                 mov     eax, [ebp+arg_0]

.text:00403262                 mov     al, [eax+4]        ; al = 5° lettera del seriale

.text:00403265                 push    eax

.text:00403266                 call    sub_403317

.text:0040326B                 add     esp, 4

.text:0040326E                 xor     ebx, ebx

.text:00403270                 mov     bl, al             ; bl = 5° lettera modificata

.text:00403272                 mov     eax, [ebp+arg_0]

.text:00403275                 mov     al, [eax+1]        ; al = 2° lettera del seriale

.text:00403278                 push    eax

.text:00403279                 call    sub_403317

.text:0040327E                 add     esp, 4

.text:00403281                 xor     ecx, ecx

.text:00403283                 mov     cl, al             ; cl = 2° lettera modificata

.text:00403285                 mov     esi, 24h

.text:0040328A                 lea     eax, [ecx+ebx*2+1Ch]

.text:0040328E                 cdq

.text:0040328F                 idiv    esi

.text:00403291                 mov     ebx, edx           ; ebx = eax mod 24h [5|2]

.text:00403293                 mov     eax, [ebp+arg_0]

.text:00403296                 mov     al, [eax+6]        ; al = 7° lettera del seriale

.text:00403299                 push    eax

.text:0040329A                 call    sub_403317

.text:0040329F                 add     esp, 4

.text:004032A2                 xor     ecx, ecx

.text:004032A4                 mov     cl, al             ; cl = 7° lettera modificata

.text:004032A6                 cmp     ebx, ecx           ; se ebx (mod[5|2]) non è = alla settima

.text:004032A8                 jnz     loc_40330B         ; lettera modificata, vai a "Seriale non valido"

.text:004032AE                 mov     eax, [ebp+arg_0]

.text:004032B1                 mov     al, [eax+6]        ; al = 7° lettera del seriale

.text:004032B4                 push    eax

.text:004032B5                 call    sub_403317

.text:004032BA                 add     esp, 4

.text:004032BD                 xor     ebx, ebx

.text:004032BF                 mov     bl, al             ; bl = 7° lettera modificata

.text:004032C1                 mov     eax, [ebp+arg_0]

.text:004032C4                 mov     al, [eax]          ; al = 1° lettera del seriale

.text:004032C6                 push    eax

.text:004032C7                 call    sub_403317

.text:004032CC                 add     esp, 4

.text:004032CF                 xor     ecx, ecx

.text:004032D1                 mov     cl, al             ; cl = 1° lettera modificata

.text:004032D3                 mov     esi, 24h

.text:004032D8                 lea     eax, [ecx+ebx*2+1Ch]

.text:004032DC                 cdq

.text:004032DD                 idiv    esi

.text:004032DF                 mov     ebx, edx           ; ebx = eax mod 24h [7|1]

.text:004032E1                 mov     eax, [ebp+arg_0]

.text:004032E4                 mov     al, [eax+3]        ; al = 4° lettera del seriale

.text:004032E7                 push    eax

.text:004032E8                 call    sub_403317

.text:004032ED                 add     esp, 4

.text:004032F0                 xor     ecx, ecx

.text:004032F2                 mov     cl, al             ; cl = 4° lettera modificata

.text:004032F4                 cmp     ebx, ecx           ; se ebx (mod[5|2] non è = alla quarta

.text:004032F6                 jnz     loc_40330B         ; lettera modificata, vai a "Seriale non valido"

.text:004032FC                 mov     eax, 1             ; altrimenti setta il flag a 1:

.text:00403301                 jmp     loc_403312         ; seriale corretto.

.text:00403306 ; ---------------------------------------------------------------------------

.text:00403306                 jmp     loc_403312

.text:0040330B ; ---------------------------------------------------------------------------

.text:0040330B

.text:0040330B loc_40330B:                             ; CODE XREF: sub_4031AD+ACj

.text:0040330B                                         ; sub_4031AD+FBj ...

.text:0040330B                 xor     eax, eax

.text:0040330D                 jmp     $+5

.text:00403312

.text:00403312 loc_403312:                             ; CODE XREF: sub_4031AD+1Bj

.text:00403312                                         ; sub_4031AD+5Aj ...

.text:00403312                 pop     edi

.text:00403313                 pop     esi

.text:00403314                 pop     ebx

.text:00403315                 leave

.text:00403316                 retn

.text:00403316 sub_4031AD      endp                       ; Esci :)

 

Bene, abbiamo reversato un controllo di un seriale. Vi pare ancora così difficile? Sì? Beh, il trucco sta nell'esercitarsi con buona costanza. Dovete cercare di far vostri i concetti spiegati e vedere codice di alto livello su quello asm (azz... sembra quasi un discorso alla Matrix. Io ci vedo brunette... :D)

 


--[CAPITOLO XIV - Si traduce]--

Finalmente siamo arrivati ad un bel esempio pratico in cui l'assembly è fondamentale per tradurre una certa cosa.
Prenderò come esempio Kotobuki(PC). Ho commentato il codice e messo qualche etichetta per poter farvi capire meglio.
Dobbiamo tradurre il menù iniziale, ma abbiamo un problema. Dobbiamo tradurre "HELP", ma non è possibile sostituirlo con "AIUTO". Qualcuno di voi potrebbe dire "ma usa i puntatori!", ma vi farò vedere perchè in questo caso i puntatori non portino a niente.
Beh, intanto direi di andare a vedere dove si trova la frase da editare. Aprite HexWorkShop e ditegli di cercare "HELP". Lo troverete all'offset 0x45344.
Ora prendiamo il nostro IDA e gli facciamo elaborare l'eseguibile del gioco.
Cercate di capire quali sono le istruzioni alle quali dovete porre attenzione e quali invece non dovete neanche guardare
Dopo che ha finito di disassemblare, andiamo all'offset 0x45344. Ci troveremo qui:

 

.data:00447740 word_447740 dw 4E45h      ; DATA XREF: sub_423860+DEr
.data:00447742 byte_447742 db 0          ; DATA XREF: sub_423860+E9r
.data:00447743 align 4
.data:00447744 dword_447744 dd 504C4548h ; DATA XREF: sub_423860+B5r
.data:00447748 byte_447748 db 0          ; DATA XREF: sub_423860+BAr
.data:00447749 align 4

a questi 4 byte corrisponde la scritta HELP. Il DATA XREF a destra ci avverte che c'è una parte di codice che richiama questo testo ("Help"). Dovrebbe essere quello che serve a noi :\
Digitando N, quando si è su quella istruzione, è possibile chiamare con un altro nome "
dword_447744". Io la chiamerò aHelp.
Clicchiamo poi sopra al collegamento e finiremo qui:

.text:004216EE loc_4216EE: ; CODE XREF: .text:004216B5j
.text:004216EE ; DATA XREF: .text:004217F8o
.text:004216EE lea eax, [esp+10h] ; case 0x5
.text:004216F2 push offset aHelp ; " Help "
.text:004216F7 push eax
.text:004216F8 jmp short loc_421704

Guardiamo però da dove parte la CALL
.text:00423860 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
.text:00423860 sub_423860 proc near ; CODE XREF: sub_4234C0+371p
.text:00423860 ; sub_423AF0+70p ...
.text:00423860 
.text:00423860 var_1C = dword ptr -1Ch
.text:00423860 var_18 = dword ptr -18h
.text:00423860 var_14 = dword ptr -14h
.text:00423860 var_10 = dword ptr -10h
.text:00423860 var_C = word ptr -0Ch
.text:00423860 var_A = byte ptr -0Ah
.text:00423860 arg_0 = dword ptr 4
.text:00423860 
.text:00423860 sub esp, 1Ch
.text:00423863 push ebx
.text:00423864 push ebp
.text:00423865 push esi
.text:00423866 push edi
.text:00423867 push 0
.text:00423869 call sub_411880
.text:0042386E add esp, 4
.text:00423871 xor edi, edi
.text:00423873 xor esi, esi
.text:00423875 mov [esp+2Ch+var_1C], esi
;A questo punto inizia uno switch (ossia il case of per chi usa Pascal, il Select Case per chi usa VB)
;Per ogni case si va a leggere il testo da visualizzare a video (le voci del menù).
.text:00423879 
.text:00423879 loc_423879: ; CODE XREF: sub_423860+1C2j
.text:00423879 cmp edi, 6 ; switch 7 cases 
.text:0042387C ja loc_42399C ; default
.text:00423882 jmp ds:off_423A30[edi*4] ; switch jump
.text:00423889 
; INIZIO
.text:00423889 loc_423889: ; DATA XREF: .text:00423A30o
.text:00423889 mov eax, dword ptr aIniz ; case 0x0
.text:0042388E mov ecx, dword ptr aIa
.text:00423894 mov dx, word_447768
.text:0042389B mov [esp+2Ch+var_14], eax
.text:0042389F mov al, byte_44776A
.text:004238A4 mov [esp+2Ch+var_10], ecx
.text:004238A8 mov [esp+2Ch+var_C], dx
.text:004238AD mov [esp+2Ch+var_A], al
.text:004238B1 jmp loc_42399C ; default
.text:004238B6 ; ---------------------------------------------------------------------------
; CONTINUA
.text:004238B6 
.text:004238B6 loc_4238B6: ; CODE XREF: sub_423860+22j
.text:004238B6 ; DATA XREF: .text:00423A30o
.text:004238B6 mov ecx, aContMenu ; case 0x1
.text:004238BC mov edx, aInuaMenu
.text:004238C2 mov al, byte_44775C
.text:004238C7 mov [esp+2Ch+var_14], ecx
.text:004238CB mov [esp+2Ch+var_10], edx
.text:004238CF mov byte ptr [esp+2Ch+var_C], al
.text:004238D3 jmp loc_42399C ; default
.text:004238D8 ; ---------------------------------------------------------------------------
; CONFIG
.text:004238D8 
.text:004238D8 loc_4238D8: ; CODE XREF: sub_423860+22j
.text:004238D8 ; DATA XREF: .text:00423A30o
.text:004238D8 mov ecx, dword ptr aConfig ; case 0x2
.text:004238DE mov dx, word ptr aConfig+4
.text:004238E5 mov al, byte ptr aConfig+6
.text:004238EA mov [esp+2Ch+var_14], ecx
.text:004238EE mov word ptr [esp+2Ch+var_10], dx
.text:004238F3 mov byte ptr [esp+2Ch+var_10+2], al
.text:004238F7 jmp loc_42399C ; default
.text:004238FC ; ---------------------------------------------------------------------------
;GOHOUBI
.text:004238FC 
.text:004238FC loc_4238FC: ; CODE XREF: sub_423860+22j
.text:004238FC ; DATA XREF: .text:00423A30o
.text:004238FC mov ecx, aGoho ; case 0x3
.text:00423902 mov edx, aUbi
.text:00423908 mov [esp+2Ch+var_14], ecx
.text:0042390C mov [esp+2Ch+var_10], edx
.text:00423910 jmp loc_42399C ; default
.text:00423915 ; ---------------------------------------------------------------------------
; HELP

; Questo è il case che ci interessa... quello che va a prendere "HELP"
.text:00423915 
.text:00423915 loc_423915: ; CODE XREF: sub_423860+22j
.text:00423915 ; DATA XREF: .text:00423A30o
.text:00423915 mov eax, aHelp ; case 0x4
.text:0042391A mov cl, byte ptr dword_447748
.text:00423920 mov [esp+2Ch+var_14], eax
.text:00423924 mov byte ptr [esp+2Ch+var_10], cl
.text:00423928 jmp short loc_42399C ; default
.text:0042392A ; ---------------------------------------------------------------------------
; ESPANDI
.text:0042392A 
.text:0042392A loc_42392A: ; CODE XREF: sub_423860+22j
.text:0042392A ; DATA XREF: .text:00423A30o
.text:0042392A mov eax, dword_44658C ; case 0x5
.text:0042392F test eax, eax
.text:00423931 jnz short loc_42395E
.text:00423933 mov edx, dword ptr aEspa
.text:00423939 mov eax, dword ptr aNdi
.text:0042393E mov cx, word_447740
.text:00423945 mov [esp+2Ch+var_14], edx
.text:00423949 mov dl, byte_447742
.text:0042394F mov [esp+2Ch+var_10], eax
.text:00423953 mov [esp+2Ch+var_C], cx
.text:00423958 mov [esp+2Ch+var_A], dl
.text:0042395C jmp short loc_42399C ; default
.text:0042395E ; ---------------------------------------------------------------------------
; FINESTRA (è lo stesso case, in cui però può cambiare la parola a video...)
.text:0042395E 
.text:0042395E loc_42395E: ; CODE XREF: sub_423860+D1j
.text:0042395E mov eax, dword ptr aFine
.text:00423963 mov ecx, dword ptr aStra
.text:00423969 mov dx, word_447734
.text:00423970 mov [esp+2Ch+var_14], eax
.text:00423974 mov al, byte_447736
.text:00423979 mov [esp+2Ch+var_10], ecx
.text:0042397D mov [esp+2Ch+var_C], dx
.text:00423982 mov [esp+2Ch+var_A], al
.text:00423986 jmp short loc_42399C ; default
.text:00423988 ; ---------------------------------------------------------------------------
; ESCI
.text:00423988 
.text:00423988 loc_423988: ; CODE XREF: sub_423860+22j
.text:00423988 ; DATA XREF: .text:00423A30o
.text:00423988 mov ecx, dword ptr aEsci ; case 0x6
.text:0042398E mov dl, byte_447728
.text:00423994 mov [esp+2Ch+var_14], ecx
.text:00423998 mov byte ptr [esp+2Ch+var_10], dl
; Voce Default (ramo ELSE)
.text:0042399C 
.text:0042399C loc_42399C: ; CODE XREF: sub_423860+1Cj
.text:0042399C ; sub_423860+51j ...
.text:0042399C cmp [esp+2Ch+arg_0], edi ; default
.text:004239A0 jnz short loc_4239AF
.text:004239A2 lea eax, [esp+2Ch+var_14]
.text:004239A6 push eax
.text:004239A7 call sub_409B20
.text:004239AC add esp, 4
.text:004239AF 
;Finiti Case
[...]
.text:004239F1 jmp short loc_423A00
.text:004239F3 ; ---------------------------------------------------------------------------

Ok, sappiamo che dobbiamo guardare proprio lì. Pasto di nuovo per poter spiegare meglio.

  
.text:00423915 loc_423915: ; CODE XREF: sub_423860+22j
  .text:00423915 ; DATA XREF: .text:00423A30o
1 .text:00423915 mov eax, aHelp ; case 0x4           <-- Mette in eax la dword puntata.
2 .text:0042391A mov cl, byte ptr dword_447748       <-- poi c'è bisogno dello 0x00 di fine stringa
3 .text:00423920 mov [esp+2Ch+var_14], eax
4 .text:00423924 mov byte ptr [esp+2Ch+var_10], cl
5 .text:00423928 jmp short loc_42399C ; default
  .text:0042392A ; ---------------------------------------------------------------------------

Adesso dovrebbe esservi chiaro del perchè non possiamo ricorrere a dei semplici puntatori.

Quello che vedete qui è filtrato da IDA... l'istruzione vera e propria sarebbe questa:
mov eax, dword [00447744]
Viene memorizzato nel registro interno eax una dword (4 byte) indirizzata a questo indirizzo [00447744]
Eax ha una capienza massima di 4 byte (cioè di 4 caratteri) e quindi non si possono leggere altre lettere.
Cl invece ha la capienza di un byte, e quindi può solo contenere il byte di fine stringa (0x00 nei software per PC) che deve sempre esserci.
Come possiamo fare?
E' semplice, basta dire al programma di caricare poi un'altra dword in un altro registro di 2 o 4 byte, invece di 1.
Decidiamo quindi di sostituire cl con cx (2 byte). 
In questo modo avremo eax che conterrà "Aiut" e cx che conterrà "o[00]" (la lettera o e il byte 00).
E poi dovremo cambiare il registro cl anche nella quarta riga... sostituendolo con cx.
A dire così sembra facile, ma dobbiamo assicurarci che il numero dei byte modificati sia ESATTAMENTE UGUALE a quello originale, altrimenti rovineremmo tutto.

Utilizziamo il PHOG per vedere di quanti (e di quali byte) è formata l'istruzione che vogliamo sostituire.
Allora... di quanti byte è formata l'istruzione nella seconda riga?
8A0D48774400
Questo invece è l'insieme di byte che forma l'istruzione con cx:
668B0D48774400 Uhm... è più lungo di un byte... cazzo!
Vediamo come sarebbe mettendoci ax al posto di cx... (a volte le istruzioni che coinvolgono il registro A sono più corte :\ )
66A148774400 è lungo uguale!
Però non possiamo usare subito ax così... sovrascriveremmo il contenuto di eax (ricordate che sono lo stesso registro). Quindi usiamo un trucchetto...
Sostituiamo la seconda con la terza riga e ci mettiamo ax al posto di cx... in questo modo il registro A (eax) è stato già salvato sullo stack e possiamo scriverci sopra con ax tranquillamente.

Questi sarebbero i byte delle istruzioni iniziali
A144774400
8A0D48774400
89442418
884C241C
EB72

la seconda riga la sostituiamo con
66A148774400
la quarta sarebbe
8944241C
ok... è della stessa misura dell'originale pure questa! :)

Infine... scambiando la seconda con la terza riga avremo:
A144774400
89442418
66A148774400
8944241C
EB72

e quindi in ASM è
mov eax, aAIUT ; case 0x4
mov [esp+2Ch+var_14], eax
mov ax, word ptr aO[00]
mov word ptr [esp+2Ch+var_10], ax
jmp short loc_42399C ; default


Fine! Prendete l'editor esadecimale, trovate nel programma quella sequenza di byte che compongono le istruzioni che dobbiamo modificare...
Modificate i byte corrispondenti alla seconda, terza e quarta riga di codice. Salvate, incrociate le dita e avviate il prog. :)
Uhm... ma guarda... ora il prog visualizza AIUTO... ganzo! :D
CLAP! CLAP! CLAP! CLAP!


Se il numero di byte per le istruzioni vi viene minore, poco male. Basta aggiungere dei NOP (0x90: 1 byte) fino a che la dimensione ritorna ad essere come l'originale.
Se invece proprio non riuscite a stare nello spazio concessovi, vi conviene spostarvi in una zona del file libera (sempre però nella sezione in cui state lavorando) tramite un JMP. Quindi scrivete le vostre istruzioni e quindi tramite un altro JMP ritornate. (Ho preferito il JMP alla CALL perchè non influisce sullo stack).
Se non trovate dello spazio nella sezione l'unica sarebbe quella di creare una nuova sezione nel file, ma qui si va su cose un pochino più difficiline.


 


--[CAPITOLO XV - Compressioni]--

Siamo giunti finalmente al capitolo che un po' tutti aspettavano... le compressioni e/o criptazioni.
Beh, chiaramente non si può spiegare una regola base in quanto ogni gioco ne può avere una diversa. Comunque può essere utile mostrare un metodo in modo da permettervi di avere una minima esperienza. In questo modo non sarete completamente impreparati.
Direi di prendere come esempio ancora Sentimental Shooting che abbiamo visto avere una protezione molto semplice, ideale per i nostri scopi.
Prima di tutto dobbiamo avere un punto di partenza. Vediamo... Prendiamo l'HexWorkshop e cerchiamo qualcosa di utile... qualcosa che ci porti vicino al punto in cui viene attuata la decriptazione/decompressione. Beh, sicuramente durante il processo di decodifica verrà letto il nome del file, quindi cerchiamo il nome di un file da decodificare. Scriviamo "HNK.pcg" (a volte non viene letta l'estensione, solo il nome del file. Quindi, se non trovate niente, provate a scrivere il nome del file senza estensione.) e otterremo un risultato. Segniamoci l'offset e andiamo a vedere cosa c'è su IDA (l'offset si trova in basso, quello sulla sinistra è il virtual address. Vi rimando al capitolo sul formato PE per ulteriori spiegazioni.).
Questo è quello che vedrete se giungete a quell'offset:
.data:00432AB6 align 4
.data:00432AB8 aHnk_pcg db 'HNK.pcg',0       ; DATA XREF: sub_41CC90+19o          
<-- Offset 306B8
.data:00432AC0 aHonoka_bmp db 'Honoka.bmp',0 ; DATA XREF: sub_41CC90+14o
Come possiamo vedere dal DATA XREF quella stringa viene richiamata dal programma. Si presume che lo faccia il decriptatore. Quindi andiamo a vedere il codice che lo richiama. Arriviamo qui:
.text:0041CC90 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
.text:0041CC90 
.text:0041CC90 
.text:0041CC90 sub_41CC90 proc near ; CODE XREF: sub_422080+527p
.text:0041CC90 
.text:0041CC90 arg_0 = dword ptr 4
.text:0041CC90 
.text:0041CC90 mov eax, [esp+arg_0]                            A
.text:0041CC94 cmp eax, 0Bh ; switch 12 cases                  |
   Qua c'è uno switch a 12 casi.
.text:0041CC97 ja loc_41CDC4 ; default                         |
.text:0041CC9D jmp ds:off_41CDC8[eax*4] ; switch jump          V
.text:0041CCA4 
.text:0041CCA4 loc_41CCA4: ; DATA XREF: .text:0041CDC8o       <--
1° Case. E' quello che interessa a noi.
.text:0041CCA4 push offset aHonoka_bmp ; case 0x0              <--
Questo è il nome che avrà il file decriptato creato
.text:0041CCA9 push offset aHnk_pcg ; "HNK.pcg"                <-- e questo è il file criptato che verrà decriptato.
.text:0041CCAE call sub_41CE00                                 <-- Probabilmente qui avverrà la decriptazione.
.text:0041CCB3 add esp, 8
.text:0041CCB6 mov eax, 1
.text:0041CCBB retn                                            <--
si esce e continua col programma.

Bene... andiamo a vedere che cosa fa questa call

.text:0041CE00 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
.text:0041CE00 
.text:0041CE00 
.text:0041CE00 sub_41CE00 proc near ; CODE XREF: sub_41CC90+1Ep
.text:0041CE00 ; sub_41CC90+36p ...
.text:0041CE00 
.text:0041CE00 var_401 = byte ptr -401h
.text:0041CE00 FileName = byte ptr -400h
.text:0041CE00 arg_0 = dword ptr 4
.text:0041CE00 arg_4 = dword ptr 8
.text:0041CE00 
.text:0041CE00 sub esp, 404h
.text:0041CE06 push ebx
.text:0041CE07 push ebp
.text:0041CE08 push esi
.text:0041CE09 push edi
.text:0041CE0A push offset aOmake ; lpPathName     ; A
Crea la cartella OMAKE (dove verranno salvate le immagini  
.text:0041CE0F call __mkdir                        ; V decriptate)
.text:0041CE14 mov eax, [esp+418h+arg_4]           
.text:0041CE1B add esp, 4                          
.text:0041CE1E lea ecx, [esp+414h+FileName]        
.text:0041CE22 push eax
.text:0041CE23 push offset aOmake ; "OMAKE"
.text:0041CE28 push offset aSS ; "%s\\%s"
.text:0041CE2D push ecx
.text:0041CE2E call _sprintf
.text:0041CE33 add esp, 10h
.text:0041CE36 lea edx, [esp+414h+FileName]
.text:0041CE3A push 0 ; char
.text:0041CE3C push edx ; lpFileName
.text:0041CE3D call __access
.text:0041CE42 add esp, 8
.text:0041CE45 cmp eax, 0FFFFFFFFh
.text:0041CE48 jz short loc_41CE57
.text:0041CE4A xor eax, eax
.text:0041CE4C pop edi
.text:0041CE4D pop esi
.text:0041CE4E pop ebp
.text:0041CE4F pop ebx
.text:0041CE50 add esp, 404h
.text:0041CE56 retn
.text:0041CE57 ; ---------------------------------------------------------------------------
.text:0041CE57 
.text:0041CE57 loc_41CE57: ; CODE XREF: sub_41CE00+48j
.text:0041CE57 mov eax, [esp+414h+arg_0]         
; eax = "Hnk.pcg"
.text:0041CE5E push offset aRb ; "rb"
.text:0041CE63 push eax
.text:0041CE64 call _fopen                       
; Apre il file "Hnk.pcg" in lettura binaria
.text:0041CE69 mov edi, eax
.text:0041CE6B add esp, 8
.text:0041CE6E test edi, edi                     
; Se il puntatore = 0 c'è un errore di apertura --> esci
.text:0041CE70 jnz short loc_41CE7D               ; altrimenti salta e continua
.text:0041CE72 pop edi
.text:0041CE73 pop esi
.text:0041CE74 pop ebp
.text:0041CE75 pop ebx
.text:0041CE76 add esp, 404h
.text:0041CE7C retn
.text:0041CE7D ; ---------------------------------------------------------------------------
.text:0041CE7D 
.text:0041CE7D loc_41CE7D: ; CODE XREF: sub_41CE00+70j
.text:0041CE7D lea ecx, [esp+414h+FileName]
.text:0041CE81 push offset aWb ; "wb"
.text:0041CE86 push ecx
.text:0041CE87 call _fopen                       
; Apre il file "Honoka.bmp" in scrittura binaria
.text:0041CE8C mov ebp, eax
.text:0041CE8E add esp, 8
.text:0041CE91 test ebp, ebp                     
; Se il puntatore = 0 c'è un errore di apertura --> esci
.text:0041CE93 jnz short loc_41CEAB              
; altrimenti salta e continua
.text:0041CE95 push edi
.text:0041CE96 call _fclose                      
; Prima di uscire chiudi il file "Hnk.pcg"
.text:0041CE9B add esp, 4
.text:0041CE9E xor eax, eax
.text:0041CEA0 pop edi
.text:0041CEA1 pop esi
.text:0041CEA2 pop ebp
.text:0041CEA3 pop ebx
.text:0041CEA4 add esp, 404h
.text:0041CEAA retn
.text:0041CEAB ; ---------------------------------------------------------------------------
.text:0041CEAB 
.text:0041CEAB loc_41CEAB: ; CODE XREF: sub_41CE00+93j
.text:0041CEAB xor esi, esi                      
; esi = 0
.text:0041CEAD mov bl, 10h                        ; bl = 10h
.text:0041CEAF 
.text:0041CEAF loc_41CEAF: ; CODE XREF: sub_41CE00+E6j
.text:0041CEAF push edi                          
; Puntatore al file su cui leggere i byte
.text:0041CEB0 push 1                             ; Quanti byte leggere
.text:0041CEB2 lea edx, [esp+41Ch+var_401]        
.text:0041CEB6 push 1                            
; Dimensione del tipo da leggere (1 byte)
.text:0041CEB8 push edx                           ; Puntatore al buffer in cui andranno i byte letti
.text:0041CEB9 call _fread                        ; Legge i byte
.text:0041CEBE add esp, 10h
.text:0041CEC1 cmp esi, 3E8h                     
; Se ESI (byte letti) < 1000 continua,
.text:0041CEC7 jge short loc_41CECE               ; altrimenti salta e non sommare 90h ai byte
.text:0041CEC9 add [esp+414h+var_401], 90h        ; Se il numero di byte letti è minore di 1000, allora somma al byte 90h
.text:0041CECE 
.text:0041CECE loc_41CECE: ; CODE XREF: sub_41CE00+C7j
.text:0041CECE test [edi+0Ch], bl                 
; Per capire come funziona questa istruzione ci serve qualche conoscenza
                                                                                                    ; del c Ansi...
; Edi è un puntatore a FILE. FILE è una struttura  (per quelli che conoscono il Pascal, è un record). Edi quindi punta alla struttura.
; Edi+0C punta ad un membro della struct (int _flag;). 0x10 è solo un flag che si chiama _IOEOF e viene settato se si raggiunge la fine del file.
; Se non avete capito molto bene pasto un po' di codice che vi potrebbe essere più chiaro.
;   FILE *f;
;   f = fopen()
;   fread(.., f)
;   if (f->_flag == _IOEOF)
;   {
;      fclose(f);
;      return;
;   }
; Capito? Il programma guarda se il flag è settato... se sì, capisce che sono stati letti tutti i byte e a quel punto smette di leggerli ed esce.

.text:0041CED1 jnz short loc_41CEE8               ; Se abbiamo letto tutti i byte dal file sorgente usciamo
.text:0041CED3 push ebp                          
; Puntatore al file su cui scrivere i byte
.text:0041CED4 push 1                             ; Quanti byte leggere
.text:0041CED6 lea eax, [esp+41Ch+var_401] 
.text:0041CEDA push 1                            
; Dimensione del tipo da leggere (1 byte)
.text:0041CEDC push eax                           ; Buffer dal quale verranno copiati i byte sul file destinazione
.text:0041CEDD call _fwrite
.text:0041CEE2 add esp, 10h
.text:0041CEE5 inc esi                           
; Aumenta l'indicatore del numero dei byte letti (e scritti)
.text:0041CEE6 jmp short loc_41CEAF
.text:0041CEE8 ; ---------------------------------------------------------------------------
.text:0041CEE8 
.text:0041CEE8 loc_41CEE8: ; CODE XREF: sub_41CE00+D1j
.text:0041CEE8 push edi
.text:0041CEE9 call _fclose                      
; Chiude il file sorgente
.text:0041CEEE add esp, 4
.text:0041CEF1 push ebp
.text:0041CEF2 call _fclose                      
; Chiude il file destinazione
.text:0041CEF7 add esp, 4
.text:0041CEFA mov eax, 1
.text:0041CEFF pop edi
.text:0041CF00 pop esi
.text:0041CF01 pop ebp
.text:0041CF02 pop ebx
.text:0041CF03 add esp, 404h
.text:0041CF09 retn                              
; Ed esce
.text:0041CF09 sub_41CE00 endp

Visto? Senza analizzare i file ottenuti, basandoci unicamente sulla routine di decriptazione propria del programma, siamo riusciti ad ottenere l'algoritmo di decriptazione. Ora è un attimo creare un tool che decripti. Per la recriptazione è ugualmente semplice... basta dire al programma di sottrarre 90h ai primi 999 byte del file.
 

 


--[CAPITOLO XVI - Progressi]--
2003 - 08 - 23
Versione 1.0 - Prima versione pubblica.
2003 - 08 - 20

Versione 1.0a - Ok, finiti i capitoli PC e Z80 per la prima versione pubblica.
2003 - 08 - 18
Versione 0.9.7 - Terminati i capitoli sugli interrupt e le memorie per il x86. Piccole correzioni a qualche capitolo. Aggiunto anche il capitolo "Accenni al formato PE" (azz... quest'ultimo è stata proprio una faticaccia scriverlo!)
2003 - 08 - 17
Versione 0.9.2 - Aggiunti capitoli sul processore Z80 (interrupt e registri). Inoltre sono stati corretti alcuni piccoli errori. È stata aggiunta anche il "capitolo" sui progressi della guida e il "capitolo" sulle conclusioni (ma si può scriverlo prima di aver finito tutto? :D )
2003 - 08 - 12
Versione 0.9 - Manca poco e la versione da uppare nel forum Traduzioni di Emuita (prima release pubblica ufficiale) sarà pronta.
2003 - 07 - 29
Versione 0.0 - È quando ho iniziato a scriverla... è da lì che è nato tutto :D

 

 


--[CAPITOLO XVII - Conclusioni]--

Bene... eccomi arrivato alle conclusioni. Che dire... è stata proprio una faticaccia realizzare questa guida e penso che non finirò mai di completarla, tante sono le cose da dire e tante sono le cose nuove da scoprire. Beh... comunque spero che abbiate trovato la guida perlomeno interessante e che magari abbiate imparato qualcosa da essa.
Però ricordate bene... (ovvero l'immancabile Disclaimer)
Lo scopo di questa guida è puramente educativo. L'obiettivo è solo quello di ampliare le conoscenze del lettore e non mi assumo nessuna responsabilità (diretta e indiretta) del cattivo uso che il lettore ne può fare delle informazioni contenute in questo documento. Insomma, ragazzi, non fate cazzate con queste info... e se le fate, io non c'entro niente!
Dopo queste righe (che per me vengono saltate all'istante da chi legge, ma mi servono per evitarmi guai.) possiamo continuare coi saluti e ringraziamenti.
Beh... innanzitutto saluto e ringrazio gli
- UST e Lord British per avermi permesso di provare l'esperienza di far parte di un gruppo di traduttori.
- SadNES e a IAGTG per avermi permesso (grazie alle loro guide e tutorial) di imparare molto sul mondo delle traduzioni di sofware.
- Quequero. Senza di lui non avrei mai iniziato ad interessarmi all'ASM e non saprei quello che so ora.
- Clomax e a Dewos che grazie ai loro siti permettono a molte persone di conoscere il mondo delle traduzioni italiane.
- Mat che quando gli chiedo consigli sul c mi dà sempre una mano e sopporta anche i miei discorsi insensati in chat.
- Gemini che mi fa sbragare dalle risate quando si incaxxa coi niubbini del forum.
- Neutrinus che si sbatte per farmi i favori (vedi MircStats etc...)
- Spike che si è offerto di controllare la guida e mi ha dato preziosi consigli (ringraziate lui per il capitolo sui Sistemi di Numerazione).
- Brisma che, oltre a leggere la mia guida e darmi consigli (ringraziate lui per il capitolo Assembly 4 Newbies), ha anche detto che la mia guida è tra le migliori che ci siano (forse perchè è l'unica guida ASM relativa alle traduzioni? ;D ).
- NTosKrnl, Tanatos e AndreaGeddon per l'aiuto che mi hanno dato sull'assembly.
- Tutti i traduttori che si impegnano con serietà.
- Tutti i niubbini che coi loro post inutili e assurdi mi fanno sganasciare dalle risate :D (suvvia, non ve la prendete... vedrete che dopo un po' incomincerete a riderci sopra anche voi ;) . Ci siamo passati tutti).
- Tutti gli autori delle guide e tutorial che mi hanno preparato e sulle quali mi sono basato per realizzare questa guida (a voi un ringraziamento molto speciale ;) )
Gli altri invece non li saluto perchè mi stanno sulle palle :D (no scherzo, è che altrimenti verrebbero solo 10kb di saluti :) ) 
Azz... quasi dimenticavo. Se volete scrivermi la mia e-mail è dany298@libero.it
Ok... basta... non posso più di star qui a scrivere. Ciauz!
EndOfFile