<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Undocumented]]></title><description><![CDATA[This is where I write about the connected devices in my home, the kind plenty of us have, and what turns up when I poke at the parts that aren't in the manual.]]></description><link>https://undocumented.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Tue, 03 Nov 2026 16:59:20 GMT</lastBuildDate><atom:link href="https://undocumented.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[My projector didn't want to be vibe-coded]]></title><description><![CDATA[A 70-euro MagCubic projector didn't be vibe-coded — locked bootloader, no ADB, signed apps. How a root vendor service and one leftover property flag…]]></description><link>https://undocumented.hashnode.dev/my-projector-didnt-want-to-be-vibe-coded</link><guid isPermaLink="true">https://undocumented.hashnode.dev/my-projector-didnt-want-to-be-vibe-coded</guid><category><![CDATA[whaleos]]></category><category><![CDATA[jailbreak]]></category><category><![CDATA[Amlogic]]></category><category><![CDATA[magcubic]]></category><category><![CDATA[Right To Repair ]]></category><category><![CDATA[projector]]></category><category><![CDATA[Android]]></category><dc:creator><![CDATA[Alex Pichler]]></dc:creator><pubDate>Tue, 27 Oct 2026 13:38:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6adbeec878f53e928ea3798d/77676e05-5baa-43ee-97f8-376f6054051b.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There's a genre of post going around right now. Someone points an AI coding agent at their smart TV and types:</p>
<blockquote>
<p>get rid of the ads and the bloat, no mistakes</p>
</blockquote>
<p>A few minutes later they're posting screenshots of a clean home screen. <a href="https://techcrunch.com/2026/10/19/people-are-using-ai-to-make-their-smart-tvs-dumb-again/">TechCrunch</a> called it making smart TVs "dumb again," and <a href="https://decrypt.co/381456/vibe-coding-ads-out-smart-tvs-heres-how">Decrypt</a> went with "vibe coding your TV." The idea sounds simple: point the thing at your TV, let it tidy up, nothing you can't undo, no root needed.</p>
<p>I have a cheap MagCubic projector that runs WhaleOS, a locked-down Android that ships on a lot of budget TV hardware. It's slow in a way that feels deliberate, and I mostly use it as a dumb HDMI screen anyway. So one evening I tried the trick from the posts: put it on the network, get a shell, clean it up.</p>
<p>It did not go the way those posts go. Twenty minutes of tidying turned into a couple days, because every door the demos walk through was bolted shut on this one. I don't regret the time I spent on this. It was a good puzzle.</p>
<h2>No easy way in</h2>
<p>The plan was ADB: turn on developer options, enable USB debugging, sideload from there.</p>
<p>This box runs a vendor Settings app, not Android's, and the developer path through it is half gone. There's no build number to tap seven times. A separate "Developer Tools" app from the app store opens, but clicking anything the vendor stripped out just doesn't work or even crashes it. Wireless debugging is the clearest tell: the toggle is there and selectable, and the moment you touch it the switch turns on, thinks for a second, and slides back to off.</p>
<p>Trying to install an APK off a USB stick did nothing. No install, no error, not even a dialog. Flashing a signed recovery image got me the one straight answer the device ever gave:</p>
<pre><code class="language-plaintext">Download is not allowed on locked devices
</code></pre>
<p>The bootloader is locked with no unlock in any menu, secure boot is fused on in HW, verified boot is enforcing. Every published way in was a wall, and most of them were walls someone built on purpose. Nobody ships a settings app with its developer menu unreachable by accident.</p>
<p>(For anyone with the same box: a MagCubic sold as HOTACK SMART2KPJT, Amlogic T5D, Android 13, WhaleOS build from mid-2025, seventy euros in July 2025. If that's roughly your projector, the rest might apply to you as well.)</p>
<p>I'd owned it over a year. The reasonable move was to live with a slow screen or get a new one. I did neither.</p>
<h2>Borrowing a shell</h2>
<p>If the OS won't give me a shell, I can build one out of software I'm allowed to run. None of this is a vulnerability, just ordinary features used off-label:</p>
<ul>
<li>The casting app speaks DLNA. Started and paired from the couch, it will inject a key press or launch one of the manufacturer-approved apps on request.</li>
<li>Cx File Explorer has a FTP server. Switch it on, you get read/write to shared storage, but nothing privileged.</li>
<li>Kodi has a JSON-RPC remote and runs add-ons, and add-ons are Python. This was my way in.</li>
</ul>
<p>An add-on runs inside Kodi's process, next to Kodi's Java VM, and Python plus that VM is enough to call Android's system services from a program built to play video. That got me a shell on a device that wouldn't hand me one.</p>
<p>I did most of this with AI coding assistants, same as the TV posts. One thing worth passing on: Claude would now and then decide my work looked like attacking someone else's device and stop. Fair enough, it can't tell my projector from anyone's. Each time it balked I handed the task to one of the Chinese open-weight models, which don't hesitate here. <a href="https://x.com/helixbase/status/2108306203571053020/photo/1">This seems to be a universal experience.</a></p>
<h2>Three wrong turns</h2>
<p>The shell wasn't root. The app it ran as couldn't touch admin commands or other apps' data. What it could do was start any exported activity on the device, including several the vendor apparently forgot to remove before shipping, so I pulled every preinstalled APK off the box and started reading.</p>
<p>One was a factory test app: the spinning teapot I remember from my computer graphics lectures, a wall of diagnostics, all in Chinese. I translated it, clicked everything, got nothing.</p>
<p>The vendor's settings app looked more interesting. It had a real ADB toggle I could flip. ADB was enabled now, but still wouldn't let me connect, because the approval screens live in SystemUI and aren't in this build. Another thing I flipped on, factory mode, as far as I could tell, did nothing: the box rebooted and looked identical.</p>
<p>There was also a longer detour trying to push a modified app through the device's own store. Its update channel is more trusting than it should be, which I'll write up once the vendor has fixed it. It didn't get me in.</p>
<p>At some point, a few more dead ends later, <code>getenforce</code> in the shell said permissive. SELinux had stopped denying anything, and I assumed that factory switch had done it. Not directly. Factory mode is a byte in the bootloader environment, and the init binary is patched to force permissive whenever it sees that byte; the menu only writes the byte and triggers a reboot. An ordinary app can only run my commands while SELinux is permissive, so when the box later came back enforcing I briefly lost my mind.</p>
<h2>Getting root</h2>
<p>The service that mattered is <code>system_control</code>, a vendor daemon Android builds from Amlogic ship: it starts as root, exposes <code>ISystemControl</code> over binder, and exists so the manufacturer's own apps can do what a normal app can't. One of its calls copies any file to any path as root. A pair read and write root-owned files, text only, four kilobytes at a time, and the write side only takes sysfs paths. Another sets a byte in the bootloader environment. Nothing gated any of it but SELinux, and mine was already permissive.</p>
<p>Each call stopped a step short. The copier can't set a mode, which is why the key I planted for adb landed <code>0600 root</code> and adbd, running as a normal user, couldn't read it. The readers are text and four kilobytes, so a whole log is out; the writer only follows sysfs, so a binary is out too.</p>
<p>Up to this point I only knew transaction numbers for methods some vendor app already called. The file copy let me pull the device's own copy of the library that talks to the service, and disassembling it gave the whole list: 364 methods in 1.0, 25 more in 1.1. One of those 25 is <code>systemCmd</code>. I handed it a string:</p>
<pre><code class="language-plaintext">id → uid=0(root) gid=1000(system) context=u:r:system_control:s0
</code></pre>
<p>Root. No adb, no patched boot image, just a vendor service doing exactly what it was built to do, from a Python add-on inside a media player.</p>
<p>Root first buys you the ability to see. The APK install failures that had been invisible for two days were one command away:</p>
<pre><code class="language-plaintext">INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES ("mismatched zeasn certificates")
</code></pre>
<p>There was the real gate: a second signature. The package manager wants a vendor APK Signing Block, an RSA signature over the APK made with Zeasn's key, and a self-signed app doesn't have one. The vendor signs every app in the store again, and even with root I couldn't touch the key it verifies against, or the code that runs the verification. I almost decided this was the end of the road.</p>
<p>It wasn't. With the verifier code decompiled, I read how the check runs, and it only runs when two system properties say so. One is a general partner-build escape hatch. The other skips the signature check <em>and</em> lifts the package manager's refusal to <code>pm install</code> on a user build at all. Two guards, one flag, and the flag has a wonderfully non-descriptive corporate name:</p>
<pre><code class="language-plaintext">setprop persist.sys.concert.tinsel 1
pm install my-self-signed-app.apk
  → by this brand, open function
  → Success
</code></pre>
<p>Any self-signed app, no vendor block, no signing key, no reflash, installed on a locked device. All I had to do was flip one property the manufacturer left in the build, probably for partners shipping a less locked-down variant of the device.</p>
<h2>Making it survive a reboot</h2>
<p>All of this lived in processes. The root channel ran inside Kodi, Kodi doesn't restart after a power cut, and a cold boot cleared factory mode, taking permissive with it. Something I have to walk over and restart by hand isn't much of a jailbreak.</p>
<p>Reflashing is out: a patched boot image won't boot against a locked, verifying bootloader. But the bootloader environment is just <code>name=value</code> pairs, and one value is a script it runs every boot. On a cold boot that script clears factory mode, which is why permissive kept vanishing. I edited it to set factory mode instead, one 0 into one 1, and swapped the Kodi dependency for a small app that reopens the root channel when Android starts.</p>
<p>Writing that script wrong is how you make a box that won't boot, so I read it back very carefully.</p>
<p>Then the test. Pull the plug, wait, plug it back in. It came up permissive, root listening on SSH, no laptop, no remote, nothing from me. I did it again to be sure.</p>
<p>After that, the cleanup I'd wanted on day one took ten minutes. Lighter launcher, preinstalled junk gone, about a gigabyte back. The panel shipped with a yellow cast, so I dropped the colour temperature until white was white. Then I pointed a game-streaming client at my desktop: it mirrors now at around eight milliseconds, a pretty nice place for a seventy-euro box to land, and the first time it has felt fast.</p>
<h2>Isn't this a security nightmare?</h2>
<p>Someone always asks, so: nobody can turn this chain against your projector from outside. It needs a shell and code execution on the device, which don't exist on a stock box; they're on mine because I put them there. It needs permissive mode, which comes from factory mode, which you can only switch on standing in front of the projector with the menu open. There is no network path to that. An attacker would need to be on your network, you'd need to have already rooted the thing yourself, and they'd need to physically flip factory mode. At that point, this chain is the least of your problems.</p>
<p>The one piece that does touch a stock projector is that store update channel. It's reachable by anyone on your network, no cooperation from you, and it's the part I reported to the vendor before publishing and am holding back until they fix it. Everything else here needs you, your consent, and your hands on your own device.</p>
<h2>Why bother</h2>
<p>It's a seventy-euro projector and I could have bought a Chromecast. True. But what stayed with me wasn't the root shell. It was the service underneath it: running as root, callable by any app on the box, undocumented, and nobody had mentioned it. A path I'm not allowed to use is one thing; a privileged one I'm not allowed to know about is a different one.</p>
<p>This is my device, on my network, built with a locked-down path nobody told me about and an override switch left in the off position. Right-to-repair usually gets argued over tractors and phones, but it's the same idea for any device in your home: you should be allowed to understand and change the thing you own.</p>
<p>There's an unhappy version of this where the manufacturer reads it, patches the switch, and everyone with one of these projectors is locked into the slow stock UI for good, fixing nothing real since none of it is a remote threat. I'd rather the other version: a few more people with the same box flip the same switch and get a fast projector out of a slow one.</p>
<p>If that's you, the tools are on <a href="https://github.com/alex-pichler/magcubic-whaleos-unlock">GitHub</a>.</p>
]]></content:encoded></item></channel></rss>