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 @@ -439,6 +439,7 @@
- [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)
- [Android SELinux Domains, AVC Denials and Policy Manipulation](mobile-pentesting/android-app-pentesting/android-selinux-domains-and-policy-manipulation.md)
- [Android Task Hijacking](mobile-pentesting/android-app-pentesting/android-task-hijacking.md)
- [Android VPN Bypass](mobile-pentesting/android-app-pentesting/android-vpn-bypass.md)
- [ADB Commands](mobile-pentesting/android-app-pentesting/adb-commands.md)
Expand Down
4 changes: 4 additions & 0 deletions src/mobile-pentesting/android-app-pentesting/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ Sometimes it is useful to **modify application code** to access **hidden informa
- [Android app-level virtualization / app cloning abuse & detection](android-application-level-virtualization.md)
- [Shizuku Privileged API (ADB-based non-root privileged access)](shizuku-privileged-api.md)

{{#ref}}
android-selinux-domains-and-policy-manipulation.md
{{#endref}}

{{#ref}}
../../binary-exploitation/linux-kernel-exploitation/futex-pi-uaf-pipe-buffer-workqueue-usermodehelper.md
{{#endref}}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
# Android SELinux Domains, AVC Denials and Policy Manipulation

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

Android SELinux is a second authorization boundary after the Linux UID sandbox. The techniques below are for rooted, `userdebug`/`eng`, or otherwise policy-modifiable research devices: they remove MAC restrictions **after** privileged access exists and are not an initial-access exploit.<sup>[[9]](#references)</sup>

## From APK identity to process domain

Android derives an application's process domain and data-file type from two policy inputs. `mac_permissions.xml` maps a signing identity (optionally refined by package name) to `seinfo`; `seapp_contexts` then matches `seinfo` together with selectors such as UID class, package name, privileged/ephemeral status, target SDK, and isolated/SDK-sandbox state. Its `domain` output labels the process, `type` labels the app data directory, and `levelFrom` derives MLS/MCS categories used for per-app separation.<sup>[[1]](#references)[[2]](#references)[[9]](#references)</sup>

```text
APK certificate/package
-> mac_permissions.xml -> seinfo
-> seapp_contexts selectors
-> domain (process)
-> type + levelFrom (data directory and categories)
```

At installation, PackageManager delegates data-directory labeling to `installd`, which applies the selected object type. At launch, Zygote forks and calls `selinux_android_setcontext()` before application code executes, so the child enters its selected domain while the parent remains `zygote`.<sup>[[9]](#references)</sup>

Inspect the effective mapping and runtime labels on the device as follows.<sup>[[1]](#references)[[9]](#references)</sup>

```bash
adb shell cat /system/etc/selinux/plat_mac_permissions.xml
adb shell cat /system/etc/selinux/plat_seapp_contexts
adb shell ps -AZ | grep com.example.target
adb shell run-as com.example.target cat /proc/self/attr/current
adb shell ls -Zd /data/user/0/com.example.target # root usually required
```

Do not assume that raw line order alone determines the winner on current AOSP. Entries are sorted by a defined specificity precedence and apps are checked until the first match; fixed/longer `user` or `name` selectors outrank prefixes, specified `seinfo` outranks unspecified `seinfo`, and platform entries outrank vendor entries at the final precedence step.<sup>[[1]](#references)</sup>

A package-specific mapping can therefore force a research APK into another existing domain on a policy-modifiable image. This changes the SELinux domain and data type, **not** its Linux UID, and the target domain must already exist with suitable policy rules.<sup>[[1]](#references)[[9]](#references)</sup>

```text
user=_app seinfo=default name=com.example.research \
domain=system_app type=system_app_data_file levelFrom=user
```

On production images, AVB/dm-verity and partitioned policy normally prevent directly replacing `/system/etc/selinux/plat_seapp_contexts`; use an explicitly modifiable lab image or a root framework's supported boot-time mechanism instead of assuming a remount is sufficient.<sup>[[9]](#references)</sup>

## Turn AVC denials into policy tuples

An AVC record directly describes the rejected tuple: permissions in `{ ... }`, source domain in `scontext`, target type/domain in `tcontext`, and object class in `tclass`. For example, the record below means the `shell` domain lacks `proc_mem:file { open read write }`; `permissive=0` means enforcement blocked the request.<sup>[[3]](#references)[[9]](#references)</sup>

```text
avc: denied { open read write } for pid=8821 comm="exploit_poc" name="mem"
scontext=u:r:shell:s0
tcontext=u:object_r:proc_mem:s0
tclass=file permissive=0
```

Collect a clean window while reproducing the operation. Availability of kernel logs differs between production and debug builds.<sup>[[3]](#references)[[9]](#references)</sup>

```bash
adb shell su -c 'dmesg -w | grep "avc:"'
adb logcat -b all | grep 'avc:'
adb shell su -c 'cat /proc/kmsg' | grep 'avc:'
```

If an access fails without a visible AVC, check whether `dontaudit` suppresses it. Android builds of SETools can query the live binary policy; compare these rules with the failing source, target and class, and use a temporary per-domain permissive test only on an isolated lab device to collect the rest of the denial chain.<sup>[[8]](#references)[[9]](#references)</sup>

```bash
adb push sesearch /data/local/tmp/ && adb shell chmod 755 /data/local/tmp/sesearch
adb shell /data/local/tmp/sesearch --dontaudit /sys/fs/selinux/policy \
| grep -E '(^dontaudit shell |^dontaudit untrusted_app )'
adb shell su -c "magiskpolicy --live 'permissive shell'"
# Reproduce, collect AVCs, then restore enforcement:
adb shell su -c "magiskpolicy --live 'enforce shell'"
```

`audit2allow` is useful for translating captured denials, but its output is only a starting point: blindly applying it can grant sensitive kernel-memory or device access. Reduce every candidate to the exact source, target, class and permissions required by the PoC.<sup>[[3]](#references)</sup>

```bash
adb pull /sys/fs/selinux/policy ./policy
adb logcat -b events -d | audit2allow -p ./policy
```

## File types, `chcon` and `restorecon`

A PoC may have the correct UID and process domain but still fail because its executable or library has the wrong target type. Files copied to `/data/local/tmp` normally acquire a path-defined label such as `shell_data_file`; with root, `chcon` can temporarily select a type for which the caller has `execute`, `entrypoint`, `map`, or read permissions.<sup>[[4]](#references)[[9]](#references)</sup>

```bash
adb push exploit_poc /data/local/tmp/
adb shell su -c 'ls -Z /data/local/tmp/exploit_poc'
adb shell su -c 'chcon u:object_r:shell_exec:s0 /data/local/tmp/exploit_poc'
adb shell su -c 'ls -Z /data/local/tmp/exploit_poc'
```

This does not persist against `restorecon`, filesystem relabeling, or boot-time relabel logic. `restorecon` resolves the expected type from merged, regex-based `file_contexts` files, so always record the original label and restore it after testing.<sup>[[4]](#references)[[9]](#references)</sup>

```bash
adb shell su -c 'restorecon -v /data/local/tmp/exploit_poc'
adb shell su -c 'cat /system/etc/selinux/plat_file_contexts' | grep '/data/local/tmp'
adb shell su -c 'cat /vendor/etc/selinux/vendor_file_contexts' | grep '/vendor/bin'
```

`procfs`, `sysfs` and `debugfs` use `genfscon` path mappings rather than persistent per-inode labels. Adding a mapping only assigns a target type; the test domain still requires the corresponding allow rule and object class.<sup>[[5]](#references)[[6]](#references)[[9]](#references)</sup>

```bash
magiskpolicy --live 'genfscon proc /kallsyms u:object_r:proc_kallsyms:s0'
magiskpolicy --live 'allow myexploit_t proc_kallsyms file { open read getattr }'
```

## Test another domain with `runcon`

Changing only the requested context is insufficient. A successful `runcon` test needs (1) caller-to-target `process { transition setexec }`, (2) target-domain `entrypoint` access to the executable type, and (3) permission for the target to use inherited ADB descriptors, sockets and terminal devices. The Linux UID is inherited separately, so `runcon ... id` can report UID 0 in a restricted SELinux domain.<sup>[[9]](#references)</sup>

```bash
magiskpolicy --live 'allow magisk system_app process { transition dyntransition setexec }'
magiskpolicy --live 'allow system_app toolbox_exec file { entrypoint execute read open getattr map }'
magiskpolicy --live 'allow system_app adbd fd use'
magiskpolicy --live 'allow system_app adbd unix_stream_socket { read write }'
magiskpolicy --live 'allow system_app devpts chr_file { read write open getattr ioctl }'
adb shell su -c 'runcon u:r:system_app:s0 id'
```

Use the resulting context to answer one narrow question at a time, such as whether `cameraserver` can open a GPU node or an app domain can read a specific procfs object. Follow every new AVC rather than broadening the domain globally.<sup>[[9]](#references)</sup>

## Live policy changes with `magiskpolicy`

`magiskpolicy` parses binary/runtime policy and `--live` immediately loads the result into the kernel. Its policy language supports `allow`, `deny` (removing matching allow bits), audit controls, per-type permissive/enforcing state, new types and attributes, transitions, `genfscon`, and ioctl extended permissions.<sup>[[6]](#references)[[9]](#references)</sup>

```bash
magiskpolicy --live 'allow shell proc_mem file { open read write getattr }'
magiskpolicy --live 'deny untrusted_app shell_data_file file execute'
magiskpolicy --live 'auditallow untrusted_app app_data_file file { read write }'
magiskpolicy --live 'permissive untrusted_app'
magiskpolicy --live 'enforce untrusted_app'
```

### Dedicated exploit-testing domain

Prefer a dedicated domain over making `shell`, `untrusted_app`, or the whole device permissive. The executable label becomes the transition trigger: when `shell` executes a file labeled `myexploit_exec_t`, the `type_transition` selects `myexploit_t`. Add only the resources the PoC needs, then inspect the domain's AVCs.<sup>[[6]](#references)[[9]](#references)</sup>

<details>
<summary>Create a minimal custom transition</summary>

```bash
magiskpolicy --live 'type myexploit_t domain'
magiskpolicy --live 'permissive myexploit_t'
magiskpolicy --live 'type myexploit_exec_t file_type'
magiskpolicy --live 'type_transition shell myexploit_exec_t process myexploit_t'
magiskpolicy --live 'allow shell myexploit_t process transition'
magiskpolicy --live 'allow shell myexploit_exec_t file { open read getattr execute }'
magiskpolicy --live 'allow myexploit_t myexploit_exec_t file entrypoint'
magiskpolicy --live 'allow myexploit_t shell_data_file file { open read write map getattr }'
adb shell su -c 'chcon u:object_r:myexploit_exec_t:s0 /data/local/tmp/exploit_poc'
adb shell /data/local/tmp/exploit_poc
```

</details>

### Fine-grained ioctl filtering

A basic `chr_file ioctl` allow can still be denied by SELinux extended permissions. Grant only the ioctl request values required by the driver PoC; `*` (equivalent to `0x0000-0xFFFF`) removes that fine-grained restriction for the selected source/target/class and is appropriate only for disposable testing environments.<sup>[[6]](#references)[[9]](#references)</sup>

```bash
magiskpolicy --live 'allow shell gpu_device chr_file { open read write ioctl }'
magiskpolicy --live 'allowxperm shell gpu_device chr_file ioctl { 0x6601 0x6602 }'
# Broad laboratory-only alternative:
magiskpolicy --live 'allowxperm shell gpu_device chr_file ioctl *'
```

Live edits disappear after reboot or policy replacement. For repeatable Magisk testing, place reviewed statements in `/data/adb/modules/<module>/sepolicy.rule` so Magisk merges them during boot; avoid wildcard rules and keep the module disabled outside the lab.<sup>[[9]](#references)</sup>

```text
allow my_daemon system_file file { open read getattr }
allow my_daemon init unix_stream_socket connectto
```

## Kernel-space injection: KernelSU

KernelSU performs its built-in SELinux changes in kernel code against the active `policydb`. Its current rules create a permissive `su` domain, assign trusted/network attributes, grant broad access and ioctl xperms, and replace the active policy pointer after updating the policy structures. This differs from `magiskpolicy --live`, which loads a patched policy through the normal runtime-policy path.<sup>[[7]](#references)[[9]](#references)</sup>

Because these mechanisms intentionally weaken mandatory access control, treat any unauthorized access to a root-framework policy channel as a sandbox-break amplifier: it can expose device nodes, Binder/Unix sockets, procfs/sysfs data, cross-domain tracing and privileged executable transitions even when the attacker already has UID 0.<sup>[[7]](#references)[[9]](#references)</sup>

## References

- [1] [AOSP `seapp_contexts` mapping and precedence](https://android.googlesource.com/platform/system/sepolicy/+/refs/heads/main/private/seapp_contexts)
- [2] [AOSP `mac_permissions.xml` signing identity to `seinfo` mapping](https://android.googlesource.com/platform/system/sepolicy/+/refs/heads/main/private/mac_permissions.xml)
- [3] [Android Open Source Project - Validate SELinux policy and analyze denials](https://source.android.com/docs/security/features/selinux/validate)
- [4] [AOSP `file_contexts` matching and `restorecon` use](https://android.googlesource.com/platform/system/sepolicy/+/refs/heads/main/private/file_contexts)
- [5] [AOSP `genfs_contexts` pseudo-filesystem labels](https://android.googlesource.com/platform/system/sepolicy/+/refs/heads/main/private/genfs_contexts)
- [6] [Magisk tools documentation - `magiskpolicy`](https://topjohnwu.github.io/Magisk/tools.html#magiskpolicy)
- [7] [KernelSU SELinux policy injection source](https://github.com/tiann/KernelSU/blob/main/kernel/selinux/rules.c)
- [8] [setools-android](https://github.com/xmikos/setools-android)
- [9] [Android SELinux Internals Part II - Domains, Denials, and Bypass with Root Tools](https://8ksec.io/android-selinux-internals-part-ii)

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