Мы проверили платный антидетект-браузер и нашли несогласованный отпечаток. После этого создали открытый аналог
Антидетект-браузеры используют агентства, рекламные команды и продавцы на маркетплейсах, которым требуется управлять множеством аккаунтов с одного компьютера. Для каждого профиля они назначают отдельные 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 должны описывать одну и ту же виртуальную среду. Если они рассказывают сайту разные истории, скрыть связь между профилями не получится.
