People have a tendency to install and enable things that they do not fully understand. In this article I will try to explain just what Root and Xposed Bridge is and how dangerous it can be. Don't get me wrong, I love both of these things, but it is important to be careful and not grant just any application these types of rights.
Let's picture a small village. In the middle of this village is a large bank. It is surrounded with a lot of smaller buildings. The bank represents the core Android system while the buildings are all of the Applications. All the people within each building can communicate with one another since they are all within the same building. If a building needs to communicate with another building, it has to send someone across town to the other building. It is then up to the people in that building to decide whether or not they want to let that person inside. While inside, the person can make a request or a delivery. Maybe he wants to borrow some sugar. He then delivers the response (in this case the sugar) back to his building. The same applies for the bank which is surrounded by guards. To make a request for a specific item in the bank, a building will need a permission slip for that item in order to bypass the guard watching it.
One building however has an Xposed Module implemented. In this case it is a specific type of person, I spy if you will. The building can send this person over to the bank and make him act as a guard. The bank and it's other guards will not not sense that anything is wrong. This spy can now move around in any section of the bank without any permission slip. He can steel items, place new items or make changes to the existing once without anyone asking questions. He can also disguise himself as a member of other buildings and walk around those without an invite. This spy is essentially a god amongst men.
In Android it allows applications to provide features not normally available, like changing theme and colors in any part of the system, create security modules that can restrict other applications from gathering specific information and much more. But it also allows application to do things that you might not want it to, like gathering information and uploading it to a server. Since the module can do whatever it wants, there is no way to restrict it.
Root is similar. It is the main built-in Administrator in the Linux kernel (Which Android is built on top of). In this case it acts as the emperor of the village. It is the main authority in the system and no one would dare to tell it no. It can move, do and behave just as it feels like without no one trying to stop it. The most important thing to note here is that Xposed Modules is able to acts as root, even if the device is not rooted. It is also important to note that gaining root via Xposed Bridge will not trigger your normal Root Popup window on rooted devices. So you will not even know that this has happened.
There is no doubt that devices with Xposed Bridge and Root enabled are much more fun. This article is not meant to scare anyone from rooting their devices or install Xposed Bridge on them. It is meant to inform people about the danger of doing so to make them more aware next time they enable an Xposed module or grant root to an application asking for it. Make sure that the application in question can be trusted, which most importantly mean that you should not allow this for Closed Source applications. If the source codes are close, there is no telling what has been implemented into the application.
So the next time you think about enabling an application in Xposed Bridge or grant root to an application, do some research first. Make sure that you can find a link to the source codes, make sure that the developer is contactable, do some searches to make sure that others have not warned about this application.
In any case, do not just blindly enable whatever the application asks for.
Wednesday, May 27, 2015
Tuesday, April 7, 2015
Check if current user is owner
One would think that this is a simple task in Android, especially since one would expect Google to have added some kind of tool for it, like something to return the current user id. Well they have, but they also decided to hide it. Android actually has a very pore collection of tools when it comes to working with multi-users. Like many things in the framework, Google did not think that apps had any reason to access to any information regarding the users. Android's sources might be fully open, but the framework is more closed than boot loaders on HTC devices.
The user id is normally not a very important information since it is nothing more than a number from 10 and up, except for the owner which have 00. The only thing that this number can tell you, is which user was created first. But checking for the 00 id to identify the owner can be very helpful. You may be working on an app that should have certain restrictions when not used by the device owner. Maybe your app should not be used by other users at all. In any case identifying the owner is useful enough that Google should have added something like isOwner() to the UserManager service.
Searching the web, it seams like people are using a lot of reflection to access some of the hidden classes in order to identify the device owner. However reflection is not really needed, and it should be avoided if possible, simply because hidden classes are not official and might change over time. If this happens, apps need to be updated or they will be broken on future versions of Android.
Even though Android did not directly provide any access to the current user id, they did indirectly do this in one way, namely the app data folder. In Android 4.1 and below, all app data was placed in /data/data, but in newer versions they are placed in /data/user/[userid] and all apps have access to their own data folder. So to get the current user id from within an app, you only need to get the name of the parent folder to your apps data location. This is very simple.
MainActivity.java:
The above code is even backward compatible with Android version without multi-user support, without any need to check the current API level. And we did not use any reflection or unsupported classes/methods.
The user id is normally not a very important information since it is nothing more than a number from 10 and up, except for the owner which have 00. The only thing that this number can tell you, is which user was created first. But checking for the 00 id to identify the owner can be very helpful. You may be working on an app that should have certain restrictions when not used by the device owner. Maybe your app should not be used by other users at all. In any case identifying the owner is useful enough that Google should have added something like isOwner() to the UserManager service.
Searching the web, it seams like people are using a lot of reflection to access some of the hidden classes in order to identify the device owner. However reflection is not really needed, and it should be avoided if possible, simply because hidden classes are not official and might change over time. If this happens, apps need to be updated or they will be broken on future versions of Android.
Even though Android did not directly provide any access to the current user id, they did indirectly do this in one way, namely the app data folder. In Android 4.1 and below, all app data was placed in /data/data, but in newer versions they are placed in /data/user/[userid] and all apps have access to their own data folder. So to get the current user id from within an app, you only need to get the name of the parent folder to your apps data location. This is very simple.
MainActivity.java:
public class Utils {
public static boolean isOwner(Context context) {
/*
* Get the parent location.
* This can either be /data/user/[userid] or /data/data depending on Android version.
*/
File file = new File(context.getApplicationInfo().dataDir).getParentFile();
/*
* Get the name of the folder in the parent location.
* This can either be [userid] or data
*/
String user = file.getName();
try {
/*
* Returns TRUE if this user has the id 0 (device owner)
*/
return Integer.valueOf(user) == 0;
} catch (NumberFormatException e) {
/*
* The user variable contained "data".
* This is not an multi-user environment, which means that we only have the device owner available.
*/
return true;
}
}
}
The above code is even backward compatible with Android version without multi-user support, without any need to check the current API level. And we did not use any reflection or unsupported classes/methods.
Friday, March 20, 2015
JNI / Android NDK
If you visit the Android developer page and read about Android's NDK, it will seam as if Google is trying to scare people from using it and just stick with Java. In most cases this will properly be the best idea. Java is a great language and does a good job for most tasks. However JNI is not as dangerous as Google is trying to make it. Most of Android is build on JNI and IPC. Information is being parsed in and out of JVM and between processes constantly, so why should applications not take advantage of this as well?
One thing to remember about JNI is that it creates a bit of overload to parse in and out of JVM. But depending on the task, this is not necessarily enough downside to keep away from it. The best way to figure out whether or not to use C/C++ or Java, is to test your task in both and then compare the result.
One good example of a task better suited for JNI could be some extensive file operations. For this example we will collect the content of the stat file for all currently running processes on a device. On my device running Android 5, we are talking about rounded 300 files. We add this to a loop which will run 100 times which in turn will create 3000 file read operations.
The first thing we need, is a basic activity to run our examples in.
MainActivity.java:
We also need a Java method to do the collection work. We will separate the native method and the Java method into two files in this example. It makes it easier to keep an overview.
JavaCollector.java:
And we cannot compare the above Java class to JNI without a native example as well.
NativeCollector.java:
collector.cpp:
If we compile and run this application, we will get the following result:
So get started with:
One thing to remember about JNI is that it creates a bit of overload to parse in and out of JVM. But depending on the task, this is not necessarily enough downside to keep away from it. The best way to figure out whether or not to use C/C++ or Java, is to test your task in both and then compare the result.
One good example of a task better suited for JNI could be some extensive file operations. For this example we will collect the content of the stat file for all currently running processes on a device. On my device running Android 5, we are talking about rounded 300 files. We add this to a loop which will run 100 times which in turn will create 3000 file read operations.
The first thing we need, is a basic activity to run our examples in.
MainActivity.java:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
StringBuilder builder = new StringBuilder();
for (int i=0; i < 2; i++) {
double start = System.nanoTime();
int files = 0;
for (int x=0; x < 100; x++) {
String[] list = null;
switch (i) {
case 0: list = JavaCollector.collect(); break;
case 1: list = NativeCollector.collect();
}
files += list.length;
}
double end = System.nanoTime();
builder.append(i == 0 ? "JavaCollector" : "NativeCollector\n");
builder.append("Files = " + files + "\n");
builder.append("Time = " + ((end - start) * Math.pow(10, -9)) + " seconds\n");
builder.append("\n");
}
TextView view = (TextView) findViewById(R.id.textview);
view.setText(builder.toString());
}
}
We also need a Java method to do the collection work. We will separate the native method and the Java method into two files in this example. It makes it easier to keep an overview.
JavaCollector.java:
public class JavaCollector {
/*
* Re-defined regexp to match directories in /proc with process id's
*/
protected static final Pattern REFEXP_PID = Pattern.compile("^[0-9]+$");
/*
* The method that collects all the file data
*/
public static String[] collect() {
/*
* Get a listing from the /proc directory
*/
String[] procListing = new File("/proc").list();
/*
* Define our input stream
*/
BufferedReader in = null;
/*
* Create a temp container for the file output
*/
ArrayList<String> lines = new ArrayList<String>();
/*
* Handle each entity in /proc
*/
for (String procEntity : procListing) {
/*
* We only want the pid directories
*/
if (REFEXP_PID.matcher(procEntity).matches()) {
try {
/*
* Open the sub directory stat file
*/
in = new BufferedReader(new FileReader("/proc/" + procEntity + "/stat"));
/*
* Get the content of the stat file
*/
lines.add(in.readLine());
} catch (IOException e) {} finally {
if (in != null) {
try {
/*
* Close the file
*/
in.close();
in = null;
} catch (IOException e) {}
}
}
}
}
/*
* Create and return a string array
*/
return lines.toArray( new String[ lines.size() ] );
}
}
And we cannot compare the above Java class to JNI without a native example as well.
NativeCollector.java:
public class NativeCollector {
/*
* Load our collector library
*/
static {
System.loadLibrary("collector");
}
/*
* This is really a call to Java_com_example_NativeCollector_collect() in collector.cpp
*/
public static native String[] collect();
}
collector.cpp:
JNIEXPORT jobjectArray JNICALL Java_com_example_NativeCollector_collect(JNIEnv *env, jobject thisObj) {
/*
* Open /proc
*/
DIR* procDirectory = opendir("/proc");
if (procDirectory != NULL) {
/*
* Create a temp container with minimum 100 indexes pre-allocated
*/
std::vector<std::string> lines(100);
/*
* Create an input stream
*/
std::ifstream in;
/*
* Create a variable for the proc entities
*/
struct dirent* procEntity;
/*
* Handle each entity in /proc
*/
while ((procEntity = readdir(procDirectory)) != NULL) {
/*
* We only want the pid directories
*/
if (std::regex_match (procEntity->d_name, std::regex("^[0-9]+$") )) {
/*
* Open the sub directory stat file
*/
std::string path = std::string("/proc/") + procEntity->d_name + "/stat";
in.open( path.c_str() );
if (in && in.good()) {
/*
* Get the content of the stat file
*/
std::string line;
std::getline(in, line);
lines.push_back(line);
}
/*
* Close the file
*/
if (in) {
in.close();
}
}
}
/*
* Create Java return data
*/
if (lines.size() > 0) {
/*
* Create a Java array
*/
jobjectArray ret = env->NewObjectArray(lines.size(), env->FindClass("java/lang/String"), NULL);
for (int i=0; i < lines.size(); i++) {
/*
* Place the line in a Java String
*/
jstring stringObject = env->NewStringUTF( lines[i].c_str() );
/*
* Add the Java String to the Java Array
*/
env->SetObjectArrayElement(ret, i, stringObject);
/*
* Release the Java String reference.
*
* Note: This is important. We can only have a limited ammount of Java objects
* at a time and they are not auto released until we return to the JVM. And since we
* are looping an unknown, but large, number of files, we could end up with a memory overflow.
*/
env->DeleteLocalRef(stringObject);
}
/*
* Return the array to JVM
*/
return ret;
}
}
/*
* Return an empty array
*/
return env->NewObjectArray(0, env->FindClass("java/lang/String"), NULL);;
}
If we compile and run this application, we will get the following result:
- JavaCollector$collect: 21.2 seconds
- NativeCollector$collect: 2.7 seconds
This is not just slightly faster than Java, this is 87% faster.
This is only a simple example. If it should be used in a real application, then depending on the amount of files and amount of data, it might be a good idea to make some sort of iteration mechanism to avoid memory overflow. But as the result shows, it is sometimes a good idea to check whether or not a JNI solution might be better. Java is not always the correct solution.
So get started with:
- Learning C/C++
- Learning JNI
- Setup your NDK
- Bookmark the C++ manual and the JNI manual
Subscribe to:
Posts (Atom)