Quando il reverse engineering è l'unica soluzione

In questi giorni mi sono trovato a lavorare sulle sulle stampanti Fasy tramite Java. L'unico problema è che l'SDK fornito a corredo della stampante funziona tramite DLL. Vediamo come sono riuscito comunque ad aggirare il problema

  
a-
+
Non so quanti di voi si sono mai trovati di fronte a delle stampanti fiscali come la Fasy F1 e hanno avuto il problema di doverle interfacciare da un altro software.

Per un cliente che sto seguendo, ho dovuto riscrivere un vecchio programma sviluppato in Visual Basic 6, in qualcosa di maggiormente modulare e portabile.

Dato che il linguaggio nel quale mi sento maggiormente a mio agio è Java, ho deciso di utilizzarlo per riscrivere il codice.

Funzionamento
Al momento la soluzione tecnica non è delle migliori: il programma non attende un evento, ma continua ad interrogare un sito web per sapere se ci sono nuovi scontrini da stampare.
Questo tipo di approccio è dovuto ad una precedente implementazione che, per retrocompatibilità, ho scelto di non variare nella prima release, in modo che il cambio di software non avesse impatti lato server, ma solo lato client.

Ogni volta che è presente una vendita, viene preparato un nuovo scontrino ed inviato alla stampante fiscale.

In questo caso la stampa è fatta tramite una Fasy:



Ammetto che fino a poche settimane fa non avevo mai utilizzato questo prodotto, ma chiaramente non è stato un motivo per demotivarmi dal provare a programmarlo.

Il primo ostacolo che ho trovato è stata la totale assenza di interfacce ufficiali in Java: esempi e supporto, volendo usare il protocollo "custom", era fatti su Visual Basic 6, VB.NET, VC#, VC++ 6.0 e VC++.NET.

Fortunatamente tutti utilizzavano la libreria CeFdll.dll, le cui API erano indicate chiaramente in un manuale di poche ed essenziali pagine.

Viva la JNA
Quando da Java si vuole uscire dalla Virtual Machine ed utilizzare qualcosa di Nativo è necessario affidarsi a delle librerie realizzate per questo scopo. Quella maggiormente utilizzata al momento, dato che non necessita programmazione in altri linguaggi, se non in Java è JNA.

JNA è una libreria sviluppata per superare l'approccio alle estensioni Java dato da JNI, che obbliga la scrittura di codice in un linguaggio diverso da Java (tipicamente in C) e permette, continuando a scrivere codice Java, di richiamare qualsiasi DLL a 32 o 64 bit.

Dopo qualche ora di lavoro avevo realizzato e testato un'interfaccia JNA fra Java e CeFdll.dll.

Quasi tutto a posto
Tutto funzionava come da manuale, fino a quando non mi sono imbattuto nella funzione che permette la connessione alla stampante tramite porta Ethernet in TCP/IP.

L'unica diversità rispetto al flusso standard era data dal metodo di apertura della stampante. Un nuovo metodo che non avevo mai usato.

Il problema era che, quel metodo, continuava a darmi un errore di comunicazione:

Errore 65543: Errore di comunicazione ritornato dalla stampante in fase di scrittura

Ho controllato e ricontrollato il codice più volte, ma probabilmente c'era qualcosa che mi sfuggiva.

Per questa ragione, ho rispolverato il mio cassetto degli attrezzi per iniziare ad entrare nel dettaglio di quello che, per me, era un errore incomprensibile.

Utilizziamo JNI
Non capendo a che livello era il problema, ho iniziato a levare degli strati software, andando a programmare ad un livello più basso ed utilizzando JNI al posto di JNA.
Sicuramente un modo più rozzo di utilizzare una DLL, ma col vantaggio di poter fare un debug a livello di chiamata diretta fra C e DLL.

Scrivo quindi la classe Java, aggiungo il singolo metodo nativo che dovevo controllare, genero l'interfaccia, creo il programma in C, lo compilo in MinGW, provo il tutto e .. incredibilmente: funziona.

Provo a portare la classi JNI nel programma principale, per usarla al posto di quella JNA e, anche con JNI ho lo stesso errore di comunicazione.

Iniziavo a non capire il problema: come è possibile che, con un programma di poche righe tutto funzionasse e con un programma più complesso ci fossero errori di comunicazione?

Era venuto il momento di mettere le mani a CeFdll, di cui non avevo però i sorgenti.

Poco male: lancio OllyDBG, che in questo caso ero sicuro mi potesse dare soddisfazioni, e inizio a capire cosa fa CeFdll e il motivo per cui avesse un comportamento diverso sui miei due programmi.

Hidden debugger
La prima cosa che scopro è che, CeFdll, è predisposta internamente per fare il log di tutte le operazioni effettuate, sul file C:\CeFdll.log.

Non avendo trovato un modo standard per sbloccare il log, decido di modificare CefDLL per capirci qualcosa di più e mi permetto una piccola patch, cambiando un paio di byte e trasformando due je in jne.

000015AD: 75 74
00001627: 75 74

Come per magia, la dll inizia a generare un po' di log, indispensabile per capire che problema ci potesse essere.

Nell'ambiente di test funzionava tutto, il log era perfetto.

[ Sat Oct 10 16:38:52 2015 ] CeComOutputDebugString Open ethernet core - Start
[ Sat Oct 10 16:38:52 2015 ] CeComOutputDebugString Verify Printer Status
[ Sat Oct 10 16:38:52 2015 ] CeComOutputDebugString VC Write Start
[ Sat Oct 10 16:38:52 2015 ] CeComOutputDebugString VC Write End
[ Sat Oct 10 16:38:52 2015 ] CeComOutputDebugString VC Read Start
[ Sat Oct 10 16:38:52 2015 ] CeComOutputDebugString Custom LITE Protocol
[ Sat Oct 10 16:38:52 2015 ] CeComOutputDebugString VC Read End
[ Sat Oct 10 16:38:52 2015 ] CeComOutputDebugString Open ethernet - End

Riprovando la cosa nel programma principale, dove avevo l'errore di comunicazione, ho come risultato un log di una sola riga:

[ Sat Oct 10 16:39:29 2015 ] CeComOutputDebugString Open ethernet core - Start

sembrava quasi che non ci fosse neppure il tentativo di apertura della comunicazione TCP/IP verso la stampante.

Già che avevo fatto del reverse engineerig sulla DLL mi sono detto: perché non farlo anche a livello di protocollo TCP/IP, per capire quali dati transitano e perché non funziona la comunicazione fra stampante e PC?

In questo caso ho usato qualcosa di più semplice.

SockRedirector
Uno dei miei primi lavori per capire il funzionamento delle socket è stato quello di costruire un programma in grado di ricevere una chiamata TCP/IP verso un IP:PORTA e rigirarla verso un altro IP:PORTA, facendo un log dei byte in ingresso e dei byte in uscita. Una specie di NAT software con log.

Ammetto che il programma è abbastanza primitivo, ma fa il suo sporco lavoro. Per questo motivo lo uso spesso per analizzare i dati scambiati fra applicazioni diverse via TCP/IP.

In questo caso ho attivato SockRedirector su 127.0.0.1 porta 9100 e ho rediretto il traffico verso la stampante in ascolto all'indirizzo 192.168.10.121 porta 9100.

Grazie a questo ho capito alcune cose:

* La comunicazione è tutta in chiaro e non cifrata: questo è stato un bene, dato che mi ha permesso di analizzare i dati in transito.
* Il protocollo Custom è molto semplice e segue la prassi di avere un header di pochi byte fissi, una stringa di comunicazione, 2 byte di CRC e un terminatore finale.
* Era semplice ricostruire il protocollo di comunicazione in Java, senza utilizzare la DLL esterna, tramite l'uso di semplici socket.

Stavo però partendo per la tangente, mi ero allontanato un po' troppo dal problema.

Sono tornato quindi a leggere meglio il decompilato, in prossimità della funzione di apertura della porta TCP/IP, e mi sono accorto della chiamata a un'altra DLL CeNComLayer.dll.

Provo a metterla nella stessa cartella del programma che non funzionava e .. tutto inizia ad andare.

Ero stato fuorviato da un messaggio d'errore che avevo interpretato come di configurazione TCP/IP e non di mancanza di requisiti software.

Le ragioni
Per pulizia di programma avevo scelto di fare il deploy dell'applicazione tramite un unico JAR, costruito con Maven, al cui interno erano presenti anche le dll utilizzate da JNA (in prarica la CeFdll.dll ed altro).
Se non fosse che, da quella posizione, CeFdll non riuscire a fare la LoadLibrady della seconda DLL che veniva utilizzata per la comunicazione TCP/IP.

Finalmente avevo capito il problema e avevo una soluzione.

Conclusioni
Devo ammettere che quello che poteva sembrare un problema, alla fine si è rivelato un banale errore di configurazione.
La voglia di arrivare ad una soluzione è stata però uno stimolo per poter capire più in dettaglio come funzionava la comunicazione verso le stampanti Fasy e per entrare nel dettaglio del protocollo software utilizzato.

Cosa porto a casa?
- Come funzionano le dipendenze fra DLL all'interno di un programma JNA realizzato con un unico JAR
- Che in certi programmi la parte documentata è solo parte di quello che in realtà il programma è in grado di fare.
- Come funziona il protocollo di comunicazione Custom
- Una classe socket di comunicazione fra il mio programma e la stampante fiscale, che mi permetterà di gestire un numero infinito di stampanti all'interno dello stesso programma, anche in modalità multithread.
- Capisco, nuovamente, che un po' di sano reverse engineering può arrivare dove i messaggi d'errore e i manuali, a volte, non arrivano
Leggi QUI il post completo