Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@
- [Android Anti Instrumentation And Ssl Pinning Bypass](mobile-pentesting/android-app-pentesting/android-anti-instrumentation-and-ssl-pinning-bypass.md)
- [Android Application Level Virtualization](mobile-pentesting/android-app-pentesting/android-application-level-virtualization.md)
- [Android Applications Basics](mobile-pentesting/android-app-pentesting/android-applications-basics.md)
- [Android SELinux Policy Analysis](mobile-pentesting/android-app-pentesting/android-selinux-policy-analysis.md)
- [Android Enterprise Work Profile Bypass](mobile-pentesting/android-app-pentesting/android-enterprise-work-profile-bypass.md)
- [Android Hce Nfc Emv Relay Attacks](mobile-pentesting/android-app-pentesting/android-hce-nfc-emv-relay-attacks.md)
- [Android Physical Attacks](mobile-pentesting/android-app-pentesting/android-physical-attacks.md)
Expand Down
1 change: 1 addition & 0 deletions src/mobile-pentesting/android-app-pentesting/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Sometimes it is useful to **modify application code** to access **hidden informa

## Other interesting tricks

- [Android SELinux live-policy analysis and attack-path mapping](android-selinux-policy-analysis.md)
- [Spoofing your location in Play Store](spoofing-your-location-in-play-store.md)
- [Play Integrity attestation spoofing (SafetyNet replacement)](play-integrity-attestation-bypass.md)<sup>[[1]](#references)</sup>
- [Android app-level virtualization / app cloning abuse & detection](android-application-level-virtualization.md)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# Android SELinux Policy Analysis

{{#include ../../banners/hacktricks-training.md}}

Android 8+ builds SELinux from independently maintained platform, `system_ext`, product, vendor and ODM components. `init` either validates and loads a precompiled policy or compiles the CIL inputs before loading one binary policy into the kernel. Consequently, `/sys/fs/selinux/policy` is the authoritative target for access-path analysis; reviewing only AOSP `.te` files or one partition's CIL can miss compatibility mappings, OEM rules and runtime injection.<sup>[[1]](#references)[[8]](#references)</sup>

## Treble policy layout

The most useful on-device artifacts are distributed across these locations.<sup>[[1]](#references)[[2]](#references)</sup>

```text
/system/etc/selinux/ # platform policy, contexts, mappings, bug_map
/system_ext/etc/selinux/ # system_ext policy and contexts
/product/etc/selinux/ # product policy and contexts
/vendor/etc/selinux/ # OEM policy, contexts and precompiled policy
/odm/etc/selinux/ # device-specific policy and contexts
/sys/fs/selinux/policy # live merged binary policy
```

Platform **public** types form the API that vendor policy may reference; platform-private types are implementation details. Compatibility CIL maps old versioned public attributes to current concrete types, allowing a newer platform to run with an older vendor partition. Vendor-defined types should normally use the `vendor_` namespace.<sup>[[1]](#references)[[2]](#references)</sup>

## Extract the effective policy

[`setools-android`](https://github.com/xmikos/setools-android) provides Android builds of `seinfo` and `sesearch`. Reading the live policy normally requires root; perform collection on an authorized research device and remember that Magisk, KernelSU, APatch or a policy-injection module may already have added domains and rules. Record both a live dump and the partition inputs so the effective policy can be compared with a stock image.<sup>[[3]](#references)[[8]](#references)</sup>

```bash
adb shell getenforce
adb shell id -Z
adb shell su -c 'cat /sys/fs/selinux/policy' > live-policy
adb pull /system/etc/selinux/ system-selinux/
adb pull /system_ext/etc/selinux/ system-ext-selinux/
adb pull /product/etc/selinux/ product-selinux/
adb pull /vendor/etc/selinux/ vendor-selinux/
adb pull /odm/etc/selinux/ odm-selinux/
```

If `adb pull` skips a context or XML file, stream it through the privileged shell instead. Establish a baseline and identify permissive domains before interpreting individual rules.<sup>[[8]](#references)</sup>

```bash
adb exec-out su -c 'cat /system/etc/selinux/plat_seapp_contexts' > plat_seapp_contexts
adb shell su -c '/data/local/tmp/seinfo --stats /sys/fs/selinux/policy'
adb shell su -c '/data/local/tmp/seinfo --permissive /sys/fs/selinux/policy'
```

An unexpected permissive domain on a production image is important because denials for that domain are audited but not enforced. However, a permissive domain or unusually high rule count on a rooted test phone may belong to the rooting framework rather than the stock firmware. Older Android SETools ports can also report incomplete statistics for modern policy features, so confirm surprising counts with `sesearch` results or a second parser rather than interpreting them as absence.<sup>[[8]](#references)</sup>

### `neverallow` is a build assertion

AOSP `neverallow` rules constrain policy compilation and compatibility testing; they are not decision rules preserved in the loaded binary policy. Therefore, an empty `Neverallow` count in `seinfo --stats` is expected, and a live-policy injector can add an `allow` that the original source policy would have rejected. Treat a stock-to-live policy diff—not the source `neverallow` set—as the runtime integrity check.<sup>[[4]](#references)[[8]](#references)</sup>

## Query from the compromised domain

Start with the exact domain obtained from `id -Z` or `/proc/<pid>/attr/current`; Android has multiple app domains, including target-SDK-specific `untrusted_app_*`, isolated-app and SDK-sandbox domains, so blindly querying `untrusted_app` can omit the real path. The following examples use `untrusted_app`; replace it with the target domain.<sup>[[5]](#references)[[8]](#references)</sup>

```bash
P=/sys/fs/selinux/policy
S=/data/local/tmp/sesearch
adb shell su -c "$S -A -s untrusted_app $P"
adb shell su -c "$S -A -s untrusted_app -c file $P"
adb shell su -c "$S -A -s untrusted_app -c chr_file $P"
adb shell su -c "$S -A -s untrusted_app -c process $P"
adb shell su -c "$S -A -s untrusted_app -c process -p transition $P"
```

Enumerate cross-domain IPC separately because it often supplies the first edge from an app to a privileged parser or service.<sup>[[8]](#references)</sup>

```bash
adb shell su -c "$S -A -s untrusted_app -c binder $P"
adb shell su -c "$S -A -s untrusted_app -c unix_stream_socket -p connectto $P"
adb shell su -c "$S -A -s untrusted_app $P" | grep vendor_
```

Then search globally for permissions which make a compromised target domain valuable. These results are leads, not vulnerabilities: correlate every rule with an object label and a reachable code path.<sup>[[8]](#references)</sup>

```bash
for cap in sys_admin sys_rawio sys_ptrace sys_module; do
adb shell su -c "$S -A -c capability -p $cap $P"
done
adb shell su -c "$S -A -c file -p execute $P" | grep -E 'data_file|tmpfs'
adb shell su -c "$S -A -t proc_mem $P" | grep vendor_
adb shell su -c "$S -A -t kmsg_device $P" | grep vendor_
```

High-value findings include broad `chr_file` access with `ioctl`, privileged Binder or Unix-socket targets, executable writable storage, sensitive persistent files and powerful Linux capabilities. An `allow` does not create code execution by itself; its offensive significance is that code execution in the source domain inherits that authority and may reach a second vulnerable driver, HAL or service.<sup>[[8]](#references)</sup>

## Convert rules into an attack-path graph

Model **domains and labeled objects as nodes** and permitted operations as directed edges. This avoids treating a large list of allows as equally important and exposes multi-stage chains.<sup>[[8]](#references)</sup>

1. Fix the starting domain and confirm its real context on the target process.
2. Enumerate files, device nodes, Binder services, Unix sockets and allowed transitions reachable from it.
3. Resolve each result to the concrete object (`ls -Z`, `service list`, context files and device-node major/minor values); attributes returned by `sesearch` can represent many concrete types.
4. Check whether attacker-controlled data reaches code in the target service or driver.
5. Repeat the policy query with each reachable service/HAL domain as the new source.
6. Prioritize chains ending in driver IOCTLs, process-memory access, module loading, raw hardware access, sensitive writes or a domain transition.

Vendor policy deserves special attention because OEM HALs and proprietary daemons frequently connect application-reachable IPC to device-specific kernel drivers. The useful finding is the conjunction **reachable entry point + exploitable implementation + policy permission to reach the next target**, not an overbroad rule in isolation.<sup>[[2]](#references)[[8]](#references)</sup>

## Android 16 portability checks

Starting with vendor API level `202504`, `genfscon` labels can be selected by `BOARD_GENFS_LABELS_VERSION`, stored in `/vendor/etc/selinux/genfs_labels_version.txt`. Before assuming the type of a `/proc` or `/sys` path, read that value and inspect the matching `plat_sepolicy_genfs_<version>.cil`; the same path can receive a different type when paired with another vendor partition.<sup>[[2]](#references)[[8]](#references)</sup>

```bash
adb shell cat /vendor/etc/selinux/genfs_labels_version.txt
adb shell ls '/system/etc/selinux/plat_sepolicy_genfs_*'
adb shell cat /vendor/etc/selinux/plat_sepolicy_vers.txt
adb shell cat /sys/fs/selinux/policyvers
```

`/sys/fs/selinux/policyvers` reports the maximum binary-policy version supported by the kernel; it is not the Treble platform-policy identifier and does not necessarily equal the version of the loaded binary. Resolve versioned types through `/system/etc/selinux/mapping/*.cil` rather than stripping their suffixes by assumption.<sup>[[2]](#references)[[8]](#references)</sup>

Android 16 also adds policy selectors for `sdk_sandbox_next`, `sdk_sandbox_audit` and the privileged virtualization terminal domain `vmlauncher_app`. Query these as distinct domains: `sdk_sandbox_audit` uses `auditallow` telemetry to measure currently permitted accesses, while `vmlauncher_app` has virtualization-service and VM-management reachability. Such rules identify review targets and release-portability risks, but do not prove exploitation.<sup>[[6]](#references)[[7]](#references)[[8]](#references)</sup>

```bash
grep -E 'sdk_sandbox_(next|audit)|vmlauncher_app' plat_seapp_contexts
P=/sys/fs/selinux/policy
S=/data/local/tmp/sesearch
adb shell su -c "$S -A -s sdk_sandbox_audit $P"
adb shell su -c "$S -A -s vmlauncher_app $P"
adb shell cat /system/etc/selinux/bug_map
adb shell cat /vendor/etc/selinux/selinux_denial_metadata
```

`bug_map` and `selinux_denial_metadata` map denial tuples to issue identifiers. Use them to prioritize components with known policy friction, not as evidence that the denied operation is allowed or that the target is exploitable.<sup>[[8]](#references)</sup>

## Kernel primitive versus SELinux data-only bypass

Modern Android kernel mitigations mostly obstruct the conversion of memory corruption into stable kernel read/write: MTE/hardware-tagged KASAN can detect stale or incorrectly tagged accesses, SCS and PAC protect return-oriented control flow, and kCFI validates indirect-call target types. They do not independently validate every legitimate-looking write after an attacker already owns an arbitrary kernel read/write primitive.<sup>[[8]](#references)</sup>

This distinction matters when evaluating a proposed final action.<sup>[[8]](#references)[[9]](#references)[[10]](#references)</sup>

| Final action after kernel R/W | Relevant constraint |
| --- | --- |
| Unlink an SELinux LSM hook | Data-only list mutation; no invalid indirect call is made, so kCFI is not directly triggered. |
| Replace an LSM hook pointer | The destination must satisfy the call site's kCFI type check and may also be subject to pointer authentication. |
| Change policy/AVC data | Control-flow mitigations do not directly protect the semantic integrity of these data structures. |
| Change the enforcing field | Build-specific: GKI exposes `selinux_state`, but when `CONFIG_SECURITY_SELINUX_DEVELOP` is disabled, `enforcing_enabled()` returns `true` regardless of that field. |

Do not hard-code an obsolete `selinux_enforcing` global or structure offset. Recover symbols and layouts from the exact kernel build, account for KASLR and `__randomize_layout`, and check the SELinux configuration before selecting a target. The AVC implementation uses 512 buckets in the reviewed kernel, but occupancy and chain length are dynamic and can be inspected through `/sys/fs/selinux/avc/hash_stats`.<sup>[[9]](#references)[[11]](#references)[[8]](#references)</sup>

```bash
adb shell zcat /proc/config.gz | grep -E 'SELINUX_DEVELOP|CFI_CLANG|PTR_AUTH|MTE|KASAN_HW_TAGS|SHADOW_CALL_STACK'
adb shell cat /sys/fs/selinux/avc/hash_stats
adb shell grep -E ' selinux_state$| selinux_enforcing' /proc/kallsyms
```

Zeroed `/proc/kallsyms` addresses are normal on production-like builds, so a real chain needs an independent disclosure, KASLR bypass or other symbol-location technique. Hypervisor-backed integrity mechanisms are qualitatively different from compiler and allocator hardening because they can restrict modifications to protected kernel data even after the guest kernel is compromised.<sup>[[8]](#references)</sup>

## References

- [1] [AOSP - Build SELinux policy](https://source.android.com/docs/security/features/selinux/build)
- [2] [AOSP - SELinux policy compatibility](https://source.android.com/docs/security/features/selinux/compatibility)
- [3] [setools-android](https://github.com/xmikos/setools-android)
- [4] [AOSP - Customize SELinux and neverallow rules](https://source.android.com/docs/security/features/selinux/customize)
- [5] [AOSP system/sepolicy - Application domain overview](https://android.googlesource.com/platform/system/sepolicy/+/refs/heads/main/README.apps.md)
- [6] [AOSP system/sepolicy - vmlauncher_app policy](https://android.googlesource.com/platform/system/sepolicy/+/08f7c99456097c9214fd8270ff2e61a60b724934/private/vmlauncher_app.te)
- [7] [AOSP system/sepolicy - SDK sandbox audit policy](https://android.googlesource.com/platform/system/sepolicy/+/refs/heads/main/private/sdk_sandbox_audit.te)
- [8] [8kSec - Android SELinux Internals Part IV: Policy Analysis, Kernel Mitigations, and Android 16 Changes](https://8ksec.io/android-selinux-internals-part-iv)
- [9] [Android GKI 6.6 - SELinux state and enforcing helpers](https://android.googlesource.com/kernel/common/+/refs/heads/android15-6.6-lts/security/selinux/include/security.h)
- [10] [Android GKI 6.6 - LSM hook dispatch](https://android.googlesource.com/kernel/common/+/refs/heads/android15-6.6-lts/security/security.c)
- [11] [Android GKI 6.6 - SELinux AVC implementation](https://android.googlesource.com/kernel/common/+/refs/heads/android15-6.6-lts/security/selinux/avc.c)

{{#include ../../banners/hacktricks-training.md}}