
Elevação de privilégio local proof-of-concept para CVE-2023-20938, um use-after-free no binder do Android, alcançando root e desabilitando o SELinux em builds de teste vulneráveis.
Uma prova de conceito de escalada local de privilégios para CVE-2023-20938, um use-after-free no driver binder do Android. A partir de um shell sem privilégios, ela obtém uid 0 e rebaixa o SELinux para permissive, e então inicia um shell root.
Alvo desta compilação: Android Cuttlefish / Goldfish AVD, x86_64, kernel 5.10.107.
⚠️ Somente para pesquisa autorizada / uso educacional. Execute isto apenas contra seus próprios dispositivos de teste e emuladores. O bug foi corrigido no Android atual; esta PoC tem como alvo uma compilação de teste antiga e deliberadamente vulnerável. Sem garantia.

Gravado com asciinema e renderizado em GIF com agg:
drivers/android/binder.c, binder_transaction_buffer_release()binder_transaction_buffer_release() confia em um offsets_size fornecido pelo atacante. Enviar uma transação cujo offsets_size esteja um byte aquém de uma entrada completa (sizeof(offset) - 1 == 7) faz com que o caminho de liberação interprete incorretamente o flat_binder_object final e decremente em excesso o refcount de um binder_node, liberando-o enquanto uma referência pendente (dangling) ainda está ativa.
O exploit encadeia um vazamento cross-cache em uma leitura arbitrária e uma escrita controlada e, em seguida, reescreve as credenciais. Estágio por estágio (veja main() em poc_cf.c):
B. Cross-cache leak
free a wave of binder_nodes (kmalloc-128) -> page returns to the buddy
allocator -> reclaim the page with eventpoll epitems (eventpoll_epi cache,
0x80-byte objects). A dangling binder transaction now reads kernel
pointers out of an epitem, leaking a struct file and its epitem address.
C. Arbitrary read
re-trigger the UAF and use a sprayed fake binder_node to turn binder's
hlist_del unlink into a controlled *(where) = what write. Two writes
redirect a victim file's f_inode at a fake inode overlaid on the epitem
and mark it S_PRIVATE. FIGETBSZ on the file then returns
*(epitem.event.data + 24): a steerable 4-byte read.
D/E/F. Privilege escalation
defeat KASLR (leak eventfd_fops via f_op), walk init_task's task list to
the launcher's cred, zero its uid/gid set and seccomp.mode, then zero
selinux_state to drop SELinux to permissive.
G. A pre-forked, eventfd-free launcher execs a root shell.
Dois processos sem privilégios precisam compartilhar um nó binder para acionar o UAF; Através do ITokenManager do hwservicemanager (createToken /get-by-token).
clang -static -Wall -Wextra -o poc_cf poc_cf.c -lpthread
O binder.h deve estar ao lado de poc_cf.c. A compilação é estática de propósito para que o binário único rode no alvo sem toolchain presente.
No diretório do pacote host do Cuttlefish, inicie o AVD com o kernel + initramfs correspondentes:
HOME=$PWD ./bin/launch_cvd \
--kernel_path=bzImage \
--initramfs_path=initramfs.img
adb push poc_cf /data/local/tmp/
adb shell /data/local/tmp/poc_cf
Uma execução bem-sucedida percorre os estágios e termina em um shell root:
O exploit é probabilístico. Cada estágio faz novas tentativas internamente.
Portanto, quer você veja uma saída limpa ou um panic do guest, basta executar novamente (o /data do AVD é apagado na reinicialização; portanto, reenvie o binário após um panic).
Se a taxa de acerto do reclaim for baixa em um alvo mais lento ou mais ocupado, aumente os tempos de espera ou eleve a quantidade de spray (os #defines perto do topo de poc_cf.c):
Os offsets das structs e a linha de base (baseline) de símbolos não randomizados (*_NOKASLR) são específicos desta compilação exata 5.10.107 e não funcionarão em outro lugar. Para portar para um kernel diferente, atualize o bloco de offsets e o bloco de símbolos perto do topo de poc_cf.c com valores do vmlinux correspondente. Testei no Pixel 7 após extrair os offsets.
| Macro | Padrão | Significado |
|---|
SETTLE_US | 5000 | pausa após o nó ser liberado, antes do spray |
FAKE_SPRAY_COUNT | 8 | sendmsg()s bloqueados disputando o reclaim do slot |
FAKE_WAIT_US | 10000 | pausa após liberar os sprays, antes do unlink |
PRESKIP_US | 1000 | pausa após o primeiro sendmsg de sacrifício |
KW_RETRY_COUNT | 8 | tentativas por escrita de unlink único |
STAGE_C_TRIES | 8 | tentativas para ativar a leitura arbitrária |