Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
android-badbinder-demo — demo CVE-2019-2215 (Bad Binder) for Android Q | Kitploit
Tools/GitHubGitHub/i-redbyte/android-badbinder-demo
Android SecurityPrivilege EscalationExploitationLearning & EducationBinary Exploitation
GitHubi-redbyte/android-badbinder-demo

android-badbinder-demo

demo CVE-2019-2215 (Bad Binder) for Android Q

View Repository
511410 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2019-2215 (Bad Binder) — Exploit Analysis

This repository is a small test project for researching the vulnerability
CVE-2019-2215 (Bad Binder) and writing a working prototype of an exploit for Android with a simple graphical interface in Kotlin/Jetpack Compose.

In the README I:

  1. Describe the environment setup and running the exploit prototype.
  2. Analyze the main stages of exploiting CVE-2019-2215 and map them to specific functions in the C code.
  3. List separately the difficulties I encountered along the way and how I solved them.

Ready APK (GitHub Actions)

The repository has a GitHub Actions workflow configured that, on every push/PR, builds the project with the command ./gradlew assembleDebug and publishes the finished badbinder-debug.apk as an artifact.

You can download it like this:

  1. Open the Actions tab in the repository.
  2. Select the desired workflow run.
  3. At the bottom of the page, find the Artifacts section and take the archive badbinder-debug-apk with the built APK.

This is done for convenience, if you just want to test the application without setting up a local environment.


Briefly about the vulnerability

CVE-2019-2215 is a Use-After-Free (UAF) in the Binder IPC subsystem of the Android kernel.

Simplified:

  • in the kernel there is a structure struct binder_thread that describes a thread performing Binder calls;
  • this structure can be freed, but under a certain sequence of calls it still remains in the wait queues (waitqueue);
  • later the kernel tries to work with already freed memory in remove_wait_queue, which opens a classic UAF scenario;
  • if you carefully choose the environment and subsequent allocations, you can force the kernel to read/write to arbitrary addresses, and then obtain kernel privileges, and then root in userspace.

I made a more detailed theoretical breakdown based on the materials from:

  1. https://cloudfuzz.github.io/android-kernel-exploitation/
  2. https://dayzerosec.com/blog/2019/11/07/analyzing-androids-cve-2019-2215-dev-binder-uaf.html
  3. https://hernan.de/blog/tailoring-cve-2019-2215-to-achieve-root/

1. Environment setup and running the exploit prototype

1.1. Selecting and preparing a virtual device

The assignment recommends using an AVD with Android 10.0 (Q) x86_64 image.
I did the following:

  1. Created an AVD in Android Studio (Pixel device, Android 10 (Q), x86_64).
  2. Made sure the image has Binder enabled and the /dev/binder device exists.
  3. Enabled USB/ADB debugging and verified access to the device:
    adb shell
    ls -l /dev/binder
    

At this stage I ran into an unpleasant fact:
currently, the current AVD images already come with a patched kernel where CVE-2019-2215 is fixed. That is, you won't actually be able to get root on a modern official emulator — the exploit fails at later stages or simply doesn't give privilege escalation.

In the end, I use the AVD as a simulator to reproduce the exploit logic:

  • I get the same sequences of system calls,
  • observe UAF attempts, address leaks, and the attempt to overwrite addr_limit,
  • but the final "getting root" on the current patched kernel naturally does not work (and that is expected).

This is an important nuance: all code and report below are educational, not "combat".


1.2. Building an Android application with a native exploit

I made a small Android application:

  • UI on Kotlin + Jetpack Compose,
  • Native part in C via JNI — the exploit code itself,
  • communication between them — via a JNI callback, so that strings from the C code fly directly into the UI.

Main steps:

  1. Created a regular project in Android Studio (Kotlin, minimum API Android 10).

  2. Added NDK and CMake.

  3. Added a native file with the exploit (that same cve-2019-2215.c with functions leak_task_struct, overwrite_addr_limit, etc.).

  4. In CMakeLists.txt added the build of libcve-2019-2215.so.

  5. In MainActivity:

    init {
        System.loadLibrary("cve-2019-2215")
    }
    
    external fun runNativeExploit(): String
    external fun setNativeLogger(logger: NativeLogger)
    
  6. On the Kotlin side, I made an ExploitViewModel that implements the NativeLogger interface and puts all messages into a StateFlow<List<String>>. The UI subscribes to this flow and displays the log in a "terminal".

When the activity starts, I call setNativeLogger(viewModel), so that the native code gets an object to which it can send strings.


1.3. Launch and usage scenario

  1. Build and install the application:

    ./gradlew installDebug
    
  2. Start the AVD and the application itself.

  3. On the screen I see a "terminal" and a button RUN EXPLOIT.

  4. When pressed:

    • Kotlin calls runNativeExploit() in a background thread.
    • C code begins executing all stages of the exploit and logs the steps.
    • Via the JNI callback, the log reaches the ViewModel and is displayed in the Compose UI.

On a real vulnerable kernel, I would expect to see something like at the end:

[+] Selinux changed: Permissive now.
[+] Root escalation successful!
uid=0(root)...

On the current Android 10 emulator this, of course, does not happen, but everything else — leak of task_struct, attempt to overwrite addr_limit, calculation of cred and kernel_base — works as a "scenario", which is what was required for the assignment.


2. Analysis of the main stages of the exploit and mapping to code

Below is the logical scheme of the exploit with reference to specific C functions.

2.1. General exploit scenario

The high-level plan is:

  1. Create a UAF on the struct binder_thread object and use it to leak the address of task_struct of my process (leak_task_struct).
  2. With a second UAF cycle and carefully selected structures overwrite the addr_limit field in task_struct (overwrite_addr_limit) — this removes the restriction between user-space and kernel-space addresses for subsequent copy_to_user / copy_from_user.
  3. Using pipes, implement arbitrary read/write of any kernel memory (arb_read / arb_write).
  4. Using this, find the cred of the current process and the kernel base (verifying), then:
    • disable SELinux (selinux_enforcing = 0),
    • overwrite the cred fields to become root and get the full set of capabilities (runNativeExploit).

In parallel, I integrated a JNI logger, so that all these stages are visible directly in the UI.


2.2. Stage 1 — leaking the address of task_struct (leak_task_struct)

Key function:

void leak_task_struct() {
    android_log("[*] Starting leak_task_struct...");

    cpu_set_t cpu_set;
    CPU_ZERO(&cpu_set);
    CPU_SET(0, &cpu_set);
    ret = sched_setaffinity(0, sizeof(cpu_set), &cpu_set);
    assert(ret >= 0);
    ...
}

What the function does:

  1. Pins the thread to CPU 0 (sched_setaffinity) to make kernel allocator behavior more predictable. This improves UAF exploit stability.

  2. Opens /dev/binder, creates an epoll descriptor:

    fd = open("/dev/binder", O_RDONLY);
    epfd = epoll_create(1000);
    

    The binder descriptor is registered in epoll:

    epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
    
  3. Prepares an array struct iovec iov_buffers[IOVEC_N] and allocates memory:

    spinner = mmap((void *)0x100000000, page_size, PROT_READ | PROT_WRITE,
                   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    

    Here it is important that the lower 32 bits of the address are zero:

    if (((long) spinner & 0xffffffff) != 0) {
        android_log("[!] mmap returned wrong address!");
        return;
    }
    
Download Tool