diff options
Diffstat (limited to 'content')
| -rw-r--r-- | content/posts/WIP-xprinter-wifi/index.org | 497 | 
1 files changed, 296 insertions, 201 deletions
| diff --git a/content/posts/WIP-xprinter-wifi/index.org b/content/posts/WIP-xprinter-wifi/index.org index 8bba537..2f17363 100644 --- a/content/posts/WIP-xprinter-wifi/index.org +++ b/content/posts/WIP-xprinter-wifi/index.org @@ -1,256 +1,351 @@ -#+TITLE: Setting up an Xprinter thermal receipt printer WiFi from Linux +#+TITLE: Reverse engineering a thermal printer's WiFi setup  #+DATE: 2021-10-12T15:57:12-04:00  #+DRAFT: true -#+DESCRIPTION: -#+TAGS[]: -#+KEYWORDS[]: +#+DESCRIPTION: Setting up an Xprinter thermal receipt printer WiFi from Linux +#+TAGS[]: linux hardware +#+KEYWORDS[]: linux hardware  #+SLUG:  #+SUMMARY: -I recently purchased a thermal receipt printer off of [[https://web.archive.org/web/20211012195845/https://www.aliexpress.com/item/32842111016.html?spm=a2g0s.9042311.0.0.207d4c4d6xNWgO][AliExpress]] for a -project. It features both WiFi and USB connectivity which I thought -was really cool for the price. +* Introduction -To my dismay, I realized after purchasing that the drivers and -configuration application only run on Windows. +  I recently purchased a thermal receipt printer off of [[https://web.archive.org/web/20211012195845/https://www.aliexpress.com/item/32842111016.html?spm=a2g0s.9042311.0.0.207d4c4d6xNWgO][AliExpress]] for a +  project. It features both WiFi and USB connectivity which I thought +  was really cool for the price. -This wasn't a huge deal, as thermal printers generally use the -somewhat kinda standardized command set called [[https://github.com/escpos/escpos][ESC/POS]]. Unfortunately -while many of the formatting commands are shared between printers, the -commands to setup the WiFi connection don't seem to be documented -anywhere, and I suspect are device-specific. +  To my dismay, I realized after purchasing that the drivers and +  configuration application only run on Windows. -Since booting into Windows every time I want to manage the printer's -network settings isn't ideal, I decided to reverse engineer the -WiFi configuration commands. +  This wasn't a huge deal, as thermal printers generally use the +  somewhat kinda standardized command set called [[https://github.com/escpos/escpos][ESC/POS]]. Unfortunately +  while many of the formatting commands are shared between printers, the +  commands to setup the WiFi connection don't seem to be documented +  anywhere, and I suspect are device-specific. -Initially I tried to run the configuration tool in wine, but it -couldn't communicate with the printer over USB, which wasn't too -surprising. +  Since booting into Windows every time I want to manage the printer's +  network settings isn't ideal, I decided to reverse engineer the +  WiFi configuration commands. -I booted my spare laptop into windows and launched the config tool -there. +  Initially I tried to run the configuration tool in wine, but it +  couldn't communicate with the printer over USB, which wasn't too +  surprising. -I then setup the WiFi through =Advanced -> Set Net= +* Running in Windows -#+ATTR_HTML: :title Xprinter config tool running in wine -#+ATTR_HTML: :alt Xprinter config tool running in wine -[[file:xprinter_setup_tool.png]] +  I booted my spare laptop into windows and launched the config tool +  there. +  I then setup the WiFi through =Advanced -> Set Net= -#+ATTR_HTML: :title Xprinter advanced settings -#+ATTR_HTML: :alt Xprinter advanced settings -[[file:xprinter_advanced.png]] +  #+ATTR_HTML: :title Xprinter config tool running in wine +  #+ATTR_HTML: :alt Xprinter config tool running in wine +  [[file:xprinter_setup_tool.png]] -#+ATTR_HTML: :title Xprinter net settings -#+ATTR_HTML: :alt Xprinter net settings -[[file:xprinter_set_net.png]] -At this point I noticed that the application supported configuring the -printer over the network, meaning I might be able to change the -settings under wine again, as network sockets should work normally. +  #+ATTR_HTML: :title Xprinter advanced settings +  #+ATTR_HTML: :alt Xprinter advanced settings +  [[file:xprinter_advanced.png]] -After rebooting into Linux and testing my assumption, it turned out to -be true, I was able to run the configuration tool without issue as -long as it was connected through the network. +  #+ATTR_HTML: :title Xprinter net settings +  #+ATTR_HTML: :alt Xprinter net settings +  [[file:xprinter_set_net.png]] -While I could have stopped there I decided to go one step further and -reverse the command sequence that configures the WiFi settings so I -could re-configure the printer's WiFi over USB if it ever got messed -up. +  At this point I noticed that the application supported configuring the +  printer over the network, meaning I might be able to change the +  settings under wine again, as network sockets should work normally. -To do that, I sniffed the traffic going from the Xprinter application -and the printer's socket using Wireshark. +* Wireshark on Linux -[[file:xprinter_wireshark.png]] +  After rebooting into Linux and testing my assumption, it turned out to +  be half true, I was able to run the configuration tool over the +  network without issue and print from it, but I couldn't configure the +  WiFi. -In the examples below I've encoded the data being sent to hex to make -it easier to understand the contents of the packets. +  While I could have stopped there I decided to go one step further and +  reverse the command sequence that configures the WiFi settings so I +  could re-configure the printer's WiFi over USB if it ever got messed +  up. -Based on the traffic, I was able to come up with the following. +  To do that, I sniffed the traffic going from the Xprinter application +  and the printer's socket using Wireshark, assuming the commands it +  sends over the network are the same ones it sends over USB. -* Setting IP address +  [[file:xprinter_wireshark.png]] -  The following sets -  - IP =192.168.0.7= +  In the examples below I've encoded the data being sent to hex to make +  it easier to understand the contents of the packets. -  The IP hex: -  #+begin_src ruby -  [192, 168, 0, 7].map { _1.to_s(16).rjust(2, '0') } -  #+end_src +  Based on the traffic, I was able to come up with the following. -  #+RESULTS: -  | c0 | a8 | 00 | 07 | +** Setting IP address -  Packet contents from wireshark: +   The following sets +   - IP =192.168.0.7= -  =0000   1f 1b 1f 22 c0 a8 00 07= +   The IP hex: +   #+begin_src ruby +   [192, 168, 0, 7].map { _1.to_s(16).rjust(2, '0') } +   #+end_src -  | Description    | Characters    | -  |----------------+---------------| -  | Unit separator | =1f=          | -  | Escape         | =1b=          | -  | Unit separator | =1f=          | -  | Command code   | =22=          | -  | IP             | =c0 a8 00 07= | +   #+RESULTS: +   | c0 | a8 | 00 | 07 | -* Setting Subnet Mask +   Packet contents from wireshark: -  The following sets -  - Subnet mask =255.255.255.0= +   =0000   1f 1b 1f 22 c0 a8 00 07= -  Subnet mask to hex: -  #+begin_src ruby -  [255, 255, 255, 0].map { _1.to_s(16).rjust(2, '0') } -  #+end_src +   | Description    | Characters    | +   |----------------+---------------| +   | Unit separator | =1f=          | +   | Escape         | =1b=          | +   | Unit separator | =1f=          | +   | Command code   | =22=          | +   | IP             | =c0 a8 00 07= | -  #+RESULTS: -  | ff | ff | ff | 00 | +** Setting subnet Mask -  Packet contents from wireshark: +   The following sets +   - Subnet mask =255.255.255.0= -  =0000   1f 1b 1f b0 ff ff ff 00= +   Subnet mask to hex: +   #+begin_src ruby +   [255, 255, 255, 0].map { _1.to_s(16).rjust(2, '0') } +   #+end_src -  | Description    | Character     | -  |----------------+---------------| -  | Unit separator | =1f=          | -  | Escape         | =1b=          | -  | Unit separator | =1f=          | -  | Command code   | =b0=          | -  | Subnet mask    | =ff ff ff 00= | +   #+RESULTS: +   | ff | ff | ff | 00 | -* Setting gateway +   Packet contents from wireshark: -  The following sets -  - Gateway =192.168.0.1= +   =0000   1f 1b 1f b0 ff ff ff 00= -  Subnet mask to hex: -  #+begin_src ruby -  [192, 168, 0, 1].map { _1.to_s(16).rjust(2, '0') } -  #+end_src +   | Description    | Character     | +   |----------------+---------------| +   | Unit separator | =1f=          | +   | Escape         | =1b=          | +   | Unit separator | =1f=          | +   | Command code   | =b0=          | +   | Subnet mask    | =ff ff ff 00= | -  #+RESULTS: -  | c0 | a8 | 00 | 01 | +** Setting gateway -  Packet contents from wireshark: +   The following sets +   - Gateway =192.168.0.1= -  =0000   1f 1b 1f b1 c0 a8 00 01= +   Subnet mask to hex: +   #+begin_src ruby +   [192, 168, 0, 1].map { _1.to_s(16).rjust(2, '0') } +   #+end_src -  | Description    | Character     | -  |----------------+---------------| -  | Unit separator | =1f=          | -  | Escape         | =1b=          | -  | Unit separator | =1f=          | -  | Command code   | =b1=          | -  | Net Mask        | =c0 a8 00 01= | +   #+RESULTS: +   | c0 | a8 | 00 | 01 | -* Setting IP, netmask, and gateway +   Packet contents from wireshark: -  The following sets -  - IP =192.168.0.1= -  - Subnet mask =255.255.255.0= -  - Gateway =192.168.0.1= +   =0000   1f 1b 1f b1 c0 a8 00 01= + +   | Description    | Character     | +   |----------------+---------------| +   | Unit separator | =1f=          | +   | Escape         | =1b=          | +   | Unit separator | =1f=          | +   | Command code   | =b1=          | +   | Net Mask        | =c0 a8 00 01= | + +** Setting IP, subnet mask, and gateway + +   The following sets +   - IP =192.168.0.1= +   - Subnet mask =255.255.255.0= +   - Gateway =192.168.0.1= + +   Packet contents from wireshark: + +   =0000   1f 1b 1f b2 c0 a8 00 07 ff ff ff 00 c0 a8 00 01= + +   | Purpose        | Character     | +   |----------------+---------------| +   | Unit separator | =1f=          | +   | Escape         | =1b=          | +   | Unit separator | =1f=          | +   | Command code   | =b2=          | +   | IP             | =c0 a8 00 07= | +   | Subnet mask    | =ff ff ff 00= | +   | Gateway        | =c0 a8 00 01= | + +** Setting WiFi network + +   The following sets +   - SSID =SSID_HERE= +   - Key =PASSWORD_HERE= +   - Key Type =WPA2_AES_PSK= + +   SSID to hex: +   #+begin_src ruby +   "SSID_HERE".bytes.map { _1.to_s(16) } +   #+end_src + +   #+RESULTS: +   | 53 | 53 | 49 | 44 | 5f | 48 | 45 | 52 | 45 | + +   Key to hex: +   #+begin_src ruby +   "PASSWORD_HERE".bytes.map { _1.to_s(16) } +   #+end_src + +   #+RESULTS: +   | 50 | 41 | 53 | 53 | 57 | 4f | 52 | 44 | 5f | 48 | 45 | 52 | 45 | + +   Packet contents from wireshark (including string representation): +     #+begin_src +     0000   1f 1b 1f b3 06 53 53 49 44 5f 48 45 52 45 00 50   .....SSID_HERE.P +     0010   41 53 53 57 4f 52 44 5f 48 45 52 45 00            ASSWORD_HERE. +     #+end_src + +   | Purpose         | Character       | +   |-----------------+-----------------| +   | Unit separator  | =1f=            | +   | Escape          | =1b=            | +   | Unit separator  | =1f=            | +   | Command code    | =b3=            | +   | Key type        | =06=            | +   | SSID            | =SSID_HERE=     | +   | NUL-termination | =00=            | +   | Key             | =PASSWORD_HERE= | +   | NUL-termination | =00=            | + +   If the WiFi key type is anything like the menu, the other key types +   are as follows + +   | Key Type             | Value | +   |----------------------+-------| +   | =NULL=               | =00=  | +   | =WEP64=              | =01=  | +   | =WEP128=             | =02=  | +   | =WPA_AES_PSK=        | =03=  | +   | =WPA_TKIP_PSK=       | =04=  | +   | =WPA_TKIP_AES_PSK=   | =05=  | +   | =WPA2_AES_PSK=       | =06=  | +   | =WPA2_TKIP=          | =07=  | +   | =WPA2_TKIP_AES_PSK=  | =08=  | +   | =WPA_WPA2_MixedMode= | =09=  | + +** Setting all network options + +   The following sets +   - IP =192.168.0.7= +   - Subnet mask =255.255.255.0= +   - Gateway =192.168.0.1= +   - SSID =SSID_HERE= +   - Key =PASSWORD_HERE= +   - Key Type =WPA2_AES_PSK= + +   Packet contents from wireshark (including string representation): + +   #+begin_src +   0000   1f 1b 1f b4 c0 a8 00 07 ff ff ff 00 c0 a8 00 01   ................ +   0010   06 53 53 49 44 5f 48 45 52 45 00 50 41 53 53 57   .SSID_HERE.PASSW +   0020   4f 52 44 5f 48 45 52 45 00                        ORD_HERE. +   #+end_src + +   | Description     | Character       | +   |-----------------+-----------------| +   | Unit Separator  | =1f=            | +   | Escape          | =1b=            | +   | Unit Separator  | =1f=            | +   | Command Code    | =b4=            | +   | IP              | =c0 a8 00 07=   | +   | NetMask         | =ff ff ff 00=   | +   | Gateway         | =c0 a8 00 01=   | +   | Key Type        | =06=            | +   | SSID            | =SSID_HERE=     | +   | NUL-termination | =00=            | +   | Key             | =PASSWORD_HERE= | +   | NUL-termination | =00=            | + +* Post-packet analysis + +  At this point, after writing an application that could send +  identical packets given the correct input, I realized that for some +  reason, my printer was not responding to the commands issued from +  either the config utility or my program. + +  I tried to look deeper for better documentation, but was only able +  to come across [[file:80XX_Programmer_Manual.pdf][this PDF]] from their [[https://xprinter.com.ua/terms--conditions.html][Russian language website]], which +  unfortunately still didn't contain the WiFi setup instructions. + +  I was also able to find [[https://github.com/daotuyen9244/XprinterPOS][this]] GitHub repo that seems to contain some +  commands for Xprinter systems, but not the ones I need. + +  The data sheets on the Xprinter website claims they have the Linux +  test utility, which should contain the necessary tools to configure +  wifi on the printers, but it seems they only support Android and +  Windows. + +* Wine USB attempt 2 + +  I tried again to get the printer software to work under wine. It +  turns out wine only looks at =/dev/lp*= devices by default and +  doesn't add =/dev/usb/lp*=. This time I searched the wine wiki for +  ways to get the Linux =/dev/usb/lp0= device to show up as =LPT1= +  under wine. After some digging it appears you can tell wine which +  devices to map to =COM= / =LPT= ports with registry values. + +  It's described in section [[https://wiki.winehq.org/Wine_User's_Guide#Serial_and_Parallel_Ports][4.3.1]] on the [[https://wiki.winehq.org/Wine_User's_Guide][Wine User's Guide]]. + +  I followed the guide and created the following registry key. + +  #+ATTR_HTML: :title Adding the registry key in wine +  #+ATTR_HTML: :alt wine regedit +  [[file:xprinter_wine_regedit.png]] + +  I then restarted the wine server using the following command. + +  #+begin_src shell +  wineserver -k +  #+end_src -  Packet contents from wireshark: +  I've exported the registry entry [[file:xprinter_lp0.reg][here]] in case anyone wants to do the +  same. -  =0000   1f 1b 1f b2 c0 a8 00 07 ff ff ff 00 c0 a8 00 01= +  At that point the Xprinter setup tool was able to recognize the +  printer as =LPT1=. -  | Purpose        | Character     | -  |----------------+---------------| -  | Unit separator | =1f=          | -  | Escape         | =1b=          | -  | Unit separator | =1f=          | -  | Command code   | =b2=          | -  | IP             | =c0 a8 00 07= | -  | Subnet mask    | =ff ff ff 00= | -  | Gateway        | =c0 a8 00 01= | +  I then setup wireshark to be able to sniff USB traffic using the +  their guide [[https://wiki.wireshark.org/CaptureSetup/USB][here]]. -* Setting WiFi network +  From there I was able to figure out which USB hub it was running +  through and its device ID, and filter it out using a wireshark +  filter. -  The following sets -  - SSID =SSID_HERE= -  - Key =PASSWORD_HERE= -  - Key Type =WPA2_AES_PSK= +  #+ATTR_HTML: :title wireshark USB sniffing +  #+ATTR_HTML: :alt wireshark USB sniffing +  [[file:xprinter_usb_wireshark.png]] -  SSID to hex: -  #+begin_src ruby -  "SSID_HERE".bytes.map { _1.to_s(16) } -  #+end_src +  After sniffing the USB traffic sent by the xprinter configuration +  app, it looks identical to what was being sent over the TCP +  connection, meaning what I built should have worked. -  #+RESULTS: -  | 53 | 53 | 49 | 44 | 5f | 48 | 45 | 52 | 45 | +* The solution -  Key to hex: -  #+begin_src ruby -  "PASSWORD_HERE".bytes.map { _1.to_s(16) } -  #+end_src +  So apparently I made some assumptions when starting this project +  that turned out to not be true. -  #+RESULTS: -  | 50 | 41 | 53 | 53 | 57 | 4f | 52 | 44 | 5f | 48 | 45 | 52 | 45 | - -  Packet contents from wireshark (including string representation): -    #+begin_src -    0000   1f 1b 1f b3 06 53 53 49 44 5f 48 45 52 45 00 50   .....SSID_HERE.P -    0010   41 53 53 57 4f 52 44 5f 48 45 52 45 00            ASSWORD_HERE. -    #+end_src - -  | Purpose         | Character       | -  |-----------------+-----------------| -  | Unit separator  | =1f=            | -  | Escape          | =1b=            | -  | Unit separator  | =1f=            | -  | Command code    | =b3=            | -  | Key type        | =06=            | -  | SSID            | =SSID_HERE=     | -  | NUL-termination | =00=            | -  | Key             | =PASSWORD_HERE= | -  | NUL-termination | =00=            | - -  If the WiFi key type is anything like the menu, the other key types -  are as follows - -  | Key Type             | Value | -  |----------------------+-------| -  | =NULL=               | =00=  | -  | =WEP64=              | =01=  | -  | =WEP128=             | =02=  | -  | =WPA_AES_PSK=        | =03=  | -  | =WPA_TKIP_PSK=       | =04=  | -  | =WPA_TKIP_AES_PSK=   | =05=  | -  | =WPA2_AES_PSK=       | =06=  | -  | =WPA2_TKIP=          | =07=  | -  | =WPA2_TKIP_AES_PSK=  | =08=  | -  | =WPA_WPA2_MixedMode= | =09=  | - -* Setting all network options - -  The following sets -  - IP =192.168.0.7= -  - Subnet mask =255.255.255.0= -  - Gateway =192.168.0.1= -  - SSID =SSID_HERE= -  - Key =PASSWORD_HERE= -  - Key Type =WPA2_AES_PSK= - -  Packet contents from wireshark (including string representation): - -  #+begin_src -  0000   1f 1b 1f b4 c0 a8 00 07 ff ff ff 00 c0 a8 00 01   ................ -  0010   06 53 53 49 44 5f 48 45 52 45 00 50 41 53 53 57   .SSID_HERE.PASSW -  0020   4f 52 44 5f 48 45 52 45 00                        ORD_HERE. -  #+end_src +  It seems only the command to set all options at once consistently +  works, even when using the Xprinter setup tool from within Windows. +  The printer will also not allow you to reconfigure it's WiFi unless +  connected over USB. + +  After I narrowed down the number of commands I was testing to only +  =xb4= and only trying over USB, it worked fine. + +  I suppose I should have checked that all the commands worked +  properly from the beginning, but I did learn a lot along the way +  so it wasn't a total loss. + +  After figuring out the issue, I wrote a small command line tool to +  configure the printer. You can check it out [[https://github.com/dantecatalfamo/xprinter-wifi][here]]. -  | Description     | Character       | -  |-----------------+-----------------| -  | Unit Separator  | =1f=            | -  | Escape          | =1b=            | -  | Unit Separator  | =1f=            | -  | Command Code    | =b4=            | -  | IP              | =c0 a8 00 07=   | -  | NetMask         | =ff ff ff 00=   | -  | Gateway         | =c0 a8 00 01=   | -  | Key Type        | =06=            | -  | SSID            | =SSID_HERE=     | -  | NUL-termination | =00=            | -  | Key             | =PASSWORD_HERE= | -  | NUL-termination | =00=            | +  #+ATTR_HTML: :title Xprinter WiFi config tool screenshot +  #+ATTR_HTML: :alt Xprinter WiFi config tool screenshot +  [[file:xprinter_wifi_screenshot.png]] | 
