Как запустить отладку dll windows 10
Перейти к содержимому

Как запустить отладку dll windows 10

Практическое руководство. Отладка из проекта DLL в Visual Studio (C#, C++, Visual Basic, F#)

Одним из способов отладки проекта библиотеки DLL является указание вызывающего приложения в свойствах проекта библиотеки DLL. Затем можно запускать отладку из самого проекта DLL. Чтобы этот метод работал, приложение должно вызвать одну и ту же библиотеку DLL в том расположении, которое вы настроили. Если приложение находит и загружает другую версию библиотеки DLL, эта версия не будет содержать точки останова. Другие методы отладки библиотек DLL см. в разделе Отладка проектов DLL.

Если управляемое приложение вызывает собственную библиотеку DLL или ваше собственное приложение вызывает управляемую библиотеку DLL, можно выполнить отладку библиотеки DLL и вызывающего приложения. Дополнительные сведения см. в разделе Практическое руководство. Отладка в смешанном режиме.

Собственные и управляемые DLL-проекты имеют разные параметры для указания вызывающих приложений.

Указание вызывающего приложения в собственном проекте DLL

Выберите проект C++ DLL в обозревателе решений. Щелкните значок Свойства, нажмите сочетание клавиш ALT+ВВОД или щелкните проект правой кнопкой мыши и выберите элемент Свойства.

В диалоговом окне Страницы свойств <проект> убедитесь, что в поле Настройка в верхней части окна задано значение Отладка.

Выберите Свойства конфигурации>Отладка.

В списке Запускаемый отладчик выберите Локальный отладчик Windows или Удаленный отладчик Windows.

В поле Команда или Удаленная команда добавьте полный путь и имя файла вызывающего приложения, например EXE-файл.

Debug Properties window

Добавьте необходимые аргументы программы в поле Аргументы команды.

Нажмите кнопку ОК.

Указание вызывающего приложения в проекте C# или DLL (.NET Core, .NET 5+)

Выберите проект C# или Visual Basic DLL в обозревателе решений. Щелкните значок Свойства, нажмите сочетание клавиш ALT+ВВОД или щелкните проект правой кнопкой мыши и выберите элемент Свойства.

Откройте вкладку «Отладка» и выберите пункт Открыть пользовательский интерфейс профилей запуска отладки.

В диалоговом окне «Профили запуска» щелкните значок Создать новый профиль и выберите Исполняемый файл.

Screenshot of the UI to create a new debug profile.

В новом профиле в разделе Исполняемый файл перейдите к расположению исполняемого файла ( .exe) и выберите его.

В диалоговом окне «Профили запуска» найдите и запишите имя профиля по умолчанию, а затем выберите его и удалите.

Присвойте новому профилю имя профиля по умолчанию.

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

Указание вызывающего приложения в управляемом проекте DLL

Выберите проект C# или Visual Basic DLL в обозревателе решений. Щелкните значок Свойства, нажмите сочетание клавиш ALT+ВВОД или щелкните проект правой кнопкой мыши и выберите элемент Свойства.

Убедитесь, что в поле Настройка в верхней части окна задано значение Отладка.

В разделе Запустить действие

Для библиотек DLL .NET Framework выберите Запустить внешнюю программу и добавьте полный путь и имя вызывающего приложения.

Или выберите Запустить браузер с URL-адресом и введите URL-адрес локального приложения ASP.NET.

  • У библиотек DLL .NET Core для Visual Basic страница свойств Отладка отличается. Выберите Исполняемый файл из раскрывающегося списка Запуск, а затем добавьте полный путь и имя вызывающего приложения в поле Исполняемый файл.
  • Для библиотек DLL .NET Core страница свойств Отладка отличается. Выберите Исполняемый файл из раскрывающегося списка Запуск, а затем добавьте полный путь и имя вызывающего приложения в поле Исполняемый файл.

Добавьте необходимые аргументы командной строки в поле Аргументы командной строки или Аргументы приложения.

C# Debug Properties window

Используйте Файл>Сохранить выбранные элементы или CTRL+S, чтобы сохранить изменения.

Отладка из проекта DLL

Задайте точки останова в проекте DLL.

Щелкните проект DLL правой кнопкой мыши и выберите Назначить запускаемым проектом.

Убедитесь, что в поле Конфигурация решений установлено значение Отладка. Нажмите клавишу F5, щелкните зеленую стрелку Запуск или выберите Отладка>Начать отладку.

Если отладка не достигает точек останова, убедитесь, что выходные данные библиотеки DLL (по умолчанию — папка <проект>\Debug) — это расположение, которое вызывает вызывающее приложение.

Если вы хотите прервать выполнение кода в управляемом вызывающем приложении из собственной библиотеки DLL или наоборот, включите отладку в смешанном режиме.

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

Отладка библиотек DLL в Visual Studio (C#, C++, Visual Basic, F#)

Библиотека DLL (библиотека динамической компоновки) — это библиотека, содержащая код и данные, которые могут использоваться несколькими приложениями. Visual Studio можно использовать для создания, сборки, настройки и отладки библиотек DLL.

Создание библиотеки DLL

Создавать библиотеки DLL можно с помощью следующих шаблонов проектов Visual Studio.

  • Библиотека классов C#, Visual Basic или F#
  • Библиотека элементов управления Windows Forms (WCF) C# или Visual Basic
  • Библиотека динамической компоновки (DLL) C++

Дополнительные сведения см. в разделе Методы отладки MFC.

Отладка библиотеки WCF аналогична отладке библиотеки классов. Дополнительные сведения см. в статье Элементы управления Windows Forms.

Обычно библиотека DLL вызывается из другого проекта. При отладке вызывающего проекта в зависимости от конфигурации библиотеки DLL можно выполнить шаг с заходом и отладить код библиотеки DLL.

Конфигурация отладки библиотеки DLL

Когда вы используете шаблон проекта Visual Studio для создания приложения, Visual Studio автоматически создает требуемые параметры для конфигураций сборки отладки и выпуска. При необходимости эти параметры можно изменить. Дополнительные сведения см. в следующих статьях:

Установка DebuggableAttribute C++

Чтобы отладчик мог присоединиться к библиотеке DLL C++, код C++ должен добавлять DebuggableAttribute .

Установка DebuggableAttribute

В обозревателе решений выберите проект DLL C++ и щелкните значок Свойства либо щелкните проект правой кнопкой мыши и выберите пункт Свойства.

В области Свойства в разделе Компоновщик>Отладка выберите Да (/ASSEMBLYDEBUG) для свойства Отлаживаемая сборка.

Дополнительные сведения см. в статье /ASSEMBLYDEBUG.

Задание расположений файлов DLL C/C++

Для отладки внешней библиотеки DLL вызывающий проект должен находить библиотеку DLL, ее PDB-файл и любые другие файлы, необходимые библиотеке DLL. Вы можете создать пользовательскую задачу сборки, чтобы скопировать эти файлы в выходную папку проекта <папка проекта>\Debug, или скопировать файлы вручную.

Для проектов C/C++ можно задать расположения файлов заголовков и LIBD-файла на страницах свойств проекта, а не копировать их в выходную папку.

Задание расположений файла заголовка C/C и LIB-файла

В обозревателе решений выберите проект DLL C/C++ и щелкните значок Свойства либо щелкните проект правой кнопкой мыши и выберите пункт Свойства.

В верхней части области Свойства в разделе Конфигурация выберите Все конфигурации.

В разделе C/C++>Общие>Дополнительные включаемые каталоги укажите папку с файлами заголовков.

В разделе Компоновщик>Общие>Дополнительные каталоги библиотек укажите папку с LIB-файлами.

В разделе Компоновщик>Ввод>Дополнительные зависимости укажите полный путь и имя файла для LIB-файлов.

Нажмите кнопку ОК.

Дополнительные сведения о параметрах проекта C++ см. в статье Справочник C++ по страницам свойств проекта Windows.

Сборка отладочной версии

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

Вы можете создать пользовательскую задачу сборки, чтобы скопировать эти файлы DLL в выходную папку проекта <папка вызывающего проекта>\Debug, или скопировать файлы вручную.

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

Отладка библиотека DLL

Библиотеку DLL нельзя запускать напрямую. Ее должно вызывать приложение, обычно это EXE-файл. Дополнительные сведения см. в статье Проекты Visual Studio — C++.

Чтобы отладить библиотеку DLL, можно начать отладку из вызывающего приложения или выполнить отладку из проекта DLL, указав вызывающее приложение. Можно также использовать окно «Интерпретация» отладчика для вычисления функций или методов DLL во время разработки без использования вызывающего приложения.

Дополнительные сведения см. в статье Первое знакомство с отладчиком.

Запуск отладки из вызывающего приложения

Вызывать библиотеку DLL могут следующие виды приложений.

  • Приложение из проекта Visual Studio в том же или другом решении из библиотеки DLL.
  • Существующее приложение, которое уже развернуто и работает на тестовом или рабочем компьютере.
  • Программа расположена в Интернете и доступна по URL–адресу.
  • Веб-приложение с веб-страницей, которая внедряет библиотеку DLL.

Выполнить отладку библиотеки DLL из вызывающего приложения можно следующими способами.

Откройте проект для вызывающего приложения и начните отладку, выбрав Отладка>Начать отладку или нажав клавишу F5.

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

Перед запуском отладки этого вызывающего приложения нужно установить точку останова в DLL. См. статью Использование точек останова. При попадании в точку останова DLL можно пошагово проходить по коду, наблюдая действия в каждой строке. Дополнительные сведения см. в статье Навигация по коду с помощью отладчика.

Во время отладки в окне Модули можно проверить библиотеки DLL и EXE-файлы, загружаемые приложением. Чтобы открыть окно Модули, во время отладки выберите Отладка>Окна>Модули. Дополнительные сведения см. в разделе Практическое руководство. использовать окно модулей.

Использование окна «Интерпретация»

Окно Интерпретация можно использовать для вычисления функций или методов DLL во время разработки. Окно Интерпретация выполняет роль вызывающего приложения.

Окно Интерпретация можно использовать во время разработки с большинством типов проектов. Оно не поддерживается для SQL, веб-проектов или скриптов.

Например, чтобы протестировать метод с именем Test в классе Class1 , выполните следующие действия.

Откройте проект DLL, откройте окно Интерпретация, последовательно выбрав пункты Отладка>Окна>Интерпретация или нажав сочетание клавиш CTRL+ALT+I.

Создайте объект типа Class1 , введя следующий код C# в окне Интерпретация и нажав клавишу ВВОД. Этот управляемый код работает для C# и Visual Basic с соответствующими изменениями синтаксиса.

В C# все имена должны быть указаны полностью. Когда языковая служба пытается вычислить выражение, все методы или переменные должны находиться в текущей области и контексте.

Предположим, что Test принимает один параметр int , вычислим Test с помощью окна Интерпретация :

Результат будет выведен в окне Интерпретация.

Можно продолжить отладку Test , установив в нем точку останова, а затем снова вычислив эту функцию.

Будет достигнута точка останова и можно будет пошагово пройти Test . После выполнения Test , отладчик вернется в режим разработки.

Отладка в смешанном режиме

Вы можете написать вызывающее приложение для библиотеки DLL в управляемом или машинном коде. Если собственное приложение вызывает управляемую библиотеку DLL и вы хотите выполнить отладку этих двух объектов, можно включить управляемые и машинные отладчики в свойствах проекта. Точный способ выполнения этой операции зависит от того, откуда будет начата отладка: из проекта DLL или из проекта вызывающего приложения. Дополнительные сведения см. в разделе Практическое руководство. Отладка в смешанном режиме.

Можно выполнить отладку собственной библиотеки DLL из управляемого вызывающего проекта. Дополнительные сведения см. в статье об отладке управляемого и машинного кода.

Как запустить отладку dll windows 10

Отладка библиотек DLL в Visual Studio (C#, C++, Visual Basic, F#)

Библиотека DLL (библиотека динамической компоновки) — это библиотека, содержащая код и данные, которые могут использоваться несколькими приложениями. Visual Studio можно использовать для создания, сборки, настройки и отладки библиотек DLL.

Создание библиотеки DLL

Создавать библиотеки DLL можно с помощью следующих шаблонов проектов Visual Studio.

  • Библиотека классов C#, Visual Basic или F#
  • Библиотека элементов управления Windows Forms (WCF) C# или Visual Basic
  • Библиотека динамической компоновки (DLL) C++

Дополнительные сведения см. в разделе Методы отладки MFC.

Отладка библиотеки WCF аналогична отладке библиотеки классов. Дополнительные сведения см. в статье Элементы управления Windows Forms.

Обычно библиотека DLL вызывается из другого проекта. При отладке вызывающего проекта в зависимости от конфигурации библиотеки DLL можно выполнить шаг с заходом и отладить код библиотеки DLL.

Конфигурация отладки библиотеки DLL

При создании приложения на основе шаблона проекта Visual Studio [!INCLUDEvsprvs] автоматически создает требуемые параметры для конфигурации сборки отладки и выпуска. При необходимости эти параметры можно изменить. Дополнительные сведения см. в следующих статьях:

Установка DebuggableAttribute C++

Чтобы отладчик мог присоединиться к библиотеке DLL C++, код C++ должен добавлять DebuggableAttribute .

Установка DebuggableAttribute

В обозревателе решений выберите проект DLL C++ и щелкните значок Свойства либо щелкните проект правой кнопкой мыши и выберите пункт Свойства.

В области Свойства в разделе Компоновщик > Отладка выберите Да (/ASSEMBLYDEBUG) для свойства Отлаживаемая сборка.

Дополнительные сведения см. в статье /ASSEMBLYDEBUG.

Задание расположений файлов DLL C/C++

Для отладки внешней библиотеки DLL вызывающий проект должен находить библиотеку DLL, ее PDB-файл и любые другие файлы, необходимые библиотеке DLL. Вы можете создать пользовательскую задачу сборки, чтобы скопировать эти файлы в выходную папку <project folder>\Debug, или скопировать файлы вручную.

Для проектов C/C++ можно задать расположения файлов заголовков и LIBD-файла на страницах свойств проекта, а не копировать их в выходную папку.

Задание расположений файла заголовка C/C и LIB-файла

В обозревателе решений выберите проект DLL C/C++ и щелкните значок Свойства либо щелкните проект правой кнопкой мыши и выберите пункт Свойства.

В верхней части области Свойства в разделе Конфигурация выберите Все конфигурации.

В разделе C/C++ > Общие > Дополнительные включаемые каталоги укажите папку с файлами заголовков.

В разделе Компоновщик > Общие > Дополнительные каталоги библиотек укажите папку с LIB-файлами.

В разделе Компоновщик > Ввод > Дополнительные зависимости укажите полный путь и имя файла для LIB-файлов.

Нажмите кнопку ОК.

Дополнительные сведения о параметрах проекта C++ см. в статье Справочник C++ по страницам свойств проекта Windows.

Сборка отладочной версии

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

Вы можете создать пользовательскую задачу сборки, чтобы скопировать эти файлы DLL в выходную папку <calling project folder>\Debug, или скопировать файлы вручную.

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

Отладка библиотека DLL

Библиотеку DLL нельзя запускать напрямую. Ее должно вызывать приложение, обычно это EXE-файл. Дополнительные сведения см. в статье Проекты Visual Studio — C++.

Чтобы отладить библиотеку DLL, можно начать отладку из вызывающего приложения или выполнить отладку из проекта DLL, указав вызывающее приложение. Можно также использовать окно «Интерпретация» отладчика для вычисления функций или методов DLL во время разработки без использования вызывающего приложения.

Дополнительные сведения см. в статье Первое знакомство с отладчиком.

Запуск отладки из вызывающего приложения

Вызывать библиотеку DLL могут следующие виды приложений.

  • Приложение из проекта [!INCLUDEvsprvs] в том же или другом решении из библиотеки DLL.
  • Существующее приложение, которое уже развернуто и работает на тестовом или рабочем компьютере.
  • Программа расположена в Интернете и доступна по URL–адресу.
  • Веб-приложение с веб-страницей, которая внедряет библиотеку DLL.

Выполнить отладку библиотеки DLL из вызывающего приложения можно следующими способами.

Откройте проект для вызывающего приложения и начните отладку, выбрав Отладка > Начать отладку или нажав клавишу F5.

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

Перед запуском отладки этого вызывающего приложения нужно установить точку останова в DLL. См. статью Использование точек останова. При попадании в точку останова DLL можно пошагово проходить по коду, наблюдая действия в каждой строке. Дополнительные сведения см. в статье Навигация по коду с помощью отладчика.

Во время отладки в окне Модули можно проверить библиотеки DLL и EXE-файлы, загружаемые приложением. Чтобы открыть окно Модули, во время отладки выберите Отладка > Окна > Модули. Дополнительные сведения см. в разделе Практическое руководство. использовать окно модулей.

Использование окна «Интерпретация»

Окно Интерпретация можно использовать для вычисления функций или методов DLL во время разработки. Окно Интерпретация выполняет роль вызывающего приложения.

[!NOTE] Окно Интерпретация можно использовать во время разработки с большинством типов проектов. Оно не поддерживается для SQL, веб-проектов или скриптов.

Например, чтобы протестировать метод с именем Test в классе Class1 , выполните следующие действия.

Откройте проект DLL, откройте окно Интерпретация, последовательно выбрав пункты Отладка > Окна > Интерпретация или нажав сочетание клавиш CTRL+ALT+I.

Создайте объект типа Class1 , введя следующий код C# в окне Интерпретация и нажав клавишу ВВОД. Этот управляемый код работает для C# и Visual Basic с соответствующими изменениями синтаксиса.

В C# все имена должны быть указаны полностью. Когда языковая служба пытается вычислить выражение, все методы или переменные должны находиться в текущей области и контексте.

Предположим, что Test принимает один параметр int , вычислим Test с помощью окна Интерпретация :

Результат будет выведен в окне Интерпретация.

Можно продолжить отладку Test , установив в нем точку останова, а затем снова вычислив эту функцию.

Будет достигнута точка останова и можно будет пошагово пройти Test . После выполнения Test , отладчик вернется в режим разработки.

Отладка в смешанном режиме

Вы можете написать вызывающее приложение для библиотеки DLL в управляемом или машинном коде. Если собственное приложение вызывает управляемую библиотеку DLL и вы хотите выполнить отладку этих двух объектов, можно включить управляемые и машинные отладчики в свойствах проекта. Точный способ выполнения этой операции зависит от того, откуда будет начата отладка: из проекта DLL или из проекта вызывающего приложения. Дополнительные сведения см. в разделе Практическое руководство. Отладка в смешанном режиме.

Можно выполнить отладку собственной библиотеки DLL из управляемого вызывающего проекта. Дополнительные сведения см. в статье об отладке управляемого и машинного кода.

Отладка, модификация и устранение багов в сторонних 32-х и 64-х разрядных приложениях под ОС Windows

В этой статье я хочу поделиться практическими методами отладки, модификации и устранения багов в 32-х и 64-х разрядных приложениях под ОС Windows, разработанных на языке C/C++, исходные коды которых по тем или иным причинам не стали достоянием общественности.

Этот пробел отчасти можно попытаться устранить, например, с помощью плагина Hex-Rays для IDA Pro, и зачастую удаётся довольно качественно восстановить нужный участок исходного кода, обнаружив в нём проблемное место. Но после этого всегда возникает вопрос — что с этим исправленным кодом делать дальше, как и где его можно использовать? На данном этапе мне всегда хотелось взять этот отдельно декомпилированный фрагмент программы, поменять в нём что-нибудь и затем каким-то чудесным образом «поместить обратно» в программу.

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

Иногда возникает необходимость исправить какой-нибудь баг в сторонней программе, добавить к ней отсутствующий функционал, изменить существующий, либо модифицировать логику некоторых процессов, особенно если нет возможности обратиться за помощью к разработчикам. Описание процедуры выявления «интересных» мест выходит за рамки данной статьи. В некоторых случаях это может быть банальная ошибка в программе, которую удаётся вылечить несложным патчем непосредственно в теле EXE или DLL файла. Но если же предстоит исправлять алгоритмы поведения некоторых функций, либо добавлять новые, обычным «байтовым» патчем здесь уже не обойтись. Устранение же некоторых на первый взгляд простых багов может в итоге вылиться в «перепахивание» доброй половины программы. Также приходится потрудиться, чтобы заставить правильно работать приложения, созданные, например, под Win XP в новых ОС Windows.

Освоив и давно применяя на практике некоторые методы модификации программного кода, самым удобным и простым на мой взгляд способом оказалась его модификация в ран-тайме с помощь метода DLL-инъекции. На эту тему существует огромное количество материала и в плане реализации самого метода DLL-инъекции данная статья не претендует ни на новизну, ни на оригинальность, но в большей степени призвана систематизировать практические наработки в области отладки, модификации, поиска и устранения багов в чужом коде с его помощью.

Описываемый здесь способ вынесения некоторых функций сторонних приложений в свою собственную программу на C/C++ позволяет отлаживать, модифицировать и при необходимости более детально изучать их работу с помощью отладчика на высокоуровневом языке, вместо того, чтобы «дебажить» их в дизассемблированном коде.

Прилагаемый к данной статье пример с помощью минимальных изменений можно использовать в своих целях в качестве готового проекта для Visual Studio (VS).

В VS при создании 32-х разрядных приложений платформа называется «x86», а 64-x разрядных «x64». Поэтому чтобы не путаться с названиями я буду для обозначения разрядности приложений использовать термины «Win32» и «Win64».

Инструментарий.

IDA Pro (файлы примеров в проекте для v7.5).

CFF Explorer или любой другой инструмент редактирования импорта.

Visual Studio С/C++ (проект примера для Platform Toolset: Visual Studio 2022 (v143)).

Практика внедрения.

Для примера рассмотрим тестовую программу SimpleCalc – простое оконное приложение (проект для VS прилагается), которое производит сложение и умножение двух чисел.
В программу намеренно внесён «баг», приводящий к тому, что для операций «2+2» и «2*2» результат получается равным 5. Заголовок диалогового окна данного приложения — «Simple Buggy Calc ( 2+2=5; 2*2=5 )».

Следовательно, нам будет необходимо модифицировать его работу таким образом, чтобы в результате выполнения уникальных операций 2+2 и 2*2 получались правильные значения, а текст заголовка окна выглядел бы, как «Simple Calc».

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

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

Займёмся описанием класса Adder (имя этому классу можно задать любое).

Для работы с классами в IDA Pro существует плагин ClassInformer. В некоторых случаях он может помочь восстановить иерархию классов и имена виртуальных таблиц, чтобы получить близкие к оригиналу имена классов.

Место создания класса Adder обнаруживается в методе DialogProc, откуда можно определить его размер (для Win32 версии):

Псевдокод конструктора в IDA Pro:

Теперь создим в IDA Pro структуру такого же размера с именем класса. В нашем случае структура для класса и интерфейса будет выглядеть так:

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

А конструктор будет теперь выглядеть так:

Т.к. вызов реализованного метода Add интерфейса IAdder в программе происходит через таблицу адресов, найдём его в этой таблице Adder::`vftable’.

После причёсывания декомпилированного псевдокода в IDA Pro метод Adder::Add будет выглядеть примерно так:

Здесь для нас важно запомнить адрес этого метода — 0x4011B0.

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

А код метода Mult_() так:

В программе SimpleCalc метод Mult() намеренно был создан, как псевдо __usercall с передачей параметров через регистры EAX и EBX. Компилятор от MS для Win32 использует соглашения о вызовах __cdecl, __stdcall или __thiscall, но некоторые другие компиляторы могут использовать иные соглашения и примером такого поведения является метод Mult().

Конечно, в данном конкретном случае можно было бы ограничиться исправлением только __stdcall метода Mult_(), но для полноты картины опишем способ с нестандартным для MS соглашением о вызовах.

Для того, чтобы поместить значения регистров в стек для вызова «обычного» метода __stdcall Mult_() из метода Mult(), воспользуемся атрибутом naked. Этот атрибут указывает компилятору не создавать пролог и эпилог внутри метода, чтобы не испортить значения регистров EAX и EBX (в нашем случае) до помещения их в стек:

Можно было бы написать код метода Mult() и на чистом ассемблере, а для Win64 это единственный способ, так как в MSVC для него не поддерживается атрибут naked. Но для Win64, как правило, используется соглашение о вызове __fastcall и другие, по крайней мере среди тех приложений, которые мне приходилось «лечить», не попадались.

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

Адреса метода Mult() и глобальной переменной g_hMulRes нам понадобятся, поэтому их необходимо будет запомнить.

Способ изменения заголовка диалогового окна будет описан чуть позже.

Создание инжектируемой DLL.

Итак, мы обнаружили и декомпилировали интересующие нас методы и теперь перейдём к созданию собственной DLL, которую позже внедрим в тестовую программу SimpleCalc. Проект такой DLL (Win32 и Win64) для VS прилагается.

Основная идея метода DLL-Injection заключается в том, что на этапе загрузки приложения, но ещё до передачи ему управления, загрузчик ОС, среди прочих, загрузит в общее адресное пространство и нашу DLL (которую мы добавим в секцию импорта EXE файла) и вызовет из неё метод:

со значением переменной ul_reason равным DLL_PROCESS_ATTACH.

Для упрощения описания не будем рассматривать здесь частные случаи типа отложенной загрузки, LoadLibrary или TLS-коллбэков.

На данном этапе мы должны модифицировать начальную часть перехватываемых нами методов из EXE файла по адресам, полученным из IDA Pro, разместив там команды безусловных переходов на адреса наших переписанных в DLL методов.

Для Win32 команда перехода, содержащая адрес непосредственно в своём операнде (а нам нужна именно такая, чтобы не портить ни стек, ни регистры) состоит из пяти байт, первый — 0xE9, а остальные четыре знаковое смещение относительно адреса следующей команды.
Для Win64 эта команда состоит из шести байт: 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 и следом за ними восемь байт адрес перехода – итого четырнадцать байт.

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

Установкой адреса перехода занимается метод Patch(). Так как адресное пространство EXE модуля, в котором расположен исполняемый код недоступно для записи, сначала мы модифицируем права доступа к этой памяти с помощью функции VirtualProtect, затем записываем в начало перехватываемого метода нужные байты – команду перехода на наш метод в DLL и в конце восстанавливаем прежние права с помощью той же функции VirtualProtect.

При расчёте адреса перехода существует одна тонкость, заключающаяся в том, что IDA Pro по умолчанию дизассемблирует EXE файл по стандартному адресу загрузки — 0x00400000 (либо 0x140000000 для Win64). Но иногда встречаются EXE файлы, у которых в заголовке в параметре IMAGE_NT_HEADER.OptionalHeader.DllCharacteristics установлен флаг «DLL Can Move» и в этом случае ОС вправе загрузить его по любому другому адресу.

В свойствах проекта SimpleCalc в VS – за этот флаг отвечает пункт «Linker -> Advanced -> Randomize Base Address», который намеренно установлен в «Yes» для тестирования данного поведения.

Следовательно, в нашей DLL мы должны учитывать адрес, по которому загружен EXE файл, и затем использовать его в методе Patch() для расчёта смещения в команде перехода относительно адреса взятого из IDA Pro.

При работе с большими проектами, в которых используется множество классов, исходный код DLL в VS обрастает большим количеством отдельных файлов. И следить за тем, перехвачен ли тот или иной метод в DllMain с помощью вызова Patch() становится неудобным. Поэтому впоследствии был использован способ для объявления перехвата непосредственно возле нужного метода с помощью вспомогательной статической структуры. При этом оказалось возможным отказаться от использования DllMain, как места для объявления всех перехватов.

В дополнение к этому был создан вспомогательный класс CPatch.

При загрузке DLL ещё до попадания в точку входа (DllMain) инициализируются все статические объекты и таким образом, через вызов метода Patch( exeAddr, dllAdd ) происходит замена первых байт из оригинального метода в EXE на команду перехода в переписанный нами метод в DLL.

Класс CPatch содержит в себе контейнер std::map<ADDR, PATCH_DATA>, ключом в котором является адрес оригинального метода из EXE, а значением – структура PATCH_DATA, в которой хранятся оригинальные байты, заменённые на команду перехода и байты самой команды перехода. При каждом вызове метода Patch( exeAddr, dllAddr ) в этот контейнер добавляется новый элемент с соответствующим ключом – адресом оригинального метода из EXE и структурой PATCH_DATA, описанной выше.

В классе CPatch также содержатся методы Patch( exeAddr ) и Unpatch( exeAddr ), смысл использования которых состоит в следующем. Иногда бывает необходимо «попасть» в какой-нибудь метод из EXE, чтобы проконтролировать передаваемые в него аргументы, сохранив их значения, скажем, в лог файл, но весь метод для этого переписывать и декомпилировать в наши планы пока не входит.

Приведём пример использования этих методов для изменения заголовка диалогового окна программы SimpleCalc.

«Пропатчим» оконную процедуру диалогового окна DialogProc таким образом, что при попадании в наш переписанный метод DialogProc в DLL мы каждый раз будем восстанавливать оригинальные байты в EXE методе с помощью _patch->Unpatch(), вызывать оригинальный метод и после этого проверять значение аргумента uMsg. Если оно будет равным WM_INITDIALOG, для нас это может означает, что можем менять его заголовок. При всех других значениях аргумента uMsg, перед выходом из нашего метода DialogProc мы снова будем меняем начальные байты оригинального метода EXE на команду перехода на наш метод в DLL с помощью _patch->Patch().

Проверка работоспособности инжектируемой DLL.

После создания DLL, в которой описан класс Adder, метод Mul() и DialogProc() необходимо дополнительно объявить в ней один экспортируемый метод с любым именем. В нашем случае им будет пустой метод Setup(). В свойствах проекта в VS помещаем созданную DLL в ту же директорию, что и EXE файл.

Теперь настало время добавить в секцию импорта программы SimpleCalc созданную нами DLL и единственный экспортируемый из неё метод Setup(). Для этого можно воспользоваться утилитой CFF Explorer. Открываем в ней EXE файл SimpleCalc, в левой части окна выбираем пункт «Import Adder», в открывшемся окне нажимаем кнопку «Add» и выбираем наш DLL файл. После этого в окне «Exported Functions» должен отобразиться наш единственный экспортируемый метод Setup(), выделяем его мышкой и нажимаем кнопку «Import By Name» после чего он отобразится в соседнем справа окне «Imported Functions»:

Затем нажимаем кнопку «Rebuild Import Table» и перезаписываем исходный EXE файл через меню «File -> Save» или сохраняем его с новым именем «File -> Save As».

Часто возникает соблазн внедрить свой код в чужой файл без какой бы то нибыло его модификации. Обычно любой EXE файл импортирует несколько системных DLL из которых, к тому же, используются далеко не все методы. При этом можно было бы создать свою DLL с тем же именем, скажем, kernel32.dll. Описать в ней прокси переходы для всех импортируемых из этой DLL методов, добавить свои и в итоге поместить её в тот же каталог рядом с EXE файлом. Но MS об этом тоже подумала и создала список так называемых Known DLLs, куда входят все системные, чтобы они не могли загружаться из того же каталога, что и EXE.

Существует способ обойти это ограничение, но в мои планы не входило создание вирусов, программ-шпионов, или подключение к уже запущенным процессам, поэтому модификация секции импорта EXE файла выглядит в данном случае абсолютно приемлемой и совершенно несложной операцией.

Теперь в VS в свойствах DLL проекта в разделе «Debugging» в пункте «Command» прописываем путь к модифицированному EXE файлу, ставим точки останова на начало методов Add, Mult, DialogProc и стартуем отладку. Сначала мы должны попасть в метод DialogProc, а при нажатии в окне программы на кнопку «=» — в соответствующий метод вычисления суммы или произведения. Убеждаемся, что патч программы был произведен успешно.

Вызов методов EXE файла из DLL.

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

Предположим, у нас есть метод sub_4037B0, из которого вызывается другой, по адресу 0x404800. Тогда, определив из дизассемблера соглашение о вызове этого метода и тип аргументов, можно будет вызвать его таким образом:

Если же он вызывается из множества мест нашей DLL, но декомпилировать его пока в наши планы не входит, можно реализовать его таким образом:

Для случая, если метод является членом класса, его вызов будет выглядеть так:

Способ вызова методов с нестандартными для MSVC соглашениями о вызовах был описан выше на примере с __usercall Mult().

Доступ к данным EXE файла из DLL.

В случае, если метод сторонней программы, перехват которого производится, был написан с использованием классов и все данные, используемые в этом методе – члены этого (или другого) класса, то доступ к ним в своём переписанном методе сводится лишь к правильному описанию самого класса. Если все члены класса расположены в его описании строго на своих местах, то и доступ к ним будет осуществляться автоматически по их имени (как в классе Adder). Их значения также будут отображаться в отладчике VS через указатель this.
Но в случае использования глобальных переменных ситуация усложняется. Доступ к таким переменным можно получить лишь по их адресу в памяти основной программы, взятому из дизассемблера. В MSVC, к сожалению, невозможно объявить переменную по абсолютному адресу (даже без выделения для неё памяти), как это делается во многих Embedded системах, поэтому приходится объявлять глобальные переменные для указателей, например, таким образом:

а для переменных, используемых по значению, таким:

Но, во-первых, при этом возникает огромное неудобство при отладке программы. Дело в том, что VS не отображает в отладчике значения макросов, объявленных с помощью директивы препроцессора #define. А во-вторых, что ещё более печально, для указателя, объявленного таким образом, не удаётся получить его адрес:

Можно объявить и инициализировать указатель так:

И это будет работать для отображения его значения в отладчике и для операции разадресации «*_ptr1», но как только мы попытаемся получить адрес этого указателя, им окажется адрес нашей локальной переменной _ptr1 в адресном пространстве DLL.

Применение оператора «placement new» тоже не даёт нужного результата, т.к. непосредственно переменную с нужным именем разместить по конкретному адресу не удаётся. Так, например, конструкция типа:

действительно размещает объект типа int со значением 5 по адресу 0x40BEF4, но не переменную _ptr1 ! А она опять же находится в адресном пространстве DLL, что для нашей задачи равносильно определению:

Поэтому в качестве универсального метода пришлось прибегнуть к такой страшноватой конструкции, помещая переменную в структуру:

Здесь мы сначала объявляем новый тип структуры, который содержит в себе единственный элемент – нашу переменную с именем var. Затем создаём указатель на эту структуру с именем нашей переменной и инициализируем его адресом этой переменной из EXE файла.

Объявление глобальной переменной g_hMulRes в этом случае будет выглядеть так:

а обращение к ней так:

Несколько «ugly», но зато с этой переменной можно производить операции взятия адреса, разадресации и, что немаловажно, её значение будет отображаться в отладчике.

Буду весьма признателен, если кто-нибудь подсказажет более изящный способ решения этого вопроса.

Управление памятью.

При сборке EXE файла использовалась та или иная версия библиотеки C/C++ (например, CRT), в которой были определены операторы и методы для управления памятью «new», «delete», «malloc», «free» и другие. И эта версия может и скорее всего будет не совпадать с версией библиотеки используемой для линковки с DLL.

Если в каком-либо из переписанных из EXE в DLL методов применяется один из этих операторов, например «new» для выделения памяти, а для её освобождения будет соответственно использоваться оператор «delete», но уже в недрах EXE модуля, то при работе программы может возникнуть исключение. Поэтому, если в переписанном методе в DLL необходимо использовать отдельный оператор «new» или «delete», предпочтительнее вызывать его по соответствующему адресу из EXE модуля, определив его предварительно в дизассемблере.

Пример такого поведения содержится в проекте DLL в методе Adder::Add() для создания массива символов.

Ведение логов.

Иногда может оказаться полезным регистрация каких либо событий, происходящих в сторонней программе.

Для реализации такого примера в конструкторе метода Patch тестовой DLL создаётся и открывается на запись файл SimpleCalc.log, а затем в этот файл записываются производимые пользователем операции.

Заключение.

Описанный здесь способ неоднократно помог мне в самых различных ситуациях. С его помощью были исправлены баги и недочёты в десятках программных продуктах для моей частной и профессиональной деятельности и я по сей день прибегаю к его использованию. Он одинаково хорошо применим для работы с 32-х и 64-х разрядными приложениями и апробирован на различных версиях ОС Windows, так что его смело можно рекомендовать к практическому применению при минимальных трудозатратах на внедрение.

Спасибо за проявленный интерес!

Приложение.

Архивный файл с проектом программы SimpleCalc и тестовой DLL для VS.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *