Открыть список
Как стать автором
Обновить

Комментарии 26

Приватные методы таким образом тоже можно перехватывать?
Да, любые методы, которые можно получить через reflection.
На основе его была разработана библиотека .NET, с использованием которой перехват .NET функций становится достаточно простым в применении.

Была разработана и куда дальше делась? В опенсорсе лежит, за деньги продаётся?
Пока, особо не афишируясь, используется в промышленных разработках, где необходимо перехватывать функции .NET.
Например, при перехвате обращений к базе данных SqlServer для обеспечения безопасного соединения.
В планах написать такую же библиотеку для Mono под Linux-ые платформы.
Вы даже близко не представляете, сколько благодарности на вас свалится если вы её выложите на гитхаб

Написание целого класса для перехвата вызова одного метода выглядит очень громоздко. Было бы удобнее ставить хук таким образом:


using (Hook.On(type, methodName, bindingFlags))
{
  // Some code
}

А для перехвата нескольких методов можно было бы создать какой-нибудь builder c fluent синтаксисом.

Да, для простоты в примере приведен класс для одного метода.
Но в этом же классе можно перехватить сколько угодно методов для произвольных классов.
Для этого в функции GetTypes нужно указать все требуемые классы, а в функции OnLoad
(в зависимости от принятого класса) перехватить сколько угодно его функций.
x86:
mov eax, pMethodDesc
mov ebp, ebp
jmp ThePreStub


Наверное, все-таки
mov eax, pMethodDesc 
mov ebp, esp
jmp ThePreStub

Пост интересный. В закладки.
Нет, именно(!!!) mov ebp, ebp.
Команда смысла не несет, а используется в качестве идентифицирующего признака переходника.
Что насчет .NET Core? Какова вероятность что все это отвалиться в следующих версиях?
Способ вызова через переходники не меняется с самого начала CLR (указанный способ можно мониторить в исходниках CLR на github).
Единственное, что приходится учитывать — не изменилась ли реализация ThePreStub, поскольку способ поиска PrestubWorker основывается на том, что ThePreStub не вызывает других функций, кроме PrestubWorker. Функции ThePreStub нет в исходниках (поскольку она реализована на ассемблере), приходится проверять на практике.
В процессе выполнения при анализе памяти указанный адрес можно явно прочитать по смещению 1 (для x86) или 2 (для x64) переходника с учетом разрядности процессора.

Может наоборот, 2 для х86 и 1 для х64?
Команда mov eax, imm для x86 содержит 4-байтовый операнд imm по смещению 1 от начала команды (которая занимает 5 байт).
Команда mov rax, imm для x64 содержит 8-байтовый операнд imm по смещению 2 от начала команды (которая занимает 10 байт).
Ясно. Просто как раз на днях столкнулся с кодом, который особо не понял, как получает смещения. Можете кстати прокомментировать? Особенно дебаг версию :)
http://stackoverflow.com/questions/7299097/dynamically-replace-the-contents-of-a-c-sharp-method/36415711#36415711
1) Отличия следующих команд для различных платформ только в размерности адреса.

int* inj = (int*)methodToInject.MethodHandle.Value.ToPointer() + 2;
long* inj = (long*)methodToInject.MethodHandle.Value.ToPointer()+1;

только в размерности адреса.

2) Следующие строки (скорее всего) получают адреса слотов в таблице MethodTable (см. приведенную картинку)

int* inj = (int*)methodToInject.MethodHandle.Value.ToPointer() + 2;
int* tar = (int*)methodToReplace.MethodHandle.Value.ToPointer() + 2;

3) Следующие строки определяют адрес переходника FixupPrecode после компиляции для двух функций

byte* injInst = (byte*)*inj;
byte* tarInst = (byte*)*tar;

4) Следующие строки

int* injSrc = (int*)(injInst + 1);
int* tarSrc = (int*)(tarInst + 1);

в командах jmp NativeCode (см. описание FixupPrecode после компиляции) находят смещение сгенерированного кода относительно окончания самих команд (которые занимают 5 байт);

5) Следующая строка вычисляет относительное смещение сгенерированного внедряемого кода относительно окончания команды jmp NativeCode для перехватываемого кода(!!!) и записывает его в команду jmp NativeCode для перехватываемого кода

*tarSrc = (((int)injInst + 5) + *injSrc) — ((int)tarInst + 5);

Таким образом, в переходнике команда jmp NativeCode(Source) заменяется на jmp NativeCode(Inject)

6) В Release-версии адрес напрямую заменяется в слотах таблицы MethodTable;
Замечу, правда, что приведенный по ссылке метод не является переносимым (т.е. может работать не всегда). И вот почему:

1) Иногда поток управления проходит через переходник, минуя адрес в таблице слотов;
2) Способ определения адреса слота в таблице MethodTable может изменяться с каждой версией CLR;
3) Вызов

RuntimeHelpers.PrepareMethod(methodToReplace.MethodHandle);

не всегда гарантирует выполнение JIT-компиляции;

4) Переходники могут не быть FixupPrecode (особенно для NGen-модулей).
Великолепная статья! Как раз на этой неделе искал источники с описанием реализации переходников. Скажите, пользовались ли Вы какими-то источниками кроме анализа самого исходного кода? И если не секрет, какими? Я перелопатил целую кучу книг затрагивающих эти темы, в т.ч. SSCLI 2.0 INTERNALS, и не нашёл не то что упоминания структуры FixupPrecode, а даже четкого деления переходников на типы. Везде тема переходников затрагивается вскользь и ограничивается общими словами. А у Вас всё так ёмко изложено, со схемой связывания SLOT+MethodDesc+Precode и даже с указанием выводов «Указанный переходник был разработан с целью оптимизации использования памяти.». Где же об этом можно найти информацию?
Спасибо! При изучении внутреннего строения CLR я в основном пользовался анализом ассемблерного кода. Все переходники были изучены таким образом, но обобщить их (особенно понять логику FixupPrecode) мне помогла следующая ссылка:
https://github.com/dotnet/coreclr/blob/775003a4c72f0acc37eab84628fcef541533ba4e/Documentation/botr/method-descriptor.md
ого, спасибо большое! Я и не знал, что в репозитории есть такое подробное описание
Да, только оно датируется 2006 годом. И многие вещи там устарели.
Большое спасибо за статью. Я лишь мог подозревать, что такое должно быть возможно.
Много интересной информации о внутреннем устройстве CLR

Только созрел вопрос, ради любопытства только:
Работоспособность приведенного алгоритма была неоднократно проверена на практике (в том числе, в промышленных разработках) на различных версиях .NET и аппаратных платформах.


Если не секрет, в какой сфере получилось использовать эти навыки? Чертовски интересно услышать какой-то пример.
Используется при защите соединений с базами данных (сфера информационных технологий). В двух словах: при перехвате функции создания соединения с MSSQL-сервером выполняется функциональность дополнительной двухфакторной аутентификации для последующего зашифрования/расшифрования передаваемых данных «на-лету».
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.