Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
ds3-nrssr-rce — Документация и PoC-код для CVE-2022-24125 и CVE-2022-24126. | Kitploit
Инструменты/GitHubGitHub/tremwil/ds3-nrssr-rce
Фреймворки для эксплойтовАнализ уязвимостейЭксплуатацияОбратная инженерияШелл-кодТестирование на ПроникновениеОбучение и ОбразованиеRed TeamingГенерация ШеллкодаРазработка Полезной НагрузкиЭксплуатация Бинарных Файлов
169854 лет назадПроверено Kitploit
GitHub
tremwil/ds3-nrssr-rce

ds3-nrssr-rce

Документация и PoC-код для CVE-2022-24125 и CVE-2022-24126.

Репозиторий

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

Обновление: Dark Souls III 1.15.1

Новое обновление игры, 1.15.1, было выпущено для Dark Souls III 25.08.2022, вместе с восстановлением онлайн-сервисов. Это обновление исправило как CVE-2022-24125, так и CVE-2022-24126, а также множество других потенциальных уязвимостей безопасности в P2P-сети игры (чтения/записи за пределами границ). Кроме того, были исправлены все известные эксплойты, позволяющие повредить сохранения других игроков. Также были пропатчены многие распространённые мелкие читы (например, «проклятый нож»), которые часто встречались в онлайн-мультиплеере.

ds3-nrssr-rce

Этот репозиторий содержит код доказательства концепции и документацию для самой последней RCE-уязвимости, затрагивающей игры FROM SOFTWARE, CVE-2022-24126. Хотя теоретически это возможно и в других играх, основное внимание уделяется Dark Souls III, поскольку именно на этой игре проводилось моё исследование. На данный момент код доказательства концепции существует только для Dark Souls III; подтверждено наличие уязвимости в:

  • Dark Souls 1 PTDE (авторство: LukeYui)
  • Dark Souls Remastered (авторство: metal-crow)
  • Dark Souls 2 (включая Scholar) (авторство: LukeYui)
  • Dark Souls 3 (до 1.15.0) (авторство: tremwil)

Уязвимый код также присутствует в Sekiro (авторство: LukeYui), хотя способа его запуска нет. Наличие в Demon's Souls не подтверждено, но весьма вероятно. Хотя закрытое сетевое тестирование было затронуто этой проблемой, релизная версия Elden Ring — нет. Фактически, огромный список сетевых вылетов, чтений/записей за пределами границ и эксплойтов, позволявших игрокам изменять данные игры других участников в Dark Souls III, был исправлен в Elden Ring. Благодарность за составление этого списка и FROM SOFTWARE за быструю реакцию! Я рад сообщить, что

LukeYui
Elden Ring — несомненно самый безопасный проект FROM SOFTWARE с точки зрения ущерба, который могут нанести хакеры.

Развеиваем заблуждения

Вопреки распространённому мнению, это НЕ эксплойт одноранговой (P2P) сети. Он связан с матчмейкинг-сервером и поэтому гораздо серьёзнее, поскольку из-за другой уязвимости матчмейкинг-сервера (CVE-2022-24125) вам не нужно участвовать в каких-либо действиях в мультиплеере, чтобы стать уязвимым.

В Dark Souls III злоумышленник, злоупотребляющий этой уязвимостью, мог бы надёжно выполнить полезную нагрузку размером до 1.3MiB1 шеллкода на машине каждого онлайн-игрока в течение нескольких секунд.

Учитывая, что в месяцы, предшествовавшие отключению серверов, в игру в среднем одновременно играло около 20 000 человек, было очевидно, что проблему необходимо исправить немедленно, особенно с учётом возможности её наличия в Elden Ring. Поскольку FROM SOFTWARE не предприняла никаких действий в течение более чем 40 дней после моего первоначального отчёта с видео доказательствами концепции и подробной документацией по эксплойту (на которой основана большая часть этого readme), я решил публично продемонстрировать существование эксплойта безвредным способом, надеясь привлечь внимание и добиться его устранения разработчиками, — и это сработало.

Содержание

  • Сводка по эксплойту (CVE-2022-24126)
  • Векторы распространения (CVE-2022-24125)
  • Общая тактика эксплуатации для всех игр
    • Ошибка №1: отсутствие проверки границ в парсере списка записей
    • Ошибка №2: переполнение буфера в парсере NRSessionSearchResult
    • Подготовка к ROP-цепочке
  • Код доказательства концепции для Dark Souls III
    • Запуск PoC-кода
    • Вектор атаки
    • Цепочка перенаправления виртуальных вызовов
    • Дополнительная информация

Сводка по эксплойту (CVE-2022-24126)

Некорректная проверка границ стекового буфера и поля размера данных при разборе матчмейкинг-данных NRSessionSearchResult позволяет атакующему выполнить произвольный код. Переполнение стека позволяет перезаписать младшие два байта vftable_ptr объекта DLMemoryInputStream, используемого внутри потоковым ридером, перенаправляя выполнение на тщательно выбранный соседний код. Умелое использование структуры объекта DLMemoryInputStream и поля размера данных затем позволяет добиться перенаправления произвольного кода, при этом RCX указывает на адрес нашего пакета. Далее серия перенаправлений кода через виртуальные вызовы с различными смещениями (которые теперь будут переходить по адресам, записанным нами в буфер пакета) может быть использована для достижения выполнения произвольного кода.

Векторы распространения (CVE-2022-24125)

Векторы распространения — это то, что делает данную RCE особенно серьёзной (помимо того, что это уже RCE). Эксплойт передаётся через push-запросы матчмейкинга, содержащие информацию NRSessionSearchResult. Это означает, что атакующий может нацелиться на любого, кто присоединяется к его онлайн-сессии. В частности, для DS3:

  • призыв фантомов (PushRequestSummonSign)
  • вторжения тёмных духов (PushRequestAllowBreakInTarget)
  • игроки, присоединяющиеся через ковенант (PushRequestVisit)
  • участники арены (PushRequestAcceptQuickMatch)

Это уже довольно плохо, но настоящий потенциал открывается запросом RequestSendMessageToPlayers:

root@kitploit:~
message RequestSendMessageToPlayers { 
    repeated uint32 player_ids = 1; 
    required bytes push_message = 2;
}

Хост использует этот запрос для прямой отправки push-сообщения PushRequestAllowBreakInTarget вторгшимся духам, чтобы они могли получить координаты появления и присоединиться к его P2P-сессии. Вот и всё. Это единственный способ использования этого запроса в игре.

Тем не менее он позволяет любому клиенту отправлять произвольные push-сообщения сотням тысяч конкретных игроков.

Я не могу достаточно подчеркнуть, насколько это ужасно небезопасно. Любой игрок может фактически выдать себя за матчмейкинг-сервер. Используя этот запрос для отправки эксплойта через PushRequestVisit, атакующий может удалённо нацелиться на любого онлайн-игрока, если известен его ID игрока. Атакующий также может очень быстро отправить эксплойт всей онлайн-аудитории, отправив несколько запросов, каждый из которых содержит большой диапазон возможных ID игроков.

Общая тактика эксплуатации для всех игр

Хотя RCE не переносится один в один на каждую игру, основная идея эксплойта, дающая атакующему перенаправление произвольного кода, одинакова. Если этого удаётся достичь, весьма вероятно, что затем можно найти специфичную для игры цепочку виртуальных вызовов или ROP-цепочку. Этот «первый шаг» использует следующие уязвимости:

Ошибка №1: отсутствие проверки границ в парсере списка записей

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

root@kitploit:~
struct Entry
{
    uint32_t type_or_id; // not sure, but probably a type (fixed length = 2, variable length = 1 ?)
    uint32_t size;
    uint8_t data[size];
}

Игровая функция, отвечающая за копирование данных этих записей, слепо доверяет полю размера, что создаёт чтение за пределами границ. Злоумышленный клиент может воспользоваться этим, установив поле размера в такие значения, как 0x7FFFFFFF, что приведёт к сбою выделения памяти и вылету игры жертвы. Позже этот размер также передаётся в конструктор DLMemoryInputSteam, что является важной частью эксплойта.

Ошибка №2: переполнение буфера в парсере NRSessionSearchResult

Одна из записей в структуре данных, описанной выше, представляет собой сериализованный объект NRSessionSearchResult. Парсер этих данных сначала разбирает список свойств. Эти свойства могут быть 4-байтовыми целыми, 8-байтовыми целыми или широкими строками с завершающим нулём. За этим списком свойств следует имя профиля Steam хоста в виде широкой строки с завершающим нулём и некоторые дополнительные данные, не важные для эксплойта. И эта функция, и парсер списка свойств используют стековый буфер фиксированного размера для чтения строк, и в обоих случаях проверка границ буфера не выполняется. Вот игровой код, отвечающий за копирование имени хоста (получен с помощью декомпилятора Ghidra и затем очищен):

root@kitploit:~
size_t idx = 0;
wchar_t wchr = 0;
do {
  // read_wchar() function at vftable index 17 of DLEndianStreamReader
  wchr = stream_reader->read_wchar();
  player_name_buff[idx] = wchr;
  idx++;
} while (wchr != 0);

Это приводит к эксплуатации переполнения буфера, позволяя атакующему повредить стек.

Подготовка к цепочке виртуальных вызовов / ROP-цепочке

Чтобы добиться перенаправления произвольного кода, мы используем это, а также раскладку памяти объекта DLMemoryInputStream, создаваемого на стеке функцией, вызывающей парсер; этот объект используется внутри потоковым ридером:

root@kitploit:~
struct DLMemoryInputStream {
    uintptr_t* vftable_ptr; // Offset 0
    size_t data_size;       // Offset 4 (32bit) / 8 (64bit)
    uint8_t* data_buffer;   // Offset 8 (32bit) / 16 (64bit)
    // Entries after the buffer are not important for the exploit
}

Поскольку мы контролируем поле data_size (Ошибка №1), его можно установить в адрес стековой памяти поля data_buffer. Это сработает при условии, что адрес постоянен и не слишком велик (DS3 удовлетворяет этим требованиям). Так как компилятор размещает стековый буфер в верхней части кадра, атакующий затем может использовать Ошибку №2, чтобы перезаписать младшие два байта поля vftable_ptr объекта DLMemoryInputStream. Поэтому при чтении следующего символа DLEndianStreamReader внутренне вызовет DLMemoryInputStream, и выполнение будет перенаправлено. Этих 2 байт достаточно, чтобы перейти к 22-й функции в vftable DLEndianStreamReader, которая вызывает 6-й виртуальный метод объекта, на который указывает её первое поле. В 64-битном процессе (т.е. Dark Souls III) будут выполнены следующие инструкции:

root@kitploit:~
MOV       RCX,qword ptr [RCX + 0x8]
MOV       RAX,qword ptr [RCX]
JMP       qword ptr [RAX + 0x40]

Поскольку RCX является указателем на объект DLMemoryInputStream, первая инструкция записывает в RCX поле data_size, которое атакующий с помощью Ошибки №1 установил в стековый адрес, указывающий на поле data_buffer. Следующие две инструкции таким образом перенаправят выполнение на адрес памяти, который атакующий записал по смещению 0x40 в буфере данных. Перенаправление произвольного кода достигнуто! Отсюда атакующий может выстроить цепочку перенаправления кода, которая копирует его полезную нагрузку в подходящую область памяти и выполняет её, выбирая код рядом с виртуальными вызовами с различными смещениями, поскольку буфер теперь действует как таблица виртуальных методов. Для доказательства концепции в Dark Souls III я нашёл схему, для которой требуется всего 3 гаджета для достижения RCE:

  • 0x18: 140e97700
  • 0x40: 1422be020
  • 0x68: 140e40f15

Подробнее об этих 3 гаджетах см. здесь. Если для какой-либо другой игры этот метод виртуальных вызовов неприменим, перенаправление произвольного кода всё равно может быть использовано для создания более традиционного ROP-эксплойта.

Код доказательства концепции для Dark Souls III

Запуск PoC-кода

Чтобы запустить код доказательства концепции, сначала необходимо иметь сервер для подключения. Поскольку официальные серверы были отключены из-за эксплойта, вы можете настроить частный сервер с помощью ds3os. ds3os спроектирован так, чтобы максимально точно имитировать поведение релизного сервера, но в этот проект уже были внесены исправления безопасности для устранения данного эксплойта. Однако вы всё равно можете настроить тестовую среду, собрав проект самостоятельно с константами SEND_MESSAGE_TO_PLAYERS_SANITY_CHECKS и NRSSR_SANITY_CHECKS, установленными в false в BuildConfig.h. Это имитирует небезопасное поведение релизного сервера. Следуйте инструкциям, предоставленным ds3os, чтобы запустить игру и подключиться к вашему серверу.

Как только это будет сделано и ваша игра подключится к серверам, соберите PoC-код и запустите исполняемый файл Injector.exe. Он внедрит DLL, содержащую код эксплойта, в процесс Dark Souls III. Затем эта DLL будет использовать игровую функцию, отправляющую FRPG-сообщения на сервер, чтобы доставить эксплойт вашему собственному клиенту.

Вектор атаки

Для доказательства концепции я решил использовать сообщение PushRequestVisit, отправляемое через RequestSendMessageToPlayers. Это самая действенная версия эксплойта в том смысле, что игра цели немедленно начнёт разбор уязвимых данных после их получения в любых ситуациях (даже в главном меню).

Цепочка перенаправления виртуальных вызовов

Смещение 0x18: 140e97700

root@kitploit:~
LEA       RAX,[DAT_144786150]
RET

Этот гаджет используется в гаджете по адресу 0x68. Нам нужно поместить в RAX адрес ниже, но довольно близкий к 144786998; это самый близкий из них.

Смещение 0x40: 1422be020

root@kitploit:~
MOV       RDX,RAX
MOV       R8,qword ptr [RCX]
CALL      qword ptr [R8 + 0x68]

Чтобы иметь возможность использовать гаджет по смещению 0x68, необходимо, чтобы адрес буфера данных хранился в RDX, а RCX остался прежним. Этот гаджет делает именно это.

Смещение 0x68: 140e40f15

root@kitploit:~
; Jumping here from the gadget at offset 0x40
MOV       RBX,RDX
CMP       R9,R8

 ; Never jumps, R9 != R8
JZ        LAB_140e40f7a
MOV       RAX,qword ptr [RCX]
MOV       R8,qword ptr [RSP + 0x50]
MOV       RDX,R9
MOV       qword ptr [RSP + 0x30],RSI

; Call gadget at offset 18 (140e97700). Loads 144786150 into RAX
CALL      qword ptr [RAX + 0x18]
MOV       RSI,RAX
TEST      RAX,RAX

; Never jumps, RAX is the data buffer addr.
JZ        LAB_140e40f4d 
CMP       RBP,RDI
MOV       RDX,RBX
MOV       RCX,RAX
CMOVC     RDI,RBP
MOV       R8,RDI ; RDI is a stack address close to 14F3B0, so the memcpy succeeds
CALL      memcpy

LAB_140e40f4d:
TEST      RBX,RBX
; Never jumps as RBX == RDX == data buffer addr, nonzero. 
JZ        LAB_140e40f62 

; We have our now fully control this RWE memory region due to the memcpy at 144786150. RCE has been achieved!
MOV       RCX,qword ptr [DAT_144786998]
MOV       RDX,RBX
MOV       RAX,qword ptr [RCX]
CALL      qword ptr [RAX + 0x68]

Этот гаджет делает за нас почти всё. Он вызывает смещение 0x18, чтобы получить указатель назначения для memcpy, копирует туда наш пакет, а затем вызывает виртуальную функцию по смещению 0x68 у статического объекта по адресу 144786998, который теперь полностью контролируется нами благодаря вызову memcpy. Поскольку объём памяти, повреждаемый memcpy, велик, а некоторые области постоянно записываются другими игровыми потоками, эксплойт сначала загружает «подготовительную» полезную нагрузку, которая копируется в безопасное место, приостанавливает все остальные потоки и заново копирует нашу фактическую полезную нагрузку перед переходом к ней. Подробнее см. в rce.h.

Дополнительная информация

Рекомендую изучить исходный код доказательства концепции, так как в нём много комментариев, подробно описывающих структуру пакета. Если вы хотите увидеть, что происходит на каждом шаге в реальном времени (а стоит — это довольно круто!), вы можете внедрить DLL доказательства концепции, запустив игру под отладчиком с точками останова на следующих интересующих адресах:

140ca5960

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

root@kitploit:~
0x140ca59f8:
    player_data_cpy_ptr = (std_vector *)VectorCopy2_140ca4ef0(&player_data_cpy,player_data);
    FUN_140ca5010(player_data_cpy_ptr,&spawn_data,0x1c);
    FUN_140ca5010(player_data_cpy_ptr,&unk,4);
    FUN_140ca4fa0(player_data_cpy_ptr,&nrssr_data);

Функция 140ca5010 проверяет размеры записей, а 140ca4fa0 предназначена для записей переменного размера и не выполняет проверок корректности поля размера (Ошибка №1). Чтобы добиться описанного выше перенаправления произвольного кода, нам нужно установить его в 14F3B0. Это вызовет чтение за пределами границ объёмом примерно 1.3МиБ, но страницы памяти должно быть достаточно, чтобы избежать нарушений доступа.

140ca56b0

Эта функция вызывается предыдущей с аргументом nrssr_data. Она создаёт объект DLMemoryInputStream на стеке, который затем передаётся в качестве аргумента парсеру NRSSR.

141955f50: ParseNRSessionSeachResult

Парсер NRSessionSearchResult. Проверяет сигнатуру NRSSR и номера версий (14196a0f0), разбирает список свойств (14196a260), имя хоста (14195603a) и ещё некоторую информацию (см. rce.h)

14195603a

Цикл в вышеуказанной функции, который небезопасно копирует имя хоста (Ошибка №2). Вот некоторые адреса, которые могут помочь отслеживать, что происходит во время переполнения буфера:

  • Адрес стекового буфера парсера: 14F128
  • Стековый адрес DLMemoryInputStream: 14F3A0
  • Указатель vtable DLMemoryInputStream после перезаписи: 1439e8b30
  • Смещение виртуальной функции в DLMemoryInputStream, используемой DLInputStreamReader: 0x18

1439e8b48

root@kitploit:~
MOV       RCX,qword ptr [RCX + 0x8]
MOV       RAX,qword ptr [RCX]
JMP       qword ptr [RAX + 0x40]

Сюда мы попадаем после первого перенаправления кода, вызванного перезаписанной vftable потока памяти. Здесь начинается цепочка перенаправлений виртуальных вызовов.

Footnotes

  1. Для Dark Souls III Вер. 1.15. Максимальный теоретический размер полезной нагрузки зависит от раскладки стека и поэтому варьируется в зависимости от игры и версии. ↩

Скачать инструмент