Как пропатчить закрытую программу для Linux, распространяемую в виде AppImage
Иногда бывает нужно внести изменения в программу, исходный код которой недоступен. Сейчас я сижу под Linux, где закрытые программы часто распространяются в виде пакетов AppImage. Предлагаю разобраться, как пропатчить такую программу. Материал не претендует на новизну, но может представлять интерес для начинающих кодокопателей.
В качестве примера возьмем программу Arinst Virtual Lab, она же ArinstVL. Это GUI-клиент к различному оборудованию производства компании Arinst. Я использую данную программу с векторным анализатором цепей ARINST VNA-PR1, в частности, чтобы снимать скриншоты для статей. Программа распространяется бесплатно, однако ее исходный код закрыт. Надеюсь, что сотрудники Arinst не расстроятся из-за того, что я выбрал их программу для примера. Это одна из наиболее часто используемых мной закрытых программ. Поэтому ее работу в особенности хочется улучшить, не дожидаясь обновлений.
За время использования ArinstVL я выявил два дефекта:
- Программа проверяет наличие обновлений при каждом запуске. Само по себе это не является проблемой. Но если сайт с обновлениями недоступен, то в течение примерно минуты после запуска выводятся сообщения об ошибках. Начать пользоваться программой можно только спустя эту минуту;
- Главному окну присваивается 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:
Строка используется только в одном месте:
Проматываем ассемблерный листинг вверх, чтобы найти начало функции. Вновь используем Show X-Refs, чтобы найти все места, из которых функция вызывается. Находим единственное место:
Теперь есть два варианта действий. Вариант первый: досконально изучить ассемблерный листинг, и только потом что-то патчить. Вариант второй: сразу пропатчить код и посмотреть, что будет. Я довольно уверен, что найденная функция – это checkForUpdates(). Если убрать ее вызов, то ArinstVL перестанет проверять обновления. Поэтому я делаю выбор в пользу второго варианта.
Переходим во вкладку Hexdump, и в контекстном меню ищем «Write hex bytes»:
В появившемся текстовом поле вводим:
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.