How apps spot an Android emulator (part 2): sensors, telephony, and the GPU
Part 1 covered the props layer and the kernel tells. Those are the cheap checks: a file you can edit, and a boot command line you can't. This part is about the expensive ones. The hardware and behavior layer, where the tells stop being strings and become things the VM simply does not have.
I measured everything below on a stock emulator this month, the same way I measured part 1. The values are real, from adb shell, not from memory.
Sensors: the vendor string is the giveaway
Android exposes its sensors through a HAL, and every sensor carries a vendor string. On a stock emulator:
$ adb shell dumpsys sensorservice | grep -E "Accelerometer|Gyroscope|Proximity|Light"
0x00000000) Goldfish 3-axis Accelerometer | The Android Open Source Project | ver: 1 | ...
0x00000001) Goldfish 3-axis Gyroscope | The Android Open Source Project | ver: 1 | ...
0x00000005) Goldfish Proximity sensor | The Android Open Source Project | ver: 1 | ...
0x00000006) Goldfish Light sensor | The Android Open Source Project | ver: 1 | ...
Two tells in one line. The sensor names all start with "Goldfish", the codename of the emulator's virtual platform. And the vendor on every sensor is "The Android Open Source Project".
A real phone does not look like this. Its accelerometer comes from Bosch or STMicroelectronics, its gyroscope from InvenSense, its proximity sensor from AMS. The vendor field names the silicon. Nobody ships a phone whose sensors are all "The Android Open Source Project", because that vendor string means "synthesized in software, no chip behind it".
The list is also thinner than a real device's. The emulator exposes the standard set: accelerometer, gyroscope, magnetometer, light, proximity, pressure, temperature, humidity. It can even stream fake events into them. But the characteristics of the ones it does expose look wrong to an app that has seen real hardware.
Telephony: the SIM says LOADED, the RIL says emulator
The emulator goes to real effort here. It simulates a full telephony stack:
$ adb shell getprop gsm.sim.state
LOADED
$ adb shell getprop gsm.operator.alpha
T-Mobile
$ adb shell getprop gsm.operator.numeric
310260
It has a SIM in state LOADED, an operator name, a network code, even an IMEI. Any app that checks "is there a SIM and a network" gets a plausible answer.
Two more props are the ones that matter:
$ adb shell getprop gsm.version.ril-impl
android reference-ril 1.0
$ adb shell getprop gsm.version.baseband
1.0.0.0
The RIL is the radio interface layer, the software that talks to the actual modem. On a real phone that string names the vendor's RIL binary: Qualcomm, MediaTek, Samsung, whatever the SoC is. The emulator reports android reference-ril 1.0, which is the reference implementation that ships with AOSP for bring-up and emulators. It is the RIL you get when there is no modem.
The baseband version is the same story. A real phone reports a modem firmware build like MPSS.DE.2.0.c3-00122-M8996FAAAANAZM-1. The emulator reports 1.0.0.0, a placeholder nobody ships.
So the telephony tell is not "no SIM". It is a perfectly ordinary SIM attached to a reference RIL and a placeholder baseband. The emulator fakes the part apps check casually, and leaves the part that requires real silicon with a string that gives it away.
GPU: the renderer string and the ANDROID_EMU extensions
This is the strongest one. The graphics stack advertises itself in more than one place.
$ adb shell getprop ro.hardware.egl
emulation
The EGL implementation property is literally the string "emulation". No real device says that. A real phone reports adreno, mali, powervr, or similar.
The OpenGL renderer string is worse:
$ adb shell dumpsys SurfaceFlinger | grep GLES
GLES: Google (Google Inc. (Google)), Android Emulator OpenGL ES Translator (ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (LLVM 10.0.0) ...
"Android Emulator OpenGL ES Translator". "SwiftShader Device". SwiftShader is Google's software renderer, a CPU implementation of Vulkan and GL. A real phone renders with a hardware GPU and reports something like "Adreno (TM) 730" or "Mali-G78". The emulator reports a software translator, because that is what it is.
Then there are the extensions. OpenGL lets an app query the list of supported extensions with glGetString(GL_EXTENSIONS). A stock emulator returns, alongside the standard ones, a family that only the Android emulator emits:
ANDROID_EMU_checksum_helper_v1
ANDROID_EMU_native_sync_v2
ANDROID_EMU_dma_v1
ANDROID_EMU_direct_mem
ANDROID_EMU_host_composition_v1
ANDROID_EMU_host_composition_v2
ANDROID_EMU_vulkan
ANDROID_EMU_YUV_Cache
...
There are more than twenty of them, all prefixed ANDROID_EMU_. They exist to let the guest and the host coordinate rendering through QEMU, and they are not present on any physical device, because no physical device runs on QEMU. A single glGetString call, a substring search for "ANDROID_EMU_", and the app knows.
This is the hardest one to fake, for the same reason as the kernel tells in part 1. The renderer string and the extension list come from the graphics driver, and the driver is part of the guest image. Changing them means shipping a different driver, not editing a property.
Why this layer is different
The props from part 1 are strings in a file. The kernel tells are strings from the boot command line. The hardware tells are behavior. A sensor HAL, a radio interface, a graphics driver. Each one is a piece of software that exists to talk to silicon the VM does not have, and each one reports its synthetic origin in its own way.
That is also why faking them is a different category of work. You cannot patch a vendor string and call it done, because the app reading that string is also asking the sensor for data, and the shape of that data has to match a real chip's. This is reimplementing a driver or a HAL, not editing a config file.
Across both parts the ladder is: props (editable), kernel (needs a custom ROM), hardware (needs a reimplemented driver). An app that checks all three is drawing the real line, and the line is not "is it an emulator", it is "how much work went into hiding it".
I have been measuring all of this while building enmulator, an open source Android emulator manager in Tauri 2 + Rust.
Educational security research. No working bypass, keys, or implementation details published.
If you build device detection, or you run automation that keeps getting flagged, this is the kind of problem I work on. DM me on X or reach me at enginozdal.com.
Top comments (0)