Last week, I purchased a fun toy: a 4.2-inch e-Paper display that works without a battery and can be updated via an NFC wireless signal from a mobile app.
The problem, however, is the app. The manufacturer provides iOS, Android, and PC applications, but the UI is poorly designed and does not allow raw image uploads. Instead, images are always dithered by the app, which significantly reduces display quality.
So, I decided to reverse engineer the NFC protocol and re-implement the app — and that was completed in a day, with the help of an “AI agent.”
I was new to NFC itself, so I first needed to learn the fundamentals. Then I reviewed the Core NFC APIs to get an overview of what would be required. I chose to reverse engineer their Windows app because the binary is easy to access.
I launched a Windows virtual machine and installed their app. I also installed vsmartcard, a virtual NFC driver that works as an network server, allowing me to capture communication between the app and the NFC card. Unfortunately, the prebuilt binary is for x86_64, and I was not motivated enough to build it from source, so I used my old Intel MacBook Pro to run the virtual machine.
After repeatedly updating the e-Paper using their app, I was able to capture sufficient protocol payloads, logs, and bitmap images sent to the device, along with the application binary itself.
I put everything into one place and asked the agent to analyze it — literally.
The Windows app (and likely the apps for other platforms) is built with Flutter. The actual application logic resides in a Dart snapshot in ELF format, which is difficult to reverse engineer. However, it did not appear to be obfuscated, so all string symbols were visible.
There are multiple NFC specifications, but this e-Paper device uses ISO 14443-A (Type A) and ISO 7816 APDU as the transport layer. APDU is a relatively simple protocol that sends and receives raw bytes in a specific command format.
By asking the agent, within minutes it analyzed the proprietary commands, identified patterns that matched the logs and input bitmaps, and determined how the data was transformed — either through pattern analysis or by searching string symbols in the ELF binary. This is essentially what I would have done manually, but it would have taken many more hours.
What impressed me most was how it identified the compression method.
It likely inferred from the high entropy of the payload that compression was being used, searched for possible compression method names in the ELF binary, and found the lzo1x_1_compress symbol.
It then generated a proof-of-concept Python script to decompress the captured data and verify that it matched the bitmap data sent to the device.
Once the agent and I had a high-level understanding of the protocol, it was time to implement a proof of concept using Core NFC in an iOS app.
I set up the basic app configuration — App ID, entitlements, and Info.plist settings — which unfortunately still required manual work, as Apple’s ecosystem is not yet fully manageable by agents.
I then asked the agent to implement the Swift code to reproduce the app’s functionality.
This turned out to be the most challenging part.
Core NFC behavior is not well documented, and there are several pitfalls.
One of the biggest obstacles was the “20-second rule.” It is mentioned only by a few developers on forums, along with a response from an Apple engineer: NFCTagReaderSession invalidates the connection exactly 20 seconds after a connect(to:) call.
Unfortunately, 20 seconds is not long enough to refresh the e-Paper. E-Paper displays refresh slowly, and this device takes more than 20 seconds to complete a refresh. Moreover, it must remain connected via NFC because it draws power from the NFC reader even when it is not actively communicating.
The agent was not able to solve this issue. Its proposed solutions remained confined to conventional approaches.
At this point, it became a human problem: how could this limitation be worked around? There was little to no information available online. A few hints existed, but none provided an explicit solution.
After experimenting with the Core NFC APIs and observing the behavior of the official iOS app, I discovered that the process could be split into two connections: first, connect to send the image data; then call restartPolling() to reconnect and trigger the e-Paper update.
With this two-phase approach, it became possible to complete the refresh within the time limit.1
At this point, I understood roughly 80–90% of the device’s behavior. I asked the agent to write a summary of the protocol and published it on Gist, then shared it on Twitter. (Yes, it is still Twitter to me — you know why.)
Within hours — not even a full day — multiple developers had produced their own implementations on various platforms and in different languages, including Windows and Python. Some even recreated a Windows app. While it is impossible to be certain, it seems clear that coding agents played a role in accelerating this process.
With proper technical documentation, the “last mile” of application development is significantly easier than before. I would not claim that it has disappeared entirely — the final 20% can still take 80% of the time — but agents can meaningfully reduce the friction.
This is probably my first piece of writing about the recent rise of coding agents. Reverse engineering becomes much easier when assisted by an agent that can use tools effectively and excels at pattern recognition. The final stretch of development can be less painful, making it easier to produce a usable app quickly.
However, there is still substantial value that humans provide: the ability to reinterpret specifications, derive deep insights from behavior, and form the right mental models. Above all, curiosity and genuine interest drive the process of building something meaningful.
In any case, it was a fun project.2
Footnotes
-
This approach worked to some extent. However, I later found a better solution. There is actually a different APDU command parameter that blocks the response on the NFC device side, effectively extending the 20-second connection limit. Using this parameter, the e-Paper now refreshes reliably. ↩
-
The full Swift implementation, reworked manually into a reusable API, is available here. ↩