-
James McFarland - 18 May, 2026
-
hacking a chinese photo frame
I was gifted a Chinese digital photo frame. As gifts go, this was excellent, because it was also obviously a bit cursed. On paper it was a modern-ish Wi-Fi photo frame. In practice it turned out to be an elderly Android tablet in costume: thin OEM shell, deeply stale software stack, mystery vendor apps, and exactly the sort of vibes you do not want from something that wants network access in your house. That is usually the fork in the road with hardware like this. Either it is annoyingly locked down, or it is barely held together and unexpectedly cooperative. Thankfully, this one was the second kind. The end result is better than I expected going in. The OEM apps are disabled, the frame now boots into a custom launcher, and that launcher points at a tiny local web service which proxies and prepares images from Immich (a self hosted iCloud/Google Photos alternative) for display. It is no longer a "smart frame" in the vendor sense. It is now a single-purpose display terminal that happens to live inside a photo-frame enclosure. This post was written with the assistance of generative AI. This was not a story about making an old Android device secure. It was a story about reducing how much trust the device needed. The frame still runs ancient software, so the win here is containment, minimised exposure, and a much narrower job description.The basic problem The device identified itself as a P105, manufactured by ZED, running Android 6.0.1 with a 2016-07-05 security patch level. At the same time, the build date was from May 2024. That combination is exactly as dubious as it sounds. In practice it usually means an ancient Android userspace, an ancient kernel branch, a vendor rebuild recent enough to keep shipping units, and very little interest in long-term software quality or security. Under the skin, the frame was a Rockchip RK3126 device in the rk312x family, with a 32-bit ARMv7 userspace, permissive SELinux, and a very old Android 6 board support package. In other words: not modern, not trustworthy, but probably hackable.Wi-Fi photo frame Actually an Android 6 Rockchip tablet OEM launcher at com.waophoto.smartframe Update-related packages including com.adups.fotaAndroid 6.0.1 with a 2016-07-05 patch level 2024 build date on top of that ancient base Permissive SELinux Unknown vendor software stack with network-facing behaviourKeep the hardware Remove reliance on the OEM ecosystem Avoid putting secrets on the frame Make it do one boring job reliablyThe first good sign: ADB was already there The first question was whether this was a locked appliance or just a generic Android system wearing a photo-frame costume. It did not take long to answer. USB ADB was available immediately, and once I had a shell it became clear that the frame was much closer to "low-end tablet with a custom launcher" than "sealed embedded appliance". Even better, su was present and working: adb shell whoami adb shell su -c whoamiThat is the sort of result that changes the whole shape of a project. Without root, you are negotiating with a device. With root, you are mostly deciding how patient you want to be. Wireless ADB was also possible, which made iteration much less annoying once the initial access work was done. Mapping the weirdness before breaking anything Once I knew I had shell access and root, the next step was not "start ripping things out". It was "work out what kind of weird machine this actually is before I brick it for no reason". The OEM experience lived in com.waophoto.smartframe, installed as a system app and acting as the default HOME launcher. There were also update-related packages like com.adups.fota and android.rockchip.update.service, which is exactly the sort of thing I do not want running on an old opaque device sitting on my network. Still, the frame was not trapped in irreversible kiosk mode. Standard Android Settings could be opened over ADB, other apps could be installed, and the launcher behaviour was ordinary enough that it could be replaced cleanly. That shaped the first phase of the project:Identify the platform properly. Pull backups of anything I might regret losing. Confirm what recovery options actually existed. Only then start disabling the OEM layer.That caution mattered because old Android appliances are often weird in exactly the wrong ways. Standard assumptions do not always hold. In this case, adb reboot bootloader dropped the frame into a Rockchip-style low-level mode rather than a clean normal fastboot workflow. Useful to know, not something I wanted to trust as my primary recovery path. The alternate firmware route was tempting Before committing to "leave the stock Android install in place and work around it", I did spend some time testing whether a more dramatic route was realistic. That mostly meant answering three questions:Is there a real recovery environment? Is fastboot usable? If not, is there at least a vendor-specific loader mode as a last resort?The answers turned out to be yes, sort of but not really, and yes in a very Rockchip way. adb reboot recovery brought the frame into a genuine Android recovery screen with options like Apply update from ADB, Apply update from SD card, Mount /system, and View recovery logs. That was encouraging. Unfortunately, the hardware then reminded me what sort of device this was. The frame effectively only had one useful physical button, and recovery navigation with it was flaky enough to be borderline unusable. Tiny presses often acted like selections, which meant recovery was more "good to know this exists if things go badly wrong" than "excellent, I can iterate here comfortably". Fastboot was even more awkward. In one state the device would enumerate in a fastboot-like way; in another it would appear as a Rockchip USB device with 2207:310d, which is much more suggestive of a vendor loader path than a friendly Android flashing workflow. Even when fastboot devices showed something, meaningful commands mostly just hung. That is a useful lesson with old embedded Android hardware: seeing a device in fastboot devices does not mean you have a practical flashing workflow. Sometimes it just means you have found another half-working interface. So yes, the alternate firmware path was explored. Recovery mode was real. Low-level modes were real. But none of them looked clean or robust enough to justify making them the main plan when ADB plus root in normal Android was already working. Replacing the OEM layer The easiest first win was to install a proper launcher and a couple of tools so the frame stopped behaving like a captive appliance. Nova Launcher installed cleanly. So did Firefox, Fully Kiosk Browser, and a small navigation overlay utility to compensate for the ROM's slightly bizarre navigation situation. Once a replacement HOME experience was proven to work, I disabled the OEM launcher, the update services, and a couple of factory-test packages rather than deleting anything outright. That became the general rule for the whole project: do the reversible thing first. On low-end Android hardware, "I can remove it later" is usually much wiser than "I should rip it out immediately". The safest kind of hacking on junk hardware is usually subtractive. Disable first. Observe. Keep your recovery options. Treat irreversible changes like a late-game optimisation, not a starting move.The obvious solution that was not actually the right one At first, Fully Kiosk Browser looked like the cleanest finish. Conceptually it fit the problem well: fullscreen, single URL, kiosk-oriented, and easy enough to configure. I inspected its config, used its supported import mechanism to push settings to the device, and got it opening the right URL in the right sort of full-screen mode. That part worked. The part that did not work was boot behaviour. On paper, Fully had the right manifest entries and a boot receiver. In practice, after rebooting the frame it would often just land in Nova instead. Log inspection showed Android trying to deliver BOOT_COMPLETED and then refusing to launch Fully because the process was considered bad. That is a wonderfully cursed old-Android problem. Nothing is obviously broken, but the firmware and the app do not quite agree on how boot-time startup should behave. I also confirmed that Fully had hidden HOME-capable activities inside the APK, and those could be enabled as root. That made it possible to surface Fully as a real launcher candidate rather than just a foreground app. Useful discovery, but also the point where it became obvious I was spending effort making a workaround stack pretend to be a first-class solution. That is usually the signal to stop being clever and build the thing you actually need. The real constraint was the browser The bigger issue was not launcher selection. It was browser compatibility. The stock WebView on this device is based on Chromium 44.0.2403.119. That is prehistoric. A lot of modern web apps simply do not target anything remotely that old, and reasonably so. Even if you can install newer apps around the system, the core embedded browsing story is still constrained by an Android 6-era rendering stack and extremely modest hardware. That changed the whole design question. Instead of asking:How do I make this frame run a modern web app?the better question became:What is the simplest possible system I can build that this frame can render reliably?That is a much more productive question, and honestly a much more fun one too. Building for the device we actually had The final setup ended up split into two small pieces: a custom Android launcher app on the frame, and a tiny local server that prepares a slideshow from Immich. The launcher app is intentionally boring. It registers as HOME, opens a configurable URL in a WebView, and stays out of the way. I built it specifically to be Android 6-compatible and deliberately kept it to plain framework Java rather than dragging in a pile of modern Android dependencies that would only make life harder on this hardware. It has a small settings screen for changing the target URL, a hidden control bar revealed with a triple-tap, and startup logic that waits for network connectivity before trying to load the page. That last part mattered more than it sounds. On boot, the app often came up before Wi-Fi was ready. A display that fails once and gives up is annoying. One that waits a few seconds and carries on is useful. Why a local server made more sense than talking to Immich directly The slideshow side followed the same principle: keep it simple, and keep the complexity somewhere more capable than the frame itself. The local service sits between the frame and Immich. It keeps the Immich API key off the frame, proxies image requests, selects and randomises images from a chosen album, normalises images server-side, rebuilds the slideshow queue when the album refreshes, and serves a minimal HTML, CSS, and JavaScript frontend that an ancient browser can actually cope with. That architecture solved several problems at once:It avoided exposing credentials to a device I do not trust. It removed any need to rely on Immich's browser-facing behaviour directly. It let the slideshow page target Chromium 44 instead of a modern browser. It moved the fragile part of the stack onto a machine that is easy to update and inspect.The frontend is intentionally plain. No framework, no build-heavy nonsense, no assumption that the browser is anything other than old and slightly grumpy. The viewer preloads the next image, handles timing predictably, and does the bare minimum needed to be a pleasant digital frame rather than a browser demo. The approach in one snapshotOEM launcher: com.waophoto.smartframe Update-related packages such as com.adups.fota Vendor-controlled "smart" behaviour Direct credentials or API access from the frame itselfThe stock Android install Root and ADB access Reversible changes where possible The original hardware, display, and enclosureBoot into a custom launcher Wait for network before loading Open a local slideshow endpoint Display Immich-backed photos through a tiny proxy serviceWhy I did not go deeper There are always more extreme routes with devices like this. I could have pushed harder on system partition changes, tried to wire in boot scripts, or spent more time exploring Rockchip flashing paths and alternate firmware options. Some of that is probably still viable. But the point of this project was not to prove that the frame could be completely reborn at every layer. The point was to make it useful without having to trust it more than necessary. Once I had owner control over the device, the OEM software disabled, a stable custom launcher, a slideshow service designed around the hardware's real limits, and a clean path into Immich, there was not much value in forcing a more dramatic solution. That is probably the most useful lesson in the whole exercise. Repurposing old hardware is not always about doing the most technically impressive thing. Often it is about finding the simplest architecture that respects the device's limitations and shrinks the trust boundary at the same time. The result The frame now boots into a custom launcher app, loads a local slideshow endpoint, and displays images sourced from Immich through a small proxy service built for the job. The OEM apps are out of the way. The update services are disabled. The device is still old and insecure, and I would not trust it with anything sensitive, but it no longer needs to be trusted very much. It is doing one job, on my terms, on an isolated VLAN. That feels like the right ending for hardware like this. Not a perfect rescue. Not a full custom ROM. Just a slightly sketchy object, understood well enough to be useful again.
-
James McFarland - 10 Apr, 2026
-
software security in the age of ai
Haven't you heard enough about AI already?! For transparency, here's the cover image prompt: Create a clean, sharp, modern SaaS-style blog cover image for an article about software security in the age of AI. The theme is AI-assisted cyber defence versus AI-assisted attack. Show an abstract, high-end engineering aesthetic: dark or neutral background, precise geometric forms, subtle glowing network lines, layered system diagrams, defensive boundaries, and autonomous agents probing a secure digital environment. Suggest ideas like continuous adversarial testing, attack paths, threat modelling, and resilient infrastructure, but keep it abstract and tasteful rather than literal or cheesy. Style: premium developer tooling brand, similar to Cloudflare's blog, Linear, Vercel, Stripe, or a high-quality security startup. Minimal, polished, technical, elegant, editorial. Avoid stock art, shields, padlocks, hooded hackers, binary rain, or generic cyberpunk clichés. Composition: strong focal point, lots of negative space for title overlay, landscape blog-header format, balanced and structured like a product engineering illustration. Use crisp lines, subtle gradients, soft glows, depth, and restrained detail. Visual motifs: abstract secure system architecture, AI agents as moving nodes or light traces, segmented trust boundaries, monitored infrastructure, attack simulation in a sandboxed environment, connected devices or services represented as minimal icons or blocks. Convey that software is under constant automated pressure, but defended by equally advanced systems. Color palette: charcoal, slate, soft whites, cool greys, deep blues, with restrained accents of electric cyan or amber. High contrast, premium, clean. Mood: calm, intelligent, rigorous, high-quality engineering, proactive security, modern infrastructure, technically optimistic but serious. No text, no logos, no UI screenshots, no people, no photorealismThis post was written in response to the Claude Mythos news; however, its implications reach far beyond Anthropic and apply to much of the wider software and security industry. Claude Mythos The Mythos model represents a jump in capability that we have not seen in a while, and I’d recommend reading the system card rather than having me rehash its claims here. In essence, it is a highly capable model which, like state-of-the-art (SoTA) models before it, lowers the barrier to entry for technical tasks. This time, that barrier appears to be the one surrounding security, penetration testing, and vulnerability research. We are getting to the point where, cost and access aside, increasingly autonomous systems can run meaningful attack workflows against a particular target, exploring different avenues in parallel and doing so meticulously and persistently. There are a few different areas within this news that are worth breaking down. The benchmarks An important caveat is that, whilst Anthropic have released very promising benchmarks, this model is only available to private groups at the time of writing. That means we cannot independently verify those benchmarks, or the model’s real-world performance more broadly. Even so, this appears to be a large but natural step forward in capability, and one that the industry is going to have to come to grips with. The private model The model being held back is, to me, both understandable and concerning. Anthropic appear to feel they need to restrict access in line with their constitution and their stated goal of doing AGI safely, and in principle that makes sense. The issue is that AI capabilities tend to proliferate quickly. OpenAI, Google, and others are usually not far behind one another, and it would be surprising if similar capabilities were not already being explored elsewhere. Keeping a model behind closed doors and limiting it to private groups may reduce short-term risk, but it does little to address the longer-term concerns now on the table. As engineers, we need to understand the shape of these capabilities so that we can adapt and build systems that not only withstand this shift in the threat landscape, but also make constructive use of the same capabilities on the defensive side. Public models with Mythos-like capability are likely coming, and we need to be ready. Why does this even matter? A lot of software and connected devices are already far less secure than people assume. Many real-world attacks do not require elite expertise; they rely on common weaknesses, persistence, and a willingness to try known techniques repeatedly until something works. I would be willing to bet that the majority of “smart home” devices could be compromised with relatively little effort. In many cases, it is not that the attacks are impossible or especially novel; it is that few people have had the incentive to spend the time doing it at scale. The knowledge around common security flaws (buffer overflows, excessive permissions, default credentials, poor authentication flows) is already widely available, and these are exactly the kinds of patterns that models can absorb and apply. Take a category many will have in their homes: robot hoovers, cameras, smart bulbs, plugs, and other connected devices. AI-assisted attack workflows lower the cost of trying the same low-skill or moderately skilled attacks across a huge number of targets. Then expand that idea to every connected device. In many cases, variation between manufacturers, firmware, and hardware may have acted as a kind of accidental protection against large-scale automated exploitation. What this means for security When I was younger, I was exactly the sort of script kiddie running basic simulated attacks against systems. Even as an inexperienced novice, it was possible to find vulnerabilities or misconfigurations in poorly defended software systems. What we have now is not just that same level of curiosity or trial and error. We now have models that can play a similar role, except they are not novices: they have access to vast amounts of information, can work persistently, and can iterate far faster than a person manually trying things one at a time. This is not to say that we should stop developing or using these models. They are an incredibly important capability, and what they can do from both an educational and practical standpoint is remarkable. But we do need to be realistic about what they mean for the security baseline of the systems we build. Distribution This also exacerbates and exposes a wider issue that is already very well documented in software and device security: distribution and updates remain a major weak point. Users do not update their software as often as they should, and often do not know that they need to. Some products do not even have proper update mechanisms. Others are not Internet-connected, or are only updated through manual intervention. Those systems are the really concerning part. How do we solve that as an industry? I do not know, and I do not think there is a single answer. One option is greater pressure on device manufacturers to invest the engineering effort needed to secure and maintain their products across their full supported lifetime. That is expensive and politically difficult, but not impossible. It becomes especially difficult when companies shut down, are acquired, change direction, or otherwise stop supporting products that remain deployed in the world. Another possibility is some kind of community-driven effort to maintain or update unsupported devices. In theory, that could help; in practice, making that work safely, legally, and at scale would be extremely difficult. It would also rely on cooperation from manufacturers around source code, documentation, signing keys, update mechanisms, and other practical constraints. Either way, we still have to deal with the fact that many devices in our homes, offices, and cars are insecure. The fact that they have not yet been breached does not mean they are secure. It may simply mean that no one has put sustained effort into breaking them yet. New products absolutely must be designed with this threat model in mind. We should assume that throughout a product’s lifetime it will face constant, automated, and increasingly capable attack attempts. Going forward I believe we need to fight fire with fire. We need defensive and adversarial models that can run against our applications and systems in the same way that red teams, blue teams, and security researchers do today. If attackers are using these tools, and they increasingly will be, then defenders need to use them too. Take a web system, for example. Create an environment identical to production, with as much realistic data as possible. Isolate it, and run a set of agentic models against it with the goal of finding flaws and holes. Let them operate autonomously within that environment and see how they perform. Perhaps this could be a part of an applications CI/CD pipeline That does not mean responsibility sits only with a company’s security operations centre or a platform team. Security has to be treated as a shared engineering responsibility. Individual developers, platform teams, security engineers, and product organisations all need to push for and advocate for more secure systems. AI is already a force multiplier for writing software. It now also needs to become a force multiplier for securing it.