Добавление функциональности в готовые программы — Архив WASM.RU

Все статьи

Добавление функциональности в готовые программы — Архив WASM.RU

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

Итак, имеется:

  • Total Calculator v2.02 – TCalc.exe, размер 36 864 байт
  • Блокнот – стандартный notepad.exe (Windows XP), размер 66 048 байт.

Добавим функциональность одного в другой.

Порядок наших действий будет такой:

  • Реверс инжиниринг TCalc.exe
  • Добавление кода и данных в notepad.exe
  • Написание кода взаимодействия приложения с добавленным кодом

Реверс инжиниринг TCalc.exe

Прежде всего, нам потребуется код, который мы будем добавлять в блокнот. Дизассемблируем TCalc.exe. Файл небольшого размера, так что разобраться с ним не будет проблем. Про то, как правильно дизассемблировать код можно почитать в других источниках, например в статьях на сайте wasm.ru. Найти участок кода, отвечающий непосредственно за вычисление выражения несложно, он находится по адресу 4037E0 (Разумно будет дать этой функции какое-либо осмысленное имя, например CalcFunc). Процедура не принимает никаких аргументов. Строка с выражением находится по адресу 40B258, результат вычисления расположен по адресу 406450. После того как исходный код будет изучен, делаем File –> Produce file –> Create ASM file и назначаем имя файлу с исходным кодом.

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

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

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

После того как весь код будет скомпилирован, нужно подготовить входные данные: во входную строку поместить какое-либо выражение (например: 2+2), затем вызвать функцию sub_4037E0. Далее имеет смысл взглянуть на процесс выполнения под отладчиком. Ставим точку останова на функцию CalcFunc и смотрим, что получается после выполнения по адресу 406450. Если сразу добиться успеха не получилось, погружаемся в медитацию и ищем ошибку, прогоняя тестовую программу и эталонную TCalc.exe.

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

.text:004038C8                 fstp    qword ptr [edx+413348h]

Должен выглядеть так:

.text:004038C8                 fstp    dbl_413348[ecx]

Особенно внимательно нужно просмотреть процедуру sub_402400, там много такого кода.

Также могу посоветовать вызов функции 4015A0 просто убрать – от этого ничего не изменится, так как там отладочная информация.

Также в файле пару раз встречается конструкция switch, с областью данных в конце процедуры

		pop	ebx
		mov	esp, ebp
		pop	ebp
		retn
CalcFunc	endp
align 4
;off_4042EC	dd offset loc_40411A	; DATA XREF: CalcFunc+7Ar
;		dd offset loc_4038F0	; jump table for switch	statement
;		dd offset loc_403CE2
;		dd offset loc_403991
;		dd offset loc_403940
;		dd offset loc_4038A5
;		dd offset loc_403883
;		dd offset loc_403861
;		align 10h

Я вышел из положения так:

		pop	ebx
		mov	esp, ebp
		pop	ebp
		retn
.data
off_4042EC	dd offset loc_40411A	; DATA XREF: CalcFunc+7Ar
		dd offset loc_4038F0	; jump table for switch	statement
		dd offset loc_403CE2
		dd offset loc_403991
		dd offset loc_403940
		dd offset loc_4038A5
		dd offset loc_403883
		dd offset loc_403861
.code
CalcFunc	endp

Но можно, например, заменить на 8 операций сравнения и последующих условных переходов.

Кроме того, надо не забыть подключить нужные библиотеки

include masm32\include\msvcrt.inc includelib masm32\lib\msvcrt.lib

К именам функций atof, _CIacos, _CIasin добавить префикс “crt_”.

Добавление кода и данных в notepad.exe

Теперь у нас есть исходный код, который мы будем добавлять в notepad.exe. Настал момент задуматься над тем, как сделать, чтобы код работал, чтобы все “длинные” переходы были правильными, чтобы данные, на которые ссылается код, были там, где надо. На данный момент базовый адрес нашей тестовой программы 400000h, базовый адрес по которому грузится notepad.exe равен 1000000h, но это нам не помешает.

Теперь нам потребуется возможность по редактированию секций PE файла, для этого воспользуемся редактором PE Tools, он поддерживает все необходимые операции по манипулированию секциями. В связи с тем, что секции в памяти должны идти одна за другой, а код мы будем добавлять в конец файла, нам потребуется код с базовым адресом, лежащим за пределами адреса Image Base + Size of Image (1000000h+13000h). Необходимо немного модифицировать наше тестовое приложение, а именно необходимо изменить адрес загрузки нашей программы. Еще раз компилируем программу, на этот раз с ключом /BASE:0x1020000. Выбор числа 0x1020000 связан с тем, что адрес загрузки файла должен быть кратен 64 Кб. Берем получившуюся программу и копируем на диск секцию кода и данных.

Следующим шагом открываем notepad.exe в PE Tools и пробуем расположить секции в памяти так, как они располагались в нашей тестовой программе. Делаем это, манипулируя полями Virtual Size и Virtual Offset каждой секции. Согласно стандарту PE, секции в файле должны идти одна за другой. У меня получилось вот так:

Я поступил следующим образом: просто добавил так называемые Fake секции в те места, где получались разрывы. Это просто секции, заполненные нулями в памяти и не занимающие места на диске.

Следующая проблема - это импорт. Беглый осмотр, показал наличие нескольких функций в тестовой программе, которых нет в notepad.exe. Здесь есть несколько вариантов решения: можно написать небольшую процедуру, подгружающую нужные библиотеки и узнающую адреса нужных функций. Можно просто расширить импорт, и здесь нам поможет программа IIDKing v2.0. Запускаем утилиту и указываем, какие функции, и из какой библиотеки нужно добавить. После того как программа отработала, замечаем, что модифицированный файл notepad.exe увеличился еще на одну секцию, и именно в ней теперь находятся новые функции импорта. После этого надо подправить поле Size of Image в опциональном заголовке.

Настало время проверить работоспособность нашего “мутанта”. Запускаем модифицированный блокнот на исполнение. Убеждаемся, что он не работает. Если все сделано, так как я описал, то проблема связанна с “bound import” я просто отключил его и notepad.exe загружался без проблем. Делается это следующим образом: идем в список директорий PE файла и вместо адреса и размера “ Bound Import Directory” прописываем нули.

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

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

Модифицируем интерфейс нашего блокнота. Я поступил следующим образом: просто добавил новый пункт в меню “Формат”. Сделать это можно любой программой, которая позволяет редактировать ресурсы. Я воспользовался Restorator-ом. Новому пункту меню необходимо дать уникальный идентификатор в пределах всего меню. Я назначил число 34.

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

Здесь мы просто вставили “длинный” прыжок JMP 010236F5. Теперь идем по адресу 010236F5 и пишем код обработки выбора пункта меню “Вычислить”. Далее я приведу код, а затем эго прокомментирую:


Первые 2 строчки этого кода просто взяты с того места, где мы добавили код для прыжка на вставленный код (там он затерся), следующие 2 строчки проверка на то, что выбран именно добавленный пункт меню. Далее идут два вызова функции SendMessageW с сообщением EM_LINEFROMCHAR, первый раз, чтобы узнать на какой строчке установлен курсор ввода, второй раз с сообщением EM_GETLINE, чтобы получить строку с выражением. После этого по адресу 01024BF4 находится выражение для вычисления, но тут нас ждет одна неприятность: текст в буфере находится в UNICOD кодировке, а наша функция для вычисления выражений работает только с ANSI форматом строк. Дальнейший код это исправит, функция WideCharToMultiByte переводит UNICOD строки в формат ANSI. Функция вызывается 2 раза: первый раз, чтобы определить размер строки, затем для преобразования. Сразу за этим следует вызов функции для вычисления выражения. Следующий код делает обратное преобразование из ANSI в UNICOD с помощью функции MultiByteToWideChar. Далее идет вызов функции SendMessageW с сообщением EM_REPLACESEL для вставки строки “=” (строка находится по адресу 010237EB), а затем строки с результатом выражения.

Вот собственно и все! Мы получили работающий файл. Блокнот с расширенной функциональностью.

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

2002-2013 (c) wasm.ru