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

     

     

     

     

     

         
     
    Виклик функції в іншому процесі
         

     

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

    Виклик функції в іншому процесі

    Сергій холодиль

    I just called to say I love you,

    And I mean it from the bottom of my heart.

    Stevie Wonder

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

    Якщо впроваджена DLL створює свій потік, завдання взаємодії легко вирішується, тому що в цьому випадку можна використовувати будь-які методи IPC: повідомлення, сокети, іменовані канали, ..., при бажанні можна навіть COM-сервер зробити:)        

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

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

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

    Ідея

    Ідея тривіальний. Алгоритм складається всього з чотирьох кроків (плюс ще один за бажанням):

    Так чи інакше завантажити в адресний простір процесу-жертви DLL, що містить потрібну функцію.        

    ПРИМІТКА   

    «Так чи інакше» означає, що DLL може   бути завантажена будь-яким способом. Наприклад, це може бути advapi32.DLL, яку   процес-жертва вантажить сам. Якщо ви хочете, щоб виконувався ваш код, швидше за   за все, DLL доведеться впроваджувати. Опис впровадження DLL дивіться в   додаткові джерела в кінці статті.     

    Отримати адреса завантаження DLL.

    Отримати адреса функції.

    Викликати функцію за допомогою CreateRemoteThread.

    (опціонально) Дочекатися завершення потоку і отримати що повертає значення функції викликом GetExitCodeThread.

    А навіщо нам DLL?

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

    код буде розташований у випадковому місці адресного простору, тому що вам навряд чи вдасться виділити пам'ять за тією ж адресою;

    DLL можуть бути завантажені на інші адреси,

    «само собою» нічого не вийде. Щоб домогтися працездатності коду, потрібно модифіковані використовувані вашим кодом адреси, тобто, фактично, виконати завдання завантажувача. А навіщо виконувати її вручну, якщо можна покластися на завантажувач:)?

    Обмеження

    Використання CreateRemoteThread пов'язано з очевидними обмеженнями:

    Підтримується тільки лінійка Windows NT/2000/XP.        

    ПРИМІТКА   

    Існує платна реалізація   CreateRemoteThread для Windows 9x, дивіться сайт http:// www.apihooks.com   розділ «PrcHelp».     

    Прототип викликається функції повинен відповідати прототипу функції потоку.

    Крім того, потрібно мати солідні права доступу до процесу-жертві:

    PROCESS_CREATE_THREAD для запуску потоку.

    PROCESS_VM_READ для визначення адреси.

    PROCESS_VM_OPERATION + PROCESS_VM_WRITE (дозвіл на виділення пам'яті і запис в адресний простір процесу) може стати в нагоді, якщо ви хочете передати викликається функції що-небудь посущественнее, ніж чотири байти.        

    ПРИМІТКА   

    Найпростіше отримати всі ці права,   створивши процес, але, будучи досить привілейованим користувачем, можна   одержати необхідний доступ і до існуючого процесу.     

    Отримання адреси завантаження DLL

    У загальному випадку, за допомогою функцій EnumProcessModules і GetModuleFileNameEx можна перебрати всі завантажені в процес-жертву модулі, знайти серед них потрібний і отримати адресу його завантаження.        

    ПРИМІТКА   

    Ці функції є частиною Process   Status API (PSAPI), тому будуть працювати тільки в лінійці Windows   NT/2000/XP. Але оскільки ми вже й так використовуємо CreateRemoteThread, втрачати   нам нічого.     

    Але якщо DLL впроваджувалася за допомогою створення в процесі-жертві потоку, потокової функцією якого є LoadLibrary, можна вчинити простіше. У цьому випадку код завершення потоку є що повертається значенням LoadLibrary, тобто саме адресою завантаження DLL в процесі-жертві.        

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

    Взагалі-то, як показує практика,   повертається значення LoadLibrary - це не зовсім адреса завантаження DLL. У   деяких випадках у молодших бітах знаходяться якісь прапори. Наприклад, при   виконанні функції LoadLibraryEx з прапором LOAD_LIBRARY_AS_DATAFILE молодший біт   значення, що повертається завжди буде встановлений в 1.   

    Вихід досить простий: оскільки при   завантажити модуль в адресному просторі створюється регіон, а адреси початку   регіонів повинні бути кратні 64К, для отримання «справжнього» адреси завантаження   потрібно просто обнулити два молодших байти.     

    Отримання адреси функції

    Є два способи отримати адресу функції: простий і для справжніх програмістів. :)

    Простий спосіб

    Простий спосіб заснований на тому, що зміщення початку функції від початку DLL - величина стала, яка не залежить від процесу. Це означає, що якщо:

    завантажити у свій процес ту ж DLL;

    отримати адресу потрібної функції;

    відняти з адреси функції адреса завантаження DLL;

    додати до що вийшов зсуву адреса завантаження DLL в процесі-жертві,

    то вийде адреса функції в процесі-жертві.        

    ПРИМІТКА   

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

    Саме на цьому заснована технологія   впровадження DLL через виклик LoadLibrary в іншому процесі.     

    Якщо з якихось причин DLL вже завантажена в процес, то, напевно, цей спосіб можна рекомендувати навіть самим-самим справжнім програмістам. А от якщо DLL потрібно спеціально вантажити, то, по-моєму, знову виходить негарно. :)

    Спосіб для справжніх програмістів

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

    Якщо додати функції LoadLibararyInOtherProcess і FreeLibraryInOtherProcess (які нескладно написати), вийде зовсім красиво, тому що з чужим процесом можна буде працювати майже так само, як і з своїм.

    Саме цей спосіб здається мені цікавим і елегантним, і саме його реалізації присвячена стаття.

    Пошук експортується функції в PE-файлі

    Як ви, напевно, знаєте, формат всіх виконуваних файлів в Windows (включаючи DLL, ocx, sys, та інші) називається PE (розшифровується як Portable Executable, але великого сенсу не несе, просто назву, нічим не гірше за інших) форматом, а самі файли, відповідно, PE-файлами. Щоб відшукати адресу потрібної функції в DLL, доведеться розібратися з тією частиною PE-формату, яка відповідає за експорт.        

    ПРИМІТКА   

    PE-формат досить складний, але, на   щастя, повністю він нам і не потрібен. Якщо вас цікавить більш детальне   опис, дивіться додаткові джерела в кінці статті.     

    Як у PE-файлі дістатися до секції експорту

    Будь-PE-файл починається з заголовка DOS, формат якого відображено в структурі IMAGE_DOS_HEADER.        

    typedef   struct _IMAGE_DOS_HEADER (//DOS   . EXE header   

    ...   

    LONG   e_lfanew;// File   address of new exe header   

    ) IMAGE_DOS_HEADER, * PIMAGE_DOS_HEADER;     

    З усіх полів цієї структури для нас інтерес представляє тільки поле e_lfanew, яке є зміщенням від початку файлу (в термінології PE-формату такі зміщення називаються RVA - Relative Virtual Address) до PE-заголовка.

    Формат PE-заголовка представлений структурою IMAGE_NT_HEADERS (вона визначена з використанням препроцесора і, на даний момент, відповідає структурі IMAGE_NT_HEADERS32):        

    typedef   struct _IMAGE_NT_HEADERS (   

    ...   

    IMAGE_OPTIONAL_HEADER32 OptionalHeader;   

    )   IMAGE_NT_HEADERS32, * PIMAGE_NT_HEADERS32;     

    З неї нас цікавить тільки поле OptionalHeader, яка розгортається в ще одну структуру:        

    typedef   struct _IMAGE_OPTIONAL_HEADER (   

    ...   

    IMAGE_DATA_DIRECTORY   DataDirectory [IMAGE_NUMBEROF_DIRECTORY_ENTRIES];   

    )   IMAGE_OPTIONAL_HEADER32, * PIMAGE_OPTIONAL_HEADER32;     

    І знову, нам потрібно тільки одне поле - DataDirectory, а, точніше, тільки елемент DataDirectory [IMAGE_DIRECTORY_ENTRY_EXPORT].

    Структура IMAGE_DATA_DIRECTORY описує розташування в пам'яті однієї із секцій PE-файлу. Вона визначена в такий спосіб:        

    typedef   struct _IMAGE_DATA_DIRECTORY (   

    DWORD VirtualAddress;// RVA (зсув від початку   файлу) секції   

      DWORD Size;// Розмір секції   

    )   IMAGE_DATA_DIRECTORY, * PIMAGE_DATA_DIRECTORY;     

    Елемент DataDirectory [IMAGE_DIRECTORY_ENTRY_EXPORT] відноситься до секції експорту.

    Разом:

    На початку файлу розташований IMAGE_DOS_HEADER.

    За зсуву IMAGE_DOS_HEADER:: e_lfanew знаходиться IMAGE_NT_HEADERS.

    IMAGE_NT_HEADERS:: OptionalHeader.DataDirectory [IMAGE_DIRECTORY_ENTRY_EXPORT] описує секцію експорту. Він містить RVA і розмір секції.

    Як у секції експорту знайти адресу функції

    Секція експорту починається зі структури IMAGE_EXPORT_DIRECTORY.        

    typedef   struct _IMAGE_EXPORT_DIRECTORY (   

    ...   

    DWORD   Base;   

    DWORD   NumberOfFunctions;   

    DWORD   NumberOfNames;   

    DWORD   AddressOfFunctions;// RVA from   base of image   

    DWORD   AddressOfNames;// RVA from   base of image   

    DWORD   AddressOfNameOrdinals;// RVA from base of image   

    )   IMAGE_EXPORT_DIRECTORY, * PIMAGE_EXPORT_DIRECTORY;     

    Тут:

    AddressOfFunctions - RVA (зсув від початку файлу) масиву, що містить RVA функцій.

    AddressOfNames - RVA масиву, що містить RVA імен функцій.

    AddressOfNameOrdinals - RVA масиву індексів функцій. Елемент n цього масиву містить індекс у масиві адрес функцій, відповідної n-ному елементу в масиві імен функцій.        

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

    По-перше, елементи цього масиву мають   тип WORD і розмір 2 байти.   

    По-друге, MSDN і стаття Метта Пітрека   «Формати PE і COFF об'єктних файлів» містять одну й ту ж саму помилку, що відноситься   до інтерпретації вмісту цього масиву. Правильно написано в статті   Максима М. Гумерова «Завантажувач PE-файлів» і тут:)     

    NumberOfFunctions - кількість елементів масиву адрес функцій.

    NumberOfNames - кількість елементів масиву імен функцій та масиву індексів функцій.

    Base - базове значення ордінала експортуються функцій. Для отримання індексу функції, що експортується з ордіналу, треба відняти з її ордінала значення Base.

    У результаті, щоб знайти адресу функції, експортується на ім'я, потрібно зробити приблизно наступне (в псевдокоді):        

    // Шукаємо в масиві імен функцій   збігається ім'я   

    int nameIndex =   FindFunctionName (AddressOfNames, NumberOfNames, name);   

    // Отримуємо відповідний імені індекс функції   

    WORD   funcIndex = AddressOfNameOrdinals [nameIndex];   

    // Отримуємо RVA функції   

    DWORD   funcRVA = AddressOfFunctions [funcIndex];             

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

    За MSDN і Пітреку, остання строчка   алгоритму має виглядати так:   

    DWORD   funcRVA = AddressOfFunctions [funcIndex - Base];   

    Де Base - базове значення ордінала.   Як показує практика, Base віднімати не треба.       

    Код

    Врешті-решт у мене вийшло три функції. Перша знаходить секцію експорту:        

    // Визначає RVA секції експорту   

    int   GetExportSectionRVA (HANDLE hProcess, const void * baseAddress)   

    (   

    // Читаємо DOS-заголовок   

    IMAGE_DOS_HEADER dos_header;   

    ReadProcessMemory (   

    hProcess,   

    baseAddress,   

    & dos_header,   

    sizeof (dos_header),   

    NULL);      

    // Читаємо PE-заголовок   

    IMAGE_NT_HEADERS pe_header;   

    ReadProcessMemory (   

    hProcess,   

    reinterpret_cast (baseAddress) + dos_header.e_lfanew,   

    & pe_header,   

    sizeof (pe_header),   

    NULL);      

    // Зміщення секції експорту   

    return   pe_header.OptionalHeader.DataDirectory   

      [IMAGE_DIRECTORY_ENTRY_EXPORT]. VirtualAddress;   

    )     

    Друга перебирає масив імен функцій в пошуку заданого імені:        

    // Шукає в масиві імен   функцій задане ім'я, повертає індекс або -1   

    int FindName (   

    HANDLE hProcess,   

    const void * baseAddress,   

    DWORD AddressOfNames,   

    DWORD count,   

    const char * name)   

    (   

    // Для порівняння імені його потрібно прочитати,   для цього потрібно знати розмір   

    int size = lstrlenA (name) + 1;   

    std:: auto_ptr   candidate (new char [size ]);      

    // перебираємо імена в масиві імен функцій   

    for (int index = 0; index   

    (   

    DWORD nameRVA;      

    // Читаємо адреса початку рядка   

    ReadProcessMemory (   

    hProcess,   

      reinterpret_cast (baseAddress)   

    + AddressOfNames +   index * sizeof (DWORD),   

    & nameRVA,   

    sizeof (nameRVA),   

    NULL);      

    // Читаємо рядок   

    ReadProcessMemory (   

    hProcess,   

      reinterpret_cast (baseAddress) + nameRVA,   

    candidate.get (),   

    size,   

    NULL);      

    if (strcmp (name,   candidate.get ()) == 0)   

    (   

    // Вона! Звалює:)   

    return index;   

    )   

    )      

    // Такий функції немає   

    return -1;   

    )     

    Третя функція використовує перші два і знаходить потрібну функцію у зазначеній DLL в зазначеному процесі:        

    // Знаходить потрібну функцію в   зазначеної DLL у зазначеному процесі.   

    void * GetProcAddress (HANDLE hProcess, HMODULE hLib, const char * name)   

    (   

    // Нам потрібен саме адреса завантаження! А   результат роботи   

    // LoadLibrary буває іноді несподіваним ..   

    char * baseAddress = reinterpret_cast   

      (reinterpret_cast (hLib) & 0xFFFF0000);      

    // Зміщення секції експорту   

    int export_offset =   GetExportSectionRVA (hProcess, baseAddress);      

    if (export_offset <= 0)   

    (   

    // Якісь проблеми з експортом   

    return NULL;   

    )      

    // Читаємо заголовок секції експорту   

    IMAGE_EXPORT_DIRECTORY export;   

    ReadProcessMemory (   

    hProcess,   

    baseAddress +   export_offset,   

    & export,   

    sizeof (export),   

    NULL);      

    // Індекс у масиві функцій   

    WORD funcIndex = -1;      

    if   (reinterpret_cast (name)> 0x0000ffff)   

    (   

    // Функція експортується по імені. Шукаємо   ім'я   

    int nameIndex = FindName (   

    hProcess,   

    baseAddress,   

    export.AddressOfNames,   

    export.NumberOfNames,   

    name);      

    if (nameIndex <0)   

    (   

    // Такий функції немає   

    return NULL;   

    )      

    // Читаємо індекс (вони двухбайтние !!!)   

    ReadProcessMemory (   

    hProcess,   

    baseAddress +   export.AddressOfNameOrdinals   

    + nameIndex *   sizeof (WORD),   

    & funcIndex,   

    sizeof (funcIndex),   

    NULL);   

    )   

    else   

    (   

    // Функція експортується за ордіналу   

    WORD funcOrdinal =   reinterpret_cast (name);      

    if ((funcOrdinal <   export.Base)   

    | | (funcOrdinal> =   export.Base + export.NumberOfFunctions))   

    (   

    // Такий функції немає   

    return NULL;   

    )      

    // Індекс це ордінал мінус база   

    funcIndex = funcOrdinal - export.Base;   

    )      

    if ((funcIndex <0) | |   (funcIndex> = export.NumberOfFunctions))   

    (   

    // Такий функції немає   

    return NULL;   

    )      

    // Читаємо адреса   

    DWORD funcRVA;   

    ReadProcessMemory (   

    hProcess,   

    baseAddress +   export.AddressOfFunctions + funcIndex * sizeof (DWORD),   

    & funcRVA,   

    sizeof (funcRVA),   

    NULL);      

    // Результат це базовий адреса + RVA   

    return (baseAddress + funcRVA);   

    )             

    ПРИМІТКА   

    Для оптимізації можна було   б спочатку скопіювати в свій процес всю секцію експорту (розмір секції   зберігається в   IMAGE_NT_HEADERS:: OptionalHeader.DataDirectory [IMAGE_DIRECTORY_ENTRY_EXPORT]. Size),   а потім вже її розбирати. Але, оскільки помітних оку затримок не виникає,   я зупинився на поточній реалізації.       

    Приклад

    Як приклад я написав три програми: aggressor.exe, victim.exe і insider.dll. Victim і insider абсолютно пасивні, всі дії виконуються aggressor-му. Aggressor:

    запускає victim.exe;

    завантажує в нього insider.dll;

    отримує адреси трьох експортованих функцій;

    викликає ці функції;

    вивантажує insider.dll з victim.exe.        

    ПРИМІТКА   

    Щоб це дійсно працювало, треба   покласти всі три виконуваних модуля в один каталог.     

    Для реалізації перерахованих дій, та й взагалі на майбутнє, в aggressor реалізовані наступні корисні функції:        

    namespace OtherProcess   

    (   

    //   

    // Викликає функцію із заданого процесу,   повертає   

    // описувач потоку, що цю функцію   виконує   

    HANDLE AsynchronousCall (   

    HANDLE hProcess,   

    void * address,   

    void * parameter,   

    DWORD * pid);      

    //   

    // Викликає функцію із заданого процесу,   чекає завершення її роботи   

    bool SynchronousCall (   

    HANDLE hProcess,   

    void * address,   

    void * parameter,   

    DWORD * result);      

    //   

    // Завантажує DLL в зазначений процес   

    HMODULE LoadLibrary (HANDLE   hProcess, const TCHAR * path);      

    //   

    // вивантажувати DLL в зазначеному процесі   

    void FreeLibrary (HANDLE   hProcess, HMODULE hLib);      

    //   

    // Знаходить потрібну функцію у зазначеній DLL в   зазначеному процесі   

    void * GetProcAddress (HANDLE hProcess, HMODULE   hLib, const char * name);   

    );     

    Призначення функцій, я сподіваюся, зрозуміло з їх назв і коротких коментарів. Розуміння реалізації також не повинно викликати труднощів, прокоментовано все досить докладно, та й сам код не такий вже складний. Успішних вам дзвінків!

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

    Джеффрі Ріхтер, «Programming Application for Microsoft Windows», четверте видання.

    Тихомиров В.А. «Перехват API-функцій в Windows NT/2000/XP ».

    Метт Пітрек «Формати PE і COFF об'єктних файлів»

    Максим М. Гумер «Завантажувач PE-файлів»

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

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

     

     

     

     

     

     

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