Перейти к содержимому

Антидетект-браузер: тест отпечатка выявил несогласованный canvas и webgl

Мы проверили платный антидетект-браузер и нашли несогласованный отпечаток. После этого создали открытый аналог

Антидетект-браузеры используют агентства, рекламные команды и продавцы на маркетплейсах, которым требуется управлять множеством аккаунтов с одного компьютера. Для каждого профиля они назначают отдельные cookies, прокси, параметры операционной системы и набор характеристик браузера. Обычно подписка на такой сервис обходится в 30-150 долларов в месяц, а исходный код почти всегда закрыт.

В рекламном интерфейсе пользователю показывают зелёные статусы: "Canvas защищён", "WebGL защищён", "Отпечаток подменён". Однако эти отметки не отвечают на главный вопрос: какие именно данные получает сайт и согласованы ли они между собой.

Мы решили проверить это экспериментально. На одном Mac рядом запустили обычный Chrome с чистым профилем и популярный платный антидетект-браузер. Затем сравнили примерно 250 характеристик, связанных с цифровым отпечатком. Результат оказался неожиданным: часть параметров действительно была изменена, но один из наиболее важных векторов - canvas - полностью совпал с настоящей машиной, байт в байт.

Название продукта намеренно не приводится. Оно не имеет принципиального значения: описанную проверку можно повторить самостоятельно практически с любым антидетектом. Именно результаты эксперимента стали причиной создать Fury - открытый форк Chromium, в котором подмена выполняется на уровне C++, там, где формируется исходное значение, а не посредством JavaScript-инъекций.

Как проводилось измерение

Для теста мы подготовили несколько инструментов. Главный из них - `probe.js`. Скрипт собирает около 250 параметров:

- свойства `navigator`;
- размеры экрана и окна;
- результат `canvas.toDataURL()`;
- значения WebGL, включая `getParameter()` и `readPixels()`;
- ограничения WebGPU;
- характеристики `AudioContext`;
- результаты `getClientRects()`;
- метрики шрифтов;
- список устройств из `enumerateDevices()`;
- доступные голоса `speechSynthesis`;
- часовой пояс и данные `Intl`;
- состояние permissions.

После этого те же проверки выполняются из Worker, обычного iframe, iframe с другим источником и дополнительных контекстов. Такой подход позволяет увидеть не только заявленные значения, но и различия в поведении API.

Скрипт `capture-chrome.sh` запускает браузер с временным чистым профилем, открывает тестовую страницу и сохраняет результат в каталог с эталонами. Чистый профиль необходим: расширения, накопленные cookies и пользовательские настройки способны изменить результат и исказить сравнение.

Для сопоставления используется `fury-detect diff`. Простое сравнение показывает сотни расхождений, но само по себе оно мало полезно. Важно разделять изменения идентичности и изменения поведения.

Какие различия допустимы

К идентификационным параметрам относятся хеш canvas, строка GPU, User-Agent, перечень шрифтов и другие значения, которые антидетект как раз должен уметь менять. Если такие показатели отличаются между обычным браузером и профилем, это ещё не означает ошибку.

Гораздо опаснее поведенческие расхождения:

- API присутствует в одном окружении и отсутствует в другом;
- функция перестала выглядеть нативной;
- кодек заявлен, но фактически не поддерживается;
- Worker возвращает результат, отличный от главного окна;
- один и тот же параметр имеет разные значения в iframe и верхнем документе;
- заявленная видеокарта не соответствует данным, которые выдаёт рендеринг.

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

Что показал тест платного продукта

На проверенном профиле браузер менял несколько заметных параметров:

- строку WebGL renderer - с M5 на M1 Pro;
- аудиохеш;
- список голосов синтеза речи;
- `hardwareConcurrency` - с 10 до 8;
- `deviceMemory` - с 16 до 8;
- часовой пояс.

При этом ряд критически важных характеристик остался полностью таким же, как у чистого Chrome на том же Mac:

| Вектор | Результат |
|---|---|
| `canvas.toDataURL()` | Совпадает с чистой системой |
| `getClientRects()` | Совпадает |
| `WebGL readPixels()` | Совпадает |
| Метрики шрифтов | Совпадают |
| Ограничения WebGPU | Сохраняют реальные значения |
| `enumerateDevices()` | Не демонстрирует полноценной изоляции |

Особенно важен canvas. Это один из самых устойчивых пассивных сигналов, который сайты могут получать без специальных разрешений. Если несколько профилей на одной машине возвращают одинаковый canvas-хеш, их можно связать между собой. Более того, они могут совпасть с обычным личным Chrome пользователя.

Таким образом, интерфейс антидетекта сообщает о защите, а наиболее проверяемый компонент отпечатка фактически остаётся настоящим.

Есть важная оговорка: параметры задаются отдельно для каждого профиля, а измерен был один конкретный профиль. Теоретически в нём могла быть включена настройка вроде "реальный canvas". Но пользователь не получает ясного предупреждения, что защита отключена. Следовательно, проблема касается не только профиля, но и прозрачности самого продукта.

Несогласованный WebGL выдаёт подмену

Вторая проблема оказалась менее очевидной, но потенциально более показательной. Браузер заменяет строку WebGL renderer на M1 Pro, однако `readPixels()` возвращает пиксели, рассчитанные настоящим графическим процессором M5.

Аналогичное расхождение обнаружилось в WebGPU: архитектура была объявлена как `common-3` вместо `metal-3`, но десятки лимитов остались от реального чипа. В результате рядом находятся два противоречащих друг другу сигнала.

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

Почему JavaScript-подмена даёт слабый результат

Многие антидетекты внедряют JavaScript-код, который перехватывает вызовы API и возвращает заранее подготовленные значения. Такой подход удобен для быстрого прототипирования, но у него есть ограничения.

Во-первых, сайт может проверить, является ли функция действительно нативной. Во-вторых, подмена часто действует только в главном окне и забывает о Worker, iframe или других контекстах. В-третьих, внешний результат может быть изменён, а внутренний процесс, который его породил, - нет.

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

Подмена на уровне C++ позволяет вмешиваться ближе к источнику данных. Однако и такой подход не решает проблему автоматически: необходимо сохранять согласованность параметров между разными API, потоками исполнения и версиями Chromium.

Как устроен Fury

Fury создаётся как открытый форк Chromium. Основная идея проекта - не маскировать уже сформированный ответ с помощью скрипта, а изменять источник значения внутри браузера.

Это позволяет контролировать:

- одинаковое поведение главного окна и Worker;
- единые ответы для iframe;
- согласованные параметры графики;
- нативность функций;
- связь между заявленными возможностями и фактическим результатом;
- повторяемость отпечатка в разных контекстах.

Работа над проектом началась с измерительного инструментария. Сначала появилась система тестов и эталонов, затем - изменения в исходном коде браузера. Такой порядок принципиален: без объективных измерений легко потратить месяцы на исправление компонента, который вообще не является главным источником утечки.

Чего в проекте пока нет

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

- покрыты не все API Chromium;
- не для каждого параметра подготовлены профили поведения;
- возможны расхождения между версиями браузера;
- ещё требуется расширять набор автоматических тестов;
- необходимо проверять работу на разных системах и графических чипах;
- часть функций пока не имеет полноценной изоляции.

Открытый код, однако, даёт важное преимущество: можно увидеть, какие именно изменения внесены, воспроизвести тесты и самостоятельно проверить заявленный уровень защиты.

Как повторить проверку самостоятельно

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

После этого стоит проверить не только хеши и строки, но и поведение:

1. Сравнить `canvas.toDataURL()` и `getImageData()`.
2. Проверить WebGL renderer и `readPixels()`.
3. Повторить тесты в Worker.
4. Открыть страницу в нескольких видах iframe.
5. Сопоставить шрифты и размеры текста.
6. Проверить WebGPU limits.
7. Сравнить список устройств и медиавозможности.
8. Проверить доступность API и нативность функций.
9. Найти параметры, которые противоречат друг другу.

Главный критерий - не количество изменённых значений. Хорошая система должна формировать цельный, непротиворечивый профиль. Если браузер меняет отдельные строки, но оставляет реальные пиксели, лимиты и метрики, его защита легко превращается в дополнительный сигнал для обнаружения.

Именно поэтому проверять антидетект следует не по зелёным индикаторам в интерфейсе, а по согласованности всего набора характеристик. Canvas, WebGL, WebGPU, аудио, шрифты, Worker и iframe должны описывать одну и ту же виртуальную среду. Если они рассказывают сайту разные истории, скрыть связь между профилями не получится.

Прокрутить вверх