Существует множество модифицированных клиентов Discord, позволяющих настраивать его внешний вид или добавлять нужные функции. Однако некоторые плагины способны разблокировать функциональность, требующую платной подписки на Discord Nitro.

Как это возможно?

Включение инструментов разработчика

Discord разработан на основе Electron — фреймворка, позволяющего создавать кросс-платформенные приложения при помощи веб-технологий. Это значит, что приложение работает внутри веб-браузера, как любой веб-сайт. По сути, Discord — это окно Chromium, что позволяет открыть инструменты разработчика. Однако эта функциональность по умолчанию отключена, так как аккаунты многих пользователей были скомпрометированы копированием зловредных команд, переданных им злоумышленником при помощи социального инжиниринга.

Но если вы осознаёте риски, то можете включить инструменты разработчика, добавив следующий параметр в файл конфигурации, находящийся по адресу %appdata%/discord/settings.json

{
    "DANGEROUS_ENABLE_DEVTOOLS_ONLY_ENABLE_IF_YOU_KNOW_WHAT_YOURE_DOING": true
}

После этого достаточно просто нажать ctrl + shift + i, чтобы открыть консоль разработчика.

Патчинг функции Discord

Чтобы оптимизировать первоначальную загрузку приложения, код разделён на множество блоков, которые могут загружаться параллельно или по необходимости (ленивая загрузка) при помощи функции разделения кода из Webpack.

Все загруженные блоки (chunk) хранятся в переменной webpackChunkdiscord_app:

Переменная webpackChunkdiscord_app в консоли разработчика
Переменная webpackChunkdiscord_app в консоли разработчика

Если необходимо исполнить код, использующий переменные Discord или вызывающий его функции, то необходимо вставить код в эту переменную следующим образом:

window.webpackChunkdiscord_app.push(
  [
    [Math.random()], // chunk ID
    {},
    (a)=>{
        /* Исполняемый код */
        console.log(a.c)
    }
  ]
)

Переменная a.c — это объект, содержащий все модули. Каждый модуль выглядит так:

{
    "chunk_id": 1234,
    "loaded": true,
    "exports": {
        # Экспортированные функции, объекты или примитивные значения из модуля
    }
}

То есть можно выполнить поиск функций из разных модулей JavaScript. Например, таким образом можно найти и вызвать функцию getCurrentUser().

let modules = window.webpackChunkdiscord_app.push([[Math.random()], {}, (a) => { return a.c }]);

function find(query) {
    for (const module of Object.values(modules)) {

        let exports = module.exports;
        if (!exports || exports === window || exports === document.documentElement || exports[Symbol.toStringTag] === "DOMTokenList")
            continue;

        if (typeof (exports) === "object") {
            for (const element of Object.values(exports)) {
                if (element && element[query])
                    return element;
            };
        }
    }
}

find("getCurrentUser").getCurrentUser();

Наконец, мы можем изменить поведение функции, просто заменив её.

let UserStore = find("getCurrentUser");
let originalFunction = UserStore.getCurrentUser;

UserStore.getCurrentUser = () => {
    
    console.log("before getCurrentUser()")
    let result = originalFunction();
    console.log("after getCurrentUser()")
    
    return result;
}

UserStore.getCurrentUser();

Теперь у нас есть все необходимые знания для исследования кода Discord и внесения в него непосредственных модификаций.

Разблокировка высококачественного стриминга

Всё из представленного ниже чётко противоречит условиям пользования Discord и может привести к бану вашего аккаунта. Действуйте на собственное усмотрение.

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

Мы можем модифицировать возвращаемые значения следующим образом:

find("canStreamMidQuality").canStreamMidQuality = (e) => true;
find("canStreamHighQuality").canStreamHighQuality = (e) => true;

В результате этого мы можем выбрать демонстрацию экрана с исходным разрешением и 60 FPS, даже если вы не подписаны на Nitro.

Пример демонстрации экрана при полном разрешении и 60 FPS.
Пример демонстрации экрана при полном разрешении и 60 FPS.

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

Разблокировка платных тем

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

find("canUseClientThemes").canUseClientThemes = (e) => true;

Она разблокирует кнопки, позволяющие выбирать разные платные темы.

Экран, позволяющий выбрать платную тему
Экран, позволяющий выбрать платную тему

Однако тема применится только на долю секунды, а затем вернётся к одной из стандартных тем. Так происходит, потому что Discord пытается сохранить данные о том, что пользователь изменил тему, но сервер сообщает, что у пользователя не должно быть возможности выбирать платную тему. Когда клиент получает ответ от сервера, он мгновенно возвращается к одной из стандартных тем.

Чтобы заблокировать синхронизацию тем, необходимо пропатчить другую функцию:

let PersistedStore = find("shouldSync");
let originalFunction = PersistedStore.shouldSync; 

// Мы хотим заблокировать синхронизацию только настроек внешнего вида
PersistedStore.shouldSync = (e) => {
    if (e === "appearance")
        return false;
    else
        originalFunction(e)
};

Но после этого мы наконец можем изменить тему.

Тема Sunset
Тема Sunset

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

Заключение

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

eval(atob("Ly8gSGVyZSdzIGEgY2FrZSBpZiB5b3UgdGhvdWdodCBhYm91dCBpbnNwZWN0aW5nIHRoZSBjb2RlIGJlZm9yZSBleGVjdXRpbmcgaXQ6IPCfjoIKCmxldCBjc3MgPSAnZm9udC1zaXplOiAzNnB4OyBmb250LXdlaWdodDogYm9sZDsgY29sb3I6IHJlZCc7CiAKY29uc29sZS5sb2coIiVjTkVWRVIgcGFzdGUgY29kZSB5b3UgZG9uJ3QgdW5kZXJzdGFuZCBpbnRvIHRoZSBkZXZlbG9wbWVudCBjb25zb2xlLiIsIGNzcyk7CmNvbnNvbGUubG9nKCIlY1RoaXMgaXMgdGhlIGJlc3Qgd2F5IHRvIGNvbXByb21pc2UgeW91ciBhY2NvdW50LiIsIGNzcyk7"))

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

Vencord: https://github.com/Vendicated/Vencord/blob/main/src/plugins/fakeNitro.ts

Replugged: https://github.com/cafeed28/replugged-nitrospoof/blob/main/src/index.ts

BetterDiscord: https://github.com/riolubruh/YABDP4Nitro/blob/main/YABDP4Nitro.plugin.js

Комментарии (4)


  1. Phoen1xBur
    23.08.2023 10:18

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


  1. gmini
    23.08.2023 10:18
    +1

    да, отлично, скопируйте длиннющий обфусцированный код из интернета и передайте его в eval на вашей машине


    1. gudvinr
      23.08.2023 10:18

      Стандартный установщик platformio так и работает, например, зато удобно /s


  1. Igorgro
    23.08.2023 10:18

    Странно, у меня этот метод не сработал. Выглядит так, будто после перезаписи функции ничего не меняется. Например

    find("canStreamHighQuality").canStreamHighQuality = (e) => true;
    console.log(find("canStreamHighQuality").canStreamHighQuality)

    выдает f(e){return te(Z,e)} а не (e) => true