HOWTO: Извикване на функция в друг процес

Автор: Сергей Холодилов
Групата RSDN
Източник: Списание RSDN № 4-2004

друг

Ако инжектираната DLL създаде своя собствена нишка, проблемът с взаимодействието се решава лесно, тъй като в този случай можете да използвате всякакви IPC методи: съобщения, сокети, именувани тръби, ..., ако желаете, можете дори да направите COM сървър:)

Описанието на DllMain казва, че някои функции, включително CreateThread, не могат да бъдат извикани от него. Обяснението "защо казват, че е невъзможно" може да се намери в Рихтер (в руското четвърто издание това е главата "DLL: по-сложни методи за програмиране", раздел "Как системата нарежда DllMain повиквания"), той също казва че всъщност е възможно, ако внимавате.:) Просто когато създавате нишка, не забравяйте, че нейното изпълнение ще започне не по-рано, отколкото текущата нишка напусне DllMain.

Идеята е тривиална. Алгоритъмът се състои само от четири стъпки (плюс още една по избор):

Така или иначе означава, че DLL може да се зарежда по всякакъв начин. Например, това може да бъде advapi32.DLL, който процесът на жертвата зарежда сам. Ако искате вашият код да се изпълни, най-вероятно ще трябва да инжектирате DLL. За описание на DLL инжектиране вижте допълнителни ресурси в края на статията.

Защо се нуждаем от DLL?

Ограничения

Има очевидни ограничения за използването на CreateRemoteThread:

  • Поддържа се само линия Windows NT/2000/XP.

Има платена реализация на CreateRemoteThread за Windows 9x, вижте сайта http://www.apihooks.com раздел "PrcHelp".

  • Прототипът на извиканата функция трябва да съвпада с прототипа на поточната функция.

Освен това трябва да имате солидни права за достъп до процеса на жертва:

Най-лесният начин да получите всички тези права е чрез създаване на процес, но като доста привилегирован потребител, можете да получите необходимия достъп до съществуващ процес.

Тези функции са част от API за състояние на процеса (PSAPI) и ще работят само на линията Windows NT/2000/XP. Но тъй като вече използваме CreateRemoteThread, няма какво да губим.

Лесният начин

Простият начин се основава на факта, че изместването на началото на функцията от началото на DLL е постоянна стойност, независима от процеса. Това означава, че ако:

На това се основава технологията за инжектиране на DLL чрез обаждане на LoadLibrary в друг процес.

Ако по някаква причина DLL вече е зареден в процеса, тогава вероятно този метод може да бъде препоръчан дори на най-истинските програмисти. Но ако DLL трябва да бъде специално зареден, тогава, по мое мнение, отново се оказва грозно.:)

Начин за истински програмисти

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

Намиране на експортираната функция в PE файла

Форматът PE е доста сложен, но за щастие не ни е необходим напълно. Ако се интересувате от по-подробно описание, вижте допълнителни източници в края на статията.

Как да стигнете до раздела за експортиране в PE файл

Всеки PE файл започва с DOS заглавка, чийто формат се отразява в структурата IMAGE_DOS_HEADER.

От всички полета на тази структура, ние се интересуваме само от полето e_lfanew, което е изместването от началото на файла (в терминологията на формата PE такива отмествания се наричат ​​RVA - Относителен виртуален адрес) към заглавката PE.

Форматът на заглавката PE е представен от структурата IMAGE_NT_HEADERS (той се дефинира с помощта на препроцесора и в момента съответства на структурата IMAGE_NT_HEADERS32):

От него се интересуваме само от полето OptionalHeader, което се разширява в друга структура:

И отново, ние се нуждаем само от едно поле - DataDirectory, или по-точно, само елементът DataDirectory [IMAGE_DIRECTORY_ENTRY_EXPORT].

Структурата IMAGE_DATA_DIRECTORY описва местоположението в паметта на един от разделите в PE файла. Определя се, както следва:

Елементът DataDirectory [IMAGE_DIRECTORY_ENTRY_EXPORT] принадлежи към раздела за експортиране.

  1. В началото на файла е IMAGE_DOS_HEADER.
  2. В офсет IMAGE_DOS_HEADER: e_lfanew е IMAGE_NT_HEADERS.
  3. IMAGE_NT_HEADERS: OptionalHeader.DataDirectory [IMAGE_DIRECTORY_ENTRY_EXPORT] описва раздела за експортиране. Съдържа RVA и размер на секцията.

Разделът за експортиране започва със структурата IMAGE_EXPORT_DIRECTORY.

Първо, елементите на този масив са от тип WORD и размер от 2 байта.

Според MSDN и Pitrek, последният ред на алгоритъма трябва да изглежда така:

DWORD funcRVA = AddressOfFunctions [funcIndex - Base];

Където Base е основната стойност на поредния ред. Както показва практиката, не е нужно да изваждате Base.

В крайна сметка завърших с три функции. Първият намира раздела за износ:

Втората итерация върху масив от имена на функции, за да намери даденото име:

Третата функция използва първите две и намира желаната функция в посочения DLL в посочения процес:

За оптимизация можете първо да копирате цялата секция за експортиране във вашия процес (размерът на секцията се съхранява в IMAGE_NT_HEADERS: OptionalHeader.DataDirectory [IMAGE_DIRECTORY_ENTRY_EXPORT] .Размер) и след това да го анализирате. Но тъй като няма забележими за окото закъснения, се спрях на текущото изпълнение.

Написах три приложения като пример: aggressor.exe, жертва.exe и insider.dll. Жертвата и вътрешният човек са абсолютно пасивни, всички действия се извършват от агресора. Агресор:

За да работи това наистина, трябва да поставите и трите изпълними файла в една директория.

За изпълнение на изброените действия и в бъдеще в агресора са внедрени следните полезни функции: