Это практически всё, что нам нужно знать о формате .exe файла для решения поставленной задачи.
После таблицы импорта (последняя запись, содержащая во всех полях NULL) в файле как правило идут данные импорта (массивы HintName, строки, с именами библиотек и т.д.). Следовательно, добавление еще одной записи о нашей DLL в существующую таблицу представляется достаточно громоздким и труднореализуемым - потому как в случае "сдвига" данных, идущих после таблицы импорта необходимо будет пересчитать все указатели на эти данные. Выход из данной ситуации - создание новой таблицы импорта, содержащей всю существующую информацию + информация о нашей DLL. Чтобы где-то расположить новую таблицу импорта, необходимо наличие свободного места в файле. И тут как нельзя кстати обнаруживается такая замечательная особенность PE-файлов, как "страничность", или, иначе говоря, выравнивание данных в файле на определенные адреса. Размеры этих страниц определяются параметром
IMAGE_OPTIONAL_HEADER->FileAlignment.
Рассмотрим, как используется этот параметр. Допустим, что его величина равна 0x200h (стандартное значение), а исходный размер данных для какой-либо секции равен 0x500h (до создания компилятором файла). Так вот, после выравнивания на величину FileAlignment размер секции будет равен 0x600h, т.е. будет кратен величине FileAlignment. И хотя последние 0х100h байт нулей добавленные компилятором в секцию нигде не используются, они исправно отображаются на адресное пространство процесса. Соответственно все, что мы туда запишем, будет присутствовать в образе файла в памяти. Размер свободного места в секции зависит от конкретного файла, но, как правило, его достаточно для размещения новой таблицы импорта. В том случае, если ни в одной из секций файла нет свободного пространства, возможен вариант создания дополнительной секции либо увеличения длины самой последней из секций (в приводимой для примера программе не реализовано). Резюмируя, разбиваем процесс внедрения на следующие стадии:
Открытие файла
Анализ файла на возможность модификации (проверка наличия свободного места в файле для новой таблицы импорта)
Формирование новой таблицы импорта, соответствующих массивов HintName (ссылки FirstThunk и Characteristics), имени библиотеки (ссылка Name).
Расчёт RVA всех структур, заносимых нами в файл (обоих массивов HintName, строки с именем DLL, нового адреса таблицы импорта).
Запись в файл новой таблицы импорта и всех новых структур.
Установка нового указателя на таблицу импорта в заголовке (второй элемент массива в IMAGE_OPTIONAL_HEADER->DataDirectory[]).
В том случае, если планируется добавление новой секции либо увеличение размера существующей, необходима дополнительная модификация таблицы секций.
Реализация
Думаю, теории достаточно, приступаем к практике. Целиком проект можете загрузить тут. Разберем пошагово каждую операцию: 1. Открываем .exe файл, отображаем его для удобства работы на своё адресное пространство.
IMAGE_DOS_HEADER *mz_head;
IMAGE_FILE_HEADER *pe_head;
IMAGE_OPTIONAL_HEADER *pe_opt_head;
IMAGE_SECTION_HEADER *sect;
char pe[] = "PE\0\0";
HANDLE f = NULL;
//Открываемфайл
f = CreateFile(openF->FileName.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (f == INVALID_HANDLE_VALUE)
{
Log->Lines->Add("Ошибка при открытии файла: ");
parse_error();
return;
}
//Создаемотображениефайла
HANDLE fMap = CreateFileMapping( f, NULL,
PAGE_READWRITE,
0, 0, NULL);
CloseHandle(f);
if (fMap == NULL)
{
Log->Lines->Add("Ошибкапривызове CreateFileMapping(): ");
parse_error();
return;
}
int size = sizeof( IMAGE_DOS_HEADER );
//Отображаем начало файла в память
LPVOID fBeg = MapViewOfFile( fMap, FILE_MAP_WRITE, 0, 0, size);
if (fBeg == NULL)
{
Log->Lines->Add("Ошибкапривызове MapViewOfFile(): ");
parse_error();
return;
}
2. Проверяем, является ли файл PE-executable:
//Определяем смещение РЕ-заголовка.
mz_head = (IMAGE_DOS_HEADER *)fBeg;
DWORD peOffset = mz_head->e_lfanew;
UnmapViewOfFile(fBeg);
//Отображаем в память с учетом смещения до РЕ-заголовка
size = peOffset + sizeof( DWORD ) + sizeof( IMAGE_FILE_HEADER )
+ sizeof( IMAGE_OPTIONAL_HEADER );
fBeg = MapViewOfFile( fMap, FILE_MAP_READ, 0, 0, size);
if (fBeg == NULL)
{
Log->Lines->Add("Ошибкапривызове MapViewOfFile(): ");
parse_error();
CloseHandle(fMap);return;
}
mz_head = (IMAGE_DOS_HEADER *)fBeg;
(DWORD)pe_head = (DWORD)fBeg + peOffset;
//Проверяем, PE или не PE файл
if ( strcmp(pe,(const char *)pe_head) != 0)
{
Log->Lines->Add("Этотфайлнеявляется Portable Executable - файлом.");
UnmapViewOfFile(fBeg);CloseHandle(fMap);
return;
}
UnmapViewOfFile(fBeg);
//По новой отображаем файл в память полностью
fBeg = MapViewOfFile( fMap, FILE_MAP_WRITE, 0, 0, 0);
if (fBeg == NULL)
{
Log->Lines->Add("Ошибкапривызове MapViewOfFile(): ");
parse_error();
CloseHandle(fMap); return;
}
3. Определяем расположение таблицы импорта, выводим информацию об используемых DLL.
mz_head = (IMAGE_DOS_HEADER *)fBeg;
(DWORD)pe_head = (DWORD)fBeg + peOffset + sizeof(DWORD);
(DWORD)pe_opt_head = (DWORD)pe_head + sizeof(IMAGE_FILE_HEADER);
//Определяем расположение таблицы импорта в секции импорта...
DWORD ImportRVA = pe_opt_head->
DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
int sect_num = -1;
//Ищем секцию с таблицей импорта...
(DWORD)sect = (DWORD)pe_opt_head + sizeof(IMAGE_OPTIONAL_HEADER);
int i;
for ( i=0; iNumberOfSections; i++)
{
if ( ImportRVA < sect->VirtualAddress )
{
sect--;
sect_num=i-1;
break;
}
sect++;
}
if (sect_num == -1)
{
Log->Lines->Add("Данная программа не использует динамические библиотеки!");
UnmapViewOfFile(fBeg);CloseHandle(fMap);
return;
}
sect++;
DWORD AfterImportSecBeg = (DWORD)fBeg + sect->PointerToRawData;
sect--;
//Получаем файловый указатель на раздел c таблицей импорта.
LPVOID ImportSecBeg;
(DWORD)ImportSecBeg = (DWORD)fBeg + sect->PointerToRawData;
//Вычисляем смещение таблицы импорта в секции
//импорта относительно ее начала (секции).
LPVOID ImportTable;
(DWORD)ImportTable = ImportRVA - sect->VirtualAddress;
(DWORD)ImportTable = (DWORD)ImportSecBeg
+ (DWORD)ImportTable;
IMAGE_IMPORT_DESCRIPTOR *DLLInfo = (IMAGE_IMPORT_DESCRIPTOR *)ImportTable;
LPVOID DLLName;
DWORD DLLCounter = 0;
//Выводим информацию об используемых DLL
while (DLLInfo->Name != NULL)
{
DLLCounter++;
(DWORD)DLLName = (DWORD)DLLInfo->Name - sect->VirtualAddress;
(DWORD)DLLName = (DWORD)ImportSecBeg + (DWORD)DLLName;
Log->Lines->Add(IntToStr(DLLCounter)+"->"+(LPSTR)DLLName);
Application->ProcessMessages();
DLLInfo++;
}
Log->Lines->Add("Всегоиспользуется "+IntToStr(DLLCounter) + " библиотек.");
4. Определяем, имеется ли в файле достаточно свободного места для размещения новой таблицы импорта.
//Вычисляем размер новой таблицы импорта:
//Суммируем количество уже используемых DLL + наша DLL + zero запись.
DWORD NewImportTableSize = sizeof(IMAGE_IMPORT_DESCRIPTOR)*(DLLCounter+2);
char dllName[] = "azx";
NewImportTableSize += strlen(dllName)+1;
//Получаем файловый указатель на конец секции импорта.
LPVOID pos;
(DWORD)pos = AfterImportSecBeg-1;
DWORD maxFree = 0;
DWORD prevPtr;
LPVOID FreePtr = NULL;
//Ищем максимальный кусок свободного места в секции...
while ( pos >= ImportSecBeg )
{
if ( *(BYTE *)pos == 0x00 )
{
prevPtr = (DWORD)pos;
while (*(BYTE *)pos == 0x00)
(DWORD)pos -= 1;
if ( ((DWORD)prevPtr - (DWORD)pos) > maxFree )
{
maxFree = ((DWORD)prevPtr - (DWORD)pos);
(DWORD)FreePtr = (DWORD)pos + 1;
}
}
(DWORD)pos -= 1;
}
//Модифицируем полученный указатель на свободный блок, т.к.
//он может указывать на завершающий нулевой DWORD
//какой-либо структуры
(LPDWORD)FreePtr +=1;
maxFree -=4;
//Проверяем объем свободного места
if ( maxFree < NewImportTableSize )
{
Log->Lines->Add("Недостаточно свободного места в таблице импорта \
для занесения информации об дополнительной библиотеке.");
UnmapViewOfFile(fBeg);
CloseHandle(fMap);
return;
}
else
Log->Lines->Add("Достаточно свободного \
места для занесения дополнительной информации.");
Application->ProcessMessages();
5. Финальная часть: Создаем в файле новую таблицу импорта и структуру IMAGE_IMPORT_BY_NAME. Записываем в файл строки с именем нашей библиотеки и импортируемой функции. Вычисляем все необходимые адреса, заносим их в структуры IMAGE_IMPORT_DESCRIPTOR, IMAGE_IMPORT_BY_NAME. Заносим в заголовок новый адрес таблицы импорта.
//1. Копируем старую таблицу импорта в новое место
memcpy(FreePtr, ImportTable, sizeof(IMAGE_IMPORT_DESCRIPTOR)*DLLCounter);
//2.1 Сохраняем строку с именем нашей DLL в старой таблице импорта
//(для экономии места)
memcpy(ImportTable, OUR_DLL_NAME, strlen(OUR_DLL_NAME));
LPDWORD zeroPtr;
(DWORD)zeroPtr = (DWORD)ImportTable + strlen(OUR_DLL_NAME);
//2.2 Сохраняем структуру IMAGE_IMPORT_BY_NAME в старой таблице импорта.
//(так же для экономии места)
IMAGE_IMPORT_BY_NAME myName;
myName.Hint = 0x00;
myName.Name[0] = 0x00;
WORD Hint = 0;
char myFuncName[] = OUR_FUNC_NAME;
hackRec patch;
patch.ZeroDword = NULL;
patch.IAT = ImportRVA + strlen(OUR_DLL_NAME) + sizeof(hackRec);
patch.IATEnd = NULL;
DWORD IIBN_Table;
memcpy(zeroPtr, &patch, sizeof(patch)); (DWORD)zeroPtr += sizeof(patch);
memcpy(zeroPtr, &Hint, sizeof(WORD)); (DWORD)zeroPtr += sizeof(WORD);
memcpy(zeroPtr, myFuncName, strlen(myFuncName)+1 );
(DWORD)zeroPtr += strlen(myFuncName)+1;
memcpy(zeroPtr, &myName, sizeof(IMAGE_IMPORT_BY_NAME) );
//2.3. Заполняем структуру IMAGE_IMPORT_DESCRIPTOR данными об нашей DLL
IMAGE_IMPORT_DESCRIPTOR myDLL;
//Вычисляем указатель на нашу структуру IMAGE_IMPORT_BY_NAME:
//это адрес начала старой таблицы импорта + длинна строки с именем
//нашей DLL + нулевой DWORD
IIBN_Table = ImportRVA + strlen( OUR_DLL_NAME ) + sizeof(DWORD);
//Указатель на таблицу Characteristics
myDLL.Characteristics = IIBN_Table;
myDLL.TimeDateStamp = NULL;
myDLL.ForwarderChain = NULL;
//Записываем адрес строки с именем файла нашей DLL
myDLL.Name = ImportRVA;
//Указатель на таблицу FirstThunk
myDLL.FirstThunk = IIBN_Table;
//Записываем в новую таблицу импорта запись о нашей DLL
LPVOID OldFreePtr = FreePtr;
(DWORD)FreePtr +=sizeof(IMAGE_IMPORT_DESCRIPTOR)*DLLCounter;
memcpy(FreePtr, &myDLL, sizeof(IMAGE_IMPORT_DESCRIPTOR));
//Создаем "финальную" нулевую запись со всеми полями равными нулю
myDLL.Characteristics = NULL;
myDLL.TimeDateStamp = NULL;
myDLL.ForwarderChain = NULL;
myDLL.Name = NULL;
myDLL.FirstThunk = NULL;
//И записываем её в конец новой таблицы импорта.