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:

Meme: a cat in the middle of a circle of arrows, between the words "I wake up" and "there is a new security vulnerability"
My life in cyber, in a nutshell.

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.

In short

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:

From the README

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 of su, the command that grants administrator permissions, with the SUID bit, which makes it run as root whoever launches it; qw, an old su daemon always running in the background; and dbgserver, 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, with rw-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:

consolereal output, shortened
$ adb shell /system/bin/wifi root id
uid=0(root) ... context=u:r:shell:s0

It'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.

DIAGRAMmermaid
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 ok

The 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:

bashon the projector, boot_b partition
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 | sha256sum

The result after rebooting:

consolereal output, shortened
$ adb shell su -v
30.7:MAGISKSU
$ adb shell su -c id
uid=0(root) ... context=u:r:magisk:s0

This 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, qw and dbgserver are replaced by harmless scripts, without the SUID bit. Port 65528 no longer exists.
  • Manufacturer apps: 21 removed with a .replace file, 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.0 in 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:

bashfirewall.sh (excerpt)
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 rejected

The 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:

bash
adb shell su -c 'dumpsys netstats --full --uid --tag' > ns_full.txt

Reading 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:

DIAGRAMmermaid
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.

json100002.log (one event, anonymised)
{"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:

DIAGRAMmermaid
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 code

It'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.

DIAGRAMmermaid
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 unsure

So 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

Written by Giovanni Rapa · 01.11.2026#android#iot#forensics

Was it useful, or did I get something wrong?

Write to me: I read everything and fix the post if needed.

Reply by email

Comments

via GitHub Discussions · giscus

Comments live on GitHub Discussions. Loading them connects your browser to giscus.app and GitHub.

You need a GitHub account. Rather not? Reply by email.