This article is only availble in english.
> Disclosure notice: Throughout this article, all vendor-specific identifiers (vendor name, binary names, service names, package names) have been replaced with generic placeholders. The vendor has been anonymized as [VENDOR], and affected names follow the convention [VENDOR]tvservice, com.[VENDOR].REDACTED, etc.
I decided to take a closer look at my Android TV after noticing it was unusually slow. Beyond the performance issue, the device came with a significant amount of pre-installed vendor software that could not be removed without root access. This sparked an interest in the security of the vendor layer running on the device.
This article documents the investigation of vendor-specific native services and their IPC interfaces. The goal is to explain what Android Binders are, how vendors extend them, and what the attack surface looks like from an offensive perspective.
Initial reconnaissance
The first step when assessing an Android IoT device is to enumerate processes running as root. Vendor binaries are a particularly interesting target: they often implement complex functionality with less security than the Android framework itself, and they run with the highest privilege level on the system.
Several non-standard vendor binaries are running as root. The main target here is [VENDOR]tvservice, a 25 MB stripped ARM ELF binary.
Opening the binary in Ghidra and searching for interesting imports, system immediately stands out.

system is used in multiple places, but one occurrence is particularly interesting:
This handler reads a command string directly from the caller-supplied Parcel and passes it to tos_system_execute_cmd, which calls system() (a direct OS shell execution). Since [VENDOR]tvservice runs as root, any caller that reaches this code path with an arbitrary command achieves root command execution. The only protection in place is the tos_system_verify check at the top. Understanding what that means requires some background on Android IPC.
Background: Android IPC
System services
Android separates application code from privileged system functionality through system services : background processes that expose interfaces to clients. Unlike Android application services (declared in a manifest), system services run outside the application lifecycle and are managed by the platform itself.
Examples include WindowManagerService, ActivityManagerService, or hardware abstraction layers for audio and sensors. On vendor-customized devices, additional proprietary system services are layered on top to expose device-specific functionality.
Binder: the kernel IPC driver
All processes on Android run in isolated sandboxes. Direct memory sharing between processes is not allowed. The Binder 1 is the kernel-level IPC mechanism that bridges this isolation. It is exposed as a character device, /dev/binder, which every participating process opens and mmap()'s.
When a client sends a call to a service, the kernel driver copies the data from the client's address space directly into the service's mapped region, without an intermediate copy through a kernel buffer.

Android ships with three standard binder devices:

On the device under analysis, a fourth device is present:
/dev/sbinder is not part of the AOSP standard. We will come back to it.
ServiceManager
Since multiple services can coexist on the same binder device, there needs to be a name registry. The ServiceManager 2 fulfills this role: it runs at handle 0 (the reserved context manager handle) and maintains a mapping between service names and their binder handles. Services register themselves at startup; clients query by name to obtain a handle before sending calls.
The object model
The Binder framework exposes three core abstractions 3 6 :
- IBinder: The common interface implemented by all binder objects.
- BBinder: The server-side base class. Services subclass it and override onTransact() to handle incoming calls.
- BpBinder (Binder Proxy): The client-side stub. It holds a remote handle and forwards calls through the kernel driver.
When a client calls a method on a remote service, it invokes transact() on a BpBinder. The kernel delivers a BR_TRANSACTION event to the server thread, which dispatches it to onTransact() on the corresponding BBinder.
Parcels
Data is exchanged through Parcel objects 4 : byte buffers with typed read/write accessors (readInt32(), readString16(), readBlob(), etc.)
The first field written into any Parcel is the interface token, a string that identifies the target interface. The server calls enforceInterface() on receipt to verify the token matches, preventing a client from sending a call meant for interface A to a service implementing interface B.
Going back to the Ghidra output above: onTransact() is the server-side handler. param_2 is the transaction code (which method is being called), param_3 is the input Parcel (caller-supplied data), and param_4 is the reply Parcel. param_1 is the implicit this pointer, typed as uint by Ghidra since the class type was not resolved.
The vendor sbinder ecosystem
Going back to the binary, [VENDOR]tvservice does not register its services on the standard /dev/binder. Instead, it opens a completely separate device: /dev/sbinder.
This function is used to bootstrap a full ServiceManager loop embedded inside the binary itself:
[VENDOR]tvservice is both a service host and a ServiceManager for /dev/sbinder. It establishes a fully parallel IPC universe that is completely invisible to the standard service list command, which only talk to /dev/binder.
Registered services
The function proxy_add_service() registers services on sbinder at startup, driven by a bitmask:
Among others, remote_property_module is registered (bit 5 of 0x126 is set). sbinder_system_service, the handler containing the system() call is also registered, separately from this function.
SELinux and sbinder access
The device file has world-readable/writable permissions but carries a SELinux label:
Inspection of the firmware's SELinux policy (vendor/etc/selinux/vendor_sepolicy.cil) reveals which domains can access it:
Notably, both untrusted_app (any third-party application) and shell (ADB) appear to be allowed to open and map the device. In practice, whether this is sufficient to communicate with the services depends on a second security layer.
The security layer: tos_system_verify
Allowing unprivileged processes to open /dev/sbinder does not directly expose the methods offered by the services registered on it. Every onTransact() handler in [VENDOR]tvservice begins with an authorization check:
tos_system_verify maintains an in-memory whitelist of authorized PIDs. The calling PID is obtained through IPCThreadState::getCallingPid(), which reads the value set by the kernel at the time of the transaction so it cannot be forged by the caller. If the PID is not on the whitelist, the call is rejected before any service logic runs. Any process whose PID is absent from this list will receive PERMISSION_DENIED regardless of the method it is trying to invoke.
How does a process get authorized?
The whitelist is populated through a dedicated bootstrap service: remote_property_module in the same binary. Reversing its onTransact() handler recovers the full set of transaction codes it implements:
The one that matters here is TRANSACTION_tos_system_authorized (code 6), whose sole purpose is to add a PID to the whitelist. The call requires a signed authorization key:
The authorized_key field follows the format:
This key is verified by fpi_system_security_authorized, a function within [VENDOR]tvservice that is invoked by tos_system_authorized when processing this transaction. It performs the following steps:
- Base64-decode the signature
- RSA-decrypt it using a public key embedded in the binary
- Verify that the SHA-1 of the cmdline field matches the decrypted hash
- Verify that /proc/<pid>/cmdline matches the declared cmdline
- On success, add the PID to the licensed app list
To authorize any process, a valid RSA signature from the vendor's private key is required.
The bypass: method codes 1–7 skip verification
A closer look at the onTransact() header in remote_property_module reveals an unusual condition:
That condition lets a small set of low transaction codes through without calling tos_system_verify. Working it out, the codes that skip verification are 2, 3, 6, and 7. Every other code is verified:

This is deliberate. The authorization method (code 6) has to be reachable by a process that isn't trusted yet otherwise nothing could ever be added to the whitelist. So the developers carved this bootstrap range out of the check. The security was meant to come from the RSA signature, not from the access-control check.
PID spoofing vulnerability
Inside the handler for code 6, the PID to be authorized is read from the client-supplied Parcel:
The PID is not taken from IPCThreadState::getCallingPid(). It is an arbitrary integer supplied by the caller. This means an attacker can request authorization for any PID of their choice, for instance, the PID of [VENDOR]tvservice itselfwithout needing to run code in that process.
The exploit flow would be:
- Open /dev/sbinder from ADB
- Send method code 6 to remote_property_module
- Supply any PID in the Parcel
- If the signature check passes, that PID is whitelisted for all privileged operations
Step 4 is where the attack stalls.
The RSA wall
fpi_system_security_authorized validates the authorization key against a 1024-bit RSA public key embedded in [VENDOR]tvservice:
The debug strings in the binary confirm the verification steps:
A search across the entire firmware for keys matching this public key's modulus, and for any pre-signed authorization tokens, returned no results. The private key is held on the vendor's build infrastructure. No application in the firmware embeds a hardcoded signed token.
Even with the PID spoofing primitive and the bypass on method code 6, generating a valid signature requires the private key.
application_manager: a different approach
While [VENDOR]tvservice enforces tos_system_verify on all its services, the application_manager binary does not implement this check at all.
Inspection of application_manager's onTransact() in Ghidra confirms: there is no call to tos_system_verify. The service accepts calls from any process without any authorization check.
The service descriptor is remote_application_manager_sbinder. Its interface, derived from libtaf_client.so (the vendor's Application Framework client library), exposes the following operations among others:

Method 34 - InstallApp is the most significant. The installation path leads to AppRunner::StartApp:
The binary at the caller-supplied path is executed via fork + execve. Since application_manager runs as root, the spawned process inherits root privileges. Any caller that can reach this method can execute arbitrary binaries as root, with no RSA signature required.
The Parcel format for InstallApp (method code 34) is:
I wrote a native proof-of-concept that implements the raw sbinder protocol to send this transaction:
Result:
/dev/sbinder opened successfully. mmap() failed. SELinux intervened.
SELinux: the real gatekeeper
Despite the SELinux policy granting shell_30_0 the map permission on sbinder_device, the kernel AVC log shows a denial:
The ADB shell process runs in the u:r:shell:s0 domain, not u:r:shell_30_0:s0. These are distinct SELinux types, and shell (the context assigned to processes spawned via ADB) does not have map permission on sbinder_device.
Without mmap, the Binder protocol cannot function: the kernel driver requires a memory-mapped receive buffer to deliver reply data to the caller. The exploitation attempt is blocked before it can even resolve the service handle.
The situation across all binder devices:

What would have worked
The following summarizes each attempted attack path and the control that blocked it:
Path 1 : Direct sbinder access from ADB:
- open /dev/sbinder ✅
- mmap /dev/sbinder ❌ SELinux: shell:s0 ≠ shell_30_0
Path 2 : Reach sbinder_system_service as an authorized process:
- tos_system_verify passes ✅ (if PID is whitelisted)
- tos_system_execute_cmd → system() with caller-controlled command ✅
- Getting authorized requires RSA private key ❌
Path 3 : Reach application_manager (no tos_system_verify):
- open /dev/sbinder ✅
- mmap /dev/sbinder ❌ SELinux
Path 4 : PID spoofing via remote_property_module:
- Send method code 6 (bypasses tos_system_verify) ✅
- Spoof any PID in the Parcel ✅
- Requires valid RSA key in authorized_key field ❌
Identified vulnerabilities:
- PID spoofing in remote_property_module (method code 6): the PID to whitelist is read from caller-controlled data rather than from the kernel.
- application_manager has no tos_system_verify: any process able to communicate via sbinder can install an application that will be executed as root.
- sbinder_system_service exposes arbitrary command execution: method code 0x18 passes a caller-controlled Parcel blob directly to system() as root. Any process that clears tos_system_verify can achieve root RCE by supplying an arbitrary command in the Parcel.
The defense-in-depth combination, SELinux blocking the mmap at the kernel level, and RSA signatures gating the authorization path, prevented full exploitation from the ADB shell context.
Conclusion
This investigation illustrates how vendor customizations can introduce complexity into the Android security model. The vendor here built a fully parallel IPC infrastructure on /dev/sbinder with its own ServiceManager, its own service registry, and its own authorization scheme.
Two design weaknesses stand out. First, using an attacker-controlled value as a security-sensitive identifier (the PID in the authorization call) violates a basic principle: security decisions must rely on kernel-verified data, not caller-supplied data. Second, exposing a service that executes arbitrary shell commands as root (application_manager) without any authorization check creates a critical issue that is one SELinux policy gap away from full compromise.
References
[1] Android Binder IPC - https://source.android.com/docs/core/architecture/hidl/binder-ipc
[2] Android ServiceManager - https://cs.android.com/android/platform/superproject/+/main:frameworks/native/cmds/servicemanager/
[3] Binder object model - https://cs.android.com/android/platform/superproject/+/main:frameworks/native/libs/binder/
[4] Android Parcel - https://developer.android.com/reference/android/os/Parcel
[5] Binder transactions in the Linux kernel - https://www.synacktiv.com/en/publications/binder-transactions-in-the-bowels-of-the-linux-kernel
[6] Binder Architecture and Core Components - https://medium.com/swlh/binder-architecture-and-core-components-38089933bba
Further reading
- Fuzzing Samsung system services over Binder - Thalium, 2023
- Android Binder: from basics to exploitation - Duo Security
- Android IPC mechanisms overview - Android documentation





