Cześć wszystkim
Dzisiaj my, Victor Antipov i Ilya Aleshin, porozmawiamy o naszych doświadczeniach w pracy z urządzeniami USB za pomocą Python PyUSB, a także trochę o inżynierii wstecznej.

prehistoria
W 2019 roku weszła w życie Uchwała Rządu Federacji Rosyjskiej nr 224 „W sprawie zatwierdzenia zasad oznaczania wyrobów tytoniowych środkami identyfikacyjnymi i specyfiki wdrażania państwowego systemu informacyjnego do monitorowania obrotu towarów podlegających obowiązkowemu oznaczaniu środkami identyfikacyjnymi w zakresie wyrobów tytoniowych”.
W dokumencie wyjaśniono, że od 1 lipca 2019 r. producenci mają obowiązek oznakowania każdej paczki tytoniu. Natomiast dystrybutorzy bezpośredni muszą otrzymać te produkty za pośrednictwem uniwersalnego dokumentu przelewu (UTD). Sklepy z kolei muszą rejestrować sprzedaż oznaczonych produktów przy kasie.
Ponadto od 1 lipca 2020 r. zakazano obrotu nieoznakowanymi wyrobami tytoniowymi. Oznacza to, że wszystkie paczki papierosów muszą być oznaczone specjalnym kodem kreskowym Datamatrix. Co więcej – co ważne – okazało się, że Datamatrix nie będzie zwykły, lecz odwrotny. Nie chodzi o czarny kod na białym, lecz odwrotnie.
Przetestowaliśmy nasze skanery i okazało się, że większość z nich wymaga ponownego flashowania/trenowania, w przeciwnym razie po prostu nie będą w stanie pracować normalnie z tym kodem kreskowym. Taki obrót spraw zafundował nam potężny ból głowy, bo nasza firma posiada wiele sklepów rozsianych na ogromnym terenie. Kilkadziesiąt tysięcy kas fiskalnych – i bardzo mało czasu.
Co należało zrobić? Są dwie możliwości. Po pierwsze: inżynierowie na miejscu ręcznie wgrywają oprogramowanie i dostrajają skanery. Po drugie: pracujemy zdalnie i najlepiej, gdy obsługujemy wiele skanerów jednocześnie w jednej iteracji.
Pierwsza opcja oczywiście nam nie odpowiadała: musielibyśmy wydać pieniądze na wizyty inżynierów, a w takim przypadku trudno byłoby kontrolować i koordynować proces. Ale najważniejsze jest to, że ludzie by pracowali, co oznacza, że potencjalnie popełnilibyśmy wiele błędów i najprawdopodobniej nie dotrzymalibyśmy terminu.
Druga opcja jest dobra pod każdym względem, choć nie z jednego powodu. Niektórzy dostawcy nie oferowali narzędzi do zdalnego flashowania, których potrzebowaliśmy dla wszystkich wymaganych systemów operacyjnych. A że termin był napięty, musiałem sam pomyśleć.
Następnie opowiemy o tym, jak opracowaliśmy narzędzia dla skanerów ręcznych w systemie operacyjnym Debian 9.x (wszystkie nasze kasy fiskalne działają na Debianie).
Rozwiąż zagadkę: jak sflashować skaner
Victor Antipov opowiada historię.
Oficjalne narzędzie udostępnione przez dostawcę działa w systemie Windows i tylko z przeglądarką IE. Narzędzie to umożliwia flashowanie i konfigurację skanera.
Ponieważ naszym systemem docelowym jest Debian, zainstalowaliśmy serwer USB-redirector na Debianie i klienta USB-redirector na Windows. Korzystając z narzędzia usb-redirector, przekierowaliśmy skaner z komputera z systemem Linux na komputer z systemem Windows.
Narzędzie dostawcy dla systemu Windows wykryło skaner i nawet normalnie go wgrało. W ten sposób doszliśmy do pierwszego wniosku: nic nie zależy od systemu operacyjnego, wszystko zależy od protokołu oprogramowania sprzętowego.
OK. Na komputerze z systemem Windows rozpoczęliśmy flashowanie, na komputerze z systemem Linux wykonaliśmy zrzut.
Wgraliśmy zrzut do WireSharka i... poczuliśmy smutek (pominę niektóre szczegóły zrzutu, nie są interesujące).
Co pokazał nam zrzut:


Adresy 0000-0030, według Wiresharka, to informacje o usłudze USB.
Nas interesowała część 0040-0070.
Z pojedynczej ramki transmisji nie dało się niczego wyraźnie odczytać, poza symbolami MOCFT. Okazało się, że te symbole pochodzą z pliku oprogramowania sprzętowego, podobnie jak reszta symboli aż do końca ramki (plik oprogramowania sprzętowego jest podświetlony):

Osobiście, podobnie jak Ilja, nie miałem pojęcia, co oznaczają symbole fd 3e 02 01 fe.
Przyjrzałem się poniższej ramce (informacje serwisowe zostały tutaj usunięte, plik oprogramowania sprzętowego został podświetlony):

Co stało się jasne? Że pierwsze dwa bajty są pewnego rodzaju stałą. Wszystkie kolejne bloki to potwierdziły, ale przed końcem bloku transmisyjnego:

Ta ramka również wprawiła mnie w osłupienie, ponieważ stała uległa zmianie (zaznaczono ją) i, co dziwne, znajdowała się tam część pliku. Rozmiar pliku przesłanych bajtów wykazał, że przesłano 1024 bajty. Co oznaczały pozostałe bajty, tego znów nie wiedziałem.
Pierwszą rzeczą, jaką zrobiłem, jako stary użytkownik BBS, było zapoznanie się ze standardowymi protokołami transmisji. Żaden protokół nigdy nie przesłał 1024 bajtów. Zacząłem badać sprzęt i natknąłem się na protokół 1K Xmodem. Umożliwiło to transmisję 1024 bajtów, ale z pewnym niuansem: początkowo tylko 128 i dopiero w przypadku braku błędów protokół zwiększył liczbę przesyłanych bajtów. Natychmiast otrzymałem transfer 1024 bajtów. Postanowiłem przyjrzeć się protokołom transmisji, a w szczególności modemowi X.
Istniały dwie wersje modemu.
Najpierw format pakietu XMODEM z obsługą CRC8 (oryginalny XMODEM):

Po drugie, format pakietu XMODEM z obsługą CRC16 (XmodemCRC):

Wygląda podobnie, z wyjątkiem SOH, numeru pakietu, CRC i długości pakietu.
Spojrzałem na początek drugiego bloku transmisyjnego (i ponownie zobaczyłem plik oprogramowania sprzętowego, ale z wcięciem 1024 bajtów):

Widziałem znajomy nagłówek fd 3e 02, ale następne dwa bajty już się zmieniły: było 01 fe, a teraz jest 02 fd. Tutaj zauważyłem, że drugi blok został teraz ponumerowany 02, co zrozumiałem: przede mną znajduje się numeracja bloku transmisyjnego. Pierwsza transmisja 1024 to 01, druga to 02, trzecia to 03 i tak dalej (ale oczywiście w systemie szesnastkowym). Ale co oznacza zmiana z fe na fd? Oczy zobaczyły spadek o 1, mózg przypomniał sobie, że programiści liczą od 0, a nie od 1. Ale dlaczego w takim razie pierwszy blok to 1, a nie 0? Nadal nie znalazłem odpowiedzi na to pytanie. Ale zrozumiałem, jak obliczany jest drugi blok. Drugi blok to nic innego jak FF - (minus) numer pierwszego bloku. W związku z tym drugi blok oznaczono jako = 02 (FF-02) = 02 FD. Późniejsza lektura zrzutu potwierdziła moje przypuszczenia.
Następnie zaczął wyłaniać się następujący obraz transmisji:
Początek transmisji
fd3e02-Rozpocznij
01 FE – licznik transmisji
Transfer (34 bloki, 1024 bajty przeniesione)
fd 3e 1024 bajtów danych (podzielonych na bloki 30-bajtowe).
Koniec transmisji
fd 25
Pozostałe dane do wyrównania do 1024 bajtów.
Jak wygląda ramka transmisji końcowej bloku:

fd 25 – sygnał zakończenia transmisji bloku. Następne 2f 52 – reszta pliku do rozmiaru 1024 bajtów. 2f 52, zgodnie z protokołem, jest 16-bitową sumą kontrolną CRC.
Z przyzwyczajenia napisałem program w języku C, który pobierał 1024 bajty z pliku i obliczał 16-bitową sumę kontrolną (CRC). Uruchomienie programu wykazało, że nie jest to 16-bitowy CRC. Znów otępienie - przez około trzy dni. Przez cały czas próbowałem zrozumieć, czym to może być, jeśli nie sumą kontrolną. Studiując strony anglojęzyczne, odkryłem, że X-modem wykorzystuje własny algorytm obliczania sumy kontrolnej – CRC-CCITT (XModem). Nie znalazłem żadnych implementacji tego obliczenia w języku C, ale znalazłem stronę online, która oblicza tę sumę kontrolną. Po przesłaniu 1024 bajtów mojego pliku na stronę internetową, wyświetliła mi się suma kontrolna, która całkowicie odpowiadała sumie kontrolnej z pliku.
Hurra! Ostatnia zagadka została rozwiązana, teraz należało stworzyć własny firmware. Następnie przekazałem swoją wiedzę (i pozostała ona tylko w mojej głowie) Ilji, który zna potężny zestaw narzędzi – Python.
Tworzenie programu
Ilja Aleszyn opowiada historię.
Otrzymawszy stosowne instrukcje byłem bardzo „zadowolony”.
Od czego zacząć? Tak, od początku. Poprzez usunięcie zrzutu z portu USB.
Uruchom USB-pcap
Wybieramy port, do którego podłączone jest urządzenie i plik, w którym zapiszemy zrzut.

Podłączamy skaner do komputera, na którym zainstalowano natywne oprogramowanie EZConfigScanning dla systemu Windows.

Znajdziemy w nim element umożliwiający wysyłanie poleceń do urządzenia. A co z zespołami? Gdzie mogę je dostać?
Po uruchomieniu programu następuje automatyczne sprawdzenie stanu sprzętu (zobaczymy to nieco później). Były tam także szkoleniowe kody kreskowe z oficjalnych dokumentów sprzętu. DOMYŚLNY. To jest nasz zespół.

Otrzymano wymagane dane. Otwórz dump.pcap za pomocą wiresharka.
Blokada podczas uruchamiania EZConfigScanning. Miejsca wymagające uwagi oznaczono na czerwono.


Widząc to wszystko po raz pierwszy, byłem zniechęcony. Nie jest jasne, gdzie dalej kopać.
Trochę burzy mózgów i... i... Aha! Na wysypisku na zewnątrz - to inI in to na zewnątrz.
Wyszukałem w Google co to jest URB_INTERRUPT. Dowiedziałem się, że jest to metoda przesyłania danych. Istnieją cztery takie metody: kontrola, przerwanie, izochroniczna i masowa. Możesz o nich przeczytać osobno.
Adres punktu końcowego w interfejsie urządzenia USB można uzyskać albo za pomocą polecenia „lsusb –v”, albo za pomocą pyusb.
Teraz musimy znaleźć wszystkie urządzenia z tym VID. Można wyszukiwać konkretnie według VID:PID.
![]()
To wygląda tak:


Mamy więc potrzebne nam informacje: polecenia P_INFO. lub DEFALT, adresy, gdzie należy pisać polecenia endpoint=03 i gdzie pobierać odpowiedź endpoint=86. Pozostaje tylko przetłumaczyć polecenia na system szesnastkowy.
![]()

Skoro już znaleźliśmy urządzenie, odłączmy je od jądra...

…i zapiszemy do punktu końcowego o adresie 0x03,

… a następnie odczytujemy odpowiedź z punktu końcowego o adresie 0x86.

Odpowiedź strukturalna:
P_INFOfmt: 1
mode: app
app-present: 1
boot-present: 1
hw-sn: 18072B44CA
hw-rev: 0x20
cbl: 4
app-sw-rev: CP000116BBA
boot-sw-rev: CP000014BAD
flash: 3
app-m_name: Voyager 1450g
boot-m_name: Voyager 1450g
app-p_name: 1450g
boot-p_name: 1450g
boot-time: 16:56:02
boot-date: Oct 16 2014
app-time: 08:49:30
app-date: Mar 25 2019
app-compat: 289
boot-compat: 288
csum: 0x6986Te dane widzimy w dump.pcap.



Świetnie! Tłumaczymy kody kreskowe systemowe na kod szesnastkowy. To wszystko, funkcjonalność szkoleniowa jest gotowa.
Co zrobić z oprogramowaniem sprzętowym? Wydaje się, że to to samo, ale jest pewien niuans.
Po przeprowadzeniu pełnego procesu flashowania, mniej więcej zrozumieliśmy, z czym mamy do czynienia. Oto artykuł o XMODEM, który naprawdę pomógł mi zrozumieć, jak przebiega ta komunikacja, choć tylko w ogólnym zarysie: Polecam lekturę.
Patrząc na zrzut, można zobaczyć, że rozmiar ramki wynosi 1024, a rozmiar danych URB wynosi 64.

Dlatego – 1024/64 – otrzymujemy 16 linii w bloku, odczytujemy plik oprogramowania układowego znak po znaku i tworzymy blok. Uzupełnienie 1 wiersza w bloku znakami specjalnymi fd1e3 + numer bloku.
Następnych 14 wierszy uzupełniamy poleceniem fd25 +, za pomocą XMODEM.calc_crc() obliczamy sumę kontrolną całego bloku (długo zajęło nam zrozumienie, że „FF – 1” to CSUM) i uzupełniamy ostatni, 16 wiersz poleceniem fd3e.
Wydawałoby się, że to już wszystko: odczytaj plik oprogramowania układowego, uruchom bloki, odłącz skaner od rdzenia i wyślij go do urządzenia. Ale to nie jest takie proste. Skaner musi zostać przełączony w tryb oprogramowania układowego,
отправив ему NEWAPP = ‘\xfd\x0a\x16\x4e\x2c\x4e\x45\x57\x41\x50\x50\x0d’.
Skąd pochodzi to polecenie? Ze śmietnika.

Ale nie możemy wysłać całego bloku do skanera ze względu na limit 64:
![]()
Otóż skaner w trybie aktualizacji oprogramowania NEWAPP nie obsługuje formatu heksadecymalnego. Dlatego każdy wiersz będzie musiał zostać przetłumaczony bytes_array
[253, 10, 22, 78, 44, 78, 69, 87, 65, 80, 80, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]A następnie przesłać te dane do skanera.
Otrzymujemy odpowiedź:
[2, 1, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]Jeśli sprawdzisz artykuł o XMODEM, stanie się jasne: dane są akceptowane.

Po przeniesieniu wszystkich bloków kończymy transfer END_TRANSFER = 'xfdx01x04'.
Ponieważ bloki te nie niosą ze sobą żadnych informacji dla przeciętnego użytkownika, domyślnie umieścimy oprogramowanie sprzętowe w trybie ukrytym. A na wszelki wypadek zorganizujemy pasek postępu za pomocą tqdm.
![]()
Tak naprawdę pozostała tylko drobna sprawa. Pozostaje jedynie opakować rozwiązanie w skrypty do masowej replikacji w ściśle określonym czasie, tak aby nie spowalniać pracy przy kasach, oraz dodać logowanie.
Łączny
Poświęciwszy mnóstwo czasu, wysiłku i wysiłku, udało nam się wypracować rozwiązania, których potrzebowaliśmy, i dotrzymać terminu. Jednocześnie skanery są teraz ponownie programowane i szkolone centralnie, dzięki czemu mamy pełną kontrolę nad całym procesem. Firma zaoszczędziła czas i pieniądze, a my zyskaliśmy bezcenne doświadczenie w inżynierii wstecznej urządzeń tego typu.
Źródło: www.habr.com
