I wanted to build a DIY photobooth for my wedding, the type of box that lets you take pictures and even print them out in a small format. I though about the printer itself and decided to go for a VC-500W from brother, mostly because of its compact size (it fits in a relatively small box) and because of the zero ink technology: you can just insert or replace the label roll, which includes "ink" and paper and is adhesive, too.
(interestingly, the color is actually achieved through separate heat-sensitive microcrystal layers, see the Wikipedia article for more information)

Anyhow, the label printer from Brother does support Windows, Mac OS X and Android/iOS only, but my plan was of course to use a Linux-powered Raspberry Pi as the photobooth brain, as shown in the following schemathic:

The Raspberry Pi connects to the label printer via WiFi

Note that the printer itself can either connect to an existing WiFi hotspot, broadcast its own hotspot or talk over USB. I ignored the USB variant because I could not run Windows and have an USB sniffer on at the same time, but also because a wireless connection is more practical.

Hacking the printer

Hopeful that this would work, I bought the VC-500W and installed the official Windows application, as well as installing mobile app itself. Both are similar, although the app seemed to require a mDNS/DNS-SD record to be published for it to find the printer in the network, whereas the Windows application allows you to connect to an IP address directly.

In order to sniff the packets coming from the official applications, I had to pretend being the printer itself (performing a Man in the Middle Attack) and not only sniff, but also relay packets to the printer and back to the application in the process. I had no access to an OpenWRT router at the time, so I had to clumsily set up a mDNS/DNS-SD record for the printer itself to my computer's address, manually set up packet forwarding and run tcpdump locally.

What I strongly recommend instead, in case you want to try out reverse engineering yourself, is to set up a router with tcpdump instead, for example by flashing an OpenWRT on it and installing its package. There's no need to clumsily work with obscure commands on Linux to forward packets and test via trial and error if the router can do the sniffing for you instead.

Anyway, the results seemed at least somewhat promising, as I saw no encryption at all and could read the raw TCP packets being exchanged between the application and label printer, and Wireshark started showing XML payloads in cleartext. The format itself was somewhat curious, but some fields were definitely readable.

What was definitely off was seeing XML being transferred, but without any HTTP encapsulation whasoever, simply raw XML in a TCP packet, including the address of the operation or "file" being called or fetched. Additionally, some XML files had a kind of custom "content-length" XML preamble, whereas other hadn't.

Then I started a test print, only to see binary blobs scrolling down my screen. That is, intil I actually looked at the beginning of the TCP payload, right after the last XML message. After a couple of unreadable bytes I saw the "JFIF" header, clear sign that the blob itself was only the JPEG image being transmitted (in binary form) on the same TCP channel (which was ASCII-only until now).

To sum up, the following picture shows the different types of messages that can be exchanged with the printer itself:

The VC-500W uses a custom TCP protocol based on XML

Putting it all together

I decided to create a python3 module to talk to the printer, seeing as it's the easiest way of making it work on the raspberry Pi. The only tricky part of the programming exercise was to handle the ASCII/binary TCP channel and to interpret the XML messages correctly.

It took some trial and error, however, before getting it right, and the printer itself would every once in a while discard some printer roll (especially when trying to use a lock).
At one point the printer seemed stuck and would not restart, hence an explicit disclaimer in the next section.

Download

Warning: the following python module may permanently damage your printer. You download it at your own risk.
Version Format Download Size
0.1 zip labelprinter-0.1.zip 35 KB
tar.xz labelprinter-0.1.tar.xz 23 KB

This python module is released under the terms of the GPLv3 or any later version. See the details.

Usage

The module itself can be downloaded below and can be started directly with the included labelprinter.sh helper script or via python3 -m labelprinter. Here is an overview of the options offered by the main routine:

usage: labelprinter.sh [-?] [-h HOST] [-p PORT]
                       (--print-jpeg JPEG | --get-status | --release JOB_ID)
                       [--print-lock] [--print-mode {vivid,normal}]
                       [--print-cut {none,half,full}] [--wait-after-print]
                       [-j]

Remotely control a VC-500W via TCP/IP.

optional arguments:
  -?, --help            show this help message and exit
  -h HOST, --host HOST  the VC-500W's hostname or IP address, defaults to
                        192.168.0.1
  -p PORT, --port PORT  the VC-500W's port number, defaults to 9100

command argument:
  --print-jpeg JPEG     prints a JPEG image out of the VC-500W
  --get-status          connects to the VC-500W and returns its status
  --release JOB_ID      tries to release the printer from an unclean lock
                        earlier on

print options:
  --print-lock          use the lock/release mechanism for printing (error
                        prone, do not use unless strictly required)
  --print-mode {vivid,normal}
                        sets the print mode for a vivid or normal printing,
                        defaults to normal
  --print-cut {none,half,full}
                        sets the cut mode after printing, either not cutting
                        (none), allowing the user to slide to cut (half) up to
                        a complete cut of the label (full), defaults to full
  --wait-after-print    wait for the printer to turn idle after printing
                        before returning

status options:
  -j, --json            return the status information in JSON format