0x01 Introduction
Android is based on the Linux multi-user access control mechanism. By default, applications cannot execute other applications, including reading and writing users’ private data. The process of an application is a secure sandbox (running the application in a restricted security environment, with all changes in the sandbox having no harm to the operating system).
Each Android application is assigned a unique Linux user ID upon installation, creating a sandbox for it, preventing it from interacting with other applications. This user ID is assigned at installation and remains the same on that device.
All Android applications must be signed with a certificate, and the private key of this certificate is held by the developer. This certificate can be used to identify the author of the application. The most important way that signing affects security is by determining who can access signature-based permissions and who can share user IDs. Through this mechanism, without considering the root user, each application is isolated from each other, achieving a certain level of security.
In the Linux operating system, root privileges are the highest, also known as superuser privileges. In the system, every file, directory, and process belongs to a specific user, and no ordinary user can operate without user permission, except for root.
During penetration testing of Android applications, most techniques require root privileges to install various tools, thus jeopardizing the security of the application. This has led to a phenomenon of confrontation between attackers and defenders. What techniques do Android developers use to detect if the running application is on a rooted device? Next, we will introduce some common methods used by Android developers to check if a device has been rooted and some bypass techniques.
For security reasons, many applications do not allow running on rooted devices, as rooting a phone grants attackers (users) significant autonomy, allowing them to delete system applications, modify security settings, or view and alter the running information of applications. At the same time, this opens the door for malware, posing a significant challenge to device information security. Currently, many apps conduct root environment monitoring at startup to prevent the app from running in a rooted phone environment. If a rooted device is detected, a prompt will appear warning the user of security risks, preventing the app from proceeding or installing (resulting in APK crashes).
The detection of a rooted phone mentioned above mainly has two handling methods:
-
The first method: Destroy upon detection, which means that once a ROOT device is detected, the application is not allowed to run normally.
-
The second method: Allow the application to run but switch interactions with the backend server.
A simple comparison of the two root handling methods:
-
The first method: How to stop the application from running once a ROOT device is detected? According to the Ministry of Industry and Information Technology’s supervision requirements, applications cannot be directly exited, so the general practice is to use pop-ups, Toast messages, etc., to inform users that their phone is rooted and poses risks, allowing users to decide whether to exit or exit after a few seconds. This shows that this handling places user experience in a relatively important position, but it also provides many conveniences for attackers. Friendly prompts allow attackers to confirm that the environment detection mechanism is triggered, allowing them to focus on bypassing. Not immediately stopping the application’s operation gives attackers enough time to analyze and inject, thus bypassing the application’s root detection mechanism.
-
The second method: For attackers, it is difficult to directly locate the cause of business failure, which may require packet capture analysis or even guessing to locate, which increases the cost and difficulty of attacks to a certain extent. However, this method may also mistakenly kill some legitimate users who root their phones for personalization, so to reduce the misfire rate, current applications will also implement some special handling.
Root: Obtaining super administrator privileges on the phone. The Android system is based on the Linux kernel, which does not provide super administrator privileges by default. Obtaining su permissions is called rooting.
Privileges of the root user: The root user can read, modify, or delete files or directories beyond any user and user group (within the normal permission range); execute and terminate executable programs; add, create, and remove hardware devices; and can also modify file and directory ownership and permissions to suit system management needs (because root is the highest privilege user in the system); root cannot be isolated by the sandbox based on user ID permissions.
In addition to checking (test-keys, release-keys) whether the system is a test version, detecting the installation path of root tools, package names (specific root tool package names) containing keywords like su, activity, busybox, supersu, or superuser, there are also the following detection methods:
-
Check if the su command exists.
-
Check if common directories contain su (or check if there are files with s permissions).
-
Use the which command to see if su exists.
-
Proactively request root permissions.
-
Execute busybox.
-
Check Android properties (read key properties from build.prop, such as ro.build.tags and ro.build.type).
-
Check if specific paths have write permissions (in the Android system, some directories are not accessible to ordinary users, such as
/data, /system, /etc
, etc.). -
Check mainstream emulators.
-
Detect features of hook frameworks like Frida and Xposed.
Currently, it seems that the vast majority of applications use a combination of the above detection methods, and many only implement one or two of them. Furthermore, the above methods do not cover all possibilities, as some applications also check for the existence of “Magisk” or “Superuser” for root detection. In summary, the mainstream root detection currently verifies unique characteristics of rooted phones, such as whether characteristic files exist, whether there are privilege escalations, and whether key attributes have been modified, etc.
Supplementary Knowledge: 1) Standard File Permissions
Ordinary file permissions are represented by ten bits, as shown below:
The first bit represents the “file” type, which can have the following possible values: – (file), d (directory), b (block device), l (link file), c (character device, such as serial port), s (socket).
-
The 2nd to 4th bits represent the permissions of the file owner.
-
The 5th to 7th bits represent the permissions of the file group.
-
The 8th to 10th bits represent the permissions of other users and groups.
In the Linux system, there are three types of identities for files:
u: owner (user, the owner of the file) g: group (group, the group the owner belongs to) o: others (other users) a: all (all the above)
2) rwx Permission Setting
File or directory permissions are represented by rwx for the corresponding permission values, where r is 4, w is 2, x is 1. The chmod command can be used to change the permissions of files or directories. Common permissions are:
-
r: read permission, the user can read the contents of the document, such as using cat or more to view.
-
w: write permission, the user can edit the document.
-
x: the directory has executable permissions.
There are two ways to represent permissions:
Permission value representation, as follows: # chmod 777 file1 Letter value representation, as follows: # chmod a+x file1
Command | Result | Meaning |
---|---|---|
chmod a-x myfile |
rw- rw- rw- | Revoke all user execution permissions. |
chmod og-w myfile |
rw- r– r– | Revoke write permissions for group users and other users. |
chmod g+w myfile |
rw- rw- r– | Grant write permissions to group users. |
chmod u+x myfile |
rwx rw- r– | Grant execution permissions to the file owner. |
chmod go+x myfile |
rwx rwx r-x | Grant execution permissions to group users and other users. |
3) umask
The default setting of umask value is in the /etc/profile file, with a default value of 022, which corresponds to the default permission values for files and directories created:
Default directory permissions: 777-umask Default file permissions: 666-umask
Therefore, with umask set to 022, the default permissions for created directories are 755, and for created files are 644.
4) Special Permissions
In addition to the aforementioned rwx permissions, there are three special permissions: s, s, t permissions (setuid and sticky bits). The specific descriptions are as follows:
Permission | Impact on Files | Impact on Directories |
---|---|---|
suid | Execute with the identity of the file’s owner, rather than the user executing the file. | None. |
sgid | Execute with the identity of the file’s group. | The group of any new files created in this directory will be the same as the directory’s group. |
sticky | None. | Users with write permissions on the directory can only delete their own files, not files owned by others. |
These three special permissions can also be represented using letters and numbers, as follows:
Set suid: chmod u+s test Set sgid: chmod g+s test Set sticky: $ chmod o+t test SUID: 4 SGID: 2 Sticky: 1
s means (SUID, Set UID) enables a file to have the permissions of the file owner during execution, effectively temporarily acquiring the identity of the file owner. A typical file is passwd. If a regular user executes this file, during execution, the file can obtain root privileges, thus allowing the user to change their password.
In special cases, special permission bits may be used, such as the passwd command. Without s permissions, other users cannot use the passwd command to modify their own passwords.
# ls -l /usr/bin/passwd -rwsr-xr-x. 1 root root 27832 Jun 10 2014 /usr/bin/passwd
Example of a file with added s, s, t permissions:
# touch test # ll test -rw-r--r--. 1 root root 0 Aug 5 01:03 test # chmod 7777 test # ll test -rwsrwsrwt. 1 root root 0 Aug 5 01:03 test # su - usera
Since the t permission has been added, the ordinary user usera can write to and modify the file but cannot delete files owned by others.
Note:
-
When setting s permissions, the file owner and group must first have the corresponding x permissions set; otherwise, s permissions will not take effect. (The chmod command does not perform necessary integrity checks; even if x permissions are not set and s permissions are set, chmod will not report an error. When we use ls -l, if we see rwS, the capital S indicates that s permissions are not effective.)
-
It is important to note that special permissions are a double-edged sword; many trojans also exploit s permission bits, so when searching for host trojans, we often use find to look for all 4777 and 6777 files.
-
If there was originally an x permission at that position, these special flags (suid, sgid, sticky) will be displayed as lowercase letters (s, s, t); otherwise, they will be displayed as uppercase letters (S, S, T).
-
There is also a large X permission, which will also be mentioned later in ACL.
-
s or S (SUID, Set UID): If an executable file is paired with this permission, it can gain privileges and access all system resources available to the file owner. Note that files with SUID permissions are often exploited by hackers, allowing them to create a backdoor in the system for future access.
-
T or T (Sticky): The /tmp and /var/tmp directories allow all users to temporarily access files, meaning every user has full permissions to enter the directory, browse, delete, and move files.
Additional Notes: BusyBox is a single executable implementation of many standard Linux tools. BusyBox contains some simple tools, such as cat and echo, and also includes some larger and more complex tools, such as grep, find, mount.
Rooting methods can be divided into two types:
-
Partial root.
-
Full root.
Currently, the common method for obtaining Android root permissions is through various system vulnerabilities, replacing or adding the SU program to the device to obtain root privileges. After obtaining root permissions, a program is usually installed to remind users whether to grant the program the highest permissions, which can prevent malicious software to a certain extent. This method is usually called “partial rooting.”
On the other hand, “full rooting” refers to replacing the device’s original ROM to cancel secure settings.
How to bypass root detection mechanisms? Here are two ideas: one is to interfere with the application’s root detection behavior; the other is to hide the system’s own root-related features. We can use reverse engineering tools like jadx to analyze the application source code and hook related implementation functions to bypass; we can also obtain AOSP source code and compile our own ROM to hide root features.
-
Hooking.
Currently mainstream hooking frameworks include Frida and Xposed, which can use Frida’s visible framework RMS for injection. Relatively speaking, bypassing root detection mechanisms via hooking is simpler and more convenient, but hooking itself is subject to many constraints. On one hand, due to the application’s own hardening measures, it may be difficult to locate the root detection implementation functions; on the other hand, the hooking frameworks themselves may have detectable features, which may constrain their ability to work.
-
Custom ROM.
There are many ways to customize ROMs, including unpacking the official package, modifying it, and repackaging it. The recommended approach is to obtain AOSP source code, compile it, and create a ROM package. This allows for a higher degree of customization, and compared to modifying existing packages, this method offers greater operational space, but it also comes with higher compilation costs and greater modification difficulties.
-
Hotfix.
The essence of hotfix implementation is to place the code generated after fixing bugs as dex at the head of the array.
Device cheating detection:
-
Detecting dangerous APP package names.
-
Mainly detecting hook frameworks, simulation click tools, magisk, supersu, and other root tools.
Check if root permissions exist.
-
Generally, this is checked by whether the su file exists in the bin or sbin directories, as well as the kingroot permission management apk.
Check if there is debugging status.
-
Check the default.prop file’s
ro.secure=1, ro.debuggable=1
state, and the TracerPid value in the/proc/self/status
file.
Check if the device is a factory ROM.
-
This is generally judged through various parameter values of android.os.Build, such as Build.fingerprint.
Some routine device information, such as battery status, USB status, screen brightness, geographical location, wifi or sim card information, ip, mac, etc.
-
Generally, during batch operations or device wiping operations, it is more challenging and costly to change these values, so being able to forge these indicators will significantly reduce the risk of being detected.
Behavior-based cheating detection:
-
Log log collection.
-
During Android development, there are
Log.i, Log.e
runtime logs, which most APPs use to debug or detect the operating status of the APP, and the release version generally uses boolean values to switch. If this switch can be hooked, it becomes easy to detect the APP. -
Exception stack information collection.
-
Most APP logs collect exceptions; if the stack contains the package path of the hook framework, it will kill itself, making it difficult for hooks to execute for long.
-
Statistical SDK information, such as talkingdata, umeng, etc.
-
Generally, Alibaba’s system uses umeng as a statistical tool; many of these SDKs have their unique identification numbers, and some apps use this number as an indicator to determine app installation uniqueness. Others may use statistical data to identify bulk cheating behaviors, as these SDKs collect some device fingerprints, which are technically difficult to crack or restore.
-
Exception reporting SDKs, such as bugly, umeng SDK, etc.
-
Exception reporting SDKs generally record all exceptions during runtime and will record device fingerprints. Through these, cheating devices can also be detected.
APP anti-cheating strategies and ideas:
-
Device fingerprinting.
-
For example, Shumei, Yidun, or some large factory apps will embed modules to collect device fingerprints to determine the uniqueness of the device, such as imei, android_id, mac, and other device information combined to form a unique identifier.
-
Collecting IP, MAC address, Bluetooth MAC, WIFI, and other network fingerprints.
-
After extensive collection and combining with other collected information, continuously improving proxy IP or black IP, mac databases can help identify cheating sources.
-
Geographical location, download channels, authorized logins.
-
Checking if geographical location and ip correspond, whether base station information corresponds, or whether device models correspond with download channels. Generally, Xiaomi phones should be downloaded from the Xiaomi store, such as WeChat authorized login, QQ authorized login, etc. If simulating authorized logins, it requires cracking some embedded protocols in the APP.
-
Parameter signing, parameter encryption.
-
Generally, APPs will sign get or post parameters using some algorithms, and this signature is difficult to restore. For example, Douyin’s x-gorgon, some directly encrypt post or get parameters, generally using aes, rsa, des, etc.
-
Private network protocols, protobuf protocols, private network certificates, anti-proxy packet capture.
-
Currently, the protobuf protocol is popular for replacing json, and some private
TCP/Socket
protocols cannot be read directly, requiring a parsing tool or analysis restoration. Private network certificates require certificate keys combined with proxy packet capture tools to analyze; the difficulty lies in finding the certificate key and determining whether a private certificate is used. Generally, chat APPs’ IM protocols commonly use this. -
Dex code obfuscation, native layer ollvm compilation, webview js obfuscation.
-
Corresponding dex code obfuscation restoration or analysis is relatively easy, and various decompilation software can analyze it without much difficulty. The main difficulty lies in asynchronous frameworks like rxjava and interface implementation class searches. The native layer mainly has difficulty restoring various obfuscated codes based on ollvm; other native layers are mostly media or network libraries.
-
APP packing, security SDK, private security plugins.
-
Packing is more about compliance; dex files in Android can always be dumped from memory unless the operating system changes from the bottom up. Therefore, all modified ROMs can handle this. Companies like 360, Tencent, and Baidu are involved.
-
Security SDK can also be considered a paper tiger; however, security SDKs are generally related to machine learning and big data matching, making it increasingly challenging to cope with, especially for anti-virtual login in social e-commerce.
-
Some large APPs have their security-related modules, which are similar to security SDKs; the main thing is that data forgery and algorithm cracking can bypass them.
APP anti-cheating tools and strategies:
-
Reverse engineering tools.
-
Java: dex2jar, jadx, jeb, android-killer, etc.
-
So files: IDA, jeb, Gidra.
-
Js: Actually, nodejs combined with Google or Firefox can do it.
-
Others: unicorn, unidibug, etc., are tools based on the QEMU virtual machine.
-
Packet capture tools.
-
Charles, Fiddler: These two are quite similar and are used for http, websocket, and other application layer packet captures.
-
WireShark: Supports various protocols but requires a deeper understanding of network protocol technology.
-
BurpSuite: Can develop some plugins; those who can develop can perform various magical operations.
-
Hooking frameworks.
-
Xposed: Popular among users, especially in cloud control, group control, and sales of cosmetic, educational, and insurance products.
-
Frida: Generally used more by developers; it is fast and does not require rebooting; if you know js, you can play with it.
-
Cydia: More commonly used for hooking native layers, often by older developers.
-
Inlinehook, xhook: These two are similar; inlinehook is often used for hooking b jumps, while xhook is often used for system functions.
-
Magisk: Based on Android 8 and above; xposed or other certificate installation tools are based on this, which is something worth studying in depth.
-
Simulation clicking.
-
uiautomator: Many simulation click software is based on this, and it is suitable to combine with xposed for development; understanding Android development is relatively simple.
-
There are also key spirits, node spirits, touch spirits, etc.
-
Industry difficulty.
-
ollvm obfuscation: Currently, this is the most troublesome in the reverse engineering industry; the main principle is that if-else is changed to
while(true){switch() case:}
, but the reverse engineering cost increases, and various extensions become more and more difficult, requiring experience and technical accumulation to fully restore or crack. -
Machine learning risk control strategies: Without years of experience or sufficient time to explore or without following an APP’s growth for a long time, it is challenging to cope with, and most are addressed through account nurturing and device nurturing, along with cracking protocols and forging large amounts of device information through IP proxy pools, card vendors, and third-party services to maintain. However, with the improvement of laws and the establishment of APP’s risk control systems, the corresponding costs are increasing, making it difficult to achieve large-scale account logins and registrations.
0x02 Bypassing Root Detection Experiments
1) Check if the su command exists.
Generally, to obtain root privileges, the su command is used, so checking whether this command exists can indicate whether the running environment is rooted.
2) Check Android properties.
Check whether the ro.debuggable and ro.secure properties are true; if true, the APP’s running environment is likely rooted.
3) Check if specific paths have write permissions.
Specific paths include: /system, /system/bin, /system/sbin, /system/xbin, /vendor/bin, /sys, /sbin, /etc, /proc, /dev
.
Use the mount command to confirm whether the corresponding partition’s permissions are “rw”.
adb shell mount | grep -w /sysfs on /sys type sysfs (rw,seclabel,relatime)
Next, let’s discuss several detection techniques widely used by applications. If a device has been rooted, new files will be added, so checking for the existence of these files can help determine this, and some developers also check if commands that can only run with root privileges can be executed to determine this, along with other methods.
-
In previous years, the most popular (now long discontinued) root tool is Superuser.apk, widely used to root Android devices, so checking for the existence of this app is a good method. However, before detection, we should first resolve the issue of incorrect installation packages.
Solution:
SuperSU includes a su executable file and a Superuser.apk; simply replacing the system’s su file with the one provided by SuperSU and granting it permissions -rwsr-sr-x (6755) will suffice.
Typically, there are two ways to replace files on actual devices:
Manually replace the su file provided by SuperSU with the system file, requiring root privileges; Through Recovery mode, directly flash the su file as a patch. For emulators, they do not have Recovery mode; they directly use img images to start, so only the first method can be used.
Download from the official website: https://supersuroot.org/download/, select Recovery V2.82 Flashable.zip to download, which contains the necessary su files for various architectures and the Superuser.apk installation package.
-
Check the path where the su command is located on the emulator.
adb shell which su
-
Open a cmd window and execute the following command; adb remount is to mount /system as writable, then we will replace su.
adb root adb remount adb push su /system/bin/su
Note: For Android version 5.0 and above, the su.pie file must be used, which is a position-independent executable file compiled with the -fPIE flag, possessing address space randomization features.
-
Open another cmd window and execute the following command:
adb shell chmod 6755 /system/bin/su ls -al /system/bin/su su --install su --daemon& setenforce 0
The above commands are explained as follows:
-
Set permissions so that the su executable file provided by SuperSU can be executed by all applications.
-
Initialize the installation of su.
-
Set up the su daemon process.
-
Disable SELinux security policies to lift restrictions on root permissions.
Clicking the restart button may get stuck; just close the emulator to restart it, and it will no longer prompt that su is occupied.
adb shell ls -l /system/app/ | grep 'Super'
How SuperSU Works:
-
daemonsu is the daemon process started by su.
-
Now third-party applications begin to call the su command, requesting root privileges.
-
su is an executable file that communicates with daemonsu to send execution command requests.
-
daemonsu creates a sush subprocess, and the sush process uses the am (Activity Manager) command to start the Superuser application, requesting authorization, which will display the user authorization interface.
-
If authorization is granted, the Superuser application returns the authorization result to sush through the socket, and based on the authorization, sush decides whether to execute the requested command.
The timing diagram of how SuperSU works is shown below:
SuperSu can be used by flashing its files into the system through recovery. However, Android will verify the integrity of the system, making this method potentially unfeasible. Moreover, many manufacturers lock the bootloader, meaning that the Android partitions cannot be modified privately, making flashing Su files impossible, and root permissions become a distant legend.
Everyone can try the Magisk tool, which is similar to SuperSu but much better, and SuperSu has not been maintained since 2021.
Download link: https://github.com/topjohnwu/Magisk
Magisk is an open-source software for customizing Android, supporting devices above Android 5.0. Some prominent features include:
-
MagiskSU: Provides root access for applications.
-
Magisk Modules: Modify read-only partitions by installing modules.
-
MagiskBoot: The most complete tool for unpacking and repacking Android boot images.
-
Zygisk: Runs code in the process of every Android application.
-
We can also search for some special packages, as shown in the image below:
pm list packages | grep 'shell'
-
Some applications can only run on rooted devices, so checking if they exist is also a good method. For example, the well-known Busybox:
busybox pwd
Running “su” and “id” and checking the uid can help verify this.
su id
-
Officially bypassing root detection.
Detection with SuperSU installed:
Detection without SuperSU installed:
-
The test code is as follows:
-
CheckRoot.java code.
package com.example.testpoc4; import android.util.Log; import java.io.File; public class CheckRoot { // Define TAG constant private static String TAG = CheckRoot.class.getName(); // Check if SuperSU.apk file exists, if it does, then it's rooted, log a message, otherwise return false public static boolean checkSuperuserApk() { try{ File file = new File("/system/app/SuperSU/SuperSU.apk"); if (file.exists()){ Log.w(TAG, "/system/app/SuperSU/SuperSU.apk exist"); return true; } } catch (Exception e){ } return false; } }
-
MainActivity.java code.
package com.example.testpoc4; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); boolean root = CheckRoot.checkSuperuserApk(); ((TextView) findViewById(R.id.text)).setText("Device Root:" + root); } }
-
activity_main.xml code.
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
To bypass this check, let’s rename the application “Superuser.apk” to “Superuser0.apk” and first change the /system directory from readable to writable using remount, as shown in the image below:
Simply changing its name allows us to bypass it; when we rerun the detection program, it will no longer prompt true.
0x03 Common Root Detection Methods
Common detection methods: Detect whether the system is a test version (test-keys) or a release version (release-keys), detect the package names of applications that have been elevated to root, check common or uncommon directories for the existence of su, check if su can be found using which, check if Busybox exists, check if the /data directory has read and write permissions, etc. Below are some common detection methods:
-
Check if the system is a test version.
You can check the released system version to see if it is test-keys (test version) or release-keys (release version).
public static boolean checkDeviceDebuggable() { String buildTags = android.os.Build.TAGS; if (buildTags != null && buildTags.contains("test-keys")) { Log.i(TAG, "buildTags=" + buildTags); return true; } return false; }
In practice, some manufacturers’ official release versions are also test-keys, so this identifier may not be particularly noted. Therefore, whether to use it should be carefully considered.
-
Check for the existence of Superuser.apk.
Superuser.apk is a widely used tool for rooting Android devices, so checking for its existence is a good method.
Detection method:
public static boolean checkSuperuserApk() { try{ File file = new File("/system/app/SuperSU/SuperSU.apk"); if (file.exists()){ Log.w(TAG, "/system/app/SuperSU/SuperSU.apk exist"); return true; } } catch (Exception e){ } return false; }
-
Check the su command.
su is the command for switching users in Linux, and when used without parameters, it switches to the superuser. Typically, we use the su command to obtain root privileges, so checking for its existence is useful.
Check if su exists in common directories:
public static boolean checkRootPathSU() { File f = null; final String kSuSearchPaths[] = {"/system/bin/", "/system/xbin/", "/system/sbin/", "/sbin/", "/vendor/bin/"}; try { for (int i = 0; i < kSuSearchPaths.length; i++) { f = new File(kSuSearchPaths[i] + "su"); if (f != null && f.exists()) { Log.i(TAG, "find su in : " + kSuSearchPaths[i]); return true; } } } catch (Exception e) { e.printStackTrace(); } return false; }
-
Execute su to see if root privileges can be obtained.
Executing this command will search for su in the PATH; if found, it will execute. If successful, it indicates that true superuser privileges have been obtained.
public static synchronized boolean checkGetRootAuth() { Process process = null; DataOutputStream os = null; try { Log.i(TAG, "to exec su"); process = Runtime.getRuntime().exec("su"); os = new DataOutputStream(process.getOutputStream()); os.writeBytes("exit\n"); os.flush(); int exitValue = process.waitFor(); Log.i(TAG, "exitValue=" + exitValue); if (exitValue == 0) { return true; } else { return false; } } catch (Exception e) { Log.i(TAG, "Unexpected error - Here is what I know: " + e.getMessage()); return false; } finally { try { if (os != null) { os.close(); } process.destroy(); } catch (Exception e) { e.printStackTrace(); } } }
-
Access the /data directory and check read and write permissions.
In the Android system, some directories are not accessible to ordinary users, such as /data, /system, /etc, etc. Taking /data as an example, we will attempt to read and write to it. Cautiously, I will first write a file and then read it to check if the content matches; if it matches, we consider the system to be rooted.
public static synchronized boolean checkAccessRootData() { try { Log.i(TAG, "to write /data"); String fileContent = "test_ok"; Boolean writeFlag = writeFile("/data/su_test", fileContent); if (writeFlag) { Log.i(TAG, "write ok"); } else { Log.i(TAG, "write failed"); } Log.i(TAG, "to read /data"); String strRead = readFile("/data/su_test"); Log.i(TAG, "strRead=" + strRead); if (fileContent.equals(strRead)) { return true; } else { return false; } } catch (Exception e) { Log.i(TAG, "Unexpected error - Here is what I know: " + e.getMessage()); return false; } } // Write file public static Boolean writeFile(String fileName, String message) { try { FileOutputStream fout = new FileOutputStream(fileName); byte[] bytes = message.getBytes(); fout.write(bytes); fout.close(); return true; } catch (Exception e) { e.printStackTrace(); return false; } } // Read file public static String readFile(String fileName) { File file = new File(fileName); try { FileInputStream fis = new FileInputStream(file); byte[] bytes = new byte[1024]; ByteArrayOutputStream bos = new ByteArrayOutputStream(); int len; while ((len = fis.read(bytes)) > 0) { bos.write(bytes, 0, len); } String result = new String(bos.toByteArray()); Log.i(TAG, result); return result; } catch (Exception e) { e.printStackTrace(); return null; } }}
We will implement the above detection methods as code; I will not demonstrate it here; everyone can operate it themselves:
CheckRoot.java code:
package com.example.testpoc4; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.util.ArrayList; import android.util.Log; public class CheckRoot { // Define TAG constant private static String TAG = CheckRoot.class.getName(); public static boolean isDeviceRooted() { if (checkDeviceDebuggable()) { return true; } // check buildTags if (checkSuperuserApk()) { return true; } // Superuser.apk if (checkRootPathSU()) { return true; } // find su in some path if (checkRootWhichSU()) { return true; } // find su use 'which' if (checkBusybox()) { return true; } // find busybox if (checkAccessRootData()) { return true; } // find access to /data if (checkGetRootAuth()) { return true; } // exec su return false; } // Check if SuperSU.apk file exists public static boolean checkSuperuserApk() { try{ File file = new File("/system/app/SuperSU/SuperSU.apk"); if (file.exists()){ Log.w(TAG, "/system/app/SuperSU/SuperSU.apk exist"); return true; } } catch (Exception e){ } return false; } public static boolean checkDeviceDebuggable() { String buildTags = android.os.Build.TAGS; if (buildTags != null && buildTags.contains("test-keys")) { Log.i(TAG, "buildTags=" + buildTags); return true; } return false; } public static boolean checkRootPathSU() { File f = null; final String kSuSearchPaths[] = {"/system/bin/", "/system/xbin/", "/system/sbin/", "/sbin/", "/vendor/bin/"}; try { for (int i = 0; i < kSuSearchPaths.length; i++) { f = new File(kSuSearchPaths[i] + "su"); if (f != null && f.exists()) { Log.i(TAG, "find su in : " + kSuSearchPaths[i]); return true; } } } catch (Exception e) { e.printStackTrace(); } return false; } public static boolean checkRootWhichSU() { String[] strCmd = new String[]{"/system/xbin/which", "su"}; ArrayList<String> execResult = executeCommand(strCmd); if (execResult != null) { Log.i(TAG, "execResult=" + execResult.toString()); return true; } else { Log.i(TAG, "execResult=null"); return false; } } public static ArrayList<String> executeCommand(String[] shellCmd) { String line = null; ArrayList<String> fullResponse = new ArrayList<String>(); Process localProcess = null; try { Log.i(TAG, "to shell exec which for find su :"); localProcess = Runtime.getRuntime().exec(shellCmd); } catch (Exception e) { return null; } BufferedWriter out = new BufferedWriter(new OutputStreamWriter(localProcess.getOutputStream())); BufferedReader in = new BufferedReader(new InputStreamReader(localProcess.getInputStream())); try { while ((line = in.readLine()) != null) { Log.i(TAG, "–> Line received: " + line); fullResponse.add(line); } } catch (Exception e) { e.printStackTrace(); } Log.i(TAG, "–> Full response was: " + fullResponse); return fullResponse; } public static synchronized boolean checkGetRootAuth() { Process process = null; DataOutputStream os = null; try { Log.i(TAG, "to exec su"); process = Runtime.getRuntime().exec("su"); os = new DataOutputStream(process.getOutputStream()); os.writeBytes("exit\n"); os.flush(); int exitValue = process.waitFor(); Log.i(TAG, "exitValue=" + exitValue); if (exitValue == 0) { return true; } else { return false; } } catch (Exception e) { Log.i(TAG, "Unexpected error - Here is what I know: " + e.getMessage()); return false; } finally { try { if (os != null) { os.close(); } process.destroy(); } catch (Exception e) { e.printStackTrace(); } } } public static synchronized boolean checkBusybox() { try { Log.i(TAG, "to exec busybox df"); String[] strCmd = new String[]{"busybox", "df"}; ArrayList<String> execResult = executeCommand(strCmd); if (execResult != null) { Log.i(TAG, "execResult=" + execResult.toString()); return true; } else { Log.i(TAG, "execResult=null"); return false; } } catch (Exception e) { Log.i(TAG, "Unexpected error - Here is what I know: " + e.getMessage()); return false; } } public static synchronized boolean checkAccessRootData() { try { Log.i(TAG, "to write /data"); String fileContent = "test_ok"; Boolean writeFlag = writeFile("/data/su_test", fileContent); if (writeFlag) { Log.i(TAG, "write ok"); } else { Log.i(TAG, "write failed"); } Log.i(TAG, "to read /data"); String strRead = readFile("/data/su_test"); Log.i(TAG, "strRead=" + strRead); if (fileContent.equals(strRead)) { return true; } else { return false; } } catch (Exception e) { Log.i(TAG, "Unexpected error - Here is what I know: " + e.getMessage()); return false; } } // Write file public static Boolean writeFile(String fileName, String message) { try { FileOutputStream fout = new FileOutputStream(fileName); byte[] bytes = message.getBytes(); fout.write(bytes); fout.close(); return true; } catch (Exception e) { e.printStackTrace(); return false; } } // Read file public static String readFile(String fileName) { File file = new File(fileName); try { FileInputStream fis = new FileInputStream(file); byte[] bytes = new byte[1024]; ByteArrayOutputStream bos = new ByteArrayOutputStream(); int len; while ((len = fis.read(bytes)) > 0) { bos.write(bytes, 0, len); } String result = new String(bos.toByteArray()); Log.i(TAG, result); return result; } catch (Exception e) { e.printStackTrace(); return null; } }}
MainActivity.java code:
package com.example.testpoc4; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); boolean deviceRoot = CheckRoot.isDeviceRooted(); ((TextView) findViewById(R.id.text)).setText("Device Root:" + deviceRoot); } }
Reference Links:
https://blog.csdn.net/weixin_47883636/article/details/108687059
https://www.jianshu.com/p/8a9b84df5018
https://blog.chrxw.com/archives/2020/07/18/1301.html
https://bbs.pediy.com/thread-263203.htm
https://github.com/yunshuipiao/Potato/issues/53
https://juejin.cn/post/6844903733248131079
https://github.com/DeFuture/Superuser
https://blog.csdn.net/quanshui540/article/details/48242459
https://github.com/Labmem003/anti-counterfeit-android
https://github.com/t0thkr1s/allsafe
Recommended Reading
Basic Android Anti-Reverse Engineering
Android Penetration Testing Tools
Common Android Screen Casting Tools
Objection Dynamic Analysis of Apps
Android JNI Dynamic Library Reverse Engineering
Learn Packet Capture with “Dora Security”
Using eBPF in Android Systems
Comprehensive List of Android Security Testing Tools
Performance Monitoring of Android Security IO
Application of ART in Android Security Offense and Defense
One-on-One Technical Consulting Services (Remote Guidance)
Application of Linux Kernel Monitoring in Android Offense and Defense
ASM Instrumentation to Achieve Point-Free Performance Monitoring on Android
Framework for Reverse Analysis/Security Testing/Penetration Testing of Android and iOS
eCapture is based on eBPF technology to achieve user-space data capture without CA certificate to capture HTTPS network plaintext communication.