AI Coding Agent Reverse-Engineers a Canon Printer — Panaxeo
Contact
Open Menu<br>Close Menu
Contact
Open Menu<br>Close Menu
AI reverse-engineered my printer. I just pressed the buttons.
Aug 14
Written By Igor Liska
Getting a printer onto a locked-down network without the vendor app.<br>Written by Igor Liska and Opus 5.
What I wanted<br>I have a Canon PIXMA TS3300 and a home network it needed to join. That should be a five-minute job, and for most people it is: install the vendor app, tap through a wizard, done.<br>I did not want to install the vendor app.<br>Partly, this is taste. Vendor apps tend to be built to a price; they age badly; they want an account; and they quietly make your hardware depend on a company's continued interest in supporting it. A printer is a box that puts ink on paper. It should not need a relationship.<br>But mostly it was practical. The network I wanted the printer on is filtered by hardware address, and I did not want to add my phone to it.<br>Here’s the blocker: the Canon app can only set up the printer for the network the phone is currently joined to . So the supported path required letting my phone onto a network I had deliberately kept small, purely so it could tell the printer about it, then taking the phone off and putting the filtering back the way it was. Two reconfigurations of a network I was happy with, in order to avoid learning anything.<br>Install an app and reconfigure my network twice, in 2026. I would rather spend the same evening teaching an agent to do it, and never think about it again.<br>Why this is harder than it sounds<br>A printer fresh out of the box has no network. So it makes its own: a temporary access point that the app connects to in order to hand over the real network's name and password. Canon calls this cableless setup. It is a sensible design, and it means the entire configuration exchange happens over a channel that only exists for about two minutes.<br>It is worth sitting with the problem the manufacturer has here, because it explains a lot of what follows. The printer and the phone have never met. There is no pairing, nothing to authenticate with. Other vendors solve it by printing a password on the case, or deriving one from the serial number and the manufacturing date. There are not many options. Whatever the printer uses to protect that two-minute window, it has to be something it can tell you itself, which means it is not really a secret.<br>To do that without the app, I had to speak whatever the app speaks, over that temporary network, within that window.<br>There was one more constraint: I did not do this work on my laptop. I did it from a container at home that Claude Code lives in: its own filesystem, its own place on the network, and it keeps working when I am not at the computer. That machine has no wireless hardware at all, so it cannot join the printer's temporary network.<br>What it does have is an old Raspberry Pi on the wired network, and the Pi has a radio. So the Pi became the wireless front end. The agent's machine drives it over Ethernet, and the Pi does the actual joining and relaying.
So the division of labor is: the agent gets a Linux box to play in, and I decide what it is allowed to try and press the buttons on the printer. That setup deserves its own write-up, which I will get to, because it turned out to be more interesting than this printer.<br>Attempt one: read the app<br>If the app knows the protocol, the app contains the protocol. So we pulled apart the Android package.<br>What we found: the setup channel is SNMPv3, the version of SNMP that has authentication and encryption. Everything lives under Canon's own branch of the SNMP tree, and the names are not exactly cryptic once you see them together
OID = "1.3.6.1.4.1.1602.1.3" # 1602 is Canon's enterprise number<br>O_MODE = OID + ".2.100.2.0" # wireless operating mode<br>O_SSID = OID + ".2.100.10.3.0" # network name<br>O_AUTH = OID + ".2.100.10.6.0" # authentication type<br>O_ENC = OID + ".2.100.10.7.0" # encryption type<br>O_WPAPASS = OID + ".2.100.10.110.0" # passphrase<br>O_ENABLE = OID + ".3.3.1.100.10.1.3.3" # profile enable flag<br>O_P5 = OID + ".2.100.10.5.0" # a link field the WPA2 profile wants set to 6
The username is fixed. And the credentials are not a shared secret at all, they are derived from an identifier the printer publishes about itself, called the engine ID. Anyone who can see the printer can compute them.<br>The derivation is short enough to quote. The printer's engine ID is a public identifier, and the authentication key is built out of it with no secret involved anywhere:
def auth_key_from_engineid(engineid_bytes):<br>h1 = engineid_bytes.hex()<br>a = h1[10:] # drop the fixed prefix, what remains is the hardware address<br>return create_v3_password_hash(a, a, a)
Yes, the same value three times. The function takes three separate inputs, and the app feeds it one value in all three slots, which suggests it was written to be general and then only ever used one way. I have no better explanation than that, and it does...