ПЕРЕЛІК ДИСЦИПЛІН:
  • Адміністративне право
  • Арбітражний процес
  • Архітектура
  • Астрологія
  • Астрономія
  • Банківська справа
  • Безпека життєдіяльності
  • Біографії
  • Біологія
  • Біологія і хімія
  • Ботаніка та сільське гос-во
  • Бухгалтерський облік і аудит
  • Валютні відносини
  • Ветеринарія
  • Військова кафедра
  • Географія
  • Геодезія
  • Геологія
  • Етика
  • Держава і право
  • Цивільне право і процес
  • Діловодство
  • Гроші та кредит
  • Природничі науки
  • Журналістика
  • Екологія
  • Видавнича справа та поліграфія
  • Інвестиції
  • Іноземна мова
  • Інформатика
  • Інформатика, програмування
  • Юрист по наследству
  • Історичні особистості
  • Історія
  • Історія техніки
  • Кибернетика
  • Комунікації і зв'язок
  • Комп'ютерні науки
  • Косметологія
  • Короткий зміст творів
  • Криміналістика
  • Кримінологія
  • Криптология
  • Кулінарія
  • Культура і мистецтво
  • Культурологія
  • Російська література
  • Література і російська мова
  • Логіка
  • Логістика
  • Маркетинг
  • Математика
  • Медицина, здоров'я
  • Медичні науки
  • Міжнародне публічне право
  • Міжнародне приватне право
  • Міжнародні відносини
  • Менеджмент
  • Металургія
  • Москвоведение
  • Мовознавство
  • Музика
  • Муніципальне право
  • Податки, оподаткування
  •  
    Бесплатные рефераты
     

     

     

     

     

     

         
     
    Методи перехоплення API-викликів в Win32
         

     

    Інформатика, програмування

    Методи перехоплення API-викликів в Win32

    Ігор В. Філімонов

    Введення

    Дана стаття написана внаслідок аналізу відомих методів перехоплення API-викликів в Windows. У деяких широко відомих прикладах реалізації перехоплення системних функцій є невеликі помилки, які в деяких випадках призводять до того, що перехоплення не працює. Один з таких прикладів був описаний в RSDN Magazine # 1, інший - у відомій книзі Джеффрі Ріхтера «Windows для професіоналів: створення ефективних Win32-додатків із урахуванням специфіки 64-розрядної версії Windows », 4-е видання.

    Перехоплення системних функцій операційної системи -- прийом, відомий давно. Зазвичай перехоплюється деяка системна функція з метою моніторингу або зміни її поведінки. За часів DOS програмісти перехоплювали програмні переривання (int 21h, int 16h, int 10h). З приходом Win16 знадобилися кошти для перехоплення API-функцій. І, нарешті, з появою Win32 засоби перехоплення ще раз еволюціонували, підстроївшись під нову систему. Операційні системи сімейства Windows ніколи не містили вбудованих коштів, спеціально призначених для перехоплення системних функцій. І зрозуміло чому - все-таки це трохи хакерський прийом. Тому перехоплення зазвичай здійснюється «підручними засобами», і для його реалізації потрібно чітко представляти багато глибинні аспекти пристрої та функціонування операційної системи.

    У даній статті розглядаються методи реалізації перехоплення системних API-функцій в 32-розрядних операційних системах Windows. Розглядаються особливості реалізації перехоплення в Win9X (Windows 95/98/98SE/ME) і WinNT (Windows NT/2000/XP/2003).

    Особливості організації пам'яті в Windows

    Так як перехоплення практично завжди пов'язаний з модифікацією пам'яті (або коду перехоплюваних функції, або таблиць імпорту/експорту), то для його здійснення необхідно враховувати особливості архітектури пам'яті WinNT і Win9X.

    Кожному процесу (починаючи з Windows 95) виділяється власне віртуальне адресний простір. Для 32-розрядних процесів його розмір складає 4 Гб. Це адресний простір розбивається на розділи, функціональне призначення і властивості яких досить сильно відрізняються у сімейств ОС WinNT і Win9Х.

    Адресний простір будь-якого процесу в Win9Х можна розділити на три розділи:

    Молодші два гігабайти (00400000-7FFFFFFF) - код і дані для користувача режиму (у діапазоні 00000000-003FFFFF розташовані пристрої для виявлення нульових покажчиків і для сумісності з програмами DOS і Win16);

    Третій гігабайт - для загальних файлів, що проектуються у пам'ять (MMF), і системних DLL.

    Четвертий гігабайт - для коду та даних режиму ядра (тут розташовується ядро операційної системи та драйвери).

    Старші два гігабайти є спільними для всіх процесів. Основні системні DLL - kernel32.dll, advAPI32.dll, user32.dll і GDI32.dll завантажуються в третьому гігабайт. З цієї причини ці чотири бібліотеки доступні всіх процесів в системі. Оскільки цей гігабайт загальний, вони існують у всіх процесах за одними і тими ж адресами. З міркувань безпеки Microsoft заборонила запис в область, куди вони завантажуються. Якщо ж запис туди все-таки виробити (а це можливо з режиму ядра або недокументовані методами), то зміни відбудуться у всіх процесах одночасно.

    У WinNT загальних розділів у процесів немає, хоча системні бібліотеки, як і раніше у всіх процесах завантажуються за однаковими адресами (але тепер уже в область коду і даних для користувача режиму). Запис в цю область дозволена, але у образів системних бібліотек в пам'яті стоїть атрибут «Копіювання при записі» (copy-on-write). З цієї причини спроба запису, наприклад, в образ kernel32.dll призведе до появи у процесу своєї копії зміненої сторінки kernel32.dll, а на інших процесах це ніяк не відіб'ється.

    Всі ці відмінності істотно впливають на способи реалізації перехоплення функцій, розташованих у системних DLL.

    Перехоплення можна розділити на два типи: локальні (перехоплення в межах одного процесу) та глобальні (у масштабах всієї системи).

    Локальний перехоплення

    Локальний перехоплення з використанням розділу імпорту

    Локальний перехоплення може бути реалізований і в Win9X, і в WinNT за допомогою підміни адреси перехоплюваних функції в таблиці імпорту. Для розуміння механізму роботи цього методу потрібно мати уявлення про те, як здійснюється динамічне зв'язування. Зокрема, необхідно розбиратися у структурі розділу імпорту модуля.

    У розділі імпорту кожного exe-або DLL-модуля міститься список усіх використовуваних DLL. Крім того, в ньому перераховані всі імпортовані функції. Викликаючи імпортовану функцію, потік отримує її адресу фактично з розділу імпорту. Тому, щоб перехопити певну функцію, треба лише змінити її адресу в розділі імпорту. Для того, щоб перехопити довільну функцію в деякому процесі, необхідно поправити її адресу імпорту у всіх модулях процесу (тому що процес може викликати цю функцію не тільки з exe-модуля, але і з DLL-модулів). Крім того, процес може скористатися для завантаження DLL функціями LoadLibraryA, LoadLibraryW, LoadLibraryExA, LoadLibraryExW або, якщо вона вже завантажена, визначити її адресу за допомогою функції GetProcAddress. Тому для перехоплення будь-API-функції необхідно перехоплювати і всі ці функції.

    Існує кілька широко відомих прикладів реалізації цього методу, зокрема один з них описаний у книзі Джеффрі Ріхтера «Windows для професіоналів: створення ефективних Win32 програм з урахуванням специфіки 64-розрядної версії Windows »(Jeffrey Richter« Programming Applications for Microsoft Windows »), 4-е видання. Інший приклад - бібліотека APIHijack, написана Wade Brainerd на основі DelayLoadProfileDLL.CPP (Matt Pietrek, MSJ, лютий 2000). Для опису цього методу я взяв за основу приклад Джеффрі Ріхтера (з невеликими змінами).

    Для реалізації перехоплення був створений клас CAPIHook, конструктор якого перехоплює задану функцію в цьому процесі. Для цього він викликає метод ReplaceIATEntryInAllMods, який, перераховуючи всі модулі поточного процесу, викликає для кожного метод ReplaceIATEntryInOneMod, в якому і реалізується пошук та заміна адреси в таблиці імпорту для заданого модуля.        

    void CAPIHook:: ReplaceIATEntryInOneMod (PCSTR pszCalleeModName,   

    PROC pfnCurrent, PROC pfnNew,   HMODULE hmodCaller)   

    (   

    // Отримаємо адреса секції імпорту   

    ULONG ulSize;   

    PIMAGE_IMPORT_DESCRIPTOR pImportDesc =   

    (PIMAGE_IMPORT_DESCRIPTOR) ImageDirectoryEntryToData (hmodCaller,   TRUE,   

    IMAGE_DIRECTORY_ENTRY_IMPORT,   & ulSize);   

    if (pImportDesc == NULL)   

    return;// Тут її немає   

    // Знайдемо потрібний модуль   

    for (; pImportDesc-> Name;   pImportDesc + +)   

    (   

    PSTR pszModName =   (PSTR) ((PBYTE) hmodCaller + pImportDesc-> Name);   

    if (lstrcmpiA (pszModName,   pszCalleeModName) == 0)   

    (   

    // Знайшли   

    if (pImportDesc-> Name == 0)   

    return;// Ні одна функція не імпортується   

    // Отримаємо адреса таблиці імпорту   

    PIMAGE_THUNK_DATA pThunk =   

    (PIMAGE_THUNK_DATA) ((PBYTE) hmodCaller +   pImportDesc-> FirstThunk);   

    // Переберія всі імпортовані функції   

    for (; pThunk-> u1.Function;   pThunk + +)   

    (   

    PROC * ppfn = (PROC *)   & pThunk-> u1.Function;// Отримаємо адреса функції   

    BOOL fFound = (* ppfn ==   pfnCurrent);// Його шукаємо?   

    if (! fFound & & (* ppfn   > Sm_pvMaxAppAddr))   

    (   

    // Якщо не знайшли, то пошукаємо глибше.   

    // Якщо ми в Win98 під відладчиком, то   

    // тут може бути push з адресою нашій   функції   

    PBYTE pbInFunc = (PBYTE) * ppfn;   

    if (pbInFunc [0] ==   cPushOpCode)   

    (   

    // Так, тут PUSH   

    ppfn = (PROC *)   & pbInFunc [1];   

    // Наша адреса?   

    fFound = (* ppfn ==   pfnCurrent);   

    )   

    )      

    if (fFound)   

    (   

    // Знайшли !!!   

    DWORD dwDummy;   

    // Розв'язано запис у цю сторінку   

    VirtualProtect (ppfn,   sizeof (ppfn), PAGE_EXECUTE_READWRITE, & dwDummy);   

    // Змінимо адресу на свій   

    WriteProcessMemory (GetCurrentProcess (),   ppfn, & pfnNew,   

    sizeof (pfnNew), NULL);   

    // Відновимо атрибути   

    VirtualProtect (ppfn,   sizeof (ppfn), dwDummy, & dwDummy);   

    // Готово !!!   

    return;   

    )   

    )   

    )   

    )   

    // Тут цієї функції не знайшлося   

    )     

    За допомогою функції ImageDirectoryEntryToData визначається дескриптор таблиці імпорту і, якщо він є, перебираються всі DLL, з яких імпортуються функції. Якщо DLL знаходиться, то серед функцій, імпортованих з неї, шукається потрібна, а потім за допомогою WriteProcessMemory її адреса змінюється на адресу свого слухача. Тепер він буде викликатися кожен раз, коли з даного модуля буде відбуватися звернення до перехопленою функції.        

    ПРИМІТКА   

    Якщо ви читали уже згадувалося вище   книгу Джеффрі Ріхтера, то могли помітити, що у функції   ReplaceIATEntryInOneMod я зробив одну зміну. У нього вона працювала так: у   таблиці імпорту був список функцій того модуля, функція з якого   імпортувалася, і якщо в цьому списку ця функція не перебувала, то   ReplaceIATEntryInOneMod більше нічого не робила (тобто перехопити не   відбувався). Я зіткнувся з такою поведінкою, коли написав тестову   програму на Delphi для прикладу DriveType2 (цей приклад описано нижче, в   розділі «Глобальний перехоплення методом тотального локального перехоплення», він   перехоплює функцію GetDriveTypeA в усіх програмах з використанням   описуваного методу). Тест, написаний на Visual C + +, працював прекрасно --   функція GetDriveTypeA перехоплювалася. А от програма на Delphi все одно   для всіх перехоплюваних мною дисків повертала реальні значення. Я   подивився таблицю імпорту тестової програми за допомогою утиліти DUMPBIN і   виявив, що компілятор Delphi не помістив всі імпортовані функції з   kernel32.dll в один список, а розбив їх на 3 частини, причому GetDriveTypeA   опинилася в третій. Тому функція ReplaceIATEntryInOneMod Джеффрі Ріхтера,   переглянувши всі функції з першого списку Kernel32.dll, не знайшла функції   GetDriveTypeA, хоча вона і імпортувалася модулем DriveTypeTest.exe. Я   виправив цю функцію таким чином, щоб вона перевіряла всю таблицю імпорту та   перебирала всі списки з функціями з kernel32.dll (як виявилось, їх може   бути декілька). В описі формату РЕ-файлу ніде не обумовлюється, що   кожен модуль, з якого імпортуються функції, повинен зустрічатися в секції   імпорту тільки один раз, і, мабуть, деякі компілятори цим користуються.     

    При реалізації даного методу слід враховувати, що дзвінки з DllMain бібліотеки, в якій знаходиться перехоплюється функція, перехопити не вдасться. Це пов'язано з тим, що перехоплення може бути здійснений тільки після закінчення виконання LoadLibrary, а до цього часу DllMain вже буде викликана. Звичайно, можна написати свій варіант LoadLibrary (приклади завантаження DLL «Вручну» існують) і здійснювати перехоплення між завантаженням DLL і викликом DllMain, але це сильно ускладнює завдання.

    Основною перевагою даного методу є те, що він однаково реалізується як в Win9X, так і в WinNT.        

    ПРИМІТКА   

    У Windows NT функції Module32First і   Module32Next не реалізовані, і для перерахування модулів процесу замість них   доведеться скористатися функціями з PSAPI.dll.     

    Локальний перехоплення за допомогою зміни перехоплюваних функції (тільки WinNT)

    Даний метод перехоплення заснований на наступному: перше кілька байт перехоплюваних функції заміняються на команду безумовного переходу до функції перехоплення. Цей трюк досить просто реалізується в WinNT (як я вже згадував, в WinNT для кожного процесу створюється своя копія образів системних бібліотек), але практично нереалізуем в Win9X (так як в Win9X якщо і можна внести зміни в образ системної бібліотеки, то тільки в адресних просторах всіх процесів відразу).

    Існує безліч прикладів реалізації цього методу. Я розгляну метод, запропонований Microsoft - Detours library.

    Detours - це перша офіційна бібліотека, призначена для перехоплення функцій (не тільки системних, але і будь-яких інших). До основних понять Detours відносяться:

    цільова функція (target function) - функція, перехоплення якої здійснюється;

    функція-перехоплювач (detour function) - функція, заміщає перехоплюваних;

    функція-трамплін (trampoline function) - функція, що складається із заголовка цільової функції і команди переходу до решти коду цільової функції.        

    ПРИМІТКА   

    Trampoline в перекладі з англійської --   «Батут», однак словосполучення «функція-трамплін» більш точно передає логіку   її роботи.     

    Таким чином, якщо цільова функція має наступний заголовок:        

    TargetFunction:   

    push ebp   

    mov ebp, esp   

    push ebx   

    push esi   

    push edi   

    ...     

    то в результаті перехоплення вийде наступне:        

    TargetFunction:   

    jmp DetourFunction:      

    TargetFunction 5:   

    push edi   

    ...      

    TrampolineFunction:   

    push ebp   

    mov ebp, esp   

    push ebx   

    push esi   

    jmp TargetFunction 5   

    ...     

    Причому функція-перехоплювач може викликати функцію-трамплін як оригінальної цільової функції.

    Бібліотека Detours пропонує два методи впровадження «Трамплінів» - статичний і динамічний. Статичний метод використовується, коли адреса цільової функції відомий на етапі складання модуля. Реалізується він так:        

    # include   

    # include // Підключимо бібліотеку Detours   

    // Цей макрос створює   функцію-трамплін для функції Sleep   

    DETOUR_TRAMPOLINE (VOID WINAPI SleepTrampoline (DWORD), Sleep);   

    VOID WINAPI SleepDetour (DWORD dw)// Це - функція-перехоплювач   

    (   

    // У цьому прикладі вона нічого не робить,   просто викликає оригінальну функцію   

    return SleepTrampoline (dw);   

    )   

    void main (void)   

    (   

    // Тут здійснюється перехоплення   

    DetourFunctionWithTrampoline ((PBYTE) SleepTrampoline,   (PBYTE) SleepDetour);   

    //...   

    // А тут знімається   

    DetourRemoveTrampoline (SleepTrampoline);      

    )     

    Динамічний перехоплення використовується у випадках, коли цільова функція на етапі складання недоступна. Реалізується він так:        

    # include   

    # include // Підключимо бібліотеку Detours   

    VOID (* DynamicTrampoline) (VOID) = NULL;// Це буде функція-трамплін   

    VOID DynamicDetour (VOID)// Це - функція-перехоплювач   

    (   

    // У цьому прикладі вона нічого не робить,   просто викликає оригінальну функцію   

    return DynamicTrampoline ();   

    )   

    void main (void)   

    (   

    // Отримаємо адреса цільової функції   

    VOID (* DynamicTarget) (VOID) =   SomeFunction;   

    // Тут здійснюється перехоплення   

    DynamicTrampoline = (FUNCPTR) DetourFunction ((PBYTE) DynamicTarget,   (PBYTE) DynamicDetour);   

    //...   

    DetourRemoveTrampoline (DynamicTrampoline);   //А тут знімається   

    )     

    При перехоплення функція DetourFunction динамічно створює трамплін і повертає його адресу. Як функції SomeFunction, яка в даному прикладі повертає адресу цільової функції, можна використовувати DetourFindFunction, яка намагається знайти потрібну функцію в потрібному модулі. Спочатку вона намагається зробити це через LoadLibrary і GetProcAddress, а в разі невдачі - використовує бібліотеку ImageHlp для пошуку налагоджувальних символів.

    Макрос DETOUR_TRAMPOLINE і функція DetourFunction включають в себе вбудований табличний дизассемблер, який визначає, яке кількість байт з заголовка цільової функції має бути скопійовано в функцію-трамплін (не менше 5 байт (розмір команди jmp), що складають ціле число команд процесора). Якщо цільова функція займає менше 5 байт, то перехоплення закінчується невдачею.

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

    push ecx; у функцію передається кількість   ітерацій циклу   

    begin_loop:   

    ;...   

    ; тут якийсь код   

    ;...   

    loop begin_loop   

    ret     

    Вищенаведений приклад Galen Hunt, один з авторів Detours, прокоментував так: «Існує безліч теоретичних прикладів коду, де пролог функції менше 5 байт, необхідних для команди jmp. Однак ніхто не повідомляв про реальних прикладах функції з такими проблемами ».        

    ПОПЕРЕДЖЕННЯ   

    На момент установки/зняття перехоплення   потрібно зупиняти всі інші потоки процесу, в якому відбувається   перехоплення (або упевнитися, що вони не можуть викликати перехоплюваних   функцію).     

    Існує інший спосіб реалізації даного методу. Замість команди jmp в початок функції міститься команда INT 3, а управління функції-перехоплювачі передається опосередковано в обробнику необроблених винятків (її адреса заноситься до pExceptionInfo-> ContextRecord-> Eip і оброблювач повертає EXCEPTION_CONTINUE_EXECUTION). Так як команда INT 3 займає 1 байт, то вищеописана ситуація в цьому випадку навіть теоретично неможлива.        

    ПОПЕРЕДЖЕННЯ   

    Основним недоліком даного способу   є його вкрай мала швидкодія (обробка виключення в Windows   займає досить тривалий час). Крім того, наявність обробника   винятків в перехоплюваних процесі призведе до того, що даний метод   працювати не буде. Також даний спосіб не буде працювати під відладчиком.     

    Глобальний перехоплення

    Глобальний перехоплення може бути реалі?? ован різними способами. Перший спосіб - застосування локального перехоплення до всіх програм в системі (запущеним в момент перехоплення або пізніше). Другий спосіб - «злом системи »- має на увазі підміну коду перехоплюваних функції безпосередньо в DLL-файлі або його образ у пам'яті.

    Глобальний перехоплення методом тотального локального перехоплення

    Даний метод заснований на наступному: якщо можна перехопити функцію з поточного процесу, то потрібно виконати код перехоплення під всіх процесах в системі. Існує кілька методів змусити чужий процес виконати код перехоплення. Найпростіший - внести цей код в DllMain деякої бібліотеки, а потім впровадити її в чужій процес. Методів впровадження DLL також існує кілька (див. Джеффрі Ріхтер). Найпростіший, що працює і в Win9X, і в WinNT - впровадження DLL за допомогою пасток. Реалізується він так: у системі встановлюється пастка (за допомогою функції SetWindowsHookEx) типу WH_GETMESSAGE (ця пастка служить для перехоплення Windows-повідомлень). У цьому випадку модуль, в якому знаходиться пастка, автоматично підключається до потоку, вказаною в останньому аргумент SetWindowsHookEx (якщо вказано 0, то здійснюється підключення до всіх потоків в системі). Однак підключення відбувається не відразу, а перед тим, як в чергу повідомлень потоку буде надіслано якесь повідомлення. Тому перехоплення здійснюється не відразу після запуску додатки, а перед обробкою процесом перше повідомлення. Так що всі виклики перехоплюваних функції до обробки процесом перші повідомлення перехоплюватися не будуть. А в додатках без черги повідомлень (наприклад, консольних) цей спосіб впровадження взагалі не працює.

    Я написав приклад, який реалізує глобальний перехоплення функції GetDriveTypeA з використанням впровадження DLL за допомогою пасток та перехоплення з використанням секції імпорту.

    Функція GetDriveTypeA з бібліотеки kernel32.dll використовується програмами Windows для визначення типу диска (локальний, CD-ROM, мережевий, віртуальний і т. д.). Вона має наступний прототип:        

    UINT GetDriveType (LPCTSTR   lpRootPathName);     

    lpRootPathName - шлях до диска (А:, В: і т.д.)

    GetDriveTypeA повертає одне з наступних значень:        

    # define   DRIVE_UNKNOWN 0   

    # define   DRIVE_NO_ROOT_DIR 1   

    # define   DRIVE_REMOVABLE 2   

    # define   DRIVE_FIXED 3   

    # define   DRIVE_REMOTE 4   

    # define   DRIVE_CDROM 5   

    # define DRIVE_RAMDISK 6     

    Перехоплення цієї функції дозволяє «обманювати» програми Windows, перевизначений значення, що повертається цією функцією, для будь-якого диска.

    Програма DriveType2 складається з двох модулів: DriveType2.exe і DT2lib.dll.

    DriveType2.exe реалізує інтерфейс, а вся робота виконується в DT2lib.dll.

    Проект DT2lib складається з трьох основних файлів:

    APIHook.cpp - це фото написаний Джеффрі Ріхтером (за винятком деяких виправлень, зроблених мною. Про них я розповім нижче). У цей файл описаний клас CAPIHook, який реалізує перехоплення заданої API-функції у всіх модулях поточного процесу. Тут же автоматично перехоплюються функції LoadLibraryA, LoadLibraryW, LoadLibraryExA, LoadLibraryExW і GetProcAddress.

    Toolhelp.h - це фото також написаний Джеффрі Ріхтером. У ньому описаний клас CToolhelp, який реалізує звернення до системних toolhelp-функцій. В даному випадку він використовується класом CAPIHook для перерахування всіх модулів, підключених до процесу.

    DT2Lib.cpp - в цьому файлі я реалізував перехоплення функції GetDriveTypeA з використанням класу CAPIHook, а також установку пастки типу WH_GETMESSAGE, що забезпечує підключення даного модуля (DT2lib.dll) до всіх потоків в системі.

    Як же відбувається перехоплення?

    Відразу ж після запуску DriveType2.exe викликається функція DT2_HookAllApps з DT2lib.dll, яка встановлює пастку.        

    BOOL WINAPI DT2_HookAllApps (BOOL fInstall, DWORD dwThreadId)   

    (   

    BOOL fOk;   

    if (fInstall)   

    (   

    chASSERT (g_hhook == NULL);// 2   рази перехоплювати ні до чого   

    g_hhook =   SetWindowsHookEx (WH_GETMESSAGE, GetMsgProc,   

    ModuleFromAddress (DT2_HookAllApps),   dwThreadId);// Встановимо пастку   

    fOk = (g_hhook! = NULL);   

    )   

    else   

    (   

    chASSERT (g_hhook! = NULL);// Знімати-то нічого   

    fOk = UnhookWindowsHookEx (g_hhook);//   Знімемо пастку   

    g_hhook = NULL;   

    )   

    return (fOk);   

    )     

    Функція пастки GetMsgProc нічого не робить, а просто викликає наступну функцію пастки (можливо, не тільки наша програма встановила пастку, і це, як мінімум потрібно перевірити). Перед тим, як помістити в чергу, пов'язану з деяким потоком, якесь повідомлення, система повинна викликати будь-яких пастки типу WH_GETMESSAGE (зазвичай такі пастки використовуються для моніторингу або зміни деяких повідомлень, однак ми нічого подібного не робимо - нам потрібно просто підключитися до всіх потоків у системі). Система не може просто викликати нашу функцію пастки - вона і одержувач повідомлення знаходяться в різних процесах, а значить, і в різних адресних просторах. І вихід з цієї ситуації один - система просто підключає модуль (а це обов'язково має бути DLL-модуль), в якому знаходиться пастка, до того процесу, якому надсилається повідомлення (що нам власне і потрібно).

    Після підключення DLL до потоку відбувається ініціалізація всіх змінних (кожен процес, до якого підключається DLL, має копії всіх глобальних і статичних змінних, що описані в ній). З глобальних змінних у нас є 6 примірників класу CAPIHook (для GetDriveTypeA в DT2Lib.cpp і для LoadLibraryA, LoadLibraryW, LoadLibraryExA, LoadLibraryExW і GetProcAddress - в APIHook.cpp). Таким чином, при підключенні DLL відбувається шестиразовий виклик конструктора класу CAPIHook, перехоплює перераховані вище функції в поточному (тобто в тому, до якого тільки що відбулося підключення) процесі.

    При завершенні процесу впроваджена DLL відключається. При цьому відбувається виклик деструктора CAPIHook для всіх екземплярів класу.

    Ця функція тепер буде викликатися кожного разу, коли з даного модуля буде відбуватися звернення до GetDriveTypeA.        

    int WINAPI Hook_GetDriveTypeA (PCSTR lpRootPathName)   

    (   

    // Виклик оригінальну функцію   

    int Result =   ((PGetDriveTypeA) (PROC) g_GetDriveTypeA) (lpRootPathName);   

    if (Result>   DRIVE_NO_ROOT_DIR)   

    (   

    int Drive =   toupper (* lpRootPathName);   

    if (Drive> = 'A' & &   Drive <= 'Z')   

    (   

    Drive -= 'A';// Індекс у масиві Drives   

    // Якщо цей диск перевизначений, повернемо   значення з масиву   

    if (Drives [Drive] <0xFF)   

    Result = Drives [Drive];   

    )   

    )   

    return Result;   

    )     

    Функція Hook_GetDriveTypeA спочатку викликає оригінальну GetDriveTypeA. Потім, якщо повертається значення більше DRIVE_NO_ROOT_DIR (тобто функції був переданий коректний аргумент, і вона виконати без помилок), то перевіряється, перевизначений чи диск, тип якого запитується. Інформація про значення перехоплюваних функцій у даному випадку зберігається у реалізованому мною масиві BYTE Drives [26], що дозволяє реалізувати перехоплення 26 дисків, від A: до Z:. У цьому масиві зберігаються значення, що повертаються функцією GetDriveTypeA для кожного з дисків. Отже, якщо значення елемента масиву, відповідного аргументу GetDriveTypeA одно 0xFF, то значення повертається без змін, в іншому випадку повертається значення з масиву. Запис значень в цей масив реалізується в DriveType2.cpp.        

    РАДА   

    Якщо ви хочете, щоб ця програма   повноцінно працювала в WinNT, слід також перехопити функцію GetDriveTypeW.     

    Ще одна реалізація даного методу описано в статті «Перехват API-функцій в Windows NT/2000/XP», автор Тихомиров В. А., публікувалася в RSDN Magazine # 1 (будьте обережні, там та ж помилка, що і у Джеффрі Ріхтера).        

    ПРИМІТКА   

    У цього методу є ще один   істотний недолік: деякі комерційні програми (наприклад,   Популярний файловий менеджер Total Commander, упакований ASPack) використовують   різні системи захисту (ASProtect, VBox і т. д.), шифрувальні таблицю імпорту   захищається програми. З такими програмами цей метод не працює.     

    Глобальний перехоплення може бути реалізований і за допомогою Detours (тільки в WinNT). А так як методів впровадження DLL відомо кілька, то різних варіантів реалізації глобального перехоплення можна запропонувати досить багато.

    Глобальний перехоплення методом підміни коду в DLL

    Даний метод можна реалізувати двома способами: безпосередній редагуванням коду DLL, в якій розташована цільова функція, або підміною цієї DLL іншого, що експортує той же набір функцій. Другий спосіб відомий під назвою «Підміна з використанням обгорток (wrappers )».

    Перший спосіб дозволяє реалізовувати тільки порівняно невеликі за розміром функції-перехоплювачі, так як код необхідно впроваджувати у вільні ділянки DLL - в основному в міжсекційних простір. Інший недолік - код необхідно писати на асемблері. Загальна ідеологія роботи цього методу та ж, що і в Detours. У код цільової функції впроваджується команда jmp до функції-перехоплювачі. Байти, скопійовані «з-під» jmp'а, переміщаються в перехоплювач (так як перехоплювач все одно пишеться на асемблері, в цьому випадку його простіше відразу поєднати з функцією-трампліном). Ось приклад реалізації цього методу.

    У каталозі DriveType0 знаходиться файл kernel32.dll, в якому я зробив наступні виправлення (за допомогою hiew32.exe):

    За адресою 4E02 - локальна адреса .0 BFF74E02 (це кінець функції GetDriveTypeA) я помістив команду jmp .0 BFF71080 - на перше-ліпше вільне місце (у виконуваних файлах завжди багато вільного місця - звичайно в кінцях секцій).

    За адресою .0 BFF71080 (глобальний адреса 1080) я помістив наступний код:        

    . BFF71080:   3C03 cmp al, 003; Повертаємо DRIVE_FIXED?   

    . BFF71082:   750E jne .0 BFF71092   

    . BFF71084:   B402 mov ah, 002; Так.   

    . BFF71086: CD16 int 016;/Перевіримо стан ScrollLock   

    . BFF71088:   2410 and al, 010;   

    . BFF7108A:   7404 je .0 BFF71090; Світлодіод горить?   

    . BFF7108C:   B005 mov al, 005; Так. Повертаємо DRIVE_CDROM   

    . BFF7108E:   EB02 jmps .0 BFF71092; На повернення   

    . BFF71090:   B003 mov al, 003   

    . BFF71092:   5F pop edi;/Повернення з GetDriveTypeA   

    . BFF71093: 5E pop esi; (шматок коду, скопійований   

    . BFF71094: 5B pop ebx; з .0 BFF74E02 -. BFF74E06)   

    . BFF71095:   C9 leave;   

    . BFF71096:   C20400 retn 00004;     

    Таким чином, коли світлодіод ScrollLock не горить, функція GetDriveTypeA працює як звичайно, а якщо горить - то для всіх Windows-додатків всі локальні диски (у мене це С: і D:) перетворюються на CD-ROMи.        

    ПРИМІТКА   

    Щоб все це запрацювало, необхідно   замінити файл C: WindowsSystemkernel32.dll на файл DriveType0kernel32.dll.   Зробити це можна, лише завантаживши комп'ютер в режимі MS-DOS, тому що   kernel32.dll - одна з системних бібліотек Windows. Даний приклад реалізований   для Windows 98. Оскільки системні бібліотеки змінюються залежно від   версії Windows (і навіть від номера білду), то в інших операційних системах   цей приклад працювати не буде (його потрібно реалізовувати для кожної версії   kernel32.dll заново).     

    Цей спосіб перехоплення - один з найпотужніших. Однак у комерційних продуктах його використовувати не вдасться, тому що він, очевидно, порушує практично будь-яке ліцензійну угоду.

    Інший спосіб реалізації цього методу - використання обгорток (wrappers). Суть його в створенні власної DLL з тим же набором експортованих функцій, що і оригінальна. Як приклад можу навести наступний варіант реалізації вищенаведеного прикладу:

    Системну бібліотеку Kernel32.dll перейменовуємо в kernel31.dll:).

    Створюємо бібліотеку з ім'ям Kernel32.dll, в якій реалізована одна функція - GetDriveTypeA (це буде функція-перехоплювач), а всі інші функції переадресуємо до kernel31.dll (благо компілятор Visual C + + підтримує переадресацію функцій DLL).

    Отриману бібліотеку поміщаємо в системний каталог.

    При цьому функція-перехоплювач може викликати оригінальну функцію з kernel31.dll.

    Основним недоліком даного способу є те, що він не годиться для DLL, що експортують змінні.

    Глобальний перехоплення методом підміни коду DLL в пам'яті (тільки Win9X)

    Ідея даного методу полягає в наступному: у Win9X системні DLL завантажуються в загальну для всіх процесів область пам'яті (у третьому гігабайт). Тому якщо б вдалося провести реалізацію Detours під Win9X, то зміни торкнулися би всіх процесів (тобто стався б глобальний перехоплення). Ситуація ускладнюється тим, що запис в область системних DLL в Win9X можлива або з режиму ядра, або недокументовані засобами. Крім того, в час запису необхідно зупинити всі потоки, які можуть викликати цільову функцію. Це можна зробити за допомогою SuspendThread. Однак ця функція вимагає як аргумент handle потоку, а використовувані для перерахування потоків функції Thread32First/Thread32Next повертають ThreadID. Функція OpenThread, яка дозволяє отримати handle з ThreadID, реалізована тільки починаючи з Windows ME. З цієї причини в загальному вигляді документовані коштами з режиму користувача даний метод в Win9X нереалізуем. Однак є інший спосіб. Обидві позначені проблеми (запис в область системних DLL і зупинка всіх призначених для користувача потоків у системі) можуть бути вирішені, якщо для перехоплення використовувати драйвер. Драйвер працює в режимі ядра, тому з нього можна проводити запис з будь-яким доступним адресами. А якщо на момент установки/зняття перехоплення підняти IRQL (рівень запиту на переривання) до DISPATCH_LEVEL, то перервати потік зможе тільки процедура обробки апаратного переривання (звідки викликати для користувача системні функції не можна). Крім того, застосування драйвера дозволяє вирішити ще одну проблему. Функція-перехоплювач і функція-трамплін повинні розташовуватися в області пам'яті, загальною для всіх процесів (у старших 2 гігабайти). Звичайно, можна було б створити файл, що відображається в пам'ять (MMF - вони в Win9X розміщуються в третьому гігабайті), і помістити код цих функцій туди, або розмістити їх в окремій DLL з ImageBase в третьому гігабайті, але простіше реалізувати їх безпосередньо в драйвері (драйвери в Win9X розміщуються в четвертому гігабайті - у розділі коду та даних режиму ядра).

    Розглянемо приклад, який реалізує описаний метод. Для здійснення перехоплення і розміщення функції-трампліну і функції-перехоплювача я написав WDM-драйвер (з використанням Visual C + + 6.0, Windows 2000 DDK і Compuware DriverStudio 2.7), а також програму для взаємодії з ним. Програма і драйвер розташовані в каталозі DriveType1 (там же - інструкції встановлення).

    Приклад DriveType1 складається з двох частин - драйвера DTDrv.sys і інсталяційного скрипта DTDrv.inf, а також програми DriveType.exe.

    DriveType.exe компілюється з одного модуля DriveType.cpp, в якому реалізовані користувальницький інтерфейс і інтерфейс з драйвером. Інтерфейс із драйвером реалізується через функції CreateFile (відкриття драйвера), DeviceIoControl (операції введення-виведення) і CloseHandle (закриття драйвера). Реалізовано чотири команди, які викликаються через DeviceIoControl -- перехоплення функції GetDriveTypeA, зняття перехоплення, установка повертається значення функцією перехоплення для кожного з дисків A: .. Z:, читання поточного стану перехоплення.

    Ну а вся робота з перехоплення робиться в драйвері, за винятком того, що адреса функції GetDriveTypeA визначається також у DriveType.cpp та надсилається як параметр в команді перехоплення. Після отримання цієї адреси функція DTDRV_IOCTL_HOOK_Handler (з модуля DTDrvDevice.cpp) реалізує перехоплення розглянутим вище способом. Функція DTDRV_IOCTL_UNHOOK_Handler знімає перехоплення, функція DTDRV_IOCTL_SETDRIVE_Handler встановлює значення, що повертається перехоплювачем, функція DTDRV_IOCTL_GETSTATE_Handler повертає значення перехоплення і прапор перехоплення.

    Основні змінні, використовувані DriveType.cpp:        

    unsigned   char IsHook = false;// Прапор перехоплення   

    unsigned   char Drives [26];// Значення перехоплення   

    PUCHAR   GDT;// Адреса GetDriveTypeA     

    У Drives [26] зберігаються значення, що повертаються функцією MGetDriveType для дисків A: .. Z: (= 0xFF, якщо інформація про диску не перевизначено).

    Отже, функція-трамплін NewGetDriveType буде виглядати наступним чином:        

    __declspec (naked) unsigned int NewGetDriveType (LPSTR Path)   

    (   

    _asm   

    (   

    nop// Тут будуть перші 5 байт з функції   GetDriveTypeA   

    nop// (в Win98 3 команди асемблера)   

    nop   

    nop   

    nop   

    jmp $// А тут - перехід до GetDriveTypeA +   5   

    )   

    )     

    Спочатку ця функція «порожня», тому що весь її код пишеться під час перехоплення функцією DTDRV_IOCTL_HOOK_Handler, яка, якщо оперувати термінами Detours, реалізує динамічний перехоплення.        

    ПРИМІТКА   

    Код цієї функції від початку може бути   будь-яким, але він повинен займати принаймні 10 байт (щоб вмістилися 5 байт   із заголовка GetDriveTypeA і 5 байт - команда jmp).     

    Власне функція-перехоплювач MGetDriveType реалізована в моєму прикладі так:        

    unsigned int   MGetDriveType (LPSTR Path)// Це --   функція-перехоплювач   

    (   

    unsigned int res =   NewGetDriveType (Path);// виклик старій GetDriveTypeA   

    unsigned char Letter = * Path;   

    if (Letter> = 'a' & &   Letter <= 'z') Letter-= 'a' - 'A';// Заголовні   

    if (Letter> = 'A' & &   Letter <= 'Z')   

    (   

    unsigned char NewRes =   Drives [Letter - 'A'];   

    if (NewRes <0xFF) res = NewRes;// Якщо диск перепризначений, повернемо   значення з таблиці   

    )   

    return res;   

    )     

    Спочатку викликається функція-трамплін NewGetDriveType, яка фактично виконує код оригінальній GetDriveTypeA (спочатку виконуються перші 5 байт - це 3 команди асемблера, потім - в?? е інше). Після цього визначається буква диска. Перетворення літери у верхній регістр здійснюється вручну. Далі, якщо цей диск перехоплений, повертається значення з масиву Drives, в іншому випадку - те, що повернула NewGetDriveType.

    Перехоплення реалізований у функції DTDRV_IOCTL_HOOK_Handler наступним чином:        

    NTSTATUS DTDrvDev:: DTDRV_IOCTL_HOOK_Handler (KIrp I)   

    (   

    NTSTATUS status =   STATUS_SUCCESS;   

    I. Information () = 0;   

    if (IsHook)   

    return status;   

    # pragma pack (push, 1)// Включимо вирівнювання по межі байти   

    struct   

    (   

    UCHAR Byte0;   

    ULONG Byte1_4;   

    ) Patch = (0xE9);// Код інструкції jmp   

    # pragma pack (pop)// Повернемо вирівнювання за замовчуванням   

    KIRQL oldirql;   

    KeRaiseIrql (DISPATCH_LEVEL,   & oldirql);// Піднімемо IRQL до DISPATCH_LEVEL   

    GDT =   (PUCHAR) * (PULONG) I. IoctlBuffer ();// GDT = Адреса GetDriveTypeA   

    RtlCopyMemory (NewGetDriveType,   GDT, 5);// Заголовок NewGetDriveType   

    Patch.Byte1_4 = (ULONG) GDT --   (ULONG) NewGetDriveType - 5;   

    RtlCopyMemory ((PVOID) ((ULONG) NewGetDriveType   + 5), & Patch, 5);// jmp GetDriveTypeA + 5   

    Patch.Byte1_4 =   (ULONG) MGetDriveType - (ULONG) GDT - 5;   

    RtlCopyMemory (GDT, & Patch,   5);// jmp MGetDriveType   

    IsHook = true;   

    KeLowerIrql (oldirql);// Повернемо IRQL назад   

    return status;   

    )     

    Якщо функція GetDriveTypeA ще не перехоплена (IsHook = false), то:

    Визначається адреса функції GetDriveTypeA (він надсилається як параметр);

    За адресою NewGetDriveType копіюються 5 байт з початку GetDriveTypeA;

    За ними вставляється байт 0xE9 (код команди jmp) і зсув до точки GetDriveTypeA + 5;

    За адресою GetDriveTypeA вставляється 0xE9 і зсув до точки MGetDriveType;

    Прапор перехоплення IsHook встановлюється в true.

    Функція зняття перехоплення повертає все на свої місця:        

    NTSTATUS DTDrvDev:: DTDRV_IOCTL_UNHOOK_Handler (KIrp I)   

    (   

    NTSTATUS status =   STATUS_SUCCESS;   

    I. Information () = 0;   

    if (! IsHook) return status;   

    KIRQL oldirql;   

    KeRaiseIrql (DISPATCH_LEVEL,   & oldirql);// Піднімемо IRQL до DISPATCH_LEVEL   

    RtlCopyMemory (GDT,   NewGetDriveType, 5);// Повернемо заголовок GetDriveTypeA на місце   

    IsHook = false;   

    KeLowerIrql (oldirql);// Повернемо IRQL назад   

    return status;   

    )     

    Даний метод - компроміс між гнучкістю (перехоплення через таблицю імпорту не вимагає написання драйвера), і потужністю (за потужністю він практично не поступається підміну коду в DLL), проте він реалізується лише в Win9X.

    Висновок

    Отже, перехоплення API-викликів - річ, хоча й досить складна, але все-таки реалізована (причому різними способами). Методи перехоплення різні і часто не переносите з однієї версії Windows в іншу.

    Microsoft прагне зберігати сумісність програмного забезпечення з старими версіями Windows, але виходить це далеко не завжди, і аспекти програмування, настільки наближені до низкорівневому системного програмування, дуже сильно розрізняються для різних версій Windows. Тому часто доводиться жертвувати ефективністю на шкоду універсальності - і навпаки.

    Список літератури

    Для підготовки даної роботи були використані матеріали з сайту http://www.rsdn.ru/

         
     
         
    Реферат Банк
     
    Рефераты
     
    Бесплатные рефераты
     

     

     

     

     

     

     

     
     
     
      Все права защищены. Reff.net.ua - українські реферати ! DMCA.com Protection Status