← На главную

Как пропатчить закрытую программу для Linux, распространяемую в виде AppImage

Иногда бывает нужно внести изменения в программу, исходный код которой недоступен. Сейчас я сижу под Linux, где закрытые программы часто распространяются в виде пакетов AppImage. Предлагаю разобраться, как пропатчить такую программу. Материал не претендует на новизну, но может представлять интерес для начинающих кодокопателей.

В качестве примера возьмем программу Arinst Virtual Lab, она же ArinstVL. Это GUI-клиент к различному оборудованию производства компании Arinst. Я использую данную программу с векторным анализатором цепей ARINST VNA-PR1, в частности, чтобы снимать скриншоты для статей. Программа распространяется бесплатно, однако ее исходный код закрыт. Надеюсь, что сотрудники Arinst не расстроятся из-за того, что я выбрал их программу для примера. Это одна из наиболее часто используемых мной закрытых программ. Поэтому ее работу в особенности хочется улучшить, не дожидаясь обновлений.

За время использования ArinstVL я выявил два дефекта:

  1. Программа проверяет наличие обновлений при каждом запуске. Само по себе это не является проблемой. Но если сайт с обновлениями недоступен, то в течение примерно минуты после запуска выводятся сообщения об ошибках. Начать пользоваться программой можно только спустя эту минуту;
  2. Главному окну присваивается WM_CLASS со значением «AppRun.wrapped». Это дэфолтное значение для пакетов AppImage, в связи с чем велика вероятность коллизии с другими пакетами. Так у меня в системе ArinstVL отображается в Dock с иконкой от Qucs-S, потому что последний использует тот же WM_CLASS;

Попробуем исправить названные дефекты. Сначала отключим проверку обновлений, а затем поправим WM_CLASS.

AppImage представляет собой исполняемый файл. В себе он содержит образ файловой системы, которая монтируется на время работы программы (см вывод df -h). Файловая система хранит программу и ее зависимости. Таким образом, AppImage работает на разных дистрибутивах Linux без установки и без конфликта зависимостей.

Патчить непосредственно AppImage лишено особого смысла. Первым делом его нужно распаковать:

./ArinstVL_2.10.1.AppImage --appimage-extract

Получаем каталог squashfs-root/. Теперь программу можно запускать так:

./squashfs-root/AppRun

... или так:

./squashfs-root/usr/bin/ArinstVL

При этом в консоли видим:

(...пропущено...) DeviceConnector >> restoreSettings.. MainWindow >> restoreSettings.. PCShSettings >> restoreSettings.. AppUpdater >> trying to download manifest: http://arinst.ru/arinst_vl/manife... AppUpdater >> downloaded new manifest AppUpdater >> Updating manifest data.. AppUpdater >> selected update channel: release AppUpdater >> actual version is already installed, no app update is required MainWindow >> closing application.. (...пропущено...)

Отладочный вывод здесь весьма кстати. С ним будет проще найти код, отвечающий за проверку обновлений. Не могу не заострить внимание на том, что программа ходит за обновлениями по HTTP. Это не очень-то безопасно.

Для внесения изменений в исполняемый файл воспользуемся бесплатным и открытым (GPL 3.0) дизассемблером Cutter. Последнюю версию дизассемблера можно найти на GitHub, в разделе с релизами.

Открываем в Cutter программу ./squashfs-root/usr/bin/ArinstVL. Дизассемблеру нужно некоторое время на анализ исполняемого файла. По завершении анализа находим в Cutter вкладку Strings. Вводим в поиске «trying to download manifest». Такая строка в файле одна. Переходим к месту ее определения.

Смотрим, где эта строка используется. Для этого в контекстном меню выбираем Show X-Refs:

Поиск перекресных ссылок в дизассемблере Cutter

Строка используется только в одном месте:

Найденное место использования строки в дизассемблере Cutter

Проматываем ассемблерный листинг вверх, чтобы найти начало функции. Вновь используем Show X-Refs, чтобы найти все места, из которых функция вызывается. Находим единственное место:

Найденное место использования заданной функции в Cutter

Теперь есть два варианта действий. Вариант первый: досконально изучить ассемблерный листинг, и только потом что-то патчить. Вариант второй: сразу пропатчить код и посмотреть, что будет. Я довольно уверен, что найденная функция – это checkForUpdates(). Если убрать ее вызов, то ArinstVL перестанет проверять обновления. Поэтому я делаю выбор в пользу второго варианта.

Переходим во вкладку Hexdump, и в контекстном меню ищем «Write hex bytes»:

Редактирование бинарных данных в дизассемблере Cutter

В появившемся текстовом поле вводим:

9090909090

Вызов функции кодируется пятью байтами. Мы заменили его на пять инструкций nop. Инструкция кодируется одним байтом и ничего не делает. Сохраняем результат.

Проверяем логи после патча:

(...пропущено...) DeviceConnector >> restoreSettings.. MainWindow >> restoreSettings.. PCShSettings >> restoreSettings.. MainWindow >> closing application.. (...пропущено...)

Как будто проверки обновлений больше нет. Но что, если мы пропатчили только логирование? Можно ли удостовериться, что ArinstVL действительно перестал ходить в сеть? Для этого воспользуемся сниффером.

Вывод сниффера при запуске оригинальной программы:

$ sudo /home/eax/bin/eaxsniff lo 'udp src or dst port 53' Listening lo, filter: udp src or dst port 53... 127.0.0.1:33765 -> 127.0.0.53:53, 38 (0x26) bytes 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 0000 1A CE 01 20 00 01 00 00 00 00 00 01 06 61 72 69 ... .........ari 0010 6E 73 74 02 72 75 00 00 01 00 01 00 00 29 04 B0 nst.ru.......).. 0020 00 00 00 00 00 00 ...... (...пропущено...) $ sudo /home/eax/bin/eaxsniff wlp0s20f3 'tcp src or dst port 80' Listening wlp0s20f3, filter: tcp src or dst port 80... 192.168.88.21:48292 -> 176.99.4.57:80, 168 (0xa8) bytes 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 0000 47 45 54 20 2F 61 72 69 6E 73 74 5F 76 6C 2F 6D GET /arinst_vl/m 0010 61 6E 69 66 65 73 74 5F 76 31 2E 79 61 6D 6C 20 anifest_v1.yaml 0020 48 54 54 50 2F 31 2E 31 0D 0A 48 6F 73 74 3A 20 HTTP/1.1..Host: 0030 61 72 69 6E 73 74 2E 72 75 0D 0A 43 6F 6E 6E 65 arinst.ru..Conne 0040 63 74 69 6F 6E 3A 20 4B 65 65 70 2D 41 6C 69 76 ction: Keep-Aliv 0050 65 0D 0A 41 63 63 65 70 74 2D 45 6E 63 6F 64 69 e..Accept-Encodi 0060 6E 67 3A 20 67 7A 69 70 2C 20 64 65 66 6C 61 74 ng: gzip, deflat 0070 65 0D 0A 41 63 63 65 70 74 2D 4C 61 6E 67 75 61 e..Accept-Langua 0080 67 65 3A 20 65 6E 2D 55 53 2C 2A 0D 0A 55 73 65 ge: en-US,*..Use 0090 72 2D 41 67 65 6E 74 3A 20 4D 6F 7A 69 6C 6C 61 r-Agent: Mozilla 00A0 2F 35 2E 30 0D 0A 0D 0A /5.0.... (...пропущено...)

После применения патча видим, что программа больше не резолвит доменное имя и не ходит по HTTP. Все указывает на то, что догадка по поводу checkForUpdates() была верна. Патч делает то, что нужно.

Осталось поправить WM_CLASS. Вот только я не знаю, откуда программа берет его значение. В исполняемом файле строки «AppRun.wrapped» не нашлось. Если внимательно посмотреть на содержимое squashfs-root/, то в нем мы видим скрипт AppRun, который вызывает скрипт AppRun.wrapped, который вызывает ArinstVL.

Переименуем скрипт AppRun.wrapped и посмотрим, что будет:

$ cd squashfs-root/ $ mv AppRun.wrapped ArinstVL.wrapped

Не забываем отредактировать скрипт AppRun:

# было: # exec "$this_dir"/AppRun.wrapped "$@" # стало: exec "$this_dir"/ArinstVL.wrapped "$@"

Чтобы собрать новый AppImage, воспользуемся утилитой appimagetool. Утилита доступна на GitHub, в разделе с релизами. Пользоваться ею просто:

$ mv ArinstVL_2.10.1.AppImage ArinstVL_2.10.1.AppImage.orig $ ~/Applications/appimagetool.AppImage ./squashfs-root ArinstVL_2.10.1.AppImage

Запускаем пропатченный AppImage. Выполняем команду:

$ xprop WM_CLASS

Тыкаем курсором мыши в окно ArinstVL. В выводе xprop видим, что значение WM_CLASS изменилось на ArinstVL.wrapped. Значит, все сделано правильно.

Остались последние штрихи. Копируем иконку:

$ cp ./squashfs-root/app_icon.png ~/Applications/ArinstVL.png

... и создаем ~/.local/share/applications/ArinstVL.desktop:

[Desktop Entry] Name=ArinstVL StartupWMClass=ArinstVL.wrapped Exec=/home/eax/Applications/ArinstVL_2.10.1.AppImage Icon=/home/eax/Applications/ArinstVL.png Type=Application Categories=Science;Development Comment=Management software for mainline Arinst devices

Теперь иконка правильно отображается в Dock, а также в списке установленных приложений.

Подведем итоги. Даже если исходники программы недоступны, то устранить дефекты в ней можно без помощи компании-разработчика. Как говорится, если вы знаете ассемблер, то любая программа для вас – open source.