45819 (Вызов функции в другом процессе)

2016-07-31СтудИзба

Описание файла

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

Онлайн просмотр документа "45819"

Текст из документа "45819"

Вызов функции в другом процессе

Сергей Холодилов

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 < count; 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 = 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), а потом уже её разбирать. Но, поскольку заметных глазу задержек не возникает, я остановился на текущей реализации.

Пример

Свежие статьи
Популярно сейчас
Зачем заказывать выполнение своего задания, если оно уже было выполнено много много раз? Его можно просто купить или даже скачать бесплатно на СтудИзбе. Найдите нужный учебный материал у нас!
Ответы на популярные вопросы
Да! Наши авторы собирают и выкладывают те работы, которые сдаются в Вашем учебном заведении ежегодно и уже проверены преподавателями.
Да! У нас любой человек может выложить любую учебную работу и зарабатывать на её продажах! Но каждый учебный материал публикуется только после тщательной проверки администрацией.
Вернём деньги! А если быть более точными, то автору даётся немного времени на исправление, а если не исправит или выйдет время, то вернём деньги в полном объёме!
Да! На равне с готовыми студенческими работами у нас продаются услуги. Цены на услуги видны сразу, то есть Вам нужно только указать параметры и сразу можно оплачивать.
Отзывы студентов
Ставлю 10/10
Все нравится, очень удобный сайт, помогает в учебе. Кроме этого, можно заработать самому, выставляя готовые учебные материалы на продажу здесь. Рейтинги и отзывы на преподавателей очень помогают сориентироваться в начале нового семестра. Спасибо за такую функцию. Ставлю максимальную оценку.
Лучшая платформа для успешной сдачи сессии
Познакомился со СтудИзбой благодаря своему другу, очень нравится интерфейс, количество доступных файлов, цена, в общем, все прекрасно. Даже сам продаю какие-то свои работы.
Студизба ван лав ❤
Очень офигенный сайт для студентов. Много полезных учебных материалов. Пользуюсь студизбой с октября 2021 года. Серьёзных нареканий нет. Хотелось бы, что бы ввели подписочную модель и сделали материалы дешевле 300 рублей в рамках подписки бесплатными.
Отличный сайт
Лично меня всё устраивает - и покупка, и продажа; и цены, и возможность предпросмотра куска файла, и обилие бесплатных файлов (в подборках по авторам, читай, ВУЗам и факультетам). Есть определённые баги, но всё решаемо, да и администраторы реагируют в течение суток.
Маленький отзыв о большом помощнике!
Студизба спасает в те моменты, когда сроки горят, а работ накопилось достаточно. Довольно удобный сайт с простой навигацией и огромным количеством материалов.
Студ. Изба как крупнейший сборник работ для студентов
Тут дофига бывает всего полезного. Печально, что бывают предметы по которым даже одного бесплатного решения нет, но это скорее вопрос к студентам. В остальном всё здорово.
Спасательный островок
Если уже не успеваешь разобраться или застрял на каком-то задание поможет тебе быстро и недорого решить твою проблему.
Всё и так отлично
Всё очень удобно. Особенно круто, что есть система бонусов и можно выводить остатки денег. Очень много качественных бесплатных файлов.
Отзыв о системе "Студизба"
Отличная платформа для распространения работ, востребованных студентами. Хорошо налаженная и качественная работа сайта, огромная база заданий и аудитория.
Отличный помощник
Отличный сайт с кучей полезных файлов, позволяющий найти много методичек / учебников / отзывов о вузах и преподователях.
Отлично помогает студентам в любой момент для решения трудных и незамедлительных задач
Хотелось бы больше конкретной информации о преподавателях. А так в принципе хороший сайт, всегда им пользуюсь и ни разу не было желания прекратить. Хороший сайт для помощи студентам, удобный и приятный интерфейс. Из недостатков можно выделить только отсутствия небольшого количества файлов.
Спасибо за шикарный сайт
Великолепный сайт на котором студент за не большие деньги может найти помощь с дз, проектами курсовыми, лабораторными, а также узнать отзывы на преподавателей и бесплатно скачать пособия.
Популярные преподаватели
Добавляйте материалы
и зарабатывайте!
Продажи идут автоматически
5167
Авторов
на СтудИзбе
437
Средний доход
с одного платного файла
Обучение Подробнее