Жорстке впровадження DLL в Windows-програми b> b> p>
На суд читача видається
стаття про впровадження власного виконуваного коду в Windows програми. p>
У статті використані різні
технічні терміни, що відносяться до програмування під ОС Windows, зокрема
із застосуванням Windows API. Тексти програм, наведені як приклади,
тестувалися в середовищі Borland C + + Builder 6.0. Робота програми перевірялася в
середовищі Windows 2000, Windows 9x середу в силу морального старіння НЕ
розглядалася. p>
Введення в
спеціальність h2>
Впровадження свого коду в чужу
програму може знадобиться для безлічі завдань, наприклад, для налагодження і
аналізу роботи додатків, для встановлення різного роду програмних захистів, для
"шпигунських" цілей. На
сьогоднішній день існує досить багато методів впровадити свій код в чужу
програму. Досить докладно вони описані в статті автора Tanaka "Програми-невидимки"
, Однак усі
описані методи є зовнішніми по відношенню до цільової програми, тобто для
впровадження коду кожного разу необхідний запуск стороннього додатку, так
званого "завантажувача", що здійснює впровадження. Пропонований
спосіб відрізняється тим, що здійснює разову, досить просту модифікацію
внутрішніх структур. exe файлу, не модифікує виконуваний код програми, не
збільшує розмір файлу, не потребує зовнішнього завантажувача, практично непомітний
для користувача. Метод дозволяє модифікувати більшість. Exe файлів. P>
Як це
працює h2>
Принцип роботи заснований на
наступному: практично будь-яка Windows-програма використовує динамічні
бібліотеки (DLL). У них можуть зберігатися різні функції (у тому числі системні
- В USER32.DLL, GDI.DLL і т.д.), ресурси типу діалогів, ікон, картинок,
покажчиків миші. Досить складно знайти програму, яка не використовувала б
DLL взагалі. Відповідно, програма, що використовує DLL-бібліотеки, містить у
своєму. exe файлі інформацію про те, які саме бібліотеки їй потрібні, і які
функції з цих бібліотек вона використовує (імпортує). При запуску будь-якої
програми завантажувач Windows зчитує список використовуваних цієї
програмою DLL-бібліотек і завантажує (відображає) їх у адресний простір
програми. Після цього для кожної імпортованої програмою функції завантажувач
визначає адресу виклику. p>
Кожна DLL-бібліотека містить функцію з ім'ям DllMain наступного
виду: p>
BOOL WINAPI DllMain (HINSTANCE hinstDll, DWORD fdwReason, PVOID
fImpLoad); p>
Призначення
її - суто інформаційний. Викликаючи цю функцію, завантажувач повідомляє бібліотеці
про те, що вона буде підключена до якогось процесу, або в контексті процесу
відбувається створення потоку. Завантаження будь-якої програми включає в себе
послідовний виклик функцій DllMain всіх використовуваних DLL. Аналогічно даний
виклик здійснюється при відключенні і вивантаження бібліотеки. Думаю, що суть
методу вам вже зрозуміла: досить додати до списку використовуваних програмою
DLL-бібліотек свою, у якій функція DllMain містить необхідний вам код.
Весь код в рамках цієї ф-ції буде виконуватись з пріоритетом
"зарядженої" програми. p>
У міру скромного уяви наведу кілька прикладів
використання даної методики для вирішення практичних завдань. Перше, що
спадає на думку - це система "навісний" захисту, яку можна
встановити на будь-яке готове додаток. Метод перевірки допуску може бути будь-яким
- Від найпростішого пароля, до звернення до зовнішнього пристрою, що містить
private-key для декодування частини виконуваного коду програми (наприклад,
USB-ключ). Далі - як було сказано в передмові - будь-які види троянів/вірусів. P>
Рецепт h2>
Що ж, тепер нам необхідно
змінити. exe файл таким чином, що б у списку використовуваних програмою DLL
бібліотек з'явилася наша бібліотека. На перший погляд дана задача
представляється як "темний ліс" - де цей список взяти, чого в ньому
шукати, як чого міняти? Але все не так сумно! До нашого з вами щастя,
формат. exe файлу Windows досить строго стандартизований і докладно описаний
в документації. Бажаючим детально покопирсатися у нутрощах можу
порадити ознайомитися з ось цим документом: p>
Peering
Inside the PE: A Tour of the Win32 Portable Executable File Format, p>
для інших я постараюсь
привести тут мінімум інформації, необхідний для реалізації програми,
"заряджають" нашим кодом майже будь-який. exe файл. (Щодо
обмежень методу - див. гл. Висновки)
Приступаючи до пацієнта. Формат файлу програми, так само званий
"стерпним виконавчим" (PE - portable executable), визначає
поведінка операційної системи на всіх етапах роботи - починаючи від відображення
файлу на адресний простір процесу, завантаження необхідних бібліотек,
ініціалізації ресурсів, до власне вивантаження програми. Найважливіше з того,
що варто знати про РЕ-файлах, це те, що виконуваний файл на диску і модуль,
одержуваний після завантаження, дуже схожі. Причиною цього є те, що
завантажувач Windows повинен створити з дискового файлу виконуваний процес без
великих зусиль. Точніше кажучи, завантажувач просто використовує механізми
відображення файлів у пам'ять, щоб завантажити відповідні частини РЕ-файл до
адресний простір програми .* Так само просто завантажується і DLL. Після того
як ЕХЕ або. DLL модулі завантажені, Windows поводиться з ними так само, як і з
іншими відображеними в пам'ять файлами. p>
p>
* Див довідку за функція CreateFile, MapViewOfFile,
CreateFileMapping. p>
Так
як структура виконавчого файлу є досить громіздкою, вникати
докладно в опис кожного її елемента ми не буде, лише коротко зупинимося на
необхідних нам елементах. Всі файли
програм для Windows мають такий вигляд (див. рис. 1): p>
p >
рис. 1 Структура виконавчого
файлу. p>
1. Заголовок MSDOS Починаючи з нульового зсуву, у файлі
розташовується заголовок MSDOS, що має формат IMAGE_DOS_HEADER (див. файл
winnt.h). У цьому заголовку нас цікавить тільки одне поле: p>
IMAGE_DOS_HEADER-> e_lfanew, p>
містить зміщення сигнатури
файлу. p>
2. Сигнатура PE-файлу Сигнатура, інакше - підпис, що означає, що
цей файл є виконавчим файлом для WIndows. Являє собою рядок
p>
PEif (sect_num == -1) p>
( p>
Log-> Lines-> Add ( "Дана програма не використовує
Динамічні бібліотеки !"); p>
UnmapViewOfFile (fBeg); CloseHandle (fMap); p>
return; p>
) p>
sect ++; p>
DWORD AfterImportSecBeg =
(DWORD) fBeg + sect-> PointerToRawData; p>
sect -; p>
// Отримуємо файловий вказівник на розділ c таблицею імпорту. p>
LPVOID ImportSecBeg; p>
(DWORD) ImportSecBeg =
(DWORD) fBeg + sect-> PointerToRawData; p>
// Обчислюємо зсув таблиці імпорту в секції p>
// імпорту щодо її початку (секції). p>
LPVOID ImportTable; p>
(DWORD) ImportTable = ImportRVA - sect-> VirtualAddress; p>
(DWORD) ImportTable =
(DWORD) ImportSecBeg p>
+
(DWORD) ImportTable; p>
IMAGE_IMPORT_DESCRIPTOR
* DLLInfo = (IMAGE_IMPORT_DESCRIPTOR *) ImportTable; p>
LPVOID DLLName; p>
DWORD DLLCounter = 0; p>
// Виводимо інформацію про використовувані DLL p>
while (DLLInfo-> Name! = NULL) p>
( p>
DLLCounter ++; p>
(DWORD) DLLName =
(DWORD) DLLInfo-> Name - sect-> VirtualAddress; p>
(DWORD) DLLName =
(DWORD) ImportSecBeg + (DWORD) DLLName; p>
Log-> Lines-> Add (IntToStr (DLLCounter )+"->"+( LPSTR) DLLName); p>
Application-> ProcessMessages (); p>
DLLInfo ++; p>
) p>
Log-> Lines-> Add ( "Усього використовується" + IntToStr (DLLCounter) +
"Бібліотек ."); p>
4.
Визначаємо, чи є у файлі достатньо вільного місця для розміщення нової
таблиці імпорту. p>
// Обчислюємо розмір нової таблиці імпорту: p>
// Підсумовуємо кількість вже використовуються DLL + наша DLL + zero
запис. p>
DWORD NewImportTableSize =
sizeof (IMAGE_IMPORT_DESCRIPTOR) * (DLLCounter +2); p>
char dllName [] =
"azx"; p>
NewImportTableSize + =
strlen (dllName) 1; p>
// Отримуємо файловий вказівник на кінець секції імпорту. p>
LPVOID pos; p>
(DWORD) pos =
AfterImportSecBeg-1; p>
DWORD maxFree = 0; p>
DWORD prevPtr; p>
LPVOID FreePtr = NULL; p>
// Шукаємо максимальний шматок вільного місця в секції ... p>
while (pos> = ImportSecBeg) p>
( p>
if (* (BYTE *) pos ==
0x00) p>
( p>
prevPtr = (DWORD) pos; p>
while (* (BYTE *) pos ==
0x00) p>
(DWORD) pos -= 1; p>
if (((DWORD) prevPtr --
(DWORD) pos)> maxFree) p>
( p>
maxFree =
((DWORD) prevPtr - (DWORD) pos); p>
(DWORD) FreePtr =
(DWORD) pos + 1; p>
) p>
) p>
(DWORD) pos -= 1; p>
) p>
// модифікуємо отриманий покажчик на вільний блок, тому що p>
// він може вказувати на завершальний нульовий DWORD p>
// будь-якої структури p>
(LPDWORD) FreePtr + = 1; p>
maxFree -= 4; p>
// Перевіряємо обсяг вільного місця p>
if (maxFree
( p>
Log-> Lines-> Add ( "Недостатньо вільного місця в
таблиці імпорту p>
для занесення інформації про додаткової
бібліотеці ."); p>
UnmapViewOfFile (fBeg); p>
CloseHandle (fMap); p>
return; p>
) p>
else p>
Log-> Lines-> Add ( "Достатньо вільного p>
місця для занесення додаткової інформації ."); p>
Application-> ProcessMessages (); p>
5. Фінальна частина: Створюємо у файлі нову таблицю імпорту та
структуру IMAGE_IMPORT_BY_NAME. Записуємо в файл рядки з ім'ям нашої
бібліотеки та імпортованої функції. Обчислюємо всі необхідні адреси, заносимо
їх у структури IMAGE_IMPORT_DESCRIPTOR, IMAGE_IMPORT_BY_NAME. Заносимо в заголовок
нова адреса таблиці імпорту. p>
// 1. Копіюємо стару таблицю імпорту в нове місце p>
memcpy (FreePtr, ImportTable,
sizeof (IMAGE_IMPORT_DESCRIPTOR) * DLLCounter); p>
// 2.1 Зберігаємо рядок з іменем нашої DLL в старій таблиці імпорту p>
// (для економії місця) p>
memcpy (ImportTable, OUR_DLL_NAME, strlen (OUR_DLL_NAME )); p>
LPDWORD zeroPtr; p>
(DWORD) zeroPtr =
(DWORD) ImportTable + strlen (OUR_DLL_NAME); p>
// 2.2 Зберігаємо структуру IMAGE_IMPORT_BY_NAME в старій таблиці імпорту. p>
// (так само для економії місця) p>
IMAGE_IMPORT_BY_NAME myName; p>
myName.Hint = 0x00; p>
myName.Name [0] = 0x00; p>
WORD Hint = 0; p>
char myFuncName [] =
OUR_FUNC_NAME; p>
hackRec patch; p>
patch.ZeroDword = NULL; p>
patch.IAT = ImportRVA +
strlen (OUR_DLL_NAME) + sizeof (hackRec); p>
patch.IATEnd = NULL; p>
DWORD IIBN_Table; p>
memcpy (zeroPtr, & patch,
sizeof (patch)); (DWORD) zeroPtr + = sizeof (patch); p>
memcpy (zeroPtr, & Hint,
sizeof (WORD)); (DWORD) zeroPtr + = sizeof (WORD); p>
memcpy (zeroPtr, myFuncName,
strlen (myFuncName) +1); p>
(DWORD) zeroPtr + =
strlen (myFuncName) 1; p>
memcpy (zeroPtr, & myName,
sizeof (IMAGE_IMPORT_BY_NAME)); p>
// 2.3. Заповнюємо структуру IMAGE_IMPORT_DESCRIPTOR даними про нашої DLL p>
IMAGE_IMPORT_DESCRIPTOR myDLL; p>
// Обчислюємо покажчик на нашу структуру IMAGE_IMPORT_BY_NAME: p>
// це адреса початку старої таблиці імпорту + довжина рядка з
ім'ям p>
// нашій DLL + нульовий DWORD p>
IIBN_Table = ImportRVA + strlen (OUR_DLL_NAME) + sizeof (DWORD); p>
// Покажчик на таблицю Characteristics p>
myDLL.Characteristics = IIBN_Table; p>
myDLL.TimeDateStamp = NULL; p>
myDLL.ForwarderChain =
NULL; p>
// Записуємо адреса рядки з ім'ям файлу нашої DLL p>
myDLL.Name = ImportRVA; p>
// Покажчик на таблицю FirstThunk p>
myDLL.FirstThunk = IIBN_Table; p>
// Записуємо в нову таблицю імпорту запис про нашу DLL p>
LPVOID OldFreePtr = FreePtr; p>
(DWORD) FreePtr
+ = sizeof (IMAGE_IMPORT_DESCRIPTOR) * DLLCounter; p>
memcpy (FreePtr, & myDLL,
sizeof (IMAGE_IMPORT_DESCRIPTOR )); p>
// Створюємо "фінальну" нульову запис з усіма полями рівними
нулю p>
myDLL.Characteristics = NULL; p>
myDLL.TimeDateStamp = NULL; p>
myDLL.ForwarderChain = NULL; p>
myDLL.Name = NULL; p>
myDLL.FirstThunk = NULL; p>
p>
// І записуємо її в кінець нової таблиці імпорту. p>
(DWORD) FreePtr + = sizeof (IMAGE_IMPORT_DESCRIPTOR) * DLLCounter; p>
memcpy (FreePtr, & myDLL,
sizeof (IMAGE_IMPORT_DESCRIPTOR )); p>
// 3. Встановлюємо покажчик на нашу таблицю імпорту. P>
// Обчислюємо RVA нашої таблиці p>
DWORD NewImportTableRVA = (DWORD) OldFreePtr - (DWORD) ImportSecBeg + p>
sect-> VirtualAddress; p>
// заносити його в DataDirectory p>
pe_opt_head-> DataDirectory [IMAGE_DIRECTORY_ENTRY_IMPORT]. VirtualAddress
= p>
NewImportTableRVA; p>
pe_opt_head-> DataDirectory [IMAGE_DIRECTORY_ENTRY_IMPORT]. Size
= p>
(DLLCounter + 1) *
sizeof (IMAGE_IMPORT_DESCRIPTOR); p>
UnmapViewOfFile (fBeg); p>
CloseHandle (fMap); p>
Висновок h2>
Ця методика дозволяє
впроваджувати свою DLL бібліотеку в програми, що мають достатньо вільного місця в
секції з таблицею імпорту. Наведена програма може бути доопрацьована
наступних напрямках: p>
Створення таблиці імпорту в
другий секції (якщо в секції з оригінальною таблицею не вистачає місця) p>
Створення нової секції та зберігання
нової таблиці імпорту в ній. p>
Особисте слово варто сказати про. exe файлах, що входять до
стандартну поставку Windows (таких як calc.exe, paint.exe, wordpad.exe, etc.).
У них таблиця імпорту продубльована на початку файлу, між MZ-і PE-заголовками,
тому при модифікації таких файлів необхідно у відповідних записів у
DataDirectory обнулити адреси на ці таблиці (докладніше див файл winnt.h,
розділ Directory Entries). p>
Список
літератури h2>
Для підготовки даної роботи
були використані матеріали з сайту http://www.bugtraq.ru/
p>