Как продлить подписку на фотошоп в России, или обходим защиту Adobe за 5 минут
UPD: по состоянию на ноябрь 2022, хак всё ещё работает даже на новых версиях (Photoshop 24, Illustrator 27), обновления тоже работают.
Введение
Год назад я купил себе лицензионный фотошоп на свой MacBook Air на M1. Хотя идеи покупать софт и платить за байты мне не близки, я готов заплатить за хороший, качественный продукт, open source аналогов которому нет. Например, я честно купил лицензию на IDE от JetBrains и ни секунды об этом не жалел.
Нынче же, однако, зарубежные IT-компании одна за другой отказываются работать в России, с гражданами РФ, с российскими компаниями. Двойные стандарты, дискриминация по национальному признаку и прочие культуры отмены сегодня в моде, Adobe присоединилась к вечеринке, подписка на мой фотошоп закончилась, а продлить - нельзя. Что ж, не хотите моих денег - не надо. Считаю, что отныне все российские айтишники с чистой совестью могут "отменять" интеллектуальные права западных IT-компаний, а руководству России пора, наконец, реализовать то самое предложение Павла Дурова.
Но это всё лирика.
Итак, Adobe Photoshop больше не работает
Меня встречает красная плашка (справа сверху), сообщающая о том, что Subscription ended, а затем появляется блокирующее окно с кнопками "продлить" (открывает браузер) и "закрыть фотошоп", которое нельзя свернуть, скрыть или каким-либо иным образом обойти. Если закрыть окно, закроется и весь фотошоп.
Для начала проверим пару совсем уж детских трюков. Переводим время на неделю назад - не помогает, вылезает похожее окно с текстом, мол, у вас рассинихрон времени, не могу продолжить. Отключаем интернет или блокируем доступ к хостам Adobe - не помогает, вылезает такое же окно с жалобой на отключенный интернет. Ладно.
Я знаю, что вроде бы фотошоп испокон веков ломают патчингом atmlib. В дизассемблер я, конечно, тоже могу, но что-то мне подсказывает, что тут всё намного проще. К тому же, патчить код - крайняя мера, сродни хирургической операции, которая ещё и влечёт за собой проблемы типа необходимости переподписывания всего и вся своим сертификатом (иначе Gatekeeper не пропустит) и т.д.
Дело в том, что между открытием фотошопа и появлением окошка о просроченной подписке проходит некоторое время (2-3 секунды), в течение которых интерфейс полностью доступен и всё работает. Очевидно, что в это время идут запросы к серверам Adobe и проверяется лицензия, и когда приложение получает ответ, оно и показывает блокирующее окно. Превращением фотошопа в SaaS и привязкой к Creative Cloud они сделали его поведение зависимым от ответов сервера, а их очень легко подменить, если конечно там нету SSL Pinning или чего-нибудь подобного (спойлер: нету). Давайте же заглянем в трафик!
Смотрим трафик
На дворе 2022. Ясен пень, всё зашифровано, но это обычный https, так что для начала нам понадобится mitmproxy
. Его можно поставить из Homebrew:
$ brew install mitmproxy
Запускаем:
$ mitmproxy --mode socks5 --showhost
В данный момент mitmproxy
слушает порт 8080
, давайте временно настроим его, как системный прокси. Заходим в настройки сети, включаем на вкладке Proxies галочку "SOCKS Proxy" и прописываем туда 127.0.0.1:8080
:
Теперь надо открыть браузер и зайти на mitm.it. Если всё хорошо и трафик идет через mitmproxy, откроется локальный сайт, где можно будет скачать сертификат CA, которым mitmproxy будет на лету подписывать другие сертификаты. Там же будет и инструкция, как добавить этот CA в доверенные:
Добавили:
Теперь запускаем фотошоп и смотрим в лог mitmproxy. В нем должны появиться запросы, которые фотошоп посылает на серверы Adobe.
Нас интересует 2-й (https://*.adobelogin.com/img/token/v1
, для общего развития) и 3-й (https://lcs-cops.adobe.io/asp/nud/v4
, это ключевой для нас запрос).
Во втором запросе (на https://*.adobelogin.com/img/token/v1
), клиент отправляет данные устройства, в ответ получает данные пользователя (в JSON) и access_token
, используемый в дальнейшем.
В третьем запросе, клиент отправляет данные на https://lcs-cops.adobe.io/asp/nud/v4
, авторизуясь полученным access_token
, и, по-видимому, получает некий статус пользователя и его подписки и инструкцию, что делать дальше, всё так же в JSON.
Для нас ключевым является объект workflow
, в котором, собственно, и находится инструкция открыть блокирующее окно браузера, заставляющее продлить лицензию (действие, возможность осуществления которого, напомню, Adobe нас лишил).
Если слегка отредактировать ответ сервера на этот запрос и вернуть вместо workflow
пустой объект, то... проблема решена! Никакое окно не открывается, ничего не блокируется, фотошоп работает, как и раньше. Кстати, это прокатывает и с иллюстратором. А самое смешное, что обновления через Creative Cloud Desktop App продолжают работать, как и обновлённый фотошоп. Проверено на Photoshop 23.3.1 и Illustrator 26.2.1.
Подменяем ответ сервера
Чтобы подменить ответ сервера, надо написать небольшой скрипт для mitmproxy, буквально несколько строк на питоне:
import json
from mitmproxy import http
def response(flow: http.HTTPFlow) -> None:
if flow.request.path.startswith("/asnp/nud/v") and flow.response and flow.response.content:
data = json.loads(flow.response.content)
data['workflow'] = {}
flow.response.text = json.dumps(data, separators=(',', ':'))
Чтобы подключить сей скрипт, нужно перезапустить mitmproxy, передав путь к скрипту в параметре -s
:
$ mitmproxy --mode socks5 -s ~/path/to/adobe-magic.py
Перезапустите фотошоп, и если всё сработало, никаких блокирующих окон о закончившейся подписке вы не увидите!
На всякий случай: если вдруг скрипт не работает, ошибки могут быть в event-логе, который открывается через Shift+E. Дебажить можно тоже через него.
От прототипа к рабочему решению
mitmproxy, запущенный в терминале... system-wide прокси, подменяющий все сертификаты... всё это, конечно, неудобно. Теперь надо придумать простое готовое решение, желательно, не требующее root-прав, отключения SIP для подгрузки dylib, колдунств с файрволом ядра pf и т.д.
Я придумал такое: PAC-файл + пользовательский сервис.
Если вы знаете другое решение, не требующее рута и всего прочего, сообщите мне на почту и я добавлю его в статью.
launchd позволяет создавать и запускать пользовательские сервисы, положив plist с описанием сервиса в ~/Library/LaunchAgents
. Создайте там файл io.ch1p.adobe-magic.plist
с таким содержанием:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>io.ch1p.adobe-magic</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/mitmdump</string>
<string>--mode</string>
<string>socks5</string>
<string>--listen-host</string>
<string>127.0.0.1</string>
<string>--listen-port</string>
<string>56789</string>
<string>-q</string>
<string>-s</string>
<string>/path/to/adobe-magic.py</string>
</array>
<key>KeepAlive</key>
<true/>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
Несколько важных пояснений:
mitmdump
- это, по сути, headless версия mitmproxy;- путь к
mitmdump
должен быть абсолютным (у launchd в$PATH
не будет brew). Если путь к бинарникам, установленным из homebrew у вас отличается от моего, скорректируйте его; - я выбрал порт
56789
просто потому, что почему бы и нет. Можете его поменять. - путь к скрипту
adobe-magic.py
тоже должен быть абсолютным
Запустить и остановить сервис можно через launchctl
.
Теперь PAC-файл. Тут всё просто. Отправляем на нашу проксю все адобовские хосты (вместе со всеми поддоменами), которые были замечены в логе mitmproxy.
function FindProxyForURL(url, host) {
if ( shExpMatch(host, "*.adobe.com") || host == "adobe.com"
|| shExpMatch(host, "*.adobelogin.com") || host == "adobelogin.com"
|| shExpMatch(host, "*.adobe.io") || host == "adobe.io") {
return "SOCKS 127.0.0.1:56789";
}
return "DIRECT";
}
Ссылку на PAC-файл нужно прописать в тех же настройках, где прописывался системный SOCKS-прокси (который уже пора отключить):
По непонятной причине macOS не принимает локальный путь к PAC-файлу (через протокол file://
), поэтому можете ввести, например, ссылку на файл из моего репозитория: https://git.ch1p.io/adobe-magic.git/plain/proxy.pac
Весь код лежит тут.