Android SELinux Internals Part I | 8kSec Blogs

This is part I of a 2 part series on Android SELinux Internals where we will do a deepdive into the world of SELinux on Android and understand its inner workings, along with its functionalities and benefits. We’ll discuss how SELinux provides security on Android devices and ways to bypass it.

This is going to be a long read so grab your cup of coffee and lets start!

What is SELinux?

SELinux is a Mandatory Access Control (MAC) mechanism that enforces a set of security policies on your Android device. These policies define what resources, such as files, or network connections each process or application running on your device can access. It also controls what actions these different processes can take on your device.

For example, let’s say you have installed a new social media application on your Android device. When you install the application, the application could request for accessing your device contacts so it can suggest you already your existing friends on the platform. If your device did not have SELinux, the application would be granted complete access to the entire contacts list without any further restrictions or checks. This could put your device data to risk of compromise by malicious actors.

When an application requests access to a sensitive resource, such as the device’s contacts, SELinux will compare the label assigned to the application’s process to the label assigned to the contacts. Access is allowed only if the applications process label has sufficient privilege to access that the contacts. This is especially useful when an application with malicious intent installed on the device attempts to exploit vulnerabilities in the system, to bypass the permission prompts and gain access to sensitive resources on the device without the user’s knowledge. SELinux helps restrict the malicious application’s access to resources in all such scenarios based on its security context.

How do you read the SELinux context of a file or directory in Android?

When an application requests access to a sensitive resource, like the contacts on your device, SELinux uses a combination of security contexts and security labels to enforce access controls.

You can use the command adb shell ls -Z /system to view the security context labels of a directory/file using ADB.

				
					adb shell ls -Z /system

u:object_r:system_file:s0     apex
u:object_r:system_file:s0     app
u:object_r:system_file:s0     bin
u:object_r:system_file:s0     build.prop
u:object_r:system_file:s0     etc
u:object_r:system_file:s0     fonts
u:object_r:system_file:s0     framework
u:object_r:system_lib_file:s0 lib64
u:object_r:system_file:s0     priv-app
u:object_r:system_file:s0     product
u:object_r:system_file:s0     system_ext
u:object_r:system_file:s0     usr
u:object_r:vendor_file:s0     vendor
u:object_r:system_file:s0     xbin
				
			

When your phone boots, SELinux will load the SELinux policy and label every file with its own security label. The above command lists the contents of the /system directory on our Android device and displays the SELinux context of each of the files and directories.

The security context label for the /system directory on Android is u:object_r:system_file:s0. This label provides information about the security domain, object class, and sensitivity level of the directory. Let us break down the security context label for this process and understand it further.

  • The first part of the label, u, indicates the SELinux user for the object. In this case, the user is u which corresponds to the **system** user.

  • The second part of the label, object_r, specifies the security domain for the object. The system_file domain is used for system files that should be protected from unauthorized modification or access. This domain is defined in the SELinux policy and has a set of associated permissions and rules.

    Some examples of security domains used for apps include:

    • - app_data_file: This is used for files that belong to an application’s data directory, such as its private preferences, databases, and cached data.
    • - app_exec: This is used for app executables, such as the main app process and any services or broadcast receivers.

    • - app_domain: This is used for app domains, which provide a context for running scripts or plugins within an app.

    • - media_rw_file: This is used for files that are accessible by the media server, such as music and videos.

    • - untrusted_app: This is used for apps that run in an untrusted environment, such as when running code downloaded from the internet.

    Each security domains has a set of associated permissions and rules that define what resources the application is allowed to access and what actions it can perform.

  • The third part of the label, s0, specifies the sensitivity level for the object. The sensitivity level defines the default security level for the object and determines the permissions that are granted to processes that access the object. In this case, the sensitivity level is s0, which is the default sensitivity level for system files and provides a high degree of protection against unauthorized access or modification. For example, a process running with the s0 sensitivity level might be prevented from reading or modifying files in another app’s data directory, or from sending network requests to a restricted set of IP addresses.

    However, if needed, some system or privileged applications may be assigned higher security levels such as s1 or s2. This grants them greater access to system resources, but also subject to stricter access controls.

How do you read the SELinux context of a process in Android?

When an application requests access to a sensitive resource, like the contacts on your device, SELinux uses a combination of security contexts and security labels to enforce access controls.

Just like we explained how adb can be used to read the SELinux context for a file, you can use the command adb shell ps -Z to view the security context labels of a process.

Let us see how things look when we use a target Android APK. We launched a custom application named  “AccessLocalFile” to further look at these security context labels further.

				
					adb shell ps -Z | grep com.dns.accesslocalfile
u:r:untrusted_app:s0:c132,c256,c512,c768 u0_a132 6384 289 13720540 143576 do_epoll_wait      0 S com.dns.accesslocalfile
				
			

This command shows the SELinux context label for our installed sample application “com.dns.accesslocalfile”.

The SELinux context label for the process is u:r:untrusted_app:s0:c132,c256,c512,c768 . This label provides information about the security domain, role, and sensitivity level for the process, as well as the set of security contexts it has been granted access to.

Let us understand it in further detail.

  •  The first part of the label, u, specifies the SELinux user for the process. In this case, the user is u, which corresponds to the system user. System user is the user account that system processes and services run under.

  •  The second part of the label, r, specifies the SELinux role for the process. The untrusted_app role is used for apps that run in an untrusted environment, such as when running code downloaded from the internet.

  • - s0 represents the initial SELinux security level. The initial SELinux security level s0 refers to the default security context for a process. This level provides a basic level of protection and restricts the process’s access to system resources, such as files, directories, and network sockets. However, if needed, some system or privileged applications may be assigned higher security levels such as s1 or s2. This grants them greater access to system resources, but also subject to stricter access controls.

  •  The fourth part of the label – capabilities c132,c256,c512,c768, specifies the set of security contexts the process has been granted access to. These security contexts correspond to different levels of access to system resources, with lower context levels representing more privileged access and higher context levels representing more restricted access.

  •  The output also shows that the process is in state S, which indicates that it is sleeping, waiting for an event to occur. The command used to start the process is do_epoll_wait, which is a system call used to wait for events on a set of file descriptors using the epoll I/O event notification mechanism.

How does SELinux work under the hood?

SELinux uses a set of policies, written in a specialized language called SELinux policy language.

All allowed operations and rules are stored in type enforcement files (.te files). These files specify the security contexts for different types of objects, such as files, directories, and processes, as well as the permissions and access controls that apply to those objects.

Head over to the following link to see the AOSP code repository which points to the .te files used in the SELinux policy. From here, you can browse and download the files as needed.

https://android.googlesource.com/platform/external/sepolicy/+/refs/heads/master

As another example, you can head over to https://android.googlesource.com/platform/external/sepolicy/+/refs/heads/brillo-m10-release to view the sepolicy directory that contains the SELinux policy files used in the Brillo operating system, which may be different from the SELinux policy used in other versions of Android.

Also take a look at the following folders in the AOSP code:

On a physical Android devices and emulators, these policies are stored in a binary file. You can run the following command to print out the location of the the SELinux policy files for your device.

				
					emu64a:/ $ grep selinuxfs /proc/mounts
selinuxfs /sys/fs/selinux selinuxfs rw,relatime 0 0
				
			

So in our case, the policy file will be in /sys/fs/selinux, and in a file as “policy” – **/sys/fs/selinux/policy**.

Also observe that the file can only be accessible as a root user.

How do we interact with this SELinux policy file?

  1. To read an SELinux policy file, we can use the utilities from https://github.com/xmikos/setools-android. Go ahead and download the compiles binaries from the releases section.

  2. You can use the command adb shell getprop ro.product.cpu.abi to get the architecture of your device. In our case it is arm64-v8a. So, we will push the binaries from the folder setools-android/arm64-v8a to the device.

				
					adb push arm64-v8a/* /data/local/tmp
				
			
  1. Using adb navigate to /data/local/tmp and make the binaries executable.

  1. Change the shell to root.

				
					su
				
			
  1. You can view the list of available SELinux policy attributes using the following command:

				
					./seinfo -a /sys/fs/selinux/policy
				
			
  1. You can view the list of available SELinux object classes using the following command:

				
					./seinfo -c /sys/fs/selinux/policy
				
			
  1. You can view the information of the available roles defined in an selinux policy using the command:

				
					./seinfo --role /sys/fs/selinux/policy
				
			

In our case we have the following roles:

				
					# ./seinfo --role /sys/fs/selinux/policy                      

Roles: 4
   auditadm_r
   r
   object_r
   secadm_r
				
			
  1. you can view the list of all policy capabilities defined in the SELinux policy file using the following command:

				
					# ./seinfo --polcap  /sys/fs/selinux/policy 

Policy Capabilities: 4
   network_peer_controls
   open_perms
   extended_socket_class
				
			
  1. You can get the statistics for the SELinux policy file using the following command:

				
					./seinfo --stats  /sys/fs/selinux/policy 
				
			
				
					# ./seinfo --stats  /sys/fs/selinux/policy                  

Statistics for policy file: /sys/fs/selinux/policy
Policy Version & Type: v.30 (binary, mls)

   Classes:           103    Permissions:       234
   Common classes:      5
   Sensitivities:       1    Categories:       1024
   Types:               0    Attributes:       1949
   Users:               1    Roles:               4
   Booleans:            0    Cond. Expr.:         0
   Allow:           30132    Neverallow:          0
   Auditallow:         67    Dontaudit:        1044
   Type_trans:        678    Type_change:         0
   Type_member:         0    Role allow:          0
   Role_trans:          0    Range_trans:         0
   Constraints:        89    Validatetrans:       0
   Initial SIDs:       27    Fs_use:             20
   Genfscon:          505    Portcon:             0
   Netifcon:            0    Nodecon:             0
   Permissives:         1    Polcap:              4
				
			
  1. To search for all SELinux policy rules in the SELinux policy file, you can use the following command:

				
					./sesearch -A  /sys/fs/selinux/policy
				
			
  1. You can further use the various available commandline options to get as specific data as you want. For example, the following command can be used to search for SELinux policy rules that allow read access to files with the type media_rw_data_file and class file, from processes with the type kernel in the SELinux policy file located at /sys/fs/selinux/policy.

				
					./sesearch -A -s kernel -t media_rw_data_file -c file -p read /sys/fs/selinux/policy
				
			

This output indicates that there is one SELinux policy rule that allows the kernel process to access files with the media_rw_data_file type and the file class, with various permissions including ioctl, read, write, create, getattr, setattr, lock, append, map, unlink, rename, open, watch, and watch_reads.

  1. You can use the following command to display all SELinux policy rules that allow access to system resources, such as files, directories, and network interfaces, in the SELinux policy file.

				
					./sesearch --allow /sys/fs/selinux/policy
				
			

For example consider:

				
					allow adbd vendor_framework_file : file { ioctl read getattr lock map open watch watch_reads } ;
				
			

This output indicates that there is one SELinux policy rule that allows the adbd process to access files with the vendor_framework_file type and the file class, with various permissions including ioctl, read, getattr, lock, map, open, watch, and watch_reads.

The adbd process is the Android Debug Bridge daemon, which allows communication between a development machine and an Android device. The rule allows adbd to perform the specified actions on files with the vendor_framework_file type and the file class, such as reading and locking the file.

  1. To view all SELinux policy rules that define the write permission for the file class, use the following command:

				
					./sesearch -T -c file -p write /sys/fs/selinux/policy
				
			
  1. You can can use commands like the following one to display all SELinux policy rules that have been explicitly marked as neverallow in the SELinux policy file.

				
					./sesearch --neverallow /sys/fs/selinux/policy
				
			

SE Linux Permission Modes (enforcing/permissive)

SE Linux on Android has basically 2 modes of operations:

  • – Enforcing mode: In this mode, the Android SE Linux strictly enforces the defined security policies. If a process or application attempts to perform an action that violates the policies, SE Linux denies the operation and logs the event. This mode provides users with the highest level of security and is definitely the recommended mode for production Android devices.

  • – Permissive Mode: In this mode, the Android SE Linux allows all operations without explicitly enforcing the policies or blocking any actions. The system logs the violations, but does not block any operation or action. This mode is useful for troubleshooting and debugging because as it allows us to identify any potential policy violations without having any impact on the overall functioning of the Android device.

You can read the current SE Linux mode of the device using the command getenforce .

You can switch to enforcing mode using the command setenforce 1 . Here 1 means enforcing.

Other modes exist like MLS Mode (Multi-Level Security), MCS Mode (Multi-Category Security) and Disabled Mode but those are not implemented in Android OS.

How do we Bypass SELinux Context for a specific process?

  1. ADB shell into the device as a normal user

				
					adb shell
id
ls -al
				
			

The init.environ.rc file is a configuration file that sets environment variables during the Android boot process. It contains a list of variable assignments in the form of export VARNAME=value. These variables are used by the system and apps to store configuration information, such as paths to system libraries and other settings.

Let us try to read this file as our current user. Observe that you get a “Permission denied” error.

3. Run the command following adb command to filter the output of the dmesg command and display only messages related to Android’s SELinux Access Control (AVC) enforcement.

				
					adb shell su root dmesg | grep 'avc: ' 

[  652.530759] type=1400 audit(1683566539.031:64): avc: denied { read } for comm="cat" name="init.environ.rc" dev="dm-0" ino=26 scontext=u:r:shell:s0 tcontext=u:object_r:rootfs:s0 tclass=file permissive=0
				
			

Here is a quick description of the various components you see in the response.

  • The timestamp displayed in the output is represented in seconds since the Unix epoch and provides the exact moment the system message was recorded.

  1. The type=1400 value denotes that an SELinux permission denial has occurred.
  2. The audit() section following the type field provides additional details about the SELinux permission denial.
  3. The SELinux permission read was denied, as indicated in the avc: denied { read } section of the output.
  4. The comm="cat" field specifies the process that was attempting to perform the operation, in this case, the cat command.
  5. The name="init.environ.rc" field indicates the name of the file that was being accessed.
  6. The dev="dm-0" field specifies the device where the file is located.
  7. The ino=26 field specifies the inode number of the file.
  8. The scontext field specifies the SELinux context of the process or command that was attempting to perform the operation.
  9. The tcontext field specifies the SELinux context of the file being accessed.
  10. The tclass=file field specifies the type of object being accessed.
  11. The permissive=0 field indicates whether SELinux is running in permissive mode. A value of 0 indicates that SELinux is not in permissive mode, and therefore, the permission denial will be enforced.
 

In summary what this tells us is that the shell user is not allowed to read init.environ.rc file on rootfs based on the SELinux policy.


4. We will use the sepolicy-inject tool to inject a new policy rule into the existing SELinux policies, thereby altering the access controls and permissions enforced by SELinux.

Download setools-android from https://github.com/xmikos/setools-android/releases and push the binaries to the device using the following commands (our device is an aarch64 device).

				
					adb push arm64-v8a/* /data/local/tmp
				
			

5. Based on the SELinux denial output we can craft the following command to allow the “shell” process to read files in the “rootfs” context, which in turn grants the permission to read files with the specified SELinux security contexts.

				
					adb shell
su
cd /data/local/tmp
cp /sys/fs/selinux/policy policy
chmod +x sepolicy-inject
./sepolicy-inject -s shell -t rootfs -c file -p read -P policy  'allow shell rootfs:file { read }'
				
			

Here is a quick description of the above command.

  • - s shell specifies the name of the security context for the shell process.

  • - t rootfs specifies the name of the security context for the rootfs filesystem.

  • - c file specifies the type of permission being granted, in this case read access to a file.

  • - p read specifies the name of the permission being granted, in this case read access.

  • - P policy specifies the path to the SELinux policy file that should be modified.

  • - 'allow shell rootfs:file { read }' is the new SELinux policy rule that is being injected.

We get the following output as response.

				
					libsepol.policydb_index_others: security:  1 users, 4 roles, 1949 types, 0 bools
libsepol.policydb_index_others: security: 1 sens, 1024 cats
libsepol.policydb_index_others: security:  103 classes, 32422 rules, 0 cond rules
Success
				
			

Here is a description of what this means

  • – There is 1 user defined in the policy database.

  •  There are 4 roles defined in the policy database.

  •  There are 1949 types defined in the policy database.

  •  There are 0 booleans defined in the policy database.

  •  There is 1 sensitivity level defined in the policy database.

  •  There are 1024 categories defined in the policy database.

  •  There are 103 classes defined in the policy database.

  •  There are 32422 rules defined in the policy database.

  •  There are 0 conditional rules defined in the policy database.

  •  

  • 6. Now copy the policy back into its original location and then load the policy.

				
					cp policy /sys/fs/selinux/policy
/system/bin/load_policy policy 
				
			

7. Again try to access the init.environ.rc file as a limited user.

				
					adb shell 
cat init.environ.rc
exit
adb shell su root dmesg | grep 'avc: '
				
			
				
					[ 4131.041556] type=1400 audit(1684296538.489:283): avc: denied { open } for comm="cat" path="/init.environ.rc" dev="dm-0" ino=26 scontext=u:r:shell:s0 tcontext=u:object_r:rootfs:s0 tclass=file permissive=0
				
			

Observe that you again get an AVC Denial. This denial can again be bypassed using sepolicy-inject

				
					adb shell
su 
cd /data/local/tmp
cp /sys/fs/selinux/policy policy
./sepolicy-inject -s shell -t rootfs -c file -p read,open -P policy "allow shell rootfs:file { read open }"

cp policy /sys/fs/selinux/policy
				
			

Reload the policy using the following command

				
					/system/bin/load_policy policy
				
			

8 Again try to access the init.environ.rc file as a limited user.

Now when you try and access the init.environ.rc file, you will observe that the file is now accessing in the context as the limited “shell” user.

We have reached the end of our exploration into the realm of Android SELinux in this segment. In the next part of our SELinux Internals series, we will learn about bypassing the SELinux context for an Android application under your control on a target device. Additionally, we will examine a practical use-case where we combine this technique with a 1-day exploit to overcome SELinux restrictions on the exploit binary, enabling the execution of operations that are typically restricted. Stay tuned for these intriguing topics in our upcoming discussions!

 

Also – Join us at our next training on Offensive Mobile Reversing and Exploitation at Black Hat USA 2023 where we talk about more such techniques. Register at https://www.blackhat.com/us-23/training/schedule/#offensive-mobile-reversing-and-exploitation-virtual-30043

GET IN TOUCH

Visit our training page if you’re interested in learning more about these techniques and developing your abilities further. Additionally, you may look through our Events page and sign up for our upcoming Public trainings. 

Check out our Certifications Program and get Certified today.

Please don’t hesitate to reach out to us through out Contact Us page or through the Button below if you have any questions or need assistance with Penetration Testing or any other Security-related Services. We will answer in a timely manner within 1 business day.

We are always looking for talented people to join our team. Visit out Careers page to look at the available roles. We would love to hear from you.

On Trend

Most Popular Stories

Hacking Android Games

Greetings and welcome to our blog post on the topic of Android game hacking. Today, we aim to provide you with an overview of the process involved in hacking Android games. It’s crucial to distinguish between app hacking and game hacking within the Android ecosystem.

Subscribe & Get InFormation

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.