HY300: what was my 80-euro projector sending to China?
Analysis of a cheap Android projector: factory backdoors, telemetry to China and root without unlocking the bootloader.
CONTENTS · 6 SECTIONS
My Android mini projector was sending servers in China its serial number, its MAC address, the list of apps I opened with times and durations, and an "I'm on" signal every five minutes. I only found out because it was lagging during my well-deserved rewatch of Better Call Saul, in 720p.
It's a story I've already told at work, and my colleagues talked me into writing it up. Part of the blame goes to my colleague Danilo, who keeps sending me security articles: after reading enough of them, when a device at home slows down my first thought is no longer "it must be old". In cyber, after all, this is my life:

The projector in question is one of many in the HY300 family, sold under a thousand different brands on Amazon and AliExpress: this one has Android 11, 1 GB of RAM and an Allwinner H713 processor. I only wanted to understand why it lagged so much. I ended up with root, the administrator permissions that manufacturers usually keep to themselves on Android, with none of the manufacturer's apps left, and with evidence of what it had been doing before.
The projector had three administrator access points left open at the factory, and a group of manufacturer apps telling Chinese servers what I was doing with it. I took control of the device without losing any data, shut everything down and reconstructed what had gone out: usage data, yes; the most dangerous malware reported on these models, no. More by luck than by merit, as we'll see.
Many firmware packages (the software installed at the factory) are called com.htc.*, but HTC, the Taiwanese company, has nothing to do with it. It's just the prefix chosen by whoever put the system together. Off to a great start.
The work went through four phases, with a single rule: every change had to be reversible, and the only risky operation, the root, could only come after a complete, verified backup. I had paid 80 euros for the projector after all, and going back to watching series in bed hunched over an iPad was never an option.
| Phase | Goal | What I touched |
|---|---|---|
| 1 · Reconnaissance | understand what's running, without touching anything | nothing: read-only adb |
| 2 · Root | get full control, without losing data | only the boot partition, after backing up 25 partitions |
| 3 · Cleanup | close the backdoors and remove the manufacturer's apps | a Magisk module, removable at any time |
| 4 · Investigation | find out what the projector had sent out | nothing: just reading counters and files |
A note: I wasn't the first to get my hands on these projectors, but I only found that out afterwards. Three weeks earlier a researcher had published on GitHub a debloat for the Magcubic HY300 Pro, a script that removes useless or harmful apps, opening it like this:
The 3 following packages are malware installed on Magcubic HY300 Pro.
One of the three is the very launcher, the home screen, that was running on mine. Phase by phase, let's see what was around it.
| Fact sheet | HY300 (my unit) |
|---|---|
| SoC | Allwinner H713 (sun50iw12p1), 4 cores |
| System | 32-bit Android 11 on a 64-bit kernel |
| Bootloader | declared "locked", but verifies nothing |
| SELinux | Permissive |
| Root | Magisk 30.7, without unlocking and without losing data |
| Outcome | usage data sent to China; no trace of the proxy malware |
Phase 1: reconnaissance
The goal of the first phase was simple: understand what's running on the projector without touching anything. On the Wi-Fi network, on port 5555, the projector exposes adb, Android's debugging tool that lets you control it from a computer: you just authorise the computer's key on the projector's screen and you have a shell, a command line on the device. From there, before touching anything, I looked around.
To give you an idea of what I found, picture a new house where the builder left three service doors open, took the lock off the vault and disconnected the alarm. Absurd, right? Here's what was there:
- three root access points left by the manufacturer:
/system/bin/wifi, a renamed copy ofsu, the command that grants administrator permissions, with the SUID bit, which makes it run as root whoever launches it;qw, an oldsudaemon always running in the background; anddbgserver, Allwinner's debug server, started as root and listening on port 65528, accepting commands from any device on the home network; - the entire flash memory (
/dev/block/mmcblk0), where the whole system lives, writable by any app, withrw-rw-rw-permissions; - SELinux in Permissive mode: SELinux is the last barrier between an app and the system, and in this mode it logs violations without blocking them.
A detail about the first backdoor, because details matter:
$ adb shell /system/bin/wifi root id
uid=0(root) ... context=u:r:shell:s0It's a "crippled" root: uid 0, the administrator user, but without capabilities (CapEff=0), the individual powers an administrator normally carries. On this device, though, that hardly matters. With SELinux permissive and the flash writable by anyone, you didn't even need it to do damage.
On top of all this ran a network of manufacturer apps:
| App | What it did |
|---|---|
| Launcher | ads and profiling (Zeasn, AdMob, DoubleClick, Baidu Analytics) |
| Telemetry | sending usage data to a server on Tencent Cloud |
| Store | downloading and installing apps on its own |
| Three OTA services | system updates over the Internet, over unencrypted HTTP |
com.hotack.writesn |
device identification, from the same family as the proxy malware reported on these projectors |
And the lag? At idle there were 186 MB of free RAM out of 960, 470 MB were already in swap, the (slow) reserve memory used when RAM runs out, and about 1,500 threads were running. The manufacturer had also made the Play Store and its own service apps "immortal": the system couldn't close them even when memory was running out.
With this picture in hand, the next step was taking control of the device. First, though, the backup: 25 partitions, the sections the memory is divided into, saved and verified one by one with SHA-256, a fingerprint that changes if even a single bit changes.
Phase 2: root without unlocking the bootloader
On a smartphone, at power-on the bootloader, the program that loads the system, verifies the signature of the boot partition with Android Verified Boot (AVB). If the bootloader is locked and the image has been modified, it refuses to boot it. So to install Magisk, the most widely used tool for rooting Android, you have to unlock it, which wipes all data, and then write the image over a USB cable in fastboot, a boot mode made precisely for rewriting the system.
Not on this projector. The system declares itself "locked", but the bootloader boots with androidboot.trustchain=false and doesn't pass the verification state to the kernel. It's a bouncer who asks for your ID and doesn't look at it: anyone gets in, even a modified image. And since the flash was writable by any app, the boot partition could be rewritten from inside Android, over Wi-Fi, without a cable.
flowchart LR
subgraph S["Smartphone"]
S1["Unlock<br/>wipes the data"] --> S2["Patch with Magisk"] --> S3["Flash over USB<br/>in fastboot"]
end
subgraph P["This projector"]
P1["No unlock<br/>data intact"] --> P2["Patch on the<br/>projector itself"] --> P3["Write with dd<br/>over Wi-Fi"]
end
classDef risk stroke:#e0607e,stroke-width:2px
classDef ok stroke:#3fa66b,stroke-width:2px
class S1 risk
class P1 okThe patch was done by Magisk's official script directly on the projector, with the 32-bit binaries. Then the image went into place with dd, which copies memory block by block, and before rebooting I read it back to compare its hash:
export BOOTMODE=true KEEPVERITY=true KEEPFORCEENCRYPT=true
./busybox sh boot_patch.sh boot.img # produces new-boot.img
# boot_b starts at 64 KB block 2,632 and is 1,024 blocks long
dd if=new-boot.img of=/dev/block/mmcblk0 bs=65536 seek=2632 count=1024 conv=notrunc,fsync
dd if=/dev/block/mmcblk0 bs=65536 skip=2632 count=1024 | sha256sumThe result after rebooting:
$ adb shell su -v
30.7:MAGISKSU
$ adb shell su -c id
uid=0(root) ... context=u:r:magisk:s0This time it's real root, with every capability.
There's a flip side, though: the same hole that let me get root let any app rewrite the system. That's why, as soon as I had root, I closed it as fast as I could, also because I still didn't know what the projector was really up to. All this at midnight (anyone who knows me knows that's an unthinkable hour for me: I'm veeery old, I know).
Phase 3: cleanup with the hy300-clean module
From here on I called in my friend Claudio (Claude on his ID card, the only other lunatic who'll reverse binaries at midnight without complaining) for an in-depth analysis. The whole cleanup lives in a Magisk module, hy300-clean. At every boot it overlays its files on the system ones without modifying the system partition: removing the module brings everything back exactly to the original.
- Backdoors:
wifi,qwanddbgserverare replaced by harmless scripts, without the SUID bit. Port 65528 no longer exists. - Manufacturer apps: 21 removed with a
.replacefile, which makes their folder look empty. As far as Android is concerned, those apps no longer exist. - Servers: the manufacturer's domains point to
0.0.0.0in the hosts file, the system's built-in address book: looking them up leads nowhere. - Per-app firewall: the few manufacturer apps the hardware needs can only talk to the home network.
The firewall uses a Linux feature you rarely see on Android: the owner match, which filters traffic based on the user that generates it. On Android every app runs as a user of its own:
iptables -A hy300 -m owner --uid-owner <uid> -d 192.168.0.0/16 -j RETURN # home network ok
iptables -A hy300 -m owner --uid-owner <uid> -j REJECT # everything else rejectedThe result shows right away:
| Measure | Before | After |
|---|---|---|
| Available RAM at idle | 186 MB | about 515 MB |
| Swap used at idle | 470 MB | about 185 MB |
| Ports open as root on the home network | 1 | 0 |
| SUID files in the system | 1 | 0 |
An unexpected bonus concerns YouTube. The chip decodes H.264 and HEVC in hardware, handling them on its own without loading the processor, but not VP9 and AV1, the formats YouTube prefers to send. By setting SmartTube, an alternative YouTube app, to 720p H.264, the projector's native resolution, dropped frames went from "constant" to zero and CPU usage from 67% to 36%.
With the present sorted out, the past remained: what had the projector been doing before I came along?
Phase 4: the investigation
With root I could finally read what Android keeps to itself: each app's traffic counters and the system apps' files. Two leads, and the first one gave me quite a scare.
The mystery of the 18 GB
Android keeps each app's traffic counters for about three and a half months. The default summary only covers the last boot; for the history you need the right options:
adb shell su -c 'dumpsys netstats --full --uid --tag' > ns_full.txtReading them, the manufacturer's AirPlay/DLNA app, the one that receives videos from your phone, had received 9.1 GB and sent 8.8 GB. A receiver should mostly receive. Outgoing traffic matching incoming traffic is the typical signature of a residential proxy: a device that secretly lends your connection to strangers, who browse with your IP. And that's exactly what the malware reported on these projectors does. My first thought was: well, that's it. Half of China has been browsing the Internet with my IP: in two or three hours the GIS, Italy's special forces, will storm my flat. During the analysis I was living with two designers most of the time: between them and a Red Team Penetration Tester (according to LinkedIn, at least), guess who they'd haul away, as they'd say in Rome.
The numbers, however, said otherwise:
- the pace was a machine's: on average about 85 MB a day in and as much out, with the same pattern by day and by night. A person doesn't use AirPlay like that, a timer does;
- the packets were small: 390–400 bytes on average, the size of a UPnP/SSDP announcement (more on that in a moment). A proxy relaying browsing moves packets close to 1,400 bytes;
- inside the VPN the numbers matched to the byte;
- Claudio seemed calm: not to be underestimated.
Android traffic counters, AirPlay/DLNA app, 8 July – 22 October 2026
| Network | Received | Sent |
|---|---|---|
| VPN | 211,158,684 bytes (529,884 packets) | 211,158,684 bytes (529,884 packets) |
| Wi-Fi | 8,922,228,314 bytes | 8,552,099,200 bytes |
To understand what was going on, imagine shouting in an empty room and counting your own echo as "messages received". The DLNA app sent about five multicast announcements per second, day and night: "I'm here" messages sent to every device on the network at once, the same ones your phone uses to find the TV for streaming. And it got back a copy of its own packets:
sequenceDiagram
participant A as DLNA app
participant M as SSDP multicast<br/>239.255.255.250
participant D as Other home devices
loop about 5 times per second
A->>M: announcement (~400 bytes)
M-->>A: echo of its own announcement
end
Note over A,M: in the VPN there's only the echo:<br/>received = sent
D->>M: their announcements
M-->>A: these arrive too
Note over A,D: on Wi-Fi, incoming exceeds outgoing by 4–6%There are no other devices in the VPN, so only the echo came back: identical numbers. On Wi-Fi the announcements from the rest of the house were added, and incoming exceeded outgoing by a few percentage points.
So no data went out: just a constant waste of CPU and Wi-Fi. Had I stopped at the first number I'd have written a wrong post, and a much more clickable one than this.
What it was really sending
The real evidence was in a forgotten file: the telemetry service's send queue, in readable JSON, with the last events before I disabled it.
{"channel":"hy260_...",
"content":"{\"endTime\":1792702128475,\"pkgName\":\"com.android.tv.settings\",\"runTime\":6382,\"startTime\":1792702134857}",
"deviceId":"<MAC address>","deviceSn":"<serial number>",
"eventCode":100002,"language":"it_IT","model":"GM200", ...}Each event tied one of two pieces of information to the device's identity:
- event 100002: every app opened, with start, end and duration. The app's name, not the title being watched;
- event 100001: an "I'm on" exactly every 300 seconds.
A little treat for those who look at the numbers: endTime comes before startTime, and the 6382 milliseconds of runTime are their difference the wrong way round. Even the telemetry is badly written.
The queue itself shows that those events went out. Every "I'm on" also records how long the projector had been on: 13 days. At one every five minutes, more than 3,700 should have piled up; the file held three. The queue kept emptying: a sign that the events were being sent and deleted.
The destination was a server on Tencent Cloud, over unencrypted HTTP: anyone along the way could read them. Shared storage also held a permanent Baidu identifier, hidden in backups/.SystemConfig/.cuid2 precisely to survive reinstalling the apps.
One question remains: how do you remotely control a device behind a home router, which blocks incoming connections? Simple, you don't need to. It's the projector that periodically asks "any instructions for me?", and the answer comes back like any other web page:
sequenceDiagram
participant P as Projector
participant R as Home router
participant S as Manufacturer's server
S-xR: incoming connection
Note over R: blocked
P->>R: "any instructions for me?"
R->>S: outgoing request (allowed)
S-->>P: update, app or codeIt's the basic principle of a reverse shell, an attacker classic: if you can't reach the resource yourself, make it contact you. And the channel worked: on 28 April 2025 my projector installed a full system update, and the store and launcher were also updated compared with the factory version.
And the VPN I had on the projector? It routes the traffic of every app, system ones included. It hides the data from the provider and changes the IP the servers see, but it doesn't stop an app from sending it: it's a pipe, not a filter.
How bad was it?
On my unit, as far as one can tell, it could have gone much worse. What went out was usage data tied to the device's identity: which apps I opened, when and for how long, and when the projector was on. Annoying, but nothing more.
The rest is clean:
writesn, the piece from the proxy malware family, never used the network in the three and a half months recorded;silentsdk, the actual malware, wasn't on mine;- none of the apps installed today comes from the manufacturer, and there are no added CA certificates (they would allow reading encrypted traffic), device administrators or suspicious accessibility services, the permissions malware likes best for controlling a device.
But the point is a different one, and it's what changed my mind while I was studying the sources for this post. On other units of the same family, silentsdk presented itself as an analytics SDK, a component for usage statistics, and was in fact a trojan with system privileges. Above all, the malicious code didn't stay on the device: the command and control (C2) server delivered it when needed, and could pull it back as soon as someone started analysing it. Another report describes the same architecture with two pieces: the SilentSDK RAT, a remote-control program, and a dropper called StoreOS, whose only job is to download and install more.
On my projector there was an app called com.htc.storeos, a store that could install apps on its own. I didn't compare my APK with the one in the report, so I can't say they're the same code. But the picture is clear: I had all the infrastructure needed to receive a payload, the actual malicious code, and as far as I can tell only the payload was missing.
flowchart LR
C2["C2 server<br/>delivers the payload"] -.-> ST["Store<br/>installs silently"]
C2 -.-> LA["Launcher<br/>downloads and runs code"]
C2 -.-> OT["3 OTA services<br/>updates over HTTP"]
ST -.-> SS["silentsdk<br/>never arrived"]
WS["writesn<br/>present, never online"] -.- SS
classDef present stroke:#e0607e,stroke-width:2px
classDef absent stroke:#3fa66b,stroke-width:2px,stroke-dasharray:5 4
classDef unsure stroke:#e0a030,stroke-width:2px,stroke-dasharray:5 4
class ST,LA,OT,WS present
class SS absent
class C2 unsureSo yes, I was lucky. Whether the payload never arrived, or arrived and left without a trace, was up to the server, not me.
And there's an honest limit. The manufacturer's apps ran as the system user, whose traffic blends in with Android's, and the counters only cover the last three and a half months, while the projector had been in use long before that. "I found no evidence", unfortunately, doesn't mean "nothing happened". Still, at least for the last three and a half months, no communist has heard me snore. How boring, though.
Conclusions
This projector didn't have a single flaw, but an entire system built without any notion of a boundary: root backdoors left in at the factory, a flash writable by anyone, SELinux permissive, a bootloader that says it's locked and verifies nothing. On top of that, a network of apps ready to tell distant servers what I was doing, and to receive anything from those servers.
What I'm taking home, and what I'd recommend to anyone with a device like this:
- a separate network: projectors, TV boxes and smart plugs belong on the guest network, not next to your work computer;
- "locked" doesn't mean secure, and a VPN isn't a filter: it changes the route the data takes, not which data leaves;
- account hygiene: if the device ever had your Google account, remove it from "Your devices" and sign in again;
- before buying: be wary of Android devices without a recognisable brand, the ones sold identical under a thousand different names: nobody answers for the firmware and nobody updates it. If you already have one, use it as a screen: no Wi-Fi, and a streaming stick from a manufacturer you know in the HDMI port.
They say that if a product is free, you are the product. I paid for this projector, and I was the product anyway.
I hope you enjoyed it. Below you'll find the indicators to check your own devices, and more references.
Indicators
For anyone who wants to check their own HY300 or their network. The hy300-clean module is built around my unit; for a ready-made debloat there's micha102's in the sources. IP addresses can change over time.
| Indicator | What it is |
|---|---|
com.htc.eventuploadservice |
telemetry (events 100001 and 100002) |
com.htc.storeos |
store that installs apps on its own |
com.hotack.writesn |
device identification, proxy malware family |
/system/bin/wifi, /system/bin/qw, /vendor/bin/dbgserver |
factory root access points |
| port 65528 | dbgserver, root from the home network |
event-api, pb-api and store-api.aodintech.com |
event and store APIs: 119.28.17.161, Tencent Cloud (Hong Kong) |
ota.triplesai.com, otaapi.triplesai.com |
updates: 111.230.36.129, Tencent Cloud (China) |
*.zeasn.tv |
launcher and ads |
hm.baidu.com, hmma.baidu.com, mtj.baidu.com |
Baidu Analytics |
| 103.102.202.118, 152.32.240.141, 3.12.169.56, 40.160.68.219 | other manufacturer servers |
backups/.SystemConfig/.cuid2 |
permanent Baidu identifier |
Sources
- micha102/hy300pro-debloat on GitHub: removing the Hotack Silent SDK proxy from a Magcubic HY300 Pro
- TuttoAndroid: the low-cost projector and the residential proxy (in Italian), based on the analysis published by DDay.it (in Italian)
- ThreatCluster: factory-installed malware in Android projectors, a summary of a report on Reddit (SilentSDK and StoreOS)
- lolmam/HY300-H713-Research on GitHub: firmware dump and root of an HY300 Pro+
- shift/sun50iw12p1-research on GitHub: research on the H713 and porting Linux to the HY300
- Magisk on GitHub (version 30.7)
Comments
via GitHub Discussions · giscusComments live on GitHub Discussions. Loading them connects your browser to giscus.app and GitHub.