+++
title = "Studiare un protocollo di comunicazione"
summary = "Step 2: usare uno squalo per sniffare pacchetti"
date = "2024-05-01"

tags = ["Reverse Engineering", "Lettore di Presenze", "TCP", "Sniffing", "Wireshark"]
categories = ["Progetti"]
series = ["Lettore di presenze"]
series_order = 2
+++

Nell'articolo precedente abbiamo iniziato a studiare come funziona il client
del lettore di presenze, andando anche a decompilarne l'eseguibile. In questo
articolo vorrei cercare di comprendere il protocollo di comunicazione che il
client usa per interfacciarsi col lettore.

I motivi per cui non ho fatto subito il reverse engineering del protocollo sono
sostanzialmente due:

1. Se fossi riuscito a decompilare correttamente il codice dell'eseguibile
   avrei potuto creare un client alternativo con molta più semplicità;

2. A volte non si può eseguire (almeno in modo semplice) lo *sniffing* di una
   comunicazione per via del
   [TLS](https://en.wikipedia.org/wiki/Transport_Layer_Security).

Purtroppo però la decompilazione dei DLL non è affatto facile perchè:

> Non esiste un pulsante magico per tornare indietro, ne esiste uno per
> generare codice C di merda con nomi di variabili a caso, ma non è un bel
> bottone
>
> Traduzione di **fasterthanlime** nel video [How does the detour crate
> work?](https://youtube.com/watch?v=aLeMCUXFJwY&t=174s)

Nel caso foste interessati, l'NSA ha sviluppato il suo decompilatore chiamato
[Ghidra](https://ghidra-sre.org/); vi consiglio di darci un occhiata.

## Configurazione del client

Nello scorso articolo abbiamo solo installato il client per Windows, ma non lo
abbiamo mai aperto.

Dato che per intercettare la comunicazione dobbiamo avere un client che vada
effettivamente ad interrogare il lettore, riapro la mia macchina virtuale con
[Windows 10 AME](https://archive.org/details/windows10-ame-21h1-2021-08-09/) e
finisco la configurazione del client:

{{< carousel images="images/01-client-setup/*" aspectRatio="16-9"
interval="1000" >}}

Una volta finita la configurazione (e aver modificato qualche file di
configurazione a mano perchè il client comunque non vedeva il lettore nella
rete) abbiamo la possibilità di poter richiedere le presenze tramite la rete.

Una volta aperto il client **come amministratore**, aver premuto il tasto per
scaricare i dati e aver aspettato **due lunghi minuti**, la bellezza di 3543
presenze compaiono sullo schermo.

Qualcosa mi puzza: perchè impiega due minuti a trasferire l'equivalente di un
file che pesa poco meno di 200kiB?

Facendo un secondo due calcoli:

{{< katex >}}
$$
\frac{3543\ \textrm{righe}}{120\ \textrm{secondi}} \ \cdot\sim460\ \textrm{bit
per riga} = 13.26\ kib/s
$$

13kibps di throughput utile su una connessione da 100Mbps? ***Che schifo!***

Non voglio sapere quale disastro di programmazione aziendale italiana può aver
causato questo, ma ho come l'impressione che sto per scoprirlo...

## *The quieter you become...*

Come analizzatore di reti andrò ad installare ed utilizzare
[Wireshark](https://wireshark.org), uno strumento molto popolare per questo
tipo di operazioni.

Una volta installato ed aver aggiunto il nostro utente al gruppo `wireshark`
possiamo avviarlo ed incominciare a *sniffare* tutti i pacchetti sulla nostra
interfaccia di rete.

![Wireshark in funzionamento](images/02-wireshark-working.png "Ecco Wireshark
mentre ascolta tutti i pacchetti che circolano sulla mia rete")

Se è la prima volta che usate uno strumento del genere, potrete accorgervi che
anche in una rete locale di piccole dimensioni circolano davvero tanti
pacchetti, troppi per essere analizzati uno ad uno.

È proprio qui che entrano in nostro soccorso i filtri: se digitiamo nella barra
dei filtri la seguente stringa:

```
ip.addr == <IP del dispositivo>
```

Vedremmo solo i pacchetti che provengono *da* o sono diretti *verso*
l'indirizzo IP che abbiamo specificato. Possiamo anche decidere di filtrare il
traffico che passa attraverso una specifica porta TCP con:

```
ip.addr == <IP> && tcp.port == <Porta>
```

Il mondo dei filtri in Wireshark è molto vasto, lascio il [link alla
documentazione ufficiale](https://wiki.wireshark.org/DisplayFilters) per gli
interessati.

Una volta fatta partire la registrazione con i filtri corretti possiamo far
ripartire un'altra scansione completa delle presenze sul client ufficiale e
dovremmo vedere tutti i pacchetti che si scambiano client e dispositivo in
tempo reale.

![Wireshark con il filtro per IP](images/03-wireshark-with-filter.png "I
pacchetti scambiati fra il client ed il dispositivo")

Alla fine del processo abbiamo registrato la bellezza di 14423 pacchetti, che
trasportano 3543 presenze. *Il tutto diventa ancora più strano...*

Dando un'occhiata veloce al traffico possiamo intuire un po' di cose:

1. Nel layer di trasporto viene usato il protocollo TCP sulla porta `5005`
2. Non viene utilizzato
   [TLS](https://en.wikipedia.org/wiki/Transport_Layer_Security) per
   crittografare i dati, *phew*
3. Ci sono almeno tre fasi:
   * Una prima fase di inizializzazione;
   * Una seconda fase di scambio di dati nella quale vengono inviati pochi
     pacchetti ma molto grandi;
   * Una terza fase, nella quale vengono inviati moltissimi pacchetti di
     piccole dimensioni, dove si può intravedere di tanto in tanto il nome dei
     dipendenti in formato ASCII.

![Utente "test" nella finestrella ASCII](images/04-test-name.png "Ecco che
compare un nome familiare nella finestrella ASCII, in basso a destra")

Per studiare più approfonditamente il protocollo avremmo bisogno del solo
contenuto dei pacchetti TCP. Qui ci aiuta Wireshark con una funzionalità molto
utile.

Se infatti andiamo a selezionare un pacchetto della comunicazione TCP a cui
siamo interessati e premiamo il tasto destro, selezionando `Follow` > `TCP
Stream`, Wireshark aprirà in automatico il payload di tutti i pacchetti e ci
mostrerà solo il traffico di livello 7.

Se andiamo a visualizzare i dati come `Raw`, Wireshark ci mostrerà i dati
scambiati in formato esadecimale, mostrando in rosso i messaggi inviati dal
client ed in blu le risposte date dal lettore di presenze.

Adesso possiamo copiare le richieste nel nostro text editor di fiducia ed
incominciare a studiare il protocollo.

![Lo stream TCP mostrato da Wireshark](images/05-wireshark-tcp-stream.png "Ecco
come appare lo scambio di messaggi quando si vanno ad aprire solo i pacchetti
TCP")

## *Fuck around and find out*

Ora non rimane che comprendere il protocollo di comunicazione che,
sfortunatamente, non è in un formato testuale come l'ASCII o l'UTF-8.

Può sembrare complesso, ma mi ci è voluto solo un pomeriggio per trovare una
soluzione abbastanza completa per quello che devo fare.

### Richieste

Le richieste inviate dal client sono tutte lunghe 16 byte e hanno questa
struttura:

```rexeg
^55aa([0-9a-f]{24})([0-9a-f]{4})$
```

* I primi due byte sono sempre `55 aa` (`01010101 10101010` in binario);
* I 12 byte successivi specificano il comando del client, che d'ora in poi
  chiamerò "payload";
* Infine, ci sono due byte **little-endian** che specificano il numero del
  pacchetto, iniziando da `00 00`.

Ho notato anche che il server non verifica che gli ultimi due byte siano
inviati in modo sequenziale, quindi possono rimanere a `00 00` per tutto lo
scambio di messaggi.

### Risposte

Le risposte del server invece non hanno una lunghezza fissa e sono divise in
due parti, che d'ora in poi andrò a chiamare "header" ed "payload". L'header è
sempre presente ed è lungo 10 byte, mentre il payload può anche essere assente.

Quando il payload è assente il messaggio si comporta come una specie di
`null`/`ACK`.

```regex
^aa55([0-9a-f]{16})(?:55aa([0-9a-f]+))?$
```

* I primi due byte sono sempre `aa 55` (`10101010 01010101` in binario);
* Gli 8 byte successivi sono l'header. Di solito sono `01 01 00 00 00 00 00
  00`, ma possono cambiare;
* Se è presente un payload, il messaggio continua con `55 aa` (`01010101
  10101010` in binario);
* I byte rimanenti rappresentano il payload.

---

### Ping

Se vogliamo eseguire un "ping" e verificare che il server risponda possiamo
inviare una richiesta col payload impostato a `01 80 00 00 00 00 00 00 00 00 00
00`:

```
55aa0180000000000000000000000100
aa550101000000000000
```

Il server risponderà poi con un pacchetto senza payload con l'header impostato
a `01 01 00 00 00 00 00 00`.

### Nome del dipendente

Sapendo l'ID di un dipendente, è possibile richiedere al server il suo nome
tramite una richiesta con payload impostato a `01 c7 xx xx xx xx 00 00 00 00 14
00`, dove `xx xx xx xx` è un intero a 32 bit **little-endian** che rappresenta
l'ID del dipendente.

```
55aa01c7xxxxxxxx0000000014000100
aa55010100000000000055aaxxxxxxxxxxxxxxxxxxxx4c0000000000595a7c7c0000
```

Se l'header della risposta è impostato a `01 00 00 00 00 00 00 00` allora ciò
significa che lo username non è stato trovato, invece se è impostato a `01 01
00 00 00 00 00 00` allora i primi 10 bit del payload rappresentano il nome del
dipendente.

In caso il nome sia più corto di 10 caratteri lo spazio rimanente sarà riempito
con dei caratteri terminatori `\0`.

Questi messaggi compongono quasi la totailtà della terza fase che ho descritto
nel capitolo precedente, quella nella quale vengono inviati tanti piccoli
messaggi. Questo fa intuire che il client che prima di tutto fa il dump delle
presenze in modo quasi istantaneo , poi aspetta due minuti scaricando **per
ogni presenza rilevata** il nome del dipendente, anche se questo è già stato
richiesto in precedenza. Qualcuno insegni il concetto di
[memoizzazione](https://it.wikipedia.org/wiki/Memoizzazione) a questi
informatici...

### Numero totale di presenze

Per chiedere quante presenze sono registrate sul dispositivo bisogna effettuare
una richiesta con payload `01 b4 08 00 00 00 00 00 ff ff 00 00`:

```
55aa01b4080000000000ffff00000100
aa550101xxxx00000000
```

Dove `xx xx` sarà il numero delle presenze salvate rappesentato in un intero a
16 bit **little-endian**.

65535 richieste massime sembrano un po' troppo poche, ma immagino che sarà un
problema del me del futuro.

### Scaricamento di tutte le presenze

La lista di tutte le presenze va scaricata a blocchi, continuando a chiedere al
server dei blocchi da 1024 byte (ovvero 85,333 presenze alla volta) finchè non
viene estratto il tutto.

Per fare ciò dobbiamo prima di tutto richiedere il numero totale delle
presenze, poi dobbiamo inviare una richiesta con payload `01 a4 00 00 00 00 xx
xx 00 00 00 04`, dove `xx xx` è il numero delle presenze totali
**little-endian**.

```
55aa01a400000000xxxx000000040100
aa55010100000000000055aa ...
```

Il server ci risponderà con un payload da 1026 byte, contenente le prime
registrazioni seguite da due byte a zero.

Possiamo richiedere un altro blocco da 1026 byte inviando una richiesta con
payload `01 a4 00 00 00 00 00 00 xx xx 00 04`, dove `xx xx` è un intero
**little-endian** che parte da `01 00`:

```
55aa01a4000000000000010000040100
aa55010100000000000055aa ...
```

Una volta finite le registrazioni il server incomincerà ad inviare dei byte di
padding impostati a `ff` per arrivare a 1026 byte di payload.

### Struttura delle presenze

Una volta ottenuti tutti i blocchi di registrazioni, possiamo andarli a
scomporre in singole registrazioni da 12 byte ciascuno. Non sono riuscito a
comprendere per cosa stessero tutti i singoli byte, ma quelli importanti sono
questi:

```regex
..([26ae]).{5}([0-9a-f]{8})([0-9a-f]{8})
```

* Del secondo byte ci interessano i due bit più significativi per capire se la
  registrazione rappresenta un'entrata o un'uscita:
  * Se sono `00` allora la registrazione è la prima entrata;
  * Se sono `01` allora la registrazione è la prima uscita;
  * Se sono `10` allora la registrazione è la seconda entrata;
  * Se sono `11` allora la registrazione è la seconda uscita;
* I penultimi quattro byte rappresentano l'ID del dipendende (in
  little-endian);
* Gli ultimi 4 byte rappresentano la data e l'ora della presenza (in
  little-endian).

All'inizio pensavo che la data fosse rappresentata come una UNIX Epoch, invece
a quanto pare ha questo formato (quando rappresentato come big-endian):

* I primi 6 bit rappresentano i minuti;
* I 5 bit successivi rappresentano le ore;
* I 5 bit successivi rappresentano i giorni;
* I 4 bit successivi rappresentano i mesi;
* Infine, gli ultimi 12 bit rappresentano gli anni.

---

Sospetto inoltre che nei primi quattro byte di ogni presenza siano presenti
anche:

* I secondi;
* La modalità di registrazione (se con il PIN, con l'impronta o col badge);
* L'ID del registratore.

Ma dato che non sono campi molto importanti ho deciso che per il momento li
lascerò perdere.

## Provare col terminale

Se volessimo provare la comunicazione senza dover scrivere alcun programma che
invia byte su un socket TCP, possiamo utilizzare un po' di utils di sistema
come `netcat` e `xxd`:

```shell
# Se usate Bash o Zsh
function send_bytes { echo -n "$3" | xxd -r -p | timeout 1 nc "$1" "$2" | xxd; }

# Oppure, se state usate Fish
function send_bytes -a ip porta dati
    echo -n "$dati" | xxd -r -p | timeout 1 nc "$ip" "$porta" | xxd
end

send_bytes 127.0.0.1 5005 55aa0180000000000000000000000100
```

Provando qualche comando preso dagli esempi sopra posso confermare che tutto
sembra funzionare correttamente. Nel prossimo articolo vedremo come creare una
piccola libreria in Rust per ricavare i dati dal lettore.
