image
image

Всё верно — это маленькое устройство стояло между мной и возможностью запускать ещё более старое ПО, которое я раскопал в процессе программной археологии.

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

Это ПО было написано на языке программирования RPG (Report Program Generator), который старше Кобола (!); он использовался в компьютерах IBM среднего ценового диапазона наподобие System/3, System/32 и вплоть до AS/400. Похоже, позже RPG портировали в MS-DOS, поэтому те же программные инструменты, написанные на RPG, могут работать на персональных компьютерах. Так фирма и оказалась в этой ситуации.

Эта бухгалтерская фирма работала на компьютере с Windows 98 (да, в 2026 году) и запускала написанное на RPG ПО в консольном окне DOS. Оказалось, что для работы ПО требовалось подключить к параллельному порту компьютера специальный аппаратный донгл защиты от копирования! В те времена это было достаточно распространённой практикой, особенно у поставщиков «корпоративного» ПО, защищавшего свои очень важные™ программы от неавторизованного применения.

image

К сожалению, большинство текста и маркировок на этикетке донгла уже стёрлось, но у нас есть несколько зацепок:

  • Слова «Stamford, CT» и, с большой долей вероятности, логотип компании «Software Security Inc». Единственное свидетельство существования этой компании — статья о демонстрации её продуктов на конференциях SIGGRAPH в начале 1990-х, а также множество патентов, связанных с защитой ПО.

  • Слово, похожее на «RUNTIME», которое можно едва разглядеть.

Первым делом я создал образ диска PC c Windows 98, на котором работало это ПО, и запустил его в эмуляторе, чтобы можно было увидеть, что делает ПО, и, вероятно, экспортировать данные из ПО в более современный формат для использования в современных бухгалтерских программах. Но, разумеется, для всего этого требуется аппаратный донгл; похоже, ни один из бухгалтерских инструментов без его подключения не работал.

Прежде, чем приступать к работе, я поискал на образе диска любые интересные подсказки, и обнаружил множество любопытных (и археологически ценных?) вещей:

image
  • У нас есть компилятор языка RPG II (отлично!), созданный компанией Software West Inc.

  • Более того, на диске есть две версии компилятора RPG II, выпущенные в 1990-х той же Software West.

  • Есть полный исходный код бухгалтерского ПО, написанный на RPG. Похоже, полный бухгалтерский пакет состоит из множества модулей RPG, управляемых bat-файлами DOS; всё это выстроено в систему «меню», по которым пользователь перемещается, вводя числовые комбинации. Очевидно, что автор этой бухгалтерской системы изначально программировал мейнфреймы IBM и твёрдо решил перенести свои навыки в DOS, но результат оказался спорным.

Я начал изолированные эксперименты с компилятором RPG и очень быстро осознал, что аппаратный донгл требуется самому компилятору RPG, который автоматически инъецирует ту же логику защиты от копирования во все генерируемые им исполняемые файлы. Это объясняет надпись (предположительную) «RUNTIME» на д��нгле.

Компилятор состоит из нескольких исполняемых файлов, в частности, из RPGC.EXE, (компилятора) и SEU.EXE (редактора исходников, Source Entry Utility). Вот, что мы получаем спустя несколько секунд после запуска SEU без донгла:

image

Немного грубо, но это даёт нам важную зацепку: эта программа несколько секунд пытается обмениваться данными по параллельному порту (что может позволить нам приостановить её для отладки и посмотреть, что происходит в это время), а затем выполняет выход с сообщением (которое мы теперь можем найти в дизассемблированном коде программы и оттрасировать, как она попадает в это место).

Отличный инструмент для дизассемблирования таких винтажных испо��няемых файлов — это Reko. Он понимает исполняемые файлы 16-битного реального режима и даже пытается декомпилировать их в читаемый код на C, соответствующий дизассемблированному.

image

Изучая декомпилированный/дизассемблированный код в Reko, я ожидал найти команды in и out, которые стали бы признаками того, что программа пытается коммуницировать с параллельным портом через порты ввода-вывода PC. Однако… я нигде не смог найти этих команд! Но затем я кое-что заметил: Reko дизассемблировал исполняемый файл в два «сегмента»: 0800 и 0809, а я исследовал только сегмент 0809.

image

При изучении сегмента 0800 мы находим улику: команды in и out, означающие, что подпрограмма защиты от записи определённо находится здесь; более того, весь сегмент кода имеет размер всего 0x90 байт, а значит, подпрограмму достаточно легко будет разобрать и понять. По какой-то причине Reko не смог декомпилировать этот код в код на C, но всё равно создал дизассемблированный код, которого нам вполне хватит. Возможно, это был примитивный вид обфускации той эпохи, который запутал Reko и не позволил ему ассоциировать этот блок кода с остальной частью программы... кто знает.

Дизассемблированный код вместе с моими аннотациями и примечаниями можно посмотреть в GitHub Gist. Я уже подзабыл ассемблер x86, но вкратце опишу, что делает этот код:

  • Это совершенно точно единая автономная подпрограмма, предназначенная для вызова при помощи «дальней» команды CALL, потому что она выполняет возврат командой RETF.

  • Сначала она определяет адрес параллельного порта считыванием области данных BIOS. Если у компьютера несколько параллельных портов. то донгл должен быть подключён к первому (LPT1).

  • Подпрограмма выполняет цикл, в котором записывает значения в регистр данных параллельного порта, а затем считывает регистр статуса и накапливает ответы в регистрах BH и BL.

  • К концу подпрограммы «результат» всей процедуры сохраняется в регистр BX (BH и BL вместе), который, предположительно, будет «проверяться» стороной, вызвавшей подпрограмму.

  • Очень важно, что в эту подпрограмму, похоже, не выполняется никакого «ввода». Она не извлекает ничего из стека и её не волнует, какие значения регистров ей передаются. И это может значить только, что результат этой подпрограммы полностью константный! Какой бы сложный обмен данными она ни выполняла с донглом, результат всегда должен оставаться одинаковым.

Зная, что эта подпрограмма должна выполнить выход с неким магическим значением, сохранённым в BX, мы можем пропатчить первые несколько байт подпрограммы, чтобы именно это и сделать! Не зная пока, какое значение нужно поместить в BX, давайте начнём с 1234:

BB 34 12       MOV BX, 1234h
CB             RETF

Пропатчить нужно всего четыре первых байта — присвоить BX нужное нам значение и выполнить выход (RETF). Если запустить пропатченный исполняемый файл с этими новыми байтами, то он всё равно прекратит выполнение с тем же сообщением (что ожидаемо), но выполнение прекращается сразу, а не через несколько секунд общения с параллельным портом. Прогресс!

Пошагово исследуя дизассемблированный код, мы получим ещё одну важную улику: единственное значение, которое может иметь BH к концу подпрограммы, равно 76h (оно жёстко прописано в подпрограмме). То есть суммарное значение магического числа в BX должно иметь вид 76xx. Иными словами, неизвестным остаётся только значение BL:

BB __ 76       MOV BX, 76__h
CB             RETF

Так как BL — это 8-битный регистр, у нас есть всего 256 возможных значений. А что мы делаем, если проверить нужно всего 256 комбинаций? Брутфорсим их! Я написал скрипт, который подставляет значение в этот конкретный байт (от 0 до 255), программно запускает исполняемый файл в DosBox и наблюдает за выводом. Сработало! Брутфорс не занял много времени, потому что нужным числом оказалось… 6. То есть итоговое магическое число в BX должно быть равно 7606h:

BB 06 76       MOV BX, 7606h
CB             RETF
image

Бинго!

После изучения других исполняемых файлов в пакете компилятора выяснилось, что подпрограмма параллельного порта одинакова. У всех исполняемых файлов одна и та же логика защиты от копирования. Когда компилятор (RPGC.EXE) компилирует исходный код на RPG, он, похоже, копирует подпрограмму параллельного порта из себя в компилируемую программу. И да, пропатченная версия компилятора будет генерировать ту же самую пропатченную подпрограмму защиты от копирования! Очень удобно.

Должен сказать, что этот механизм защиты от копирования выглядит немного... упрощённым. Аппаратный донгл, который просто возвращает постоянное число? И защита, которую можно обойти четырёхбайтным патчем? Действительно ли оно стоит патента? Но кто я такой, чтобы судить. Возможно, я не полностью разобрался в логике, и защита от копирования снова всплывёт каким-то иным образом. Также возможно, что создатели компилятора RPG (Software West, Inc) не в полной мере воспользовались преимуществами аппаратного донгла и использовали его легко обходимым способом.

Как бы то ни было, теперь компилятор RPG II компании Software West свободен от привязки к донглу параллельного порта! Вскоре я собираюсь заняться удалением из папок компилятора всей личной информации, чтобы выложить этот компилятор как артефакт компьютерной истории. Похоже, в вебе его больше нигде нет. Если кто-то из читателей статьи связан с Software West Inc, то напишите мне, у меня есть много вопросов!