11357-1 (662950), страница 2
Текст из файла (страница 2)
Запись в файл новой таблицы импорта и всех новых структур.
Установка нового указателя на таблицу импорта в заголовке (второй элемент массива в 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 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;
//И записываем её в конец новой таблицы импорта.
(DWORD)FreePtr +=sizeof(IMAGE_IMPORT_DESCRIPTOR)*DLLCounter;
memcpy(FreePtr, &myDLL, sizeof(IMAGE_IMPORT_DESCRIPTOR));
//3. Устанавливаем указатель на нашу таблицу импорта.
// Вычисляем RVA нашей таблицы
DWORD NewImportTableRVA = (DWORD)OldFreePtr - (DWORD)ImportSecBeg +
sect->VirtualAddress;
// Заносим его в DataDirectory
pe_opt_head->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress =
NewImportTableRVA;
pe_opt_head->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size =
(DLLCounter + 1) * sizeof(IMAGE_IMPORT_DESCRIPTOR);
UnmapViewOfFile(fBeg);
CloseHandle(fMap);
Вывод
Данная методика позволяет внедрять свою DLL библиотеку в программы, имеющие достаточно свободного места в секции с таблицей импорта. Приведенная программа может быть доработана в следующих направлениях:
Создание таблицы импорта в другой секции (если в секции с оригинальной таблицей не хватает места)
Создание новой секции и хранение новой таблицы импорта в ней.
Отдельное слово стоит сказать об .exe файлах, входящих в стандартную поставку Windows(таких как calc.exe, paint.exe, wordpad.exe, etc.). У них таблица импорта продублирована в начале файла, между MZ- и PE- заголовками, поэтому при модификации таких файлов необходимо в соответсвующих записях в DataDirectory обнулить адреса на эти таблицы (подробнее см. файл winnt.h, раздел Directory Entries).
Список литературы
Для подготовки данной работы были использованы материалы с сайта http://www.bugtraq.ru/