Hacked Me
Главная
Регистрация
 Administrator ICQ 399114574
// To Moderators. Если что, то можете перекинуть в статьи.
// Для Кодинга - слишком много написано
// Для Статей - хз. тема не очень, просто познавательная.
// Короче, решать вам )

(С) SLESH 2009

Сырая загрузка DLL (скрытая)


ВВЕДЕНИЕ:
Иногда появляется такая необходимость чтобы скрытно загрузить и использовать определенную DLL, но не всегда это удается сделать.

Существует 3 основных способа подгрузить в своё адресное пространство какую либо DLL:
1) Использование таблицы импорта - самый паливный способ, потому как о загрузке этой DLL будут знать все

2) Использование функции LoadLibrary и GetProcAddress - Дают намного лучше результат, но всё равно DLL можно обнаружить в списке загруженных модулей.

3) Второй способ + манипулирование с системными структурами, для удаления DLL из списка. Способ хорош, но есть один недостаток - При хуке LoadLibrary сразу будет вычислена эта DLL.

Так же эти способы обладают большим недостатком - нельзя удалить файл DLL не прекратив работу с ней.

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

АЛГОРИТМ:
Теперь можно рассмотреть алгоритм загрузки, использования и выгрузки DLL

1) Считываем заголовки из DLL (DOS + PE + SERCTIONS)
2) На основании данных в этих заголовках выделяем память под нашу DLL
3) Загружаем все секции и заголовки
4) Обрабатываем релоки
5) Обрабатываем таблицу импорта.
6) Передаем управление на точку входа в DLL с флагом указывающим что мы подгружаем DLL
7) т.к. система ничего не знает о нашей DLL то придется самому получать адреса функций из таблицы экспорта.
8) получаем вручную адрес функции и используем её
9) уведомляем DLL и том, что мы её выгружаем
10) освобождаем память.

С виду кажется что очень много необходимо сделать, хотя на практике это всё занимает строчек 300-350.

Для реализации всего будет использован Си компилятор от VS++ 2008.

И так нам необходимо теперь написать 3 функции.
HideLoadLibrary - для загрузки DLL
HideGetProcAddress - для поиска адресов
HideFreeLibrary - для выгрузки DLL
Также нам предварительно понадобятся следующие типы
Код:
#define MAX_SECTIONS 10 // максимальное кол-во секций в файле typedef unsigned long ULONG, *PULONG; typedef unsigned short USHORT, *PUSHORT; // структура для релоков typedef struct _FIXREC { ULONG PageRVA; ULONG BlockSize; USHORT TOR[]; } TFIXREC,*PFIXREC; // структура таблицы импорта typedef struct _IMPORT_TABLE { ULONG LookUp; ULONG TimeStamp; ULONG ForwardChain; ULONG NameRVA; ULONG AddresTableRVA; } IMPORT_TABLE, *PIMPORT_TABLE; #pragma pack(1) // отключаем выравнение // структура таблица адресов typedef struct _ADDRESS_TABLE { USHORT Hint; char Name[]; } ADDRESS_TABLE, *PADDRESS_TABLE; #pragma pack() // выключаем выравнение



STEP 1: Загрузка DLL

Код:
HMODULE __stdcall HideLoadLibrary(char * LibFileName) { ULONG retadr = 0; DWORD rb; HANDLE hFile; IMAGE_DOS_HEADER DosHeader; IMAGE_NT_HEADERS PeHeader; IMAGE_SECTION_HEADER Section[MAX_SECTIONS]; char tmp[1024]; // если dll ранее была загружена retadr = (ULONG)GetModuleHandleA(LibFileName); if (retadr) return (HMODULE)retadr; // то вернем её адрес и не будем мучатся // откроем файл на чтение hFile = CreateFileA(LibFileName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (hFile != INVALID_HANDLE_VALUE) { // считаем DOS заголовок ReadFile(hFile, &DosHeader, sizeof(IMAGE_DOS_HEADER), &rb, 0); if (DosHeader.e_magic == IMAGE_DOS_SIGNATURE) // проверим сигнатуру { // если есть какимето данные между DOS заголовком и PE // то считаем их. В MS компиляторах это часто Rich данные if (sizeof(IMAGE_DOS_HEADER) < DosHeader.e_lfanew) { ReadFile(hFile, &tmp[0], DosHeader.e_lfanew - sizeof(IMAGE_DOS_HEADER), &rb, 0); } // установим указатель в файле на PE заголовок SetFilePointer(hFile, DosHeader.e_lfanew, 0, FILE_BEGIN); // считаем заголовок ReadFile(hFile, &PeHeader, sizeof(IMAGE_NT_HEADERS), &rb, 0); if (PeHeader.Signature == IMAGE_NT_SIGNATURE) // проверим сигнатуру { // считаем 10 секций ReadFile(hFile, &Section[0], sizeof(IMAGE_SECTION_HEADER)*PeHeader.FileHeader.N umberOfSections, &rb, 0); // выделим память столько, сколько указано в SIZE OF BASE retadr = (ULONG)VirtualAlloc(0, PeHeader.OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE); if (retadr) // если память выделилась { // скопируем туда DOS заголовок memcpy((void*) retadr, &DosHeader, sizeof(IMAGE_DOS_HEADER)); // скопируем туда PE заголовок memcpy((void*)(retadr + DosHeader.e_lfanew), &PeHeader, sizeof(IMAGE_NT_HEADERS)); // скопируем туда таблицу секций memcpy((void*)(retadr + DosHeader.e_lfanew + sizeof(IMAGE_NT_HEADERS)), &Section[0], sizeof(IMAGE_SECTION_HEADER) * PeHeader.FileHeader.NumberOfSections); // если есть Rich данные то и их тоже скопируем if (sizeof(IMAGE_DOS_HEADER) < DosHeader.e_lfanew) { memcpy((void*)(retadr + sizeof(IMAGE_DOS_HEADER)), &tmp[0], DosHeader.e_lfanew - sizeof(IMAGE_DOS_HEADER)); } // обработаем каждую секцию for (int i = 0; i < PeHeader.FileHeader.NumberOfSections; i++) { // установим указатель в файле не начало секции в файле SetFilePointer(hFile, Section[i].PointerToRawData, 0, FILE_BEGIN); // считаем всё секцию ReadFile(hFile, (void*)(retadr + Section[i].VirtualAddress), Section[i].SizeOfRawData, &rb,0); } // Обработаем релоки if (!ProgressReloc(retadr)) // если ошибка { VirtualFree((void*)retadr, 0, MEM_RELEASE); // освободим память retadr = 0; } else if (!ProgressImport(retadr))// обработаем импорт { VirtualFree((void*)retadr, 0, MEM_RELEASE);// если ошибка освободим память retadr = 0; } else { __asm { mov eax, PeHeader.OptionalHeader.AddressOfEntryPoint add eax, retadr // EAX = ENTRY POINT push 0 push DLL_PROCESS_ATTACH // ставим флаг что подгрузили DLL push retadr call eax // передадим управление на точку входа в DLL } } } } } CloseHandle(hFile); // закрываем файл } return (HMODULE)retadr; // возвращаем адрес загруженного модуля в памяти }


Обработка релоков представляет из себя следующую функцию:

Код:
// обработка релоков ULONG ProgressReloc(ULONG filebase) { ULONG PE; ULONG IB; ULONG cnt; ULONG x; ULONG Delta; PFIXREC fixrec; USHORT fixtype; USHORT fixoffset; PE = *(ULONG*)(filebase + 0x3C) + filebase; // получаем адрес PE заголовка IB = *(ULONG*)(PE + 0x34); // IMAGE BASE if (filebase == IB) return 1; // Если совпадает с загруженным адресом, то фиксить не нужно ничего Delta = filebase - IB; // выцесляем дельта смещение. if (!*(ULONG*)(PE + 0xA0)) return 1; // если нет релоков то выходим fixrec = (PFIXREC)(*(ULONG*)(PE + 0xA0) + filebase); // получаем адрес таблицы релоков while (fixrec->BlockSize) // если таблица не пуста { cnt = (fixrec->BlockSize - 8) >> 1; // вычеслим кол-во элементов for (x = 0; x < cnt; x++) { fixtype = (fixrec->TOR[x]) >> 12; // типа фиксации fixoffset = (fixrec->TOR[x]) % 4096; // офсет внутри 4-х килобайтового блока if (!fixtype) continue; // если 0, то фиксация не нужна if (fixtype == 3) // если 3, то прибавить дельта смещение { *(ULONG*)(filebase+fixoffset+fixrec->PageRVA) = *(ULONG*)(filebase+fixoffset+fixrec->PageRVA) + Delta; } else return 0; // все остальные случае вызовут ошибку (хотя их и не будет теоретически) } fixrec = (PFIXREC)((ULONG)fixrec + fixrec->BlockSize); // следующая таблица реловок } return 1; }


Обработка таблицы импорта:

Код:
// Обработчик импорта ULONG ProgressImport(ULONG filebase) { ULONG PE; HMODULE lib; PIMPORT_TABLE ImportTable; PADDRESS_TABLE AddressTable; ULONG IAT_Index; ULONG RVA; ULONG addr; PE = *(ULONG*)(filebase + 0x3C) + filebase; // адрес PE заголовка if (!*(ULONG*)(PE + 0x80)) return 1; // если нет импорта то выходим ImportTable = (PIMPORT_TABLE)(*(ULONG*)(PE + 0x80) + filebase); // адрес таблицыы импорта while (ImportTable->NameRVA) // пока есть DLL откуда нужно импортировать функции { // проверим что DLL была ранее загружена lib = GetModuleHandleA((char*)(ImportTable->NameRVA + filebase)); if (!lib) // если не загружена была, то загрузим её. { lib = LoadLibraryA((char*)(ImportTable->NameRVA + filebase)); } if (!lib) return 0; // если не загрузилась, значит ошибка if (ImportTable->LookUp) // Если импорт идет через LookUp { RVA = ImportTable->LookUp + filebase; } else // если через таблицу адресов импорта { RVA = ImportTable->AddresTableRVA + filebase; } IAT_Index = 0; while (*(ULONG*)RVA) // если есть ссылка на таблицу имен { AddressTable = (PADDRESS_TABLE)(*(ULONG*)RVA + filebase); // получаем адрес структуры где хранится HINT NAME if (AddressTable->Name[0]) // если импорт по имени { addr = (ULONG)GetProcAddress(lib, AddressTable->Name); // найдем адрес } else // если импорт по ординалу { addr = (ULONG)GetProcAddress(lib, (char*)AddressTable->Hint); } // если есть IAT то сохраним в неё найденный адрес if (ImportTable->AddresTableRVA) { *(ULONG*)(ImportTable->AddresTableRVA + filebase + IAT_Index) = addr; } else // иначе сохраним туда откуда брали { *(ULONG*)RVA = addr; } RVA += 4; // сделающий элемент IAT_Index += 4; } ImportTable = (PIMPORT_TABLE)((ULONG)ImportTable+sizeof(IMPORT_T ABLE)); // следующая таблица } return 1; }


Теперь мы может использовать функцию HideLoadLibrary точно также как и LoadLibrary
Важно отметить, что нельзя использовать функцию GetProcAddress для DLL загруженных через HideLoadLibrary. Это связанно с тем, что система предварительно проверяет факт загрузки DLL итд итп. По этому необходимо будет использовать собственную функцию - HideGetProcAddress, которая является полным аналогом GetProcAddress. Но при этом умеет искать адреса в DLL, которые загруженные не только через HideLoadLibrary, но и даже через LoadLibrary.

Также после загрузки DLL можно вообще удалить её файл. Как видно из кода, его быстро можно переписать так, чтобы можно было загружать DLL сразу из памяти. Это будет удобно для реализации дополнительной защиты программ.

STEP 2: Поиск адресов
Как уже было выше сказано – функция HideGetProcAddress находит адреса в DLL. При этом не важно, каким образом была загружена DLL в память, главное чтобы она была корректно загружена (обработка импорта и релоков). Также как GetProcAddress, данная функция может принимать в качестве параметров не только имя функции, но и её ординал.

Код функции HideGetProcAddress

Код:
FARPROC _stdcall HideGetProcAddress(HMODULE hModule, char* lpProcName) { ULONG PE; PIMAGE_EXPORT_DIRECTORY ED; PULONG NamePointerRVA; PUSHORT OrdinalTableRVA; PULONG AddressTableRVA; ULONG ret = 0; USHORT hint = 0; USHORT index; char * name; ULONG addr; ULONG CNT_FUNC; if ((ULONG)lpProcName <= 0xFFFF)// если импорт по ординалу { hint = (USHORT)lpProcName; // запишем наш ординал } if (hModule) // если указан модуль откуда грузить { PE = *(ULONG*)((ULONG)hModule + 0x3C) + (ULONG)hModule;// адрес PE заголовка ED = (PIMAGE_EXPORT_DIRECTORY)(*(ULONG*)((ULONG)PE + 0x78) + (ULONG)hModule); // адрес таблицы экспорта NamePointerRVA = (ULONG*)(ED->AddressOfNames + (ULONG)hModule); // адрес таблицы имен OrdinalTableRVA = (USHORT*)(ED->AddressOfNameOrdinals + (ULONG)hModule); // адрес таблицы ординалов AddressTableRVA = (ULONG*)(ED->AddressOfFunctions + (ULONG)hModule); // адрес таблицы адерсов // вычесляем наибольшее значение - кол-во функций if (ED->NumberOfNames > ED->NumberOfFunctions) { CNT_FUNC = ED->NumberOfNames; } else { CNT_FUNC = ED->NumberOfFunctions; } // пройдемся по всем функциям for (USHORT x = 0; x < CNT_FUNC; x++) { if (x < ED->NumberOfFunctions) // если есть имя у функции { name = (char*)(NamePointerRVA[x] + (ULONG)hModule); // запомним имя index = OrdinalTableRVA[x]; // запомним ординал } else // если имени нет { name = 0; // имени нет index = x; // ординал = текущей позиции } // вычесляем адрес функции addr = AddressTableRVA[index] + (ULONG)hModule; if ((hint == index + ED->Base)|| // если это наш ординал (name && !strcmp(name, lpProcName))) // если есть имя и оно наше { ret = addr; // нашли адрес break; // прекратим обход экспорта } } } return (FARPROC)ret; // вернем адрес }


STEP 3: Выгрузка DLL

Если в процессе работы нам уже не нужна DLL, то мы можем её выгрузить по средствам функции HideFreeLibrary.
Важно отметить, что нестоит выгружать через данную функцию DLL загруженные через LoadLibrary.
Хоть эта функция и является аналогом FreeLibrary, но всё же она далека от неё.

Код HideFreeLibrary:
Код:
// функция выгрузки DLL BOOL __stdcall HideFreeLibrary(HMODULE hModule) { PIMAGE_DOS_HEADER DosHeader; PIMAGE_NT_HEADERS PeHeader; BOOL ret = false; ULONG EP; if (hModule) // если указан адрес модуля { DosHeader = (PIMAGE_DOS_HEADER)hModule; // DOS заголово PeHeader = (PIMAGE_NT_HEADERS)(DosHeader->e_lfanew + (ULONG)hModule);// PE заголовок EP = PeHeader->OptionalHeader.AddressOfEntryPoint + (ULONG)hModule; // Точка входа __asm { push 0 push DLL_PROCESS_DETACH // флаг выгрузки DLL push hModule call EP // передаем управление на точку входа в DLL } VirtualFree(hModule, 0, MEM_RELEASE); // очищаем память ret = true; } return ret; }


ТЕСТИРОВАНИЕ
Для теста попробуем подгрузить копию user32.dll. Найти в ней адрес функции MessageBoxA и вызвать её.

Для начала скопируем библиотеку с новым именем:
Windows\system32\user32.dll к примеру в e:\u32.dll

Код тестовой программы (консольный проект):
Код:
// переменная для адреса MessageBoxA int (WINAPI *MY_MessageBox)(DWORD, char*, char*, DWORD); int _tmain(int argc, _TCHAR* argv[]) { HMODULE lib; // загрузим скрыто DLL lib = HideLoadLibrary("e:\\u32.dll"); // выведем на экран адрес куда она была загружена printf("LIB=%0.8X\n", lib); if (lib) // если загружена нормально { // ищем адрем функции *(FARPROC *)&MY_MessageBox = HideGetProcAddress(lib, "MessageBoxA"); // выводим найденный адрес printf("MY_MessageBox=%0.8X\n", MY_MessageBox); if (MY_MessageBox) // если адрес найден { // вызываем функцию MY_MessageBox(0, "TEST MSG", "MY MESSAGE BOX", MB_ICONINFORMATION); } HideFreeLibrary(lib); // выгружаем DLL } return 0; }


Тестирование показало следующее:
1) Загружаются спокойно DLL написанные на С, FASM и Delphi (остальные не проверял)
2) Многие системные DLL (kernel32.dll, user32.dll) – тоже загружаются без проблем
3) Проблема возникла лишь при загрузке ws2_32.dll.
Т.е. сама DLL загрузилась без проблем, а также без проблем были найдены адреса функций. Но функция WSAStartup возвращала значение об ошибке:
Цитата:
WSAStartup cannot function at this time because the underlying system it uses to provide network services is currently unavailable

Возможно, это связано с дополнительной инициализацией DLL, которая происходит в ядре и используется функцией LoadLibrary.

ЛИТЕРАТУРА
Для полного понимания этого всего, советую почитать следующие вещи

1) ФОРМАТ ИСПОЛНЯЕМЫХ ФАЙЛОВ PortableExecutables (PE) // Hard Wisdom Это txt файл (PEFMT003.TXT) в котором описаны основные структуры PE файла. Находится в аттаче.
2) Путь воина – внедрение в pe/coff файлы // Крис Касперски
Хорошее описание PE файлов, особенно таблиц импорта и экспорта.
Находится по адресу http://www.insidepro.com/kk/019/019r.shtml

P.S. В аттаче найдете исходники всего этого и файл PEFMT003.TXT

(С) SLESH 2009