Обратное проектирование при запуске 16-битных приложений PC-IBM на последних версиях Windows | Статья в журнале «Юный ученый»

Отправьте статью сегодня! Журнал выйдет 5 октября, печатный экземпляр отправим 9 октября.

Опубликовать статью в журнале

Автор:

Научный руководитель:

Рубрика: Информатика

Опубликовано в Юный учёный №9 (83) октябрь 2024 г.

Дата публикации: 19.09.2024

Статья просмотрена: 7 раз

Библиографическое описание:

Копачев, И. П. Обратное проектирование при запуске 16-битных приложений PC-IBM на последних версиях Windows / И. П. Копачев, С. А. Панов. — Текст : непосредственный // Юный ученый. — 2024. — № 9 (83). — URL: https://moluch.ru/young/archive/83/4635/ (дата обращения: 27.09.2024).

Препринт статьи



В работе описывается методика решения задачи восстановления работоспособности программного обеспечения, разработанного для 16 битных приложений PC-IBM для работы на 64-битных компьютерах под управления операционных систем Windows последних версий.

Ключевые слова: задачи механики, программа СТЕВИН, реинжиниринг, дизассемблирование, эмуляция, тестирование.

Приведенная в работе разработка возникла вследствие поиска программного обеспечения для решения в процессе обучения студентов ВУЗа задач механики. При этом основным требованием, предъявляемым к такому программному обеспечению, было компактность, простой интерфейс, чтобы не затрачивать значительное время на изучение его использования. Наиболее часто упоминаемым таким программным продуктом была программа СТЕВИН [1], разработанная в Московском энергетическом институте в начале двухтысячных годов. Версия такой программы была скачана в интернете, но запуск ее под операционной системой (ОС) Windows 10 оказался неудачным. Поскольку в ОС Windows, начиная с версии ОС Windows 95, выпущенной в 1995 году, полностью исчезла техническая возможность «прямого» запуска 16-битных приложений, разработанных под ОС DOS. На данный момент, в последних версиях операционной системы такая программа просто не сможет функционировать даже при использовании песочниц, по типу Microsoft Sandbox.

Описание этого программного комплекса, его функционал стимулировал поиск решений по возможности восстановления функционирования под 64 битной операционной системой Windows 10.

Решение подобных задач рассматривалось в работах [2,3,4], в общем случае такие задачи можно отнести к реинжинирингу или обратному проектированию [5].

Разработанная методика восстановления работоспособности программы СТЕВИН включала в себя следующие этапы:

  1. Реверс-инжиниринг программы, включающий в себя следующие задачи:
    1. Сбор и подготовка информации о приложении
    2. Исследование целевой платформы и используемых инструкций
    3. Анализ приложения
    4. Систематизация и упорядочивание полученных данных для последующего этапа дизассемблирования
    5. Дизассемблирование приложения
    6. Анализ и интерпретация полученного кода, воссоздание моделей и классов
  2. Разработка кода эмулятора 16-битного приложения, также разделенного на следующие этапы:
    1. Создание проекта и настройка проектируемого эмулятора
    2. Проектирование архитектуры
    3. Разработка ядра
    4. Моделирование окружения DOS
    5. Интеграция с Windows
  3. Тестирование и оптимизация восстановленного программного обеспечения, включающего в себя задачи:
    1. Модульное тестирование
    2. Интеграционное тестирование
    3. Оптимизация кода для максимальной производительности
    4. Тестирование конечной совместимости
  4. Запуск восстановленной программы.

Рассмотрим каждый из выделенных этапов и подходы к его реализации.

  1. Реверс-инжиниринг

1.1 Сбор и подготовка информации о приложении.

Первым шагом является сбор информации о том, что входит в программу. Это могут быть как дополнительные EXE/COM-файлы, так и, например, статические библиотеки (.LIB) и более новые библиотеки динамической компоновки (.DLL). Также важным шагом будет сбор имеющейся документации, описывающую функционал, интерфейс и работу нужного нам приложения.

1.2 Исследование целевой платформы и используемых инструкций.

Для создания эмулятора 16-битного приложения важным будет знание и понимание работы аппаратного окружения, в котором работало 16-битное приложение. Необходимо понимать архитектуру процессора, наборы инструкций, манипуляции с регистрами, а также иметь возможность «прочтения» и понимания ассемблерного низкоуровневого кода на среднем уровне.

1.3 Анализ приложения.

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

1.4 Систематизация и упорядочивание полученных данных для последующего этапа дизассемблирования.

На данном этапе создаётся техническое задание, в котором описаны все действия, которые будет необходимо выполнить в рамках создания проекта. Для дизассемблирования могут использоваться различные программы, например, такие как IDA Pro или Ghidra, которыеиспользуют многие реверс-инженеры.

1.5. Дизассемблирование приложения.

Как только создано техническое задание и установлено необходимое ПО, можно приступать к дизассемблированию.

Код, полученный в программе дизассемблирования (например, в IDA Pro), позволяет изучить логику работы приложения. Анализировать желательно весь полученный дизассемблированный код, в первую очередь изучив использование регистров, инструкции управления потоком, вызовы функций и взаимодействия со средой выполнения (в нашем случае ОС DOS).

1.6 Анализ и интерпретация полученного кода, воссоздание моделей и классов.

В целях полного понимания работы программы, важно воссоздать все используемые ею структуры данных и их взаимосвязи. Сюда входит использование таблиц и массивов, сетевых протоколов, библиотек и определение форматов файлов. Этот этап помогает максимально точно определить, что именно выполняет программа и какие дополнительные зависимости у неё имеются.

  1. Написание кода эмулятора 16-битного приложения

2.1 Создание проекта и настройка будущего эмулятора.

В нашем примере мы будем использовать IDE Visual Studio Community 2022. Эта версия полностью бесплатна и каждый желающий может её скачать. В самом проекте необходимо создать два файла. Собственно,.cpp файл где будет описываться весь функционал нашего эмулятора, и.h файл, где будут объявляться файлы и модульная структура нашего проекта.Вконфигурации необходимо выставить Debug x86, но при «финальной» сборке готового проекта, Debug уже заменим на Release, но менять архитектуру с x86 на x64 крайне нежелательно.

2.2 Проектирование архитектуры.На этом этапе требуется создать модульную структуру, в которой каждый элемент эмулятора будет выполнять нужные нам функции, собственно, для этого и был создан заголовочный файл. В примере ниже (рис. 1) описаны часть основных компонентов, которые нужно определить, в целом, список компонентов значительно больше.

В листинге приведены часть необходимых классов и структур, ниже приведено их описание и назначение:

class CPU : Методы и данные для работы с CPU (здесь объявлена лишь функция выполнения инструкции);

struct Registers : Структура хранения регистров (в примере объявлен лишь один. Конечно же, их в разы больше);

class Memory : Управление сегментированной памятью (объявлены функции прочтения и записи в память);

class IOSystem : Управление системой ввода/вывода;

class Emulator : основной класс эмулятора и управление предыдущими модулями.

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

Листинг с частью разработанных компонентов

Рис. 1. Листинг с частью разработанных компонентов

2.3 Разработка ядра.

Теперь, нужно начать описывать функции, объявленные в нашем заголовочном файле. Для примера приведена функция executeInstruction(Memory& memory), объявленная в классе CPU выше (рис. 2).

Листинг функции executeInstruction(Memory& memory)

Рис. 2. Листинг функции executeInstruction(Memory& memory)

Функция считывает текущий opcode из памяти, после чего выполняет соответствующую инструкцию, используя команду MOV AX, imm16. Теперь память передаётся в качестве параметра для исключения возможности прямого доступа CPU к памяти. Далее, таким же образом, используя конструкцию switch case, можно описать обработку других инструкций, а в случае неизвестной инструкции, будет выполняться код, описанный в default.

2.4 Моделирование окружения DOS

На этапе моделирования окружения эмулятор должен поддерживать основные команды и функции DOS. В примере описывается функция обработки DOS-прерываний, которую необходимо заранее объявить в классе CPU (рис. 3).

Листинг функции обработки DOS-прерываний

Рис. 3. Листинг функции обработки DOS-прерываний

Здесь реализуется обработка прерывания INT 21h, которое часто используется во многих функциях DOS. 0x09 это AH=09h, функция, которая выводит строку на экран. Строка считывается из памяти по адресу, указанному в регистре DX. Конечно же, все необъявленные ранее регистры, необходимо будет объявить в соответствующих местах во избежание ошибок. Далее, как и предыдущую функцию, её можно продолжить, чтобы обработать максимальное число прерываний. Проверить это можно либо при отладке программы, либо при тестировании. Также стоит обратить внимание, что память всё также передаётся как параметр и CPU использует её, не нарушая принцип инкапсуляции.

2.5 Интеграция с ОС Windows.

В этап интеграции входит взаимодействие эмулятора с современной ОС используя WinAPI. На предыдущем этапе описана обработка DOS-прерывания, далее необходимо создать саму среду DOS, т. к. как таковой консоли на данный момент нет. На рис. 4 приведен листинг создания консоли.

Листинг программы создания консоли

Рис. 4. Листинг программы создания консоли

В программе используются функции WinAPI, такие как AllocConsole и freopen_s, которые используются для создания консольного окна и перенаправления вывода эмулируемого приложения в это окно соответственно. Функция запуска эмулятора, естественно, описана не полностью, т. к. программа может выполнять лишь одну команду и инструкцию, так и иметь множество функций и т. д., что требует более детального описания как функций, созданных ранее, так и функции run.

  1. Тестирование и оптимизация

После завершение этапа разработки эмулятора, необходимо проанализировать и протестировать его работу и поведение в самых разных ситуациях. Для тестирования в целом используется модульное и интеграционное тестирование, по результатам которых будет сделан вывод, о правильности работы, а также будет известна эффективность эмулятора при выполнении 16-битных приложений.

3.1 Модульное тестирование

В модульном тестирования осуществляется проверка каждой функциональной части проекта по отдельности, чтобы убедиться в работе всех компонентов и их соответствия ожидаемому результату. Рассмотрим только небольшой набор тестов для двух видов тестирования, например, тест на выполнение инструкций операций с памятью и IO (ввод/вывод) (рис. 5).

Листинг теста памяти

Рис. 5. Листинг теста памяти

Для модульного тестирования используем достаточно простой код, приведенный на рис. 5.

В первой функции теста записывается значение в память, а затем оно считывается, и через функцию assert (содержится в библиотеке cassert), проверяем соответствует ли оно тому, которое было записано. В случае совпадения считается, что тест модуля записи/чтения из памяти успешен. Во второй функции тестируется выполнение инструкции, в начале инициализируется память и регистры для теста, после чего выполняется инструкция проверяется, правильно ли она выполнилась. Если тесты прошли успешно — программа выведет соответствующее сообщение в консоль.

3.2 Интеграционное тестирование.

Интеграционное тестирование также проверяет работу модулей, но в отличие от модульного теста, тестирует модули не по отдельности, а взаимодействие их между собой в разных ситуациях. Такой метод тестирования позволяет убедиться, что все компоненты правильно работают в связке и корректно обмениваются необходимыми данными (рис. 6).

Листинг интеграционного теста

Рис. 6. Листинг интеграционного теста

В этом примере проверяется взаимодействие компонентов CPU и Memory между собой путём манипуляций из предыдущих примеров, за исключением присутствия некой «связки» этих модулей, что позволяет нам убедиться в корректном обмене информацией между ними.

3.3 Оптимизация кода для максимальной производительности

Крайне важным шагом будет максимально оптимизировать разработанный код, т. к. выполнение 16-битных приложений требует значительных вычислительных ресурсов и нужно постараться минимизировать количество «необязательных» вычислительных операций.

Листинг функции getRawMemoryPointer

Рис. 7. Листинг функции getRawMemoryPointer

В примере на листинге 7 приведена функция getRawMemoryPointer, которая позволяет получить оптимизированный доступ к данным в памяти для записи/чтения данных несколькими байтами за раз, взамен большого числа лишних вычислений.

Также оптимизирована функция выполнения инструкций, путём использования указателей на память и уменьшения количества операций чтения.

3.4 Тестирование конечной совместимости.

Поскольку эмулятор разработан под конкретную программу и весь его код полностью связан с ней, то тестировать, используя другие 16-битные приложения, нет смысла поскольку для этого было необходимо переписать значительную часть кода программы.

Поэтому тестирование состоит из нескольких этапов:

— проверка работоспособности 16-битного приложения под эмулятором;

— полное тестирование интерфейса (ввод, вычисления, операции и т. д.);

— диагностика возможных ошибок и их последующее исправление.

После прохождения всех тестов и отсутствия ошибок, программа собирается в конфигурации Release, и результат запуска под ОС Windows 10 приведен на рис. 8.

Экран запущенной программы СТЕВИН под ОС Windows 10

Рис. 8. Экран запущенной программы СТЕВИН под ОС Windows 10

Заключение.

В работе представлена методика разработки программного обеспечения для запуска 16 битных программ PC IBM на 64 битных операционных системах Windows. Если говорить о правовых аспектах, которые всегда присутствуют в реинжиниринге программного обеспечения, то в данном случае это «белый реинжиниринг», не затрагивающий никак авторские права разработчиков пакета СТЕВИН, а придавший ему вторую жизнь и возможность использования на современном компьютерном оборудовании.

Разработанная методика опробована также на программах Robby и Robby2 описанных в [6], которые также могут эксплуатироваться на 64 битных операционных системах Windows, начиная с 10 версии.

Вклад авторов в работу распределяется следующим образом. Панов С. А.: постановка задачи, общее редактирование текста, теоретическое кураторство разработки программного обеспечения. Копачев И. П.: разработка и реализация программного обеспечения, написание текста по структуре и работе ПО.

Литература:

  1. Корецкий А. В., Осадченко Н. В. Решение задач статики на персональном компьютере: Методическое пособие. — М.: Издательство МЭИ, 2003. — 64 с.
  2. Давлетшин, А. Д. Методика реинжиниринга устаревшего программного обеспечения / А. Д. Давлетшин // Аллея науки. — 2021. — Т. 1, № 6(57). — С. 335–338. — EDN TWQIEC.
  3. Наумов, Д. С. Реинжиниринг программного обеспечения измерения параметров СВЧ устройств при внедрении поддержки современного контрольно-измерительного оборудования / Д. С. Наумов, Л. В. Топалов // Вестник научных конференций. — 2015. — № 2–4(2). — С. 105–106. — EDN VBSUZT.
  4. Дромашко, С. Е. Реинжиниринг пакета генетико-статистических программ РИШОН / С. Е. Дромашко, А. В. Балаханов // Распределенные информационно-вычислительные ресурсы. Наука — цифровой экономике (DICR-2017): Труды XVI всероссийской конференции, Новосибирск, 04–07 декабря 2017 года / Институт вычислительных технологий СО РАН. — Новосибирск: Институт вычислительных технологий Сибирского отделения РАН, 2017. — С. 294–299. — EDN YPPOEW.
  5. Eilam E. Reversing: Secrets of Reverse Engineering — Wiley Publishing, Inc., 2011–630p.
  6. Корецкий А. В., Осадченко Н. В. Решение задач кинематики на персональном компьютере: Методическое пособие. — М.: Издательство МЭИ, 2004. — 48 с.


Задать вопрос