loading...

Building for Android SuperUsers: Let's extract WiFi passwords

idoko profile image Michael Okoko ・5 min read

In the beginning was a Manifest permission entry, and big G tagged it android.permission.ACCESS_SUPERUSER, and it was okay. But then Android Lollipop came, and the simple snippet was removed, and Chainfire's superuser library came to the rescue.

What is libsuperuser?

Libsuperuser is an android library that makes requesting and maintaining root access in your app as easy as you could ever imagine. We will be merely implementing it here in this demo app but if you feel like digging deeper (of course you should), you can check out the full HOW-to SU post

What are we building?

We will be creating an app named Fi.Sniff. Android stores it's wireless networks data - SSID, Key, etc - in a file named wpa_supplicant.conf in the /data/misc/wifi/ directory which can only be accessed by the root user. This is the file we will be exploring and parsing once the user gives us root access in the Fi.Sniff app.
Before we get started, Let's make sure we have android studio and the SDKs installed and working as well as a rooted android phone. Emulators won't work here.

Let's get coding!

Open up android studio and create a new android project. Once your project is done and android studio is ready to rock, we need to add two dependencies, the first is the libsuperuser and then, android's recyclerview library to handle our list. So open your app/build.gradle and add the following within the dependencies block:

implementation 'eu.chainfire:libsuperuser:1.0.0.+'
implementation 'com.android.support:recyclerview-v7:26.0.0'

Now getting to the meat of our application, let's edit the layout file associated with our main activity (I am assuming activity_main.xml here) and replace the content with the following:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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="com.okmichaels.fisniff.MainActivity">

    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/network_list_recycler_view">

    </android.support.v7.widget.RecyclerView>

</android.support.constraint.ConstraintLayout>

For the above code block as well as all subsequent ones, be sure to replace the package name with your app's. With that done, let's sprinkle some recycler view adapter and its variety of ingredients. But before we can dive into the adapter, let's create a layout that will reflect each of our wifi item on our user's screen. Create a new layout file (network_item_layout) and add the code below:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/network_ssid_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="14sp"
        android:textStyle="bold"/>

    <TextView
        android:id="@+id/network_psk_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

For the adapter, we will be creating a new class named NetworkListAdapter as well as an associated inner ViewHolder class (NetworkViewHolder). Our adapter class should look like this when we are done:

package com.okmichaels.fisniff;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.List;

public class NetworkListAdapter extends RecyclerView.Adapter<NetworkListAdapter.NetworkViewHolder> {

    private List<Network> networkList;
    private Context context;

    public NetworkListAdapter(Context ctx, List<Network> list) {
        networkList = list;
        context = ctx;
    }

    public int getItemCount() {
        return networkList.size();
    }

    public NetworkViewHolder onCreateViewHolder(ViewGroup parent, int viewtype) {
        final View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.network_item_layout, parent, false);
        return new NetworkViewHolder(view);
    }

    public void onBindViewHolder(NetworkViewHolder holder, int position) {
        Network network = networkList.get(position);
        holder.ssidView.setText(network.getSsid());
        holder.pskView.setText(network.getPsk());
    }

    class NetworkViewHolder extends RecyclerView.ViewHolder
    {
        TextView ssidView;
        TextView pskView;

        public NetworkViewHolder(View itemView) {
            super(itemView);
            ssidView = itemView.findViewById(R.id.network_ssid_view);
            pskView = itemView.findViewById(R.id.network_psk_view);
        }
    }

    public void refreshList(List<Network> networks) {
        this.networkList.clear();
        this.networkList.addAll(networks);
        this.notifyDataSetChanged();
    }
}

PS: If the whole RecyclerView and ViewHolder pattern thingy seems foreign to you, try looking up the official documentation here or this awesome tutorial by
Another PS: If you get squigly red lines beneath the Network class in the above, Farabale - we are coming to that soon.

Next, we need to create a data class (ViewModel in android lingo). Basically, this class reflects the items to be displayed in the adapter so create a new Network class and add the code below:

package com.okmichaels.fisniff;

public class Network {
    private String ssid;
    private String psk;

    public Network() {
        this.ssid = null;
        this.psk = null;
    }

    public void setSsid(String ssid) {
        this.ssid = ssid;
    }

    public void setPsk(String psk) {
        this.psk = psk;
    }

    public Network(String ssid, String key) {
        this.ssid = ssid;
        this.psk = key;
    }

    public String getSsid() {
        return this.ssid;
    }

    public String getPsk() {
        return this.psk;
    }
}

Now to our main activity. This is where stuff gets interesting. You remember that superuser library you added? This is where you get to make it work but hang on! Modify your Main Activity file to look like this first: Android Studio might whine about unused imports blah blah blah, ask it to drink water and proceed.

package com.okmichaels.fisniff;

import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import eu.chainfire.libsuperuser.Shell;

public class MainActivity extends AppCompatActivity {

    public RecyclerView networkListView;
    NetworkListAdapter adapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        networkListView = findViewById(R.id.network_list_recycler_view);
        networkListView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
        adapter = new NetworkListAdapter(getApplicationContext(),
                new ArrayList<Network>());
        networkListView.setAdapter(adapter);
    }
}

Now back to the shell. Good news is this is probably the easiest part. Bad news is you don't run SU commands on the main thread plus it returns an ArrayList of Strings where each element is a line in the command's return value. Anyway, let's add it up and be done with right? Add a new AsyncTask child within your main activity and let it mirror the code as shown: (Mine is named NetworkFetcherTask and I've added comments where necessary to shed light on what we are trying to do)

class NetworkFetcherTask extends AsyncTask<Void, Void, List<Network>>
    {
        private boolean suEh;
        @Override
        protected List<Network> doInBackground(Void... voids) {
            List<Network> networks = new ArrayList<>();
            List<String> supplicant;
            /*this is what triggers the SuperUser permission on the device, if the phone is not rooted or the permissioin is denied, it returns false.*/
            suEh = Shell.SU.available();
            if (suEh) {
            /*android stores wifi data in the wpa_supplicant.conf file in the location below, let's try printing it 🙏🙏*/
                supplicant = Shell.SU.run("cat /data/misc/wifi/wpa_supplicant.conf");
            /*the return value of SU.run() is an array list, let's flatten it into a string to ease parsing with regex*/
                StringBuilder flattener = new StringBuilder();
                if (supplicant != null) {
                    for (String line: supplicant) {
                        flattener.append(line);
                    }
                }
                String flattened = flattener.toString();

                /*okay, let's find every network block(they exist within curly brackets hence the expression*/
                String exp = "\\{(.*?)\\}";
                Pattern pattern = Pattern.compile(exp);
                Matcher matcher = pattern.matcher(flattened);

                while (matcher.find()) {
                    Network network = new Network();
                    String el = matcher.group();
                    //List<String> lineAsItem = Arrays.asList(el.split(" "));
                    String[] lineAsItem = el.split("\\s+");
                    for(String line: lineAsItem) {

                        if (line.contains("=")) {
                            String[] t = line.split("=");
                            if (t[0].equals("ssid")) {
                                network.setSsid(t[1]);
                            }
                            if (t[0].equals("psk")) {
                                network.setPsk(t[1]);
                            }
                        }
                    }
                    networks.add(network);
                }
            }
            return networks;
        }

        protected void onPostExecute(List<Network> result) {
            adapter.refreshList(result);
        }
    }

One more thing. We need to ask our MainActivity (nicely) to execute this async task so add the following in your MainActivity's onCreate() method to call the task once the activity is setup:

        new NetworkFetcherTask().execute();

Run your application and see the glory!

Discussion

pic
Editor guide