Как продлить подписку на фотошоп в России, или обходим защиту 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:

Network settings, proxies tab

Теперь надо открыть браузер и зайти на mitm.it. Если всё хорошо и трафик идет через mitmproxy, откроется локальный сайт, где можно будет скачать сертификат CA, которым mitmproxy будет на лету подписывать другие сертификаты. Там же будет и инструкция, как добавить этот CA в доверенные:

Добавили:

Теперь запускаем фотошоп и смотрим в лог mitmproxy. В нем должны появиться запросы, которые фотошоп посылает на серверы Adobe.

mitmproxy requests log

Нас интересует 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, используемый в дальнейшем.

Request
Response

В третьем запросе, клиент отправляет данные на https://lcs-cops.adobe.io/asp/nud/v4, авторизуясь полученным access_token, и, по-видимому, получает некий статус пользователя и его подписки и инструкцию, что делать дальше, всё так же в JSON.

Request
Response

Для нас ключевым является объект 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


Весь код лежит тут.

If you have any comments, contact me by email.