Android Hacking, Sandboxing, & Security Exploits

Crafting Custom SEAndroid Rules: How to Achieve Post-Exploitation Persistence on Android

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction to SEAndroid and Persistence Challenges

Security-Enhanced Android (SEAndroid), built upon SELinux, is a mandatory access control (MAC) system that significantly enhances the security posture of Android devices. It operates by enforcing granular permissions on processes, files, and resources, beyond traditional Unix discretionary access control (DAC). While SEAndroid effectively mitigates many common attack vectors, it also presents a formidable challenge for post-exploitation persistence. Once an attacker gains initial access, maintaining that access often requires circumventing SEAndroid policies that prevent unauthorized services, applications, or modified binaries from executing or accessing necessary resources.

This article delves into the intricacies of SEAndroid policy and provides an expert-level guide on crafting custom rules to achieve post-exploitation persistence. We will explore how to analyze existing policies, identify enforcement gaps, and generate new rules that allow your payload to operate undetected and robustly within the Android ecosystem.

Understanding SEAndroid Fundamentals

SEAndroid operates on the principle of Type Enforcement (TE). Every process, file, and IPC mechanism on the system is assigned a security context, represented by a "type." Policy rules then dictate which types can interact with other types, and in what manner (e.g., read, write, execute, bind). This granular control provides a powerful defense-in-depth mechanism.

Key SEAndroid Concepts:

  • Security Context: A label that identifies the SELinux type of an object (e.g., u:object_r:system_file:s0 for a system file, u:r:untrusted_app:s0 for an untrusted application process).
  • Types (Domains): Unique identifiers for subjects (processes) and objects (files, sockets, devices). A process’s type is its domain.
  • Policy Rules: Directives that specify allowed interactions. The most common is the allow rule: allow source_type target_type:class { permissions };.
  • AVC Denials: Access Vector Cache denials are logged whenever an operation is attempted that is explicitly forbidden by the policy. These denials are crucial for understanding and debugging policy issues.

When an attacker attempts to install a persistent backdoor or service, SEAndroid will likely block its execution, file access, or network communication, logging an AVC denial. Overcoming this requires understanding these denials and strategically modifying the policy.

The Anatomy of SEAndroid Policy Files

SEAndroid policy is defined in a collection of Security Policy Language (SPL) files, compiled into a binary sepolicy file. This file is typically located in the boot image or vendor partition (e.g., /sepolicy, /vendor/etc/selinux/precompiled_sepolicy). Key components include:

  • *.te (Type Enforcement) Files: These define types, attributes, and the core allow rules.
  • file_contexts: Maps file paths to their security contexts. This is critical for assigning the correct type to your persistent files.
  • service_contexts: Maps Android service names to their contexts.
  • property_contexts: Maps system properties to contexts.
  • seapp_contexts: Defines contexts for Android applications based on their package name and UID.

To achieve persistence, we must modify the sepolicy and potentially the context mapping files to grant our payload the necessary permissions and context.

Methodology for Custom SEAndroid Rule Crafting

Step 1: Identifying the Target and Its Requirements

First, define what your persistence mechanism will be. Will it be a new service, a modified existing binary, or a library injected into a running process? For this example, let’s assume we want to run a custom executable, /data/local/tmp/my_persistent_payload, as a standalone service.

Step 2: Initial Execution and AVC Denial Collection

Attempt to execute your payload and observe the SEAndroid AVC denials. This is typically done by pushing your payload to the device and running it:

adb push my_persistent_payload /data/local/tmp/my_persistent_payloadadb shell su -c "chmod 755 /data/local/tmp/my_persistent_payload"adb shell su -c "/data/local/tmp/my_persistent_payload"

Collect the AVC denials from the kernel log. On a rooted device:

adb shell su -c "dmesg | grep 'avc: '"

Or for persistent logging:

adb shell su -c "cat /sys/fs/selinux/audit/audit_log"

A typical denial might look like:

avc: denied { execute } for pid=1234 comm="my_payload" path="/data/local/tmp/my_persistent_payload" dev="dm-0" ino=5678 scontext=u:r:untrusted_app:s0 tcontext=u:object_r:app_data_file:s0 tclass=file permissive=0

This tells us the untrusted_app domain (scontext) was denied execute permission on a file typed as app_data_file (tcontext).

Step 3: Crafting New Policy Rules

Based on the AVC denials, we need to create rules. This involves:

  1. Defining a New Type/Domain (if necessary): For a dedicated persistent service, it’s best to create a new domain. Let’s call it my_persistence_domain.
  2. Defining New File Types: Assign a specific type to your payload’s executable and any data files it creates. For example, my_persistence_exec and my_persistence_data.
  3. Granting Necessary Permissions: Write allow rules for all operations denied.
  4. Allowing Domain Transition: If your payload is launched by an existing service (e.g., init or a compromised app), you’ll need a rule to allow that service to transition to your new domain.

Example Rule Snippets (within a new my_persistence.te file):

# Define our new domain and file typestype my_persistence_domain;type my_persistence_exec, file_type, exec_type;type my_persistence_data, file_type, data_file_type;domain_auto_trans(init, my_persistence_exec, my_persistence_domain); # Allow init to transition# Grant basic permissions for the domainallow my_persistence_domain self:capability { setuid setgid net_raw };allow my_persistence_domain my_persistence_exec:file { execute_no_trans read open getattr };allow my_persistence_domain my_persistence_data:file { create read write append unlink getattr setattr };# Allow network access (example)allow my_persistence_domain self:socket { create bind listen connect };allow my_persistence_domain node:tcp_socket { node_bind node_connect };allow my_persistence_domain port:tcp_socket { name_bind name_connect };# Allow loggingallow my_persistence_domain kmsg_device:chr_file { write };# Example: allow to read some system propertiesallow my_persistence_domain system_prop:property_service { get };

You would also need to update file_contexts to map your payload’s path to its new type:

/data/local/tmp/my_persistent_payload  u:object_r:my_persistence_exec:s0/data/local/tmp/my_persistence_data(/.*)? u:object_r:my_persistence_data:s0

Step 4: Compiling and Loading the Custom Policy

This is the most critical and complex step for persistence, as it requires modifying the device’s boot image or vendor partition.

  1. Extract the Current Policy: Obtain the current sepolicy file from the device (e.g., from /sepolicy or within the boot image/vendor partition).
  2. Decompile the Policy: Use the secilc tool (from AOSP source) to decompile the binary sepolicy into a CIL (Common Intermediate Language) file. While direct decompilation to *.te is not feasible, CIL is human-readable enough to integrate custom rules.
    secilc -P sepolicy -o sepolicy.cil
  3. Integrate Custom Rules: Insert your new my_persistence.te rules (converted to CIL format, or by creating a separate CIL file for them) into the main sepolicy.cil or include them as part of the policy compilation process. For simplicity, you can append your CIL-formatted rules to the existing policy CIL.
  4. Recompile the Policy: Compile the modified CIL policy back into a binary sepolicy file. This often involves using secilc with all original policy files and your additions. For a full AOSP build, you’d integrate your .te files into the appropriate AOSP paths and rebuild the boot image.
    secilc -M -P sepolicy.cil -o new_sepolicy
  5. Flash the Modified Policy: This is device-specific. Common methods include:
    • Modifying the Boot Image: Extract the sepolicy from the boot.img, replace it with your new_sepolicy, and repack/reflash the boot image.
    • Modifying the Vendor Partition: On devices using a separate vendor partition for SELinux policy, replace the precompiled_sepolicy there.
    • Using a Custom Recovery (e.g., TWRP): If the device has an unlocked bootloader and a custom recovery, you might be able to flash a zip containing the modified sepolicy directly to the relevant partition.

After flashing, reboot the device. Your custom payload should now execute without SEAndroid AVC denials, achieving persistence.

Conclusion

Crafting custom SEAndroid rules for post-exploitation persistence is a sophisticated technique that requires a deep understanding of Android’s security architecture. By meticulously analyzing AVC denials, defining new security contexts, and integrating tailored policy rules, attackers can circumvent SEAndroid’s robust protections. While the process of compiling and flashing a modified policy demands significant technical expertise and device-specific knowledge, mastering this method provides a powerful avenue for maintaining control over compromised Android systems. This exploration underscores the continuous cat-and-mouse game between security researchers, developers, and those seeking to exploit vulnerabilities in highly secured environments.

Android Mobile Specs & Compare Directory

Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!

Compare Devices Specs →
Google AdSense Inline Placement - Content Footer banner