Skinner: Nelson, sarai costretto a fare il lavoro più basso e disonorevole che
l'umanità abbia mai conosciuto...Il bidello!
Benvenuti nella terza edizione della mia guida
all'uso di IDA il miglior disassembler attualmente in circolazione, ovviamente è stata
aggiornata alla versione per windows con alcuni piccoli e gradevoli accorgimenti:
È stato aggiunto un menu Java che cerca di simulare il menu di IDA,
in questo modo vi sarà più semplice trovare la spiegazione di qualche comando che non
conoscete, è stata aggiunta anche la barra dei comandi di IDA per lo stesso motivo di
prima, tutte le definizioni sono state ampliate o cmq migliorate, il tutorial è stato
generalmente risistemato :)
Willie: Ma direttore, davanti a lei sono, proprio ora lo doveva dire....
Tutti conoscete IDA (Interactive DisAssembler) vero? Bene, se avete difficoltà ad
usarlo allora vi propongo una guida che ritengo discretamente efficace, buona lettura.
IDA 4.x Guide (The Interactive Disassembler)
Written by Quequero
Vi consiglio caldamente di leggere offline questo tutorial ma dovete scaricare i file: 1, 2, 3, 4, 5 (230Kb ~) poi li mettete tutti nella
stessa directory ed aprite tutbar.html con un browser che supporti il java.
Introduzione
IDA è il miglior disassembler in circolazione, presenta svariate caratteristiche che lo
rendono relativamente semplice da utilizzare e molto efficace, per i target più difficili
IDA è la soluzione migliore
IDA 4.0.4.362 Demo sarebbe ottimale per seguire il tutorial, ma qualunque altra versione
di IDA 4.x va ugualmente bene.
Essay
IDA 4.x The Interactive
Disassembler
(Tutorial v1.2)
Benvenuti nella terza versione della mia guida ad IDA, il tutorial
è stato lievemente ampliato e soprattutto ora è molto più semplice ritrovare ciò che
ci serve senza andarlo a cercare per tutto il documento, l'unico consiglio che vi do è
quello di aspettare il completo caricamento dell'applet java che vi ha portato in questa
pagina, buona lettura e buon reversing:
Return to previous position: Se
decidete di seguire un jump, questo comando vi permette di tornare indietro (premendo ESC
otterrete lo stesso risultato):
Undo Last Jump Back: Se premete per
sbaglio ESC oppure tornate indietro dopo aver saltato e volete tornare alla vostra
posizione originale, avvaletevi di questo comando o premete Ctrl+Enter
Hide Current Function: Collassa tutte
le subroutine che indicate, in questo modo avrete meno codice nella finestra
Open File: scegliamo
il file da disassemblare. Possiamo aprire pressocchè qualunque cosa exe, com, dll, vxd,
bin, class e praticamente tutte le altre estensioni conosciute :))
Per fare qualche prova iniziale scegliamo un file da
disassemblare, possibilmente uno molto piccolo e magari per windows, ad esempio
notepad.exe, clickiamoci sopra ed IDA aprirà una finestra con scritto in alto "Load
File of New Format", nelle prime tre righe ci viene chiesto di scegliere il tipo di
file che si vuole disassemblare, cioè se un eseguibile Windows (o che comunque usi il
formato PE), un eseguibile DOS oppure un file binario, checkiamo la prima casella:
Portable Executable (lo facciamo dal momento che il file scelto era un eseguibile per
windows che usava il formato PE), più in basso vediamo il campo "Processor Type", a differenza di ciò
che potreste credere qui non dovrete specificare il tipo di processore che avete ma il
tipo di processore per il quale è stato scritto il programma, di default è 80386 ma non
è detto che non sia stato fatto per gli 80586, se poi il file è per amiga, è un file
java o è per i MIPS (i processori della playstation) allora dovrete scegliere i
corrispondenti processori, diciamo che per tutti gli eseguibili che riguardano il pc
dobbiamo scegliere "Intel 80x86 processor: metapc", oppure "Java Virtual
Machine: JAVA" se il file è un .class.
Più in basso troviamo "Loading
Segment", qui dovrete inserire l'indirizzo dal quale il
programma verrà caricato, ma solo se il file è in formato binario, a volte è 0x1000,
come di default, ma se siete incerti lasciamo 0x1000 ci penserà poi IDA a ritrovarselo,
più in basso appare un altro campo "Loading offset", nel quale dovremo inserire l'offset del primo byte dall'inizio del
primo segmento, ciò vuol dire che se come indirizzo di segmento abbiamo settato 0x1000 e
come offset 0x1500 allora il primo byte del primo segmento sarà a: 1000:1500, anche
questa opzione va utilizzata solo se si vuole disassemblare un file binario, più in basso
dobbiamo checkare altre caselle:
Create segments
Load resources
Rename DLL entries
Manual load
Fill segment gaps
Make imports section
Don't align segments
Create segments: è un'opzione utile
per i file binari, infatti ci crea i vari segmenti, ma ciò non è necessario se si vuole
disassemblare un eseguibile.
Load as code segment: questa opzione
appare solo se stiamo disassemblando un file binario, se la checkiamo IDA disassemblerà
tutti i dati come se fosse un unico segmento di codice.
Load resources: carica le risorse dai file NE, neanche questa è necessaria se si
disassembla un eseguibile con formato PE. Rename DLL entries: è un'opzione che se lasciata disattivata permette a IDA di piazzare tra
il disassemblato dei commenti ripetibili ad ogni funzione importata/esportata, IDA si
preoccupa anche di dare un nome agli ordinali esportati dalle DLL delle MFC.
Manual load: Non serve nel caso dei
file con formato PE, se però il formato è diverso (NE, LE, LX) allora l'opzione può
essere attivata e IDA ci chiederà l'indirizzo per il caricamento (ed i selettori) di ogni
oggetto del file.
Fill segment gaps: utile solo per il
formato NE, se checkata IDA non fa altro che riempire tutti i "buchi" tra i vari
segmenti creando un unico enorme chunk, questa opzione va settata se durante il disasm ci
viene mostrato il messaggio "Maximum Number of Chunks Reached", possiamo
definire una variabile d'ambiente (IDA_PAGESIZE) più grande per far si che IDA accetti un
maggior numero di chunk, il valore di default è 4096.
Make imports section: utile solo per
i file in formato PE, se attivata dice a IDA di eliminare la sezione .idata dal file e di
convertire tutte quante le definizioni in essa presenti come direttive esterne, la guida
ci dice che qualche volta nella sezione .idata possiamo trovare delle informazioni
aggiuntive e dobbiamo quindi disabilitare questa opzione se IDA non carica tutto il
necessario nel database.
Don't align segments: come dice la
parola stessa, se checkiamo questa opzione non permettiamo il riallineamento dei segmenti,
ma serve solo per i file con formato diverso dal PE.
Analysis enabled: se la casella è
checkata allora IDA analizzerà il programma mentre noi non stiamo agendo sul
disassemblato, lasciando comunque la priorità alle nostre richieste, questa opzione deve
essere abilitata, se non lo fosse potremmo incorrere in vari problemi.
Indicator enabled: è quel
contatore che si trova in basso e ci mostra varie tabelle riguardanti il file che sta per
essere disassemblato, potete chiudere quella finestra col mouse o se volete la potete
ingrandire.
Abbiamo a destra due pulsanti, clickiamo sul primo "Kernel options 1" e troviamo tante
opzioni:
Create offsets and segments using fixup info
Mark typical code sequences as code
Delete instructions with no xrefs
Trace execution flow
Create functions if call is present
Analyse and create all xrefs
Use flirt signatures
Create function if data xref data->code32 exists
Rename jump functions as j_...
Rename empty functions as nullsub_...
Create stack variables
Trace stack pointer
Create ascii string if data xref exists
Convert 32bit instruction operand to offset
Create offset if data xref to seg32 exists
Make final analysis pass
Create offsets and segments using fixup info: IDA per rendere più piacevole il disassemblato, fa uso delle
informazioni di rilocazione e converte tutti gli oggetti con le info di rilocazione in
word o doubleword e se un'istruzione ha assegnate delle informazioni di rilocazione IDA
converte i dati ad un offset oppure ad un segmento, facciamo un esempio per rendere più
chiara questa spiegazione, poniamo di avere nel nostro codice sorgente qualcosa di questo
tipo:
.386
-----snip snip-----
.data
valore dd 000F3785h
.code
start:
mov eax, offset valore
end start
Se disassembliamo il file con W32Dasm otteniamo questo:
mov eax, 0040200
Se invece lo disassemblassimo con IDA e con l'opzione attivata
allora otterremo questo:
mov eax, offset valore
Può essere una funzione davvero utile al fine della comprensione
del disassemblato.
Mark typical code sequences as code:
IDA riconosce alcune sequenze tipiche di codice (esempio: push bp e mov bp, sp) e durante
il caricamento converte questi byte in istruzioni anche se non ci sono referenze ad essi.
Delete instructions with no xrefs:
permette a IDA di non definire le istruzioni non referenziate, in pratica se noi eseguiamo
il comando "undefine" (che vi spiegherò in seguito) all'inizio di una funzione
il programma cancellerà tutte le istruzioni che hanno perso contatto con l'istruzione
appena "undefinita"...supponiamo di avere ad una determinata riga l'istruzione
"jmp 401000", se facciamo "undefine" IDA trasformerà quell'istruzione
in soli byte (come se non l'avesse disassemblata) se esisteva una qualche referenza nel
codice a quell'istruzione allora IDA la cancellerà, se non selezioniamo questa opzioni
allora IDA lascierà intatta la referenza.
Trace execution flow: IDA segue il
flusso di esecuzione e tenta di convertire tutte le referenze a dei byte gia disassemblati
in istruzioni ed evita di lasciarne solo l'opcode.
Create functions if call is present:
fa si che IDA crei una funzione se nel codice è presente una chiamata, se nel codice
troviamo una chiamata di questo genere:
call routine
allora IDA creerà una funzione all'etichetta "routine"
che conterrà tuttò il codice che si trova nella chiamata, questa opzione cerca di
"staccare" le chiamate dal resto del codice per permetterci un approccio
visuale, ma anche tecnico, più semplice.
Analyse and create all xrefs:
questa è l'opzione che fa la differenza tra IDA ed altri disassemblatori, senza di essa
il nostro programma non analizzerebbe il file più volte e non creerebbe tutte le
xreferences e le altre chicche caratteristiche di IDA, avremmo quindi codice disassemblato
puro e non vedremmo tutte le "semplificazioni" che potremmo invece ottenere
attivando questa opzione.
Use FLIRT signatures: permette l'uso
della tecnologia FLIRT, non preoccupatevi non vedrete IDA e IDO fare gli sporcaccioni sul
monitor ;)))))), questa tecnologia consente di riconoscere il tipo di compilatore usato e
di identificare le chiamate standard, in questo modo invece di vedere questo: call 00469187, vedremo: call MessageboxA, nel caso che
questa tecnologia fallisca nel riconoscere i compilatori (leggi: Delphi) la si può sempre
forzare ad usare le istruzioni del compilatore che riteniamo sia stato usato per fare quel
determinato programma semplicemente aprendo il menu "View | Signatures" e
premendo "ins" per far apparire la lista dei compilatori disponibili.
Create function if data xref data->code32 exists: se IDA incontra un riferimento ad un dato da un SEGMENTO DATI ad un
SEGMENTO CODICE a 32bit allora controlla la presenza di istruzioni disassemblabili utili,
se trova un'istruzione la marca (manco a dirlo ;) come un'istruzione e crea lì una
funzione. È molto più semplice di quanto appaia con questa spiegazione :)
Rename jump functions as j_...:
funzione di utilità elevatissima, questa opzione permette ADDIRITTURA di cambiare una
istruzione come:
jmp indirizzo
in:
j_indirizzo
vi rendete conto...Ma dico ve ne rendete conto di quanto la
presenza di questa opzione possa permettere anche a mio cugino di 10 anni di diventare un
reverser :P....In realtà la presenza di un'opzione tanto "poco" importante non
fa altro che confermare la professionalità di questo programma.
Rename empty functions as nullsub_...:
permette a IDA di rinominare le funzioni vuote, cioè quelle contenenti solo l'istruzione
"ret" come nullsub_1...nullsub_2 ecc...
Create stack variables: fa si che IDA
crei automaticamente le variabili dello stack ed i parametri delle funzioni
Trace stack pointer: con questa
opzione attivata IDA traccerà il lo Stack Pointer, attivatela perchè come funzione è
davvero davvero utile, può permettervi di identificare alcuni trick che altrimenti
sarebbero impossibili da notare guardando soltato il disassemblato.
Create ascii string if data xref exists: se IDA incontra una referenza ad un oggetto non definito allora
cerca una stringa ASCII, se la lunghezza della stringa è abbastanza (più di 4 caratteri
per eseguibili a 16bit o più di 16 caratteri per altri eseguibili) allora IDA crea
guarda caso una stringa ASCII ;)
Convert 32bit instruction operand to offset: funziona solo su segmenti a 32bit, se un'istruzione ha un operando che
può essere convertito in un'utile espressione allora la converte in offset, il valore
dell'operando deve però essere più grande di 0x10000
Create offset if data xref to seg32 exists: se nel programma IDA incontra un riferimento ad un segmento a 32bit ed il
file contiene un valore che può essere rappresentato come un offset allora IDA lo
converte.....ad un offset
Make final analysis pass: questa
opzione permette a IDA di coagulare tutti i byte non esplorati convertendoli in dati o, se
possibile in istruzioni.
Premendo il pulsante Kernel options 2" potremmo settare le seguenti opzioni:
Locate and create jump tables
Coagulate data segments in the final pass
Automatically hide library functions
Locate and create jump tables: questa
opzione permette a IDA di ipotizzare la dimensione e l'indirizzo di una "jump
table"
Coagulate data segments in the final pass: questa opzione torna utile solo se è attivata anche l'opzione "Make
final analysis pass" e permette a IDA di convertire i byte inesplorati ad array di
dati nel segmento dati. Se disabilitiamo questa opzione IDA coagulerà solo il segmento
codice. Non ci nuocerà se la lasceremo disabilitata.
Automatically hide library functions: nasconde
le funzioni riconosciute dalla tecnologia FLIRT, questa opzione è utile solo se abbiamo
deciso di fare uso della tecnologia FLIRT.
Entriamo ora nelle opzioni del pulsante "Processor options", dopo il click ci si
presentano alcune opzioni:
Convert immediate operand of "push" to offset
Convert db 90h after "jmp" to "nop"
Convert immediate operand of "mov reg,..." to offset
Convert immediate operand of "mov memory,..." to offset
Disassemble zero opcode instructions
Advanced analysis of Borland's RTTI
Check 'unknown_libname' for Borland's RTTI
Advanced analysis of catch/finally block after function
Allow references with different segment bases
Convert immediate operand of "push" to offset: se IDA vede una sequenza tipo questa: push seg, push gino, proverà a
convertire gino in un offset
Convert db 90h after "jmp" to "nop": se dopo un jump troviamo "db 90h" invece di lasciarlo com'è lo
trasforma in nop (No operation)
Convert immediate operand of "mov reg,..." to offset: anche qui se IDA trova qualcosa come: mov ax, 258h, mov sp, es
allora convertirà 258h in un offset
Convert immediate operand of "mov memory,..." to offset: ancora se IDA trova istruzioni come: mov ax, 895h mov X, seg (X è
un riferimento ad una memoria) allora converte 895h in offset
Disassemble zero opcode instructions: se
nel codice trova degli opcode del tipo "0000" cerca di disassemblarli e
trasformarli in codice.
Advanced analysis of Borland's RTTI: cerca
di attuare un'analisi avanzata del Borland RTTI, questa opzione è utile se il programma
è stato scritto con un compilatore Borland
Check 'unknown_libname' for Borland's RTTI: ricerca dei nomi di librerie sconosciuto per il Borland RTTI
Advanced analysis of catch/finally block after function: le istruzioni catch/finally sono istruzioni usate solamente per la
gestione delle exception nel linguaggio C/C++ (vi rimando al mio tute sulle Exception) che occupano una parte
considerevole dell'eseguibile, il problema delle exception è che sono davvero difficili
da vedere sia da disassemblato che da debugger, attivando questa opzione IDA cerca di
mostrarci più informazioni possibile su tutti gli exception handler installati dal
programma.
Allow references with different segment bases: permette ad IDA di creare delle referenze a parti di codice che provengono
da segmenti diversi.
C'è ancora un ultimo campo da riempire, quello
contrassegnato dalla stringa "System DLL directory", dobbiamo inserire la directory nella quale IDA andrà a guardare
per trovare i riferimenti alle varie DLL, il problema è che IDA non cerca le DLL bensì i
file .ids, la directory nel mio caso è: c:\ida\ids\Win se state disassemblando un file
per windows altrimenti cambierà solo l'ultima cartella.
Siamo pronti a questo punto per iniziare il disassembling del
file, sceglietene uno piccolo....molto piccolo consiglio 8-15 kb che ci vogliono pochi
secondi, lo dico per coloro che stanno conoscendo IDA solo ora, questo programma è molto
ma molto più lento del W32dasm vi garantisco però che tutto il tempo in più che perdete
nel disassembling lo riguadagnate dopo, quindi premete "OK" ed
aspettate....Appena il segnalatore in alto a destra diventerà verde vorrà dire che IDA
avrà finito, a quel punto avrete davanti come prima cosa una pagina come questa:
00401000
; File Name : C:\WINDOWS\Notepad.exe
00401000 ;
Format : Portable executable for IBM PC (PE)
00401000 ;
Section 1. (virtual address 00001000)
00401000 ;
Virtual size : 00003E9C ( 16028.)
00401000 ;
Section size in file : 00004000 ( 16384.)
00401000 ;
Offset to raw data for section: 00001000
00401000 ;
Flags 60000020: Text Executable Readable
00401000 ;
Alignment : 16 bytes ?
00401000
00401000
model flat
00401000
00401000 ;
--------------------------------------------------------------
00401000
00401000 ; Segment type: Pure code
00401000 _text
segment para public 'CODE' use32
00401000 assume
cs:_text
00401000 ;org 401000h
00401000 assume
es:nothing, ss:nothing, ds:_data, fs:nothing, gs:nothing
In questa prima parte abbiamo tutte le intestazioni iniziali del
file, quelle in pratica che vedremmo se avessimo il sorgente e soprattutto IDA ci informa
del tipo di file appena disassemblato nel caso non lo avessimo saputo prima. Vorrei
passare però ad un esempio pratico, l'autore ci dice che possiamo utilizzare IDA per tre
scopi:
1) Imparare un trucco di programmazione in un programma
interessante
2) Patchare il file se non si ha il sorgente
3) Ottenere un codice compilabile dal disassemblato
Noi faremo tutte e tre le cose, iniziamo.
Quello che vi propongo ora è il codice assembly di un
programmillo che genera una messagebox a seconda del valore che assume eax, ecco il
codice, capirete con il commento:
; Per compilare il sorgente è
necessario possedere TASM 5.0 reperibile sul
; mio sito: http://quequero.cjb.net nella sezione tools
;
; Ecco le istruzioni di compilazione, basta scriverle in un prompt di DOS
; tasm32 /t -ml -m5 -q esempio
; tlink32 -Tpe -aa -x -c esempio ,,, import32
.386p
.model flat, stdCALL
extrn MessageBoxA:Proc ;
definiamo le API necessarie
extrn ExitProcess:Proc
include windows.inc
.data
Caption db 'Esempio - - Coded
by Quequero',0
Messaggio db 'Il registro EAX ha assunto un
valore inferiore di 1.771.590.911',0
Random1 dd 12553
; valori casuali utilizzati nell'algoritmo
Random2 dd 2212
; di Lehmer
null equ 0
.code
start:
xor eax, eax
; Azzeriamo EAX
mov ecx, 01ffffffh
; Queste due righe servono a creare un timer,
l'istruzione
wait: loop wait ; "loop" decrisa ogni volta ECX fino a che
contiene zero, il
;
loop in questione fa comparire il messagebox dopo circa
;
un secondo dall'avvio del programma
casuale: mov eax,
Random1 ; Algoritmo di Lehmer per la generazione di numeri
mul Random2
; casuali (anche se dovremmo parlare di
pseudo-casualità)
inc eax
mov Random1, eax
; Fine algoritmo
cmp Random1, 699854ffh ; Confronta Random1 con 1.771.590.911 (699854ffh)
jae casuale
; Se Random1 contiene un valore maggiore o uguale a
quello
;
stabilito prima ripeti l'algoritmo altrimenti continua sotto
push MB_OK OR MB_ICONQUESTION ; Genera una messagebox con un pulsante di ok,
push offset Caption
; l'icona della "i" di informazione ed il
testo
push offset Messaggio ; contenuto nelle stringhe "Caption" e
"Messaggio"
push null
call MessageBoxA
call ExitProcess
; Esci dal programma
end start
---------------------------------------------------8<-------------------------------------------------------
come potete vedere il programma setta un timer ad un secondo
e poi da il via alla generazione di un numero pseudo-casuale tramite l'algoritmo di
Lehmer, il timer all'inizio è dovuto al fatto che la CPU impiega un tempo infinitesimale
a generare tanti numeri affinchè uno sia minore del valore da noi proposto, in questo
modo ritardiamo il sorgere della messagebox.
Supponiamo di essere a conoscenza della presenza dell'algoritmo di
Lehmer in questo programma, questo algoritmo ci serve assolutamente ma non sappiamo dove
reperirlo, cosa facciamo allora? Ovvio! Disassembliamo il programma e cerchiamo di
ritrovarcelo là in mezzo, apriamo IDA e vediamo cosa ci racconta:
CODE:00401000
; File Name : C:\tasm\prog\random.EXE
CODE:00401000 ; Format : Portable
executable (PE)
CODE:00401000 ; Section 1. (virtual
address 00001000)
CODE:00401000 ; Virtual size : 00001000 (
4096.)
CODE:00401000 ; Section size in file :
00000200 ( 512.)
CODE:00401000 ; Offset to raw data for
section: 00000600
CODE:00401000 ; Flags 60000020: Text
Executable Readable
CODE:00401000 ; Alignment : 16 bytes ?
CODE:00401000 p386n
CODE:00401000 model flat
CODE:00401000
;
----------------------------------------------------------------
CODE:00401000 ; Segment type: Pure code
CODE:00401000 CODE segment para public
'CODE' use32
CODE:00401000 assume cs:CODE
CODE:00401000 assume es:nothing,
ss:nothing, ds:nothing, fs:nothing, gs:nothing
CODE:00401000 ;
________________________________________________________________
CODE:00401000 ; S u b r o u t i n e
CODE:00401000
public start
CODE:00401000 start
proc near
CODE:00401000 33 C0
xor eax, eax
CODE:00401002 B9 FF FF FF 01 mov ecx, 1FFFFFFh
CODE:00401007 CODE_401007:
; CODE XREF:
start+7j
CODE:00401007 E2 FE
loop CODE_401007
CODE:00401009 CODE_401009:
; CODE XREF:
start+24j
CODE:00401009 A1 5E 20 40 00 mov eax, ds:DATA_40205E
CODE:0040100E F7 25 62 20 40 00 mul ds:DATA_402062
CODE:00401014 40 inc eax
CODE:00401015 A3 5E 20 40 00 mov ds:DATA_40205E, eax
CODE:0040101A 81 3D 5E 20 40 00+ cmp ds:DATA_40205E, 699854FFh
CODE:00401024 73 E3
jnb short
CODE_401009
CODE:00401026 6A 20
push 20h
CODE:00401028 68 00 20 40 00 push offset str->Esempio--CodedB
CODE:0040102D 68 1E 20 40 00 push offset str->IlRegistroEaxHa
CODE:00401032 6A 00
push 0
CODE:00401034 E8 05 00 00 00 call j_MessageBoxA
CODE:00401039 E8 06 00 00 00 call j_ExitProcess
CODE:00401039 start endp
CODE:0040103E ;
________________________________________________________________
CODE:0040103E ; S u b r o u t i n e
CODE:0040103E j_MessageBoxA proc near ; CODE XREF: start+34p
CODE:0040103E FF 25 4C 30 40 00 jmp ds:MessageBoxA
CODE:0040103E j_MessageBoxA endp
CODE:00401044 ;
________________________________________________________________
CODE:00401044 ; S u b r o u t i n e
CODE:00401044 j_ExitProcess proc near ; CODE XREF: start+39p
CODE:00401044 FF 25 54 30 40 00 jmp ds:ExitProcess
CODE:00401044 j_ExitProcess endp
CODE:0040104A 00 00 00 00 00 00+ align 200h
CODE:00401200 ?? ?? ?? ?? ?? ??+ db 0E00h dup(?)
CODE:00401200 ?? ?? ?? ?? ?? ??+ CODE ends
DATA:00402000 ; Section 2. (virtual address 00002000)
DATA:00402000 ; Virtual size : 00001000 ( 4096.)
DATA:00402000 ; Section size in file : 00000200 ( 512.)
DATA:00402000 ; Offset to raw data for section: 00000800
DATA:00402000 ; Flags C0000040: Data Readable Writable
DATA:00402000 ; Alignment : 16 bytes ?
DATA:00402000 ;
----------------------------------------------------------------
DATA:00402000 ; Segment type: Pure data
¦DATA:00402000 DATA segment para public 'DATA' use32
¦DATA:00402000 assume cs:DATA
¦DATA:00402000 45 73 65 6D 70 69+str->Esempio--CodedB db 'Esempio - - Coded by Quequero',0
¦DATA:00402000 6F 20 2D 20 2D 20+ ; DATA XREF: start+28o
¦DATA:0040201E 49 6C 20 72 65 67+str->IlRegistroEaxHa db
'Il registro EAX ha assunto un valore infe'
¦DATA:0040201E 69 73 74 72 6F 20+ ; DATA XREF: start+2Do
¦DATA:0040205E*09 31 00 00 DATA_40205E dd 3109h ; DATA XREF:
start+9r
¦DATA:0040205E* ; start+15w
¦DATA:0040205E* ; start+1Ar
¦DATA:00402062 A4 08 00 00 DATA_402062 dd 8A4h ; DATA XREF:
start+Er
¦DATA:00402066 00 00 00 00 00 00+align 1000h
¦DATA:00402066 00 00 00 00 00 00+DATA ends
¦.idata:0040304C ;
¦.idata:0040304C ; Imports from USER32.dll
¦.idata:0040304C ;
¦.idata:0040304C ; Section 3. (virtual address 00003000)
¦.idata:0040304C ; Virtual size : 00001000 ( 4096.)
¦.idata:0040304C ; Section size in file : 00000200 ( 512.)
¦.idata:0040304C ; Offset to raw data for section: 00000A00
¦.idata:0040304C ; Flags C0000040: Data Readable Writable
¦.idata:0040304C ; Alignment : 16 bytes ?
¦.idata:0040304C ;
--------------------------------------------------------------
¦.idata:0040304C ; Segment type: Externs
¦.idata:0040304C ; _idata
¦.idata:0040304C ?? ?? ?? ?? extrn MessageBoxA:dword ; DATA
XREF: j_MessageBoxAr
¦.idata:00403050
¦.idata:00403054 ;
¦.idata:00403054 ; Imports from KERNEL32.dll
¦.idata:00403054 ;
.idata:00403054 ?? ?? ?? ?? extrn ExitProcess:dword ; DATA XREF: j_ExitProcessr
.idata:00403058 00 00 00 00 end start
Come potete notare il codice è di difficile interpretazione, non
per la difficoltà ma per il casino di numeri, per diminuire il caos possiamo fare una
cosa cioè: sappiamo che in un programma (salvo eccezioni) il codice che ci interessa si
trova nelle sezioni CODE (nel caso sia stato compilato con un compilatore Borland) o .text
(se è stato compilato con qualcos'altro),
come potete ben vedere, di lato al virtual address c'è il nome della sezione ".idata.00403054" che come abbiamo detto prima
è "inutil" quindi potremmo tagliar via una buona parte di codice, ma perchè
farlo a mano quando IDA ci mette a disposizione un'opzione molto utile? Bene usiamola,
andate nel menu "View" e cliccate su "Segments" e scegliete
"CODE", doppioclickateci sopra e guradate un po' cosa abbiamo davanti?....
Tadàààààà i sofficiniiiii.....Ehm scusate volevo
dire....Tadààààààààà il codiceeee!!!! In questo caso se attivate l'opzione
"Make Import Section" fate bene :), cmq per chiarire ulteriormente il codice
potete levare gli opcode che per ora non servono a molto quindi andate sul menu
"Options" "General" e a destra dove c'è scritto "Number of
opcode bytes" mettete 0 :). Ed ecco il codice po' cambiato, ma in fondo è quello,
allora cerchiamo di capire come funziona questo algoritmo, possiamo identificarlo subito,
parte all'indirizzo 00401009 e termina tre righe sotto, comunque eccolo:
allora, facciamo finta di non aver creato noi il programma e di
aver identificato l'algoritmo per tentativi; ci troviamo davanti due valori che non
sappiamo di che genere siano e questi sono: DATA_40205E e
DATA_402062, come facciamo a sapere che valori contengono? Semplice premete in IDA
"G" che significa "Go to address" (oppure clickiamo sul menu Jump |
Jump to address) e scriviamo nel box che ci è appena apparso "DATA:40205E" e
poi invio, potevamo anche cliccare su "View | Segments", andare su
"DATA" e cercare l'indirizzo manualmente, ma così si fa prima ed è molto più
semplice ecco ciò che ci appare davanti:
guardate un po', abbiamo due valori esadecimali che se convertiti
in decimali sono gli stessi di quelli che avevamo usato noi per Random1 e Random2, se
siamo proprio dei beoti e non capiamo ancora l'algoritmo usiamo una delle funzione che amo
di più cioè quella che ci permette di rinominare le istruzioni, clickiamo una volta
su DATA_40205E e premiamo "N", scriviamo nel box
"Random1", clickiamo poi su DATA_402062 e facciamo la stessa cosa
solo inserendo "Random2", torniamo al pezzo di codice precedente premendo
"esc" e guardate un po' la sorpresa:
proviamo ora a fare una bella cosa, selezionate queste quattro
righe in IDA col mouse (cliccate sulla prima riga e scendete fino alla quarta), cliccate
poi sul menu "File"
poi "Produce output file"
e poi ancora "Create asm file..." salvate il file con un nome qualunque ed andate ad aprirlo con il
notepad, togliete tutti i "ds", ricordate anche che IDA ci ha fatto vedere che
Random1 e Random2 erano delle doubleword contenenti i valori 3109h e 8a4h....ed ecco il
risultato:
Random1
dd 12553 ; questo ce lo
aggiungete voi, 12553 e 2212 sono i valori esadecimali
Random2 dd 2212
; covertiti in decimali
mov eax, Random1
mul RANDOM2
inc eax
mov Random1,
eax
ecco per voi l'algoritmo che cercavamo uguale identico a quello
del nostro sorgente. Abbiamo scoperto come si fa a "leggere" un disassemblato,
impariamo ora come si patcha. IDA ci da la possibilità di cambiare i byte di un programma
e di produrre un eseguibile "patchato" ma IDA non sa creare eseguibili a 32bit,
riesce a manipolare bene solo gli eseguibili a 16bit, di conseguenza non possiamo patchare
il programma appena visto ma dobbiamo crearne uno appositamente fatto per girare a 16bit,
ecco il codice assembly:
---------------------------------------------------8<-------------------------------------------------------
; Per compilare il sorgente
è necessario possedere TASM 5.0 reperibile sul
; mio sito: http://quequero.cjb.net nella sezione tools
;
; Ecco le istruzioni di compilazione, basta scriverle in un prompt di DOS
; tasm esempio
; tlink esempio
.286
.model small
.stack 100h
.DATA
Stringa db 'Bravo mi hai patchato correttamente!!!',0Dh,0Ah,'$'
mov es, ax
xor ax,
ax
; Azzera AX
cmp ax, 1
; Vedi se è uguale ad 1 ?!
je esatto
; È uguale? Mostra la stringa altrimenti chiudi il programma
mov ah,4ch
; Funzione 'Chiudi'
int 21h
esatto:
mov dx, offset Stringa
mov ah, 09h
; Visualizziamo la stringa
int 21h
end start
---------------------------------------------------8<-------------------------------------------------------
Questo programma non fa altro che azzerare Ax, controllarlo
per vedere se è uguale a 1 e mostrare una stringa se lo è, il problema è che se
azzeriamo Ax, non potrà mai essere uguale a 1 e quindi il programma si chiuderà sempre,
ecco perchè abbiamo bisogno di patcharlo. Apriamo IDA, selezioniamo questo file e
disassembliamolo, le vie che ci si pongono sono essenzialmente due cioè: cambiare
l'istruzione "cmp ax, 1" in modo da farla diventare "cmp ax, 0" oppure
forzare il "je esatto" facendolo diventare "jmp esatto", noi comunque
le esamineremo tutte e due. Facciamo un click sull'istruzione "cmp ax, 1",
clicchiamo in alto nel menu "edit" e successivamente su "patch
program" poi dobbiamo scegliere se cambiare un byte, una word oppure assemblare
l'istruzione, in questo caso scegliamo "Assemble..." e scriviamo nel box
"cmp ax, 0", il programma ora è patchato, ma vediamo anche l'altro metodo,
clickiamo su "je esatto" andiamo nel menu di cui sopra, clickiamo su change byte
e scriviamo......cosa scriviamo? Bhè è semplice, dobbiamo cambiare un salto condizionato
in uno incondizionato, sappiamo che tutti i jump short non condizionati hanno la forma
"EB XX" quindi non dobbiamo fare altro che sostituire i byte "74 04"
con "EB 04" ed il gioco è fatto, a questo punto andate nel menu
"File" poi "Produce output file" ed infine "Produce exe
file", salvatelo con un nome qualunque e cliccateci sopra, vedete la stringa? Bene
allora l'avete patchato come si deve.
Non mi soffermo ulteriormente su questo punto dato che è molto
facile patchare un programma, ma non lo farete comunque mai con IDA dal momento che di
programmi per dos ce ne sono rimasti davvero pochi. Passiamo ora ad analizzare l'aspetto
più importante di IDA cioè: come ricavare un sorgente compilabile dal disassemblato. Per
prima cosa stavolta non vi do il codice ma il disassemblato di modo che possiate imparare
a ricostruire un sorgente, il codice che ho scritto ve lo darò alla fine, volevo
reversare un programmino di winzoz ma non ne ho trovato nessuno abbastanza piccolo per
iniziare, gli unici piccoli erano poi in formato NE, comunque ecco il file smembrato ;) :
Bene bene, il codice l'ho scritto io e non è difficile anzi
direi che la conversione è piuttosto immediata vi consiglio comunque di studiarvi un
attimo il disassemblato qua sopra prima di procedere.....fatto? Ok vediamo un po' di
rinominare qualche funzione, guardate il codice e ve lo spiego:
una semplice addizione ci rivela che 23h è
uguale a 0020h+0003h cioè MB_ICONQUESTION+MB_YESNOCANCEL, ciò vuol dire che il
programmatore (io) nella prima riga ha usato questi parametri:
push MB_YESNOCANCEL OR
MB_ICONQUESTION.
adesso rinominiamo in IDA le stringhe in modo da avere qualcosa
come:
push offset caption
invece di: push offset str->Esempio2...
Senza che vi dico come si fa che l'avete imparato prima, a questo
punto possiamo già ricostruire tutte le routine di messagebox presenti nel codice,
rinominate tutte le stringhe come "Caption", "Testo",
"Testo2" ecc....Avremo in questo modo la struttura originale, ma manca una cosa,
se guardate bene dopo ogni chiamata ad un messagebox c'è un "cmp eax, ?", cosa
sarà mai? Bhè, prendiamo la nostra API reference preferita ed andiamo a vedere.....Ci
dice che l'ID del tasto premuto viene mosso in eax, ma come facciamo a sapere a cosa
corrisponde il numero che sta lì? Semplice, sempre con la nostra API reference! Ecco la
tabella:
IDOK = 1
IDCANCEL = 2
IDABORT = 3
IDRETRY = 4
IDIGNORE = 5
IDYES = 6
IDNO = 7
e così notiamo con piacere che dopo ogni chiamata
il programma checka EAX per vedere quale pulsante è stato premuto ed aprire un'altra
messagebox, quindi finiamo di rinominare tutti quanti i vari jump e vediamo come è
diventato il nostro codice...:
public start
start proc near ; CODE XREF:
start+30j start+45j
push MB_ICONQUESTION or MB_YESNOCANCEL
push offset Caption
push offset Messaggio
push NULL
call MessageBoxA
cmp eax, 6
jz short SI_PREMUTO
cmp eax, 2
jz short ESCI
push MB_ICONQUESTION or MB_OK
push offset Caption
push offset No_Premuto
push NULL
call MessageBoxA
jmp short start
Ottimo!!! Vorrei darvi però ancora dei consigli,
a dire il vero non è facile ricordare tutti gli ID a memoria e per facilitarci il lavoro
IDA ci mette a disposizione i "commenti", i commenti non sono altro che degli
appunti a bordo riga che mette IDA o che mettiamo noi, ne esistono due tipi: ripetibili e
non, la differenza sta nel fatto che se mettiamo su un jump un commenti ripetibile del
tipo "questo fa uscire dal programma" IDA posizionerà la stessa frase
all'arrivo di quel jump, mentre nel caso di un commento non ripetibile questo non succede,
per inserire un commento ripetibile basta cliccare sulla riga che ci interessa e premere ;
(si un punto e virgola) se invece ne vogliamo inserire uno non ripetibile allora dobbiano
premere : (due punti) e poi scriverlo nel box, sappiamo tutto ora del programma, ci resta
solo di andarci a leggere tutte le varie stringhe che si trovano nella sezione data ed
unirle....fatto ecco il codice:
.386p
.model flat, stdCALL
EXTRN ExitProcess:PROC
EXTRN MessageBoxA:PROC
INCLUDE WINDOWS.INC
.DATA
null equ 0
Caption db
'Esempio 2 - - Coded by Quequero',0
Messaggio db 'Ciauz!!! Clicca
su un tasto',0
Messaggio1 db 'Bravo hai scelto
occhei!!!',0
Messaggio2 db 'Hai scelto
annulla!!!',0
Messaggio3 db 'Hai scelto termina',0
Messaggio4 db 'Hai scelto NO!!!',0
Lo so che la stringa "Hai premuto termina..." non
viene mai usata, lei sta li solo per confondere le idee ;))))
La nostra cara IDA ha altre doti "nascoste" che
vengono usate di meno e che mi accingerò a spiegarvi. Se andate nel menu "File"|"Load file" vedrete
varie opzioni e più precisamente:
Reload the input file
Additional binary file
IDS File
DBG File
FLIRT signature file...
Reload the input file: se per
qualche motivo ci fossero stati dei problemi durante la fase di disasm con questo pulsante
facciamo il reload e ripartiamo da capo.
Additional binary file: da qui
carichiamo un file binario addizionale e aggiungiamo le nuove informazioni in coda
alle vecchie
IDS file: questa non serve
potenzialmente mai, i file .IDS sono quelli contenenti tutte le informazioni sulle varie
funzioni di winzoz ma IDA le carica da sola durante il disassembling
DBG file: se IDA trova un file
DBG inerente al file che stiamo disassemblando allora si preoccupa di caricarlo, se invece
non lo trova allora glielo possiamo fare caricare noi
FLIRT signature file: da questi
file IDA carica delle funzioni modello standard che l'aiuteranno a riconoscere delle
funzioni standard, IDA cerca il file adatto da sola ma non sempre ci riesce, nel caso lo
volessimo fare a mano troveremmo tali file nella directory SIG, per caricarne o scaricarne
uno basta aprire la finestra "signature" e premere "ins" per inserirne
uno e "del" per scaricarne uno.
Esploriamo adesso il menu "Produce", ecco le varie opzioni:
Produce MAP file... Shift-F10
Produce ASM file... Alt-F10
Produce LST file...
Produce EXE file... Ctrl-F10
Produce DIF file...
Dump database to IDC file...
Dump typeinfo to IDC file...
Produce MAP file: IDA scriverà
un file con tutte le informazioni riguardo ai segmenti ed i nomi ordinati a seconda dei
loro valori
Produce ASM file: già
incontrata, produce comunque un file .asm contenente il testo asm da noi selezionato, è
molto utile quando si creano i keygenerator
Produce LST file: IDA scrive un
file con il disassemblato attuale oppure con una porzione di codice da noi selezionata
Produce EXE file: come già
detto ricrea l'eseguibile nel caso lo avessimo modificato (funziona solo con i prg a
16bit)
Produce DIF file: questa
funzione crea un file in pieno testo nel quale sono annotati i valori originali e quelli
che sono stati cambiati, il file assumerà queste sembianze:
commento
nome_del_file
offset: vecchio_valore nuovo_valore
Dump database to IDC file: con
questa opzione IDA salva il database in un file di testo.
Dump typeinfo to IDC file:
grazie a questo menu possiamo salvare in un file di testo tutte le definizioni che abbiamo
fatto su un certo disassemblato, la funzione è utile perchè le definizioni possono poi
essere riapplicate su un altro file.
Il menu "IDC file..."
ci consente di caricare uno script già creato per IDA
"
ci consente di creare un piccolo script fatto specificatamente per IDA, la lista è
talmente lunga che se ne avete bisogno aprite il menu e premete F1 per vedervela ;))))
Scusate ma sto iniziando a sentire i primi sintomi
della fatica, sono infatti 4 ore che sto scrivendo......Senza contare quelle dei giorni
precedenti ovviamente ;))
Passiamo al successivo menu cioè "Edit", un menu maledettamente ricco di
opzioni, ecco cosa si presenta davanti ai nostri occhi una volta aperto:
Code
Data
ASCII
Array...
Undefine
Rename...
Operand type
Comments
Segments
Structs
Enums
Functions
Patch program
Other
C'mon babe let's explain.......
Code: opzione alquanto carina che serve a convertire in istruzioni i byte
segnati da IDA come inesplorati, allora, questa istruzione spesso e volentieri sarà più
che indispensabile per giungere ai vostri più oscuri scopi, ecco che vi presento un
esempio pratico dell'uso di questo comando che sarebbe altrimenti un po' emblematico.
Può capitare (anzi, con eseguibili dos capita sempre :) che IDA non riconosca al primo colpo dei byte utili di codice con
istruzioni importanti, questo potrebbe succedere con programmi senza uno specificato entry
point oppure se IDA non ha trovato un determinato path di esecuzione, in tal caso il
codice che avremmo sarebbe di questo tipo (esempio preso dal disasm di un crackme ;):
seg000:0100
start:
seg000:0100 jmp short seg000_15B
seg000:0102 nop
seg000:0103 or ax, 610Ah
seg000:0106 db 6Ah ; j
seg000:0107 db 61h ; a
seg000:0108 db 78h ; x
seg000:0109 db 20h ;
seg000:010A db 66h ; f
seg000:010B db 88h ; ê
seg000:010C db 74h ; t
seg000:010D db 65h ; e
seg000:010E db 20h ;
seg000:010F db 64h ; d
seg000:0110 db 75h ; u
seg000:0111 db 20h ;
seg000:0112 db 66h ; f
seg000:0113 db 6Ch ; l
seg000:0114 db 65h ; e
seg000:0115 db 75h ; u
seg000:0116 db 72h ; r
seg000:0117 db 20h ;
seg000:0118 db 63h ; c
seg000:0119 db 72h ; r
seg000:011A db 61h ; a
seg000:011B db 63h ; c
seg000:011C db 6Bh ; k
seg000:011D db 6Dh ; m
seg000:011E db 65h ; e
seg000:011F
db 2Ch ; ,
seg000:0120 db 20h ;
seg000:0121 db 31h ; 1
seg000:0122 db 30h ; 0
seg000:0123 db 20h ;
seg000:0124 db 63h ; c
seg000:0125 db 68h ; h
seg000:0126 db 61h ; a
seg000:0127 db 72h ; r
seg000:0128 db 73h ; s
seg000:0129 db 2Ch ; ,
seg000:012A db 20h ;
seg000:012B db 6Eh ; n
seg000:012C db 6Fh ; o
seg000:012D db 20h ;
seg000:012E db 68h ; h
seg000:012F db 65h ; e
seg000:0130 db 78h ; x
seg000:0131 db 65h ; e
seg000:0132 db 64h ; d
seg000:0133 db 69h ; i
seg000:0134 db 74h ; t
seg000:0135 db 6Fh ; o
seg000:0136 db 72h ; r
seg000:0137 db 20h ;
seg000:0138 db 61h ; a
seg000:0139 db 6Ch ; l
seg000:013A db 6Ch ; l
seg000:013B db 6Fh ; o
seg000:013C db 77h ; w
seg000:013D db 65h ; e
seg000:013E db 64h ; d
seg000:013F db 3Ah ; :
seg000:0140 db 20h ;
seg000:0141 db 24h ; $
seg000:0142 db 0Dh ;
seg000:0143 db 0Ah ;
seg000:0144 db 77h ; w
seg000:0145 db 72h ; r
seg000:0146 db 6Fh ; o
seg000:0147 db 6Eh ; n
seg000:0148 db 67h ; g
seg000:0149 db 24h ; $
seg000:014A db 0Dh ;
seg000:014B db 0Ah ;
seg000:014C db 0Dh ;
seg000:014D db 0Ah ;
seg000:014E db 72h ; r
seg000:014F db 69h ; i
seg000:0150 db 67h ; g
seg000:0151 db 68h ; h
seg000:0152 db 74h ; t
seg000:0153 db 24h ; $
seg000:0154 db 0Dh ;
seg000:0155 db 0Ah ;
seg000:0156 db 0Ah ;
seg000:0157 db 0 ;
seg000:0158 db 0 ;
seg000:0159 db 0 ;
seg000:015A db 0 ;
seg000:015B seg000_15B:
seg000:015B mov ah, 9
a questo punto vi starete chidendo cos'è quella
roba, già infatti me lo sono chiesto anche io, se guardiamo meglio vediamo che non è
altro che una stringa ASCII, ma perchè allora IDA non l'ha messa nella sua forma
caratteristica? Perchè lei c'ha capito meno di noi ;)))) per aiutarla a comprendere
meglio possiamo fare una cosa, selezionate col mouse tutto il tratto da seg000:0106 a seg000:015A,
a questo punto premete "C"...et voilà ecco la nostra stringa ASCII in
una forma più "umana" ;):
seg000:0106
str->JaxFiteDuFleurC db 'jax
fête du fleur crackme, 10 chars, no hexeditor allowed: $'
seg000:0106 db 'wrong$',0Dh,0Ah
seg000:0106 db 0Dh,0Ah
seg000:0106 db 'right$',0Dh,0Ah
seg000:0106 db 0Ah,0
seg000:0158 db 3 dup(0)
e questo ci aiuta non poco a capire il codice, bhè se ci da una
mano nel caso di una stringa ASCII figuratevi quanto ci può aiutare nel caso di un po'
codice, se non l'avevate capito questa opzione serve principalmente a convertire il codice
mal interpretato. Per riconoscere questi pezzetti un po' "pazzi" possiamo
avvalerci dell'aiuto che ci da IDA con i colori, infatti lei marca di bianco su sfondo
nero l'indirizzo di questi pezzi cioè:
seg000:014F
seg000:0150
seg000:0151
seg000:0152
seg000:0153
db 69h ; i
db 67h ; g
db 68h ; h
db 74h ; t
db 24h ; $
a parte le battutacce di prima, è assolutamente normale che IDA
confonda il codice che riceve, per aiutarla nella sua missione possiamo disattivare
l'opzione "Make final analysis pass" che si trova nel menu "Kernel options
1", può comunque essere utile disattivare nello stesso pannello anche l'opzione
"Coagulate Data Segments", se vi dovesse capitare un programma completamente
marcato di bianco non vuol dire che IDA non c'ha capito 'na mazza ma significa che è
stato crittato/packato, in tal caso dovete decomprimere il programma oppure, da bravi
reverser quali siete, reversare l'algoritmo di crittazione e creare un piccolo
decrittatore in IDA C.
In pratica converte un byte in una word, se lo riapplichiamo
lo converte in una doubleword e così di seguito, se un file non supporta quel determinato
tipo di dato allora si salta al passo successivo
: questa serve a convertire
i byte inesplorati in stringhe, funziona nello stesso modo di Code, solo che fa
interpretare ad IDA il codice, non come opcodes, ma come caratteri ASCII
Array: ci permette di dichiarare la grandezza di un array
Undefine: selezionate del testo, cliccate su Undefine e come per magia
tutti byte selezionati verranno convertiti ad inesplorati, vi starete chiedendo: "Ma
a che mi serve convetire ad inesplorato un determinato numero di byte?". Posso
assicurarvi che vi può servire e come, per esempio IDA a volte scambia dei byte con altri
tipi di istruzioni e poi ce li presenta in modo errato, vedere per credere:
seg000:0157
db 20202020h;
seg000:0158 db 20202020h;
seg000:0159 db 20202020h;
sapete cosa è successo? Dovete sapere che 20h
equivale, in ASCII, al carattere di spaziatura, IDA quindi ha commesso l'errore di
definire una probabile stringa ASCII come 3 doubleword, come fare a rimediare? Semplice,
non dobbiamo far altro che convertire quei dati in caratteri ASCII, per farlo sarebbe
necessario selezionare le tre db e premere "A" ma non possiamo farlo in quanto
IDA non considera quei byte come solitari o inesplorati ma li ha già segnati come db,
possiamo rimediare al problema selezionando col mouse le tre doubleword, premendo
"U" per Undefinirle e poi "A" per dire ad IDA che li deve considerare
come caratteri ASCII, l'errore che vi ho mostrato non accade più dalla versione 3.82
però questo era un esempio per farvi capire la funzione di questo comando, vi capiterà
spesso infatti di doverlo usare.
Rename: abbiamo già usato questa funzione ma abbiamo delle sotto-opzioni
disponibili e precisamente sono:
Include in names list: IDA ha una gamma di nomi predefiniti e questi sono:
sub_
instruction, subroutine start
locret_ 'return' instruction
loc_ instruction
off_ data, contains offset value
seg_ data, contains segment address value
asc_ data, ascii string
byte_ data, byte (or array of bytes)
word_ data, 16-bit (or array of words)
dword_ data, 32-bit (or array of dwords)
qword_ data, 64-bit (or array of qwords)
flt_ floating point data, 32-bit (or
array of floats)
dbl_ floating point data, 64-bit (or array
of doubles)
tbyte_ floating point data, 80-bit (or array of
tbytes)
stru_ structure (or array of structures)
algn_ alignment directive
unk_ unexplored byte
Attivando questa opzione IDA inserisce un
nome nella lista che sta qua sopra.
Public name: possiamo dichiarare un nome pubblico, se il file supporta la
direttiva "public" allora il nome sarà utilizzato da IDA altrimenti sarà
mostrato solo come commento.
Autogenerated name: nome autogenerato dal
programma, appari di un colore marroncino e scompare da solo se "undefiniamo" i
byte di quel nome
Weak name: stessa
cosa di public name, se il programma supporta la direttiva "weak" allora IDA lo
userà altrimenti il nome sarà mostrato come commento
Create name anyway:se
per qualche masochistico motivo volessimo creare un nome nonostante ne esista uno identico
allora dovremmo checkare questa opzione per permettere alla povera IDA di aggiungere un
suffisso al nome pre-esistente
Operand types: altro
menu stramaledettamente carico di opzioncine, ne elencherò solo le più incomprensibili
visto che le altre sono estremamente....Intuitive, questo menu contiene sia le opzioni di
IDA 3.8 che quelle di IDA 4.0 in questo modo trovere una referenza per tutti e due i
programmi:
Convert to number
Convert to hex number
Convert to decimal number
Convert to binary number
Convert to octal number
Convert to character
Mark as variable
Convert to segment
Convert to offset (DS)
Convert to offset (CS)
Convert offset by any segment
Convert offset by any user-specified base
Convert to struct offset
Convert to enum
Convert to stack variable
Change sign of operand
Bitwise negate
User-defined operand
Mark as variable<"#Mark as variable">: questa opzione marka e unmarka i
byte selezionati come variabili, le variabili si riconoscono perchè iniziano con un
asterisco "*"
Convert to offset (DS): questo comando converte un operando di un'istruzione corrente in un
offset del segmento dati (DS) corrente
Convert offset by any segment: stessa cosa di sopra solo
che converte un operando in un offset di qualunque segmento
Convert to struct offset: questo comando converte un operando
di un'istruzione corrente in un offset all'interno di una specificata struttura.
Bitwise negate: nega l'operando bit a bit
Poi abbiamo la sezione "Structs"
che serve a creare e modificare delle strutture, se non sapete cosa sia una struttura vi
faccio il solito esempio, ecco allora una struttura in C (è un esempio e non funziona
serve solo a farvi capire :):
una struttura non è altro che una variabile in grado di
contenere più tipi di dati, se per esempio state realizzando uno sparatutto e dovete dare
delle caratteristiche ad un mostro di un qualche quadro allora potreste creare una
stuttura simile a questa:
struct Mostro{
char nome[20];
int forza;
long proiettili;
};
nella quale definite con comodità il nome del
mostro, la forza ed il numero di proiettili che si porta dietro, come potete vedere abbiamo inserito
tre tipi di dati (char, int e long) in una sola variabile, potemmo poi modificare a
piacere simili dati durante il gioco semplicemente richiamando tale struttura (vedi
esempio in alto. Adesso che sapete cos'è una struttura potete facilmente intuirne la
comodità, però potreste avere un dubbio del tipo: "Ma cosa mi interessa sapere a
che servono e cosa sono le strutture?". Conoscere le strutture vi serve perchè i
programmatori ne fanno un uso smodato nei loro programmilli maledetti e ce le ritroviamo
nel disasm ogni volta, il problema non si sarebbe posto se IDA le avesse ricreate
autonomamente ma, dal momento che non lo fa, la dobbiamo come al solito aiutare noi (anche
se non serve a nulla ricreare una struttura tranne che alla nostra comodità). Supponiamo
di aver disassemblato un programma che usi delle strutture e del quale possediamo i
sorgenti, lo guardiamo a fondo e notiamo che nel frammento di codice dove ci dovrebbe
essere la struttura "Mostro" c'è solo una serie di confusi numeri.....Niente
paura, clicchiamo sul menu "View | Structures" ed iniziamo a ricreare manualmente la struttura presente
nel nostro sorgente, nella finestra dove ci troviamo premiamo "Ins" e scriviamo
il nome della nostra struttura cioè: Mostro, premiamo poi "D" per creare un
membro della struttura e chiamiamolo come il primo membro della stessa del nostro sorgente
cioè: Nome, clickiamo con il mouse una riga sotto al nuovo membro e inseriamo gli altri
due membri, una volta finito avrete qualcosa del genere:
0000 Mostro struc
0000 Nome db ?
0001 Forza db ?
0002 Proiettili db ?
0003 Mostro ends
notate qualcosa? Credo di si, abbiamo commesso due
errori, il primo è che il membro "Nome" non possiamo definirlo come byte dal
momento che nel sorgente era definito come array (char nome[20]; ) dobbiamo quindi
tramutare "db ?" in un array, per farlo è sufficiente cliccare una volta sulla
riga del membro che ci interessa e poi premere "A", ci apparirà un menù nel
quale ci verrà chiesto di inserire la grandezza di questo array, inserite 20 e così
abbiamo risolto il primo errore, vedete anch il secondo? Credo di si, se date uno sguardo
al sorgente vedrete che il membro proiettili era definito come long (long proiettili), quindi in questo caso
dovremo cambiare "db ?" in "dd ?" per farlo cliccate come al solito
sulla riga che vi interessa e premete "D", in questo modo "db ?"
diventerà prima "dw ?" e poi "dd ?". Abbiamo ora creato la struttura,
ma come la inseriamo nel disasm? Semplice, chiudiamo la finestra che stavamo usando,
clicchiamo nel punto di codice che ci interessa ed andiamo nel menu "Edit | Operand types | Struct offset...", o più semplicemente premiamo
"T" ;)))) e tadàààà come per magia se l'avete messa al punto giusto il
disasm muterà leggermente divenendo un po' più leggibile, avrete in sostanza dei
cambiamenti tipo questi:
prima: push dword ptr [esi+1]
dopo: push dword ptr
[esi+Mostro.Name+1]
Prima di concludere la spiegazione vorrei aprire una parentesi sul significato
dell'istruzione PTR, se avete mai programmato in assembly, disassemblato o debbuggato
qualcosa allora avrete sicuramente visto delle istruzioni come queste:
mov eax, dword PTR [00454898]
mov byte PTR[esi],
0
mov word PTR[qualcosa],
ebx
mov word PTRqualcosa,
ebx
mov ecx, word PTR
[esi+71]
mov [ebp+00402588], byte PTR 95h
mov cl, byte PTR[eax]
tutte queste instruzioni hanno in comune una cosa cioè
l'istruzione PTR, ma vi siete mai chiesti cosa significa? Bhè se l'avete fatto e non
avete trovato risposta ecco che vi do la soluzione, la Intel ci mette a disposizione tre
parole riservate cioè: Byte PTR, Word PTR, Dword PTR, bene, queste significano
rispettivamente: PoinTeR to a byte,PoinTeR to a word, PoinTeRto a doubleword, cioè
puntatore ad un byte, ad una word, ad una doubleword e DEVONO essere usati quando facciamo
uso di un puntatore ad una costante, spieghiamo però il concetto di puntatore: allora
facciamo un esempio:
stringa db
"+Malattia è un grande",0
mov eax, 12345678h
mov eax, offset stringa
cosa c'è di differente tra queste due istruzioni? Molto
vi dirò io, la prima istruzione muove in EAX il valore 12345678h, quindi se nella
finestra di un debugger a caso ;) guardiamo il registro EAX, oppure scriviamo "?
eax" ci verrà stampato qualcosa come: 12345678
0305419896 "ô4Vx", ciò significa che eax CONTIENE il valore
12345678h, nella seconda istruzione invece, eax PUNTA ad una stringa, cosa significa
punta? Significa che se andiamo a scrivere "? eax" non vedremo il testo della
stringa bensì un numero tipo questo: 00404F95.....Mmmmm ma allora la stringa dove
sta?????? Bhè la stringa che come dicevamo non è CONTENUTA in eax ma PUNTATA da eax si
trova all'offset 00404F95, quindi se la vogliamo vedere non dobbiamo far altro che
scrivere nel debugger "d eax" oppure andare a spulciare con lo stesso comando a
quell'offset. Ma se invece di far puntare la stringa da eax la volessi spostare in eax
come dovrei fare? Semplice, ora dobbiamo far uso dell'istruzione PTR, infatti non potremmo
scrivere "mov eax, stringa" perchè riceveremmo errore, possiamo però scrivere:
"mov eax, Dword PTR stringa", in questo modo spostiamo in eax una doubleword (4 byte) ed
andando a verificare il contenuto di eax con "? eax" vedremmo le lettere:
"+mal"....No non è esatto, per il modo in cui funzionano i nostri processori
vedremmo scritto "lam+"....al contrario in pratica........eax conterrà solo 4
lettere questo perchè eax è grande 4 byte, quando i processori saranno a 64 bit avremmo
i registri di 8 byte :), se invece avessimo voluto spostare in eax solo un byte della
stringa avremmo dovuto scrivere "mov eax, byte ptr stringa". Il motivo per il
quale si usano i puntatori è proprio perchè anche volendo non potremmo spostare in un
registro 200 parole......Se siete bravi ragazzi ora vi dovreste chiedere: "E se io
volessi spostare il valore che punta eax in un altro registro come potrei
fare?"...Bhè se dite che si possa fare così: "mov ebx, eax" allora avete
sbagliato, però potete scrivere questo: "mov ebx, [eax]" oppure "mov ebx,
dword ptr [eax]", rispettivamente significano: muovi in ebx il valore puntato da eax,
qualunque esso sia e di qualunque dimensione finchè non riempi ebx, e l'altra: muovi in
ebx la prima dword (quindi i primi 4 byte) puntata da eax, ciò significa che in tutti e
due i casi avremmo lo stesso risultato, cioè ebx contenente "lam+" però la
prima istruzione è più veloce. Vi faccio un altro esempio di utilizzo dell'istruzione
PTR e poi vi spiego l'uso dell'istruzione "lea".
.386p
.model flat, STDcall
extrn ExitProcess:Proc
.data
stringa
db 2Bh, 6Dh, 61h, 6Ch, 61h, 74h, 74h, 69h, 61h ;
questi numeri in ASCII significano
; +malattia
.code
start:
xor ecx, ecx
continua:
mov al, byte ptr stringa+ecx
inc ecx
cmp ecx, 9
jne continua
call ExitProcess
end start
Sapete cosa fa il programmillo qui sopra? Azzera ecx, inizia un conto da zero ed ogni
volta carica in al un byte della nostra stringa, come potete vedere copia esattamente un
byte (byte ptr) della stringa + ecx, cos'è quel +ecx? Se guardate la stringa in alto
notate che ho suddiviso ogni lettera, se poi guardate anche il programma notate che ad
ogni loop incrisa ecx, quindi al terzo loop al conterrà la stringa+3, cioè 61h,
capitooooooo...Se no rileggete questo pezzo...;) Se avessi voluto spostare due lettere in
ax ad ogni loop avrei dovuto cambiare l'istruzione in questo modo: mov ax, byte ptr
stringa+ecx, se invece ci avessi voluto spostare una intera doubleword allora avrei dovuto
scrivere: mov eax, dword ptr stringa+ecx....bhè basta parlare di PTR, l'ha capito mio
fratello, non lo capite voi!!!!!! ;))))))
Per caricare un puntatore in un registro possiamo usare anche l'istruzione LEA (Load
Effective Address) che funziona in questo modo:
"lea eax, stringa", se al posto di lea avessimo utilizzato mov il compilatore
ci avrebbe dato errore ma così non lo fa perchè "lea eax, stringa" significa:
carica in eax l'offset di stringa, il risultato ottenuto usando lea e mov è uguale ma è
ottenuto a due velocità di esecuzione estremamente differenti, la prima infatti è molto
più veloce essendo l'offset una costante già nota all'assembler, mentre la seconda va
più pianino ;)) per chi non lo sapesse la maggioranza dei compilatori di alto livello
(leggi: microsoft visual c++) sostituiscono tutte le istruzioni come "mov reg,
offset xxx" con "lea reg, xxx" rallentando di molto l'esecuzione.
Credo di aver chiarito l'uso della funzione.
"
Enums"
da qui possiamo definire un tipo di Enum, in tutti i linguaggi spesso si usa fare delle
enumerazioni di qualche genere, ecco un esempio di enumerazione in C:
int È_una_bella_giornata(Calore_t caldo,
stagione_t stagione) {
if ( (stagione==Primavera||stagione==Estate)
&& caldo==LAMPADA||caldo==SOLE) return TRUE;
if (
(stagione==Inverno||stagione==Autunno) && caldo==GHIACCIO||caldo==GRANDINE) return TRUE;
return FALSE;
}
Durante il disasm verrebbero mostrati a schermo
dei numeri e non dei nomi come "Sole, Estate, Ghiaccio" quindi per nostra
chiarezza sarebbe meglio poter vedere scritto "Sole, Estate, Ghiaccio" invece di
1,2,3 ecc....Andate quindi nel menu "Options | Enumarations", apparirà una
finestra molto simile a quella di prima nella quale dovrete ricreare l'enumerazione, in
quale modo? Premete "ins" e create come prima cosa i valori booleani
"False/True", scrivete quindi "Boolean Value" e premete invio, per
inserire un membro in questa enumerazione andate con il mouse sull'ultima riga e premete
ctrl+N, scrivete "False" e come valore lasciate 0, spostatevi di nuovo
sull'ultima riga e scrivete "True" dando 1 come valore, create poi un'altra
enumerazione e chimatela Calore, e così via fino ad avere qualcosa di simile:
da adesso in poi la pappa è sempre la stessa,
cercate il punto dove volete applicare l'enumerazione, ci clickate col mouse, aprite il
menu "Edit | Operand types | Enum member...", oppure premete "M", e la inserite dove vi serve ;)))
"Functions" da qui
possiamo aggiungere ed editare le varie funzioni, non vi illustro il funzionamento in
quanto è qualcosa di troppo intuitivo
(Re)name any address...:
indovinate un po'? Da qui potete nominare/rinominare/cancellare un indirizzo
Variable:
uguale a "Mark as Variable"
Jump table...: se troviamo un jump
indiretto da una tavola possiamo informare IDA della grandezza e dell'indirizzo di questa
tavola di modo che possa continuare la sua analisi convertendo per esempio in codice tutti
quegli indirizzi riferiti a quella tavola
Alignment...: questo comando
consente di creare una direttiva di allineamento, questa direttiva rimpiazzerà il numero
di byte inutili inseriti dal linker del programma per allineare il codice ed i dati in un
paragrafo, possiamo provare a selezionare un'area che desideriamo convertire ed IDA
proverà ad individuare un allineamento corretto
Manual instruction...: Questo
comando ci permette di specificare la rappresentazione di un'istruzione o di un dato in un
programma
Hide/show item: Questo comando ti
permette di nascondere e mostrare un'istruzione o un dato
Hide/show border: Se attivata
nasconde il sottile bordo creato da IDA per dividere le istruzioni dai dati
Menu "Navigate"
di questo spiegherò solo pochissime funzioni le più utili e meno comprensibili come
solito.
Empty navigation stack: vuota lo stack di navigazione
che usa IDA
Jump to... | Problem...: salta alla prima funzione che IDA dichiara affetta da problemi i quali
sono:
Can't find offset base
Can't find name
Can't find alternative
string for an operand
Can't find comment
Can't find references
Indirect execution flow
Can't disassemble
Already data or code
Execution flows beyond
limits
Too many lines
Attention! Probably
erroneous situation
Decision to convert to
instruction/data is made by IDA
Search for | next void: i void sono delle istruzioni alle quali dobbiamo stare
attenti dal momento che IDA non è in grado di riconoscere se si tratta di Offset oppure
di Numeri, con questo comando li andiamo ad esaminare per vedere se possiamo risolvere noi
il problema
Search for | text....: cerchiamo del testo come ad esempio una stringa del tipo "sei un
coglione hai sbagliato la password"
Search for | text in core....: la stessa cosa di sopra solo molto più veloce
Search for | not function: cerca i primi byte che non sembrano una funzione
Menu "View" anche di questo
spiegherò le funzioni più importanti:
Functions: ci
mostra tutte le funzioni chiamate dal programma
Enumerations: questo comando apre la
finestra "enum" dalla quale possiamo cambiare, aggiungere e modificare le
enumerazioni.
Problems: elenca tutti i problemi che IDA ha avuto durante il disasm e che
dobbiamo cercare di fixare a mano.
Open Disassembly window: apre la finestra
che contiene il disasm
Open names window:apre la finestra che contiene
tutti i nomi che noi o IDA abbiamo dato alle funzioni tipo API
Open function window: apre la finestra che contiene tutti i nomi che noi abbiamo dato ad
una qualunque subroutine
Open names signature: apre la finestra con le FLIRT Signature
Open segments window: apre una finestra che ci elenca tutte le sezioni o i segmenti
utilizzati da un file e relative informazioni
Open cross references signature: Elenca tutte le Xrefs del disasm, davvero molto utile e comoda
Open structures signature: apre una finestra contenente tutte le strutture da noi
ricostruite, possiamo inserirne o toglierne a piacimento
Open Enumeraton signature: apre
la finestra che contiene tutte le Enum
Open segments window: apre una finiestra che contiene i valori di tutti i segmenti
Open problems: vedi
qualche riga più sopra alla voce "Problems"
Open calculator: apre
un'utilissima calcolatrice
Execute an IDC File:
apre ed esegue un file .IDC
Statuts: cosa
sta facendo IDA? Se è verde significa che non sta facendo niente, gialla che sta pensando
ma nel frattempo possiamo scorrere il disasm, rosso che non possiamo fare nulla.
Oki e questi sò finiti adesso manca il menu più lungo quindi se siete stanchi di
leggere andate a riposarvi e continuate domani mattina, questo menu è sostanzialmente
invariato rispetto alla versione DOS di IDA, l'unica cosa un po' differente è che nel
menu "General" sono raccolti la maggior parte di questi sottomenu, i nomi
delle varie opzioni e le loro funzioni restano comunque totalmente invariati.
Options:
Text
representation...
Cross references...
Assembler directives...
Name representation...
Demangled names...
ASCII strings style...
ASCII strings options...
Colors...
Dump/normal view
Setup data types
Processor type...
Target assembler...
Analysis options...
Search direction
Allora allorino continuiamo questa tediosa seduta parlando delle
"opzioni" vere e proprie riguardo la configurazione del programma, salterei
volentieri questo passaggio per volare felice alla conclusione ma "ho una forza
dentro che neanch'io so spiegare come" (by Geloso Yole ®) mi trattiene su questa
sedia a scrivere un inutile tutorial che molto probabilmente sarà letto da poche decine
di persone..;(((
Bando alle ciance: Text
representation...: menu a dir poco arzigogolato che ci presenta una miriade di
piccole opzioncine che andremo ora ad esaminare.
Line prefixes: se
la disattiviamo vedremo scomparire gli indirizzi davanti ai byte
Number of opcode bytes: gli opcode sono i codice esadecimali di ogni istruzioni, IDA di default ne
rappresenta 6 e se l'istruzione ne richiede di più allora aggiunge un "+" alla
fine (c'è caduta pure la rima ;)
Use segment names: abilita o disabilita la nomenclatura dei segmenti così se abilitato
vedremo "segment:3200" invece di "3000:3200"
Segment addresses: se è abilitata vedremo il segmento davanti ad un indirizzo:
"segment:3000" invece di un semplice "3000", questa la tengo
disabilitata che fa meno bordello
Display 'void' marks: fa comparire dei segni di
riconoscimento davanti ai "void"
Display empty lines: se abilitata fa comparire delle linee vuote diminuendo lo spazio utile sul
monitor
Display borders between data/code: se disabilitato fa scomparire la linea separatrice tra i dati ed
i segmenti
Display bad instructions <BAD> marks: tutti i processori hanno
le loro belle funzioni non documentate e quando una di queste passa sotto il disasm il
programma dice "e tu ki kazzo sei?", IDA è molto sensibile da questo punto di
vista e se trova una funzione che non conosce prova ad identificarla ma se proprio non ci
riesce la marchia con tanta ferocia quanto un tedesco ad un povero ebreo e con un bel
tatuaggio rosso le scrive sopa <BAD>
Use tabulations in output: con questa opzione abilitata
IDA disabilita il conosciutissimo tab-stop (0x09) nel suo file di output
Questi mi sembrano intuitivi:
Display comments
Display repeatable comments
Display auto comments
Instructions indention: letteralmente: spaziatura
delle istruzioni, settate questo valore a 2-3
Comments indention:
regola la "rientranza" dei commenti
void's low limit: vedi sotto
void's high limit: questi due valori sono molto importanti in quanto dichiarano se una
istruzione è oppure no un "void", un oggetto è detto "void" se ha un
valore immediato come operando o parte di un operando e questo valore immediato è tra
quelli che definiamo noi nei box "low limit" e "high limit", è questa
l'importanza dei due box
"Cross references" ecco le opzioni che ci mette a disposizione questo menu:
Display segments in xrefs: ecco la differenza tra
abilitato e disabilitato:
Display xref type mark: mette un "segnale" su ogni xref
Display function offsets: bho! Ci mostrerà gli offset delle
funzioni? ;)))
Display xref values: su questa poi non so che dirvi, forse mostra i valori dell xref ;)))
Right margin: opzione di difficilissima
interpretazione può darsi che sia la misura del
margine destro, ma chissà le vie del signore sono infinite ;))))
Cross reference depth: questa invece è un po' più seria e per non confondervi vi riporto
l'esempio dell guida, l'autore suppone di avere un array:
A db 100 dup(0)
se alcune istruzioni si riferiscono all'elemento 5-th dell'array:
mov al, A+5
con TD=3 non ci verranno mostrate xrefs
con TD=10 ci verranno invece mostrate
Number of xrefs to display: la funzione è ovvia, alzatela però di un po'
"Assembler
directives...": altro piccolo menu
ASSUME directives: questa opzione attiva o disattiva la generazione di alcune direttve quali
"assume" e "origin", lasciatela abilitata può essere utile nel
ricostruire il sorgente
.ORG directives:
la stessa cosa di prima solo che è valida per i file .com
"Name
representation...": poco da dire, qui ci
viene chiesto in che modo chiamare i vari segmenti e credo che l'opzione di default sia la
migliore, le altre 4 opzioni riguardanti i tipi di nomi non saranno analizzate in quanto
le ho già spiegate prima
"Demangled
names...": IDA può rimettere a posto i
nomi maciullati (hei la guida dice proprio maciullati=mangled) dai compilatori C++
ed in questo menu ci chiede come farlo, se sotto forma di commento, di nome o di macellato
;)), il setup dei nomi lunghi e corti lo lascio a voi in quanto è facilissimo
"ASCII strings
style...":
in questo menu possiamo definire lo stile delle stringhe e possiamo anche definirne
una al momento che ci serve, qui non c'è altro da spiegare
"ASCII
strings options...": ahhhhhh!!! Uno dei menu
che mi fanno godere di più aahhhhhhhh, sembro Homer ;) ecco cosa possiamo fare:
Generate names: le vogliamo le stringhe ASCII? Allora abilitiamola ;)
Names prefix:
cambiate questa maledetta stringa e metteteci qualco's altro
Mark as autogenerated: segna la stringa come autogenerata
Serial: genera
un nome in maniera seriale e progressiva
Serial width:
specifica la lunghezza del nome generato
ASCII next line char: distanza minima alla quale
troveremo il prossimo carattere ASCII
"Colors...": scegliete la palette che volete usare
"Dump/normal
view": vi fa scegliere tra la visuale come editor esadecimale e
quella come disassembler
"Setup data
types": vi chiede che tipo di dati volete usare
nel comando MakeData
Oki i menu sono finiti, ed io spero di avervi fatto capire il funzionamento di
IDA, a presto
Beh, stavolta mando un FUCK di dimensioni ingloriose:
1) A Mamma Micro$oft perchè Frontpage 2000 è bacato, il 98 un po' meno, ma è cmq
tanto idiota da non capire che "Ingrandire un carattere" NON significa
"Rimpicciolisci tutto il resto del documento per far sembrare il carattere da
ingrandire più grande....
2) A quell'imbecille della mia vicina di casa che ha picchiato il mio corvo solo per
colpa delle sue inutili fobie
3) A quella che abita sotto la mia vicina....Ma lo vuoi capire che il mio corvo NON
mangia gerani?
4) Al furetto che inseguo oramai da mesi e che non prenderò mai.
5) Ai pirla che fanno casino sul forum
Grazie e tanti saluti a:
Olga, Annalisa, Caterina, Magritte, Chiara (tutte e due), Elena, Claudia (tutte e due),
Dalila, Jammina, Vision, Natacha, Daniela, Maura (a tutte e due), Valentina (a tutte e
tre), Lara, Maria, Mamma, Anna, Cassiopea, Lisi, Celeste...Passiamo agli uomini: Tin_Man,
N0bodY88, Phobos, Andreageddon, Nedda, Spinone, Nerds, eTTax, Xoanino, Malattia, Kill3xx,
LittleJo, Alor, Along3x, Rigor_Mortem, Master, Steve, Alt255, Xunil, Chrome, Cek, Diego,
DiLuzio, Metboyz, Sume, Pirozzi, Oldiron61, Jamil, \Spirit\, Kalkutta, Mayhem, Nikdh,
Pispola, Wizche.....Beh, ora non me ne vengono più in mente :) Abbiate pietà di me e
sentitevi tutti salutati :)