45821 (665165), страница 2

Файл №665165 45821 (API Spying) 2 страница45821 (665165) страница 22016-07-31СтудИзба
Просмтор этого файла доступен только зарегистрированным пользователям. Но у нас супер быстрая регистрация: достаточно только электронной почты!

Текст из файла (страница 2)

Система в целом состоит из четырёх частей:

Функция-шпион.

Механизм установки шпионов.

Функция сбора статистики.

Механизм сбора и отображения статистики.

Функция-шпион

Задачи

Задачи работы функции-шпиона:

Вызвать функцию сбора статистики, каким-то образом сообщив ей, какая отслеживаемая функция вызывается.

Вызвать отслеживаемую функцию.

Ограничения

Ограничения связаны с тем, что отслеживаемая функция должна работать без изменений. Для этого перед её вызовом:

Необходимо привести стек в то же состояние, которое было до начала работы функции-шпиона. Это значит, что, во-первых, нельзя сохранить в стеке какое-нибудь значение для использования после возврата из отслеживаемой функции, во-вторых, нельзя использовать для вызова инструкцию call, так как она добавит в стек адрес возврата (на эту тему см. ниже, в разделе «Получение управления после возврата из отслеживаемой функции»).

Поскольку в принципе параметры могут передаваться и в регистрах, желательно привести регистры в то же состояние, которое было до начала работы функции-шпиона, или хотя бы в максимально близкое.

Код, который надо сгенерировать

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

Оба подхода одинаково просто реализуются, но из-за особенности системы команд Intel x86 ближний вызов/передача управления по абсолютному адресу будет выглядеть примерно так:

; Вызов

mov eax,

call eax

; Передача управления

mov eax,

jmp eax

То есть, как ни старайся, а значение одного регистра (в данном примере регистра eax, но на его месте мог быть каждый) сохранить не удаётся.

Поэтому выбрана версия с относительной адресацией:

pusha ; сохраняем регистры и флаги.

pushf ; Это, конечно, паранойя...

push ; передаём в параметре номер отслеживаемой функции

call

popf ; восстанавливаем флаги

popa ; и регистры

jmp

Поскольку эта функция-шпион заканчивается непосредственным вызовом отслеживаемой функции, она может совместно работать только с методами перехвата, не изменяющими код перехватываемой функции. Это:

перехват через таблицу импорта;

перехват через таблицу экспорта;

перехват GetProcAddress и подмена адреса запрашиваемой функции.

Если вы используете другой метод перехвата (например, замену нескольких начальных байтов на команду jmp), вам придётся немного изменить мой код.

Получение управления после возврата из отслеживаемой функции

Если по каким-то причинам вам очень нужно получить возвращаемое значение отслеживаемой функции, или вы хотите измерить время её выполнения, или что-то ещё, недоступное моему пониманию, вы всё-таки можете написать функцию-шпион так, чтобы она использовала call для вызова отслеживаемой функции и получала управления после её завершения.

Для этого нужно:

Удалить из стека старый адрес возврата.

ПРИМЕЧАНИЕ

А если функция вызвана дальним вызовом, то (сюрприз!) адрес возврата будет занимать 6 байт. Хуже того, новый адрес тоже должен быть шестибайтным, так как отслеживаемая функция очень на это рассчитывает. Вряд ли вы встретитесь с такой ситуацией в Windows, но про другие ОС я ничего сказать не могу.

Где-то сохранить его на время вызова отслеживаемой функции.

Вызвать функцию.

Получить/измерить/.. то, что вы хотели.

Вернуть управление по старому адресу.

Ключевым вопросом этого алгоритма является: «где же это где-то, в котором можно сохранить адрес возврата?» Стек менять нельзя, поэтому он отпадает. Хранить в регистрах тоже нельзя: те регистры, которые могут измениться после вызова функции, может изменить отслеживаемая функция, и данные пропадут, а те регистры, которые не должны меняться после вызова, нельзя менять нам, так как восстановить их мы не сумеем – негде сохранить их старые значения :)

Остаётся только хранение в глобальной области памяти. Так как приложение может быть многопоточным, доступ к памяти нужно синхронизировать, и отдельно хранить данные для каждого потока. Так как возможна рекурсия, необходимо хранить не один адрес возврата, а стек адресов… И, несмотря на все эти предосторожности, что будет, если в отслеживаемой функции произойдёт исключение и начнётся развёртывание стека? Правильно, будет очень плохо…

В общем, это путь для людей, крепких духом и готовых к испытаниям. Далее в статье он не рассматривается.

Механизм установки шпионов

Алгоритм установки одной функции-шпиона:

Генерируется функция-шпион, при генерации устанавливается её номер, адрес отслеживаемой функции и адрес функции сбора статистики.

Перехватывается отслеживаемая функция, теперь вместо неё приложением должна вызываться функция-шпион.

Где-то сохраняется информация, о том, что перехвачена функция с таким-то именем и ей сопоставлен такой-то номер. Эта информация будет использована при вызове функции сбора статистики.

Очевидно, что этот алгоритм никак не зависит от прототипа/формата вызова/.. отслеживаемой функции, и может быть без изменений применён для любого количества функций. Тем не менее, рассмотрим два случая.

Отслеживание вызовов функций динамически загружаемых dll

Это самое простое. Поскольку адреса таких функций приложение получает через GetProcAddress, достаточно просто перехватить GetProcAddress и производить описанную выше процедуру для всех запрашиваемых функций.

Отслеживание всех вызовов

Общая идея: пройтись по таблицам импорта загруженных модулей и, не особо задумываясь, перехватить все упомянутые там функции. Кроме того, нужно позаботиться о GetProcAddress (см. предыдущий пункт) и о ещё не загруженных модулях: их таблицы импорта тоже необходимо обработать. Чтобы не пропустить появление новых модулей, можно, например, перехватить все версии LoadLibrary[Ex]A/W.

Просто, правда? Просто, но, к сожалению, в таком виде работать, скорее всего, не будет.

ПРЕДУПРЕЖДЕНИЕ

Этот вариант я так и не реализовал (незачем было), поэтому о его неизбежных маленьких особенностях почти ничего не знаю. Мои попытки поразмышлять представлены ниже, но практики за ними не стоит, и гарантировать отсутствие проблем я не могу. Сожалею.

Проблема этого подхода заключается в почти гарантированном возникновении бесконечной рекурсии. Например, пусть collectStatistic записывает данные в файл при помощи функции WriteFile. Если эта функция оказалась перехвачена и в вашем модуле, то попытка записи приведёт к вызову вашей функции-шпиона, которая вызовет collectStatistic и т.д. пока не кончится место в стеке.

Ладно, вы поняли свою ошибку и больше не меняете таблицу импорта своего модуля. Но дело в том, что для реализации WriteFile kernel32.dll вызывает функцию NtWriteFile из ntdll.dll. А, поскольку таблицу импорта kernel32.dll вы изменили, опять вызывается функция-шпион, которая вызывает colleclStatistic и всё начинается заново.

Отсюда вывод: при проведении перехвата необходимо пропустить модули, которые вы сами прямо или косвенно используете. Идеально было бы менять таблицы импорта только в «нестандартных» модулях, так как, скорее всего, именно это вам и нужно: вряд ли вас интересует, какие функции ntdll.dll вызываются во время вызова WriteFile, обычно достаточно просто знать, что приложение вызвало WriteFile. Определять нестандартные модули можно разными способами, мне пришли в голову следующие:

По каталогу, в котором лежит файл.

По дате создания файла (системные файлы обычно имеют вполне определённые даты создания).

По фиксированному списку имён.

Кроме того, всегда есть радикальное решение: написать графический интерфейс и взвалить эту задачу на пользователя. :)

Функция сбора статистики

В соответствии с тем, как она используется функциями-шпионами, функция сбора статистики должна иметь следующие характеристики:

Принимает один четырёхбайтный параметр, передаваемый через стек.

Не возвращает значение (во всяком случае, оно игнорируется).

Сама очищает стек.

Очевидно, как-то собирает какую-то статистику. Как именно и какую, пока не важно.

На C++ это реализуется примерно так:

void __stdcall collectStatistic(unsigned long n)

{

// Что угодно, например такое

functions[n].count++;

printf(("called %s (%d)\n", functions[n].name.c_str(), functions[n].count);

}

В этом примере статистическая информация состоит из имени функции и количества вызовов, всё это хранится в массиве functions, отображением статистики занимается само исследуемое приложение.

Механизм сбора и отображения статистики

Что собирать

Потенциально, функция сборки статистики может для каждого вызова сохранять следующие параметры:

Имя функции.

Имя модуля.

Имя модуля, из которого произошёл вызов.

Идентификатор текущего потока.Время вызова.

Дамп стека.

Состояние регистров процессора

и так далее.

В общем, уровень детализации может быть очень разным и зависит от задачи.

Политика отображения

Два принципиально разных подхода:

Данные доступны в реальном времени (посредством какого-нибудь GUI).

Данные доступны после завершения исследуемого приложения (в файле на диске).

Оба подхода имеют свои плюсы и минусы: с точки зрения получения данных, очевидно, что первый обладает всеми возможностями второго (если уж данные отображаются, параллельно сохранять их в лог не проблема), а, с точки зрения влияния на исследуемое приложение, второй может получиться гораздо мягче, и в какой-то ситуации это может оказаться критичным. Кроме того, второй подход может оказаться значительно проще в реализации.

ПРИМЕЧАНИЕ

Например, если данные можно в течение всего времени выполнения хранить в памяти, а запись на диск сделать только в самом конце (в DllMain). Или, чуть более интеллектуально, попытаться записывать/передавать данные только в те моменты, когда исследуемое приложение само обращается к диску.

Но, поскольку первый подход гораздо эффектнее (real-time, on-line, и даже мультимедиа, если постараться, – все эти слова можно обоснованно употребить в пресс-релизе :) ), далее рассматривается в основном он.

Где хранить и как отображать статистику

Есть три варианта реализации «сбора и отображения»:

Данные хранятся и отображаются dll, внедрённой в исследуемое приложение.

Данные хранятся dll, внедрённой в исследуемое приложение, для отображения она пересылает их внешнему приложению.

И хранением, и отображением занимается внешнее приложение, dll просто пересылает ему данные по мере поступления.

Наиболее интересен последний вариант (рассматриваем отображение в реальном времени), так как за счёт выноса части логики во внешнее приложение dll получается относительно простой, в результате чего снижается риск случайно испортить что-нибудь в исследуемом приложении, упрощается отладка и повышается надёжность системы в целом.

Реализация

Ограничимся простым случаем:

Отслеживаем только вызовы функций, адреса которых исследуемое приложение получает через GetProcAddress.

Сохраняем только имена функций и модулей.

Отображаем данные в реальном времени. В качестве GUI выступает консоль. :)

Данные хранятся и отображаются во внешнем приложении.

Генерация функции-шпиона

Основную работу по генерации выполняют следующие несложные классы:

// Класс, позволяющий работать с относительными адресами.

// Позволяет копировать относительные адреса, сохраняя их корректными.

struct relative_address

{

relative_address() : value(0) {}

// Корректно копирует относительный адрес.

relative_address(const relative_address& a)

{

// Копирование со смещением на расстояние между указателями.

value = (unsigned long)a.value

+ (unsigned long)&a.value

- (unsigned long)&value;

}

// Корректно присваивает относительный адрес.

relative_address& operator = (const relative_address& a)

{

if (this != &a)

{

// Копирование со смещением на расстояние между указателями.

value = (unsigned long)a.value

+ (unsigned long)&a.value

- (unsigned long)&value;

}

return *this;

}

// Устанавливает относительный адрес соответствующим указанному абсолютному.

void set_absolute(void* a)

{

// Относительный адрес отсчитывается от начала следующей инструкции.

// Поскольку в тех инструкциях, в которые входит относительный адрес,

// он находится в конце, начало следующей инструкции - это конец адреса.

value = (unsigned long)a - (unsigned long)&value - sizeof(value);

}

unsigned long value;

};

// Класс, упрощающий работу с однобайтной командой.

template

struct one_byte_command

{

one_byte_command() : code(c) {}

unsigned char code;

};

// Класс, упрощающий работу с командой с однобайтным кодом

// и 4-байтным операндом.

template

struct one_byte_value_command

{

one_byte_value_command() : code(c) {}

unsigned char code;

unsigned long value;

};

// Класс, упрощающий работу с командой с однобайтным кодом

// и относительным адресом

template

struct one_byte_rel_address_command

{

one_byte_rel_address_command() : code(c) {}

unsigned char code;

relative_address address;

};

С их помощью можно определить классы для команд процессора, а из них уже собрать функцию. Например, так:

// Команда pusha

typedef one_byte_command pusha;

// Команда pushf

typedef one_byte_command pushf;

// Команда push xxx

typedef one_byte_value_command push_value;

// Команда popa

typedef one_byte_command popa;

// Команда popf

typedef one_byte_command popf;

// Команда call xxx

typedef one_byte_rel_address_command call_address;

// Команда jmp xxx

typedef one_byte_rel_address_command jmp_address;

//

// Функция-шпион, собранная из этих команд

struct spy_function

{

pusha c1;

pushf c2;

push_value number;

call_address statistic;

popf c5;

popa c6;

jmp_address func;

};

ПРИМЕЧАНИЕ

Естественно, чтобы это работало, необходимо при объявлении классов установить выравнивание данных по границе одного байта. В Visual C++ это делается так:

#pragma pack(1, push)

… // здесь все объявления

#pragma pack(pop)

Как пользоваться получившимся в итоге классом spy_function, продемонстрировано ниже.

myGetProcAddress

Характеристики

Тип файла
Документ
Размер
229,44 Kb
Материал
Тип материала
Учебное заведение
Неизвестно

Список файлов реферата

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