Как продлить подписку на фотошоп в России, или обходим защиту Adobe за 5 минут

Год назад я купил себе лицензионный фотошоп на свой 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.
powered by OpenBSD
© ch1p 2021