| 
		 After Width: | Height: | Size: 681 KiB  | 
| 
		 After Width: | Height: | Size: 111 KiB  | 
| 
		 After Width: | Height: | Size: 113 KiB  | 
| 
		 After Width: | Height: | Size: 110 KiB  | 
| 
		 After Width: | Height: | Size: 386 KiB  | 
| 
		 After Width: | Height: | Size: 457 KiB  | 
| 
		 After Width: | Height: | Size: 371 KiB  | 
| 
		 After Width: | Height: | Size: 1.2 MiB  | 
@@ -0,0 +1,358 @@
 | 
				
			|||||||
 | 
					+++
 | 
				
			||||||
 | 
					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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## *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
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					I primi 10 bit del payload sono 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 ci sono 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.
 | 
				
			||||||
							
								
								
									
										338
									
								
								content/posts/2024/05/studying-a-communication-protocol/index.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,338 @@
 | 
				
			|||||||
 | 
					+++
 | 
				
			||||||
 | 
					title = "Studying a communication protocol"
 | 
				
			||||||
 | 
					summary = "Step 2: Using a shark to sniff packets"
 | 
				
			||||||
 | 
					date = "2024-05-01"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tags = ["Reverse Engineering", "Attendance Reader", "TCP", "Sniffing", "Wireshark"]
 | 
				
			||||||
 | 
					categories = ["Projects"]
 | 
				
			||||||
 | 
					series = ["Attendance Reader"]
 | 
				
			||||||
 | 
					series_order = 2
 | 
				
			||||||
 | 
					+++
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In the previous article, we started studying how the attendance reader client
 | 
				
			||||||
 | 
					works, we even attempted to decompile its executable. In this article, I'd like
 | 
				
			||||||
 | 
					to explore the communication protocol that the client uses to talk to the
 | 
				
			||||||
 | 
					reader.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					There are basically two reasons why I didn't immediately reverse-engineer the
 | 
				
			||||||
 | 
					protocol:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. If I could decompile the executable code, I could create an alternative
 | 
				
			||||||
 | 
					   client much more easily;
 | 
				
			||||||
 | 
					2. Sometimes it's not possible (not easily, at least) to *sniff* a
 | 
				
			||||||
 | 
					   communication 'cause of
 | 
				
			||||||
 | 
					   [TLS](https://en.wikipedia.org/wiki/Transport_Layer_Security).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					However, decompiling DLLs is far from easy because:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> There's no magic "go back" button, there's a "generate shitty C code with
 | 
				
			||||||
 | 
					> random-ass variable names" button, but that's not a very good button
 | 
				
			||||||
 | 
					>
 | 
				
			||||||
 | 
					> **fasterthanlime** in the [How does the detour crate
 | 
				
			||||||
 | 
					> work?](https://www.youtube.com/watch?v=aLeMCUXFJwY&t=174s) video
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If you're interested, the NSA has developed its own decompiler called
 | 
				
			||||||
 | 
					[Ghidra](https://ghidra-sre.org/), check it out.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Client configuration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In the last article, we only installed the client for Windows but never opened
 | 
				
			||||||
 | 
					it.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Since we need a client that can actually interact with the reader to intercept
 | 
				
			||||||
 | 
					the communication, I reopened my VM with [Windows 10
 | 
				
			||||||
 | 
					AME](https://archive.org/details/windows10-ame-21h1-2021-08-09/) and finished
 | 
				
			||||||
 | 
					configuring the client:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{{< carousel images="images/01-client-setup/*" aspectRatio="16-9"
 | 
				
			||||||
 | 
					interval="1000" >}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Once the configuration is completed (and after manually modifying some
 | 
				
			||||||
 | 
					configuration files because the client still couldn't see the reader on the
 | 
				
			||||||
 | 
					network), we can request the reader's data over the network.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					After opening the client **as an administrator**, pressing the button to
 | 
				
			||||||
 | 
					download data, and waiting **two minutes**, a total of 3543 attendances
 | 
				
			||||||
 | 
					appeared on the screen.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Something's odd: why does it take two minutes to transfer the equivalent of a
 | 
				
			||||||
 | 
					file weighing just under 200 kiB?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Doing some quick math:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{{< katex >}}
 | 
				
			||||||
 | 
					$$
 | 
				
			||||||
 | 
					\frac{3543\ \textrm{lines}}{120\ \textrm{seconds}} \ \cdot\sim460\ \textrm{bit
 | 
				
			||||||
 | 
					per row} = 13.26\ kib/s
 | 
				
			||||||
 | 
					$$
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					13 kibps of useful throughput on a 100 Mbps connection? ***This sucks!***
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					I don't want to know what disaster of italian corporate coding could have
 | 
				
			||||||
 | 
					caused this, but I have a feeling I’m about to find out...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## *The quieter you become...*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To analyze the network, I will use [Wireshark](https://wireshark.org), a very
 | 
				
			||||||
 | 
					popular tool for this type of operations.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					After installing it and adding our user to the `wireshark` group, we can run it
 | 
				
			||||||
 | 
					and begin to *sniff* all packets on our network interface.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If this is your first time using a tool like this, you might notice that even
 | 
				
			||||||
 | 
					in a small Local Area Network there are a lot of packets flying around — too
 | 
				
			||||||
 | 
					many to analyze individually.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This is where filters come and save the day. If we type the following string
 | 
				
			||||||
 | 
					into the filter bar:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					ip.addr == <Device's IP>
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					We will see only packets that come *from* or are directed *to* the specified IP
 | 
				
			||||||
 | 
					address. We can also filter traffic that passes through a specific TCP port
 | 
				
			||||||
 | 
					with:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					ip.addr == <IP> && tcp.port == <Port>
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Filters in Wireshark are a vast argument; here's a [link to the official
 | 
				
			||||||
 | 
					documentation](https://wiki.wireshark.org/DisplayFilters) for those interested.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Once we start recording with the correct filters, we can start another full
 | 
				
			||||||
 | 
					scan of attendances on the official client, and we should see the packet
 | 
				
			||||||
 | 
					exchange between the client and the device in real-time.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					At the end of the process, we've recorded an astonishing 14,423 packets,
 | 
				
			||||||
 | 
					carrying 3,543 attendances. *Things just get stranger...*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					By taking a quick look at the traffic, we can deduce a few things:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. The transport layer uses the TCP protocol on port `5005`;
 | 
				
			||||||
 | 
					2. [TLS](https://en.wikipedia.org/wiki/Transport_Layer_Security) is not used,
 | 
				
			||||||
 | 
					   *phew*;
 | 
				
			||||||
 | 
					3. There are at least three phases:
 | 
				
			||||||
 | 
					   * An initial setup phase;
 | 
				
			||||||
 | 
					   * A second phase in which data is exchanged with a few but large packets;
 | 
				
			||||||
 | 
					   * A third phase with many but small packets, where you can occasionally
 | 
				
			||||||
 | 
					     observe employee names in ASCII.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To study the protocol in more depth, we'd need only the content of the TCP
 | 
				
			||||||
 | 
					packets. This is where Wireshar comes in handy.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If we select a packet from the TCP communication we're interested in and
 | 
				
			||||||
 | 
					right-click, selecting `Follow` > `TCP Stream`, Wireshark will automatically
 | 
				
			||||||
 | 
					open the payload of all packets and show only the level-7 traffic.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If we view the data as `Raw`, Wireshark will display the exchanged data in
 | 
				
			||||||
 | 
					hexadecimal format, with messages sent by the client in red and responses from
 | 
				
			||||||
 | 
					the attendance reader in blue.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Now we can copy the payloads into our preferred text editor and start to study
 | 
				
			||||||
 | 
					the protocol.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Fuck around and find out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Now we just need to understand the communication protocol, which,
 | 
				
			||||||
 | 
					unfortunately, isn't in a text-based format like ASCII or UTF-8.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It may seem complex, but it only took me an afternoon to find a comprehensive
 | 
				
			||||||
 | 
					enough solution for what I need to do.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Requests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Client requests are all 16 bytes long and have this structure:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```regex
 | 
				
			||||||
 | 
					^55aa([0-9a-f]{24})([0-9a-f]{4})$
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* The first two bytes are always `55 aa` (`01010101 10101010` in binary);
 | 
				
			||||||
 | 
					* The next 12 bytes specify the client command. I will call them "payload" from
 | 
				
			||||||
 | 
					  now on;
 | 
				
			||||||
 | 
					* Finally, there are two **little-endian** bytes indicating the packet number,
 | 
				
			||||||
 | 
					  starting from `00 00`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					I noticed that the server doesn't check if the last two bytes are sent
 | 
				
			||||||
 | 
					sequentially, so they can remain at `00 00` throughout the message exchange.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Responses
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Server responses do not have a fixed length and are divided into two parts,
 | 
				
			||||||
 | 
					which I will call "header" and "payload." The header is always present and is
 | 
				
			||||||
 | 
					10 bytes long, while the payload can be absent.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					When there's no payload, the message acts like a kind of `null`/`ACK`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```regex
 | 
				
			||||||
 | 
					^aa55([0-9a-f]{16})(?:55aa([0-9a-f]+))?$
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* The first two bytes are always `aa 55` (`10101010 01010101` in binary);
 | 
				
			||||||
 | 
					* The following eight bytes are the header. Usually, they are `01 01 00 00 00
 | 
				
			||||||
 | 
					  00 00 00`, but they can change;
 | 
				
			||||||
 | 
					* If a payload is present, the message continues with `55 aa` (`01010101
 | 
				
			||||||
 | 
					  10101010` in binary);
 | 
				
			||||||
 | 
					* The remaining bytes are the payload.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Ping
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If we want to perform a "ping" and check if the server responds, we can send a
 | 
				
			||||||
 | 
					request with the payload set to `01 80 00 00 00 00 00 00 00 00 00 00`:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					55aa0180000000000000000000000100
 | 
				
			||||||
 | 
					aa550101000000000000
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The server will then respond with a packet without a payload and the header set
 | 
				
			||||||
 | 
					to `01 01 00 00 00 00 00 00`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Employee uame
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Knowing the ID of an employee, it's possible to ask the server for their name
 | 
				
			||||||
 | 
					by sending a request with a payload set to `01 c7 xx xx xx xx 00 00 00 00 14
 | 
				
			||||||
 | 
					00`, where `xx xx xx xx` is a 32-bit **little-endian** integer representing the
 | 
				
			||||||
 | 
					employee ID.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					55aa01c7xxxxxxxx0000000014000100
 | 
				
			||||||
 | 
					aa55010100000000000055aaxxxxxxxxxxxxxxxxxxxx4c0000000000595a7c7c0000
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The first 10 bits of the payload contain the employee's name; if it's shorter
 | 
				
			||||||
 | 
					than 10 characters, the remaining space will be filled with null terminators
 | 
				
			||||||
 | 
					(`\0`).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					These messages comprise almost the entirety of the third phase I described in
 | 
				
			||||||
 | 
					the last chapter, the one with many but small messages. This suggests that the
 | 
				
			||||||
 | 
					client quickly dumps the attendance data, then spends two whole minutes
 | 
				
			||||||
 | 
					downloading the employee's name **for each attendance**, even if it's been
 | 
				
			||||||
 | 
					requested before. Someone should teach these developers the concept of
 | 
				
			||||||
 | 
					[memoization](https://en.wikipedia.org/wiki/Memoization)...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Total number of records
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To ask for the total number of attendances registered on the device, you need
 | 
				
			||||||
 | 
					to send a request with a payload of `01 b4 08 00 00 00 00 00 ff ff 00 00`:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					55aa01b4080000000000ffff00000100
 | 
				
			||||||
 | 
					aa550101xxxx00000000
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Where `xx xx` is the number of saved attendances represented as a 16-bit
 | 
				
			||||||
 | 
					**little-endian** integer.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					65535 maximum requests seem a bit too few, but I guess it's a future-me
 | 
				
			||||||
 | 
					problem.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Downloading all records
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The list of all attendances must be downloaded in blocks, continuing to request
 | 
				
			||||||
 | 
					1024-byte blocks from the server (approximately 85.333 attendances at a time)
 | 
				
			||||||
 | 
					until the entire list is extracted.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To do this, we first have to request the total number of attendances, then send
 | 
				
			||||||
 | 
					a request with a payload of `01 a4 00 00 00 00 xx xx 00 00 00 04`, where `xx
 | 
				
			||||||
 | 
					xx` is the total number of attendances in **little-endian**.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					55aa01a400000000xxxx000000040100
 | 
				
			||||||
 | 
					aa55010100000000000055aa ...
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The server will respond with a 1026-byte payload, containing the initial
 | 
				
			||||||
 | 
					records followed by two zero bytes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					We can request another 1026-byte block by sending a request with a payload of
 | 
				
			||||||
 | 
					`01 a4 00 00 00 00 00 00 xx xx 00 04`, where `xx xx` is a **little-endian**
 | 
				
			||||||
 | 
					integer starting from `01 00`:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					55aa01a4000000000000010000040100
 | 
				
			||||||
 | 
					aa55010100000000000055aa ...
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Once the records are finished, the server will start sending padding bytes set
 | 
				
			||||||
 | 
					to `ff` to reach 1026-byte.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Record structure
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Once we have all the registration blocks, we can break them down into
 | 
				
			||||||
 | 
					individual registrations, each one 12 bytes long. I wasn't able to
 | 
				
			||||||
 | 
					understand what all the bytes represent, but the important ones are:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```regex
 | 
				
			||||||
 | 
					..([26ae]).{5}([0-9a-f]{8})([0-9a-f]{8})
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* The second byte's two most significant bits indicate if the registration
 | 
				
			||||||
 | 
					  represents an entry or an exit:
 | 
				
			||||||
 | 
					  * If it's `00`, it's the first entry;
 | 
				
			||||||
 | 
					  * If it's `01`, it's the first exit;
 | 
				
			||||||
 | 
					  * If it's `10`, it's the second entry;
 | 
				
			||||||
 | 
					  * If it's `11`, it's the second exit;
 | 
				
			||||||
 | 
					* The second-to-last four bytes represent the employee ID (in
 | 
				
			||||||
 | 
					  **little-endian**);
 | 
				
			||||||
 | 
					* The last four bytes represent the date and time of the attendance (in
 | 
				
			||||||
 | 
					  **little-endian**).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Initially I thought the date was represented as a UNIX Epoch, but it seems to
 | 
				
			||||||
 | 
					have this format when shown as big-endian:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* The first 6 bits represent the minutes;
 | 
				
			||||||
 | 
					* The next 5 bits represent the hours;
 | 
				
			||||||
 | 
					* The next 5 bits represent the days;
 | 
				
			||||||
 | 
					* The next 4 bits represent the months;
 | 
				
			||||||
 | 
					* The last 12 bits represent the years.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					I suspect that the first four bytes of each attendance may contain:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* The seconds;
 | 
				
			||||||
 | 
					* The recording method (if the employee checked-in with the PIN, fingerprint,
 | 
				
			||||||
 | 
					  or the badge);
 | 
				
			||||||
 | 
					* The recorder ID.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					But since these aren't very important fields, I've decided to ignore them for
 | 
				
			||||||
 | 
					now.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Testing using the terminal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If you want to test communication without writing any program that sends bytes
 | 
				
			||||||
 | 
					over a TCP socket, you can use some basic core utilities like `netcat` and
 | 
				
			||||||
 | 
					`xxd`:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```shell
 | 
				
			||||||
 | 
					# If you're using Bash or Zsh
 | 
				
			||||||
 | 
					function send_bytes { echo -n "$3" | xxd -r -p | timeout 1 nc "$1" "$2" | xxd; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If you're using Fish
 | 
				
			||||||
 | 
					function send_bytes -a ip port data
 | 
				
			||||||
 | 
					    echo -n "$data" | xxd -r -p | timeout 1 nc "$ip" "$port" | xxd
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					send_bytes 127.0.0.1 5005 55aa0180000000000000000000000100
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Trying some requests from the examples above, I can confirm everything seems to
 | 
				
			||||||
 | 
					work correctly. In the next article, we'll see how to create a small Rust
 | 
				
			||||||
 | 
					library to extract data from the reader.
 | 
				
			||||||