• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Побег из песочницы Chrome через состояние гонки IndexedDB

yashechka

Генератор контента.Фанат Ильфака и Рикардо Нарвахи
Эксперт
Регистрация
24.11.2012
Сообщения
2 344
Реакции
3 563
30 июля 2019 года был выпущен Chromium 76.0.3809.87, исправляющий несколько уязвимостей. Помимо перечисленных уязвимостей, другая уязвимость неожиданно умерла из-за рефакторинга браузерной части реализации IndexedDB Chrome. Указанный коммит изменил способ отслеживания открытых баз данных в памяти. Вместо использования необработанных указателей коммит заменил эти указатели на умные указатели, что в конечном итоге устранило исходную уязвимость.

В этой статье мы подробно рассмотрим уязвимость и ее первопричину, а также покажем, как ее можно превратить в надежный эксплоит, выйдя из песочницы Chrome. Мы используем уязвимость в качестве примера, чтобы продемонстрировать, как типичная уязвимость в одном из интерфейсов IPC, открытая для процесса изолированного рендеринга, может быть превращена в межпроцессную утечку информации и, наконец, использована для выполнения произвольного кода в процессе браузера за пределами изолированной программной среды. Полный POC код эксплоита, порождающий реверс-шелл, можно найти в конце этой статьи.

Хотя детали могут отличаться для других ошибок, концепции довольно схожи, и мы надеемся предоставить некоторые полезные сведения о том, как можно успешно использовать типичные ошибки в открытых интерфейсах IPC, чтобы выйти из песочницы последних версий Google Chrome.

Следующий анализ был проведен на стабильном выпуске Chromium для Android версии 75.0.3770.89.

2. Внутреннее устройство IndexedDB

API IndexedDB реализует постоянное хранилище для структурированных данных на стороне клиента. Он может использоваться веб-приложением для хранения больших объемов данных внутри браузера. Данные хранятся в виде пар ключ/значение, а значения могут быть объектами JavaScript со сложной структурой. IndexedDB построен на модели транзакционной базы данных, и каждая операция с базой данных происходит в контексте транзакции.

Реализация IndexedDB в Chrome довольно сложна и предоставляет различные интерфейсы IPC процессам отрисовки в песочнице, что делает ее привлекательной целью для поиска ошибок, чтобы избежать изолированной программной среды Chrome.

2.1. Интерфейсы Mojo

Большая часть реализации IndexedDB в Chrome реализуется в процессе браузера. Несколько различных интерфейсов mojo IPC существуют как в браузере, так и в модуле визуализации, чтобы обеспечить связь между обоими процессами и позволить изолированному модулю визуализации выполнять операции IndexedDB.

Интерфейс IDBFactory mojo обеспечивает основную точку входа для средства визуализации. Среди нескольких служебных методов он предоставляет метод Open, который соответствует методу open() интерфейса JavaScript IDBFactory и может использоваться для запроса открытия соединения с базой данных.

Два служебных метода, предоставляемых интерфейсом IDBFactory, релевантные для нашего обсуждения ниже, - это AbortTransactionsAndCompactDatabase и AbortTransactionsForDatabase. Вызов любого из этих методов немедленно завершит все текущие транзакции. Интересно, что рендер никогда не использовал эти функции в анализируемой нами версии.

Два аргумента, переданных методу Open, - это указатели на интерфейсы, указывающие на интерфейсы mojo IDBCallbacks и IDBDatabaseCallbacks, реализованные в процессе рендеринга. В то время как первый используется процессом браузера для возврата результатов модулю визуализации для отдельных запросов, второй используется для уведомления модуля визуализации о внешних событиях, связанных с этими запросами.

После успешного вызова метода Open браузер отправляет указатель интерфейса на интерфейс IDBDatabase обратно в средство визуализации. Интерфейс IDBDatabase предоставляет все методы для выполнения операций с открытой базой данных. Когда средство визуализации завершает работу с базой данных, оно вызывает метод Close интерфейса IDBDatabase, который закрывает соединение с базой данных на стороне браузера.

Для IndexedDB определено гораздо больше интерфейсов mojo, но описанных выше достаточно для нашего обсуждения ниже. Полный список интерфейсов mojo для IndexedDB можно найти в соответствующем файле mojom по адресу third_party/blink/ public/mojom/indexeddb/indexeddb.mojom.


2.2. Базы данных, подключения и запросы

IndexedDB имеет концепцию баз данных и соединений. В реализации Chrome они представлены классами IndexedDBDatabase и IndexedDBConnection соответственно. Одновременно может существовать несколько подключений к одной базе данных, но для каждой базы данных будет только один объект IndexedDBDatabase.

Средство визуализации, использующее интерфейс IDBDatabase mojo для связи с базой данных, всегда будет проходить через текущее соединение для выполнения методов для соответствующего объекта базы данных.

Еще одна важная концепция, которую нужно понять, - это запросы. Открытие или удаление базы данных не происходит синхронно, но будет запланирован запрос на выполнение соответствующего действия. Классы IndexedDBDatabase::OpenRequest и IndexedDBDatabase::DeleteRequest реализуют эту функциональность.

Как указано выше, IndexedDB основан на модели транзакционной базы данных. Код реализует одну транзакцию как объект IndexedDBTransaction. Большинство операций выполняется в контексте транзакции, которую можно откатить в случае сбоя.

2.3. Карта базы данных

Чтобы отслеживать все открытые базы данных, карта базы данных, проиндексированная по источнику и имени базы данных, хранит необработанные указатели на соответствующие объекты IndexedDBDatabase. Карта базы данных хранится в классе IndexedDBFactoryImpl как database_map_.

Когда средство визуализации запрашивает открытие соединения с базой данных, вызывая метод Open интерфейса IDBFactory, оно обращается к карте базы данных [1], чтобы выяснить, открыта ли уже запрошенная база данных.


C:
void IndexedDBFactoryImpl::Open(
    const base::string16& name,
    std::unique_ptr<IndexedDBPendingConnection> connection,
    const Origin& origin,
    const base::FilePath& data_directory) {
  IDB_TRACE("IndexedDBFactoryImpl::Open");
  IndexedDBDatabase::Identifier unique_identifier(origin, name);
  auto it = database_map_.find(unique_identifier);                              [1]
  if (it != database_map_.end()) {
    it->second->OpenConnection(std::move(connection));                          [2]
    return;
  }

[...]

  scoped_refptr<IndexedDBDatabase> database;                                    [3]
  std::tie(database, s) = IndexedDBDatabase::Create(
      name, backing_store.get(), this,
      std::make_unique<IndexedDBMetadataCoding>(), unique_identifier,
      backing_store->lock_manager());

[...]

  database->OpenConnection(std::move(connection));
  if (database->ConnectionCount() > 0) {
    database_map_[unique_identifier] = database.get();                          [4]
    origin_dbs_.insert(std::make_pair(origin, database.get()));
  }
}

Если база данных еще не открыта, создается новый объект IndexedDBDatabase [3], а необработанный указатель на объект сохраняется в карте базы данных [4].

Если запрошенная база данных уже открыта, необработанный указатель на объект IndexedDBDatabase берется из карты [1] и используется для создания нового соединения с базой данных с помощью метода IndexedDBDatabase::OpenConnection [2].

В любом случае указатель интерфейса mojo IDBDatabase, соответствующий объекту базы данных, в конце возвращается модулю визуализации, позволяя модулю визуализации взаимодействовать с соответствующей базой данных.

2.4. Время жизни объекта IndexedDBDatabase

Объект IndexedDBDatabase - это объект с подсчетом ссылок. Подсчитанные ссылки на него хранятся в объекте IndexedDBConnection, объекте IndexedDBTransaction, а также в объектах текущего или ожидающего запроса. Как только счетчик ссылок упадет до нуля, объект будет освобожден.

В случае, если объект базы данных освобожден, важно, чтобы соответствующий необработанный указатель на IndexedDBDatabase также был удален с карты базы данных. Это происходит внутри метода IndexedDBDatabase::Close при закрытии соединения с базой данных.

C:
void IndexedDBDatabase::Close(IndexedDBConnection* connection, bool forced) {
  DCHECK(connections_.count(connection));
  DCHECK(connection->IsConnected());
  DCHECK(connection->database() == this);

  IDB_TRACE("IndexedDBDatabase::Close");

  // Abort outstanding transactions from the closing connection. This can not
  // happen if the close is requested by the connection itself as the
  // front-end defers the close until all transactions are complete, but can
  // occur on process termination or forced close.
  connection->FinishAllTransactions(IndexedDBDatabaseError(                     [5]
      blink::kWebIDBDatabaseExceptionUnknownError, "Connection is closing."));

  // Abort transactions before removing the connection; aborting may complete
  // an upgrade, and thus allow the next open/delete requests to proceed. The
  // new active_request_ should see the old connection count until explicitly
  // notified below.
  connections_.erase(connection);                                               [6]

  // Notify the active request, which may need to do cleanup or proceed with
  // the operation. This may trigger other work, such as more connections or
  // deletions, so |active_request_| itself may change.
  if (active_request_)                                                          [7]
    active_request_->OnConnectionClosed(connection);

  // If there are no more connections (current, active, or pending), tell the
  // factory to clean us up.
  if (connections_.empty() && !active_request_ && pending_requests_.empty()) {  [8]
    backing_store_ = nullptr;
    factory_->ReleaseDatabase(identifier_, forced);                             [9]
  }
}

Метод Close сначала прервет все незавершенные транзакции в текущем соединении [5].Кроме того, он уведомит текущую выполняющуюся транзакцию и сообщит, что текущая база данных скоро будет закрыта [7].

Наконец, код проверяет, было ли текущее закрытое соединение последним подключением к базе данных и нет ли текущих или ожидающих запросов [8]. Только в этом случае код удаляет необработанный указатель IndexedDBDatabase из карты базы данных, вызывая IndexedDBFactoryImpl::ReleaseDatabase в [9].

Если это условие не выполняется, необработанный указатель на объект базы данных будет сохранен в карте базы данных. Цель кода - удалить необработанный указатель базы данных с карты базы данных после закрытия последнего соединения с базой данных и всех соответствующих запросов.

Предполагается, что в случае, если условие оценивается как ложное, либо соединение, либо запрос все еще содержат ссылку на объект IndexedDBDatabase, чтобы поддерживать его. Однако, как выясняется, это предположение ошибочно.

3. Состояние гонки IndexedDB

Код уязвим для состояния гонки, которое может привести к зависанию необработанного указателя на освобожденный объект IndexedDBDatabase на карте базы данных.

Чтобы создать этот сценарий, мы начнем с открытия базы данных, указав версию 0. Это создаст новый объект IndexedDBDatabase и сразу откроет к нему новое соединение.

После этого мы просим снова открыть ту же базу данных, но на этот раз указав более высокую версию 2. Для этого требуется операция обновления базы данных. Однако, поскольку у нас все еще есть соединение с версией 0, открытой для базы данных, обновление не начинается сразу после выполнения OpenRequest, а откладывается до тех пор, пока не будет вызван OpenRequest::OnConnectionClosed и все подключения к базе данных не будут закрыты.

C:
void OnConnectionClosed(IndexedDBConnection* connection) override {
  // This connection closed prematurely; signal an error and complete.
  if (connection && connection->callbacks() == pending_->database_callbacks) {
    pending_->callbacks->OnError(
        IndexedDBDatabaseError(blink::kWebIDBDatabaseExceptionAbortError,
                               "The connection was closed."));
    db_->RequestComplete(this);
    return;
  }

  if (!db_->connections_.empty())                                             [10]
    return;

  std::vector<ScopesLockManager::ScopeLockRequest> lock_requests = {
      {kDatabaseRangeLockLevel, GetDatabaseLockRange(db_->metadata_.id),
       ScopesLockManager::LockType::kExclusive}};
  db_->lock_manager_->AcquireLocks(
      std::move(lock_requests),
      base::BindOnce(&IndexedDBDatabase::OpenRequest::StartUpgrade,
                     weak_factory_.GetWeakPtr()));
}

После того, как мы открыли второе соединение с базой данных, IndexedDBDatabase::active_request_ указывает на объект OpenRequest версии 2, который задерживает операцию обновления базы данных.

Если мы сейчас закроем первое соединение с базой данных для версии 0, IndexedDBDatabase::Close сначала удалит последнее соединение с базой данных [6], а затем вызовет OpenRequest::OnConnectionClosed для текущего OpenRequest, на который указывает IndexedDBDatabase::active_request_.

Поскольку последнее соединение с базой данных было удалено, OpenRequest::OnConnectionClosed начнет операцию отложенного обновления, вызвав IndexedDBDatabase::OpenRequest::StartUpgrade. StartUpgrade создаст новое соединение и запланирует новую задачу VersionChangeOperation в текущей транзакции:

C:
// Initiate the upgrade. The bulk of the work actually happens in
// IndexedDBDatabase::VersionChangeOperation in order to kick the
// transaction into the correct state.
void StartUpgrade(std::vector locks) {
  connection_ = db_->CreateConnection(pending_->database_callbacks,
                                      pending_->child_process_id);
  DCHECK_EQ(db_->connections_.count(connection_.get()), 1UL);

  std::vector<int64_t> object_store_ids;

  IndexedDBTransaction* transaction = connection_->CreateTransaction(
      pending_->transaction_id,
      std::set<int64_t>(object_store_ids.begin(), object_store_ids.end()),
      blink::mojom::IDBTransactionMode::VersionChange,
      new IndexedDBBackingStore::Transaction(db_->backing_store()));
  transaction->ScheduleTask(
      base::BindOnce(&IndexedDBDatabase::VersionChangeOperation, db_,
                     pending_->version, pending_->callbacks));
  transaction->Start(std::move(locks));
}

Если мы теперь дойдем до проверки [8] в последних строках IndexedDBDatabase::Close, условие будет оцениваться как false, и необработанный указатель на текущий объект IndexedDBDatabase не будет удален, поскольку соединение с базой данных все еще существует.

Сразу после вызова метода Close для закрытия соединения с базой данных версии 0, мы теперь быстро вызываем метод AbortTransactionsForDatabase в интерфейсе mojo IDBFactory из средства визуализации, чтобы он выполнялся до того, как отправленная задача IndexedDBDatabase::VersionChangeOperation получила возможность запустить.

Вызов метода mojo AbortTransactionsForDatabase заканчивается вызовом IndexedDBConnection::FinishAllTransactions при каждом подключении к базе данных:

C:
void IndexedDBConnection::FinishAllTransactions(
    const IndexedDBDatabaseError& error) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  std::unordered_map<int64_t, std::unique_ptr<IndexedDBTransaction>> temp_map;
  std::swap(temp_map, transactions_);
  for (const auto& pair : temp_map) {
    auto& transaction = pair.second;
    if (transaction->is_commit_pending()) {
      IDB_TRACE1("IndexedDBDatabase::Commit", "transaction.id",
                 transaction->id());
      transaction->ForcePendingCommit();
    } else {
      IDB_TRACE1("IndexedDBDatabase::Abort(error)", "transaction.id",
                 transaction->id());
      transaction->Abort(error);
    }
  }
}

Поскольку отложенного коммита нет, код вызывает метод IndexedDBTransaction::Abort для каждой транзакции, который затем вызывает IndexedDBDatabase::transactionFinished [11], чтобы сигнализировать о завершении транзакции.

C:
void IndexedDBTransaction::Abort(const IndexedDBDatabaseError& error) {

[...]

  database_->TransactionFinished(mode_, false);                             [11]

  // RemoveTransaction will delete |this|.
  // Note: During force-close situations, the connection can be destroyed during
  // the |IndexedDBDatabase::TransactionFinished| call
  if (connection_)
    connection_->RemoveTransaction(id_);
}

IndexedDBDatabase::transactionFinished теперь вызывает OpenRequest::UpgradeTransactionFinished, который завершает вызов IndexedDBDatabase::RequestComplete, завершая активный запрос и очищая указатель IndexedDBDatabase::active_request_ в [12]:

C:
void IndexedDBDatabase::RequestComplete(ConnectionRequest* request) {
  DCHECK_EQ(request, active_request_.get());
  scoped_refptr<IndexedDBDatabase> protect(this);
  active_request_.reset();                                                  [12]

  // Exit early if |active_request_| held the last reference to |this|.
  if (protect->HasOneRef())
    return;

  if (!pending_requests_.empty())
    ProcessRequestQueue();
}

Объекту OpenRequest принадлежит соответствующий объект IndexedDBConnection. Когда активный OpenRequest уничтожается очисткой указателя IndexedDBDatabase::active_request_, объект IndexedDBConnection также освобождается, включая все его транзакции.

На этом этапе все ссылки на объект IndexedDBDatabase удалены, и он будет освобожден. Мы также закрыли все подключения к базе данных, но обманом заставили код не удалять необработанный указатель IndexedDBDatabase с карты базы данных. Итак, мы успешно создали сценарий, в котором карта базы данных имеет висящий необработанный указатель на освобожденный объект IndexedDBDatabase!

Если теперь мы попытаемся снова открыть ту же базу данных из средства визуализации, мы закончим работу с освобожденным объектом IndexedDBDatabase.

4. Эксплуатация

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

Целью эксплоита, описанного в следующих разделах, является 64-разрядная версия Chrome, работающая на Android. Но с небольшими изменениями уязвимость также может использоваться в Linux или Windows.

4.1. Создание утечки информации

Первое, что нам нужно сделать, это превратить баг в утечку информации, чтобы передать базовый адрес Chrome в средство визуализации. Это можно сделать несколькими способами, но мы решили использовать для этой цели интерфейсы mojo IndexedDB и их обратные вызовы.

Мы не нашли способа напрямую передать базовый адрес Chrome, поэтому нам нужно запустить ошибку два раза.

4.1.1. Утечка указателя кучи

Мы запускаем ошибку в первый раз, выполняя два вызова Open с версией 0 и версией 2 соответственно, за которыми следует вызов Close и AbortTransactionsForDatabase для запуска состояния гонки и возможного освобождения объекта IndexedDBDatabase.

Затем мы неправильно используем метод CreateObjectStore для перераспределения освобожденного объекта IndexedDBDatabase со строкой ключевого пути IDB метаданных, которые создаются для соответствующего хранилища объектов.

Мы полностью контролируем содержимое строки keypath и используем ее для создания поддельного объекта IndexedDBDatabase, устанавливая для полей pending_requests_.buffer и pending_requests_.capacity значение 0.

Если теперь мы вызовем метод Open, чтобы получить указатель интерфейса mojo на освобожденный объект IndexedDBDatabase, мы будем работать с созданным нами поддельным объектом. Вызов Open просто добавит новый OpenRequest в очередь pending_requests_. Поскольку мы устанавливаем буфер и его емкость равными 0, будет выделен новый буфер, а в поле pending_requests_.buffer поддельного объекта будет установлен новый указатель кучи, который фактически сохраняется внутри строки пути к ключу.

Метаданные хранилища объектов могут "утечь" обратно в средство визуализации путем вызова метода Commit, поэтому мы можем легко передать этот указатель кучи в средство визуализации.

Перед утечкой указателя мы продолжаем вызывать метод Open в освобожденной базе данных IndexedDBDatabase, который будет добавлять все больше и больше указателей OpenRequest в очередь pending_requests_ и, таким образом, увеличивать базовый буфер поддержки. Контролируя количество вызовов Open, мы, таким образом, контролируем размер выделенного резервного буфера.

Затем происходит утечка указателя на этот резервный буфер, вызывая метод Commit и извлекая указатель кучи из возвращенных метаданных.

4.1.2. Замена памяти указателя кучи на объект

После утечки указателя кучи на резервный буфер pending_requests_ мы продолжаем вызывать метод Open еще несколько раз для освобожденного объекта IndexedDBDatabase, который после нескольких вызовов снова перераспределяет резервный буфер для его увеличения. Это приводит к тому, что указатель кучи, в котором произошла утечка, теперь освобождается.

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

4.1.3. Утечка указателя виртуальной таблицы

Чтобы теперь получить утечку указателя виртуальной таблицы, мы еще раз запускаем уязвимость. Сначала нам нужно снова перераспределить ранее освобожденный объект IndexedDBDatabase с действующим объектом IndexedDBDatabase, чтобы не вызывать сбоев при повторном запуске ошибки. Поскольку при вызове метода AbortTransactionsForDatabase будет выполняться итерация через database_map_ и касаться каждого объекта, на который имеется ссылка.

После повторного запуска ошибки мы снова используем метод CreateObjectStore, чтобы перераспределить освобожденный объект IndexedDBDatabase с новым созданным поддельным объектом.

В созданном фальшивом объекте мы устанавливаем в поле pending_requests_.buffer предыдущий указатель кучи в котором произошла утечка (который указывает на строку ключевого пути в метаданных ранее созданного хранилища объектов) и устанавливаем поле pending_requests_.capacity равным 1.

Теперь мы вызываем Open на освобожденной базе данных IndexedDBDatabase один раз, которая попытается добавить новый OpenRequest в очередь pending_requests_ поддельного объекта.

Поскольку мы устанавливаем емкость равной 1, код будет пытаться перераспределить резервный буфер, эффективно освобождая память, на которую указывает pending_requests_.buffer, и заменяя ее буфером большего размера.

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

Один из приемов, который мы здесь используем, - это присвоить именам созданных баз данных очень большую строку размером 0x4000 байт. На более позднем этапе мы будем делать утечку содержимого одного из созданных объектов IndexedDBDatabase, что приведет к утечке не только указателя vtable, но и указателя на строку имени базы данных, которая затем предоставит нам указатель кучи, указывающий на большое свободное пространство, которое мы можем использовать для хранения ROP-цепочки и шелл-кода позже.

Теперь мы просто считываем метаданные ранее созданного хранилища объектов, которое получит содержимое одного из объектов IndexedDBDatabase.

Мы используем указатель vtable из объекта IndexedDBDatabase в котором произошла утечка и указатель на строку имени объекта для утечки указателя на память, достаточную для хранения цепочки ROP и шелл-кода.

4.2. Получение выполнения кода

После утечки указателя на резервную память и базовый адрес Chrome мы используем память резервной памяти для размещения поддельного объекта OpenRequest, где мы используем его виртуальный метод Perform, чтобы получить выполнение кода и запустить цепочку ROP.

Затем мы заменяем память освобожденной базы данных IndexedDBDatabase созданным фальшивым объектом, устанавливающим для processing_pending_requests_ значение 0 и pending_requests_.buffer в память, в которую мы поместили фальшивый указатель OpenRequest. Поскольку processing_pending_requests_ равен 0, вызов метода Open в освобожденной базе данных IndexedDBDatabase затем начнет вызывать метод Perform для запросов, хранящихся в pending_requests_, который затем будет использовать наш поддельный объект и даст нам выполнение кода.

4.2.1. ROP цепочка


Мы неправильно используем метод IndexedDBDatabase::ProcessRequestQueue, чтобы получить контроль над счетчиком программы:


C:
void IndexedDBDatabase::ProcessRequestQueue() {
  // Don't run re-entrantly to avoid exploding call stacks for requests that
  // complete synchronously. The loop below will process requests until one is
  // blocked.
  if (processing_pending_requests_)
    return;

  DCHECK(!active_request_);
  DCHECK(!pending_requests_.empty());

  base::AutoReset<bool> processing(&processing_pending_requests_, true);
  do {
    active_request_ = std::move(pending_requests_.front());
    pending_requests_.pop();
    active_request_->Perform();                                             [13]
    // If the active request completed synchronously, keep going.
  } while (!active_request_ && !pending_requests_.empty());
}

Мы получаем управление, когда метод ConnectionRequest::Perform выполняется в [13]. Во время вызова регистр x0 указывает на резервное пространство, которое мы ранее выделили и полностью контролируем. Соответствующий код ассемблера показан ниже:

C:
<content::IndexedDBDatabase::ProcessRequestQueue()+72>:   ldr x0, [x21]
<content::IndexedDBDatabase::ProcessRequestQueue()+76>:   ldr x8, [x0]
<content::IndexedDBDatabase::ProcessRequestQueue()+80>:   ldr x8, [x8,#16]
<content::IndexedDBDatabase::ProcessRequestQueue()+84>:   blr x8              [13]
<content::IndexedDBDatabase::ProcessRequestQueue()+88>:   ldr x8, [x21]

4.2.1.1. Стратегия

Поскольку мы ранее получили утечку адреса нашего выделенного свободного пространства (x0) и базовый адрес хрома, мы можем построить простую цепочку ROP, которая просто изменяет права доступа к страницам в резервном пространстве на чтение/запись/ выполнение и, наконец, выполнит переход к шеллкоду после ROP-цепочки в свободной памяти.

4.2.1.2. Гаджеты

Мы используем следующие шесть гаджетов ROP (смещения, специфичные для нашей тестовой сборки):

C:
* G1: 0x4959c14 : ldr x8, [x0, #0x48]! ; ldr x1, [x8, #0x100] ; br x1
* G2: 0x1df7f8c : ldr x9, [x8, #0x190] ; ldr x6, [x8, #0x80] ; blr x9
* G3: 0x3e7a4b0 : ldr x20, [x0, #0x68] ; ldr x9, [x8] ; mov x0, x8 ;
                  ldr x9, [x9, #0xf8] ; blr x9
* G4: 0x3f9152c : ldr x2, [x0, #0x18] ; ldr x0, [x0, #0x38] ; br x2
* G5: 0x2fbf400 : ldr x8, [x8, #0x10] ; blr x8 ; ldr x8, [x20, #0x3b8] ;
                  cbz x8, #0x2fbf424 ; blr x8
* G6: 0x3f0fd88 : ldr x5, [x6, #0x28] ; ldr x4, [x6, #0x20] ;
                  ldr x3, [x6, #0x18] ; ldr x2, [x6, #0x10] ;
                  ldr x1, [x6, #8] ; mov x8, x0 ; ldr x0, [x6] ; svc #0 ;

Соответствующие смещения следует искать (например, с помощью ROPgadget) в целевом двоичном файле. Первый гаджет G1 - это тот, который мы используем для запуска цепочки ROP.

4.2.1.3. Подготовка макета памяти

Цепочка ROP, а также шелл-код будут помещены в область резервной памяти.Конкретно это будет настроено следующим образом:


C:
| Offset              | Value               | Used By | Comment                                    |
| ------------------  |:-------------------:|:-------:| ------------------------------------------:|
| 0x50 (0x48+8)       | slackbase+0x100     | G1      | x8 = slackbase+0x100                       |
| 0xb8 (0x48+0x68+8)  | slackbase           | G3      | x20 = slackbase                            |
| 0x100               | slackbase+0x260     | G3      | x9 = slackbase+0x260, x0 = slackbase+0x100 |
| 0x110 (0x100+0x10)  | gadget addr G6      | G5      | x30 = addr of ldr x8                       |
| 0x118 (0x100+0x18)  | gadget addr G5      | G4      | x2 = addr of G5                            |
| 0x138 (0x100+0x38)  | 226                 | G4      | x0 = 226                                   |
| 0x180 (0x100+0x80)  | slackbase+0x300     | G2      | x6 = slackbase+0x300                       |
| 0x200 (0x100+0x100) | gadget addr G2      | G1      | x1 = addr of G2                            |
| 0x290 (0x100+0x190) | gadget addr G3      | G2      | x9 = addr of G3                            |
| 0x300               | slackbase (aligned) | G6      | x0 = aligned slackbase                     |
| 0x308 (0x300+0x8)   | 0x4000              | G6      | x1 = 0x4000                                |
| 0x310 (0x300+0x10)  | 7                   | G6      | x2 = 7                                     |
| 0x318 (0x300+0x18)  | 0                   | G6      | x3 = 0                                     |
| 0x320 (0x300+0x20)  | 0                   | G6      | x4 = 0                                     |
| 0x328 (0x300+0x28)  | 0                   | G6      | x5 = 0                                     |
| 0x358 (0x260+0xf8)  | gadget addr G4      | G3      | x9 = addr of G4                            |
| 0x3b8               | 0x1000              | G5      | x8 = slackbase+0x1000                      |
| 0x1000              | shellcode

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

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

4.3. Android эксплоит

Последний эксплойт, нацеленный на 64-битную версию Chrome для Android, можно найти здесь (https://labs.bluefrostsecurity.de/files/chrome_indexeddb_exploit.tgz). Он предоставляется как набор исправлений исходного кода модуля рендеринга для Chromium с некоторыми простыми привязками JavaScript для тестирования. Смещения и гаджеты нужно настраивать. Успешная эксплуатация запускает полезную нагрузку реверс-шелл внутри привилегированного процесса браузера.


Источник: https://labs.bluefrostsecurity.de/b...rome-sandbox-via-an-indexeddb-race-condition/
Автор перевода: yashechka
Переведено специально для портала xss.pro (c)
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх