Con questo articolo diamo un’occhiata a quelle che sono le principali tecniche di debugging in ambito unix per capire meglio il funzionamento del linguaggio assembly.
Due sono i principali strumenti impiegati per questo genere di operazioni:
- Objdump è un tool che viene utilizzato per esaminare i file binari compilati
- Gdb è un debugger per seguire passo-passo il flusso dei programmi compilati
Tramite questi programmi è facile interrogare i registri presenti nella CPU per visualizzarne il contenuto e capire come viene allocata la memoria. Nel nostro esempio ho adottato una classica architettura Intel i386, presente nella maggior parte dei pc. In questo ambiente i principali registri con i quali la CPU lavora sono i seguenti:
EAX: accumulatore
ECX: contatore
EDX: dati
EBX: base
EIP: puntatore alla istruzione successiva
ESP: puntatore della fine dello stack
EBP: puntatore al frame corrente
SPF: riporta EBP al valore precedente
ESI: indice di origine
EDI: indice di destinazione
Per effettuare le interrogazioni adotteremo il comando x, che sta appunto per examine, specificando anche come visualizzare l’output del registro esaminato:
x/x = esamina in esadecimale
x/o = esamina in ottale
x/u = esamina in decimale senza segno
x/d = esamina in decimale
x/s= esamina in stringa
x/i = esamina un istruzione
Inoltre digitando un numero subito dopo lo slash è possibile specificare quanti byte interrogare. Ricordo che ogni lettera ASCII corrisponde ad un byte (ad esempio la lettera “A” corrisponde 0×41 scritto in esadecimale).
Analizziamo ora il nostro primo programma in C firstprog.c:
#includeint main(){int i;for(i=0; i <10; i++){printf("Hello, world!\n");}return0;}
Questo programma non fa altro che stampare 10 volte di seguito la stringa “Hello world”. Tale codice anche se risulta molto semplice e banale, è tuttavia utile a capire in prima battuta il funzionamento dell’assembly. Procediamo dunque con la compilazione:
# gcc -g firstprog.c -o firstprog
Ora che abbiamo l’eseguibile passiamo all’analisi con gdb:
# gdb -q ./firstprog(gdb) set dis intel
(gdb) list
1#include23int main()4{5int i;6for(i=0; i <10; i++)7{8printf("Hello, world!\n");9}10return0;(gdb)
Con l’opzione -q evitiamo di stampare banner inutili, mentre con l’opzione set dis intel abbiamo un output dell’assembly molto più leggibile.
Proseguiamo disassemblando il main e dando una prima occhiata alla locazione della memoria:
Questo output ci mostra la traduzione della funzione maindel nostro programma in assembly. Ad un primo sguardo è possibile capire la sintassi: locazione della memoria in esadecimale: operatore registro destinazione, registro d’origine. Tuttavia in questo momento nessun registro risulta visualizzabile, proprio perchè ancora il programma non è stato effettivamente lanciato. Poniamo dunque break al main, lanciamo l’eseguibile e analizziamo i registri:
(gdb) break main:
Breakpoint 1 at 0x80483d5: file firstprog.c, line 6.(gdb) run
Starting program:./firstprog
Breakpoint 1, main () at firstprog.c:66for(i=0; i <10; i++)(gdb) i r
eax 0xbfe3fc04 -1075577852
ecx 0xbfe3fb80 -1075577984
edx 0x1 1
ebx 0xb800bff4 -1207910412
esp 0xbfe3fb50 0xbfe3fb50
ebp 0xbfe3fb68 0xbfe3fb68
esi 0x8048420 134513696
edi 0x8048310 134513424
eip 0x80483d5 0x80483d5
eflags 0x200286 [ PF SF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51(gdb) i r eip
eip 0x80483d5 0x80483d5
In particolare analizziamo il registro l’eip, il quale punta alla prossima istruzione da eseguire, analizzando la memoria sulla quale sta puntando. Entrambi i comandi mostrano lo stesso output proprio perchè effettivamente stiamo effettuando la stessa richiesta.
A questo punto si vede come al registro puntato di eip c’è una “DWORD PTR [ebp-0x8],0×0“. Tale operazione significa che il valore zero viene allocato alla locazione ebp – 8 ossia 0xbfff7d10 che al momento contiene 0xb7febf50:
(gdb) i r ebp
ebp 0xbfff7d18 0xbfff7d18
(gdb) x/x $ebp-8
0xbfff7d10: 0xb7febf50
Nella successiva locazione di memoria, ossia all’indirizzo 0x80483dc è presente un salto incondizionato jmp alla locazione 0x80483ee. Verifichiamo, proseguendo di una istruzione nexti, che l’eip successivo sia proprio quello:
(gdb) nexti
0x080483dc 6for(i=0; i <10; i++)(gdb) i r eip
eip 0x80483dc 0x80483dc
(gdb) x/i $eip
0x80483dc : jmp 0x80483ee
Infatti, ora l’eip contiene un salto incondizionato jmp.
Guardiamo adesso le successive 10 istruzioni:
Da questo listato si vede non troppo facilmente per i newbe come viene effettuato il ciclo for.
Inizialmente viene fatto un salto incondizionato sulla locazione 0x80483ee, nella quale viene effettuata una compare cmp, la quale dice che se il il numero presente alla locazione ebp – 8, dove precedentemente era stato inizializzato 0, è minore o uguale di 9 allora esegue l’istruzione successiva jle che risulta essere apppunto un salto condizionato, proprio per via della compare, all’istruzione 0x80483de. Siccome risulta vera, 0 è minore o uguale di 9, rinizia il ciclo.
Nel momento in cui risulterà falsa, ossia ebp – 8 vale 10, allora non effettuerà il salto e continuerà con l’istruzione successiva.
Ora controlliamo proprio che ebp – 8 sia 0 e che quindi jle faccia il salto condizionato
Sia con il metodo print che analizzando con x/x direttamente sulla locazione di memoria, che con x/d lo visualizziamo in decimale, controlliamo che ha valore 0 e che quindi il jle ha condizione positiva per effettuare il salto.
Ora invece cerchiamo di capire cosa fanno le altre operazioni. Analizzando la locazione 0x80483de è presente un’istruzione che essenzialmente muove il valore dell’indirizzo 0x80484d0 nell’indirizzo esp. Verifichiamo quindi cosa contiene l’indirizzo 0x80484d0:
Notiamo che contiene il valore 0x6c6c6548 che codificato in ASCII corrisponde ad “Hello,” infatti tramite l’opzione c effettua la codifica di ogni singolo byte in ASCII mentre con s converte proprio tutta la stringa.
Per ultimo l’istruzione alla locazione 0x80483ea non fa altro che incrementare di uno il valore all’interno di ebp – 8, la quale poi viene poi controllata dal salto condizionato.
Giusto per completare il discorso, procediamo con diversi nexti fino alla fine del del ciclo for ossia quando l’ebp – 8 contiene il valore 10. Infine controlliamo che l’eip punta all’uscita del ciclo:
(gdb) x/d 0xbfff7d10
0xbfff7d10:10(gdb) i r eip
eip 0x80483ee 0x80483ee
(gdb) x/10i $eip
0x80483ee :cmp DWORD PTR [ebp-0x8],0x9
0x80483f2 : jle 0x80483de
0x80483f4 : mov eax,0x0
0x80483f9 : add esp,0x14
0x80483fc :pop ecx
0x80483fd :pop ebp
0x80483fe : lea esp,[ecx-0x4]
0x8048401 : ret
0x8048402: nop
0x8048403: nop
(gdb) nexti
0x080483f2 6for(i=0; i <10; i++)(gdb) i r eip
eip 0x80483f2 0x80483f2
(gdb) nexti
10return0;(gdb) i r eip
eip 0x80483f4 0x80483f4
Possiamo vedere dall’output come appunto l’eip punta 0x80483f4 e non più 0x80483de come nei precedenti casi, confermando appunto l’uscita dal ciclo for.
Bene ora che abbiamo appreso i concetti base del debugging immaginate cosa potrebbe capitare se un attaccante riesca a sovrascrivere l‘eip facendolo puntare ad una locazione di memoria arbitraria a lui congeniale!
Potrebbe essere necessario nelle varie configurazioni di dover montare sul proprio hd delle macchine remote con samba.
Ad esempio personalmente ho avuto la necessità di farlo per poter utilizzare delle macchine remote con VMware. Quindi per caricare la macchina è necessario dargli il file vmx che appunto risiede nel file system remoto.
Per far questo ho dovuto quindi montarlo tramite il comando:
# mkdir /mnt/samba# mount -t smbfs -o username=xxxx,password=yyyy //z.z.z.z/directory_condivisa /mnt/samba/
Per renderlo eseguibile ad ogni avvio della macchina, evitando così di scriverlo ogni volta, basta semplicemente aggiungere in /etc/fstab la seguente riga:
Inoltre non dimentichiamoci di aprire la 445 del nostro firewall.
Se abbiamo pf basta semplicemente aggiungere la seguente regola:
pass in quick on $int inet proto tcp from $my_net to any port $smb flags S/SA keep state
Dove $int è l’interfaccia di rete, fxp0 nel mio caso, $my_net è la mai rete dalla quale voglio che sia possibile passare mentre $smb è appunto la porta.
Durante la creazione del certificato, andrà benissimo utilizzare le opzioni di default segnalate.
A questo punto non ci rimane che registrare il prodotto per poter aggiornare i plugin di Nessus. Vi sono due alternative: la versione a pagamento (circa $1,200) oppure quella free.
Una volta ottenuta la chiave possiamo registrare Nessus tramite il comando:
L’interfaccia che ci troviamo di fronte risulta essere semplice e intuitiva. Cliccando su “connect” e successivamente su “edit” possiamo definire l’hostname, la porta (di solito la 1241) l’username e la password con i quali accedere al server. Effettuata la connessione, nella parte sinistra possiamo definire il target (range o ip singolo) da scansire mentre in quella destra definiamo la politica della scansione (come le opzioni, i plugin ecc..).
Infine per aggiornare i plugin alle ultime versioni disponibili, basta semplicemente digitare:
Ovviamente lo spazio dipende da quanto abbiamo libero nel nostro hd. Tuttavia 128M, 1024M e 10G rispettivamente dovrebbe andare.
Possiamo fare questo sia con “fdisk /dev/sda” che con “cfdisk“, ricordandoci di mettere il type 82 allo swap e il bootable alla /boot.
Se vi dovesse capitare di installare una vmware server su una macchina windows e dovete poi accedervi da un player esterno: ricordatevi di configurare il firewall!
Il classico errore che compare è il seguente:
Per porre rimedio basta andare nelle impostazioni del firewall e aggiungere un’eccezione indicando la 902 come numero di porta:
Per masterizzare con openBSD è necessario avere installati i seguenti pacchetti:
cdrecord
mpg321
mkisofs
growisofs
Questi pacchetti di solito sono già presenti con l’installazione di default. Tuttavia è possibile recuperarli presso il repository ufficiale oppure nei rispettivi siti che supportano il progetto.
Per prima cosa è necessario individuare il device che utilizzeremo per masterizzare.
#cdrecord -scanbus
Di norma su openBSD il device è /dev/rcd0c. Per controllare che sia quello giusto digitiamo:
Di solito quando mi capitava un warning del genere, era consuetudine riavviare la macchina.
Al boot successivo OpenBSD eseguiva un check in automatico dell’hard disk e tutto andava a buon fine. Di solito questo warning mi capitava quando per svariati motivi staccavo l’hard disk dalla scheda madre.
Ma questa volta mi sono trovato ad una cosa del genere:
automatic boot in progtress: starting file system cheks./dev/rwd0c: file system is clean;not cheking
1366448 DUP I=329986/dev/rwd0c: UNEXPECTED INCONSISTENCY; RUN fsck_fss MANUALLY.
THE FOLLOWING FILE SYSTEM HAD AN UNEXPECTED INCONSISTENCY:
fss:/dev/rwd0c /
Automatic file system chek failed; help!
Enter pahtname of shell or RETURN for sh:
L’output riportato sopra non è quello esatto ma una copia ritrovata in rete, tuttavia è utile per capire che genere di errore mi sono trovato davanti.
Per risolvere questo inghippo è bastato semplicemente premere enter e indicare il tipo di shell, nel mio caso ho adottato la classica “vt200″. Dopo di che è bastato eseguire il seguente comando e riavviare la macchina.
Poco tempo fa stavo stavo dando un occhiata ai miei log e mi sono subito accorto di un attacco brute force sulla porta ssh.
#tail /var/log/authlog
Dec 2619:22:31 maxbsd sshd[12620]: Failed password for root from 218.23.105.25 port 45219 ssh2
Dec 2619:22:31 maxbsd sshd[13293]: Received disconnect from 218.23.105.25:11: Bye Bye
Dec 2619:22:35 maxbsd sshd[6301]:reverse mapping checking getaddrinfo for 25.105.23.218.broad.static.hf.ah.cndata.com [218.23.105.25] failed - POSSIBLE BREAK-IN ATTEMPT!
Dec 2619:22:35 maxbsd sshd[6301]: Failed password for root from 218.23.105.25 port 45474 ssh2
Dec 2619:22:35 maxbsd sshd[2351]: Received disconnect from 218.23.105.25:11: Bye Bye
Dec 2619:22:39 maxbsd sshd[3801]:reverse mapping checking getaddrinfo for 25.105.23.218.bro
Per eliminare questo genere di attacchi possiamo:
Abilitare ssh in modo che dopo 3 tentativi sbagliati cada la connessione
Inserire una regola in pf in modo che non accetti più di 3 connessioni contemporaneamente dallo stesso Ip, per poi bannarlo in una blacklist
Creare uno script in modo che legga l’ip dell’attaccante dai log e lo inserisca nella blacklist.
Per risolvere il primo punto è sufficiente modificare il file di configurazione di ssh, sshd_config, decommentando l’opzione:
# nano /etc/ssh/sshd_config
MaxAuthTries 3
Per il secondo punto è necessario definire una tabella che contenga gli ip bloccati (la nostra blacklist). Dunque editiamo il file di configurazione di pf che si trova in /etc/pf.conf:
#nano /etc/pf.conf
table persist file "/etc/spammers"
Ora invece scriviamo la regola in modo che non accetti più di 3 connessioni contemporaneamente dallo stesso Ip e lo inserisca nella blacklist:
int="fxp0"
ssh_limit="(max-src-conn-rate 3/30, overload flush global)"
pass in log quick on $int inet proto tcp from any to $int port ssh flags S/SA keep state $ssh_limit
Infine per il terzo punto è sufficiente creare uno script, ssh_block_attack, come il seguente, in modo che legga il file di log e metta in blacklist l’indirizzo ip che ha sbagliato la password o l’username:
# nano /root/ssh_block_attack
#!/bin/shwhileread mm dd hms localhostname sshd word1 word2 word3 word4 host1 host2 rest;doif["$word1 $word2 $word4"="Invalid user from"];then
data=`date`
pfctl -t spammers -T add $host1
echo "Aggiunto $host1 a spammers il $data">>/root/ssh_log
elif ["$word1 $word2 $word3 $host1"="Failed password for from"];then
pfctl -t spammers -T add $host2
echo "Aggiunto $host2 a spammers il $data">>/root/ssh_log
fi
done
Per lanciarlo avvio dandogli in pasto il log da monitorare aggiungiamo questa riga in /etc/rc.local .
tail -f /var/log/authlog | sh ssh_block_attack &
Infine se vogliamo vedere la tabella degli ip bloccati è necessario utilizzare questo comando
pfctl -t spammers -T show
Ovviamente per non saturare la nostra macchina ogni tanto andrebbe svuotata: